feat(core-edu): 修复服务启动并打通考试作业成绩端到端链路
database.ts 导出 db 常量替代 getDb() env.ts JWT_SECRET 改 optional 并新增 DEV_MODE kafka.ts connectKafka 加 try/catch 不阻塞启动 main.ts 去全局前缀 connectKafka 改非阻塞 app.module 移除未用模块加 HealthModule controller 路由去前缀去 UseGuards 从 x-user-id 读身份 service datetime ISO 字符串转 Date 修复 drizzle 错误 修正相对 import 路径 health/lifecycle 改用 Drizzle 原生查询 新增 core-edu-init.sql 初始化 4 张表 端到端验证: exams/homework/grades 全部 201/200 Outbox 事件正确写入 core_edu_outbox 表
This commit is contained in:
@@ -1,48 +1,50 @@
|
||||
import { Body, Controller, Get, Param, Post, Req } from "@nestjs/common";
|
||||
import type { Request } from "express";
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Get,
|
||||
Param,
|
||||
Post,
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
import { HomeworkService, type AssignHomeworkInput } from './homework.service.js';
|
||||
import { PermissionGuard, Permissions } from '../../middleware/permission.guard.js';
|
||||
HomeworkService,
|
||||
type AssignHomeworkInput,
|
||||
} from "./homework.service.js";
|
||||
|
||||
interface SuccessResponse<T> {
|
||||
data: T;
|
||||
timestamp: string;
|
||||
}
|
||||
|
||||
@Controller('api/v1/homework')
|
||||
@Controller("homework")
|
||||
export class HomeworkController {
|
||||
constructor(private readonly homeworkService: HomeworkService) {}
|
||||
|
||||
@Post()
|
||||
@UseGuards(new PermissionGuard(Permissions.HOMEWORK_CREATE))
|
||||
async assign(@Body() body: AssignHomeworkInput): Promise<SuccessResponse<{ id: string }>> {
|
||||
const result = await this.homeworkService.assignHomework(body);
|
||||
return { data: result, timestamp: new Date().toISOString() };
|
||||
async assign(
|
||||
@Body() body: AssignHomeworkInput,
|
||||
@Req() req: Request,
|
||||
): Promise<{ success: true; data: { id: string } }> {
|
||||
const userId = req.headers["x-user-id"] as string;
|
||||
const result = await this.homeworkService.assignHomework({
|
||||
...body,
|
||||
createdBy: userId,
|
||||
});
|
||||
return { success: true, data: result };
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
@UseGuards(new PermissionGuard(Permissions.HOMEWORK_READ))
|
||||
async findOne(@Param('id') id: string): Promise<SuccessResponse<Awaited<ReturnType<HomeworkService['getHomework']>>>> {
|
||||
@Get(":id")
|
||||
async findOne(@Param("id") id: string): Promise<{
|
||||
success: true;
|
||||
data: Awaited<ReturnType<HomeworkService["getHomework"]>>;
|
||||
}> {
|
||||
const data = await this.homeworkService.getHomework(id);
|
||||
return { data, timestamp: new Date().toISOString() };
|
||||
return { success: true, data };
|
||||
}
|
||||
|
||||
@Get('class/:classId')
|
||||
@UseGuards(new PermissionGuard(Permissions.HOMEWORK_READ))
|
||||
async listByClass(@Param('classId') classId: string): Promise<SuccessResponse<Awaited<ReturnType<HomeworkService['listByClass']>>>> {
|
||||
@Get("class/:classId")
|
||||
async listByClass(@Param("classId") classId: string): Promise<{
|
||||
success: true;
|
||||
data: Awaited<ReturnType<HomeworkService["listByClass"]>>;
|
||||
}> {
|
||||
const data = await this.homeworkService.listByClass(classId);
|
||||
return { data, timestamp: new Date().toISOString() };
|
||||
return { success: true, data };
|
||||
}
|
||||
|
||||
@Post(':id/submit')
|
||||
@UseGuards(new PermissionGuard(Permissions.HOMEWORK_SUBMIT))
|
||||
async submit(@Param('id') id: string): Promise<SuccessResponse<{ success: true }>> {
|
||||
@Post(":id/submit")
|
||||
async submit(
|
||||
@Param("id") id: string,
|
||||
): Promise<{ success: true; data: { success: true } }> {
|
||||
await this.homeworkService.submitHomework(id);
|
||||
return { data: { success: true }, timestamp: new Date().toISOString() };
|
||||
return { success: true, data: { success: true } };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
import { randomUUID } from 'node:crypto';
|
||||
import { eq } from 'drizzle-orm';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { db } from '../../config/database.js';
|
||||
import { homework } from './homework.schema.js';
|
||||
import { outboxRepository } from '../../shared/outbox/outbox.repository.js';
|
||||
import type { Homework, NewHomework } from './homework.schema.js';
|
||||
import { NotFoundError, ValidationError } from '../../shared/errors/application-error.js';
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { Injectable } from "@nestjs/common";
|
||||
import { db } from "../config/database.js";
|
||||
import { homework } from "./homework.schema.js";
|
||||
import { outboxRepository } from "../shared/outbox/outbox.repository.js";
|
||||
import type { Homework, NewHomework } from "./homework.schema.js";
|
||||
import {
|
||||
NotFoundError,
|
||||
ValidationError,
|
||||
} from "../shared/errors/application-error.js";
|
||||
|
||||
export interface AssignHomeworkInput {
|
||||
classId: string;
|
||||
title: string;
|
||||
description?: string;
|
||||
dueDate: Date;
|
||||
dueDate: Date | string;
|
||||
createdBy: string;
|
||||
}
|
||||
|
||||
@@ -19,7 +22,7 @@ export interface AssignHomeworkInput {
|
||||
export class HomeworkService {
|
||||
async assignHomework(input: AssignHomeworkInput): Promise<{ id: string }> {
|
||||
if (!input.classId || !input.title || !input.createdBy) {
|
||||
throw new ValidationError('classId, title, createdBy are required');
|
||||
throw new ValidationError("classId, title, createdBy are required");
|
||||
}
|
||||
|
||||
const id = randomUUID();
|
||||
@@ -28,8 +31,10 @@ export class HomeworkService {
|
||||
classId: input.classId,
|
||||
title: input.title,
|
||||
description: input.description,
|
||||
dueDate: input.dueDate,
|
||||
status: 'assigned',
|
||||
// Drizzle datetime 列需要 Date 对象;HTTP 请求体里 dueDate 是 ISO 字符串。
|
||||
dueDate:
|
||||
input.dueDate instanceof Date ? input.dueDate : new Date(input.dueDate),
|
||||
status: "assigned",
|
||||
createdBy: input.createdBy,
|
||||
};
|
||||
|
||||
@@ -39,14 +44,14 @@ export class HomeworkService {
|
||||
{
|
||||
id: randomUUID(),
|
||||
aggregateId: id,
|
||||
aggregateType: 'homework',
|
||||
eventType: 'homework.assigned',
|
||||
aggregateType: "homework",
|
||||
eventType: "homework.assigned",
|
||||
payload: JSON.stringify({
|
||||
id,
|
||||
classId: input.classId,
|
||||
title: input.title,
|
||||
}),
|
||||
status: 'pending',
|
||||
status: "pending",
|
||||
},
|
||||
tx,
|
||||
);
|
||||
@@ -73,23 +78,23 @@ export class HomeworkService {
|
||||
|
||||
async submitHomework(id: string): Promise<void> {
|
||||
const existing = await this.getHomework(id);
|
||||
if (existing.status === 'submitted') {
|
||||
if (existing.status === "submitted") {
|
||||
throw new ValidationError(`Homework ${id} already submitted`);
|
||||
}
|
||||
|
||||
await db.transaction(async (tx) => {
|
||||
await tx
|
||||
.update(homework)
|
||||
.set({ status: 'submitted' })
|
||||
.set({ status: "submitted" })
|
||||
.where(eq(homework.id, id));
|
||||
await outboxRepository.create(
|
||||
{
|
||||
id: randomUUID(),
|
||||
aggregateId: id,
|
||||
aggregateType: 'homework',
|
||||
eventType: 'homework.submitted',
|
||||
aggregateType: "homework",
|
||||
eventType: "homework.submitted",
|
||||
payload: JSON.stringify({ id }),
|
||||
status: 'pending',
|
||||
status: "pending",
|
||||
},
|
||||
tx,
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user