@@ -4,13 +4,15 @@ use crate::{email::Email, models::User, worker::Environment, Emails};
4
4
use anyhow:: anyhow;
5
5
use chrono:: SecondsFormat ;
6
6
use crates_io_worker:: BackgroundJob ;
7
+ use diesel:: dsl:: IntervalDsl ;
7
8
use diesel:: {
8
- dsl:: now, Connection , ExpressionMethods , NullableExpressionMethods , PgConnection , RunQueryDsl ,
9
+ dsl:: now, BoolExpressionMethods , Connection , ExpressionMethods , NullableExpressionMethods ,
10
+ PgConnection , QueryDsl , QueryResult , RunQueryDsl , SelectableHelper ,
9
11
} ;
10
12
use std:: sync:: Arc ;
11
13
12
14
/// The threshold in days for the expiry notification.
13
- const EXPIRY_THRESHOLD : i64 = 3 ;
15
+ const EXPIRY_THRESHOLD : chrono :: TimeDelta = chrono :: TimeDelta :: days ( 3 ) ;
14
16
15
17
/// A job responsible for monitoring the status of a token.
16
18
/// It checks if the token is about to reach its expiry date.
@@ -38,7 +40,7 @@ impl BackgroundJob for CheckAboutToExpireToken {
38
40
// Check if the token is about to expire and send a notification if it is.
39
41
fn check ( emails : & Emails , conn : & mut PgConnection ) -> anyhow:: Result < ( ) > {
40
42
info ! ( "Checking if tokens are about to expire" ) ;
41
- let expired_tokens = ApiToken :: find_tokens_expiring_within_days ( conn, EXPIRY_THRESHOLD ) ?;
43
+ let expired_tokens = find_tokens_expiring_within_days ( conn, EXPIRY_THRESHOLD ) ?;
42
44
// Batch send notifications in transactions.
43
45
const BATCH_SIZE : usize = 100 ;
44
46
for chunk in expired_tokens. chunks ( BATCH_SIZE ) {
@@ -73,6 +75,28 @@ fn check(emails: &Emails, conn: &mut PgConnection) -> anyhow::Result<()> {
73
75
Ok ( ( ) )
74
76
}
75
77
78
+ /// Find all tokens that are not revoked and will expire within the specified number of days.
79
+ pub fn find_tokens_expiring_within_days (
80
+ conn : & mut PgConnection ,
81
+ days_until_expiry : chrono:: TimeDelta ,
82
+ ) -> QueryResult < Vec < ApiToken > > {
83
+ api_tokens:: table
84
+ . filter ( api_tokens:: revoked. eq ( false ) )
85
+ . filter (
86
+ api_tokens:: expired_at
87
+ . is_not_null ( )
88
+ . and ( api_tokens:: expired_at. assume_not_null ( ) . gt ( now) ) // Ignore already expired tokens
89
+ . and (
90
+ api_tokens:: expired_at
91
+ . assume_not_null ( )
92
+ . lt ( now + days_until_expiry. num_days ( ) . day ( ) ) ,
93
+ ) ,
94
+ )
95
+ . filter ( api_tokens:: expiry_notification_at. is_null ( ) )
96
+ . select ( ApiToken :: as_select ( ) )
97
+ . get_results ( conn)
98
+ }
99
+
76
100
#[ derive( Debug , Clone ) ]
77
101
struct ExpiryNotificationEmail < ' a > {
78
102
name : & ' a str ,
@@ -130,13 +154,13 @@ mod tests {
130
154
api_tokens:: user_id. eq ( user. id ) ,
131
155
api_tokens:: name. eq ( "test_token" ) ,
132
156
api_tokens:: token. eq ( token. hashed ( ) ) ,
133
- api_tokens:: expired_at. eq ( now. nullable ( ) + ( EXPIRY_THRESHOLD - 1 ) . day ( ) ) ,
157
+ api_tokens:: expired_at. eq ( now. nullable ( ) + ( EXPIRY_THRESHOLD . num_days ( ) - 1 ) . day ( ) ) ,
134
158
) )
135
159
. returning ( ApiToken :: as_returning ( ) )
136
160
. get_result ( & mut conn) ?;
137
161
138
162
// Insert a few tokens that are not set to expire.
139
- let not_expired_offset = EXPIRY_THRESHOLD + 1 ;
163
+ let not_expired_offset = EXPIRY_THRESHOLD . num_days ( ) + 1 ;
140
164
for i in 0 ..3 {
141
165
let token = PlainToken :: generate ( ) ;
142
166
diesel:: insert_into ( api_tokens:: table)
0 commit comments