import Database from 'better-sqlite3'; import { mkdirSync } from 'node:fs'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; import { getPaths } from './config.mjs'; export function seed(databasePath = getPaths().database) { mkdirSync(path.dirname(databasePath), { recursive: true }); const database = new Database(databasePath); database.pragma('foreign_keys = ON'); const seedDatabase = database.transaction(() => { const replace = (table, columns, rows) => { const placeholders = columns.map(() => '?').join(', '); const update = columns .filter((column) => column !== 'id') .map((column) => `${column} = excluded.${column}`) .join(', '); const statement = database.prepare( `INSERT INTO ${table} (${columns.join(', ')}) VALUES (${placeholders}) ON CONFLICT(id) DO UPDATE SET ${update}` ); for (const row of rows) statement.run(...row); }; replace( 'section_categories', ['id', 'name', 'color', 'sort_order'], [ ['cat-jc', 'JC', '#67DDE8', 0], ['cat-reaction', 'Reaction', '#E7B957', 1], ['cat-game', 'Spiel', '#9B8AFB', 2] ] ); replace( 'formats', ['id', 'name'], [ ['format-find', 'Fundstück'], ['format-talk', 'Gespräch'], ['format-quiz', 'Quiz'] ] ); replace( 'topics', ['id', 'name'], [ ['topic-gaming', 'Gaming'], ['topic-film', 'Film & Serien'], ['topic-internet', 'Netzkultur'] ] ); const timestamp = '2026-10-21T10:00:00.000Z'; replace( 'pieces', [ 'id', 'title', 'summary', 'format_id', 'status', 'duration_seconds', 'intro', 'moderation_notes', 'transition_text', 'presentation_text', 'created_at_utc', 'updated_at_utc' ], [ [ 'piece-console', 'Die kleine Konsole mit dem großen Dreh', 'Ein ungewöhnliches Handheld-Konzept sorgt für Diskussionsstoff.', 'format-find', 'ready', 480, 'Eine Kurbel, ein gelber Knopf und sehr viel Charme: Was macht kleine Hardware spannend?', 'Erst Formfaktor zeigen, dann über bewusst gesetzte Grenzen sprechen.', 'Von der kleinen Konsole geht es zur ganz großen Leinwand.', 'Weniger Leistung, mehr Idee.', timestamp, timestamp ], [ 'piece-effects', 'Warum praktische Effekte wieder auffallen', 'Neue Produktionen mischen Miniaturen, Masken und digitale Nacharbeit.', 'format-talk', 'ready', 720, 'Man sieht manchmal gerade deshalb weniger Trick, weil wieder mehr vor der Kamera passiert.', 'Beispiele vergleichen, nicht analog gegen digital ausspielen.', 'Nach so viel Handarbeit wird es spielerisch.', 'Wenn das Licht wirklich auf ein Modell fällt.', timestamp, timestamp ], [ 'piece-quiz', 'Drei Sounds, ein Spiel', 'Kurzes Audioquiz mit bekannten Inventar- und Menüklängen.', 'format-quiz', 'preparing', 600, 'Augen zu: Erkennt ihr das Spiel nur an seinem Inventar?', 'Drei Runden, Auflösung jeweils erst nach den Tipps.', 'Damit sind die Ohren warm für unser erstes Spiel.', 'Welches Spiel steckt hinter diesem Klang?', timestamp, timestamp ], [ 'piece-pixels', 'Pixelart, die nicht nostalgisch sein will', 'Wie moderne Spiele Pixel als Material statt als Rückblick einsetzen.', 'format-find', 'ready', 540, 'Pixel sind längst keine technische Notlösung mehr.', 'Großaufnahmen vorbereiten, drei Stilmittel benennen.', 'Genug Theorie, jetzt spielen wir selbst.', 'Pixel als bewusste Entscheidung.', timestamp, timestamp ], [ 'piece-inbox-link', 'Indie-Showcase nachhören', 'Ein Fundstück aus dem letzten Showcase, das noch eingeordnet werden muss.', 'format-find', 'inbox', 0, '', '', '', '', timestamp, timestamp ], [ 'piece-archive-demo', 'Controller mit eingebautem Bildschirm', 'Ein früheres Fundstück, das bewusst im Archiv liegt.', 'format-find', 'archived', 300, '', '', '', '', timestamp, timestamp ] ] ); database .prepare('UPDATE pieces SET research_text = ? WHERE id = ?') .run( '## Recherche\n\n- Herstellerangaben gegen unabhängige Tests prüfen\n- Fokus auf Bedienidee statt Leistungsdaten', 'piece-console' ); database .prepare('UPDATE pieces SET presentation_text = ? WHERE id = ?') .run( '## Weniger Leistung, mehr Idee\n\nDie Kurbel ist Teil des Spiels, nicht nur Dekoration.', 'piece-console' ); database.prepare('DELETE FROM piece_topics WHERE piece_id LIKE ?').run('piece-%'); const addTopic = database.prepare( 'INSERT INTO piece_topics (piece_id, topic_id) VALUES (?, ?)' ); for (const values of [ ['piece-console', 'topic-gaming'], ['piece-effects', 'topic-film'], ['piece-quiz', 'topic-gaming'], ['piece-pixels', 'topic-gaming'], ['piece-pixels', 'topic-internet'], ['piece-inbox-link', 'topic-gaming'], ['piece-archive-demo', 'topic-gaming'] ]) addTopic.run(...values); database.prepare('DELETE FROM piece_category_suggestions WHERE piece_id LIKE ?').run('piece-%'); const addCategory = database.prepare( 'INSERT INTO piece_category_suggestions (piece_id, category_id) VALUES (?, ?)' ); for (const values of [ ['piece-console', 'cat-jc'], ['piece-effects', 'cat-reaction'], ['piece-quiz', 'cat-jc'], ['piece-pixels', 'cat-reaction'], ['piece-inbox-link', 'cat-reaction'], ['piece-archive-demo', 'cat-reaction'] ]) addCategory.run(...values); replace( 'piece_sources', ['id', 'piece_id', 'label', 'url', 'timecode_seconds', 'position'], [ [ 'source-console', 'piece-console', 'Herstellerseite', 'https://example.com/console', null, 0 ], [ 'source-effects', 'piece-effects', 'Werkstattbericht', 'https://example.com/effects', 95, 0 ], ['source-pixels', 'piece-pixels', 'Galerie', 'https://example.com/pixels', null, 0], [ 'source-inbox-showcase', 'piece-inbox-link', 'Showcase-Aufzeichnung', 'https://example.com/showcase', 120, 0 ] ] ); replace( 'show_templates', ['id', 'name', 'description'], [ ['template-default', 'Standardablauf', 'Start, zwei JC-Blöcke und zwei Spiele.'], ['template-games', 'Spieleabend', 'Ein ruhiger Einstieg und drei flexible Spieleblöcke.'] ] ); replace( 'show_template_sections', ['id', 'template_id', 'name', 'category_id', 'position', 'explicit_duration_seconds'], [ ['template-default-start', 'template-default', 'Start', 'cat-jc', 0, 300], [ 'template-default-jc-reaction', 'template-default', 'JC / Reaction', 'cat-reaction', 1, null ], ['template-default-game-1', 'template-default', 'Spiel 1', 'cat-game', 2, 3600], ['template-default-jc', 'template-default', 'JC', 'cat-jc', 3, null], ['template-default-game-2', 'template-default', 'Spiel 2', 'cat-game', 4, 3600], ['template-games-start', 'template-games', 'Start', 'cat-jc', 0, 300], ['template-games-game-1', 'template-games', 'Spiel 1', 'cat-game', 1, 2700], ['template-games-break', 'template-games', 'Pause & Chat', 'cat-jc', 2, 600], ['template-games-game-2', 'template-games', 'Spiel 2', 'cat-game', 3, 2700], ['template-games-game-3', 'template-games', 'Spiel 3', 'cat-game', 4, 2700] ] ); replace( 'shows', [ 'id', 'title', 'starts_at_utc', 'twitch_title', 'announcement_text', 'status', 'revision', 'created_at_utc', 'updated_at_utc' ], [ [ 'show-october', 'Herbstsendung: Kleine Welten', '2026-10-23T18:00:00.000Z', 'Kleine Welten, große Ideen | heinzlive', 'Heute schauen wir auf Hardware mit Kurbel, handgemachte Filmtricks und Pixel mit Haltung.', 'ready', 1, timestamp, timestamp ], [ 'show-games-evening', 'Spieleabend: Kurbeln & Pixel', '2026-10-16T18:00:00.000Z', 'Kurbeln, Pixel und ein langer Spieleabend | heinzlive', 'Heute spielen wir uns von ungewöhnlicher Hardware bis zu moderner Pixelart.', 'draft', 1, timestamp, timestamp ] ] ); replace( 'show_sections', [ 'id', 'show_id', 'name', 'category_id', 'position', 'explicit_duration_seconds', 'is_reserve_bank' ], [ ['section-start', 'show-october', 'Ankommen & Warmwerden', 'cat-jc', 0, 300, 0], ['section-stories', 'show-october', 'Kleine Welten', 'cat-reaction', 1, null, 0], ['section-game', 'show-october', 'Spiel 1', 'cat-game', 2, 3600, 0], ['reserve-bank-show-october', 'show-october', 'Reservebank', null, 3, null, 1], ['games-start', 'show-games-evening', 'Start', 'cat-jc', 0, 300, 0], ['games-first', 'show-games-evening', 'Spiel 1', 'cat-game', 1, 2700, 0], ['games-chat', 'show-games-evening', 'Pause & Chat', 'cat-jc', 2, 600, 0], ['games-second', 'show-games-evening', 'Spiel 2', 'cat-game', 3, 2700, 0], ['reserve-bank-show-games-evening', 'show-games-evening', 'Reservebank', null, 4, null, 1] ] ); replace( 'piece_usages', [ 'id', 'show_section_id', 'original_piece_id', 'position', 'status', 'duration_seconds', 'title_snapshot', 'intro_snapshot', 'research_snapshot', 'moderation_snapshot', 'transition_snapshot', 'presentation_snapshot', 'own_notes', 'is_reserve' ], [ [ 'usage-console', 'section-stories', 'piece-console', 0, 'open', 480, 'Die kleine Konsole mit dem großen Dreh', 'Eine Kurbel, ein gelber Knopf und sehr viel Charme: Was macht kleine Hardware spannend?', '## Recherche\n\n- Herstellerangaben gegen unabhängige Tests prüfen\n- Fokus auf Bedienidee statt Leistungsdaten', 'Erst Formfaktor zeigen, dann über bewusst gesetzte Grenzen sprechen.', 'Von der kleinen Konsole geht es zur ganz großen Leinwand.', 'Weniger Leistung, mehr Idee.', '', 0 ], [ 'usage-effects', 'section-stories', 'piece-effects', 1, 'open', 720, 'Warum praktische Effekte wieder auffallen', 'Man sieht manchmal gerade deshalb weniger Trick, weil wieder mehr vor der Kamera passiert.', '', 'Beispiele vergleichen, nicht analog gegen digital ausspielen.', 'Nach so viel Handarbeit wird es spielerisch.', 'Wenn das Licht wirklich auf ein Modell fällt.', '', 0 ], [ 'usage-reserve', 'reserve-bank-show-october', 'piece-pixels', 0, 'open', 540, 'Pixelart, die nicht nostalgisch sein will', 'Pixel sind längst keine technische Notlösung mehr.', '', 'Großaufnahmen vorbereiten, drei Stilmittel benennen.', 'Genug Theorie, jetzt spielen wir selbst.', 'Pixel als bewusste Entscheidung.', 'Ersatz, falls ein Spiel technisch ausfällt.', 1 ], [ 'usage-games-console', 'games-first', 'piece-console', 0, 'open', 480, 'Die kleine Konsole mit dem großen Dreh', 'Wir beginnen mit Hardware, die ihr Spielprinzip gleich mitbringt.', 'Kurbelmechanik und verfügbare Spiele prüfen.', 'Konsole zeigen und Publikum nach Lieblingsmechaniken fragen.', 'Danach wechseln wir von Mechanik zu Grafik.', 'Eine Kurbel verändert, wie wir spielen.', 'Controller vor Sendungsbeginn laden.', 0 ], [ 'usage-games-pixels', 'games-second', 'piece-pixels', 0, 'open', 540, 'Pixelart, die nicht nostalgisch sein will', 'Unser zweites Spiel nutzt Pixel nicht als Rückblick.', 'Drei aktuelle Beispiele vergleichen.', 'Auf Licht, Bewegung und Materialwirkung achten.', 'Zum Schluss sammeln wir eure Empfehlungen.', 'Pixel als bewusste Entscheidung.', '', 0 ] ] ); database.prepare('DELETE FROM usage_source_snapshots').run(); database .prepare( `INSERT INTO usage_source_snapshots (id, usage_id, label, url, timecode_seconds, position) SELECT 'seed-source-' || u.id || '-' || ps.position, u.id, ps.label, ps.url, ps.timecode_seconds, ps.position FROM piece_usages u JOIN piece_sources ps ON ps.piece_id = u.original_piece_id` ) .run(); database.prepare('DELETE FROM usage_topic_snapshots').run(); database .prepare( `INSERT INTO usage_topic_snapshots (usage_id, name, position) SELECT usage_id, name, position FROM ( SELECT u.id AS usage_id, t.name, row_number() OVER (PARTITION BY u.id ORDER BY t.name, t.id) - 1 AS position FROM piece_usages u JOIN piece_topics pt ON pt.piece_id = u.original_piece_id JOIN topics t ON t.id = pt.topic_id )` ) .run(); }); seedDatabase(); database.close(); console.log(`Example data ready: ${databasePath}`); } if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) { seed(); }