import { one, query } from './db'; import { hashPassword } from './auth'; /** Legt beim Erststart Nutzer, Standard-Rezepte, Einstellungen und Modelle an. */ export async function seed(): Promise { // Einstellungen (eine Zeile) await query(`INSERT INTO settings (id) VALUES (1) ON CONFLICT (id) DO NOTHING`); // Standard-Galerie vorbelegen (wie initial gebrieft), falls leer await query(`UPDATE settings SET picdrop_default_gallery='POSTER LEA' WHERE id=1 AND picdrop_default_gallery IS NULL`); // Modelle vorbelegen (freundliche Namen, keine rohen IDs in der UI) const mCount = await one<{ n: string }>(`SELECT count(*)::text AS n FROM models`); if (mCount && Number(mCount.n) === 0) { await query(`INSERT INTO models (model_id, label, description, active, is_default, supports_alpha, sort) VALUES ('google/gemini-3.1-flash-image','Schnell','In wenigen Sekunden fertig. Für den Alltag.',true,true,true,0), ('google/gemini-3-pro-image','Beste Qualität','Dauert länger, trifft Schrift und Details genauer.',true,false,true,1)`); } // Open-Source-Option (FLUX.2, offene Gewichte) nachrüsten — auch für bestehende Installationen. await query(`INSERT INTO models (model_id, label, description, active, is_default, supports_alpha, sort) SELECT 'black-forest-labs/flux.2-flex','Open Source (FLUX.2)','Offene Gewichte, gute Qualität. Kein transparentes Freistellen.',true,false,false,2 WHERE NOT EXISTS (SELECT 1 FROM models WHERE model_id='black-forest-labs/flux.2-flex')`); // Nutzer const till = await one(`SELECT id FROM users WHERE username='till'`); if (!till) { await query( `INSERT INTO users (username, role, display_name, password_hash) VALUES ($1,'admin','Till',$2)`, ['till', await hashPassword(process.env.SEED_TILL_PASSWORD || 'klarbild-till')]); } const lea = await one(`SELECT id FROM users WHERE username='lea'`); if (!lea) { await query( `INSERT INTO users (username, role, display_name, password_hash) VALUES ($1,'user','Lea',$2)`, ['lea', await hashPassword(process.env.SEED_LEA_PASSWORD || 'klarbild-lea')]); } // Standard-Rezepte const count = await one<{ n: string }>(`SELECT count(*)::text AS n FROM recipes`); if (count && Number(count.n) === 0) { const admin = await one<{ id: string }>(`SELECT id FROM users WHERE username='till'`); const by = admin?.id ?? null; const R = (name: string, tasks: string[], fmt: string, orient: string, extra: Record = {}) => query( `INSERT INTO recipes (name, tasks, output_format, orientation, crop_mode, dpi, delivery, is_default, created_by, contour_mm) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10)`, [name, JSON.stringify(tasks), fmt, orient, (extra.crop_mode as string) || 'crop', 300, (extra.delivery as string) || 'library', !!extra.is_default, by, (extra.contour_mm as number) ?? null]); await R('Nur bereinigen', ['clean'], 'keep', 'landscape', { is_default: true }); await R('Poster 30×40', ['clean', 'format'], '30x40', 'portrait'); await R('The Frame', ['clean', 'format'], 'theframe', 'landscape', { delivery: 'both', picdrop_gallery: 'TheFrame-Backgrounds' }); await R('Sticker 5 cm', ['clean', 'cutout', 'format', 'contour'], 'sticker5', 'landscape', { contour_mm: 3 }); } // Druck-Vorlagen (Bogen ohne KI) — idempotent über den Namen, auch für bestehende Installationen. { const admin = await one<{ id: string }>(`SELECT id FROM users WHERE username='till'`); const by = admin?.id ?? null; const P = async (name: string, config: Record) => { const exists = await one(`SELECT id FROM print_presets WHERE name=$1`, [name]); if (exists) return; await query(`INSERT INTO print_presets (name, created_by, config) VALUES ($1,$2,$3)`, [name, by, JSON.stringify(config)]); }; const sheet = { paperId: 'A4', paperCustom: '', paperLandscape: false, marginMm: 5, autoGap: true, gapMm: 14, bleedMm: 0, marks: 'corner', markLen: 4, markOff: 3, dpi: 300, ext: 'jpg', center: true, footer: true, }; const F = (sizeId: string, count: number, landscape = false) => ({ sizeId, customSize: '', landscape, count, allowRotate: true }); // Der klassische Kita-/Schulfoto-Satz aus einem Bild. await P('Kita-Satz (A4)', { ...sheet, formats: [F('S13x18', 1), F('S9x13', 2), F('P35x45', 8)] }); // Nur die kleinen Abzüge, wenn der große schon gedruckt ist. await P('Schulsatz klein (A4)', { ...sheet, formats: [F('S9x13', 4), F('K30x40', 8)] }); // Voller Passbildbogen — durchgehende Linien, weil alles gleich groß ist. await P('Passbildbogen 35×45 (A4)', { ...sheet, marks: 'grid', autoGap: false, gapMm: 4, formats: [F('P35x45', 20)] }); // Zwei Bilder in Originalgröße auf ein Blatt 10×15-Fotopapier. await P('2 × 10×7,5 auf Fotopapier 10×15', { ...sheet, paperId: 'F10x15', marginMm: 0, marks: 'grid', autoGap: false, gapMm: 0, formats: [{ sizeId: 'custom', customSize: '10x7,5', landscape: false, count: 2, allowRotate: true }], }); } // The-Frame-Rezept auf die eigene Galerie verdrahten (auch für bestehende Installationen). await query( `UPDATE recipes SET picdrop_gallery='TheFrame-Backgrounds', delivery='both' WHERE output_format='theframe' AND (picdrop_gallery IS NULL OR picdrop_gallery='')`); }