import { z } from 'zod'; import { TRPCError } from '@trpc/server'; import { eq, isNull, desc } from 'drizzle-orm'; import { blocker, project, user } from '@cockpit/db'; import { router, protectedProcedure } from '../trpc.js'; export const blockerRouter = router({ listOpen: protectedProcedure.query(async ({ ctx }) => { return ctx.db .select({ blocker: blocker, opener: user, }) .from(blocker) .leftJoin(user, eq(user.id, blocker.openedBy)) .where(isNull(blocker.resolvedAt)) .orderBy(desc(blocker.openedAt)); }), byProject: protectedProcedure .input(z.object({ projectId: z.string().uuid() })) .query(async ({ ctx, input }) => { return ctx.db .select() .from(blocker) .where(eq(blocker.projectId, input.projectId)) .orderBy(desc(blocker.openedAt)); }), create: protectedProcedure .input( z.object({ title: z.string().min(1).max(200), description: z.string().max(1000).optional(), areaId: z.string().uuid().optional(), projectId: z.string().uuid().optional(), ownerUserId: z.string().uuid().optional(), }), ) .mutation(async ({ ctx, input }) => { const [created] = await ctx.db .insert(blocker) .values({ title: input.title, description: input.description ?? null, areaId: input.areaId ?? null, projectId: input.projectId ?? null, ownerUserId: input.ownerUserId ?? null, openedBy: ctx.user.id, }) .returning(); return created; }), resolve: protectedProcedure .input( z.object({ blockerId: z.string().uuid(), resolutionText: z.string().min(1).max(500), }), ) .mutation(async ({ ctx, input }) => { const b = ( await ctx.db.select().from(blocker).where(eq(blocker.id, input.blockerId)).limit(1) )[0]; if (!b) throw new TRPCError({ code: 'NOT_FOUND', message: 'Blocker nicht gefunden.' }); // Authority-Check: bei projektgebundenen Blockern nur P1; sonst Ersteller oder Verantwortlicher. let allowed = false; if (b.projectId) { const p = ( await ctx.db.select().from(project).where(eq(project.id, b.projectId)).limit(1) )[0]; if (p?.verantwortlicheUserId === ctx.user.id) allowed = true; } else { if (b.openedBy === ctx.user.id || b.ownerUserId === ctx.user.id) allowed = true; } if (!allowed) { throw new TRPCError({ code: 'FORBIDDEN', message: 'Nur die/der Projektverantwortliche darf diesen Blocker auflösen. (Bei projektlosen Blockern: Ersteller oder Verantwortliche/r.)', }); } const [updated] = await ctx.db .update(blocker) .set({ resolvedBy: ctx.user.id, resolvedAt: new Date(), resolutionText: input.resolutionText, }) .where(eq(blocker.id, input.blockerId)) .returning(); return updated; }), });