84 lines
2.8 KiB
TypeScript
84 lines
2.8 KiB
TypeScript
|
|
/**
|
||
|
|
* Migration — add `search_text` (text) to `price_list`.
|
||
|
|
*
|
||
|
|
* PB can't substring-match a JSON field, so for the Catalogue tab's
|
||
|
|
* "search everything" feature, we denormalize the searchable bits of
|
||
|
|
* each row into a flat text field. The WFDASH Catalogue helper
|
||
|
|
* (`getPriceLists`) then ORs against `search_text` and the other
|
||
|
|
* string columns.
|
||
|
|
*
|
||
|
|
* What's in search_text (per row, joined with spaces):
|
||
|
|
* - sku
|
||
|
|
* - description
|
||
|
|
* - brand
|
||
|
|
* - supplier
|
||
|
|
* - category
|
||
|
|
* - subcategory
|
||
|
|
* - specifications.synonyms (the buyer-friendly aliases)
|
||
|
|
*
|
||
|
|
* Idempotent: re-runs are safe.
|
||
|
|
*
|
||
|
|
* Run with:
|
||
|
|
* npm run pb:migrate:price-list-search-text
|
||
|
|
*/
|
||
|
|
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.");
|
||
|
|
}
|
||
|
|
|
||
|
|
async function main() {
|
||
|
|
const pb = new PocketBase(PB_URL);
|
||
|
|
await pb.collection("_superusers").authWithPassword(ADMIN_EMAIL, ADMIN_PASSWORD);
|
||
|
|
|
||
|
|
const cols = await pb.collections.getFullList();
|
||
|
|
const priceList = cols.find((c) => c.name === "price_list");
|
||
|
|
if (!priceList) {
|
||
|
|
throw new Error("price_list collection not found.");
|
||
|
|
}
|
||
|
|
|
||
|
|
// Always re-check the index — running the migration on a
|
||
|
|
// collection that already has `search_text` should still ensure
|
||
|
|
// the index exists (handles the case where the field was added
|
||
|
|
// before the index step was introduced).
|
||
|
|
const INDEX_SQL = "CREATE INDEX `idx_pl_search_text` ON `price_list` (`search_text`)";
|
||
|
|
const hasIndex = (priceList.indexes || []).some(
|
||
|
|
(i) => typeof i === "string" && i.includes("idx_pl_search_text"),
|
||
|
|
);
|
||
|
|
|
||
|
|
if (priceList.fields.some((f) => f.name === "search_text")) {
|
||
|
|
if (hasIndex) {
|
||
|
|
console.log("price_list.search_text + index already present, skipping.");
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
await pb.collections.update(priceList.id, { indexes: [...(priceList.indexes || []), INDEX_SQL] });
|
||
|
|
console.log("Added price_list index on search_text (field already existed).");
|
||
|
|
console.log("search_text index migration complete.");
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
await pb.collections.update(priceList.id, {
|
||
|
|
fields: [
|
||
|
|
...priceList.fields,
|
||
|
|
{ name: "search_text", type: "text", required: false, options: { max: 0 } },
|
||
|
|
],
|
||
|
|
// Add a non-unique index so the search box doesn't full-scan
|
||
|
|
// the table once we have thousands of rows.
|
||
|
|
indexes: [
|
||
|
|
...(priceList.indexes || []),
|
||
|
|
"CREATE INDEX `idx_pl_search_text` ON `price_list` (`search_text`)",
|
||
|
|
],
|
||
|
|
});
|
||
|
|
console.log("Added price_list.search_text (+ index)");
|
||
|
|
console.log("search_text migration complete.");
|
||
|
|
}
|
||
|
|
|
||
|
|
main().catch((err) => {
|
||
|
|
console.error(err);
|
||
|
|
process.exit(1);
|
||
|
|
});
|