1import { Body, Controller, Get, HttpCode, Inject, Param, Post, Query, UseGuards } from "@nestjs/common"; 2import { ZodValidate } from "../../common/pipes/zod-validation.pipe.js"; 3import { InternalServiceGuard } from "../../common/guards/internal-service.guard.js"; 4import { InternalBlocksService } from "./internal.service.js"; 5import { 6 InternalFlushDto, 7 type InternalFlushInput, 8 InternalReadQuery, 9 type InternalReadQueryInput 10} from "./internal.dto.js"; 11 12/** 13 * Internal block endpoints (TASK-0092 slice 2) — the service-to-service 14 * surface the realtime gateway uses to hydrate a room and flush a CRDT 15 * doc. 16 * 17 * - Auth: `InternalServiceGuard` (the shared `INTERNAL_SERVICE_TOKEN`, 18 * fail-closed when unset). A user JWT / API key is rejected here — 19 * the internal surface accepts ONLY the service token. 20 * - Routing: EXCLUDED from the global `api/v1` prefix (`main.ts`) → the 21 * canonical paths are `/internal/blocks/:pageId` + 22 * `/internal/blocks/flush` (never on the public route table). 23 * - No permission re-check: the room's join matrix (VIEW to join, EDIT 24 * to send doc updates) already gated the edits; the flush is a system 25 * write attributed `actor: 'realtime-flush'`. 26 */ 27@Controller("internal") 28@UseGuards(InternalServiceGuard) 29export class InternalBlocksController { 30 constructor( 31 @Inject(InternalBlocksService) private readonly internal: InternalBlocksService 32 ) {} 33 34 /** 35 * Hydrator read: position-ordered blocks + the `pages.version` (the 36 * flush's CAS base), cursor-paginated by `position` (the 100k-block 37 * hydration streams per page — doc §4.2). 38 */ 39 @Get("blocks/:pageId") 40 read( 41 @Param("pageId") pageId: string, 42 @Query(ZodValidate(InternalReadQuery)) query: InternalReadQueryInput 43 ) { 44 return this.internal.internalRead(pageId, query); 45 } 46 47 /** 48 * Persister write: the page's full block set under the `pages.version` 49 * CAS. A stale `baseVersion` → no-op + `conflict.externalBlocks` (the 50 * room rebases — the "flush wins" rule). 51 */ 52 @Post("blocks/flush") 53 @HttpCode(200) // an idempotent persist (CAS write), not a resource creation 54 flush(@Body(ZodValidate(InternalFlushDto)) body: InternalFlushInput) { 55 return this.internal.internalFlush(body); 56 } 57} 58