From 2640c99243db5201a2f1497ffc63c7a1f629ddef Mon Sep 17 00:00:00 2001 From: Gabriel Kaszewski Date: Tue, 12 May 2026 18:50:33 +0200 Subject: [PATCH] feat(openapi): add search and people endpoints to Swagger/Scalar --- crates/api-types/src/search.rs | 29 +++++++++++++++-------- crates/presentation/src/handlers/api.rs | 26 ++++++++++++++++++++ crates/presentation/src/openapi/mod.rs | 2 ++ crates/presentation/src/openapi/search.rs | 29 +++++++++++++++++++++++ 4 files changed, 76 insertions(+), 10 deletions(-) create mode 100644 crates/presentation/src/openapi/search.rs diff --git a/crates/api-types/src/search.rs b/crates/api-types/src/search.rs index 183f298..91cc84b 100644 --- a/crates/api-types/src/search.rs +++ b/crates/api-types/src/search.rs @@ -1,25 +1,34 @@ use serde::{Deserialize, Serialize}; +use utoipa::{IntoParams, ToSchema}; use uuid::Uuid; -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, IntoParams)] pub struct SearchQueryParams { + /// Free-text query matched across title, cast, crew, genres and keywords. pub q: Option, + /// Filter by genre name (exact match, case-sensitive). pub genre: Option, + /// Filter by release year. pub year: Option, + /// Filter by person ID (UUID). pub person_id: Option, + /// Filter crew results by department (e.g. "Directing", "Writing"). pub department: Option, + /// Filter by original language code (e.g. "en", "fr"). pub language: Option, + /// Max results to return (default 20). pub limit: Option, + /// Offset for pagination (default 0). pub offset: Option, } -#[derive(Debug, Serialize)] +#[derive(Debug, Serialize, ToSchema)] pub struct SearchResponse { pub movies: PaginatedMovieHits, pub people: PaginatedPersonHits, } -#[derive(Debug, Serialize)] +#[derive(Debug, Serialize, ToSchema)] pub struct PaginatedMovieHits { pub items: Vec, pub total_count: u64, @@ -27,7 +36,7 @@ pub struct PaginatedMovieHits { pub offset: u32, } -#[derive(Debug, Serialize)] +#[derive(Debug, Serialize, ToSchema)] pub struct PaginatedPersonHits { pub items: Vec, pub total_count: u64, @@ -35,7 +44,7 @@ pub struct PaginatedPersonHits { pub offset: u32, } -#[derive(Debug, Serialize)] +#[derive(Debug, Serialize, ToSchema)] pub struct MovieSearchHitDto { pub movie_id: Uuid, pub title: String, @@ -45,7 +54,7 @@ pub struct MovieSearchHitDto { pub genres: Vec, } -#[derive(Debug, Serialize)] +#[derive(Debug, Serialize, ToSchema)] pub struct PersonSearchHitDto { pub person_id: Uuid, pub name: String, @@ -54,7 +63,7 @@ pub struct PersonSearchHitDto { pub known_for_titles: Vec, } -#[derive(Debug, Serialize)] +#[derive(Debug, Serialize, ToSchema)] pub struct PersonDto { pub id: Uuid, pub external_id: String, @@ -63,14 +72,14 @@ pub struct PersonDto { pub profile_path: Option, } -#[derive(Debug, Serialize)] +#[derive(Debug, Serialize, ToSchema)] pub struct PersonCreditsDto { pub person: PersonDto, pub cast: Vec, pub crew: Vec, } -#[derive(Debug, Serialize)] +#[derive(Debug, Serialize, ToSchema)] pub struct CastCreditDto { pub movie_id: Uuid, pub title: String, @@ -79,7 +88,7 @@ pub struct CastCreditDto { pub poster_path: Option, } -#[derive(Debug, Serialize)] +#[derive(Debug, Serialize, ToSchema)] pub struct CrewCreditDto { pub movie_id: Uuid, pub title: String, diff --git a/crates/presentation/src/handlers/api.rs b/crates/presentation/src/handlers/api.rs index 3bd1bf1..4860e98 100644 --- a/crates/presentation/src/handlers/api.rs +++ b/crates/presentation/src/handlers/api.rs @@ -1097,6 +1097,14 @@ pub async fn export_diary( // Search and person endpoints are intentionally public — browsing the catalog // and people profiles does not require authentication. +#[utoipa::path( + get, path = "/api/v1/search", + params(api_types::search::SearchQueryParams), + responses( + (status = 200, body = api_types::search::SearchResponse), + ), + tag = "search", +)] pub async fn get_search( State(state): State, Query(params): Query, @@ -1151,6 +1159,15 @@ pub async fn get_search( } } +#[utoipa::path( + get, path = "/api/v1/people/{id}", + params(("id" = Uuid, Path, description = "Person ID")), + responses( + (status = 200, body = api_types::search::PersonDto), + (status = 404, description = "Person not found"), + ), + tag = "search", +)] pub async fn get_person_handler( State(state): State, Path(id): Path, @@ -1171,6 +1188,15 @@ pub async fn get_person_handler( } } +#[utoipa::path( + get, path = "/api/v1/people/{id}/credits", + params(("id" = Uuid, Path, description = "Person ID")), + responses( + (status = 200, body = api_types::search::PersonCreditsDto), + (status = 404, description = "Person not found"), + ), + tag = "search", +)] pub async fn get_person_credits_handler( State(state): State, Path(id): Path, diff --git a/crates/presentation/src/openapi/mod.rs b/crates/presentation/src/openapi/mod.rs index 7807edb..978b2eb 100644 --- a/crates/presentation/src/openapi/mod.rs +++ b/crates/presentation/src/openapi/mod.rs @@ -2,6 +2,7 @@ mod auth; mod diary; mod import; mod movies; +mod search; mod social; mod users; @@ -36,6 +37,7 @@ fn build() -> utoipa::openapi::OpenApi { api.merge(movies::MoviesDoc::openapi()); api.merge(users::UsersDoc::openapi()); api.merge(import::ImportDoc::openapi()); + api.merge(search::SearchDoc::openapi()); #[cfg(feature = "federation")] api.merge(social::SocialDoc::openapi()); SecurityAddon.modify(&mut api); diff --git a/crates/presentation/src/openapi/search.rs b/crates/presentation/src/openapi/search.rs new file mode 100644 index 0000000..8b96774 --- /dev/null +++ b/crates/presentation/src/openapi/search.rs @@ -0,0 +1,29 @@ +use api_types::search::{ + CastCreditDto, CrewCreditDto, MovieSearchHitDto, PaginatedMovieHits, PaginatedPersonHits, + PersonCreditsDto, PersonDto, PersonSearchHitDto, SearchResponse, +}; +use utoipa::OpenApi; + +#[derive(OpenApi)] +#[openapi( + paths( + crate::handlers::api::get_search, + crate::handlers::api::get_person_handler, + crate::handlers::api::get_person_credits_handler, + ), + components(schemas( + SearchResponse, + PaginatedMovieHits, + PaginatedPersonHits, + MovieSearchHitDto, + PersonSearchHitDto, + PersonDto, + PersonCreditsDto, + CastCreditDto, + CrewCreditDto, + )), + tags( + (name = "search", description = "Full-text search across movies and people"), + ), +)] +pub struct SearchDoc;