import type { PermissionRole, PrismaClient } from "@prisma/client"; import type { FastifyInstance } from "fastify"; import { grantTeamProjectAccess, listProjectTeamAccess, revokeTeamProjectAccess, } from "../../permissions/projectTeamAccess.js"; import { requireProjectPermission, type GuardDeps } from "../auth/guards.js"; import { handleRouteError } from "../errors.js"; const ROLES: readonly PermissionRole[] = ["READ", "EDIT", "MANAGE"]; export async function registerAccessRoutes( app: FastifyInstance, config: { readonly prisma: PrismaClient; readonly sessionSecret: string }, ): Promise { const guardDeps: GuardDeps = { prisma: config.prisma, sessionSecret: config.sessionSecret }; app.get("/api/org/:orgSlug/projects/:projectId/team-access", async (request, reply) => { try { const { orgSlug, projectId } = request.params as { orgSlug: string; projectId: string }; const auth = await requireProjectPermission(request, reply, guardDeps, { orgSlug, projectId, action: "project.read", allowOrgAdminOversight: true, }); if (auth === null) return; return { access: await listProjectTeamAccess(config.prisma, projectId) }; } catch (err) { return handleRouteError(reply, err); } }); app.put("/api/org/:orgSlug/projects/:projectId/team-access", async (request, reply) => { try { const { orgSlug, projectId } = request.params as { orgSlug: string; projectId: string }; const auth = await requireProjectPermission(request, reply, guardDeps, { orgSlug, projectId, action: "collaborator.manage", allowOrgAdminOversight: false, }); if (auth === null) return; const body = request.body as { teamId?: unknown; teamSlug?: unknown; role?: unknown; }; const role = parseRole(body.role); if (role === null) { return reply.status(400).send({ error: { code: "bad_request", message: "role must be READ|EDIT|MANAGE" }, }); } const entry = await grantTeamProjectAccess(config.prisma, { projectId, role, createdByUserId: auth.user.id, ...(typeof body.teamId === "string" ? { teamId: body.teamId } : {}), ...(typeof body.teamSlug === "string" ? { teamSlug: body.teamSlug } : {}), }); return entry; } catch (err) { return handleRouteError(reply, err); } }); app.delete( "/api/org/:orgSlug/projects/:projectId/team-access/:teamId", async (request, reply) => { try { const { orgSlug, projectId, teamId } = request.params as { orgSlug: string; projectId: string; teamId: string; }; const auth = await requireProjectPermission(request, reply, guardDeps, { orgSlug, projectId, action: "collaborator.manage", allowOrgAdminOversight: false, }); if (auth === null) return; const count = await revokeTeamProjectAccess(config.prisma, { projectId, teamId }); return { revoked: count }; } catch (err) { return handleRouteError(reply, err); } }, ); } function parseRole(value: unknown): PermissionRole | null { if (typeof value !== "string") return null; return (ROLES as readonly string[]).includes(value) ? (value as PermissionRole) : null; }