import { Body, Controller, HttpCode, Inject, Param, Post, UseGuards } from "@nestjs/common"; import { ZodValidate } from "../../common/pipes/zod-validation.pipe.js"; import { InternalServiceGuard } from "../../common/guards/internal-service.guard.js"; import { TestSeamService } from "./test-seam.service.js"; import { InternalAddMemberDto, type InternalAddMemberInput } from "./test-seam.dto.js"; /** * Test-only membership seam controller (TASK-0095 S9). * * `POST /internal/workspaces/:workspaceId/members` — add a registered user * to a workspace directly (no invite mail), unblocking the two-user flows * that the email-gated invite flow (D3) otherwise makes untestable. * * Gating (two independent switches, both fail-closed): * - `InternalServiceGuard` — the shared `INTERNAL_SERVICE_TOKEN` * (constant-time compare; user JWTs rejected; closed when the token is * empty). Same guard as `InternalBlocksController`. * - `INTERNAL_TEST_SEAM=1` — the controller AND the service both reject * when the flag is not enabled, so a mis-wired controller cannot open the * seam in production. * * EXCLUDED from the global `api/v1` prefix (like `/internal/blocks/*` — * see main.ts): the canonical path is `/internal/workspaces/:id/members`. */ @Controller("internal/workspaces") @UseGuards(InternalServiceGuard) export class InternalTestSeamController { constructor(@Inject(TestSeamService) private readonly seam: TestSeamService) {} @Post(":workspaceId/members") @HttpCode(201) addMember( @Param("workspaceId") workspaceId: string, @Body(ZodValidate(InternalAddMemberDto)) body: InternalAddMemberInput ) { return this.seam.addMember(workspaceId, body); } }