From c06172f4123ba1535756d606eb494c1a137ce897 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20F=C3=B6rtsch?= Date: Mon, 13 Apr 2026 15:09:45 +0200 Subject: [PATCH] migrate existing sqlite dbs to the new columns, don't force a nuke on deploy upgraded containers (like the unraid one with a persistent ./data volume) kept their old media_items/media_streams tables, so upsertJellyfinItem hit "table media_items has no column named original_title" on first scan after the canonical-language rewrite. sqlite has no native add-column-if-not-exists so we try/catch each alter, same pattern we had before i deleted the shims. also backports the older alters (stream_decisions.custom_title, review_plans subs_extracted/confidence/apple_compat/job_type) so pre-rewrite installs still converge without a wipe. Co-Authored-By: Claude Opus 4.6 (1M context) --- .claude/memory/MEMORY.md | 2 +- package.json | 2 +- server/db/index.ts | 41 +++++++++++++++++++++++++++++++++++++++- 3 files changed, 42 insertions(+), 3 deletions(-) diff --git a/.claude/memory/MEMORY.md b/.claude/memory/MEMORY.md index 461c064..3f77306 100644 --- a/.claude/memory/MEMORY.md +++ b/.claude/memory/MEMORY.md @@ -76,7 +76,7 @@ mise exec bun -- bun start # production: Hono serves dist/ + API on :3000 ``` ## Workflow rules -- **Always bump version** in `package.json` before committing/pushing. CalVer format: `YYYY.MM.DD` (append `.N` suffix for same-day releases). +- **Always bump version** in `package.json` before committing/pushing. CalVer with dot-suffix per global AGENTS.md (`YYYY.MM.DD.N`). `.gitea/workflows/build.yml` tags a Docker image with this version, so the `+N` form breaks CI with `invalid reference format`. ## Docker deploy (Unraid) ```fish diff --git a/package.json b/package.json index efb66a4..b57aca1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "netfelix-audio-fix", - "version": "2026.04.13.3", + "version": "2026.04.13.4", "scripts": { "dev:server": "NODE_ENV=development bun --hot server/index.tsx", "dev:client": "vite", diff --git a/server/db/index.ts b/server/db/index.ts index dacb854..6f3e603 100644 --- a/server/db/index.ts +++ b/server/db/index.ts @@ -51,11 +51,50 @@ export function getDb(): Database { if (_db) return _db; _db = new Database(dbPath, { create: true }); _db.exec(SCHEMA); + applyMigrations(_db); seedDefaults(_db); - return _db; } +/** + * Add columns that landed after the initial schema. `CREATE TABLE IF NOT + * EXISTS` above skips existing tables, so upgraded installs need per-column + * ALTERs to pick up new fields. Each call is wrapped in try/catch because + * SQLite has no native `ADD COLUMN IF NOT EXISTS` — the error is benign. + * + * Do NOT remove entries here just because the schema block also declares the + * column. Fresh installs get it from SCHEMA; existing deployments (e.g. the + * Unraid container with a persistent volume) only get it from this function. + */ +function applyMigrations(db: Database): void { + const addColumn = (table: string, column: string, type: string) => { + try { + db.exec(`ALTER TABLE ${table} ADD COLUMN ${column} ${type}`); + } catch { + /* already exists */ + } + }; + + // 2026-04-13: canonical-language + full-jellyfin-capture rewrite + addColumn("media_items", "original_title", "TEXT"); + addColumn("media_items", "runtime_ticks", "INTEGER"); + addColumn("media_items", "date_last_refreshed", "TEXT"); + addColumn("media_items", "jellyfin_raw", "TEXT"); + addColumn("media_items", "external_raw", "TEXT"); + addColumn("media_items", "last_executed_at", "TEXT"); + addColumn("media_streams", "profile", "TEXT"); + addColumn("media_streams", "bit_depth", "INTEGER"); + + // Earlier migrations kept here so installs that predate the big rewrite + // still converge to the current schema. + addColumn("stream_decisions", "custom_title", "TEXT"); + addColumn("stream_decisions", "transcode_codec", "TEXT"); + addColumn("review_plans", "subs_extracted", "INTEGER NOT NULL DEFAULT 0"); + addColumn("review_plans", "confidence", "TEXT NOT NULL DEFAULT 'low'"); + addColumn("review_plans", "apple_compat", "TEXT"); + addColumn("review_plans", "job_type", "TEXT NOT NULL DEFAULT 'copy'"); +} + function seedDefaults(db: Database): void { const insert = db.prepare("INSERT OR IGNORE INTO config (key, value) VALUES (?, ?)"); for (const [key, value] of Object.entries(DEFAULT_CONFIG)) {