Files
Edu/services/content/src/chapters/chapters.controller.ts
SpecialX a75527be80 feat(content): eager invalidation and optimistic lock for cqrs
M4: ADR-038 Eager Invalidation + ADR-039 Optimistic Lock

- EagerInvalidationService: Redis DEL after MySQL commit

- Cache key conventions for textbook/chapter/kp/question

- Version header check (If-Match/X-Expected-Version) on write endpoints

- 409 Conflict on version mismatch

- All write endpoints return updatedAt timestamp
2026-07-15 01:28:20 +08:00

79 lines
2.5 KiB
TypeScript

import {
Body,
Controller,
Delete,
Get,
Headers,
Param,
Post,
Put,
} from "@nestjs/common";
import { ChaptersService } from "./chapters.service.js";
import type { ChapterWriteResult } from "./chapters.service.js";
import type { Chapter } from "./chapters.schema.js";
import {
Permissions,
RequirePermission,
} from "../middleware/permission.guard.js";
import { createChapterSchema, updateChapterSchema } from "./chapters.dto.js";
import { extractExpectedVersion } from "../shared/cache/version-header.js";
@Controller("chapters")
export class ChaptersController {
constructor(private readonly service: ChaptersService) {}
@Post()
@RequirePermission(Permissions.CONTENT_CHAPTER_CREATE)
async create(
@Body() body: unknown,
): Promise<{ success: true; data: ChapterWriteResult }> {
const input = createChapterSchema.parse(body);
const result = await this.service.createChapter(input);
return { success: true, data: result };
}
@Get("textbook/:textbookId")
@RequirePermission(Permissions.CONTENT_CHAPTER_READ)
async listByTextbook(
@Param("textbookId") textbookId: string,
): Promise<{ success: true; data: Chapter[] }> {
const data = await this.service.listChaptersByTextbook(textbookId);
return { success: true, data };
}
@Get(":id")
@RequirePermission(Permissions.CONTENT_CHAPTER_READ)
async getById(
@Param("id") id: string,
): Promise<{ success: true; data: Chapter }> {
const data = await this.service.getChapter(id);
return { success: true, data };
}
@Put(":id")
@RequirePermission(Permissions.CONTENT_CHAPTER_UPDATE)
async update(
@Param("id") id: string,
@Body() body: unknown,
@Headers("if-match") ifMatch?: string,
@Headers("x-expected-version") xExpectedVersion?: string,
): Promise<{ success: true; data: ChapterWriteResult }> {
const input = updateChapterSchema.parse(body);
const expectedVersion = extractExpectedVersion(ifMatch, xExpectedVersion);
const result = await this.service.updateChapter(id, input, expectedVersion);
return { success: true, data: result };
}
@Delete(":id")
@RequirePermission(Permissions.CONTENT_CHAPTER_DELETE)
async remove(
@Param("id") id: string,
@Headers("if-match") ifMatch?: string,
@Headers("x-expected-version") xExpectedVersion?: string,
): Promise<{ success: true; data: { success: true } }> {
const expectedVersion = extractExpectedVersion(ifMatch, xExpectedVersion);
await this.service.deleteChapter(id, expectedVersion);
return { success: true, data: { success: true } };
}
}