- Base platform from base.git (ERP base: auth, core, config, message, workflow, plugin) - Created erp-diary module skeleton (lib.rs, dto.rs, error.rs, event.rs, state.rs) - Integrated erp-diary into workspace and erp-server - Added DiaryModule registration in main.rs - Added DiaryState FromRef in state.rs - Diary routes mounted (empty routes, ready for implementation) - Product design spec v1.2 preserved in docs/ - Implementation plan preserved in plans/ Cargo check: OK Cargo test: OK (78+ base tests passing)
156 lines
5.5 KiB
JavaScript
156 lines
5.5 KiB
JavaScript
#!/usr/bin/env node
|
|
// gen-permissions.js — 从 permissions.yaml 生成 seed SQL 和前端 routeConfig 片段
|
|
//
|
|
// 用法:
|
|
// node scripts/gen-permissions.js --sql 输出 seed INSERT SQL
|
|
// node scripts/gen-permissions.js --frontend 输出 routeConfig.ts 条目
|
|
// node scripts/gen-permissions.js --validate 验证与代码一致性
|
|
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
const yaml = require('js-yaml');
|
|
|
|
const YAML_PATH = path.resolve(__dirname, '..', 'permissions.yaml');
|
|
|
|
function loadPermissions() {
|
|
const raw = fs.readFileSync(YAML_PATH, 'utf-8');
|
|
return yaml.load(raw);
|
|
}
|
|
|
|
function getAllCodes(registry) {
|
|
const codes = [];
|
|
for (const [, group] of Object.entries(registry)) {
|
|
for (const perm of group.permissions) {
|
|
codes.push(perm.code);
|
|
}
|
|
}
|
|
return codes;
|
|
}
|
|
|
|
function generateSeedSQL(registry) {
|
|
const sys = '00000000-0000-0000-0000-000000000000';
|
|
const lines = [
|
|
'-- Auto-generated from permissions.yaml by gen-permissions.js',
|
|
`-- Generated: ${new Date().toISOString().slice(0, 10)}`,
|
|
'',
|
|
];
|
|
|
|
for (const [groupKey, group] of Object.entries(registry)) {
|
|
lines.push(`-- ${groupKey}: ${group.description}`);
|
|
for (const perm of group.permissions) {
|
|
const parts = perm.code.split('.');
|
|
const resource = parts.length >= 2 ? parts[0] : groupKey;
|
|
const action = parts.slice(1).join('.');
|
|
lines.push(
|
|
`INSERT INTO permissions (id, tenant_id, code, name, resource, action, description,` +
|
|
` created_at, updated_at, created_by, updated_by, deleted_at, version)` +
|
|
` SELECT gen_random_uuid(), t.id, '${perm.code}', '${perm.name}', '${resource}', '${action}', '${perm.name}',` +
|
|
` NOW(), NOW(), '${sys}', '${sys}', NULL, 1` +
|
|
` FROM tenant t` +
|
|
` WHERE NOT EXISTS (SELECT 1 FROM permissions p WHERE p.code = '${perm.code}' AND p.tenant_id = t.id AND p.deleted_at IS NULL)` +
|
|
` ON CONFLICT DO NOTHING;`
|
|
);
|
|
}
|
|
lines.push('');
|
|
}
|
|
return lines.join('\n');
|
|
}
|
|
|
|
function generateFrontendSnippet(registry) {
|
|
const lines = ['// Auto-generated from permissions.yaml by gen-permissions.js', ''];
|
|
|
|
for (const [, group] of Object.entries(registry)) {
|
|
const frozenPerms = group.permissions.filter(p => p.frozen);
|
|
const activePerms = group.permissions.filter(p => !p.frozen);
|
|
|
|
// Group by entity prefix (e.g., health.patient → {list, manage})
|
|
const entities = {};
|
|
for (const perm of [...activePerms, ...frozenPerms]) {
|
|
const parts = perm.code.split('.');
|
|
if (parts.length < 2) continue;
|
|
const entity = parts.slice(0, -1).join('.');
|
|
const action = parts[parts.length - 1];
|
|
if (!entities[entity]) entities[entity] = { list: [], frozen: perm.frozen || false };
|
|
entities[entity].list.push(perm.code);
|
|
}
|
|
|
|
for (const [entity, info] of Object.entries(entities)) {
|
|
const frozenAttr = info.frozen ? ',\n frozen: true' : '';
|
|
lines.push(` {`);
|
|
lines.push(` path: "/${entity.replace(/\./g, '/')}",`);
|
|
lines.push(` permissions: [${info.list.map(c => `"${c}"`).join(', ')}]${frozenAttr}`);
|
|
lines.push(` },`);
|
|
}
|
|
}
|
|
|
|
return lines.join('\n');
|
|
}
|
|
|
|
function validate(registry) {
|
|
const yamlCodes = getAllCodes(registry);
|
|
const rootDir = path.resolve(__dirname, '..');
|
|
|
|
// Recursively find .rs files and extract permission codes
|
|
const backendCodes = new Set();
|
|
function walkDir(dir) {
|
|
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
const full = path.join(dir, entry.name);
|
|
if (entry.isDirectory() && !entry.name.startsWith('.') && entry.name !== 'target') {
|
|
walkDir(full);
|
|
} else if (entry.isFile() && entry.name.endsWith('.rs')) {
|
|
const content = fs.readFileSync(full, 'utf-8');
|
|
// Match code: "xxx.yyy.zzz" pattern in PermissionDescriptor
|
|
// Must be lowercase letters/digits/hyphens with dots (permission code pattern)
|
|
const matches = content.matchAll(/code:\s*"([a-z][a-z0-9-]*\.[a-z][a-z0-9-]*(?:\.[a-z][a-z0-9-]*)*)"/g);
|
|
for (const m of matches) {
|
|
backendCodes.add(m[1]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
walkDir(path.join(rootDir, 'crates'));
|
|
|
|
let errors = 0;
|
|
|
|
// Check YAML covers all backend codes
|
|
for (const code of backendCodes) {
|
|
if (!yamlCodes.includes(code)) {
|
|
console.log(`MISSING: Backend '${code}' not in permissions.yaml`);
|
|
errors++;
|
|
}
|
|
}
|
|
|
|
// Check backend covers all YAML codes
|
|
for (const code of yamlCodes) {
|
|
if (!backendCodes.has(code)) {
|
|
console.log(`MISSING: YAML '${code}' not in backend module.rs`);
|
|
errors++;
|
|
}
|
|
}
|
|
|
|
if (errors === 0) {
|
|
console.log(`OK: ${yamlCodes.length} YAML / ${backendCodes.size} backend — 0 mismatches`);
|
|
}
|
|
return errors;
|
|
}
|
|
|
|
// Main
|
|
const args = process.argv.slice(2);
|
|
const registry = loadPermissions();
|
|
|
|
if (args.includes('--sql')) {
|
|
console.log(generateSeedSQL(registry));
|
|
} else if (args.includes('--frontend')) {
|
|
console.log(generateFrontendSnippet(registry));
|
|
} else if (args.includes('--validate')) {
|
|
const errors = validate(registry);
|
|
process.exit(errors > 0 ? 1 : 0);
|
|
} else {
|
|
const codes = getAllCodes(registry);
|
|
console.log(`permissions.yaml loaded: ${codes.length} permission codes across ${Object.keys(registry).length} modules`);
|
|
console.log('Usage:');
|
|
console.log(' node scripts/gen-permissions.js --sql Generate seed SQL');
|
|
console.log(' node scripts/gen-permissions.js --frontend Generate routeConfig snippet');
|
|
console.log(' node scripts/gen-permissions.js --validate Validate consistency');
|
|
}
|