WFDASH-WEBAPP/scripts/migrations/create-quote-attachments-and-replies.ts

102 lines
4 KiB
TypeScript

import fs from "node:fs";
import path from "node:path";
import PocketBase from "pocketbase";
const PB_URL = process.env.NEXT_PUBLIC_POCKETBASE_URL || "http://127.0.0.1:8090";
const ADMIN_EMAIL = process.env.PB_ADMIN_EMAIL;
const ADMIN_PASSWORD = process.env.PB_ADMIN_PASSWORD;
if (!ADMIN_EMAIL || !ADMIN_PASSWORD) {
throw new Error("PB_ADMIN_EMAIL and PB_ADMIN_PASSWORD are required.");
}
const ROOT = path.resolve(process.cwd(), "docs", "pocketbase");
/**
* Idempotent migration that creates the quote-email-intake collections:
* - quote_attachments (relation to quotes; holds email PDFs/images)
* - quote_replies (relation to quotes; holds email reply threads)
* - quote_counters (atomic year/month sequence for quote numbers)
*
* Also adds the `email_message_id` field to the existing `quotes` collection
* (for inbox-dedup). Re-runs are safe: every step checks for existence first.
*/
async function main() {
const pb = new PocketBase(PB_URL);
await pb.collection("_superusers").authWithPassword(ADMIN_EMAIL, ADMIN_PASSWORD);
const collections = await pb.collections.getFullList();
const byName = new Map(collections.map((c) => [c.name, c]));
const quotes = byName.get("quotes");
if (!quotes) {
throw new Error("Missing required collection: quotes. Run `npm run setup` first.");
}
const quotesId = quotes.id;
// ── 1. Add email_message_id to existing quotes collection (idempotent) ──
const quotesHasMessageId = quotes.fields.some((f) => f.name === "email_message_id");
if (quotesHasMessageId) {
console.log("quotes.email_message_id already exists, skipping field add.");
} else {
await pb.collections.update(quotesId, {
fields: [
...quotes.fields,
{ name: "email_message_id", type: "text", required: false, max: 255 },
],
});
// Add a unique-ish index to make Message-ID lookups O(log n) and to give us
// a strong signal even if the unique constraint is later relaxed.
try {
await pb.collections.update(quotesId, {
indexes: [
...(quotes.indexes || []),
"CREATE INDEX idx_quotes_email_message_id ON quotes (email_message_id)",
],
});
console.log("Added index idx_quotes_email_message_id on quotes.");
} catch (err) {
console.warn("Could not add email_message_id index (may already exist):", err);
}
console.log("Added quotes.email_message_id field.");
}
// ── 2. quote_attachments ──
const existingAttachments = byName.get("quote_attachments");
if (existingAttachments) {
console.log("quote_attachments already exists, skipping create.");
} else {
const def = JSON.parse(fs.readFileSync(path.join(ROOT, "quote_attachments.collection.json"), "utf8"));
const normalized = JSON.parse(JSON.stringify(def).replaceAll("REPLACE_QUOTES_COLLECTION_ID", quotesId));
const created = await pb.collections.create(normalized);
console.log(`Created ${created.name} (${created.id})`);
}
// ── 3. quote_replies ──
const existingReplies = byName.get("quote_replies");
if (existingReplies) {
console.log("quote_replies already exists, skipping create.");
} else {
const def = JSON.parse(fs.readFileSync(path.join(ROOT, "quote_replies.collection.json"), "utf8"));
const normalized = JSON.parse(JSON.stringify(def).replaceAll("REPLACE_QUOTES_COLLECTION_ID", quotesId));
const created = await pb.collections.create(normalized);
console.log(`Created ${created.name} (${created.id})`);
}
// ── 4. quote_counters ──
const existingCounters = byName.get("quote_counters");
if (existingCounters) {
console.log("quote_counters already exists, skipping create.");
} else {
const def = JSON.parse(fs.readFileSync(path.join(ROOT, "quote_counters.collection.json"), "utf8"));
const created = await pb.collections.create(def);
console.log(`Created ${created.name} (${created.id})`);
}
console.log("Quote email intake migration complete.");
}
main().catch((err) => {
console.error(err);
process.exit(1);
});