- number field on cards, unique per project - backfilled existing cards in created order - both create paths (owner action, client API) assign on creation - seed script is idempotent across re-runs
184 lines
No EOL
5 KiB
TypeScript
184 lines
No EOL
5 KiB
TypeScript
/**
|
|
* 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<T extends { id: string }>(
|
|
pb: PocketBase,
|
|
collection: string,
|
|
filter: string,
|
|
data: Record<string, unknown>
|
|
): Promise<T> {
|
|
const existing = await pb
|
|
.collection(collection)
|
|
.getFirstListItem<T>(filter)
|
|
.catch(() => null);
|
|
|
|
if (existing) {
|
|
return pb.collection(collection).update<T>(existing.id, data);
|
|
}
|
|
return pb.collection(collection).create<T>(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<string, string> = {};
|
|
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;
|
|
}
|
|
`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,
|
|
}
|
|
);
|
|
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);
|
|
}); |