import { Controller, Get, Inject, Param, Query, Req } from "@nestjs/common"; import type { Request } from "express"; import { z } from "zod"; import { PrismaService } from "../../common/prisma.service.js"; import { ZodValidate } from "../../common/pipes/zod-validation.pipe.js"; import { requireUser } from "../../common/types.js"; import { assertRole } from "../../lib/authorize.js"; /** * Read-only audit log (TASK-0128 — the key-lifecycle E2E asserts * `api_key.created` / `api_key.revoked` "via the audit API"; Phase 14's * audit viewer builds on this endpoint). OWNER/ADMIN-gated (the audit * trail names users and keys — it is not readable by any member), * newest-first, bounded. */ const ListQuery = z.object({ action: z.string().min(1).max(128).optional(), limit: z.coerce.number().int().min(1).max(100).default(50), }); @Controller("workspaces/:workspaceId/audit") export class AuditLogController { constructor(@Inject(PrismaService) private readonly prisma: PrismaService) {} @Get() async list( @Param("workspaceId") workspaceId: string, @Query(ZodValidate(ListQuery)) query: z.infer, @Req() req: Request, ) { const userId = requireUser(req); await assertRole(this.prisma, { workspaceId, userId }, ["OWNER", "ADMIN"]); const rows = await this.prisma.auditLog.findMany({ where: { workspaceId, ...(query.action ? { action: query.action } : {}) }, orderBy: [{ createdAt: "desc" }, { id: "desc" }], take: query.limit, select: { id: true, actorType: true, actorId: true, resourceType: true, resourceId: true, action: true, ipAddress: true, createdAt: true, metadata: true, }, }); return { data: rows }; } }