User-owned named lists of mangas with an add-to-collection modal on the manga page and dedicated /collections and /collections/:id pages. - Schema (0010): `collections` (per-user case-insensitive name uniqueness) + `collection_mangas` join with cascade FKs. - Endpoints: full CRUD on `/v1/collections`, idempotent add/remove for `/v1/collections/:id/mangas`, and `/v1/mangas/:id/my-collections` for the modal's pre-checked state. Owner-mismatch surfaces as 404 (not 403) so the API doesn't disclose collection existence to non-owners; the frontend funnels 401 to /login. Three-state PATCH via a new shared `domain::patch::Patch<T>` lets clients distinguish "leave alone", "clear", and "set" for description. - Frontend: reusable `Modal` component (focus trap, opt-in backdrop close, ESC) and `AddToCollectionModal` with optimistic toggling that's race-safe under fast clicks. /collections page renders cover-collage cards; /collections/:id is editable with per-card remove. Top nav gets a Collections link. 155 backend tests (incl. 21 collection tests covering ownership, idempotence, sample-cover enrichment, three-state PATCH, FK race); 88 frontend tests; svelte-check clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
32 lines
1.3 KiB
SQL
32 lines
1.3 KiB
SQL
-- User-owned manga collections. Each user can curate any number of
|
|
-- named lists (e.g., "Favorites", "Reading list"); mangas can belong
|
|
-- to many collections of many users without restriction.
|
|
|
|
CREATE TABLE collections (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
name text NOT NULL,
|
|
description text,
|
|
created_at timestamptz NOT NULL DEFAULT now(),
|
|
updated_at timestamptz NOT NULL DEFAULT now()
|
|
);
|
|
|
|
-- Per-user case-insensitive name uniqueness so "Favorites" and
|
|
-- "favorites" don't both end up in someone's sidebar.
|
|
CREATE UNIQUE INDEX collections_user_name_lower_uniq
|
|
ON collections (user_id, lower(name));
|
|
|
|
CREATE INDEX collections_user_idx ON collections (user_id, created_at DESC);
|
|
|
|
CREATE TABLE collection_mangas (
|
|
collection_id uuid NOT NULL REFERENCES collections(id) ON DELETE CASCADE,
|
|
manga_id uuid NOT NULL REFERENCES mangas(id) ON DELETE CASCADE,
|
|
added_at timestamptz NOT NULL DEFAULT now(),
|
|
PRIMARY KEY (collection_id, manga_id)
|
|
);
|
|
|
|
-- Reverse lookup: which collections contain this manga? Used by the
|
|
-- "Add to collection" modal to pre-check the boxes for the user's
|
|
-- collections this manga is already in.
|
|
CREATE INDEX collection_mangas_manga_idx ON collection_mangas (manga_id);
|