95 lines
3.4 KiB
TypeScript
95 lines
3.4 KiB
TypeScript
/**
|
|
* One-shot migration: broaden the `quote_attachments.file` mime-type
|
|
* allowlist to include `text/csv`, `text/plain`, and
|
|
* `application/octet-stream`, so senders that attach CSV / plain-text /
|
|
* unknown-binary files (zip, dwg, etc.) don't get silently dropped at
|
|
* the PocketBase validation layer.
|
|
*
|
|
* Idempotent: re-running is a no-op (just rewrites the same allowlist).
|
|
*
|
|
* Usage: tsx scripts/migrations/broaden-quote-attachments-mime-types.ts
|
|
*/
|
|
import PocketBase from "pocketbase";
|
|
|
|
const PB_URL = process.env.PB_URL || "https://pocketbase-gnqc88suy8fqk6wyryjjdpvl.169.239.180.130.sslip.io";
|
|
const ADMIN_EMAIL = process.env.POCKETBASE_ADMIN_EMAIL;
|
|
const ADMIN_PASSWORD = process.env.POCKETBASE_ADMIN_PASSWORD;
|
|
|
|
if (!ADMIN_EMAIL || !ADMIN_PASSWORD) {
|
|
throw new Error(
|
|
"Set POCKETBASE_ADMIN_EMAIL and POCKETBASE_ADMIN_PASSWORD in the environment."
|
|
);
|
|
}
|
|
|
|
const NEW_MIME_TYPES = [
|
|
"application/pdf",
|
|
"image/png",
|
|
"image/jpeg",
|
|
"image/gif",
|
|
"image/webp",
|
|
"application/msword",
|
|
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
"application/vnd.ms-excel",
|
|
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
"text/csv",
|
|
"text/plain",
|
|
"application/octet-stream",
|
|
];
|
|
|
|
async function main() {
|
|
const pb = new PocketBase(PB_URL);
|
|
console.log(`Authenticating as ${ADMIN_EMAIL} on ${PB_URL}…`);
|
|
await pb.collection("_superusers").authWithPassword(ADMIN_EMAIL, ADMIN_PASSWORD);
|
|
|
|
const list = await pb.collections.getList(1, 1, { filter: 'name = "quote_attachments"' });
|
|
if (list.items.length === 0) {
|
|
throw new Error('Collection "quote_attachments" not found.');
|
|
}
|
|
const col = list.items[0];
|
|
console.log(`Found quote_attachments (id=${col.id}).`);
|
|
|
|
const fileField = col.fields.find((f) => f.name === "file");
|
|
if (!fileField) {
|
|
throw new Error('No "file" field on quote_attachments.');
|
|
}
|
|
// PB stores file-field options in two possible shapes depending on
|
|
// how the collection was created: top-level `mimeTypes` (the API
|
|
// read shape returned by GET /api/collections) or nested under
|
|
// `options.mimeTypes` (the create/update write shape). We check
|
|
// both so the diff is accurate.
|
|
type FileField = { mimeTypes?: unknown; options?: { mimeTypes?: unknown } };
|
|
const ff = fileField as unknown as FileField;
|
|
const currentMimes: string[] = Array.isArray(ff.mimeTypes)
|
|
? (ff.mimeTypes as string[])
|
|
: ((ff.options?.mimeTypes as string[] | undefined) ?? []);
|
|
const missing = NEW_MIME_TYPES.filter((m) => !currentMimes.includes(m));
|
|
if (missing.length === 0) {
|
|
console.log("Allowlist already up to date. Nothing to do.");
|
|
return;
|
|
}
|
|
console.log(`Adding ${missing.length} mime type(s): ${missing.join(", ")}`);
|
|
|
|
const newFields = col.fields.map((f) => {
|
|
if (f.name !== "file") return f;
|
|
// Write to BOTH shapes so the change persists regardless of which
|
|
// shape PB's collections.update normalises into. PB ignores the
|
|
// unknown one and stores the one it expects.
|
|
const current = f as unknown as FileField;
|
|
return {
|
|
...f,
|
|
mimeTypes: NEW_MIME_TYPES,
|
|
options: {
|
|
...(current.options || {}),
|
|
mimeTypes: NEW_MIME_TYPES,
|
|
},
|
|
};
|
|
});
|
|
|
|
await pb.collections.update(col.id, { fields: newFields });
|
|
console.log("✓ quote_attachments.file.mimeTypes updated.");
|
|
}
|
|
|
|
main().catch((err) => {
|
|
console.error(err);
|
|
process.exit(1);
|
|
});
|