Skip to content

Commit 10a4c8e

Browse files
committed
Admin API: add pagination cursors to list endpoints
1 parent ad7fedf commit 10a4c8e

File tree

12 files changed

+613
-39
lines changed

12 files changed

+613
-39
lines changed

crates/handlers/src/admin/response.rs

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
#![allow(clippy::module_name_repetitions)]
88

9-
use mas_storage::Pagination;
9+
use mas_storage::{Pagination, pagination::Edge};
1010
use schemars::JsonSchema;
1111
use serde::Serialize;
1212
use ulid::Ulid;
@@ -102,22 +102,26 @@ impl<T: Resource> PaginatedResponse<T> {
102102
base,
103103
current_pagination
104104
.clear_before()
105-
.after(page.edges.last().unwrap().id()),
105+
.after(page.edges.last().unwrap().cursor),
106106
)
107107
}),
108108
prev: if page.has_previous_page {
109109
Some(url_with_pagination(
110110
base,
111111
current_pagination
112112
.clear_after()
113-
.before(page.edges.first().unwrap().id()),
113+
.before(page.edges.first().unwrap().cursor),
114114
))
115115
} else {
116116
None
117117
},
118118
};
119119

120-
let data = page.edges.into_iter().map(SingleResource::new).collect();
120+
let data = page
121+
.edges
122+
.into_iter()
123+
.map(SingleResource::from_edge)
124+
.collect();
121125

122126
Self {
123127
meta: PaginationMeta { count },
@@ -143,6 +147,31 @@ struct SingleResource<T> {
143147

144148
/// Related links
145149
links: SelfLinks,
150+
151+
/// Metadata about the resource
152+
#[serde(skip_serializing_if = "SingleResourceMeta::is_empty")]
153+
meta: SingleResourceMeta,
154+
}
155+
156+
/// Metadata associated with a resource
157+
#[derive(Serialize, JsonSchema)]
158+
struct SingleResourceMeta {
159+
/// Information about the pagination of the resource
160+
#[serde(skip_serializing_if = "Option::is_none")]
161+
page: Option<SingleResourceMetaPage>,
162+
}
163+
164+
impl SingleResourceMeta {
165+
fn is_empty(&self) -> bool {
166+
self.page.is_none()
167+
}
168+
}
169+
170+
/// Pagination metadata for a resource
171+
#[derive(Serialize, JsonSchema)]
172+
struct SingleResourceMetaPage {
173+
/// The cursor of this resource in the paginated result
174+
cursor: String,
146175
}
147176

148177
impl<T: Resource> SingleResource<T> {
@@ -153,8 +182,16 @@ impl<T: Resource> SingleResource<T> {
153182
id: resource.id(),
154183
attributes: resource,
155184
links: SelfLinks { self_ },
185+
meta: SingleResourceMeta { page: None },
156186
}
157187
}
188+
189+
fn from_edge<C: ToString>(edge: Edge<T, C>) -> Self {
190+
let cursor = edge.cursor.to_string();
191+
let mut resource = Self::new(edge.node);
192+
resource.meta.page = Some(SingleResourceMetaPage { cursor });
193+
resource
194+
}
158195
}
159196

160197
/// Related links

crates/handlers/src/admin/v1/compat_sessions/list.rs

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,13 @@ Use the `filter[status]` parameter to filter the sessions by their status and `p
137137
let sessions = CompatSession::samples();
138138
let pagination = mas_storage::Pagination::first(sessions.len());
139139
let page = Page {
140-
edges: sessions.into(),
140+
edges: sessions
141+
.into_iter()
142+
.map(|node| mas_storage::pagination::Edge {
143+
cursor: node.id(),
144+
node,
145+
})
146+
.collect(),
141147
has_next_page: true,
142148
has_previous_page: false,
143149
};
@@ -299,6 +305,11 @@ mod tests {
299305
},
300306
"links": {
301307
"self": "/api/admin/v1/compat-sessions/01FSHNB530AAPR7PEV8KNBZD5Y"
308+
},
309+
"meta": {
310+
"page": {
311+
"cursor": "01FSHNB530AAPR7PEV8KNBZD5Y"
312+
}
302313
}
303314
},
304315
{
@@ -318,6 +329,11 @@ mod tests {
318329
},
319330
"links": {
320331
"self": "/api/admin/v1/compat-sessions/01FSHNCZP0PPF7X0EVMJNECPZW"
332+
},
333+
"meta": {
334+
"page": {
335+
"cursor": "01FSHNCZP0PPF7X0EVMJNECPZW"
336+
}
321337
}
322338
}
323339
],
@@ -362,6 +378,11 @@ mod tests {
362378
},
363379
"links": {
364380
"self": "/api/admin/v1/compat-sessions/01FSHNB530AAPR7PEV8KNBZD5Y"
381+
},
382+
"meta": {
383+
"page": {
384+
"cursor": "01FSHNB530AAPR7PEV8KNBZD5Y"
385+
}
365386
}
366387
}
367388
],
@@ -403,6 +424,11 @@ mod tests {
403424
},
404425
"links": {
405426
"self": "/api/admin/v1/compat-sessions/01FSHNB530AAPR7PEV8KNBZD5Y"
427+
},
428+
"meta": {
429+
"page": {
430+
"cursor": "01FSHNB530AAPR7PEV8KNBZD5Y"
431+
}
406432
}
407433
}
408434
],
@@ -444,6 +470,11 @@ mod tests {
444470
},
445471
"links": {
446472
"self": "/api/admin/v1/compat-sessions/01FSHNCZP0PPF7X0EVMJNECPZW"
473+
},
474+
"meta": {
475+
"page": {
476+
"cursor": "01FSHNCZP0PPF7X0EVMJNECPZW"
477+
}
447478
}
448479
}
449480
],

crates/handlers/src/admin/v1/oauth2_sessions/list.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,13 @@ Use the `filter[status]` parameter to filter the sessions by their status and `p
192192
let sessions = OAuth2Session::samples();
193193
let pagination = mas_storage::Pagination::first(sessions.len());
194194
let page = Page {
195-
edges: sessions.into(),
195+
edges: sessions
196+
.into_iter()
197+
.map(|node| mas_storage::pagination::Edge {
198+
cursor: node.id(),
199+
node,
200+
})
201+
.collect(),
196202
has_next_page: true,
197203
has_previous_page: false,
198204
};
@@ -354,6 +360,11 @@ mod tests {
354360
},
355361
"links": {
356362
"self": "/api/admin/v1/oauth2-sessions/01FSHN9AG0MKGTBNZ16RDR3PVY"
363+
},
364+
"meta": {
365+
"page": {
366+
"cursor": "01FSHN9AG0MKGTBNZ16RDR3PVY"
367+
}
357368
}
358369
}
359370
],

crates/handlers/src/admin/v1/upstream_oauth_links/list.rs

Lines changed: 55 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,13 @@ pub fn doc(operation: TransformOperation) -> TransformOperation {
112112
let links = UpstreamOAuthLink::samples();
113113
let pagination = mas_storage::Pagination::first(links.len());
114114
let page = Page {
115-
edges: links.into(),
115+
edges: links
116+
.into_iter()
117+
.map(|node| mas_storage::pagination::Edge {
118+
cursor: node.id(),
119+
node,
120+
})
121+
.collect(),
116122
has_next_page: true,
117123
has_previous_page: false,
118124
};
@@ -296,7 +302,7 @@ mod tests {
296302
let response = state.request(request).await;
297303
response.assert_status(StatusCode::OK);
298304
let body: serde_json::Value = response.json();
299-
assert_json_snapshot!(body, @r###"
305+
assert_json_snapshot!(body, @r#"
300306
{
301307
"meta": {
302308
"count": 3
@@ -314,6 +320,11 @@ mod tests {
314320
},
315321
"links": {
316322
"self": "/api/admin/v1/upstream-oauth-links/01FSHN9AG0AQZQP8DX40GD59PW"
323+
},
324+
"meta": {
325+
"page": {
326+
"cursor": "01FSHN9AG0AQZQP8DX40GD59PW"
327+
}
317328
}
318329
},
319330
{
@@ -328,6 +339,11 @@ mod tests {
328339
},
329340
"links": {
330341
"self": "/api/admin/v1/upstream-oauth-links/01FSHN9AG0PJZ6DZNTAA1XKPT4"
342+
},
343+
"meta": {
344+
"page": {
345+
"cursor": "01FSHN9AG0PJZ6DZNTAA1XKPT4"
346+
}
331347
}
332348
},
333349
{
@@ -342,6 +358,11 @@ mod tests {
342358
},
343359
"links": {
344360
"self": "/api/admin/v1/upstream-oauth-links/01FSHN9AG0QHEHKX2JNQ2A2D07"
361+
},
362+
"meta": {
363+
"page": {
364+
"cursor": "01FSHN9AG0QHEHKX2JNQ2A2D07"
365+
}
345366
}
346367
}
347368
],
@@ -351,7 +372,7 @@ mod tests {
351372
"last": "/api/admin/v1/upstream-oauth-links?page[last]=10"
352373
}
353374
}
354-
"###);
375+
"#);
355376

356377
// Filter by user ID
357378
let request = Request::get(format!(
@@ -364,7 +385,7 @@ mod tests {
364385
let response = state.request(request).await;
365386
response.assert_status(StatusCode::OK);
366387
let body: serde_json::Value = response.json();
367-
assert_json_snapshot!(body, @r###"
388+
assert_json_snapshot!(body, @r#"
368389
{
369390
"meta": {
370391
"count": 2
@@ -382,6 +403,11 @@ mod tests {
382403
},
383404
"links": {
384405
"self": "/api/admin/v1/upstream-oauth-links/01FSHN9AG0AQZQP8DX40GD59PW"
406+
},
407+
"meta": {
408+
"page": {
409+
"cursor": "01FSHN9AG0AQZQP8DX40GD59PW"
410+
}
385411
}
386412
},
387413
{
@@ -396,6 +422,11 @@ mod tests {
396422
},
397423
"links": {
398424
"self": "/api/admin/v1/upstream-oauth-links/01FSHN9AG0QHEHKX2JNQ2A2D07"
425+
},
426+
"meta": {
427+
"page": {
428+
"cursor": "01FSHN9AG0QHEHKX2JNQ2A2D07"
429+
}
399430
}
400431
}
401432
],
@@ -405,7 +436,7 @@ mod tests {
405436
"last": "/api/admin/v1/upstream-oauth-links?filter[user]=01FSHN9AG0MZAA6S4AF7CTV32E&page[last]=10"
406437
}
407438
}
408-
"###);
439+
"#);
409440

410441
// Filter by provider
411442
let request = Request::get(format!(
@@ -418,7 +449,7 @@ mod tests {
418449
let response = state.request(request).await;
419450
response.assert_status(StatusCode::OK);
420451
let body: serde_json::Value = response.json();
421-
assert_json_snapshot!(body, @r###"
452+
assert_json_snapshot!(body, @r#"
422453
{
423454
"meta": {
424455
"count": 2
@@ -436,6 +467,11 @@ mod tests {
436467
},
437468
"links": {
438469
"self": "/api/admin/v1/upstream-oauth-links/01FSHN9AG0AQZQP8DX40GD59PW"
470+
},
471+
"meta": {
472+
"page": {
473+
"cursor": "01FSHN9AG0AQZQP8DX40GD59PW"
474+
}
439475
}
440476
},
441477
{
@@ -450,6 +486,11 @@ mod tests {
450486
},
451487
"links": {
452488
"self": "/api/admin/v1/upstream-oauth-links/01FSHN9AG0PJZ6DZNTAA1XKPT4"
489+
},
490+
"meta": {
491+
"page": {
492+
"cursor": "01FSHN9AG0PJZ6DZNTAA1XKPT4"
493+
}
453494
}
454495
}
455496
],
@@ -459,7 +500,7 @@ mod tests {
459500
"last": "/api/admin/v1/upstream-oauth-links?filter[provider]=01FSHN9AG09NMZYX8MFYH578R9&page[last]=10"
460501
}
461502
}
462-
"###);
503+
"#);
463504

464505
// Filter by subject
465506
let request = Request::get(format!(
@@ -472,7 +513,7 @@ mod tests {
472513
let response = state.request(request).await;
473514
response.assert_status(StatusCode::OK);
474515
let body: serde_json::Value = response.json();
475-
assert_json_snapshot!(body, @r###"
516+
assert_json_snapshot!(body, @r#"
476517
{
477518
"meta": {
478519
"count": 1
@@ -490,6 +531,11 @@ mod tests {
490531
},
491532
"links": {
492533
"self": "/api/admin/v1/upstream-oauth-links/01FSHN9AG0AQZQP8DX40GD59PW"
534+
},
535+
"meta": {
536+
"page": {
537+
"cursor": "01FSHN9AG0AQZQP8DX40GD59PW"
538+
}
493539
}
494540
}
495541
],
@@ -499,6 +545,6 @@ mod tests {
499545
"last": "/api/admin/v1/upstream-oauth-links?filter[subject]=subject1&page[last]=10"
500546
}
501547
}
502-
"###);
548+
"#);
503549
}
504550
}

0 commit comments

Comments
 (0)