feat: implement movie listing functionality with pagination and search
This commit is contained in:
@@ -375,6 +375,52 @@ impl MovieRepository for PostgresRepository {
|
||||
.map_err(Self::map_err)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn list_movies(
|
||||
&self,
|
||||
page: &domain::models::collections::PageParams,
|
||||
search: Option<&str>,
|
||||
) -> Result<domain::models::collections::Paginated<domain::models::Movie>, DomainError> {
|
||||
use sqlx::Row;
|
||||
let limit = page.limit as i64;
|
||||
let offset = page.offset as i64;
|
||||
let pattern = search.map(|s| format!("%{}%", s.to_lowercase()));
|
||||
|
||||
let rows: Vec<models::MovieRow> = sqlx::query_as(
|
||||
"SELECT id, external_metadata_id, title, release_year, director, poster_path \
|
||||
FROM movies \
|
||||
WHERE ($1::text IS NULL OR LOWER(title) LIKE $1) \
|
||||
ORDER BY title ASC \
|
||||
LIMIT $2 OFFSET $3",
|
||||
)
|
||||
.bind(&pattern)
|
||||
.bind(limit)
|
||||
.bind(offset)
|
||||
.fetch_all(&self.pool)
|
||||
.await
|
||||
.map_err(Self::map_err)?;
|
||||
|
||||
let total: i64 = sqlx::query(
|
||||
"SELECT COUNT(*) FROM movies WHERE ($1::text IS NULL OR LOWER(title) LIKE $1)",
|
||||
)
|
||||
.bind(&pattern)
|
||||
.fetch_one(&self.pool)
|
||||
.await
|
||||
.map_err(Self::map_err)?
|
||||
.try_get(0)
|
||||
.unwrap_or(0);
|
||||
|
||||
let items = rows.into_iter()
|
||||
.map(|r| r.to_domain())
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
Ok(domain::models::collections::Paginated {
|
||||
items,
|
||||
total_count: total as u64,
|
||||
limit: page.limit,
|
||||
offset: page.offset,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
|
||||
@@ -383,6 +383,54 @@ impl MovieRepository for SqliteMovieRepository {
|
||||
.map_err(Self::map_err)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn list_movies(
|
||||
&self,
|
||||
page: &domain::models::collections::PageParams,
|
||||
search: Option<&str>,
|
||||
) -> Result<domain::models::collections::Paginated<domain::models::Movie>, DomainError> {
|
||||
use sqlx::Row;
|
||||
let limit = page.limit as i64;
|
||||
let offset = page.offset as i64;
|
||||
let pattern = search.map(|s| format!("%{}%", s.to_lowercase()));
|
||||
|
||||
let rows: Vec<models::MovieRow> = sqlx::query_as(
|
||||
"SELECT id, external_metadata_id, title, release_year, director, poster_path \
|
||||
FROM movies \
|
||||
WHERE (? IS NULL OR LOWER(title) LIKE ?) \
|
||||
ORDER BY title ASC \
|
||||
LIMIT ? OFFSET ?",
|
||||
)
|
||||
.bind(&pattern)
|
||||
.bind(&pattern)
|
||||
.bind(limit)
|
||||
.bind(offset)
|
||||
.fetch_all(&self.pool)
|
||||
.await
|
||||
.map_err(Self::map_err)?;
|
||||
|
||||
let total: i64 = sqlx::query(
|
||||
"SELECT COUNT(*) FROM movies WHERE (? IS NULL OR LOWER(title) LIKE ?)",
|
||||
)
|
||||
.bind(&pattern)
|
||||
.bind(&pattern)
|
||||
.fetch_one(&self.pool)
|
||||
.await
|
||||
.map_err(Self::map_err)?
|
||||
.try_get(0)
|
||||
.unwrap_or(0);
|
||||
|
||||
let items = rows.into_iter()
|
||||
.map(|r| r.to_domain())
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
Ok(domain::models::collections::Paginated {
|
||||
items,
|
||||
total_count: total as u64,
|
||||
limit: page.limit,
|
||||
offset: page.offset,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
|
||||
@@ -1,6 +1,25 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
|
||||
// ── Movie list ────────────────────────────────────────────────────────────────
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, utoipa::IntoParams)]
|
||||
#[into_params(parameter_in = Query)]
|
||||
pub struct MoviesQueryParams {
|
||||
pub limit: Option<u32>,
|
||||
pub offset: Option<u32>,
|
||||
/// Optional title filter (case-insensitive substring match)
|
||||
pub search: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
|
||||
pub struct MoviesResponse {
|
||||
pub items: Vec<MovieDto>,
|
||||
pub total_count: u64,
|
||||
pub limit: u32,
|
||||
pub offset: u32,
|
||||
}
|
||||
|
||||
// ── Movie profile (enrichment) ────────────────────────────────────────────────
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use chrono::NaiveDateTime;
|
||||
use domain::models::{ExportFormat, FieldMapping, FileFormat, UserRole};
|
||||
use domain::models::{FieldMapping, FileFormat, UserRole};
|
||||
use uuid::Uuid;
|
||||
|
||||
pub struct LogReviewCommand {
|
||||
@@ -21,11 +21,6 @@ pub struct SyncPosterCommand {
|
||||
pub external_metadata_id: String,
|
||||
}
|
||||
|
||||
pub struct LoginCommand {
|
||||
pub email: String,
|
||||
pub password: String,
|
||||
}
|
||||
|
||||
pub struct RegisterCommand {
|
||||
pub email: String,
|
||||
pub username: String,
|
||||
@@ -38,11 +33,6 @@ pub struct DeleteReviewCommand {
|
||||
pub requesting_user_id: Uuid,
|
||||
}
|
||||
|
||||
pub struct ExportCommand {
|
||||
pub user_id: Uuid,
|
||||
pub format: ExportFormat,
|
||||
}
|
||||
|
||||
// FileFormat is now in domain::models — no longer defined here
|
||||
|
||||
pub struct CreateImportSessionCommand {
|
||||
@@ -79,3 +69,10 @@ pub struct DeleteImportProfileCommand {
|
||||
pub user_id: Uuid,
|
||||
pub profile_id: Uuid,
|
||||
}
|
||||
|
||||
pub struct UpdateProfileCommand {
|
||||
pub user_id: Uuid,
|
||||
pub bio: Option<String>,
|
||||
pub avatar_bytes: Option<Vec<u8>>,
|
||||
pub avatar_content_type: Option<String>,
|
||||
}
|
||||
|
||||
@@ -226,9 +226,8 @@ mod tests {
|
||||
async fn upsert_movie(&self, _: &Movie) -> Result<(), DomainError> {
|
||||
panic!("unexpected")
|
||||
}
|
||||
async fn delete_movie(&self, _: &MovieId) -> Result<(), DomainError> {
|
||||
panic!("unexpected")
|
||||
}
|
||||
async fn delete_movie(&self, _: &MovieId) -> Result<(), DomainError> { panic!("unexpected") }
|
||||
async fn list_movies(&self, _: &domain::models::collections::PageParams, _: Option<&str>) -> Result<domain::models::collections::Paginated<Movie>, DomainError> { panic!("unexpected") }
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
@@ -249,12 +248,9 @@ mod tests {
|
||||
) -> Result<Vec<Movie>, DomainError> {
|
||||
Ok(vec![])
|
||||
}
|
||||
async fn upsert_movie(&self, _: &Movie) -> Result<(), DomainError> {
|
||||
panic!("unexpected")
|
||||
}
|
||||
async fn delete_movie(&self, _: &MovieId) -> Result<(), DomainError> {
|
||||
panic!("unexpected")
|
||||
}
|
||||
async fn upsert_movie(&self, _: &Movie) -> Result<(), DomainError> { panic!("unexpected") }
|
||||
async fn delete_movie(&self, _: &MovieId) -> Result<(), DomainError> { panic!("unexpected") }
|
||||
async fn list_movies(&self, _: &domain::models::collections::PageParams, _: Option<&str>) -> Result<domain::models::collections::Paginated<Movie>, DomainError> { panic!("unexpected") }
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
@@ -275,12 +271,9 @@ mod tests {
|
||||
) -> Result<Vec<Movie>, DomainError> {
|
||||
Ok(vec![self.0.clone()])
|
||||
}
|
||||
async fn upsert_movie(&self, _: &Movie) -> Result<(), DomainError> {
|
||||
panic!("unexpected")
|
||||
}
|
||||
async fn delete_movie(&self, _: &MovieId) -> Result<(), DomainError> {
|
||||
panic!("unexpected")
|
||||
}
|
||||
async fn upsert_movie(&self, _: &Movie) -> Result<(), DomainError> { panic!("unexpected") }
|
||||
async fn delete_movie(&self, _: &MovieId) -> Result<(), DomainError> { panic!("unexpected") }
|
||||
async fn list_movies(&self, _: &domain::models::collections::PageParams, _: Option<&str>) -> Result<domain::models::collections::Paginated<Movie>, DomainError> { panic!("unexpected") }
|
||||
}
|
||||
|
||||
struct MetaReturnsMovie(Movie);
|
||||
|
||||
@@ -1,6 +1,16 @@
|
||||
use domain::models::SortDirection;
|
||||
use domain::models::{ExportFormat, SortDirection};
|
||||
use uuid::Uuid;
|
||||
|
||||
pub struct LoginQuery {
|
||||
pub email: String,
|
||||
pub password: String,
|
||||
}
|
||||
|
||||
pub struct ExportQuery {
|
||||
pub user_id: Uuid,
|
||||
pub format: ExportFormat,
|
||||
}
|
||||
|
||||
pub struct GetDiaryQuery {
|
||||
pub limit: Option<u32>,
|
||||
pub offset: Option<u32>,
|
||||
@@ -70,3 +80,9 @@ pub struct GetMovieSocialPageQuery {
|
||||
pub limit: u32,
|
||||
pub offset: u32,
|
||||
}
|
||||
|
||||
pub struct GetMoviesQuery {
|
||||
pub limit: Option<u32>,
|
||||
pub offset: Option<u32>,
|
||||
pub search: Option<String>,
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
use domain::{errors::DomainError, value_objects::UserId};
|
||||
|
||||
use crate::{commands::ExportCommand, context::AppContext};
|
||||
use crate::{context::AppContext, queries::ExportQuery};
|
||||
|
||||
pub async fn execute(ctx: &AppContext, cmd: ExportCommand) -> Result<Vec<u8>, DomainError> {
|
||||
pub async fn execute(ctx: &AppContext, query: ExportQuery) -> Result<Vec<u8>, DomainError> {
|
||||
let entries = ctx
|
||||
.diary_repository
|
||||
.get_user_history(&UserId::from_uuid(cmd.user_id))
|
||||
.get_user_history(&UserId::from_uuid(query.user_id))
|
||||
.await?;
|
||||
ctx.diary_exporter
|
||||
.serialize_entries(&entries, cmd.format)
|
||||
.serialize_entries(&entries, query.format)
|
||||
.await
|
||||
}
|
||||
|
||||
14
crates/application/src/use_cases/get_movies.rs
Normal file
14
crates/application/src/use_cases/get_movies.rs
Normal file
@@ -0,0 +1,14 @@
|
||||
use domain::{
|
||||
errors::DomainError,
|
||||
models::collections::{PageParams, Paginated},
|
||||
models::Movie,
|
||||
};
|
||||
|
||||
use crate::{context::AppContext, queries::GetMoviesQuery};
|
||||
|
||||
pub async fn execute(ctx: &AppContext, query: GetMoviesQuery) -> Result<Paginated<Movie>, DomainError> {
|
||||
let page = PageParams::new(query.limit, query.offset)?;
|
||||
ctx.movie_repository
|
||||
.list_movies(&page, query.search.as_deref())
|
||||
.await
|
||||
}
|
||||
@@ -3,7 +3,7 @@ use uuid::Uuid;
|
||||
|
||||
use domain::{errors::DomainError, value_objects::Email};
|
||||
|
||||
use crate::{commands::LoginCommand, context::AppContext};
|
||||
use crate::{context::AppContext, queries::LoginQuery};
|
||||
|
||||
pub struct LoginResult {
|
||||
pub token: String,
|
||||
@@ -12,8 +12,8 @@ pub struct LoginResult {
|
||||
pub expires_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
pub async fn execute(ctx: &AppContext, cmd: LoginCommand) -> Result<LoginResult, DomainError> {
|
||||
let email = Email::new(cmd.email)?;
|
||||
pub async fn execute(ctx: &AppContext, query: LoginQuery) -> Result<LoginResult, DomainError> {
|
||||
let email = Email::new(query.email)?;
|
||||
let user = ctx
|
||||
.user_repository
|
||||
.find_by_email(&email)
|
||||
@@ -22,7 +22,7 @@ pub async fn execute(ctx: &AppContext, cmd: LoginCommand) -> Result<LoginResult,
|
||||
|
||||
let valid = ctx
|
||||
.password_hasher
|
||||
.verify(&cmd.password, user.password_hash())
|
||||
.verify(&query.password, user.password_hash())
|
||||
.await?;
|
||||
if !valid {
|
||||
return Err(DomainError::Unauthorized("Invalid credentials".into()));
|
||||
|
||||
@@ -11,6 +11,7 @@ pub mod export_diary;
|
||||
pub mod get_activity_feed;
|
||||
pub mod get_diary;
|
||||
pub mod get_movie_social_page;
|
||||
pub mod get_movies;
|
||||
pub mod get_review_history;
|
||||
pub mod get_user_profile;
|
||||
pub mod get_users;
|
||||
|
||||
@@ -4,14 +4,7 @@ use domain::{
|
||||
value_objects::UserId,
|
||||
};
|
||||
|
||||
use crate::context::AppContext;
|
||||
|
||||
pub struct UpdateProfileCommand {
|
||||
pub user_id: uuid::Uuid,
|
||||
pub bio: Option<String>,
|
||||
pub avatar_bytes: Option<Vec<u8>>,
|
||||
pub avatar_content_type: Option<String>,
|
||||
}
|
||||
use crate::{commands::UpdateProfileCommand, context::AppContext};
|
||||
|
||||
pub async fn execute(ctx: &AppContext, cmd: UpdateProfileCommand) -> Result<(), DomainError> {
|
||||
let user_id = UserId::from_uuid(cmd.user_id);
|
||||
|
||||
@@ -8,7 +8,7 @@ use crate::{
|
||||
AnnotatedRow, DiaryEntry, DiaryFilter, ExportFormat, FeedEntry, FieldMapping,
|
||||
FileFormat, ImportError, ImportProfile, ImportSession, Movie, MovieProfile, MovieStats,
|
||||
ParsedFile, Review, ReviewHistory, User, UserStats, UserSummary, UserTrends,
|
||||
collections::{PageParams, Paginated},
|
||||
collections::{self, PageParams, Paginated},
|
||||
},
|
||||
value_objects::{
|
||||
Email, ExternalMetadataId, ImportProfileId, ImportSessionId, MovieId, MovieTitle,
|
||||
@@ -83,6 +83,11 @@ pub trait MovieRepository: Send + Sync {
|
||||
) -> Result<Vec<Movie>, DomainError>;
|
||||
async fn upsert_movie(&self, movie: &Movie) -> Result<(), DomainError>;
|
||||
async fn delete_movie(&self, movie_id: &MovieId) -> Result<(), DomainError>;
|
||||
async fn list_movies(
|
||||
&self,
|
||||
page: &collections::PageParams,
|
||||
search: Option<&str>,
|
||||
) -> Result<collections::Paginated<Movie>, DomainError>;
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
|
||||
@@ -177,6 +177,9 @@ mod tests {
|
||||
async fn delete_movie(&self, _: &MovieId) -> Result<(), DomainError> {
|
||||
panic!()
|
||||
}
|
||||
async fn list_movies(&self, _: &domain::models::collections::PageParams, _: Option<&str>) -> Result<domain::models::collections::Paginated<Movie>, DomainError> {
|
||||
panic!()
|
||||
}
|
||||
}
|
||||
#[async_trait::async_trait]
|
||||
impl ReviewRepository for Panic {
|
||||
|
||||
@@ -10,15 +10,15 @@ use std::str::FromStr;
|
||||
|
||||
use application::{
|
||||
commands::{
|
||||
DeleteReviewCommand, ExportCommand, LoginCommand, RegisterCommand, SyncPosterCommand,
|
||||
DeleteReviewCommand, RegisterCommand, SyncPosterCommand,
|
||||
},
|
||||
queries::{
|
||||
GetActivityFeedQuery, GetMovieSocialPageQuery, GetReviewHistoryQuery, GetUserProfileQuery,
|
||||
GetUsersQuery,
|
||||
ExportQuery, GetActivityFeedQuery, GetMovieSocialPageQuery, GetMoviesQuery,
|
||||
GetReviewHistoryQuery, GetUserProfileQuery, GetUsersQuery, LoginQuery,
|
||||
},
|
||||
use_cases::{
|
||||
delete_review, export_diary as export_diary_uc, get_activity_feed as get_feed_uc,
|
||||
get_diary, get_movie_social_page, get_review_history,
|
||||
get_diary, get_movie_social_page, get_movies, get_review_history,
|
||||
get_user_profile as get_user_profile_uc, get_users, log_review, login as login_uc,
|
||||
register as register_uc, sync_poster, update_profile,
|
||||
},
|
||||
@@ -40,9 +40,9 @@ use api_types::{
|
||||
DiaryQueryParams, DiaryResponse, DirectorStatDto, ExportQueryParams, FeedEntryDto,
|
||||
GenreDto, KeywordDto, LogReviewRequest, LoginRequest, LoginResponse, MonthActivityDto,
|
||||
MonthlyRatingDto, MovieDetailResponse, MovieDto, MovieProfileResponse, MovieStatsDto,
|
||||
PaginationQueryParams, ProfileResponse, RegisterRequest, ReviewDto, ReviewHistoryResponse,
|
||||
SocialFeedResponse, SocialReviewDto, UserProfileQueryParams, UserProfileResponse, UserStatsDto,
|
||||
UserSummaryDto, UserTrendsDto, UsersResponse,
|
||||
MoviesQueryParams, MoviesResponse, PaginationQueryParams, ProfileResponse, RegisterRequest,
|
||||
ReviewDto, ReviewHistoryResponse, SocialFeedResponse, SocialReviewDto, UserProfileQueryParams,
|
||||
UserProfileResponse, UserStatsDto, UserSummaryDto, UserTrendsDto, UsersResponse,
|
||||
};
|
||||
use crate::{
|
||||
errors::ApiError,
|
||||
@@ -74,6 +74,35 @@ pub async fn get_diary(
|
||||
}))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
get, path = "/api/v1/movies",
|
||||
params(MoviesQueryParams),
|
||||
responses(
|
||||
(status = 200, body = MoviesResponse),
|
||||
)
|
||||
)]
|
||||
pub async fn list_movies(
|
||||
State(state): State<AppState>,
|
||||
Query(params): Query<MoviesQueryParams>,
|
||||
) -> Result<Json<MoviesResponse>, ApiError> {
|
||||
let page = get_movies::execute(
|
||||
&state.app_ctx,
|
||||
GetMoviesQuery {
|
||||
limit: params.limit,
|
||||
offset: params.offset,
|
||||
search: params.search,
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(Json(MoviesResponse {
|
||||
items: page.items.iter().map(movie_to_dto).collect(),
|
||||
total_count: page.total_count,
|
||||
limit: page.limit,
|
||||
offset: page.offset,
|
||||
}))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
get, path = "/api/v1/movies/{id}/history",
|
||||
params(("id" = Uuid, Path, description = "Movie ID")),
|
||||
@@ -179,7 +208,7 @@ pub async fn login(
|
||||
) -> Result<Json<LoginResponse>, ApiError> {
|
||||
let result = login_uc::execute(
|
||||
&state.app_ctx,
|
||||
LoginCommand {
|
||||
LoginQuery {
|
||||
email: req.email,
|
||||
password: req.password,
|
||||
},
|
||||
@@ -415,7 +444,7 @@ pub async fn update_profile_handler(
|
||||
}
|
||||
}
|
||||
|
||||
let cmd = update_profile::UpdateProfileCommand {
|
||||
let cmd = application::commands::UpdateProfileCommand {
|
||||
user_id: user_id.value(),
|
||||
bio,
|
||||
avatar_bytes,
|
||||
@@ -1036,11 +1065,11 @@ pub async fn export_diary(
|
||||
ExportFormat::Csv => ("text/csv; charset=utf-8", "diary.csv"),
|
||||
ExportFormat::Json => ("application/json", "diary.json"),
|
||||
};
|
||||
let cmd = ExportCommand {
|
||||
let query = ExportQuery {
|
||||
user_id: user.0.value(),
|
||||
format,
|
||||
};
|
||||
match export_diary_uc::execute(&state.app_ctx, cmd).await {
|
||||
match export_diary_uc::execute(&state.app_ctx, query).await {
|
||||
Ok(bytes) => (
|
||||
StatusCode::OK,
|
||||
[
|
||||
|
||||
@@ -15,12 +15,12 @@ use application::ports::{
|
||||
FollowersPageData, FollowingPageData,
|
||||
};
|
||||
use application::{
|
||||
commands::{DeleteReviewCommand, ExportCommand, LoginCommand, RegisterCommand},
|
||||
commands::{DeleteReviewCommand, RegisterCommand},
|
||||
queries::{ExportQuery, GetMovieSocialPageQuery, LoginQuery},
|
||||
ports::{
|
||||
HtmlPageContext, LoginPageData, MovieDetailPageData, NewReviewPageData,
|
||||
ProfileSettingsPageData, RegisterPageData, RemoteActorView,
|
||||
},
|
||||
queries::GetMovieSocialPageQuery,
|
||||
use_cases::{
|
||||
delete_review, export_diary as export_diary_uc, get_movie_social_page, log_review,
|
||||
login as login_uc, register as register_uc, update_profile,
|
||||
@@ -133,7 +133,7 @@ pub async fn post_login(
|
||||
}
|
||||
match login_uc::execute(
|
||||
&state.app_ctx,
|
||||
LoginCommand {
|
||||
LoginQuery {
|
||||
email: form.email,
|
||||
password: form.password,
|
||||
},
|
||||
@@ -215,7 +215,7 @@ pub async fn post_register(
|
||||
.await
|
||||
{
|
||||
Ok(_) => {
|
||||
match login_uc::execute(&state.app_ctx, LoginCommand { email, password }).await {
|
||||
match login_uc::execute(&state.app_ctx, LoginQuery { email, password }).await {
|
||||
Ok(result) => {
|
||||
let max_age = (result.expires_at - Utc::now()).num_seconds().max(0);
|
||||
let cookie = set_cookie_header(&result.token, max_age);
|
||||
@@ -320,11 +320,11 @@ pub async fn get_export(
|
||||
ExportFormat::Csv => ("text/csv; charset=utf-8", "diary.csv"),
|
||||
ExportFormat::Json => ("application/json", "diary.json"),
|
||||
};
|
||||
let cmd = ExportCommand {
|
||||
let query = ExportQuery {
|
||||
user_id: user_id.value(),
|
||||
format,
|
||||
};
|
||||
match export_diary_uc::execute(&state.app_ctx, cmd).await {
|
||||
match export_diary_uc::execute(&state.app_ctx, query).await {
|
||||
Ok(bytes) => (
|
||||
StatusCode::OK,
|
||||
[
|
||||
@@ -1230,7 +1230,7 @@ pub async fn post_profile_settings(
|
||||
}
|
||||
}
|
||||
|
||||
let cmd = update_profile::UpdateProfileCommand {
|
||||
let cmd = application::commands::UpdateProfileCommand {
|
||||
user_id: user_id.value(),
|
||||
bio,
|
||||
avatar_bytes,
|
||||
|
||||
@@ -1,20 +1,30 @@
|
||||
use api_types::{
|
||||
DirectorStatDto, MonthActivityDto, MonthlyRatingDto, MovieDetailResponse, MovieDto,
|
||||
MovieStatsDto, ReviewHistoryResponse, SocialFeedResponse, SocialReviewDto, UserTrendsDto,
|
||||
CastMemberDto, CrewMemberDto, DirectorStatDto, GenreDto, KeywordDto, MonthActivityDto,
|
||||
MonthlyRatingDto, MovieDetailResponse, MovieDto, MovieProfileResponse, MovieStatsDto,
|
||||
MoviesQueryParams, MoviesResponse, ReviewHistoryResponse, SocialFeedResponse, SocialReviewDto,
|
||||
UserTrendsDto,
|
||||
};
|
||||
use utoipa::OpenApi;
|
||||
|
||||
#[derive(OpenApi)]
|
||||
#[openapi(
|
||||
paths(
|
||||
crate::handlers::api::list_movies,
|
||||
crate::handlers::api::get_movie_detail,
|
||||
crate::handlers::api::get_review_history,
|
||||
crate::handlers::api::get_movie_profile,
|
||||
crate::handlers::api::sync_poster,
|
||||
),
|
||||
components(schemas(
|
||||
MoviesResponse,
|
||||
MovieDto,
|
||||
MovieDetailResponse,
|
||||
MovieStatsDto,
|
||||
MovieProfileResponse,
|
||||
GenreDto,
|
||||
KeywordDto,
|
||||
CastMemberDto,
|
||||
CrewMemberDto,
|
||||
ReviewHistoryResponse,
|
||||
SocialFeedResponse,
|
||||
SocialReviewDto,
|
||||
|
||||
@@ -177,6 +177,7 @@ fn api_routes(rate_limit: u64) -> Router<AppState> {
|
||||
"/movies/{id}/history",
|
||||
routing::get(handlers::api::get_review_history),
|
||||
)
|
||||
.route("/movies", routing::get(handlers::api::list_movies))
|
||||
.route(
|
||||
"/movies/{id}",
|
||||
routing::get(handlers::api::get_movie_detail),
|
||||
|
||||
Reference in New Issue
Block a user