Files
AIclinicalresearch/backend/src/modules/iit-manager/test-redcap-query-from-db.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

270 lines
8.7 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.
/**
* REDCap查询测试脚本从数据库读取配置
*
* 目的测试基础的REDCap查询功能验证数据格式
* 数据来源:从数据库 iit_schema.projects 表读取项目配置
*
* 运行方式:
* ```bash
* cd backend
* npm run tsx src/modules/iit-manager/test-redcap-query-from-db.ts
* ```
*/
import { RedcapAdapter } from './adapters/RedcapAdapter.js';
import { PrismaClient } from '@prisma/client';
import { logger } from '../../common/logging/index.js';
import dotenv from 'dotenv';
dotenv.config();
const prisma = new PrismaClient();
async function main() {
console.log('='.repeat(70));
console.log('🧪 REDCap查询测试为AI对话准备');
console.log('='.repeat(70));
console.log();
try {
// =============================================
// 1. 从数据库读取test0102项目配置
// =============================================
console.log('📋 从数据库读取项目配置...');
const project = await prisma.iitProject.findFirst({
where: {
OR: [
{ redcapProjectId: '16' },
{ name: { contains: 'test0102' } }
],
status: 'active'
}
});
if (!project) {
console.log('❌ 未找到test0102项目PID: 16');
console.log();
console.log('💡 可能的原因:');
console.log(' 1. 项目未在数据库中');
console.log(' 2. 项目状态不是active');
console.log(' 3. redcapProjectId不是"16"');
console.log();
console.log('📝 解决方法:');
console.log(' 请检查数据库 iit_schema.projects 表');
console.log(' 或联系管理员添加test0102项目配置');
console.log();
process.exit(1);
}
console.log('✅ 项目配置读取成功');
console.log();
console.log('📋 项目信息:');
console.log(` 数据库ID: ${project.id}`);
console.log(` 项目名称: ${project.name}`);
console.log(` REDCap项目ID: ${project.redcapProjectId}`);
console.log(` REDCap URL: ${project.redcapUrl}`);
console.log(` API Token: ${project.redcapApiToken.substring(0, 8)}***`);
console.log(` 状态: ${project.status}`);
console.log();
// =============================================
// 2. 创建RedcapAdapter实例
// =============================================
console.log('📦 创建RedcapAdapter实例...');
const redcap = new RedcapAdapter(
project.redcapUrl,
project.redcapApiToken
);
console.log('✅ Adapter创建成功\n');
// =============================================
// 3. 测试连接
// =============================================
console.log('🔌 测试API连接...');
try {
const isConnected = await redcap.testConnection();
if (isConnected) {
console.log('✅ API连接成功\n');
} else {
console.log('❌ API连接失败');
console.log(' 请检查:');
console.log(' 1. REDCap服务是否启动');
console.log(` 2. URL是否正确: ${project.redcapUrl}`);
console.log(' 3. API Token是否有效');
console.log();
process.exit(1);
}
} catch (error: any) {
console.error('❌ 连接测试失败:', error.message);
process.exit(1);
}
// =============================================
// 4. 查询所有记录获取记录ID列表
// =============================================
console.log('📊 测试1: 查询所有记录获取记录ID列表');
console.log('-'.repeat(70));
const allRecords = await redcap.exportRecords({
fields: ['record_id']
});
// 提取唯一记录ID
const uniqueRecordIds = Array.from(
new Set(allRecords.map((r) => r.record_id || r.record))
);
console.log(`✅ 查询成功`);
console.log(` 总记录数: ${uniqueRecordIds.length}`);
console.log(` 记录ID列表: ${uniqueRecordIds.join(', ')}`);
console.log();
if (uniqueRecordIds.length === 0) {
console.log('⚠️ 项目中没有记录请先在REDCap中创建测试数据');
console.log(' 建议在test0102项目中添加几条测试记录');
console.log();
process.exit(0);
}
// =============================================
// 5. 查询特定记录的详细信息
// =============================================
const testRecordId = uniqueRecordIds[0]; // 使用第一条记录做测试
console.log(`📋 测试2: 查询特定记录的详细信息 (ID: ${testRecordId})`);
console.log('-'.repeat(70));
const specificRecord = await redcap.exportRecords({
records: [testRecordId]
});
if (specificRecord.length > 0) {
console.log('✅ 查询成功');
console.log(` 记录数: ${specificRecord.length}`);
console.log();
console.log('📄 第一条记录的数据结构:');
const firstRecord = specificRecord[0];
const fields = Object.keys(firstRecord);
console.log(`${fields.length} 个字段:`);
fields.slice(0, 10).forEach((field, index) => {
const value = firstRecord[field];
const displayValue = value === '' ? '(空值)' : value;
console.log(` ${(index + 1).toString().padStart(2, ' ')}. ${field.padEnd(30)} = ${displayValue}`);
});
if (fields.length > 10) {
console.log(` ... (还有 ${fields.length - 10} 个字段)`);
}
console.log();
} else {
console.log('❌ 未找到记录');
console.log();
}
// =============================================
// 6. 模拟AI对话场景的查询
// =============================================
console.log('🤖 测试3: 模拟AI对话场景');
console.log('-'.repeat(70));
console.log();
// 场景1: 用户问"有多少条记录?"
console.log('【场景1】用户问"我们系统中已经有几条记录了?"');
const countResult = {
projectName: project.name,
totalRecords: uniqueRecordIds.length,
recordIds: uniqueRecordIds
};
console.log('💾 AI需要的数据:');
console.log(JSON.stringify(countResult, null, 2));
console.log();
console.log('🤖 AI应该回答:');
console.log(` "您好根据REDCap系统记录当前项目${project.name}已有 **${uniqueRecordIds.length}条** 患者数据记录。"`);
console.log();
// 场景2: 用户问特定记录的信息
const demoRecordId = uniqueRecordIds.includes('7') ? '7' : uniqueRecordIds[0];
console.log(`【场景2】用户问"了解Redcap中记录为ID ${demoRecordId}的信息"`);
const recordDetailResult = await redcap.exportRecords({
records: [demoRecordId]
});
console.log('💾 AI需要的数据:');
console.log(JSON.stringify({
projectName: project.name,
recordId: demoRecordId,
data: recordDetailResult[0]
}, null, 2));
console.log();
// 场景3: 用户问"项目名称"
console.log('【场景3】用户问"咱们当前的项目名称是什么?"');
console.log('💾 AI需要的数据:');
console.log(JSON.stringify({
projectName: project.name,
redcapProjectId: project.redcapProjectId,
recordCount: uniqueRecordIds.length,
lastSync: project.lastSyncAt
}, null, 2));
console.log();
console.log('🤖 AI应该回答:');
console.log(` "您好!当前项目名称为 **${project.name}**。如需查看完整方案或项目详情请登录REDCap系统或查阅项目文档。"`);
console.log();
// =============================================
// 测试总结
// =============================================
console.log('='.repeat(70));
console.log('✅ 所有测试完成REDCap查询功能正常');
console.log('='.repeat(70));
console.log();
console.log('📝 测试总结:');
console.log(` 1. ✅ 项目配置从数据库读取成功`);
console.log(` 2. ✅ API连接正常`);
console.log(` 3. ✅ 可以查询所有记录 (${uniqueRecordIds.length} 条)`);
console.log(` 4. ✅ 可以查询特定记录`);
console.log(` 5. ✅ 数据格式符合AI对话需求`);
console.log();
console.log('🚀 下一步:');
console.log(' 将查询功能集成到ChatService让AI能够基于真实数据回答问题');
console.log();
} catch (error: any) {
console.error('❌ 测试失败:', error.message);
console.error(' 错误详情:', error);
process.exit(1);
} finally {
await prisma.$disconnect();
}
process.exit(0);
}
// 执行测试
main().catch((error) => {
console.error('💥 测试脚本执行失败:', error);
process.exit(1);
});