refactor: deps cleanup, split openapi, extract api-types crate

This commit is contained in:
2026-05-12 11:54:00 +02:00
parent 2d6121239f
commit 99ce81efe5
46 changed files with 695 additions and 808 deletions

View File

@@ -0,0 +1,9 @@
[package]
name = "api-types"
version = "0.1.0"
edition = "2024"
[dependencies]
serde = { workspace = true }
uuid = { workspace = true }
utoipa = { version = "5.5.0", features = ["axum_extras", "uuid"] }

View File

@@ -0,0 +1,23 @@
use serde::{Deserialize, Serialize};
use uuid::Uuid;
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct LoginRequest {
pub email: String,
pub password: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct LoginResponse {
pub token: String,
pub user_id: Uuid,
pub email: String,
pub expires_at: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct RegisterRequest {
pub email: String,
pub username: String,
pub password: String,
}

View File

@@ -0,0 +1,7 @@
use serde::Deserialize;
#[derive(Debug, Clone, Deserialize, Default)]
pub struct PaginationQueryParams {
pub limit: Option<u32>,
pub offset: Option<u32>,
}

View File

@@ -0,0 +1,78 @@
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use crate::movies::{MovieDto, ReviewDto};
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct LogReviewRequest {
#[serde(skip_serializing_if = "Option::is_none")]
pub external_metadata_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub manual_title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub manual_release_year: Option<u16>,
#[serde(skip_serializing_if = "Option::is_none")]
pub manual_director: Option<String>,
pub rating: u8,
#[serde(skip_serializing_if = "Option::is_none")]
pub comment: Option<String>,
pub watched_at: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct DiaryEntryDto {
pub movie: MovieDto,
pub review: ReviewDto,
}
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct DiaryResponse {
pub items: Vec<DiaryEntryDto>,
pub total_count: u64,
pub limit: u32,
pub offset: u32,
}
#[derive(Debug, Clone, Deserialize, utoipa::IntoParams)]
#[into_params(parameter_in = Query)]
pub struct DiaryQueryParams {
pub limit: Option<u32>,
pub offset: Option<u32>,
pub sort_by: Option<String>,
pub movie_id: Option<Uuid>,
}
#[derive(Debug, Clone, Deserialize, utoipa::IntoParams)]
#[into_params(parameter_in = Query)]
pub struct ActivityFeedQueryParams {
pub limit: Option<u32>,
pub offset: Option<u32>,
}
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct FeedEntryDto {
pub movie: MovieDto,
pub review: ReviewDto,
pub user_email: String,
pub user_display_name: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct ActivityFeedResponse {
pub items: Vec<FeedEntryDto>,
pub total_count: u64,
pub limit: u32,
pub offset: u32,
}
#[derive(Debug, Clone, Deserialize, utoipa::IntoParams)]
#[into_params(parameter_in = Query)]
pub struct ExportQueryParams {
/// Output format: `csv` (default) or `json`
#[serde(default = "default_export_format")]
pub format: String,
}
fn default_export_format() -> String {
"csv".to_string()
}

View File

@@ -0,0 +1,47 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct SessionCreatedResponse {
pub session_id: String,
pub columns: Vec<String>,
pub sample_rows: Vec<Vec<String>>,
}
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct SessionStateResponse {
pub session_id: String,
pub columns: Vec<String>,
pub has_mappings: bool,
pub row_count: usize,
}
#[derive(Debug, Clone, Serialize, 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(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct ApplyMappingRequest {
pub mappings: Vec<ApiFieldMapping>,
}
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct ConfirmRequest {
/// Indices (0-based) of rows from the mapping preview to import
pub confirmed_indices: Vec<usize>,
}
#[derive(Debug, Clone, Serialize, 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,
}

View File

@@ -0,0 +1,15 @@
pub mod auth;
pub mod common;
pub mod diary;
pub mod import;
pub mod movies;
pub mod social;
pub mod users;
pub use auth::*;
pub use common::*;
pub use diary::*;
pub use import::*;
pub use movies::*;
pub use social::*;
pub use users::*;

View File

@@ -0,0 +1,58 @@
use serde::{Deserialize, Serialize};
use uuid::Uuid;
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct MovieDto {
pub id: Uuid,
pub title: String,
pub release_year: u16,
pub director: Option<String>,
pub poster_path: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct ReviewDto {
pub id: Uuid,
pub rating: u8,
pub comment: Option<String>,
pub watched_at: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct ReviewHistoryResponse {
pub movie: MovieDto,
pub viewings: Vec<ReviewDto>,
pub trend: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct MovieStatsDto {
pub total_count: u64,
pub avg_rating: Option<f64>,
pub federated_count: u64,
pub rating_histogram: [u64; 5],
}
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct SocialReviewDto {
pub user_display: String,
pub rating: u8,
pub comment: Option<String>,
pub watched_at: String,
pub is_federated: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct SocialFeedResponse {
pub items: Vec<SocialReviewDto>,
pub total_count: u64,
pub limit: u32,
pub offset: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct MovieDetailResponse {
pub movie: MovieDto,
pub stats: MovieStatsDto,
pub reviews: SocialFeedResponse,
}

View File

@@ -0,0 +1,44 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct FollowRequest {
pub handle: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct ActorUrlRequest {
pub actor_url: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct RemoteActorDto {
pub handle: String,
pub display_name: Option<String>,
pub url: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct ActorListResponse {
pub actors: Vec<RemoteActorDto>,
}
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct BlockedDomainResponse {
pub domain: String,
pub reason: Option<String>,
pub blocked_at: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct AddBlockedDomainRequest {
pub domain: String,
pub reason: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct BlockedActorResponse {
pub url: String,
pub handle: String,
pub display_name: Option<String>,
pub avatar_url: Option<String>,
}

View File

@@ -0,0 +1,85 @@
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use crate::diary::{DiaryEntryDto, DiaryResponse};
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct UserSummaryDto {
pub id: Uuid,
pub email: String,
pub total_movies: i64,
pub avg_rating: Option<f64>,
}
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct UsersResponse {
pub users: Vec<UserSummaryDto>,
}
#[derive(Debug, Clone, Deserialize, utoipa::IntoParams)]
#[into_params(parameter_in = Query)]
pub struct UserProfileQueryParams {
/// One of: `recent` (default), `ratings`, `history`, `trends`
pub view: Option<String>,
pub limit: Option<u32>,
pub offset: Option<u32>,
}
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct UserStatsDto {
pub total_movies: i64,
pub avg_rating: Option<f64>,
pub favorite_director: Option<String>,
pub most_active_month: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct MonthActivityDto {
pub year_month: String,
pub month_label: String,
pub count: i64,
pub entries: Vec<DiaryEntryDto>,
}
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct MonthlyRatingDto {
pub year_month: String,
pub month_label: String,
pub avg_rating: f64,
pub count: i64,
}
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct DirectorStatDto {
pub director: String,
pub count: i64,
}
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct UserTrendsDto {
pub monthly_ratings: Vec<MonthlyRatingDto>,
pub top_directors: Vec<DirectorStatDto>,
pub max_director_count: i64,
}
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct UserProfileResponse {
pub user_id: Uuid,
pub username: String,
pub stats: UserStatsDto,
pub following_count: usize,
pub followers_count: usize,
/// Populated for view=recent and view=ratings
pub entries: Option<DiaryResponse>,
/// Populated for view=history
pub history: Option<Vec<MonthActivityDto>>,
/// Populated for view=trends
pub trends: Option<UserTrendsDto>,
}
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct ProfileResponse {
pub username: String,
pub bio: Option<String>,
pub avatar_url: Option<String>,
}