import { experimental_trpcMiddleware, TRPCError } from "@trpc/server"; import { and, desc, eq, inArray, notExists } from "drizzle-orm"; import { z } from "zod"; import { SqliteError } from "@lifetracker/db"; import { labels } from "@lifetracker/db/schema"; import { zLabelSchema, zGetLabelResponseSchema, zUpdateLabelRequestSchema } from "@lifetracker/shared/types/labels"; import type { Context } from "../index"; import { authedProcedure, router } from "../index"; function conditionFromInput(input: { labelId: string }, userId: string) { return and(eq(labels.id, input.labelId), eq(labels.userId, userId)); } async function createLabel( input: z.infer, ctx: Context, ) { console.log("Creating a label"); return ctx.db.transaction(async (trx) => { try { const result = await trx .insert(labels) .values({ name: input.name, code: input.code, description: input.description, color: input.color, userId: ctx.user!.id, }) .returning({ id: labels.id, name: labels.name, code: labels.code, description: labels.description, color: labels.color, }); return result[0]; } catch (e) { if (e instanceof SqliteError) { if (e.code == "SQLITE_CONSTRAINT_UNIQUE") { throw new TRPCError({ code: "BAD_REQUEST", message: "Line 48 trpc routers labels.ts", }); } } throw new TRPCError({ code: "INTERNAL_SERVER_ERROR", message: "Something went wrong", }); } }); } export const labelsAppRouter = router({ labelStats: authedProcedure .output( z.record( z.string(), z.object({ numEntries: z.number(), }), ), ) .query(async ({ ctx }) => { const [labelIds] = await Promise.all([ ctx.db.select({ id: labels.id }).from(labels) ]); const results: Record< string, { numEntries: number } > = {}; for (const label of labelIds) { results[label.id] = { numEntries: 3330, }; } return results; }), list: authedProcedure .output( z.object({ labels: z.array(zGetLabelResponseSchema), }), ) .query(async ({ ctx }) => { const res = await ctx.db .select({ id: labels.id, name: labels.name, code: labels.code, color: labels.color, description: labels.description, }) .from(labels) .where(eq(labels.userId, ctx.user.id)); return { labels: res.map((r) => ({ id: r.id, name: r.name, code: r.code, color: r.color, description: r.description, numEntries: 420, })), }; }), get: authedProcedure .input( z.object({ labelId: z.string(), }), ) .output(zGetLabelResponseSchema) .query(async ({ input, ctx }) => { const res = await ctx.db .select({ id: labels.id, name: labels.name }) .from(labels) .where( and( conditionFromInput(input, ctx.user.id), eq(labels.userId, ctx.user.id), ), ); if (res.length == 0) { throw new TRPCError({ code: "NOT_FOUND" }); } const numEntriesWithLabel = res.reduce< Record >( (acc, curr) => { if (curr.labeledBy) { acc[curr.labeledBy]++; } return acc; }, { ai: 0, human: 0 }, ); return { id: res[0].id, name: res[0].name, numEntries: 420 }; }), create: authedProcedure .input(zLabelSchema) .output( z.object({ id: z.string(), code: z.number(), name: z.string(), color: z.string().default("#000000"), description: z.string().optional(), }), ) .mutation(async ({ input, ctx }) => { console.log("Just started creating a label"); return createLabel(input, ctx); }), update: authedProcedure .input(zUpdateLabelRequestSchema) .output( z.object({ id: z.string(), name: z.string(), userId: z.string(), createdAt: z.date(), }), ) .mutation(async ({ input, ctx }) => { try { const res = await ctx.db .update(bookmarkTags) .set({ name: input.name, }) .where( and( eq(bookmarkTags.id, input.tagId), eq(bookmarkTags.userId, ctx.user.id), ), ) .returning(); if (res.length == 0) { throw new TRPCError({ code: "NOT_FOUND" }); } try { const affectedBookmarks = await ctx.db.query.tagsOnBookmarks.findMany( { where: eq(tagsOnBookmarks.tagId, input.tagId), columns: { bookmarkId: true, }, }, ); await Promise.all( affectedBookmarks .map((b) => b.bookmarkId) .map((id) => triggerSearchReindex(id)), ); } catch (e) { console.error("Something ELSE Went Wrong", e); } return res[0]; } catch (e) { if (e instanceof SqliteError) { if (e.code == "SQLITE_CONSTRAINT_UNIQUE") { throw new TRPCError({ code: "BAD_REQUEST", message: "Label name already exists.", }); } } throw e; } }), delete: authedProcedure .input( z.object({ labelId: z.string(), }), ) .mutation(async ({ input, ctx }) => { // const affectedBookmarks = await ctx.db // .select({ // bookmarkId: tagsOnBookmarks.bookmarkId, // }) // .from(tagsOnBookmarks) // .where( // and( // eq(tagsOnBookmarks.tagId, input.tagId), // // Tag ownership is checked in the ensureTagOwnership middleware // // eq(bookmarkTags.userId, ctx.user.id), // ), // ); const res = await ctx.db .delete(labels) .where(conditionFromInput(input, ctx.user.id)); if (res.changes == 0) { throw new TRPCError({ code: "NOT_FOUND" }); } }), });