- 在 package.json 中添加迁移脚本 `migrate:cart-active-unique-index`。 - 修改 `pocketbase.cart-order.js` 文件,更新 `cart_id` 和 `cart_product_id` 字段的必填属性,并添加唯一索引 `idx_tbl_cart_owner_product_active_unique`。 - 在 `pocketbase.ensure-cart-order-autogen-id.js` 中,调整 `cart_id` 字段的必填属性为可选,并确保 `order_id` 字段为必填。 - 在 `pocketbase.product-list.js` 中,新增 `prod_list_barcode` 字段。 - 新增 `make-openapi-standalone.cjs` 脚本,用于处理 OpenAPI 文档。 - 新增 `pocketbase.cart-active-unique-index.js` 脚本,处理购物车的唯一索引和去重逻辑。
146 lines
4.7 KiB
JavaScript
146 lines
4.7 KiB
JavaScript
import { createRequire } from 'module';
|
|
import PocketBase from 'pocketbase';
|
|
|
|
const require = createRequire(import.meta.url);
|
|
|
|
let runtimeConfig = {};
|
|
try {
|
|
runtimeConfig = require('../pocket-base/bai_api_pb_hooks/bai_api_shared/config/runtime.js');
|
|
} catch (_error) {
|
|
runtimeConfig = {};
|
|
}
|
|
|
|
const PB_URL = String(process.env.PB_URL || 'https://bai-api.blv-oa.com/pb').replace(/\/+$/, '');
|
|
const AUTH_TOKEN = process.env.POCKETBASE_AUTH_TOKEN || runtimeConfig.POCKETBASE_AUTH_TOKEN || '';
|
|
const COLLECTION = 'tbl_cart';
|
|
const ACTIVE_UNIQUE_INDEX = 'CREATE UNIQUE INDEX idx_tbl_cart_owner_product_active_unique ON tbl_cart (cart_owner, cart_product_id) WHERE is_delete = 0';
|
|
const TEMP_RULE = '@request.auth.id != ""';
|
|
|
|
if (!AUTH_TOKEN) {
|
|
console.error('Missing POCKETBASE_AUTH_TOKEN');
|
|
process.exit(1);
|
|
}
|
|
|
|
const pb = new PocketBase(PB_URL);
|
|
|
|
function buildCollectionPayload(base, overrides = {}) {
|
|
const rawFields = Array.isArray(base.fields) ? base.fields : [];
|
|
const safeFields = rawFields.filter((field) => field && field.name !== 'id');
|
|
|
|
return {
|
|
name: base.name,
|
|
type: base.type,
|
|
listRule: Object.prototype.hasOwnProperty.call(overrides, 'listRule') ? overrides.listRule : base.listRule,
|
|
viewRule: Object.prototype.hasOwnProperty.call(overrides, 'viewRule') ? overrides.viewRule : base.viewRule,
|
|
createRule: Object.prototype.hasOwnProperty.call(overrides, 'createRule') ? overrides.createRule : base.createRule,
|
|
updateRule: Object.prototype.hasOwnProperty.call(overrides, 'updateRule') ? overrides.updateRule : base.updateRule,
|
|
deleteRule: Object.prototype.hasOwnProperty.call(overrides, 'deleteRule') ? overrides.deleteRule : base.deleteRule,
|
|
fields: safeFields,
|
|
indexes: Object.prototype.hasOwnProperty.call(overrides, 'indexes') ? overrides.indexes : (base.indexes || []),
|
|
};
|
|
}
|
|
|
|
async function setTempRules(collection) {
|
|
const payload = buildCollectionPayload(collection, {
|
|
listRule: TEMP_RULE,
|
|
viewRule: TEMP_RULE,
|
|
createRule: TEMP_RULE,
|
|
updateRule: TEMP_RULE,
|
|
deleteRule: TEMP_RULE,
|
|
});
|
|
await pb.collections.update(collection.id, payload);
|
|
}
|
|
|
|
async function restoreRules(collection) {
|
|
await pb.collections.update(collection.id, buildCollectionPayload(collection));
|
|
}
|
|
|
|
function groupKey(record) {
|
|
const owner = String(record.cart_owner || '').trim();
|
|
const product = String(record.cart_product_id || '').trim();
|
|
return `${owner}||${product}`;
|
|
}
|
|
|
|
async function dedupeActiveRows() {
|
|
const rows = await pb.collection(COLLECTION).getFullList({
|
|
filter: 'is_delete = 0',
|
|
sort: '-updated',
|
|
fields: 'id,cart_owner,cart_product_id,is_delete,updated',
|
|
});
|
|
|
|
const groups = new Map();
|
|
for (const row of rows) {
|
|
const key = groupKey(row);
|
|
if (!groups.has(key)) groups.set(key, []);
|
|
groups.get(key).push(row);
|
|
}
|
|
|
|
let duplicateGroups = 0;
|
|
let softDeleted = 0;
|
|
|
|
for (const [, items] of groups) {
|
|
if (items.length <= 1) continue;
|
|
duplicateGroups += 1;
|
|
|
|
const keep = items[0];
|
|
for (let i = 1; i < items.length; i += 1) {
|
|
const row = items[i];
|
|
await pb.collection(COLLECTION).update(row.id, { is_delete: 1 });
|
|
softDeleted += 1;
|
|
}
|
|
|
|
console.log(`dedupe group keep=${keep.id} deleted=${items.length - 1}`);
|
|
}
|
|
|
|
return { total: rows.length, duplicateGroups, softDeleted };
|
|
}
|
|
|
|
async function applyActiveUniqueIndex() {
|
|
const collection = await pb.collections.getOne(COLLECTION);
|
|
const indexes = Array.isArray(collection.indexes) ? collection.indexes.slice() : [];
|
|
if (!indexes.includes(ACTIVE_UNIQUE_INDEX)) {
|
|
indexes.push(ACTIVE_UNIQUE_INDEX);
|
|
}
|
|
|
|
const payload = buildCollectionPayload(collection, { indexes });
|
|
await pb.collections.update(collection.id, payload);
|
|
|
|
const latest = await pb.collections.getOne(COLLECTION);
|
|
const ok = Array.isArray(latest.indexes) && latest.indexes.includes(ACTIVE_UNIQUE_INDEX);
|
|
if (!ok) {
|
|
throw new Error('Active unique index was not applied.');
|
|
}
|
|
}
|
|
|
|
async function main() {
|
|
pb.authStore.save(AUTH_TOKEN, null);
|
|
console.log(`connect ${PB_URL}`);
|
|
|
|
const original = await pb.collections.getOne(COLLECTION);
|
|
|
|
try {
|
|
await setTempRules(original);
|
|
const dedupe = await dedupeActiveRows();
|
|
console.log(JSON.stringify(dedupe, null, 2));
|
|
} finally {
|
|
const latest = await pb.collections.getOne(COLLECTION);
|
|
const restoreBase = {
|
|
...latest,
|
|
listRule: original.listRule,
|
|
viewRule: original.viewRule,
|
|
createRule: original.createRule,
|
|
updateRule: original.updateRule,
|
|
deleteRule: original.deleteRule,
|
|
};
|
|
await restoreRules(restoreBase);
|
|
}
|
|
|
|
await applyActiveUniqueIndex();
|
|
console.log('active unique index applied');
|
|
}
|
|
|
|
main().catch((error) => {
|
|
console.error('migration failed', error?.response || error?.message || error);
|
|
process.exitCode = 1;
|
|
});
|