Files
AIclinicalresearch/backend/src/index.ts
HaHafeng 66255368b7 feat(admin): Add user management and upgrade to module permission system
Features - User Management (Phase 4.1):
- Database: Add user_modules table for fine-grained module permissions
- Database: Add 4 user permissions (view/create/edit/delete) to role_permissions
- Backend: UserService (780 lines) - CRUD with tenant isolation
- Backend: UserController + UserRoutes (648 lines) - 13 API endpoints
- Backend: Batch import users from Excel
- Frontend: UserListPage (412 lines) - list/filter/search/pagination
- Frontend: UserFormPage (341 lines) - create/edit with module config
- Frontend: UserDetailPage (393 lines) - details/tenant/module management
- Frontend: 3 modal components (592 lines) - import/assign/configure
- API: GET/POST/PUT/DELETE /api/admin/users/* endpoints

Architecture Upgrade - Module Permission System:
- Backend: Add getUserModules() method in auth.service
- Backend: Login API returns modules array in user object
- Frontend: AuthContext adds hasModule() method
- Frontend: Navigation filters modules based on user.modules
- Frontend: RouteGuard checks requiredModule instead of requiredVersion
- Frontend: Remove deprecated version-based permission system
- UX: Only show accessible modules in navigation (clean UI)
- UX: Smart redirect after login (avoid 403 for regular users)

Fixes:
- Fix UTF-8 encoding corruption in ~100 docs files
- Fix pageSize type conversion in userService (String to Number)
- Fix authUser undefined error in TopNavigation
- Fix login redirect logic with role-based access check
- Update Git commit guidelines v1.2 with UTF-8 safety rules

Database Changes:
- CREATE TABLE user_modules (user_id, tenant_id, module_code, is_enabled)
- ADD UNIQUE CONSTRAINT (user_id, tenant_id, module_code)
- INSERT 4 permissions + role assignments
- UPDATE PUBLIC tenant with 8 module subscriptions

Technical:
- Backend: 5 new files (~2400 lines)
- Frontend: 10 new files (~2500 lines)
- Docs: 1 development record + 2 status updates + 1 guideline update
- Total: ~4900 lines of code

Status: User management 100% complete, module permission system operational
2026-01-16 13:42:10 +08:00

278 lines
11 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import Fastify from 'fastify';
import cors from '@fastify/cors';
import multipart from '@fastify/multipart';
import { config, validateEnv } from './config/env.js';
import { testDatabaseConnection, prisma } from './config/database.js';
import { projectRoutes } from './legacy/routes/projects.js';
import { agentRoutes } from './legacy/routes/agents.js';
import { conversationRoutes } from './legacy/routes/conversations.js';
import knowledgeBaseRoutes from './legacy/routes/knowledgeBases.js';
import { chatRoutes } from './legacy/routes/chatRoutes.js';
import { batchRoutes } from './legacy/routes/batchRoutes.js';
import reviewRoutes from './legacy/routes/reviewRoutes.js';
import { rvwRoutes } from './modules/rvw/index.js';
import { aslRoutes } from './modules/asl/routes/index.js';
import { registerDCRoutes, initDCModule } from './modules/dc/index.js';
import pkbRoutes from './modules/pkb/routes/index.js';
import { aiaRoutes } from './modules/aia/index.js';
import { registerHealthRoutes } from './common/health/index.js';
import { logger } from './common/logging/index.js';
import { authRoutes, registerAuthPlugin } from './common/auth/index.js';
import { promptRoutes } from './common/prompt/index.js';
import { registerTestRoutes } from './test-platform-api.js';
import { registerScreeningWorkers } from './modules/asl/services/screeningWorker.js';
import { registerExtractionWorkers } from './modules/dc/tool-b/workers/extractionWorker.js';
import { registerParseExcelWorker } from './modules/dc/tool-c/workers/parseExcelWorker.js';
import { registerReviewWorker } from './modules/rvw/workers/reviewWorker.js';
import { jobQueue } from './common/jobs/index.js';
// 全局处理BigInt序列化
(BigInt.prototype as any).toJSON = function() {
return Number(this);
};
// 生产环境使用JSON格式日志性能更好开发环境使用pino-pretty易读
const fastify = Fastify({
logger: config.nodeEnv === 'production'
? {
level: config.logLevel,
// 生产环境简单的JSON日志适合日志收集系统
}
: {
level: config.logLevel,
// 开发环境使用pino-pretty美化输出
transport: {
target: 'pino-pretty',
options: {
colorize: true,
translateTime: 'HH:MM:ss Z',
ignore: 'pid,hostname',
},
},
},
});
// 注册CORS插件 - 完整配置
await fastify.register(cors, {
origin: true, // 开发环境允许所有来源
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS', 'HEAD'], // 明确允许的HTTP方法
allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With', 'Accept', 'Origin'], // 允许的请求头
exposedHeaders: ['Content-Range', 'X-Content-Range'], // 暴露的响应头
maxAge: 600, // preflight请求缓存时间
preflightContinue: false, // Fastify处理preflight请求
});
console.log('✅ CORS已配置: 允许所有HTTP方法 (GET, POST, PUT, DELETE, PATCH, OPTIONS)');
// 注册文件上传插件
await fastify.register(multipart, {
limits: {
fileSize: 10 * 1024 * 1024, // 10MB
},
});
console.log('✅ 文件上传插件已配置: 最大文件大小 10MB');
// ============================================
// 【平台基础设施】健康检查路由
// ============================================
// 注册健康检查路由(/health, /health/liveness, /health/readiness
await registerHealthRoutes(fastify);
logger.info('✅ 健康检查路由已注册');
// ============================================
// 【平台基础设施】认证模块
// ============================================
await registerAuthPlugin(fastify);
await fastify.register(authRoutes, { prefix: '/api/v1/auth' });
logger.info('✅ 认证路由已注册: /api/v1/auth');
// ============================================
// 【运营管理】Prompt管理模块
// ============================================
await fastify.register(promptRoutes, { prefix: '/api/admin/prompts' });
logger.info('✅ Prompt管理路由已注册: /api/admin/prompts');
// ============================================
// 【运营管理】租户管理模块
// ============================================
import { tenantRoutes, moduleRoutes } from './modules/admin/routes/tenantRoutes.js';
import { userRoutes } from './modules/admin/routes/userRoutes.js';
await fastify.register(tenantRoutes, { prefix: '/api/admin/tenants' });
await fastify.register(moduleRoutes, { prefix: '/api/admin/modules' });
await fastify.register(userRoutes, { prefix: '/api/admin/users' });
logger.info('✅ 运营管理路由已注册: /api/admin/tenants, /api/admin/modules, /api/admin/users');
// ============================================
// 【临时】平台基础设施测试API
// ============================================
await registerTestRoutes(fastify);
logger.info('✅ 测试API已注册: /test/platform');
// API路由前缀
fastify.get('/api/v1', async () => {
return {
message: 'AI Clinical Research Platform API',
version: '1.0.0',
environment: config.nodeEnv,
};
});
// 注册项目管理路由
await fastify.register(projectRoutes, { prefix: '/api/v1' });
// 注册智能体管理路由
await fastify.register(agentRoutes, { prefix: '/api/v1' });
// 注册对话管理路由
await fastify.register(conversationRoutes, { prefix: '/api/v1' });
// 注册知识库管理路由
await fastify.register(knowledgeBaseRoutes, { prefix: '/api/v1' });
// 注册通用对话路由
await fastify.register(chatRoutes, { prefix: '/api/v1' });
// Phase 3: 注册批处理路由
await fastify.register(batchRoutes, { prefix: '/api/v1' });
// 注册稿件审查路由(旧版,保留兼容)
await fastify.register(reviewRoutes, { prefix: '/api/v1' });
// ============================================
// 【业务模块】RVW - 稿件审查系统
// ============================================
await fastify.register(rvwRoutes, { prefix: '/api/v1/rvw' });
logger.info('✅ RVW稿件审查路由已注册: /api/v1/rvw');
// ============================================
// 【业务模块】PKB - 个人知识库
// ============================================
await fastify.register(pkbRoutes, { prefix: '/api/v1/pkb' });
logger.info('✅ PKB个人知识库路由已注册: /api/v1/pkb');
// ============================================
// 【业务模块】AIA - AI智能问答
// ============================================
await fastify.register(aiaRoutes, { prefix: '/api/v1/aia' });
logger.info('✅ AIA智能问答路由已注册: /api/v1/aia');
// ============================================
// 【业务模块】ASL - AI智能文献筛选
// ============================================
await fastify.register(aslRoutes, { prefix: '/api/v1/asl' });
logger.info('✅ ASL智能文献筛选路由已注册: /api/v1/asl');
// ============================================
// 【业务模块】DC - 数据清洗整理
// ============================================
await registerDCRoutes(fastify);
logger.info('✅ DC数据清洗模块路由已注册: /api/v1/dc/tool-b');
// ============================================
// 【业务模块】IIT Manager Agent - IIT研究智能助手
// ============================================
import { registerIitRoutes, initIitManager } from './modules/iit-manager/index.js';
await registerIitRoutes(fastify);
logger.info('✅ IIT Manager Agent路由已注册: /api/v1/iit');
// 启动服务器
const start = async () => {
try {
// 验证环境变量
validateEnv();
// 测试数据库连接
console.log('🔍 正在测试数据库连接...');
const dbConnected = await testDatabaseConnection();
if (!dbConnected) {
console.error('❌ 数据库连接失败,无法启动服务器');
process.exit(1);
}
// ============================================
// 【Postgres-Only】启动队列和注册Workers
// ============================================
try {
logger.info('🚀 Starting Postgres-Only queue and workers...');
// 启动队列pg-boss
await jobQueue.start();
logger.info('✅ Queue started (pg-boss)');
// 注册ASL筛选Workers
registerScreeningWorkers();
logger.info('✅ ASL screening workers registered');
// 注册DC提取Workers
registerExtractionWorkers();
logger.info('✅ DC extraction workers registered');
// 注册DC Tool C Excel解析Worker
registerParseExcelWorker();
logger.info('✅ DC Tool C parse excel worker registered');
// 注册RVW审稿Worker
registerReviewWorker();
logger.info('✅ RVW review worker registered');
// 注册IIT Manager Workers
await initIitManager();
logger.info('✅ IIT Manager workers registered');
// ⚠️ 等待3秒确保所有 Worker 异步注册到 pg-boss 完成
console.log('\n⏳ 等待 Workers 异步注册完成...');
await new Promise(resolve => setTimeout(resolve, 3000));
logger.info('✅ All workers registration completed (waited 3s)');
console.log('\n' + '='.repeat(60));
console.log('✅ Postgres-Only 架构已启动');
console.log('='.repeat(60));
console.log('📦 队列类型: pg-boss');
console.log('📦 缓存类型: PostgreSQL');
console.log('📦 注册的Workers:');
console.log(' - asl_screening_batch (文献筛选批次处理)');
console.log(' - dc_extraction_batch (数据提取批次处理)');
console.log(' - dc_toolc_parse_excel (Tool C Excel解析)');
console.log(' - rvw_review_task (稿件审查任务)');
console.log(' - iit_quality_check (IIT质控+企微推送)');
console.log(' - iit_redcap_poll (IIT REDCap轮询)');
console.log('='.repeat(60) + '\n');
} catch (error) {
logger.error('❌ Failed to start Postgres-Only architecture', { error });
console.error('❌ Postgres-Only 架构启动失败:', error);
process.exit(1);
}
// 初始化DC模块Seed预设模板
try {
await initDCModule();
logger.info('✅ DC模块初始化成功');
} catch (error) {
logger.warn('⚠️ DC模块初始化失败但不影响启动', { error });
}
// 启动Fastify服务器
await fastify.listen({
port: config.port,
host: config.host,
});
console.log('\n' + '='.repeat(60));
console.log('🚀 AI临床研究平台 - 后端服务器启动成功!');
console.log('='.repeat(60));
console.log(`📍 服务地址: http://${config.host === '0.0.0.0' ? 'localhost' : config.host}:${config.port}`);
console.log(`🔍 健康检查: http://localhost:${config.port}/health`);
console.log(`📡 API入口: http://localhost:${config.port}/api/v1`);
console.log(`🌍 运行环境: ${config.nodeEnv}`);
console.log('='.repeat(60) + '\n');
} catch (err) {
fastify.log.error(err);
process.exit(1);
}
};
start();