@@ -14,6 +14,11 @@ use std::sync::Arc;
14
14
/// The threshold in days for the expiry notification.
15
15
const EXPIRY_THRESHOLD : chrono:: TimeDelta = chrono:: TimeDelta :: days ( 3 ) ;
16
16
17
+ /// The maximum number of tokens to check.
18
+ /// To avoid sending too many emails and submitting to many transactions, we limit the number of
19
+ /// tokens to check.
20
+ const MAX_ROWS : i64 = 10000 ;
21
+
17
22
/// A job responsible for monitoring the status of a token.
18
23
/// It checks if the token is about to reach its expiry date.
19
24
/// If the token is about to expire, the job triggers a notification.
@@ -37,35 +42,36 @@ impl BackgroundJob for CheckAboutToExpireToken {
37
42
. map_err ( |err| anyhow ! ( err. to_string( ) ) ) ?
38
43
}
39
44
}
45
+
40
46
// Check if the token is about to expire and send a notification if it is.
41
47
fn check ( emails : & Emails , conn : & mut PgConnection ) -> anyhow:: Result < ( ) > {
42
48
info ! ( "Checking if tokens are about to expire" ) ;
43
49
let expired_tokens = find_tokens_expiring_within_days ( conn, EXPIRY_THRESHOLD ) ?;
44
- // Batch send notifications in transactions.
45
- const BATCH_SIZE : usize = 100 ;
46
- for chunk in expired_tokens. chunks ( BATCH_SIZE ) {
50
+ if expired_tokens. len ( ) == MAX_ROWS as usize {
51
+ warn ! ( "The maximum number of API tokens per query has been reached. More API tokens might be processed on the next run." ) ;
52
+ }
53
+ for token in & expired_tokens {
47
54
conn. transaction ( |conn| {
48
- for token in chunk {
49
- // Send notification.
50
- let user = User :: find ( conn, token. user_id ) ?;
51
- let Some ( recipient) = user. email ( conn) ? else {
52
- return Err ( anyhow ! ( "No address found" ) ) ;
53
- } ;
54
- let email = ExpiryNotificationEmail {
55
- name : & user. gh_login ,
56
- token_name : & token. name ,
57
- expiry_date : token. expired_at . unwrap ( ) . and_utc ( ) ,
58
- } ;
59
- match emails. send ( & recipient, email) {
60
- Ok ( _) => {
61
- // Update the token to prevent duplicate notifications.
62
- diesel:: update ( token)
63
- . set ( api_tokens:: expiry_notification_at. eq ( now. nullable ( ) ) )
64
- . execute ( conn) ?;
65
- }
66
- Err ( e) => {
67
- error ! ( "Failed to send email: {:?} to {}" , e, recipient) ;
68
- }
55
+ // Send notification.
56
+ let user = User :: find ( conn, token. user_id ) ?;
57
+ let Some ( recipient) = user. email ( conn) ? else {
58
+ return Err ( anyhow ! ( "No address found" ) ) ;
59
+ } ;
60
+ let email = ExpiryNotificationEmail {
61
+ name : & user. gh_login ,
62
+ token_name : & token. name ,
63
+ expiry_date : token. expired_at . unwrap ( ) . and_utc ( ) ,
64
+ } ;
65
+ match emails. send ( & recipient, email) {
66
+ Ok ( _) => {
67
+ // Update the token to prevent duplicate notifications.
68
+ diesel:: update ( token)
69
+ . set ( api_tokens:: expiry_notification_at. eq ( now. nullable ( ) ) )
70
+ . execute ( conn) ?;
71
+ }
72
+ Err ( e) => {
73
+ error ! ( ?e, ?recipient, "Failed to send notification" ) ;
74
+ return Err ( anyhow ! ( "Failed to send notification: {}" , e) ) ;
69
75
}
70
76
}
71
77
Ok :: < _ , anyhow:: Error > ( ( ) )
@@ -74,7 +80,6 @@ fn check(emails: &Emails, conn: &mut PgConnection) -> anyhow::Result<()> {
74
80
75
81
Ok ( ( ) )
76
82
}
77
-
78
83
/// Find all tokens that are not revoked and will expire within the specified number of days.
79
84
pub fn find_tokens_expiring_within_days (
80
85
conn : & mut PgConnection ,
@@ -94,6 +99,8 @@ pub fn find_tokens_expiring_within_days(
94
99
)
95
100
. filter ( api_tokens:: expiry_notification_at. is_null ( ) )
96
101
. select ( ApiToken :: as_select ( ) )
102
+ . order_by ( api_tokens:: expired_at. asc ( ) ) // The most urgent tokens first
103
+ . limit ( MAX_ROWS )
97
104
. get_results ( conn)
98
105
}
99
106
0 commit comments