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
79 lines
2.5 KiB
TypeScript
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 } };
|
|
}
|
|
}
|