import { randomBytes } from "crypto"; import { TRPCError } from "@trpc/server"; import { z } from "zod"; import { asc, createId, eq } from "@homarr/db"; import { invites } from "@homarr/db/schema"; import { selectInviteSchema } from "@homarr/db/validationSchemas"; import { createTRPCRouter, permissionRequiredProcedure } from "../trpc"; import { throwIfCredentialsDisabled } from "./invite/checks"; export const inviteRouter = createTRPCRouter({ getAll: permissionRequiredProcedure .requiresPermission("admin") .output( z.array( selectInviteSchema .pick({ id: true, expirationDate: true, }) .extend({ creator: z.object({ name: z.string().nullable(), id: z.string() }) }), ), ) .input(z.undefined()) .meta({ openapi: { method: "GET", path: "/api/invites", tags: ["invites"], protect: true } }) .query(async ({ ctx }) => { throwIfCredentialsDisabled(); return await ctx.db.query.invites.findMany({ orderBy: asc(invites.expirationDate), columns: { token: false, }, with: { creator: { columns: { id: true, name: true, }, }, }, }); }), createInvite: permissionRequiredProcedure .requiresPermission("admin") .input( z.object({ expirationDate: z.date(), }), ) .output(z.object({ id: z.string(), token: z.string() })) .meta({ openapi: { method: "POST", path: "/api/invites", tags: ["invites"], protect: true } }) .mutation(async ({ ctx, input }) => { throwIfCredentialsDisabled(); const id = createId(); const token = randomBytes(20).toString("hex"); await ctx.db.insert(invites).values({ id, expirationDate: input.expirationDate, creatorId: ctx.session.user.id, token, }); return { id, token, }; }), deleteInvite: permissionRequiredProcedure .requiresPermission("admin") .input( z.object({ id: z.string(), }), ) .output(z.undefined()) .meta({ openapi: { method: "DELETE", path: "/api/invites/{id}", tags: ["invites"], protect: true } }) .mutation(async ({ ctx, input }) => { throwIfCredentialsDisabled(); const dbInvite = await ctx.db.query.invites.findFirst({ where: eq(invites.id, input.id), }); if (!dbInvite) { throw new TRPCError({ code: "NOT_FOUND", message: "Invite not found", }); } await ctx.db.delete(invites).where(eq(invites.id, input.id)); }), });