Persist blob sizes (covers + chapter pages) and surface upload storage usage to admins in two places: - Admin System tab: total/covers/chapters totals, ratio of disk, average image sizes, and top-5 largest mangas/chapters leaderboards, behind a new GET /api/v1/admin/storage endpoint (separate from /admin/system so the cheap DB aggregates aren't coupled to its 250ms CPU sample). - Manga detail page: total chapter-content size and per-chapter size, with an em-dash for uncrawled or not-yet-measured chapters. Sizes are captured at write time (crawler put_stream return, uploaded page/cover byte length) into nullable pages.size_bytes / mangas.cover_size_bytes columns; NULL means "not yet measured", distinct from a real 0. A one-shot, idempotent admin "Backfill sizes" action (POST /api/v1/admin/storage/backfill) stats pre-existing blobs in keyset-paginated, capped batches and reports more_remaining so a large legacy library can be drained over multiple runs. Aggregates and leaderboards treat NULL honestly (only fully-measured entities are ranked); a dashboard banner flags unmeasured rows so partial figures aren't read as complete. persist_pages now prunes stale page rows on a shrinking re-crawl so totals and page_count stay consistent. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
28 lines
862 B
Rust
28 lines
862 B
Rust
use chrono::{DateTime, Utc};
|
|
use serde::{Deserialize, Serialize};
|
|
use sqlx::FromRow;
|
|
use uuid::Uuid;
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
|
|
pub struct Chapter {
|
|
pub id: Uuid,
|
|
pub manga_id: Uuid,
|
|
pub number: i32,
|
|
pub title: Option<String>,
|
|
pub page_count: i32,
|
|
pub created_at: DateTime<Utc>,
|
|
/// Total bytes of this chapter's stored page images. `Some(0)` for an
|
|
/// uncrawled chapter (no page rows). `None` when the size is unknown —
|
|
/// at least one page predates the size backfill — so the frontend can
|
|
/// show an em-dash instead of a misleading "0 B". `Some(n)` once every
|
|
/// page in the chapter has a measured size.
|
|
#[serde(default)]
|
|
pub size_bytes: Option<i64>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Deserialize)]
|
|
pub struct NewChapter {
|
|
pub number: i32,
|
|
pub title: Option<String>,
|
|
}
|