2
2
3
3
use std:: cmp:: Reverse ;
4
4
5
+ use diesel:: connection:: DefaultLoadingMode ;
6
+ use indexmap:: IndexMap ;
7
+
5
8
use crate :: controllers:: frontend_prelude:: * ;
9
+ use crate :: controllers:: helpers:: pagination:: { encode_seek, Page , PaginationOptions } ;
6
10
7
11
use crate :: models:: { Crate , CrateVersions , User , Version , VersionOwnerAction } ;
8
12
use crate :: schema:: { users, versions} ;
9
13
use crate :: util:: errors:: crate_not_found;
10
14
use crate :: views:: EncodableVersion ;
11
15
12
16
/// Handles the `GET /crates/:crate_id/versions` route.
13
- // FIXME: Not sure why this is necessary since /crates/:crate_id returns
14
- // this information already, but ember is definitely requesting it
15
- pub async fn versions ( state : AppState , Path ( crate_name) : Path < String > ) -> AppResult < Json < Value > > {
17
+ pub async fn versions (
18
+ state : AppState ,
19
+ Path ( crate_name) : Path < String > ,
20
+ req : Parts ,
21
+ ) -> AppResult < Json < Value > > {
16
22
spawn_blocking ( move || {
17
23
let conn = & mut * state. db_read ( ) ?;
18
24
@@ -21,27 +27,217 @@ pub async fn versions(state: AppState, Path(crate_name): Path<String>) -> AppRes
21
27
. optional ( ) ?
22
28
. ok_or_else ( || crate_not_found ( & crate_name) ) ?;
23
29
24
- let mut versions_and_publishers: Vec < ( Version , Option < User > ) > = krate
25
- . all_versions ( )
26
- . left_outer_join ( users:: table)
27
- . select ( ( versions:: all_columns, users:: all_columns. nullable ( ) ) )
28
- . load ( conn) ?;
30
+ let mut pagination = None ;
31
+ let params = req. query ( ) ;
32
+ // To keep backward compatibility, we paginate only if per_page is provided
33
+ if params. get ( "per_page" ) . is_some ( ) {
34
+ pagination = Some (
35
+ PaginationOptions :: builder ( )
36
+ . enable_seek ( true )
37
+ . enable_pages ( false )
38
+ . gather ( & req) ?,
39
+ ) ;
40
+ }
29
41
30
- versions_and_publishers
31
- . sort_by_cached_key ( |( version, _) | Reverse ( semver:: Version :: parse ( & version. num ) . ok ( ) ) ) ;
42
+ // Sort by semver by default
43
+ let versions_and_publishers = match params. get ( "sort" ) . map ( |s| s. to_lowercase ( ) ) . as_deref ( )
44
+ {
45
+ Some ( "date" ) => list_by_date ( & krate, pagination. as_ref ( ) , & req, conn) ?,
46
+ _ => list_by_semver ( & krate, pagination. as_ref ( ) , & req, conn) ?,
47
+ } ;
32
48
33
49
let versions = versions_and_publishers
50
+ . data
34
51
. iter ( )
35
52
. map ( |( v, _) | v)
36
53
. cloned ( )
37
54
. collect :: < Vec < _ > > ( ) ;
38
55
let versions = versions_and_publishers
56
+ . data
39
57
. into_iter ( )
40
58
. zip ( VersionOwnerAction :: for_versions ( conn, & versions) ?)
41
59
. map ( |( ( v, pb) , aas) | EncodableVersion :: from ( v, & crate_name, pb, aas) )
42
60
. collect :: < Vec < _ > > ( ) ;
43
61
44
- Ok ( Json ( json ! ( { "versions" : versions } ) ) )
62
+ Ok ( Json ( match pagination {
63
+ Some ( _) => json ! ( { "versions" : versions, "meta" : versions_and_publishers. meta } ) ,
64
+ None => json ! ( { "versions" : versions } ) ,
65
+ } ) )
45
66
} )
46
67
. await
47
68
}
69
+
70
+ fn list_by_date (
71
+ krate : & Crate ,
72
+ options : Option < & PaginationOptions > ,
73
+ req : & Parts ,
74
+ conn : & mut PgConnection ,
75
+ ) -> AppResult < PaginatedVersionsAndPublishers > {
76
+ let mut query = krate
77
+ . all_versions ( )
78
+ . left_outer_join ( users:: table)
79
+ . select ( ( versions:: all_columns, users:: all_columns. nullable ( ) ) ) ;
80
+
81
+ if let Some ( options) = options {
82
+ if let Page :: Seek ( ref seek) = options. page {
83
+ let ( created_at, id) = seek. decode :: < seek:: Date > ( ) . map ( |s| ( s. 0 , s. 1 ) ) ?;
84
+ query = query. filter (
85
+ versions:: created_at
86
+ . eq ( created_at)
87
+ . and ( versions:: id. lt ( id) )
88
+ . or ( versions:: created_at. lt ( created_at) ) ,
89
+ )
90
+ }
91
+ query = query. limit ( options. per_page ) ;
92
+ }
93
+
94
+ query = query. order ( ( versions:: created_at. desc ( ) , versions:: id. desc ( ) ) ) ;
95
+
96
+ let data: Vec < ( Version , Option < User > ) > = query. load ( conn) ?;
97
+ let mut next_page = None ;
98
+ if let Some ( options) = options {
99
+ next_page = next_seek_params ( & data, options, |last| {
100
+ seek:: Date ( last. 0 . created_at , last. 0 . id )
101
+ } ) ?
102
+ . map ( |p| req. query_with_params ( p) ) ;
103
+ } ;
104
+
105
+ // Since the total count is retrieved through an additional query, to maintain consistency
106
+ // with other pagination methods, we only make a count query while data is not empty.
107
+ let total = if !data. is_empty ( ) {
108
+ versions:: table. count ( ) . get_result ( conn) ?
109
+ } else {
110
+ 0
111
+ } ;
112
+
113
+ Ok ( PaginatedVersionsAndPublishers {
114
+ data,
115
+ meta : ResponseMeta { total, next_page } ,
116
+ } )
117
+ }
118
+
119
+ // Unfortunately, Heroku Postgres has no support for the semver PG extension.
120
+ // Therefore, we need to perform both sorting and pagination manually on the server.
121
+ fn list_by_semver (
122
+ krate : & Crate ,
123
+ options : Option < & PaginationOptions > ,
124
+ req : & Parts ,
125
+ conn : & mut PgConnection ,
126
+ ) -> AppResult < PaginatedVersionsAndPublishers > {
127
+ let ( data, total) = if let Some ( options) = options {
128
+ // Sorting by semver but opted for id as the seek key because num can be quite lengthy,
129
+ // while id values are significantly smaller.
130
+ let mut sorted_versions = IndexMap :: new ( ) ;
131
+ for result in krate
132
+ . all_versions ( )
133
+ . select ( ( versions:: id, versions:: num) )
134
+ . load_iter :: < ( i32 , String ) , DefaultLoadingMode > ( conn) ?
135
+ {
136
+ let ( id, num) = result?;
137
+ sorted_versions. insert ( id, ( num, None ) ) ;
138
+ }
139
+ sorted_versions. sort_by_cached_key ( |_, ( num, _) | Reverse ( semver:: Version :: parse ( num) . ok ( ) ) ) ;
140
+
141
+ let mut idx = Some ( 0 ) ;
142
+ if let Page :: Seek ( ref seek) = options. page {
143
+ idx = seek
144
+ . decode :: < i32 > ( )
145
+ . map ( |id| sorted_versions. get_index_of ( & id) ) ?
146
+ . filter ( |i| i + 1 < sorted_versions. len ( ) )
147
+ . map ( |i| i + 1 ) ;
148
+ }
149
+ if let Some ( start) = idx {
150
+ let end = ( start + options. per_page as usize ) . min ( sorted_versions. len ( ) ) ;
151
+ let ids = sorted_versions[ start..end] . keys ( ) . collect :: < Vec < _ > > ( ) ;
152
+ for result in krate
153
+ . all_versions ( )
154
+ . left_outer_join ( users:: table)
155
+ . select ( ( versions:: all_columns, users:: all_columns. nullable ( ) ) )
156
+ . filter ( versions:: id. eq_any ( ids) )
157
+ . load_iter :: < ( Version , Option < User > ) , DefaultLoadingMode > ( conn) ?
158
+ {
159
+ let row = result?;
160
+ sorted_versions. insert ( row. 0 . id , ( row. 0 . num . to_owned ( ) , Some ( row) ) ) ;
161
+ }
162
+ (
163
+ sorted_versions
164
+ . values ( )
165
+ . flat_map ( |( _, v) | v)
166
+ . cloned ( )
167
+ . collect ( ) ,
168
+ sorted_versions. len ( ) ,
169
+ )
170
+ } else {
171
+ ( vec ! [ ] , 0 )
172
+ }
173
+ } else {
174
+ let mut data: Vec < ( Version , Option < User > ) > = krate
175
+ . all_versions ( )
176
+ . left_outer_join ( users:: table)
177
+ . select ( ( versions:: all_columns, users:: all_columns. nullable ( ) ) )
178
+ . load ( conn) ?;
179
+ data. sort_by_cached_key ( |( version, _) | Reverse ( semver:: Version :: parse ( & version. num ) . ok ( ) ) ) ;
180
+ let total = data. len ( ) ;
181
+ ( data, total)
182
+ } ;
183
+
184
+ let mut next_page = None ;
185
+ if let Some ( options) = options {
186
+ next_page =
187
+ next_seek_params ( & data, options, |last| last. 0 . id ) ?. map ( |p| req. query_with_params ( p) )
188
+ } ;
189
+
190
+ Ok ( PaginatedVersionsAndPublishers {
191
+ data,
192
+ meta : ResponseMeta {
193
+ total : total as i64 ,
194
+ next_page,
195
+ } ,
196
+ } )
197
+ }
198
+
199
+ mod seek {
200
+ use chrono:: naive:: serde:: ts_microseconds;
201
+ use serde:: { Deserialize , Serialize } ;
202
+
203
+ #[ derive( Deserialize , Serialize ) ]
204
+ pub ( super ) struct Date (
205
+ #[ serde( with = "ts_microseconds" ) ] pub ( super ) chrono:: NaiveDateTime ,
206
+ pub ( super ) i32 ,
207
+ ) ;
208
+ }
209
+
210
+ fn next_seek_params < T , S , F > (
211
+ records : & [ T ] ,
212
+ options : & PaginationOptions ,
213
+ f : F ,
214
+ ) -> AppResult < Option < IndexMap < String , String > > >
215
+ where
216
+ F : Fn ( & T ) -> S ,
217
+ S : serde:: Serialize ,
218
+ {
219
+ if matches ! ( options. page, Page :: Numeric ( _) ) || records. len ( ) < options. per_page as usize {
220
+ return Ok ( None ) ;
221
+ }
222
+
223
+ let mut opts = IndexMap :: new ( ) ;
224
+ match options. page {
225
+ Page :: Unspecified | Page :: Seek ( _) => {
226
+ let seek = f ( records. last ( ) . unwrap ( ) ) ;
227
+ opts. insert ( "seek" . into ( ) , encode_seek ( seek) ?) ;
228
+ }
229
+ Page :: Numeric ( _) => unreachable ! ( ) ,
230
+ } ;
231
+ Ok ( Some ( opts) )
232
+ }
233
+
234
+ struct PaginatedVersionsAndPublishers {
235
+ data : Vec < ( Version , Option < User > ) > ,
236
+ meta : ResponseMeta ,
237
+ }
238
+
239
+ #[ derive( Serialize ) ]
240
+ struct ResponseMeta {
241
+ total : i64 ,
242
+ next_page : Option < String > ,
243
+ }
0 commit comments