refactor: deps cleanup, split openapi, extract api-types crate
This commit is contained in:
@@ -31,19 +31,22 @@ use domain::{
|
||||
};
|
||||
|
||||
#[cfg(feature = "federation")]
|
||||
use crate::dtos::{ActorListResponse, ActorUrlRequest, FollowRequest, RemoteActorDto};
|
||||
use api_types::{
|
||||
ActorListResponse, ActorUrlRequest, AddBlockedDomainRequest, BlockedActorResponse,
|
||||
BlockedDomainResponse, FollowRequest, RemoteActorDto,
|
||||
};
|
||||
use api_types::{
|
||||
ActivityFeedQueryParams, ActivityFeedResponse, DiaryEntryDto, DiaryQueryParams, DiaryResponse,
|
||||
DirectorStatDto, ExportQueryParams, FeedEntryDto, LogReviewRequest, LoginRequest, LoginResponse,
|
||||
MonthActivityDto, MonthlyRatingDto, MovieDetailResponse, MovieDto, MovieStatsDto,
|
||||
PaginationQueryParams, ProfileResponse, RegisterRequest, ReviewDto, ReviewHistoryResponse,
|
||||
SocialFeedResponse, SocialReviewDto, UserProfileQueryParams, UserProfileResponse, UserStatsDto,
|
||||
UserSummaryDto, UserTrendsDto, UsersResponse,
|
||||
};
|
||||
use crate::{
|
||||
dtos::{
|
||||
ActivityFeedQueryParams, ActivityFeedResponse, DiaryEntryDto, DiaryQueryParams,
|
||||
DiaryResponse, DirectorStatDto, ExportQueryParams, FeedEntryDto, LogReviewData,
|
||||
LogReviewRequest, LoginRequest, LoginResponse, MonthActivityDto, MonthlyRatingDto,
|
||||
MovieDetailResponse, MovieDto, MovieStatsDto, PaginationQueryParams, ProfileResponse,
|
||||
RegisterRequest, ReviewDto, ReviewHistoryResponse, SocialFeedResponse, SocialReviewDto,
|
||||
UserProfileQueryParams, UserProfileResponse, UserStatsDto, UserSummaryDto, UserTrendsDto,
|
||||
UsersResponse,
|
||||
},
|
||||
errors::ApiError,
|
||||
extractors::AuthenticatedUser,
|
||||
forms::{to_diary_query, LogReviewData},
|
||||
state::AppState,
|
||||
};
|
||||
|
||||
@@ -60,7 +63,7 @@ pub async fn get_diary(
|
||||
State(state): State<AppState>,
|
||||
Query(params): Query<DiaryQueryParams>,
|
||||
) -> Result<Json<DiaryResponse>, ApiError> {
|
||||
let page = get_diary::execute(&state.app_ctx, params.into()).await?;
|
||||
let page = get_diary::execute(&state.app_ctx, to_diary_query(params)).await?;
|
||||
|
||||
Ok(Json(DiaryResponse {
|
||||
items: page.items.iter().map(entry_to_dto).collect(),
|
||||
@@ -415,7 +418,7 @@ fn entry_to_dto(entry: &DiaryEntry) -> DiaryEntryDto {
|
||||
#[utoipa::path(
|
||||
get, path = "/api/v1/admin/blocked-domains",
|
||||
responses(
|
||||
(status = 200, body = Vec<crate::dtos::BlockedDomainResponse>),
|
||||
(status = 200, body = Vec<BlockedDomainResponse>),
|
||||
(status = 401, description = "Unauthorized"),
|
||||
(status = 403, description = "Forbidden — admin only"),
|
||||
),
|
||||
@@ -427,9 +430,9 @@ pub async fn get_blocked_domains_admin(
|
||||
) -> impl IntoResponse {
|
||||
match state.ap_service.get_blocked_domains().await {
|
||||
Ok(domains) => {
|
||||
let response: Vec<crate::dtos::BlockedDomainResponse> = domains
|
||||
let response: Vec<BlockedDomainResponse> = domains
|
||||
.into_iter()
|
||||
.map(|d| crate::dtos::BlockedDomainResponse {
|
||||
.map(|d| BlockedDomainResponse {
|
||||
domain: d.domain,
|
||||
reason: d.reason,
|
||||
blocked_at: d.blocked_at,
|
||||
@@ -444,7 +447,7 @@ pub async fn get_blocked_domains_admin(
|
||||
#[cfg(feature = "federation")]
|
||||
#[utoipa::path(
|
||||
post, path = "/api/v1/admin/blocked-domains",
|
||||
request_body = crate::dtos::AddBlockedDomainRequest,
|
||||
request_body = AddBlockedDomainRequest,
|
||||
responses(
|
||||
(status = 201, description = "Domain blocked"),
|
||||
(status = 401, description = "Unauthorized"),
|
||||
@@ -455,7 +458,7 @@ pub async fn get_blocked_domains_admin(
|
||||
pub async fn add_blocked_domain_admin(
|
||||
State(state): State<AppState>,
|
||||
_admin: crate::extractors::AdminUser,
|
||||
axum::Json(body): axum::Json<crate::dtos::AddBlockedDomainRequest>,
|
||||
axum::Json(body): axum::Json<AddBlockedDomainRequest>,
|
||||
) -> impl IntoResponse {
|
||||
match state.ap_service.add_blocked_domain(&body.domain, body.reason.as_deref()).await {
|
||||
Ok(()) => StatusCode::CREATED.into_response(),
|
||||
@@ -531,7 +534,7 @@ pub async fn unblock_actor_api(
|
||||
#[utoipa::path(
|
||||
get, path = "/api/v1/social/blocked",
|
||||
responses(
|
||||
(status = 200, body = Vec<crate::dtos::BlockedActorResponse>),
|
||||
(status = 200, body = Vec<BlockedActorResponse>),
|
||||
(status = 401, description = "Unauthorized"),
|
||||
),
|
||||
security(("bearer_auth" = []))
|
||||
@@ -542,9 +545,9 @@ pub async fn get_blocked_actors_api(
|
||||
) -> impl IntoResponse {
|
||||
match state.ap_service.get_blocked_actors(user.0.value()).await {
|
||||
Ok(actors) => {
|
||||
let response: Vec<crate::dtos::BlockedActorResponse> = actors
|
||||
let response: Vec<BlockedActorResponse> = actors
|
||||
.into_iter()
|
||||
.map(|a| crate::dtos::BlockedActorResponse {
|
||||
.map(|a| BlockedActorResponse {
|
||||
url: a.url,
|
||||
handle: a.handle,
|
||||
display_name: a.display_name,
|
||||
|
||||
@@ -30,12 +30,10 @@ use domain::models::ExportFormat;
|
||||
use domain::{errors::DomainError, value_objects::UserId};
|
||||
|
||||
#[cfg(feature = "federation")]
|
||||
use crate::dtos::{ActorUrlForm, BlockDomainForm, FollowForm, FollowerActionForm, RemoveDomainForm, UnfollowForm};
|
||||
use crate::forms::{ActorUrlForm, BlockDomainForm, FollowForm, FollowerActionForm, RemoveDomainForm, UnfollowForm};
|
||||
use crate::{
|
||||
csrf::CsrfToken,
|
||||
dtos::{
|
||||
ErrorQuery, FeedQueryParams, LogReviewData, LogReviewForm, LoginForm, RegisterForm,
|
||||
},
|
||||
forms::{ErrorQuery, FeedQueryParams, LogReviewData, LogReviewForm, LoginForm, RegisterForm},
|
||||
extractors::{AdminUser, OptionalCookieUser, RequiredCookieUser},
|
||||
state::AppState,
|
||||
};
|
||||
@@ -280,7 +278,7 @@ pub async fn post_delete_review(
|
||||
RequiredCookieUser(user_id): RequiredCookieUser,
|
||||
Extension(csrf): Extension<CsrfToken>,
|
||||
Path(review_id): Path<Uuid>,
|
||||
Form(form): Form<crate::dtos::DeleteRedirectForm>,
|
||||
Form(form): Form<crate::forms::DeleteRedirectForm>,
|
||||
) -> impl IntoResponse {
|
||||
if crate::csrf::mismatch(&csrf, &form.csrf_token) {
|
||||
return StatusCode::FORBIDDEN.into_response();
|
||||
@@ -311,7 +309,7 @@ pub async fn post_delete_review(
|
||||
pub async fn get_export(
|
||||
State(state): State<AppState>,
|
||||
RequiredCookieUser(user_id): RequiredCookieUser,
|
||||
Query(params): Query<crate::dtos::ExportQueryParams>,
|
||||
Query(params): Query<api_types::ExportQueryParams>,
|
||||
) -> impl IntoResponse {
|
||||
let format = match params.format.as_str() {
|
||||
"csv" => ExportFormat::Csv,
|
||||
@@ -504,7 +502,7 @@ pub async fn get_user_profile(
|
||||
State(state): State<AppState>,
|
||||
Path(profile_user_uuid): Path<Uuid>,
|
||||
headers: axum::http::HeaderMap,
|
||||
Query(params): Query<crate::dtos::ProfileQueryParams>,
|
||||
Query(params): Query<crate::forms::ProfileQueryParams>,
|
||||
Extension(csrf): Extension<CsrfToken>,
|
||||
) -> impl IntoResponse {
|
||||
// Content negotiation: AP clients request application/activity+json
|
||||
@@ -800,7 +798,7 @@ pub async fn get_following_page(
|
||||
RequiredCookieUser(user_id): RequiredCookieUser,
|
||||
State(state): State<AppState>,
|
||||
Path(profile_user_uuid): Path<Uuid>,
|
||||
Query(params): Query<crate::dtos::ErrorQuery>,
|
||||
Query(params): Query<crate::forms::ErrorQuery>,
|
||||
Extension(csrf): Extension<CsrfToken>,
|
||||
) -> impl IntoResponse {
|
||||
if user_id.value() != profile_user_uuid {
|
||||
@@ -850,7 +848,7 @@ pub async fn get_followers_page(
|
||||
RequiredCookieUser(user_id): RequiredCookieUser,
|
||||
State(state): State<AppState>,
|
||||
Path(profile_user_uuid): Path<Uuid>,
|
||||
Query(params): Query<crate::dtos::ErrorQuery>,
|
||||
Query(params): Query<crate::forms::ErrorQuery>,
|
||||
Extension(csrf): Extension<CsrfToken>,
|
||||
) -> impl IntoResponse {
|
||||
if user_id.value() != profile_user_uuid {
|
||||
@@ -935,7 +933,7 @@ pub async fn get_movie_detail(
|
||||
OptionalCookieUser(user_id): OptionalCookieUser,
|
||||
State(state): State<AppState>,
|
||||
Path(movie_id): Path<uuid::Uuid>,
|
||||
Query(params): Query<crate::dtos::PaginationQueryParams>,
|
||||
Query(params): Query<api_types::PaginationQueryParams>,
|
||||
Extension(csrf): Extension<CsrfToken>,
|
||||
) -> impl IntoResponse {
|
||||
let ctx = build_page_context(&state, user_id, csrf.0).await;
|
||||
|
||||
@@ -4,7 +4,11 @@ use axum::{
|
||||
http::StatusCode,
|
||||
response::{Html, IntoResponse, Redirect},
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use api_types::{
|
||||
ApplyMappingRequest, ConfirmRequest, SaveProfileRequest, SessionCreatedResponse,
|
||||
SessionStateResponse,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use application::{
|
||||
@@ -465,13 +469,6 @@ pub async fn get_import_done(
|
||||
|
||||
// ── REST API handlers ──────────────────────────────────────────────────────
|
||||
|
||||
#[derive(Serialize, utoipa::ToSchema)]
|
||||
pub struct SessionCreatedResponse {
|
||||
pub session_id: String,
|
||||
pub columns: Vec<String>,
|
||||
pub sample_rows: Vec<Vec<String>>,
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
post, path = "/api/v1/import/sessions",
|
||||
request_body(content_type = "multipart/form-data", description = "file (binary) + format (csv|json|xlsx)"),
|
||||
@@ -544,14 +541,6 @@ pub async fn api_post_session(
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, utoipa::ToSchema)]
|
||||
pub struct SessionStateResponse {
|
||||
pub session_id: String,
|
||||
pub columns: Vec<String>,
|
||||
pub has_mappings: bool,
|
||||
pub row_count: usize,
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
get, path = "/api/v1/import/sessions/{id}",
|
||||
params(("id" = String, Path, description = "Import session UUID")),
|
||||
@@ -607,23 +596,6 @@ pub async fn api_get_session(
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, utoipa::ToSchema)]
|
||||
pub struct ApiFieldMapping {
|
||||
/// Column name in the source file
|
||||
pub source_column: String,
|
||||
/// Domain field: title | release_year | director | rating | watched_at | comment | external_metadata_id
|
||||
pub domain_field: String,
|
||||
/// For rating fields: multiply raw value by this factor (e.g. 0.5 for 10-point → 5-point scale)
|
||||
pub rating_scale: Option<f64>,
|
||||
/// For watched_at fields: strftime format hint (e.g. "%d/%m/%Y")
|
||||
pub date_format: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, utoipa::ToSchema)]
|
||||
pub struct ApplyMappingRequest {
|
||||
pub mappings: Vec<ApiFieldMapping>,
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
put, path = "/api/v1/import/sessions/{id}/mapping",
|
||||
params(("id" = String, Path, description = "Import session UUID")),
|
||||
@@ -692,12 +664,6 @@ pub async fn api_put_mapping(
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, utoipa::ToSchema)]
|
||||
pub struct ConfirmRequest {
|
||||
/// Indices (0-based) of rows from the mapping preview to import
|
||||
pub confirmed_indices: Vec<usize>,
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
post, path = "/api/v1/import/sessions/{id}/confirm",
|
||||
params(("id" = String, Path, description = "Import session UUID")),
|
||||
@@ -776,14 +742,6 @@ pub async fn api_get_profiles(
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, utoipa::ToSchema)]
|
||||
pub struct SaveProfileRequest {
|
||||
/// Session UUID whose current field_mappings to save
|
||||
pub session_id: String,
|
||||
/// Human-readable profile name (e.g. "Letterboxd")
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
post, path = "/api/v1/import/profiles",
|
||||
request_body = SaveProfileRequest,
|
||||
|
||||
Reference in New Issue
Block a user