relay filter/appview bootstrap
1use std::sync::Arc;
2
3use axum::{
4 extract::{Query, State},
5 Json,
6};
7use chrono::{DateTime, Utc};
8use serde::{Deserialize, Serialize};
9
10use crate::api::error::ApiError;
11use crate::db;
12use crate::records::{ShardView, SHARD_COLLECTION};
13use crate::AppState;
14
15#[derive(Debug, Deserialize)]
16pub struct ListShardsParams {
17 pub author: Option<String>,
18 pub limit: Option<i64>,
19 pub cursor: Option<String>,
20}
21
22#[derive(Debug, Serialize)]
23pub struct ListShardsResponse {
24 pub shards: Vec<ShardView>,
25 #[serde(skip_serializing_if = "Option::is_none")]
26 pub cursor: Option<String>,
27}
28
29pub async fn list_shards(
30 State(state): State<Arc<AppState>>,
31 Query(params): Query<ListShardsParams>,
32) -> Result<Json<ListShardsResponse>, ApiError> {
33 let author = params.author.ok_or_else(|| {
34 ApiError::InvalidRequest("Missing required parameter: author".to_string())
35 })?;
36
37 let limit = params.limit.unwrap_or(50).min(100).max(1);
38 let cursor: Option<DateTime<Utc>> = params
39 .cursor
40 .as_ref()
41 .and_then(|c| DateTime::parse_from_rfc3339(c).ok())
42 .map(|dt| dt.with_timezone(&Utc));
43
44 let records =
45 db::list_records_by_creator(state.db.pool(), SHARD_COLLECTION, &author, cursor, limit)
46 .await?;
47
48 let next_cursor = records.last().map(|r| r.indexed_at.to_rfc3339());
49 let views: Vec<ShardView> = records.into_iter().map(ShardView::from).collect();
50
51 Ok(Json(ListShardsResponse {
52 shards: views,
53 cursor: next_cursor,
54 }))
55}