Files
Web_BAI_Manage_ApiServer/script/pocketbase.apply-soft-delete-rules.js
XuJiacheng cd0373be3c feat: 添加系统刷新令牌请求和用户统计响应的 OpenAPI 规范
feat: 添加微信认证相关的 OpenAPI 规范,包括用户信息、登录请求和个人资料请求

feat: 添加 is_delete 字段迁移脚本,支持在集合中添加软删除字段

feat: 添加软删除规则应用脚本,确保所有相关集合的查询规则包含软删除条件

feat: 添加购物车和订单业务 ID 自动生成的迁移脚本,确保字段类型和自动生成规则正确
2026-04-07 20:02:10 +08:00

176 lines
6.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { createRequire } from 'module';
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import PocketBase from 'pocketbase';
const require = createRequire(import.meta.url);
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
let runtimeConfig = {};
try {
runtimeConfig = require('../pocket-base/bai_api_pb_hooks/bai_api_shared/config/runtime.js');
} catch (_error) {
runtimeConfig = {};
}
function readEnvFile(filePath) {
if (!fs.existsSync(filePath)) return {};
const content = fs.readFileSync(filePath, 'utf8');
const result = {};
for (const rawLine of content.split(/\r?\n/)) {
const line = rawLine.trim();
if (!line || line.startsWith('#')) continue;
const index = line.indexOf('=');
if (index === -1) continue;
const key = line.slice(0, index).trim();
const value = line.slice(index + 1).trim();
result[key] = value;
}
return result;
}
const backendEnv = readEnvFile(path.resolve(__dirname, '..', 'back-end', '.env'));
const PB_URL = String(
process.env.PB_URL
|| backendEnv.POCKETBASE_API_URL
|| runtimeConfig.POCKETBASE_API_URL
|| 'http://127.0.0.1:8090'
).replace(/\/+$/, '');
const AUTH_TOKEN = process.env.POCKETBASE_AUTH_TOKEN || runtimeConfig.POCKETBASE_AUTH_TOKEN || '';
const SOFT_DELETE_RULE = 'is_delete = 0';
if (!AUTH_TOKEN) {
console.error('❌ 缺少 POCKETBASE_AUTH_TOKEN无法执行软删除规则迁移。');
process.exit(1);
}
const pb = new PocketBase(PB_URL);
function isSystemCollection(collection) {
return !!(collection && (collection.system || String(collection.name || '').startsWith('_')));
}
function normalizeFieldPayload(field) {
const payload = Object.assign({}, field);
if (field.type === 'number') {
if (Object.prototype.hasOwnProperty.call(field, 'default')) payload.default = field.default;
if (Object.prototype.hasOwnProperty.call(field, 'min')) payload.min = field.min;
if (Object.prototype.hasOwnProperty.call(field, 'max')) payload.max = field.max;
if (Object.prototype.hasOwnProperty.call(field, 'onlyInt')) payload.onlyInt = !!field.onlyInt;
}
if (field.type === 'autodate') {
payload.onCreate = typeof field.onCreate === 'boolean' ? field.onCreate : true;
payload.onUpdate = typeof field.onUpdate === 'boolean' ? field.onUpdate : false;
}
if (field.type === 'file') {
payload.maxSelect = typeof field.maxSelect === 'number' ? field.maxSelect : 0;
payload.maxSize = typeof field.maxSize === 'number' ? field.maxSize : 0;
payload.mimeTypes = Array.isArray(field.mimeTypes) ? field.mimeTypes : null;
}
return payload;
}
function mergeRuleWithSoftDelete(rule) {
const currentRule = typeof rule === 'string' ? rule.trim() : '';
if (!currentRule) return SOFT_DELETE_RULE;
if (currentRule === SOFT_DELETE_RULE) return currentRule;
if (currentRule.includes(SOFT_DELETE_RULE)) return currentRule;
return `(${currentRule}) && ${SOFT_DELETE_RULE}`;
}
function buildCollectionPayload(collection) {
return {
name: collection.name,
type: collection.type,
listRule: mergeRuleWithSoftDelete(collection.listRule),
viewRule: mergeRuleWithSoftDelete(collection.viewRule),
createRule: collection.createRule,
updateRule: collection.updateRule,
deleteRule: collection.deleteRule,
fields: (collection.fields || []).filter((field) => field && field.name !== 'id').map((field) => normalizeFieldPayload(field)),
indexes: collection.indexes || [],
};
}
async function applyRules() {
const collections = await pb.collections.getFullList({ sort: 'name' });
const changed = [];
for (const collection of collections) {
if (isSystemCollection(collection)) continue;
const hasSoftDelete = (collection.fields || []).some((field) => field && field.name === 'is_delete');
if (!hasSoftDelete) continue;
const nextListRule = mergeRuleWithSoftDelete(collection.listRule);
const nextViewRule = mergeRuleWithSoftDelete(collection.viewRule);
const listChanged = nextListRule !== (collection.listRule || '');
const viewChanged = nextViewRule !== (collection.viewRule || '');
if (!listChanged && !viewChanged) {
changed.push({ name: collection.name, action: 'skipped', listRule: nextListRule, viewRule: nextViewRule });
continue;
}
await pb.collections.update(collection.id, buildCollectionPayload(collection));
changed.push({ name: collection.name, action: 'updated', listRule: nextListRule, viewRule: nextViewRule });
}
return changed;
}
async function verifyRules() {
const collections = await pb.collections.getFullList({ sort: 'name' });
const failed = [];
for (const collection of collections) {
if (isSystemCollection(collection)) continue;
const hasSoftDelete = (collection.fields || []).some((field) => field && field.name === 'is_delete');
if (!hasSoftDelete) continue;
const listRule = String(collection.listRule || '');
const viewRule = String(collection.viewRule || '');
if (!listRule.includes(SOFT_DELETE_RULE) || !viewRule.includes(SOFT_DELETE_RULE)) {
failed.push({ name: collection.name, listRule, viewRule });
}
}
if (failed.length) {
throw new Error(`以下集合未正确应用软删除规则: ${JSON.stringify(failed, null, 2)}`);
}
}
async function main() {
try {
console.log(`🔄 正在连接 PocketBase: ${PB_URL}`);
pb.authStore.save(AUTH_TOKEN, null);
console.log('✅ 已使用 POCKETBASE_AUTH_TOKEN 载入认证状态。');
const changed = await applyRules();
console.log('📝 规则更新结果:');
console.log(JSON.stringify(changed, null, 2));
await verifyRules();
console.log('✅ 校验通过:所有带 is_delete 的业务集合已默认过滤 is_delete = 0。');
console.log('🎉 软删除默认查询规则迁移完成!');
} catch (error) {
console.error('❌ 软删除默认查询规则迁移失败:', error.response?.data || error.message || error);
process.exitCode = 1;
}
}
main();