95cab106d8
- PostgreSQL schema: sources, manga, chapters, pages, source_meta with indexes - golang-migrate runner with embedded SQL via go:embed (pgx5:// scheme) - sqlc-generated type-safe queries for all tables (pgx/v5 native) - Config package with all env vars including TTL durations - Wire DB init and config into cmd/server/main.go
5.7 KiB
5.7 KiB
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
sourcestableid BIGINT PRIMARY KEYname VARCHAR(128) NOT NULLlang VARCHAR(32) NOT NULLis_nsfw BOOLEAN NOT NULL DEFAULT FALSE
mangatableid SERIAL PRIMARY KEYsource_id BIGINT NOT NULL REFERENCES sources(id)url VARCHAR(2048) NOT NULLtitle VARCHAR(512) NOT NULLinitialized BOOLEAN NOT NULL DEFAULT FALSEartist TEXT,author TEXT,description TEXT,genre TEXTstatus INTEGER NOT NULL DEFAULT 0thumbnail_url VARCHAR(2048)thumbnail_last_fetched BIGINT NOT NULL DEFAULT 0in_library BOOLEAN NOT NULL DEFAULT FALSEin_library_at BIGINT NOT NULL DEFAULT 0real_url VARCHAR(2048)last_fetched_at BIGINT NOT NULL DEFAULT 0chapters_last_fetched_at BIGINT NOT NULL DEFAULT 0update_strategy VARCHAR(64) NOT NULL DEFAULT 'ALWAYS_UPDATE'UNIQUE (source_id, url)
chapterstableid SERIAL PRIMARY KEYmanga_id INTEGER NOT NULL REFERENCES manga(id) ON DELETE CASCADEurl VARCHAR(2048) NOT NULLname VARCHAR(512) NOT NULLdate_upload BIGINT NOT NULL DEFAULT 0chapter_number REAL NOT NULL DEFAULT -1scanlator VARCHAR(256)source_order INTEGER NOT NULLis_read BOOLEAN NOT NULL DEFAULT FALSEis_bookmarked BOOLEAN NOT NULL DEFAULT FALSElast_page_read INTEGER NOT NULL DEFAULT 0last_read_at BIGINT NOT NULL DEFAULT 0fetched_at BIGINT NOT NULL DEFAULT 0real_url VARCHAR(2048)is_downloaded BOOLEAN NOT NULL DEFAULT FALSEpage_count INTEGER NOT NULL DEFAULT -1UNIQUE (manga_id, url)
pagestableid SERIAL PRIMARY KEYchapter_id INTEGER NOT NULL REFERENCES chapters(id) ON DELETE CASCADE"index" INTEGER NOT NULLurl VARCHAR(2048) NOT NULLimage_url TEXTUNIQUE (chapter_id, "index")— added for upsert support
source_metatablesource_id BIGINT NOT NULL REFERENCES sources(id)key VARCHAR(256) NOT NULLvalue TEXT NOT NULLPRIMARY KEY (source_id, key)
- Indexes
CREATE INDEX ON manga (source_id)CREATE INDEX ON manga (last_fetched_at)CREATE INDEX ON chapters (manga_id)CREATE INDEX ON pages (chapter_id)
2.2 DB Initialization — internal/db/db.go
pgxpool.Poolinit fromDATABASE_URLenv var- Configurable
MaxConnsviaDB_MAX_CONNS(default 10) - Configurable
MinConnsviaDB_MIN_CONNS(default 2) - Connection health check on startup (
pool.Ping)
- Configurable
- Migration runner using
golang-migrate/migrate- Source:
iofs(embed migration SQL files with//go:embed) - Driver:
pgx5(DSN scheme rewritten frompostgres://→pgx5://) - Run
migrate.Up()on startup; log version before/after - Non-fatal on "no change" (
migrate.ErrNoChange)
- Source:
Queriesstruct wrapping sqlc-generated query clientsClose()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
UpsertMangaGetMangaBySourceURLGetMangaByIDListMangaBySourceUpdateMangaDetailsUpdateMangaFetchedAtUpdateChaptersFetchedAt
chapter.sql
UpsertChapterGetChapterByIDListChaptersByMangaUpdateChapterFetchedAt
page.sql
UpsertPageListPagesByChapterUpdatePageImageURL
source.sql
UpsertSourceListSourcesGetSourceByIDGetSourceMetaSetSourceMeta
2.4 sqlc Configuration — sqlc.yaml
version: "2"- engine:
postgresql - schema path:
internal/db/migrations/ - queries path:
internal/db/queries/*.sql - output package:
internal/db/queries sql_package: "pgx/v5"— required for pgxpool compatibilityemit_json_tags: trueemit_db_tags: trueemit_pointers_for_null_types: true
2.5 Data Flow & Cache Logic
TTL env vars implemented in internal/config/config.go. Cache check logic lives in the API handler (Phase 5) using these values.
MANGA_LIST_TTL_SECONDSenv var (default 600)MANGA_DETAIL_TTL_SECONDSenv var (default 3600)CHAPTER_LIST_TTL_SECONDSenv var (default 600)?refresh=truequery param bypass — implemented in Phase 5 API handler- Per-flow cache check + upsert logic — implemented in Phase 5 API handler
Checklist: Phase 2 Done When
golang-migrateruns000001_init.up.sqlon a fresh DB without error (manual test with running postgres)sqlc generatecompletes without errors; generated files compileUpsertManga+GetMangaBySourceURLround-trip test passes (requires running postgres)UpsertChaptercorrectly setssource_orderfrom slice position (requires running postgres)- TTL cache logic returns DB rows on second call (Phase 5)
?refresh=truebypasses TTL (Phase 5)- All tables visible in
psqlafter API calls (Phase 5)