Files
gerbeur/api/db/migrations/0010_youtube_embed_start.ts
khannurien 0e138be6df
All checks were successful
Build and Publish Docker Image / build-and-push (push) Successful in 50s
v3: youtube embeds now honour the timestamp in the source url, with a migration backfilling existing dumps
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 11:47:26 +00:00

77 lines
2.5 KiB
TypeScript

import type { DatabaseSync } from "node:sqlite";
// Backfills the `start` parameter on stored YouTube embed URLs.
//
// `rich_content.embedUrl` is computed once, at dump creation, and previously
// dropped the timestamp carried by the source URL (`?t=1265`, `?t=1h2m3s`), so
// those videos always opened at 0:00 in the global player. The provider now
// translates `t`/`start` into the `start` param the iframe player honours; this
// migration applies the same translation to rows written before that fix.
//
// Purely local — it re-derives the param from the already-stored source URL and
// makes no network calls. Self-contained (no imports from api/services) so it
// stays reproducible even if the provider's parsing evolves later.
//
// Idempotent: rows whose embed URL already carries a `start` are skipped, so a
// fresh database built from schema.sql is simply a no-op.
/** Parse `t`/`start` — plain seconds, `90s`, or `1h2m3s` / `20m5s`. */
function extractStartSeconds(url: string): number | null {
let raw: string | null;
try {
const u = new URL(url);
raw = u.searchParams.get("t") ?? u.searchParams.get("start");
} catch {
return null;
}
if (!raw) return null;
if (/^\d+$/.test(raw)) return Number(raw);
const hms = /^(?:(\d+)h)?(?:(\d+)m)?(?:(\d+)s)?$/i.exec(raw);
if (!hms || (!hms[1] && !hms[2] && !hms[3])) return null;
return Number(hms[1] ?? 0) * 3600 + Number(hms[2] ?? 0) * 60 +
Number(hms[3] ?? 0);
}
export function up(db: DatabaseSync): void {
const rows = db.prepare(
`SELECT id, url, rich_content FROM dumps
WHERE kind = 'url' AND url IS NOT NULL AND rich_content IS NOT NULL;`,
).all() as { id: string; url: string; rich_content: string }[];
const update = db.prepare(
`UPDATE dumps SET rich_content = ? WHERE id = ?;`,
);
let patched = 0;
for (const row of rows) {
let rich: { type?: string; embedUrl?: string };
try {
rich = JSON.parse(row.rich_content);
} catch {
continue; // malformed payload — leave it untouched
}
if (rich.type !== "youtube" || !rich.embedUrl) continue;
const start = extractStartSeconds(row.url);
if (!start) continue;
let embed: URL;
try {
embed = new URL(rich.embedUrl);
} catch {
continue;
}
if (embed.searchParams.has("start")) continue;
embed.searchParams.set("start", String(start));
update.run(JSON.stringify({ ...rich, embedUrl: embed.toString() }), row.id);
patched++;
}
if (patched > 0) {
console.log(`[migrate] 0010: added start offset to ${patched} embed(s)`);
}
}