@@ -2,13 +2,23 @@ use crate::schema::crates;
2
2
use crate :: storage:: FeedId ;
3
3
use crate :: worker:: Environment ;
4
4
use anyhow:: anyhow;
5
+ use chrono:: Duration ;
5
6
use crates_io_worker:: BackgroundJob ;
6
7
use diesel:: prelude:: * ;
7
8
use std:: sync:: Arc ;
8
9
9
10
#[ derive( Serialize , Deserialize ) ]
10
11
pub struct SyncCratesFeed ;
11
12
13
+ /// Items younger than this will always be included in the feed.
14
+ const ALWAYS_INCLUDE_AGE : Duration = Duration :: minutes ( 60 ) ;
15
+
16
+ /// The number of items to include in the feed.
17
+ ///
18
+ /// If there are less than this number of items in the database, the feed will
19
+ /// contain fewer items. If there are more items in the database that are
20
+ /// younger than [`ALWAYS_INCLUDE_AGE`], all of them will be included in
21
+ /// the feed.
12
22
const NUM_ITEMS : i64 = 50 ;
13
23
14
24
impl BackgroundJob for SyncCratesFeed {
@@ -76,7 +86,26 @@ impl BackgroundJob for SyncCratesFeed {
76
86
}
77
87
}
78
88
89
+ /// Load the latest crates from the database.
90
+ ///
91
+ /// This function will load all crates from the database that are younger
92
+ /// than [`ALWAYS_INCLUDE_AGE`]. If there are less than [`NUM_ITEMS`] crates
93
+ /// then the list will be padded with older crates until [`NUM_ITEMS`] are
94
+ /// returned.
79
95
fn load_new_crates ( conn : & mut PgConnection ) -> QueryResult < Vec < NewCrate > > {
96
+ let threshold_dt = chrono:: Utc :: now ( ) . naive_utc ( ) - ALWAYS_INCLUDE_AGE ;
97
+
98
+ let new_crates = crates:: table
99
+ . filter ( crates:: created_at. gt ( threshold_dt) )
100
+ . order ( crates:: created_at. desc ( ) )
101
+ . select ( NewCrate :: as_select ( ) )
102
+ . load ( conn) ?;
103
+
104
+ let num_new_crates = new_crates. len ( ) ;
105
+ if num_new_crates as i64 >= NUM_ITEMS {
106
+ return Ok ( new_crates) ;
107
+ }
108
+
80
109
crates:: table
81
110
. order ( crates:: created_at. desc ( ) )
82
111
. select ( NewCrate :: as_select ( ) )
@@ -132,3 +161,68 @@ impl NewCrate {
132
161
}
133
162
}
134
163
}
164
+
165
+ #[ cfg( test) ]
166
+ mod tests {
167
+ use super :: * ;
168
+ use chrono:: NaiveDateTime ;
169
+ use crates_io_test_db:: TestDatabase ;
170
+ use insta:: assert_debug_snapshot;
171
+
172
+ #[ test]
173
+ fn test_load_version_updates ( ) {
174
+ crate :: util:: tracing:: init_for_test ( ) ;
175
+
176
+ let db = TestDatabase :: new ( ) ;
177
+ let mut conn = db. connect ( ) ;
178
+
179
+ let now = chrono:: Utc :: now ( ) . naive_utc ( ) ;
180
+
181
+ let new_crates = assert_ok ! ( load_new_crates( & mut conn) ) ;
182
+ assert_eq ! ( new_crates. len( ) , 0 ) ;
183
+
184
+ // If there are less than NUM_ITEMS crates, they should all be returned
185
+ create_crate ( & mut conn, "foo" , now - Duration :: days ( 123 ) ) ;
186
+ create_crate ( & mut conn, "bar" , now - Duration :: days ( 110 ) ) ;
187
+ create_crate ( & mut conn, "baz" , now - Duration :: days ( 100 ) ) ;
188
+ create_crate ( & mut conn, "qux" , now - Duration :: days ( 90 ) ) ;
189
+
190
+ let new_crates = assert_ok ! ( load_new_crates( & mut conn) ) ;
191
+ assert_eq ! ( new_crates. len( ) , 4 ) ;
192
+ assert_debug_snapshot ! ( new_crates. iter( ) . map( |u| & u. name) . collect:: <Vec <_>>( ) ) ;
193
+
194
+ // If there are more than NUM_ITEMS crates, only the most recent NUM_ITEMS should be returned
195
+ for i in 1 ..=NUM_ITEMS {
196
+ let name = format ! ( "crate-{i}" ) ;
197
+ let publish_time = now - Duration :: days ( 90 ) + Duration :: hours ( i) ;
198
+ create_crate ( & mut conn, & name, publish_time) ;
199
+ }
200
+
201
+ let new_crates = assert_ok ! ( load_new_crates( & mut conn) ) ;
202
+ assert_eq ! ( new_crates. len( ) as i64 , NUM_ITEMS ) ;
203
+ assert_debug_snapshot ! ( new_crates. iter( ) . map( |u| & u. name) . collect:: <Vec <_>>( ) ) ;
204
+
205
+ // But if there are more than NUM_ITEMS crates that are younger than ALWAYS_INCLUDE_AGE, all of them should be returned
206
+ for i in 1 ..=( NUM_ITEMS + 10 ) {
207
+ let name = format ! ( "other-crate-{i}" ) ;
208
+ let publish_time = now - Duration :: minutes ( 30 ) + Duration :: seconds ( i) ;
209
+ create_crate ( & mut conn, & name, publish_time) ;
210
+ }
211
+
212
+ let new_crates = assert_ok ! ( load_new_crates( & mut conn) ) ;
213
+ assert_eq ! ( new_crates. len( ) as i64 , NUM_ITEMS + 10 ) ;
214
+ assert_debug_snapshot ! ( new_crates. iter( ) . map( |u| & u. name) . collect:: <Vec <_>>( ) ) ;
215
+ }
216
+
217
+ fn create_crate ( conn : & mut PgConnection , name : & str , publish_time : NaiveDateTime ) -> i32 {
218
+ diesel:: insert_into ( crates:: table)
219
+ . values ( (
220
+ crates:: name. eq ( name) ,
221
+ crates:: created_at. eq ( publish_time) ,
222
+ crates:: updated_at. eq ( publish_time) ,
223
+ ) )
224
+ . returning ( crates:: id)
225
+ . get_result ( conn)
226
+ . unwrap ( )
227
+ }
228
+ }
0 commit comments