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, /// Page count of the last-read chapter (`None` when unknown / deleted). /// Lets a client distinguish a finished series (read to the last page, /// nothing new) from one still in progress — e.g. to keep finished /// series off a "Continue reading" shelf. pub chapter_page_count: Option, pub page: i32, pub updated_at: DateTime, /// How many chapters sit past the reader's last-read chapter (by /// chapter number) — a personal "new since you last read" count. /// `0` when the reader is caught up or when `chapter_number` is /// unknown (manga-level progress or a deleted chapter), so we never /// claim chapters are new when we can't place the reader. pub new_chapters_count: i64, } /// 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, /// Distinct chapter numbers past the last-read chapter — the detail /// page's authoritative "new since last read" count (computed over all /// chapters, not the client's paginated list). `0` when caught up or /// the position is unknown. See [`ReadProgressSummary::new_chapters_count`]. pub new_chapters_count: i64, } #[derive(Debug, Clone, Deserialize)] pub struct UpsertReadProgress { pub manga_id: Uuid, pub chapter_id: Option, pub page: Option, }