Commit Graph

3 Commits

Author SHA1 Message Date
sulthan 77965c3d76 docs: close the environment contract and de-ambiguate volume derivation (#26)
Review findings on the previous commit:

- README's config table listed 8 of the backend's 25 environment variables,
  omitting the whole DISCORD_* set that the backend refuses to start without.
  The canonical reference cannot be missing the vars that gate startup.
- `docker volume ls --filter name=` is a substring match. Both runbooks used
  it to find a volume they then mount read-only or delete; a second matching
  volume makes the mount fail obscurely and the delete take both. Derive the
  name exactly from the compose project instead.
- CUTOVER §6 removes the retired volume 'once the Postgres data has been
  trusted for a while', in a shell where §1's $VOL no longer exists.
- REDEPLOY and DEPLOY still pointed at /opt/bookmarkmanager, so CUTOVER §6's
  handoff to REDEPLOY §1 sent the operator to a path that does not exist.
2026-08-08 15:50:59 +07:00
sulthan 01de8903b4 docs: correct cutover and redeploy runbooks against the real deployment (#26)
Dry-running CUTOVER.md against production surfaced four things that would
have failed mid-cutover:

- The volume is named after the compose project, which is the lowercased
  directory name (mangabookmark), not the repo name. Both runbooks hardcoded
  bookmarkmanager_bookmarks-data. Derive it from docker instead.
- §1 asserted a clean stop leaves no -wal. compose stop SIGKILLs after 10s,
  and a surviving -wal holds writes bookmarks.db alone does not, so the
  export would silently lose them. Check rather than assume.
- jq is not installed on the server; §5's read-path check now uses python3,
  which the runbook already requires.
- REDEPLOY §1 still listed the pre-split table set, omitting readers and
  sessions from both the \dt output and the pg_restore contents.

Also records the git-pull failure the redeploy hits on a checkout whose
remote is the HTTPS clone URL.
2026-08-08 15:46:05 +07:00
sulthan 2cc1e69f5d Prove the import against a copy of the real library (#25) (#33)
Closes #25.

Retires the biggest risk in #18 — losing the owner's reading history — on a copy, before production is anywhere near it.

## What was run

A throwaway generator (python3 stdlib `sqlite3`, ~20 lines, **not committed**) read a copy of `bookmarks-20260807-213515.db` and emitted plain SQL: 29 distinct Series first, then 29 Bookmarks referencing them, each `INSERT ... SELECT id FROM owner` so the reader id is resolved rather than hardcoded. The target was a scratch Postgres whose schema and owner Reader were built by the real binary (`go run .` against a throwaway container), not by hand-written DDL. Production was not touched.

## Verified

| check | result |
|---|---|
| Bookmarks total | 29 |
| reading / archived / other | 18 / 11 / 0 |
| Series | 29, equal to the distinct `(site, series_id)` count in the source |
| Readers | 1; Bookmarks not owned by the owner: 0 |
| Field-by-field diff, all 29 rows x 15 columns | 0 differences |
| `GET /bookmarks` over the real read path | 29 rows, values match source |
| `TRUNCATE bookmarks, series;` then re-apply | clean, 29 again |

The spot-check the ticket asked for was widened to a full row-by-row comparison — 29 rows is small enough that sampling was the more expensive option.

## What is committed

`CUTOVER.md` only, plus two cross-links from `REDEPLOY.md`. The generator stays out of the repository: its output is the owner's reading history, and it reads SQLite, which the backend module dropped in ADR-0001. So the runbook specifies the transformation — column mapping, ordering, nullability, quoting, the temp-table ownership trick — rather than shipping a script. `backend/go.mod` gains nothing.

## Review

Two-axis review ran on the diff; six findings applied, all in the runbook:

- Six source columns (`title`, `series_url`, `cover`, `last_chapter`, `last_chapter_url`, `last_chapter_num`) are nullable in SQLite but `NOT NULL` in Postgres and must be coalesced — the opposite of `latest_chapter_num`, the one column where `NULL` is meaningful. The 2026-08-07 export had none; a fresh one is not promised the same.
- `CREATE TEMP TABLE ... ON COMMIT DROP` must sit *inside* the transaction, or psql's autocommit drops it instantly.
- The ownership check now resolves the Reader by Discord id; comparing against `ORDER BY id LIMIT 1` was true by construction and could never fail.
- The spot-check now samples archived and favourite rows explicitly instead of hoping they fall inside `ORDER BY updated_at DESC LIMIT 5`.
- `git pull --ff-only` before `up -d --build`, or a pre-cutover server rebuilds the SQLite image.
- `python3` and `jq` named as prerequisites; column count corrected to sixteen.

Every query in the runbook was executed against the scratch database as written.

`go vet`, `CGO_ENABLED=0 go build ./...` and `go test ./...` all pass — no Go code changed.

Reviewed-on: #33
Co-authored-by: Sulthan Zaki <sultankiki05@gmail.com>
Co-committed-by: Sulthan Zaki <sultankiki05@gmail.com>
2026-08-08 15:15:33 +07:00