// One-shot: copy Bonna's laptop prototype data (SQLite) into the hosted // Postgres. Run from app/ with DATABASE_URL set: // node scripts/migrate-sqlite.mjs /path/to/prototype/data/studio.db // Idempotent-ish: skips any table that already has rows in Postgres. import { DatabaseSync } from "node:sqlite"; import { PrismaClient } from "@prisma/client"; const src = process.argv[2]; if (!src) { console.error("usage: node scripts/migrate-sqlite.mjs "); process.exit(1); } const lite = new DatabaseSync(src); const db = new PrismaClient(); const rows = (sql) => { try { return lite.prepare(sql).all(); } catch { return []; } }; const d = (s) => (s ? new Date(s.replace(" ", "T")) : new Date()); async function copy(name, model, sqliteSql, map) { if ((await model.count()) > 0) return console.log(`${name}: postgres has rows — skipped`); const data = rows(sqliteSql); for (const r of data) await model.create({ data: map(r) }); console.log(`${name}: ${data.length} migrated`); } // order matters (children reference parents; ids preserved by inserting in id order) await copy("suppliers", db.supplier, "SELECT * FROM suppliers ORDER BY id", (r) => ({ id: r.id, name: r.name, kind: r.kind, distance: r.distance, url: r.url, notes: r.notes, searchUrl: r.search_url || "", created: d(r.created) })); await copy("price_entries", db.priceEntry, "SELECT * FROM price_entries ORDER BY id", (r) => ({ id: r.id, item: r.item, supplierId: r.supplier_id, price: r.price, unit: r.unit, shipping: r.shipping, note: r.note, created: d(r.created) })); await copy("glazes", db.glaze, "SELECT * FROM glazes ORDER BY id", (r) => ({ id: r.id, name: r.name, kind: r.kind, cone: r.cone, atmosphere: r.atmosphere, surface: r.surface, source: r.source, sg: r.sg, swatch: r.swatch, created: d(r.created) })); await copy("glaze_materials", db.glazeMaterial, "SELECT * FROM glaze_materials ORDER BY id", (r) => ({ id: r.id, glazeId: r.glaze_id, name: r.name, pct: r.pct, addition: !!r.addition })); await copy("glaze_journal", db.glazeJournal, "SELECT * FROM glaze_journal ORDER BY id", (r) => ({ id: r.id, glazeId: r.glaze_id, text: r.text, created: d(r.created) })); await copy("firings", db.firing, "SELECT * FROM firings ORDER BY id", (r) => ({ id: r.id, type: r.type, cone: r.cone, schedule: r.schedule, status: r.status, resultNotes: r.result_notes, started: d(r.started), unloaded: r.unloaded ? d(r.unloaded) : null })); await copy("firing_items", db.firingItem, "SELECT * FROM firing_items ORDER BY id", (r) => ({ id: r.id, firingId: r.firing_id, piece: r.piece, clay: r.clay, glaze: r.glaze, rating: r.rating, note: r.note })); await copy("products", db.product, "SELECT * FROM products ORDER BY id", (r) => ({ id: r.id, name: r.name, kind: r.kind, category: r.category, price: r.price, cost: r.cost, qty: r.qty, status: r.status, notes: r.notes || "", created: d(r.created) })); await copy("sales", db.sale, "SELECT * FROM sales ORDER BY id", (r) => ({ id: r.id, productId: r.product_id, qty: r.qty, price: r.price, cost: r.cost, created: d(r.created) })); await copy("todos", db.todo, "SELECT * FROM todos ORDER BY id", (r) => ({ id: r.id, text: r.text, done: !!r.done, parentId: r.parent_id, created: d(r.created) })); await copy("shopping", db.shopping, "SELECT * FROM shopping ORDER BY id", (r) => ({ id: r.id, text: r.text, done: !!r.done, created: d(r.created) })); await copy("notes", db.note, "SELECT * FROM notes ORDER BY id", (r) => ({ id: r.id, text: r.text, tags: r.tags, created: d(r.created) })); await copy("photos", db.photo, "SELECT * FROM photos ORDER BY id", (r) => ({ id: r.id, entity: r.entity, entityId: r.entity_id, filename: r.filename, created: d(r.created) })); await copy("files", db.fileAsset, "SELECT * FROM files ORDER BY id", (r) => ({ id: r.id, entity: r.entity, entityId: r.entity_id, stored: r.stored, name: r.name, tag: r.tag, size: r.size, created: d(r.created) })); // bump Postgres sequences past the migrated ids for (const t of ["User", "Note", "Todo", "Shopping", "Glaze", "GlazeMaterial", "GlazeJournal", "Firing", "FiringItem", "Photo", "FileAsset", "Product", "Sale", "Supplier", "PriceEntry"]) { await db.$executeRawUnsafe( `SELECT setval(pg_get_serial_sequence('"${t}"','id'), COALESCE((SELECT MAX(id) FROM "${t}"), 0) + 1, false)`); } console.log("sequences bumped — done 🌙 (photos/files binaries: copy prototype/data/{photos,files} into the container's /app/data)"); await db.$disconnect();