From cbf0025a815df14b97b4b6fd9cf18d2aa98ac4df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20F=C3=B6rtsch?= Date: Wed, 15 Apr 2026 06:55:43 +0200 Subject: [PATCH] drop review_plans.verified column and all its references Co-Authored-By: Claude Sonnet 4.6 --- server/api/review.ts | 4 +- server/db/index.ts | 1 + server/db/schema.ts | 5 -- server/services/__tests__/webhook.test.ts | 58 ----------------------- server/services/rescan.ts | 22 ++------- server/types.ts | 1 - 6 files changed, 7 insertions(+), 84 deletions(-) diff --git a/server/api/review.ts b/server/api/review.ts index cebb761..cf17fd5 100644 --- a/server/api/review.ts +++ b/server/api/review.ts @@ -327,7 +327,7 @@ app.get("/pipeline", (c) => { const done = db .prepare(` SELECT j.*, mi.name, mi.series_name, mi.type, - rp.job_type, rp.apple_compat, rp.verified + rp.job_type, rp.apple_compat FROM jobs j JOIN media_items mi ON mi.id = j.item_id JOIN review_plans rp ON rp.item_id = j.item_id @@ -770,7 +770,7 @@ app.post("/:id/reopen", (c) => { db.transaction(() => { // Leave plan.notes alone so the user keeps any ffmpeg error summary // from the prior run — useful context when redeciding decisions. - db.prepare("UPDATE review_plans SET status = 'pending', verified = 0, reviewed_at = NULL WHERE id = ?").run(plan.id); + db.prepare("UPDATE review_plans SET status = 'pending', reviewed_at = NULL WHERE id = ?").run(plan.id); db.prepare("DELETE FROM jobs WHERE item_id = ? AND status IN ('done', 'error')").run(id); })(); return c.json({ ok: true }); diff --git a/server/db/index.ts b/server/db/index.ts index 97ef818..556b0ea 100644 --- a/server/db/index.ts +++ b/server/db/index.ts @@ -78,6 +78,7 @@ function migrate(db: Database): void { // signal would come from our own ffprobe, not from a Jellyfin webhook. // RENAME COLUMN preserves values; both alters are no-ops on fresh DBs. alter("ALTER TABLE review_plans RENAME COLUMN webhook_verified TO verified"); + alter("ALTER TABLE review_plans DROP COLUMN verified"); } function seedDefaults(db: Database): void { diff --git a/server/db/schema.ts b/server/db/schema.ts index 6eeb7ae..ad9b2f0 100644 --- a/server/db/schema.ts +++ b/server/db/schema.ts @@ -70,11 +70,6 @@ CREATE TABLE IF NOT EXISTS review_plans ( subs_extracted INTEGER NOT NULL DEFAULT 0, notes TEXT, reviewed_at TEXT, - -- An independent post-hoc check has confirmed the on-disk file matches - -- the plan: either the analyzer saw is_noop=1 on first scan, or after - -- a job completed we ffprobed the output file and it agreed with the - -- kept/removed stream decisions. Surfaces as the ✓✓ in the Done column. - verified INTEGER NOT NULL DEFAULT 0, created_at TEXT NOT NULL DEFAULT (datetime('now')) ); diff --git a/server/services/__tests__/webhook.test.ts b/server/services/__tests__/webhook.test.ts index 34520e6..2a81a22 100644 --- a/server/services/__tests__/webhook.test.ts +++ b/server/services/__tests__/webhook.test.ts @@ -182,61 +182,3 @@ describe("processWebhookEvent — done-status override", () => { expect(planStatusFor(db, fresh.Id)).toBe("pending"); }); }); - -describe("processWebhookEvent — webhook_verified flag", () => { - beforeEach(() => _resetDedupe()); - - async function runWebhook(db: Database, item: JellyfinItem, cfg: RescanConfig = RESCAN_CFG) { - return processWebhookEvent( - { NotificationType: "ItemAdded", ItemId: item.Id, ItemType: item.Type as "Movie" | "Episode" }, - { db, jellyfin: JF, rescanCfg: cfg, getItemFn: async () => item }, - ); - } - - function verifiedFor(db: Database, jellyfinId: string): number { - return ( - db - .prepare( - "SELECT rp.verified as v FROM review_plans rp JOIN media_items mi ON mi.id = rp.item_id WHERE mi.jellyfin_id = ?", - ) - .get(jellyfinId) as { v: number } - ).v; - } - - test("is_noop=1 on first scan sets webhook_verified=1 (no Jellyfin round-trip needed)", async () => { - const db = makeDb(); - const fresh = fakeItem(); - await runWebhook(db, fresh); - expect(verifiedFor(db, fresh.Id)).toBe(1); - }); - - test("a post-execute webhook that still says is_noop=1 keeps webhook_verified=1", async () => { - const db = makeDb(); - const fresh = fakeItem(); - await runWebhook(db, fresh); - _resetDedupe(); - await runWebhook(db, fresh); - expect(verifiedFor(db, fresh.Id)).toBe(1); - }); - - test("webhook that flips plan off-noop clears webhook_verified back to 0", async () => { - const db = makeDb(); - const noopItem = fakeItem(); - await runWebhook(db, noopItem); - expect(verifiedFor(db, noopItem.Id)).toBe(1); - - // Second probe: Jellyfin reports a drifted file (extra french track - // that the 'deu' language config would now remove → is_noop=0). - const driftedCfg: RescanConfig = { ...RESCAN_CFG, audioLanguages: ["deu"] }; - const drifted = fakeItem({ - MediaStreams: [ - { Type: "Video", Index: 0, Codec: "h264" }, - { Type: "Audio", Index: 1, Codec: "aac", Language: "eng", IsDefault: true }, - { Type: "Audio", Index: 2, Codec: "aac", Language: "fra" }, - ], - }); - _resetDedupe(); - await runWebhook(db, drifted, driftedCfg); - expect(verifiedFor(db, noopItem.Id)).toBe(0); - }); -}); diff --git a/server/services/rescan.ts b/server/services/rescan.ts index 298ae27..3d8f94a 100644 --- a/server/services/rescan.ts +++ b/server/services/rescan.ts @@ -229,17 +229,10 @@ export async function upsertJellyfinItem( // commit that made done terminal) // error → pending (retry loop) // else keep current status - // - // `verified` tracks whether we have independent confirmation the file - // matches the plan. Set to 1 whenever is_noop=1 on a fresh analysis - // (an unchanged file is already in its desired end state). Post- - // execute, execute.ts re-runs verifyDesiredState and flips this on - // when ffprobe agrees. Cleared the moment a webhook says the file - // drifted off-noop. db .prepare(` - INSERT INTO review_plans (item_id, status, is_noop, confidence, apple_compat, job_type, notes, verified) - VALUES (?, 'pending', ?, ?, ?, ?, ?, ?) + INSERT INTO review_plans (item_id, status, is_noop, confidence, apple_compat, job_type, notes) + VALUES (?, 'pending', ?, ?, ?, ?, ?) ON CONFLICT(item_id) DO UPDATE SET status = CASE WHEN excluded.is_noop = 1 THEN 'done' @@ -252,12 +245,7 @@ export async function upsertJellyfinItem( confidence = excluded.confidence, apple_compat = excluded.apple_compat, job_type = excluded.job_type, - notes = excluded.notes, - verified = CASE - WHEN excluded.is_noop = 1 THEN 1 - WHEN ? = 'webhook' THEN 0 - ELSE review_plans.verified - END + notes = excluded.notes `) .run( itemId, @@ -266,9 +254,7 @@ export async function upsertJellyfinItem( analysis.apple_compat, analysis.job_type, analysis.notes.length > 0 ? analysis.notes.join("\n") : null, - analysis.is_noop ? 1 : 0, - source, - source, + source, // for the CASE WHEN ? = 'webhook' branch ); const planRow = db.prepare("SELECT id FROM review_plans WHERE item_id = ?").get(itemId) as { id: number }; diff --git a/server/types.ts b/server/types.ts index 3b1fad1..e881654 100644 --- a/server/types.ts +++ b/server/types.ts @@ -65,7 +65,6 @@ export interface ReviewPlan { subs_extracted: number; notes: string | null; reviewed_at: string | null; - verified: number; created_at: string; }