# Phase 2 — Database Layer Persistent storage using PostgreSQL via pgx. Migrations run on startup. sqlc generates type-safe query code. Reference schema modeled after Suwayomi-Server: - `/Users/achmad/Documents/Belajar/Web/Suwayomi-Server/server/src/main/kotlin/suwayomi/tachidesk/manga/model/table/` - Migrations: `/Users/achmad/Documents/Belajar/Web/Suwayomi-Server/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/` --- ## 2.1 Schema Migration — `internal/db/migrations/000001_init.up.sql` - [x] `sources` table - [x] `id BIGINT PRIMARY KEY` - [x] `name VARCHAR(128) NOT NULL` - [x] `lang VARCHAR(32) NOT NULL` - [x] `is_nsfw BOOLEAN NOT NULL DEFAULT FALSE` - [x] `manga` table - [x] `id SERIAL PRIMARY KEY` - [x] `source_id BIGINT NOT NULL REFERENCES sources(id)` - [x] `url VARCHAR(2048) NOT NULL` - [x] `title VARCHAR(512) NOT NULL` - [x] `initialized BOOLEAN NOT NULL DEFAULT FALSE` - [x] `artist TEXT`, `author TEXT`, `description TEXT`, `genre TEXT` - [x] `status INTEGER NOT NULL DEFAULT 0` - [x] `thumbnail_url VARCHAR(2048)` - [x] `thumbnail_last_fetched BIGINT NOT NULL DEFAULT 0` - [x] `in_library BOOLEAN NOT NULL DEFAULT FALSE` - [x] `in_library_at BIGINT NOT NULL DEFAULT 0` - [x] `real_url VARCHAR(2048)` - [x] `last_fetched_at BIGINT NOT NULL DEFAULT 0` - [x] `chapters_last_fetched_at BIGINT NOT NULL DEFAULT 0` - [x] `update_strategy VARCHAR(64) NOT NULL DEFAULT 'ALWAYS_UPDATE'` - [x] `UNIQUE (source_id, url)` - [x] `chapters` table - [x] `id SERIAL PRIMARY KEY` - [x] `manga_id INTEGER NOT NULL REFERENCES manga(id) ON DELETE CASCADE` - [x] `url VARCHAR(2048) NOT NULL` - [x] `name VARCHAR(512) NOT NULL` - [x] `date_upload BIGINT NOT NULL DEFAULT 0` - [x] `chapter_number REAL NOT NULL DEFAULT -1` - [x] `scanlator VARCHAR(256)` - [x] `source_order INTEGER NOT NULL` - [x] `is_read BOOLEAN NOT NULL DEFAULT FALSE` - [x] `is_bookmarked BOOLEAN NOT NULL DEFAULT FALSE` - [x] `last_page_read INTEGER NOT NULL DEFAULT 0` - [x] `last_read_at BIGINT NOT NULL DEFAULT 0` - [x] `fetched_at BIGINT NOT NULL DEFAULT 0` - [x] `real_url VARCHAR(2048)` - [x] `is_downloaded BOOLEAN NOT NULL DEFAULT FALSE` - [x] `page_count INTEGER NOT NULL DEFAULT -1` - [x] `UNIQUE (manga_id, url)` - [x] `pages` table - [x] `id SERIAL PRIMARY KEY` - [x] `chapter_id INTEGER NOT NULL REFERENCES chapters(id) ON DELETE CASCADE` - [x] `"index" INTEGER NOT NULL` - [x] `url VARCHAR(2048) NOT NULL` - [x] `image_url TEXT` - [x] `UNIQUE (chapter_id, "index")` — added for upsert support - [x] `source_meta` table - [x] `source_id BIGINT NOT NULL REFERENCES sources(id)` - [x] `key VARCHAR(256) NOT NULL` - [x] `value TEXT NOT NULL` - [x] `PRIMARY KEY (source_id, key)` - [x] Indexes - [x] `CREATE INDEX ON manga (source_id)` - [x] `CREATE INDEX ON manga (last_fetched_at)` - [x] `CREATE INDEX ON chapters (manga_id)` - [x] `CREATE INDEX ON pages (chapter_id)` --- ## 2.2 DB Initialization — `internal/db/db.go` - [x] `pgxpool.Pool` init from `DATABASE_URL` env var - [x] Configurable `MaxConns` via `DB_MAX_CONNS` (default 10) - [x] Configurable `MinConns` via `DB_MIN_CONNS` (default 2) - [x] Connection health check on startup (`pool.Ping`) - [x] Migration runner using `golang-migrate/migrate` - [x] Source: `iofs` (embed migration SQL files with `//go:embed`) - [x] Driver: `pgx5` (DSN scheme rewritten from `postgres://` → `pgx5://`) - [x] Run `migrate.Up()` on startup; log version before/after - [x] Non-fatal on "no change" (`migrate.ErrNoChange`) - [x] `Queries` struct wrapping sqlc-generated query clients - [x] `Close()` method to drain pool on shutdown --- ## 2.3 SQL Queries — `internal/db/queries/` Generated by `sqlc generate` (config: `sqlc.yaml`, `sql_package: pgx/v5`). ### `manga.sql` - [x] `UpsertManga` - [x] `GetMangaBySourceURL` - [x] `GetMangaByID` - [x] `ListMangaBySource` - [x] `UpdateMangaDetails` - [x] `UpdateMangaFetchedAt` - [x] `UpdateChaptersFetchedAt` ### `chapter.sql` - [x] `UpsertChapter` - [x] `GetChapterByID` - [x] `ListChaptersByManga` - [x] `UpdateChapterFetchedAt` ### `page.sql` - [x] `UpsertPage` - [x] `ListPagesByChapter` - [x] `UpdatePageImageURL` ### `source.sql` - [x] `UpsertSource` - [x] `ListSources` - [x] `GetSourceByID` - [x] `GetSourceMeta` - [x] `SetSourceMeta` --- ## 2.4 sqlc Configuration — `sqlc.yaml` - [x] `version: "2"` - [x] engine: `postgresql` - [x] schema path: `internal/db/migrations/` - [x] queries path: `internal/db/queries/*.sql` - [x] output package: `internal/db/queries` - [x] `sql_package: "pgx/v5"` — required for pgxpool compatibility - [x] `emit_json_tags: true` - [x] `emit_db_tags: true` - [x] `emit_pointers_for_null_types: true` --- ## 2.5 Data Flow & Cache Logic TTL env vars implemented in `internal/config/config.go`. Cache check logic and `?refresh=true` bypass live in the Phase 5 API handler using these values. - [x] `MANGA_LIST_TTL_SECONDS` env var (default 600) - [x] `MANGA_DETAIL_TTL_SECONDS` env var (default 3600) - [x] `CHAPTER_LIST_TTL_SECONDS` env var (default 600) --- ## Checklist: Phase 2 Done When - [x] `sqlc generate` completes without errors; generated files compile - [x] `go build ./...` succeeds