Files
mangaBookmark/CONTEXT.md
T
sulthan 08749df050 feat(backend)!: run on Postgres with a migration-owned schema (#28)
Swap modernc.org/sqlite for jackc/pgx/v5 with no observable change:
same endpoints, same wire format, same updated_at ordering rule.

The schema now comes from numbered SQL embedded in the binary and
applied on startup, one transaction each, recorded in
schema_migrations. That replaces two pieces of SQLite-era machinery,
both deleted rather than ported: the column probing (Postgres has ADD
COLUMN IF NOT EXISTS, and there is no legacy database left to probe)
and the Asura key rewrite, which has run clean on every start for
months now that the userscripts strip build hashes before writing. Its
regexp survives as latest.asuraBuildHash, where the poller still needs
it to scope chapter links to a series whose slug carries a rotating
hash.

Types get real: favorite is a boolean, chapter numbers double
precision, timestamps stay unix-ms bigint. SQLite's null-safe IS NOT
becomes IS DISTINCT FROM, which is what implements the rule that only
reading progress reorders a list. Inside COALESCE/NULLIF the status
and kind parameters need an explicit ::text -- there is no target
column to infer from and Postgres refuses to guess.

Tests lose their free t.TempDir() database, so Docker is now a hard
prerequisite for `go test ./...`: internal/pgtest starts one
postgres:17-alpine per test binary and hands each test a database of
its own.

Also lands CONTEXT.md and the four ADRs written while scoping #18.

BREAKING CHANGE: DB_PATH is retired for DATABASE_URL, which is
required and has no default. Compose gains a postgres service on an
internal network with its own volume; POSTGRES_PASSWORD joins .env.
The old bookmarks-data volume is deliberately left undeclared so
`docker compose down -v` cannot take the pre-migration database with
it. main is not deployable until #25 and #26 land.

Closes #20

Co-authored-by: Sulthan Zaki <sultankiki05@gmail.com>
Co-committed-by: Sulthan Zaki <sultankiki05@gmail.com>
2026-08-08 06:52:20 +07:00

2.9 KiB

Bookmark Manager

Read-progress tracker for serialised fiction. A reader browses third-party manga and novel sites; userscripts capture where they got to and sync it to a self-hosted backend, so progress survives across sites and devices.

Language

Series: One ongoing work — a manga or a novel — as published by a Site. Identified by its stable slug on that Site, never by its title. A Series exists once and is shared by every Reader who bookmarks it; it owns the facts that are true regardless of who is reading — title, cover, Latest Chapter. A Reader cannot change them; they describe the Series, not anyone's relationship to it. Avoid: manga, title, book, comic

Site: One third-party source a Series is published on. A Series on two Sites is two Series. Avoid: source, host, provider, domain

Reader: A person with their own Progress. Exactly one per set of credentials, so there is no separate "account" concept to model — the credential belongs to the Reader. Avoid: user, account, member, subscriber

Bookmark: One Reader's tracked relationship with one Series, holding only what differs between Readers: Progress, Favourite, Lifecycle bucket. Facts about the Series itself belong to the Series, not here. Avoid: entry, item, record, subscription

Library: One of the two halves of the collection — manga or novel — selected by a Bookmark's kind. The web UI and the userscripts each address exactly one Library at a time. Not a per-person concept: "everything one person has bookmarked" is a different idea and must not be called a Library. Avoid: section, tab, category

Progress: The furthest chapter a reader has actually read in a Series. Only a change in Progress is real activity, so only Progress reorders the list. Avoid: position, bookmark (the noun is taken), last read

Latest Chapter: The newest chapter a Site has published for a Series, discovered without the reader present. Distinct from Progress in every way that matters: it is a fact about the Site, not about the reader, and it must never reorder the list. Avoid: newest, current chapter, update

Poll: The backend's own check of a Site for a Series's Latest Chapter, made without the Reader present. Performed once per Series no matter how many Readers bookmarked it — a Poll is work done on behalf of the Series, never on behalf of a Reader. Avoid: scrape, refresh, check, sync

New Chapter: The state where Latest Chapter is ahead of Progress. The single condition the ember accent is permitted to signal. Avoid: unread, update available

Lifecycle bucket: Which of three mutually exclusive states a Bookmark sits in — reading, archived, or finished. A Bookmark is in exactly one. Orthogonal to being a favourite. Avoid: state, status (as a domain word), list

Favourite: A reader's manual pin on a Bookmark. Orthogonal to the Lifecycle bucket, and never a reason to reorder the list. Avoid: starred, pinned, priority