use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use sqlx::FromRow; use uuid::Uuid; #[derive(Debug, Clone, Serialize, Deserialize, FromRow)] pub struct ReadProgress { pub user_id: Uuid, pub manga_id: Uuid, pub chapter_id: Option, pub page: i32, pub updated_at: DateTime, } /// Enriched row for the history view — joins in the manga's title and /// cover plus the chapter number (when the chapter still exists) so a /// card can render without extra round-trips. #[derive(Debug, Clone, Serialize, FromRow)] pub struct ReadProgressSummary { pub manga_id: Uuid, pub manga_title: String, pub manga_cover_image_path: Option, pub chapter_id: Option, /// `None` when the chapter was deleted after this row was written /// (FK ON DELETE SET NULL on `chapter_id`). pub chapter_number: Option, pub page: i32, pub updated_at: DateTime, } /// Returned by `GET /me/read-progress/:manga_id`. Same shape as /// `ReadProgressSummary` minus the manga title/cover (the caller /// already knows them — they're on the manga detail page). Crucially /// includes `chapter_number` so the "Continue reading" CTA can render /// without resolving the chapter id against a paged chapters list. #[derive(Debug, Clone, Serialize, FromRow)] pub struct ReadProgressForManga { pub manga_id: Uuid, pub chapter_id: Option, pub chapter_number: Option, pub page: i32, pub updated_at: DateTime, } #[derive(Debug, Clone, Deserialize)] pub struct UpsertReadProgress { pub manga_id: Uuid, pub chapter_id: Option, pub page: Option, }