143 lines
4.8 KiB
JavaScript
143 lines
4.8 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 = (process.env.PB_URL || 'https://bai-api.blv-oa.com/pb').replace(/\/+$/, '');
|
|||
|
|
const AUTH_TOKEN = process.env.POCKETBASE_AUTH_TOKEN || runtimeConfig.POCKETBASE_AUTH_TOKEN || '';
|
|||
|
|
|
|||
|
|
if (!AUTH_TOKEN) {
|
|||
|
|
console.error('❌ 缺少 POCKETBASE_AUTH_TOKEN,无法执行文件字段迁移。');
|
|||
|
|
process.exit(1);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const pb = new PocketBase(PB_URL);
|
|||
|
|
|
|||
|
|
function buildReplacementTextField(field) {
|
|||
|
|
return {
|
|||
|
|
name: field.name,
|
|||
|
|
type: 'text',
|
|||
|
|
required: !!field.required,
|
|||
|
|
hidden: !!field.hidden,
|
|||
|
|
presentable: typeof field.presentable === 'boolean' ? field.presentable : true,
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function buildCollectionPayload(remote, fields) {
|
|||
|
|
const payload = {
|
|||
|
|
name: remote.name,
|
|||
|
|
type: remote.type,
|
|||
|
|
fields: fields,
|
|||
|
|
indexes: remote.indexes || [],
|
|||
|
|
listRule: remote.listRule,
|
|||
|
|
viewRule: remote.viewRule,
|
|||
|
|
createRule: remote.createRule,
|
|||
|
|
updateRule: remote.updateRule,
|
|||
|
|
deleteRule: remote.deleteRule,
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
if (remote.name === 'tbl_attachments') {
|
|||
|
|
payload.listRule = '';
|
|||
|
|
payload.viewRule = '';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return payload;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async function migrateCollections() {
|
|||
|
|
console.log(`🔄 正在连接 PocketBase: ${PB_URL}`);
|
|||
|
|
pb.authStore.save(AUTH_TOKEN, null);
|
|||
|
|
console.log('✅ 已使用 POCKETBASE_AUTH_TOKEN 载入认证状态。');
|
|||
|
|
|
|||
|
|
const collections = await pb.collections.getFullList({ sort: 'name' });
|
|||
|
|
const changed = [];
|
|||
|
|
|
|||
|
|
for (const remote of collections) {
|
|||
|
|
const fileFields = (remote.fields || []).filter((field) => field.type === 'file');
|
|||
|
|
const shouldOpenAttachments = remote.name === 'tbl_attachments' && (remote.listRule !== '' || remote.viewRule !== '');
|
|||
|
|
|
|||
|
|
if (!fileFields.length && !shouldOpenAttachments) {
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
console.log(`🔄 正在处理集合: ${remote.name}`);
|
|||
|
|
if (remote.name === 'tbl_attachments') {
|
|||
|
|
await pb.collections.update(remote.id, buildCollectionPayload(remote, remote.fields || []));
|
|||
|
|
} else if (fileFields.length) {
|
|||
|
|
const remainingFields = (remote.fields || []).filter((field) => field.type !== 'file');
|
|||
|
|
await pb.collections.update(remote.id, buildCollectionPayload(remote, remainingFields));
|
|||
|
|
|
|||
|
|
const refreshed = await pb.collections.getOne(remote.name);
|
|||
|
|
const replacementFields = fileFields.map((field) => buildReplacementTextField(field));
|
|||
|
|
await pb.collections.update(
|
|||
|
|
refreshed.id,
|
|||
|
|
buildCollectionPayload(refreshed, (refreshed.fields || []).concat(replacementFields)),
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
changed.push({
|
|||
|
|
name: remote.name,
|
|||
|
|
convertedFileFields: remote.name === 'tbl_attachments'
|
|||
|
|
? []
|
|||
|
|
: fileFields.map((field) => field.name),
|
|||
|
|
attachmentsRulesOpened: remote.name === 'tbl_attachments',
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return changed;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async function verifyResult() {
|
|||
|
|
const collections = await pb.collections.getFullList({ sort: 'name' });
|
|||
|
|
const residualFileFields = [];
|
|||
|
|
let attachmentsRules = null;
|
|||
|
|
|
|||
|
|
for (const remote of collections) {
|
|||
|
|
for (const field of (remote.fields || [])) {
|
|||
|
|
if (field.type === 'file' && remote.name !== 'tbl_attachments') {
|
|||
|
|
residualFileFields.push(`${remote.name}.${field.name}`);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (remote.name === 'tbl_attachments') {
|
|||
|
|
attachmentsRules = {
|
|||
|
|
listRule: remote.listRule,
|
|||
|
|
viewRule: remote.viewRule,
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (residualFileFields.length) {
|
|||
|
|
throw new Error(`仍存在未迁移的 file 字段: ${residualFileFields.join(', ')}`);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!attachmentsRules || attachmentsRules.listRule !== '' || attachmentsRules.viewRule !== '') {
|
|||
|
|
throw new Error('tbl_attachments 的公开 list/view 规则未生效');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
console.log('✅ 校验通过:除 tbl_attachments 外已无 file 字段。');
|
|||
|
|
console.log('✅ 校验通过:tbl_attachments 已开放公共 list/view。');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async function main() {
|
|||
|
|
try {
|
|||
|
|
const changed = await migrateCollections();
|
|||
|
|
console.log('📝 本次更新集合:');
|
|||
|
|
console.log(JSON.stringify(changed, null, 2));
|
|||
|
|
await verifyResult();
|
|||
|
|
console.log('🎉 文件字段迁移完成!');
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('❌ 文件字段迁移失败:', error.response?.data || error.message || error);
|
|||
|
|
process.exitCode = 1;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
main();
|