62 lines
2.2 KiB
JavaScript
62 lines
2.2 KiB
JavaScript
'use strict';
|
||
|
||
const { MODULE_REGISTRY, validateRegistry } = require('@profile/shared');
|
||
const db = require('../infra/db');
|
||
const audit = require('../audit/audit');
|
||
|
||
/**
|
||
* 启动一致性校验(migration-plan §5.4)。
|
||
* 在 app.listen 前调用一次。
|
||
*/
|
||
async function syncModulesOnBoot() {
|
||
const errors = validateRegistry();
|
||
if (errors.length > 0) {
|
||
throw new Error(`MODULE_REGISTRY invalid:\n${errors.join('\n')}`);
|
||
}
|
||
|
||
const dbRows = await db.query('SELECT `key`, status FROM modules');
|
||
const dbKeys = new Set(dbRows.map((r) => r.key));
|
||
const codeKeys = new Set(MODULE_REGISTRY.map((m) => m.key));
|
||
|
||
// code 有但 DB 无 → 自动 INSERT
|
||
for (const m of MODULE_REGISTRY) {
|
||
if (!dbKeys.has(m.key)) {
|
||
await db.execute('INSERT INTO modules (`key`, status, notes) VALUES (?, ?, ?)', [m.key, 'active', m.label]);
|
||
console.log(`[modules] auto-registered: ${m.key}`);
|
||
await audit.log({ clientType: 'system', action: 'module_auto_register', module: m.key });
|
||
}
|
||
}
|
||
|
||
// DB 有但 code 无 → orphan 告警 + 审计,不阻塞
|
||
const orphans = dbRows.filter((r) => !codeKeys.has(r.key)).map((r) => r.key);
|
||
if (orphans.length > 0) {
|
||
console.warn(`[modules] orphan in DB: ${orphans.join(',')}`);
|
||
await audit.log({ clientType: 'system', action: 'module_orphan_detected', payload: { orphans } });
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 合并 code descriptor + DB status/override,过滤 disabled 与无 READ 权限的模块。
|
||
* @param {Record<string, number>} perms module -> 位掩码
|
||
* @returns {Promise<Object[]>}
|
||
*/
|
||
async function getVisible(perms) {
|
||
const dbRows = await db.query('SELECT `key`, status, override FROM modules');
|
||
const statusMap = new Map(dbRows.map((r) => [r.key, r]));
|
||
|
||
return MODULE_REGISTRY.filter((m) => {
|
||
const row = statusMap.get(m.key);
|
||
if (row && row.status === 'disabled') return false;
|
||
return ((perms[m.rbacModule] || 0) & 1) === 1; // READ
|
||
}).map((m) => {
|
||
const row = statusMap.get(m.key);
|
||
let override = {};
|
||
if (row && row.override) {
|
||
override = typeof row.override === 'string' ? JSON.parse(row.override) : row.override;
|
||
}
|
||
return { ...m, ...override };
|
||
});
|
||
}
|
||
|
||
module.exports = { syncModulesOnBoot, getVisible };
|