/** * Seed a demo dataset: one client, one project with default columns, an owner, * a client manager, and a few cards. * * Idempotent — re-running updates the existing records rather than duplicating * them, so it is safe to run between test runs. * * Run with: npm run seed (PocketBase must be running) */ import PocketBase from "pocketbase"; import { buildColumnRecords, assertColumnInvariants } from "../src/lib/columns/seed"; import { SPACING } from "../src/lib/ordering/fractional"; import { nextCardNumber } from "../src/lib/cards/number"; const PB_URL = process.env.NEXT_PUBLIC_PB_URL ?? "http://127.0.0.1:8090"; export const DEMO = { clientSlug: "wf", projectSlug: "wfdash-webapp", ownerEmail: "owner@example.com", ownerPassword: "owner-password-123", managerEmail: "manager@wf.example.com", managerPassword: "manager-password-123", }; async function upsert( pb: PocketBase, collection: string, filter: string, data: Record ): Promise { const existing = await pb .collection(collection) .getFirstListItem(filter) .catch(() => null); if (existing) { return pb.collection(collection).update(existing.id, data); } return pb.collection(collection).create(data); } async function main() { const email = process.env.PB_SUPERUSER_EMAIL; const password = process.env.PB_SUPERUSER_PASSWORD; if (!email || !password) { throw new Error( "PB_SUPERUSER_EMAIL and PB_SUPERUSER_PASSWORD must be set. Copy .env.example to .env.local and export them." ); } const pb = new PocketBase(PB_URL); pb.autoCancellation(false); await pb.collection("_superusers").authWithPassword(email, password); const client = await upsert<{ id: string }>( pb, "clients", `slug = "${DEMO.clientSlug}"`, { name: "WF", slug: DEMO.clientSlug, brand_color: "#3b82f6", active: true, } ); const project = await upsert<{ id: string }>( pb, "projects", `slug = "${DEMO.projectSlug}"`, { client: client.id, name: "WFDash Web App", slug: DEMO.projectSlug, description: "Operations web application.", status: "active", color: "#3b82f6", repo_owner: "David", repo_name: "WFDASH-WEBAPP", } ); const columnInputs = buildColumnRecords(project.id); assertColumnInvariants(columnInputs); const columns: Record = {}; for (const input of columnInputs) { const record = await upsert<{ id: string }>( pb, "columns", `project = "${project.id}" && name = "${input.name}"`, { ...input } ); columns[input.name] = record.id; } await upsert(pb, "users", `email = "${DEMO.ownerEmail}"`, { email: DEMO.ownerEmail, password: DEMO.ownerPassword, passwordConfirm: DEMO.ownerPassword, name: "Dave", role: "owner", verified: true, }); await upsert(pb, "users", `email = "${DEMO.managerEmail}"`, { email: DEMO.managerEmail, password: DEMO.managerPassword, passwordConfirm: DEMO.managerPassword, name: "WF Manager", role: "client_manager", client: client.id, verified: true, }); const cards = [ { title: "Quote attachments split", column: "In Progress", priority: "high" }, { title: "Stock inline quantity editing", column: "To Do", priority: "medium" }, { title: "Delivery note grouping", column: "Done", priority: "low" }, ]; // Pre-fetch existing numbers so re-seeding keeps the original numbering // and only fills in the gap when a card is missing. const existingNumbers = ( await pb .collection("cards") .getFullList({ filter: `project = "${project.id}"`, fields: "number" }) ).map((c) => Number(c.number) || 0); let position = SPACING; for (const card of cards) { const cardNumber = nextCardNumber(existingNumbers); existingNumbers.push(cardNumber); await upsert( pb, "cards", `project = "${project.id}" && title = "${card.title}"`, { project: project.id, column: columns[card.column], title: card.title, description: "", position, priority: card.priority, created_by_role: "owner", accepted: true, archived: false, number: cardNumber, } ); position += SPACING; } console.log("Seeded:"); console.log(` client ${client.id} (${DEMO.clientSlug})`); console.log(` project ${project.id} (${DEMO.projectSlug})`); console.log(` owner ${DEMO.ownerEmail} / ${DEMO.ownerPassword}`); console.log(` manager ${DEMO.managerEmail} / ${DEMO.managerPassword}`); } main().catch((error) => { console.error(error); process.exit(1); });