feat: 添加系统刷新令牌请求和用户统计响应的 OpenAPI 规范
feat: 添加微信认证相关的 OpenAPI 规范,包括用户信息、登录请求和个人资料请求 feat: 添加 is_delete 字段迁移脚本,支持在集合中添加软删除字段 feat: 添加软删除规则应用脚本,确保所有相关集合的查询规则包含软删除条件 feat: 添加购物车和订单业务 ID 自动生成的迁移脚本,确保字段类型和自动生成规则正确
This commit is contained in:
233
script/pocketbase.add-is-delete-field.js
Normal file
233
script/pocketbase.add-is-delete-field.js
Normal file
@@ -0,0 +1,233 @@
|
||||
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 || '';
|
||||
|
||||
if (!AUTH_TOKEN) {
|
||||
console.error('❌ 缺少 POCKETBASE_AUTH_TOKEN,无法执行 is_delete 字段迁移。');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const pb = new PocketBase(PB_URL);
|
||||
const TARGET_FIELD = {
|
||||
name: 'is_delete',
|
||||
type: 'number',
|
||||
required: false,
|
||||
presentable: true,
|
||||
hidden: false,
|
||||
default: 0,
|
||||
min: 0,
|
||||
max: 1,
|
||||
onlyInt: true,
|
||||
};
|
||||
|
||||
function isSystemCollection(collection) {
|
||||
return !!(collection && (collection.system || String(collection.name || '').startsWith('_')));
|
||||
}
|
||||
|
||||
function normalizeFieldPayload(field, targetSpec) {
|
||||
const payload = field ? Object.assign({}, field) : {};
|
||||
const next = targetSpec || field || {};
|
||||
|
||||
if (field && field.id) {
|
||||
payload.id = field.id;
|
||||
}
|
||||
|
||||
payload.name = next.name;
|
||||
payload.type = next.type;
|
||||
|
||||
if (typeof next.required !== 'undefined') {
|
||||
payload.required = !!next.required;
|
||||
}
|
||||
if (typeof next.presentable !== 'undefined') {
|
||||
payload.presentable = !!next.presentable;
|
||||
}
|
||||
if (typeof next.hidden !== 'undefined') {
|
||||
payload.hidden = !!next.hidden;
|
||||
}
|
||||
|
||||
if (next.type === 'number') {
|
||||
if (Object.prototype.hasOwnProperty.call(next, 'default')) payload.default = next.default;
|
||||
if (Object.prototype.hasOwnProperty.call(next, 'min')) payload.min = next.min;
|
||||
if (Object.prototype.hasOwnProperty.call(next, 'max')) payload.max = next.max;
|
||||
if (Object.prototype.hasOwnProperty.call(next, 'onlyInt')) payload.onlyInt = !!next.onlyInt;
|
||||
}
|
||||
|
||||
if (next.type === 'autodate') {
|
||||
payload.onCreate = typeof next.onCreate === 'boolean' ? next.onCreate : (typeof field?.onCreate === 'boolean' ? field.onCreate : true);
|
||||
payload.onUpdate = typeof next.onUpdate === 'boolean' ? next.onUpdate : (typeof field?.onUpdate === 'boolean' ? field.onUpdate : false);
|
||||
}
|
||||
|
||||
if (next.type === 'file') {
|
||||
payload.maxSelect = typeof next.maxSelect === 'number' ? next.maxSelect : (typeof field?.maxSelect === 'number' ? field.maxSelect : 0);
|
||||
payload.maxSize = typeof next.maxSize === 'number' ? next.maxSize : (typeof field?.maxSize === 'number' ? field.maxSize : 0);
|
||||
payload.mimeTypes = Array.isArray(next.mimeTypes)
|
||||
? next.mimeTypes
|
||||
: (Array.isArray(field?.mimeTypes) ? field.mimeTypes : null);
|
||||
}
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
function buildCollectionPayload(collection, fields) {
|
||||
return {
|
||||
name: collection.name,
|
||||
type: collection.type,
|
||||
listRule: collection.listRule,
|
||||
viewRule: collection.viewRule,
|
||||
createRule: collection.createRule,
|
||||
updateRule: collection.updateRule,
|
||||
deleteRule: collection.deleteRule,
|
||||
fields,
|
||||
indexes: collection.indexes || [],
|
||||
};
|
||||
}
|
||||
|
||||
async function addFieldToCollections() {
|
||||
const collections = await pb.collections.getFullList({ sort: 'name' });
|
||||
const changed = [];
|
||||
|
||||
for (const collection of collections) {
|
||||
if (isSystemCollection(collection)) continue;
|
||||
|
||||
const currentFields = Array.isArray(collection.fields) ? collection.fields : [];
|
||||
const existingField = currentFields.find((field) => field && field.name === TARGET_FIELD.name);
|
||||
const nextFields = currentFields
|
||||
.filter((field) => field && field.name !== 'id')
|
||||
.map((field) => normalizeFieldPayload(field));
|
||||
|
||||
if (existingField) {
|
||||
for (let i = 0; i < nextFields.length; i += 1) {
|
||||
if (nextFields[i].name === TARGET_FIELD.name) {
|
||||
nextFields[i] = normalizeFieldPayload(existingField, TARGET_FIELD);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
nextFields.push(normalizeFieldPayload(null, TARGET_FIELD));
|
||||
}
|
||||
|
||||
await pb.collections.update(collection.id, buildCollectionPayload(collection, nextFields));
|
||||
changed.push({ name: collection.name, action: existingField ? 'normalized' : 'added' });
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
async function backfillRecords() {
|
||||
const collections = await pb.collections.getFullList({ sort: 'name' });
|
||||
const summary = [];
|
||||
|
||||
for (const collection of collections) {
|
||||
if (isSystemCollection(collection)) continue;
|
||||
|
||||
let page = 1;
|
||||
let patched = 0;
|
||||
const perPage = 200;
|
||||
|
||||
while (true) {
|
||||
const list = await pb.collection(collection.name).getList(page, perPage, {
|
||||
fields: 'id,is_delete',
|
||||
skipTotal: false,
|
||||
});
|
||||
|
||||
const items = Array.isArray(list.items) ? list.items : [];
|
||||
for (const item of items) {
|
||||
const value = item && Object.prototype.hasOwnProperty.call(item, 'is_delete') ? item.is_delete : null;
|
||||
if (value === 0 || value === '0') continue;
|
||||
|
||||
await pb.collection(collection.name).update(item.id, { is_delete: 0 });
|
||||
patched += 1;
|
||||
}
|
||||
|
||||
if (page >= list.totalPages) break;
|
||||
page += 1;
|
||||
}
|
||||
|
||||
summary.push({ name: collection.name, backfilledRecords: patched });
|
||||
}
|
||||
|
||||
return summary;
|
||||
}
|
||||
|
||||
async function verifyCollections() {
|
||||
const collections = await pb.collections.getFullList({ sort: 'name' });
|
||||
const failed = [];
|
||||
|
||||
for (const collection of collections) {
|
||||
if (isSystemCollection(collection)) continue;
|
||||
const field = (collection.fields || []).find((item) => item && item.name === TARGET_FIELD.name);
|
||||
if (!field || field.type !== 'number') failed.push(collection.name);
|
||||
}
|
||||
|
||||
if (failed.length) {
|
||||
throw new Error('以下集合缺少 is_delete 字段或类型不正确: ' + failed.join(', '));
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
try {
|
||||
console.log(`🔄 正在连接 PocketBase: ${PB_URL}`);
|
||||
pb.authStore.save(AUTH_TOKEN, null);
|
||||
console.log('✅ 已使用 POCKETBASE_AUTH_TOKEN 载入认证状态。');
|
||||
|
||||
const changed = await addFieldToCollections();
|
||||
console.log('📝 已更新集合:');
|
||||
console.log(JSON.stringify(changed, null, 2));
|
||||
|
||||
const backfilled = await backfillRecords();
|
||||
console.log('📝 已回填记录:');
|
||||
console.log(JSON.stringify(backfilled, null, 2));
|
||||
|
||||
await verifyCollections();
|
||||
console.log('✅ 校验通过:所有业务集合已包含 is_delete(number, default=0) 字段。');
|
||||
console.log('🎉 is_delete 字段迁移完成!');
|
||||
} catch (error) {
|
||||
console.error('❌ is_delete 字段迁移失败:', error.response?.data || error.message || error);
|
||||
process.exitCode = 1;
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
Reference in New Issue
Block a user