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