/** * TASK-0107/0108 — the PUBLIC form routes (no auth — the global AuthGuard * whitelists `/api/v1/public/forms/*`): * * GET /public/forms/:slug the spec's public payload * POST /public/forms/:slug/submit the submission pipeline * * The submit path is 201 on success (with an `X-Submission-Id` header for * client dedupe logging); the 429's `Retry-After` header is set on the * response by the service before the exception filter writes the body. */ import { Body, Controller, Get, HttpCode, HttpStatus, Inject, Param, Post, Req, UseGuards } from "@nestjs/common"; import type { Request } from "express"; import { z } from "zod"; import { PublicFormsService } from "./public-forms.service.js"; import { ZodValidate } from "../../common/pipes/zod-validation.pipe.js"; import { loadEnv } from "../../config/env.js"; import { verifyToken, TokenError } from "../../lib/jwt.js"; import type { AuthContext } from "../../common/types.js"; import { OptionalAuthGuard } from "./optional-auth.guard.js"; /** The submit body (spec: answers + optional recaptchaToken + honeypot). */ const SubmitBody = z .object({ answers: z.record(z.unknown()).default({}), recaptchaToken: z.string().max(4_096).optional(), website: z.string().max(500).optional(), // honeypot (hidden) hp: z.string().max(500).optional() // honeypot (hidden) }) .strict(); type SubmitInput = z.infer; @Controller() export class PublicFormsController { constructor(@Inject(PublicFormsService) private readonly publicForms: PublicFormsService) {} @Get("public/forms/:slug") get(@Param("slug") slug: string) { return this.publicForms.getPublic(slug); } // Optional auth: a valid user JWT attributes the submission (submittedBy); // a missing/invalid token is NOT a 401 — the public path never demands it. @UseGuards(OptionalAuthGuard) @HttpCode(HttpStatus.CREATED) @Post("public/forms/:slug/submit") submit( @Param("slug") slug: string, @Body(ZodValidate(SubmitBody)) body: SubmitInput, @Req() req: Request ) { return this.publicForms.submitPublic(slug, body, req); } }