Files
Mangalord/backend/src/domain/read_progress.rs
MechaCat02 bfaa166e0a fix(home): keep finished series off the Continue-reading shelf
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>
2026-07-04 21:52:58 +02:00

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>,
}