A "Continue reading" shelf listing series the reader already finished (read to the last page, nothing new) is odd. Expose the last-read chapter's page_count on the read-progress list, and filter the shelf to drop entries that are caught up (on the last page of the latest chapter with no new chapters). Mid-chapter and has-new-chapters entries stay; the full history view is unaffected. Finished detection is a pure, unit-tested helper. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
67 lines
2.6 KiB
Rust
67 lines
2.6 KiB
Rust
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<Uuid>,
|
|
pub page: i32,
|
|
pub updated_at: DateTime<Utc>,
|
|
}
|
|
|
|
/// 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<String>,
|
|
pub chapter_id: Option<Uuid>,
|
|
/// `None` when the chapter was deleted after this row was written
|
|
/// (FK ON DELETE SET NULL on `chapter_id`).
|
|
pub chapter_number: Option<i32>,
|
|
/// 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<i32>,
|
|
pub page: i32,
|
|
pub updated_at: DateTime<Utc>,
|
|
/// 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<Uuid>,
|
|
pub chapter_number: Option<i32>,
|
|
pub page: i32,
|
|
pub updated_at: DateTime<Utc>,
|
|
/// 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<Uuid>,
|
|
pub page: Option<i32>,
|
|
}
|