395 lines
10 KiB
TypeScript
395 lines
10 KiB
TypeScript
import { PB_ENDPOINT, PB_EMAIL, PB_PASSWORD } from "../src/env.ts";
|
|
|
|
interface FieldDef {
|
|
name: string;
|
|
type: string;
|
|
required?: boolean;
|
|
unique?: boolean;
|
|
max?: number;
|
|
min?: number;
|
|
values?: string[];
|
|
maxSelect?: number;
|
|
collectionId?: string;
|
|
cascadeDelete?: boolean;
|
|
}
|
|
|
|
interface CollectionDef {
|
|
name: string;
|
|
type: string;
|
|
fields: FieldDef[];
|
|
listRule?: string | null;
|
|
viewRule?: string | null;
|
|
createRule?: string | null;
|
|
updateRule?: string | null;
|
|
deleteRule?: string | null;
|
|
}
|
|
|
|
async function getSuperadminToken(): Promise<string> {
|
|
const res = await fetch(
|
|
`${PB_ENDPOINT}/api/collections/_superusers/auth-with-password`,
|
|
{
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ identity: PB_EMAIL, password: PB_PASSWORD }),
|
|
},
|
|
);
|
|
const data = await res.json();
|
|
if (!res.ok) throw new Error(`Auth failed: ${JSON.stringify(data)}`);
|
|
return data.token;
|
|
}
|
|
|
|
async function createCollection(
|
|
token: string,
|
|
col: CollectionDef,
|
|
): Promise<string | null> {
|
|
const existing = await fetch(
|
|
`${PB_ENDPOINT}/api/collections?filter=name='${col.name}'`,
|
|
{ headers: { Authorization: `Bearer ${token}` } },
|
|
);
|
|
const existingData = await existing.json();
|
|
if (existingData?.items?.length > 0) {
|
|
console.log(` ↳ Already exists: ${col.name}`);
|
|
return existingData.items[0].id;
|
|
}
|
|
|
|
const res = await fetch(`${PB_ENDPOINT}/api/collections`, {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
Authorization: `Bearer ${token}`,
|
|
},
|
|
body: JSON.stringify(col),
|
|
});
|
|
const data = await res.json();
|
|
if (!res.ok)
|
|
throw new Error(`Create ${col.name} failed: ${JSON.stringify(data)}`);
|
|
console.log(` ✓ Created: ${col.name}`);
|
|
return data.id;
|
|
}
|
|
|
|
function text(name: string, required = false): FieldDef {
|
|
return { name, type: "text", required };
|
|
}
|
|
|
|
function uniqueText(name: string): FieldDef {
|
|
return { name, type: "text", required: true, unique: true };
|
|
}
|
|
|
|
function number(name: string, required = false): FieldDef {
|
|
return { name, type: "number", required };
|
|
}
|
|
|
|
function bool(name: string): FieldDef {
|
|
return { name, type: "bool" };
|
|
}
|
|
|
|
function date(name: string): FieldDef {
|
|
return { name, type: "date" };
|
|
}
|
|
|
|
function jsonField(name: string): FieldDef {
|
|
return { name, type: "json" };
|
|
}
|
|
|
|
function select(name: string, values: string[], required = false): FieldDef {
|
|
return { name, type: "select", required, values, maxSelect: 1 };
|
|
}
|
|
|
|
function rel(name: string, collectionId: string, required = false): FieldDef {
|
|
return {
|
|
name,
|
|
type: "relation",
|
|
required,
|
|
collectionId,
|
|
maxSelect: 1,
|
|
cascadeDelete: false,
|
|
};
|
|
}
|
|
|
|
async function main() {
|
|
console.log("Connecting to PB at", PB_ENDPOINT);
|
|
const token = await getSuperadminToken();
|
|
console.log("Authenticated as superadmin\n");
|
|
|
|
const ids: Record<string, string> = {};
|
|
|
|
// ── Pass 1: Independent collections ──
|
|
console.log("--- Pass 1: Independent collections ---");
|
|
|
|
ids.fams = await createCollection(token, {
|
|
name: "fams",
|
|
type: "base",
|
|
listRule: null,
|
|
viewRule: null,
|
|
createRule: null,
|
|
updateRule: null,
|
|
deleteRule: null,
|
|
fields: [
|
|
text("name", true),
|
|
uniqueText("slug"),
|
|
text("inviteCode"),
|
|
text("stripeCustomerId"),
|
|
jsonField("featureFlags"),
|
|
jsonField("seasons"),
|
|
],
|
|
});
|
|
|
|
// ── Pass 2: Collections that reference fams ──
|
|
console.log("\n--- Pass 2: fam-scoped collections ---");
|
|
|
|
ids.settings = await createCollection(token, {
|
|
name: "settings",
|
|
type: "base",
|
|
listRule: "",
|
|
viewRule: "",
|
|
createRule: null,
|
|
updateRule: null,
|
|
deleteRule: null,
|
|
fields: [rel("famId", ids.fams!, true), text("webhookUrl")],
|
|
});
|
|
|
|
ids.members = await createCollection(token, {
|
|
name: "members",
|
|
type: "base",
|
|
listRule: "",
|
|
viewRule: "",
|
|
createRule: null,
|
|
updateRule: null,
|
|
deleteRule: null,
|
|
fields: [
|
|
rel("famId", ids.fams!, true),
|
|
text("name", true),
|
|
text("color"),
|
|
text("deviceToken"),
|
|
text("deviceTokenHint"),
|
|
],
|
|
});
|
|
|
|
ids.chore_templates = await createCollection(token, {
|
|
name: "chore_templates",
|
|
type: "base",
|
|
listRule: "",
|
|
viewRule: "",
|
|
createRule: null,
|
|
updateRule: null,
|
|
deleteRule: null,
|
|
fields: [
|
|
rel("famId", ids.fams!, true),
|
|
text("name", true),
|
|
text("description"),
|
|
select("defaultFrequency", ["daily", "weekly"], true),
|
|
select("defaultType", ["points", "money"], true),
|
|
number("defaultValue", true),
|
|
],
|
|
});
|
|
|
|
ids.fam_admins = await createCollection(token, {
|
|
name: "fam_admins",
|
|
type: "base",
|
|
listRule: "",
|
|
viewRule: "",
|
|
createRule: null,
|
|
updateRule: null,
|
|
deleteRule: null,
|
|
fields: [
|
|
rel("famId", ids.fams!, true),
|
|
text("userId", true),
|
|
text("email", true),
|
|
text("name"),
|
|
text("color"),
|
|
],
|
|
});
|
|
|
|
ids.bonus_configs = await createCollection(token, {
|
|
name: "bonus_configs",
|
|
type: "base",
|
|
listRule: "",
|
|
viewRule: "",
|
|
createRule: null,
|
|
updateRule: null,
|
|
deleteRule: null,
|
|
fields: [
|
|
rel("famId", ids.fams!, true),
|
|
text("name", true),
|
|
text("description"),
|
|
select("target", ["individual", "competitive", "collaborative"], true),
|
|
select("type", ["threshold", "count", "manual"], true),
|
|
select("occurrence", ["recurring", "once"], true),
|
|
select("rewardType", ["points", "cash", "prize"], true),
|
|
text("rewardValue", true),
|
|
number("criteriaValue"),
|
|
rel("memberId", ids.members!),
|
|
select("period", ["schedule", "daily", "weekly", "monthly"]),
|
|
select("status", ["active", "completed"], true),
|
|
],
|
|
});
|
|
|
|
ids.bonus_templates = await createCollection(token, {
|
|
name: "bonus_templates",
|
|
type: "base",
|
|
listRule: "",
|
|
viewRule: "",
|
|
createRule: null,
|
|
updateRule: null,
|
|
deleteRule: null,
|
|
fields: [
|
|
rel("famId", ids.fams!, true),
|
|
text("name", true),
|
|
text("description"),
|
|
select("target", ["individual", "competitive", "collaborative"], true),
|
|
select("type", ["threshold", "count", "manual"], true),
|
|
select("occurrence", ["recurring", "once"], true),
|
|
select("rewardType", ["points", "cash", "prize"], true),
|
|
text("rewardValue", true),
|
|
number("criteriaValue"),
|
|
select("period", ["schedule", "daily", "weekly", "monthly"]),
|
|
],
|
|
});
|
|
|
|
// ── Pass 3: Collections with member/template deps ──
|
|
console.log("\n--- Pass 3: Nested collections ---");
|
|
|
|
ids.weekly_history = await createCollection(token, {
|
|
name: "weekly_history",
|
|
type: "base",
|
|
listRule: "",
|
|
viewRule: "",
|
|
createRule: null,
|
|
updateRule: null,
|
|
deleteRule: null,
|
|
fields: [
|
|
rel("famId", ids.fams!, true),
|
|
rel("memberId", ids.members!, true),
|
|
date("weekStart"),
|
|
number("pointsEarned"),
|
|
number("moneyEarned"),
|
|
number("choresCompleted"),
|
|
number("bonusEarned"),
|
|
],
|
|
});
|
|
|
|
ids.rewards = await createCollection(token, {
|
|
name: "rewards",
|
|
type: "base",
|
|
listRule: "",
|
|
viewRule: "",
|
|
createRule: null,
|
|
updateRule: null,
|
|
deleteRule: null,
|
|
fields: [
|
|
rel("famId", ids.fams!, true),
|
|
rel("memberId", ids.members!, true),
|
|
rel("bonusConfigId", ids.bonus_configs!),
|
|
text("label", true),
|
|
number("value", true),
|
|
select("rewardType", ["cash", "prize", "points"], true),
|
|
select("status", ["unclaimed", "requested", "claimed"], true),
|
|
select("claimable", ["immediate", "payday"], true),
|
|
text("settleDate"),
|
|
date("claimedAt"),
|
|
date("requestedAt"),
|
|
text("date"),
|
|
],
|
|
});
|
|
|
|
ids.assigned_chores = await createCollection(token, {
|
|
name: "assigned_chores",
|
|
type: "base",
|
|
listRule: "",
|
|
viewRule: "",
|
|
createRule: null,
|
|
updateRule: null,
|
|
deleteRule: null,
|
|
fields: [
|
|
rel("famId", ids.fams!, true),
|
|
rel("memberId", ids.members!, true),
|
|
rel("templateId", ids.chore_templates!, true),
|
|
select("frequency", ["daily", "weekly"], true),
|
|
select("type", ["points", "money"], true),
|
|
number("value", true),
|
|
text("customName"),
|
|
jsonField("seasonIds"),
|
|
],
|
|
});
|
|
|
|
ids.seasons = await createCollection(token, {
|
|
name: "seasons",
|
|
type: "base",
|
|
listRule: "",
|
|
viewRule: "",
|
|
createRule: null,
|
|
updateRule: null,
|
|
deleteRule: null,
|
|
fields: [
|
|
rel("famId", ids.fams!, true),
|
|
text("name", true),
|
|
text("color"),
|
|
bool("active"),
|
|
date("autoDisable"),
|
|
date("autoStart"),
|
|
],
|
|
});
|
|
|
|
ids.completions = await createCollection(token, {
|
|
name: "completions",
|
|
type: "base",
|
|
listRule: "",
|
|
viewRule: "",
|
|
createRule: null,
|
|
updateRule: null,
|
|
deleteRule: null,
|
|
fields: [
|
|
rel("famId", ids.fams!, true),
|
|
rel("memberId", ids.members!, true),
|
|
rel("assignedChoreId", ids.assigned_chores!, true),
|
|
date("date"),
|
|
date("completedAt"),
|
|
],
|
|
});
|
|
|
|
// Chat collections (global family messages + transient typing presence)
|
|
ids.messages = await createCollection(token, {
|
|
name: "messages",
|
|
type: "base",
|
|
listRule: "",
|
|
viewRule: "",
|
|
createRule: null,
|
|
updateRule: null,
|
|
deleteRule: null,
|
|
fields: [
|
|
rel("famId", ids.fams!, true),
|
|
select("authorType", ["admin", "member"], true),
|
|
text("authorId", true),
|
|
text("authorName", true),
|
|
text("authorColor"),
|
|
text("content", true),
|
|
],
|
|
});
|
|
|
|
ids.chat_typing = await createCollection(token, {
|
|
name: "chat_typing",
|
|
type: "base",
|
|
listRule: "",
|
|
viewRule: "",
|
|
createRule: null,
|
|
updateRule: null,
|
|
deleteRule: null,
|
|
fields: [
|
|
rel("famId", ids.fams!, true),
|
|
text("actorId", true),
|
|
select("actorType", ["admin", "member"], true),
|
|
text("authorName", true),
|
|
text("authorColor"),
|
|
bool("typing"),
|
|
],
|
|
});
|
|
|
|
console.log("\n✅ All collections created successfully");
|
|
console.log("Collection IDs:", ids);
|
|
}
|
|
|
|
main().catch((err) => {
|
|
console.error("Seed failed:", err);
|
|
process.exit(1);
|
|
});
|