102 lines
4 KiB
TypeScript
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);
|
|
});
|