Files
Web_BAI_Manage_ApiServer/script/pocketbase.cart-active-unique-index.js
XuJiacheng ec6b59b4fa feat: 添加购物车相关迁移和索引功能
- 在 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` 脚本,处理购物车的唯一索引和去重逻辑。
2026-04-09 14:49:12 +08:00

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;
});