refactor(api): share Pagination across admin/public; cover get-by-id auth

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-02 21:53:21 +02:00
parent 0055616099
commit 1888e185f7
5 changed files with 40 additions and 43 deletions
+2 -21
View File
@@ -10,10 +10,10 @@ use axum::{
routing::get,
};
use domain::{CatalogueObject, ObjectId};
use serde::{Deserialize, Serialize};
use serde::Serialize;
use utoipa::ToSchema;
use crate::AppState;
use crate::{AppState, pagination::Pagination};
/// A localized label `{ lang, label }` (shared across admin views).
#[derive(Serialize, ToSchema)]
@@ -69,25 +69,6 @@ pub(crate) struct AdminObjectPage {
pub offset: i64,
}
#[derive(Deserialize)]
pub(crate) struct Pagination {
limit: Option<i64>,
offset: Option<i64>,
}
const DEFAULT_LIMIT: i64 = 50;
const MAX_LIMIT: i64 = 200;
impl Pagination {
fn limit(&self) -> i64 {
self.limit.unwrap_or(DEFAULT_LIMIT).clamp(1, MAX_LIMIT)
}
fn offset(&self) -> i64 {
self.offset.unwrap_or(0).max(0)
}
}
/// Format a `time::Date` as `YYYY-MM-DD`.
pub(crate) fn format_date(d: time::Date) -> String {
let fmt = time::macros::format_description!("[year]-[month]-[day]");
+1
View File
@@ -4,6 +4,7 @@ mod admin;
mod admin_objects;
mod health;
mod openapi;
mod pagination;
mod public;
use axum::Router;
+23
View File
@@ -0,0 +1,23 @@
//! Shared pagination query parameters used by both admin and public handlers.
use serde::Deserialize;
pub(crate) const DEFAULT_LIMIT: i64 = 50;
pub(crate) const MAX_LIMIT: i64 = 200;
/// Pagination query parameters with sane defaults and a hard cap.
#[derive(Deserialize)]
pub(crate) struct Pagination {
pub(crate) limit: Option<i64>,
pub(crate) offset: Option<i64>,
}
impl Pagination {
pub(crate) fn limit(&self) -> i64 {
self.limit.unwrap_or(DEFAULT_LIMIT).clamp(1, MAX_LIMIT)
}
pub(crate) fn offset(&self) -> i64 {
self.offset.unwrap_or(0).max(0)
}
}
+2 -22
View File
@@ -14,10 +14,10 @@ use axum::{
routing::get,
};
use domain::{CatalogueObject, ObjectId};
use serde::{Deserialize, Serialize};
use serde::Serialize;
use utoipa::ToSchema;
use crate::AppState;
use crate::{AppState, pagination::Pagination};
/// A catalogue object as exposed on the public surface (public-safe fields only).
#[derive(Serialize, ToSchema)]
@@ -50,26 +50,6 @@ pub(crate) struct PublicObjectPage {
pub offset: i64,
}
/// Pagination query parameters with sane defaults and a hard cap.
#[derive(Deserialize)]
pub(crate) struct Pagination {
limit: Option<i64>,
offset: Option<i64>,
}
const DEFAULT_LIMIT: i64 = 50;
const MAX_LIMIT: i64 = 200;
impl Pagination {
fn limit(&self) -> i64 {
self.limit.unwrap_or(DEFAULT_LIMIT).clamp(1, MAX_LIMIT)
}
fn offset(&self) -> i64 {
self.offset.unwrap_or(0).max(0)
}
}
/// List public objects (paginated).
#[utoipa::path(
get,
+12
View File
@@ -97,7 +97,19 @@ async fn list_and_get_require_auth(pool: PgPool) {
)
.await
.unwrap();
let get = app
.oneshot(
Request::builder()
.uri(format!("/api/admin/objects/{}", domain::ObjectId::new()))
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_eq!(list.status(), StatusCode::UNAUTHORIZED);
assert_eq!(get.status(), StatusCode::UNAUTHORIZED);
}
#[sqlx::test(migrations = "../db/migrations")]