forked from wangziqi/gongxue-base
Download attachments through authenticated API requests, support configurable upload paths, validate resolved file locations, and retain compatibility with legacy stored paths.
383 lines
11 KiB
TypeScript
383 lines
11 KiB
TypeScript
import {
|
|
Controller,
|
|
Get,
|
|
Post,
|
|
Put,
|
|
Delete,
|
|
Body,
|
|
Param,
|
|
UseGuards,
|
|
Request,
|
|
UseInterceptors,
|
|
UploadedFile,
|
|
Res,
|
|
} from '@nestjs/common';
|
|
import { FileInterceptor } from '@nestjs/platform-express';
|
|
import type { Request as ExpressRequest, Response } from 'express';
|
|
import * as fs from 'fs';
|
|
import { ArchiveReportService } from './archive-report.service';
|
|
import { ArchiveService } from './archive.service';
|
|
import {
|
|
UpsertProfileDto,
|
|
CreateEnrollmentDto,
|
|
CreateExamScoreDto,
|
|
CreateLearningRecordDto,
|
|
UpsertResultDto,
|
|
} from './dto/archive.dto';
|
|
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
|
|
import { OperationLogsService } from '../operation-logs/operation-logs.service';
|
|
import { extractRequestInfo } from '../common/request-utils';
|
|
import { RequirePermission } from '../auth/decorators/permission.decorator';
|
|
|
|
interface AuthenticatedRequest extends ExpressRequest {
|
|
user?: { id: number; username?: string };
|
|
}
|
|
|
|
@UseGuards(JwtAuthGuard)
|
|
@Controller('archive')
|
|
export class ArchiveController {
|
|
constructor(
|
|
private readonly archiveService: ArchiveService,
|
|
private readonly logService: OperationLogsService,
|
|
private readonly reportService: ArchiveReportService,
|
|
) {}
|
|
|
|
@Get(':studentId')
|
|
@RequirePermission('student:view')
|
|
async getProfile(@Param('studentId') studentId: string, @Request() req: AuthenticatedRequest) {
|
|
const { ipAddress, userAgent } = extractRequestInfo(req);
|
|
const result = await this.archiveService.getProfile(+studentId);
|
|
await this.logService.log({
|
|
userId: req.user?.id,
|
|
username: req.user?.username,
|
|
module: '学生档案',
|
|
action: '查看档案',
|
|
targetId: +studentId,
|
|
targetType: 'archive',
|
|
ipAddress,
|
|
userAgent,
|
|
});
|
|
return result;
|
|
}
|
|
|
|
@Put(':studentId/profile')
|
|
@RequirePermission('student:edit')
|
|
async upsertProfile(
|
|
@Param('studentId') studentId: string,
|
|
@Body() dto: UpsertProfileDto,
|
|
@Request() req: AuthenticatedRequest,
|
|
) {
|
|
const { ipAddress, userAgent } = extractRequestInfo(req);
|
|
const result = await this.archiveService.upsertProfile(+studentId, dto);
|
|
await this.logService.log({
|
|
userId: req.user?.id,
|
|
username: req.user?.username,
|
|
module: '学生档案',
|
|
action: '更新档案信息',
|
|
targetId: +studentId,
|
|
targetType: 'student_profile',
|
|
detail: JSON.stringify(dto),
|
|
ipAddress,
|
|
userAgent,
|
|
});
|
|
return result;
|
|
}
|
|
|
|
@Post(':studentId/enrollments')
|
|
@RequirePermission('student:edit')
|
|
async addEnrollment(
|
|
@Param('studentId') studentId: string,
|
|
@Body() dto: CreateEnrollmentDto,
|
|
@Request() req: AuthenticatedRequest,
|
|
) {
|
|
const { ipAddress, userAgent } = extractRequestInfo(req);
|
|
const result = await this.archiveService.addEnrollment(+studentId, dto);
|
|
await this.logService.log({
|
|
userId: req.user?.id,
|
|
username: req.user?.username,
|
|
module: '学生档案',
|
|
action: '添加报名记录',
|
|
targetId: result.id,
|
|
targetType: 'student_enrollment',
|
|
detail: `${dto.courseCategory} - ${dto.classType}`,
|
|
ipAddress,
|
|
userAgent,
|
|
});
|
|
return result;
|
|
}
|
|
|
|
@Put('enrollments/:id')
|
|
@RequirePermission('student:edit')
|
|
async updateEnrollment(
|
|
@Param('id') id: string,
|
|
@Body() dto: Partial<CreateEnrollmentDto>,
|
|
@Request() req: AuthenticatedRequest,
|
|
) {
|
|
const { ipAddress, userAgent } = extractRequestInfo(req);
|
|
const result = await this.archiveService.updateEnrollment(+id, dto);
|
|
await this.logService.log({
|
|
userId: req.user?.id,
|
|
username: req.user?.username,
|
|
module: '学生档案',
|
|
action: '编辑报名记录',
|
|
targetId: +id,
|
|
targetType: 'student_enrollment',
|
|
detail: JSON.stringify(dto),
|
|
ipAddress,
|
|
userAgent,
|
|
});
|
|
return result;
|
|
}
|
|
|
|
@Delete('enrollments/:id')
|
|
@RequirePermission('student:edit')
|
|
async deleteEnrollment(@Param('id') id: string, @Request() req: AuthenticatedRequest) {
|
|
const { ipAddress, userAgent } = extractRequestInfo(req);
|
|
const result = await this.archiveService.deleteEnrollment(+id);
|
|
await this.logService.log({
|
|
userId: req.user?.id,
|
|
username: req.user?.username,
|
|
module: '学生档案',
|
|
action: '删除报名记录',
|
|
targetId: +id,
|
|
targetType: 'student_enrollment',
|
|
ipAddress,
|
|
userAgent,
|
|
});
|
|
return result;
|
|
}
|
|
|
|
@Post(':studentId/exam-scores')
|
|
@RequirePermission('student:edit')
|
|
async addExamScore(
|
|
@Param('studentId') studentId: string,
|
|
@Body() dto: CreateExamScoreDto,
|
|
@Request() req: AuthenticatedRequest,
|
|
) {
|
|
const { ipAddress, userAgent } = extractRequestInfo(req);
|
|
const result = await this.archiveService.addExamScore(+studentId, dto);
|
|
await this.logService.log({
|
|
userId: req.user?.id,
|
|
username: req.user?.username,
|
|
module: '学生档案',
|
|
action: '添加考试成绩',
|
|
targetId: result.id,
|
|
targetType: 'exam_score',
|
|
detail: `${dto.examType} - ${dto.subject}: ${dto.score}`,
|
|
ipAddress,
|
|
userAgent,
|
|
});
|
|
return result;
|
|
}
|
|
|
|
@Put('exam-scores/:id')
|
|
@RequirePermission('student:edit')
|
|
async updateExamScore(
|
|
@Param('id') id: string,
|
|
@Body() dto: Partial<CreateExamScoreDto>,
|
|
@Request() req: AuthenticatedRequest,
|
|
) {
|
|
const { ipAddress, userAgent } = extractRequestInfo(req);
|
|
const result = await this.archiveService.updateExamScore(+id, dto);
|
|
await this.logService.log({
|
|
userId: req.user?.id,
|
|
username: req.user?.username,
|
|
module: '学生档案',
|
|
action: '编辑考试成绩',
|
|
targetId: +id,
|
|
targetType: 'exam_score',
|
|
detail: JSON.stringify(dto),
|
|
ipAddress,
|
|
userAgent,
|
|
});
|
|
return result;
|
|
}
|
|
|
|
@Delete('exam-scores/:id')
|
|
@RequirePermission('student:edit')
|
|
async deleteExamScore(@Param('id') id: string, @Request() req: AuthenticatedRequest) {
|
|
const { ipAddress, userAgent } = extractRequestInfo(req);
|
|
const result = await this.archiveService.deleteExamScore(+id);
|
|
await this.logService.log({
|
|
userId: req.user?.id,
|
|
username: req.user?.username,
|
|
module: '学生档案',
|
|
action: '删除考试成绩',
|
|
targetId: +id,
|
|
targetType: 'exam_score',
|
|
ipAddress,
|
|
userAgent,
|
|
});
|
|
return result;
|
|
}
|
|
|
|
@Post(':studentId/learning-records')
|
|
@RequirePermission('student:edit')
|
|
async addLearningRecord(
|
|
@Param('studentId') studentId: string,
|
|
@Body() dto: CreateLearningRecordDto,
|
|
@Request() req: AuthenticatedRequest,
|
|
) {
|
|
const { ipAddress, userAgent } = extractRequestInfo(req);
|
|
const result = await this.archiveService.addLearningRecord(+studentId, dto);
|
|
await this.logService.log({
|
|
userId: req.user?.id,
|
|
username: req.user?.username,
|
|
module: '学生档案',
|
|
action: '添加学习记录',
|
|
targetId: result.id,
|
|
targetType: 'learning_record',
|
|
detail: `${dto.recordType}: ${dto.content.substring(0, 50)}`,
|
|
ipAddress,
|
|
userAgent,
|
|
});
|
|
return result;
|
|
}
|
|
|
|
@Put('learning-records/:id')
|
|
@RequirePermission('student:edit')
|
|
async updateLearningRecord(
|
|
@Param('id') id: string,
|
|
@Body() dto: Partial<CreateLearningRecordDto>,
|
|
@Request() req: AuthenticatedRequest,
|
|
) {
|
|
const { ipAddress, userAgent } = extractRequestInfo(req);
|
|
const result = await this.archiveService.updateLearningRecord(+id, dto);
|
|
await this.logService.log({
|
|
userId: req.user?.id,
|
|
username: req.user?.username,
|
|
module: '学生档案',
|
|
action: '编辑学习记录',
|
|
targetId: +id,
|
|
targetType: 'learning_record',
|
|
detail: JSON.stringify(dto),
|
|
ipAddress,
|
|
userAgent,
|
|
});
|
|
return result;
|
|
}
|
|
|
|
@Delete('learning-records/:id')
|
|
@RequirePermission('student:edit')
|
|
async deleteLearningRecord(@Param('id') id: string, @Request() req: AuthenticatedRequest) {
|
|
const { ipAddress, userAgent } = extractRequestInfo(req);
|
|
const result = await this.archiveService.deleteLearningRecord(+id);
|
|
await this.logService.log({
|
|
userId: req.user?.id,
|
|
username: req.user?.username,
|
|
module: '学生档案',
|
|
action: '删除学习记录',
|
|
targetId: +id,
|
|
targetType: 'learning_record',
|
|
ipAddress,
|
|
userAgent,
|
|
});
|
|
return result;
|
|
}
|
|
|
|
@Put(':studentId/result')
|
|
@RequirePermission('student:edit')
|
|
async upsertResult(
|
|
@Param('studentId') studentId: string,
|
|
@Body() dto: UpsertResultDto,
|
|
@Request() req: AuthenticatedRequest,
|
|
) {
|
|
const { ipAddress, userAgent } = extractRequestInfo(req);
|
|
const result = await this.archiveService.upsertResult(+studentId, dto);
|
|
await this.logService.log({
|
|
userId: req.user?.id,
|
|
username: req.user?.username,
|
|
module: '学生档案',
|
|
action: '更新录取结果',
|
|
targetId: +studentId,
|
|
targetType: 'result_archive',
|
|
detail: JSON.stringify(dto),
|
|
ipAddress,
|
|
userAgent,
|
|
});
|
|
return result;
|
|
}
|
|
|
|
@Post(':studentId/attachments')
|
|
@RequirePermission('student:edit')
|
|
@UseInterceptors(FileInterceptor('file'))
|
|
async uploadAttachment(
|
|
@Param('studentId') studentId: string,
|
|
@UploadedFile() file: Express.Multer.File,
|
|
@Body('category') category: string,
|
|
@Request() req: AuthenticatedRequest,
|
|
) {
|
|
const { ipAddress, userAgent } = extractRequestInfo(req);
|
|
const result = await this.archiveService.addAttachment(+studentId, file, category || 'other');
|
|
await this.logService.log({
|
|
userId: req.user?.id,
|
|
username: req.user?.username,
|
|
module: '学生档案',
|
|
action: '上传附件',
|
|
targetId: result.id,
|
|
targetType: 'archive_attachment',
|
|
detail: `${file.originalname} (${category || 'other'})`,
|
|
ipAddress,
|
|
userAgent,
|
|
});
|
|
return result;
|
|
}
|
|
|
|
@Get(':studentId/attachments/:id')
|
|
@RequirePermission('student:view')
|
|
async downloadAttachment(
|
|
@Param('studentId') studentId: string,
|
|
@Param('id') id: string,
|
|
@Res() res: Response,
|
|
) {
|
|
const { fullPath, fileName, mimeType } = await this.archiveService.getAttachmentFile(+studentId, +id);
|
|
res.setHeader('Content-Type', mimeType);
|
|
res.setHeader(
|
|
'Content-Disposition',
|
|
`inline; filename="${encodeURIComponent(fileName)}"`,
|
|
);
|
|
const stream = fs.createReadStream(fullPath);
|
|
stream.pipe(res);
|
|
}
|
|
|
|
@Delete('attachments/:id')
|
|
@RequirePermission('student:edit')
|
|
async deleteAttachment(@Param('id') id: string, @Request() req: AuthenticatedRequest) {
|
|
const { ipAddress, userAgent } = extractRequestInfo(req);
|
|
const result = await this.archiveService.deleteAttachment(+id);
|
|
await this.logService.log({
|
|
userId: req.user?.id,
|
|
username: req.user?.username,
|
|
module: '学生档案',
|
|
action: '删除附件',
|
|
targetId: +id,
|
|
targetType: 'archive_attachment',
|
|
ipAddress,
|
|
userAgent,
|
|
});
|
|
return result;
|
|
}
|
|
|
|
|
|
@Get(':studentId/report-html')
|
|
@RequirePermission('student:view')
|
|
async generateReportHtml(
|
|
@Param('studentId') studentId: string,
|
|
@Request() req: AuthenticatedRequest,
|
|
) {
|
|
const { ipAddress, userAgent } = extractRequestInfo(req);
|
|
await this.logService.log({
|
|
userId: req.user?.id,
|
|
username: req.user?.username,
|
|
module: 'archive',
|
|
action: 'generate_report_html',
|
|
targetId: +studentId,
|
|
targetType: 'student',
|
|
ipAddress,
|
|
userAgent,
|
|
});
|
|
const html = await this.reportService.generateReportHtml(+studentId);
|
|
return { html };
|
|
}
|
|
}
|