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
This commit is contained in:
@@ -3,17 +3,20 @@ import {
|
||||
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 {
|
||||
@@ -23,7 +26,7 @@ export class ChaptersController {
|
||||
@RequirePermission(Permissions.CONTENT_CHAPTER_CREATE)
|
||||
async create(
|
||||
@Body() body: unknown,
|
||||
): Promise<{ success: true; data: { id: string } }> {
|
||||
): Promise<{ success: true; data: ChapterWriteResult }> {
|
||||
const input = createChapterSchema.parse(body);
|
||||
const result = await this.service.createChapter(input);
|
||||
return { success: true, data: result };
|
||||
@@ -52,18 +55,24 @@ export class ChaptersController {
|
||||
async update(
|
||||
@Param("id") id: string,
|
||||
@Body() body: unknown,
|
||||
): Promise<{ success: true; data: { success: true } }> {
|
||||
@Headers("if-match") ifMatch?: string,
|
||||
@Headers("x-expected-version") xExpectedVersion?: string,
|
||||
): Promise<{ success: true; data: ChapterWriteResult }> {
|
||||
const input = updateChapterSchema.parse(body);
|
||||
await this.service.updateChapter(id, input);
|
||||
return { success: true, data: { success: true } };
|
||||
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 } }> {
|
||||
await this.service.deleteChapter(id);
|
||||
const expectedVersion = extractExpectedVersion(ifMatch, xExpectedVersion);
|
||||
await this.service.deleteChapter(id, expectedVersion);
|
||||
return { success: true, data: { success: true } };
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user