import { Body, Controller, Get, HttpCode, Inject, Param, Post, Query, UseGuards } from "@nestjs/common"; import { ZodValidate } from "../../common/pipes/zod-validation.pipe.js"; import { InternalServiceGuard } from "../../common/guards/internal-service.guard.js"; import { InternalBlocksService } from "./internal.service.js"; import { InternalFlushDto, type InternalFlushInput, InternalReadQuery, type InternalReadQueryInput } from "./internal.dto.js"; /** * Internal block endpoints (TASK-0092 slice 2) — the service-to-service * surface the realtime gateway uses to hydrate a room and flush a CRDT * doc. * * - Auth: `InternalServiceGuard` (the shared `INTERNAL_SERVICE_TOKEN`, * fail-closed when unset). A user JWT / API key is rejected here — * the internal surface accepts ONLY the service token. * - Routing: EXCLUDED from the global `api/v1` prefix (`main.ts`) → the * canonical paths are `/internal/blocks/:pageId` + * `/internal/blocks/flush` (never on the public route table). * - No permission re-check: the room's join matrix (VIEW to join, EDIT * to send doc updates) already gated the edits; the flush is a system * write attributed `actor: 'realtime-flush'`. */ @Controller("internal") @UseGuards(InternalServiceGuard) export class InternalBlocksController { constructor( @Inject(InternalBlocksService) private readonly internal: InternalBlocksService ) {} /** * Hydrator read: position-ordered blocks + the `pages.version` (the * flush's CAS base), cursor-paginated by `position` (the 100k-block * hydration streams per page — doc §4.2). */ @Get("blocks/:pageId") read( @Param("pageId") pageId: string, @Query(ZodValidate(InternalReadQuery)) query: InternalReadQueryInput ) { return this.internal.internalRead(pageId, query); } /** * Persister write: the page's full block set under the `pages.version` * CAS. A stale `baseVersion` → no-op + `conflict.externalBlocks` (the * room rebases — the "flush wins" rule). */ @Post("blocks/flush") @HttpCode(200) // an idempotent persist (CAS write), not a resource creation flush(@Body(ZodValidate(InternalFlushDto)) body: InternalFlushInput) { return this.internal.internalFlush(body); } }