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 { categories } from "@lifetracker/db/schema"; import { zCategorySchema, zGetCategoryResponseSchema, zUpdateCategoryRequestSchema } from "@lifetracker/shared/types/categories"; import type { Context } from "../index"; import { authedProcedure, router } from "../index"; function conditionFromInput(input: { categoryId: string }, userId: string) { return and(eq(categories.id, input.categoryId), eq(categories.userId, userId)); } async function createCategory( input: z.infer, ctx: Context, ) { console.log("Creating a category"); return ctx.db.transaction(async (trx) => { try { const result = await trx .insert(categories) .values({ name: input.name, code: input.code, description: input.description, color: input.color, userId: ctx.user!.id, }) .returning({ id: categories.id, name: categories.name, code: categories.code, description: categories.description, color: categories.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 categories.ts", }); } } throw new TRPCError({ code: "INTERNAL_SERVER_ERROR", message: "Something went wrong", }); } }); } export const categoriesAppRouter = router({ categoryStats: authedProcedure .output( z.record( z.string(), z.object({ numEntries: z.number(), }), ), ) .query(async ({ ctx }) => { const [categoryIds] = await Promise.all([ ctx.db.select({ id: categories.id }).from(categories) ]); const results: Record< string, { numEntries: number } > = {}; for (const category of categoryIds) { results[category.id] = { numEntries: 3330, }; } return results; }), list: authedProcedure .output( z.object({ categories: z.array( z.object({ id: z.string(), name: z.string(), }), ), }), ) .query(async ({ ctx }) => { const dbCategories = await ctx.db .select({ id: categories.id, name: categories.name, }) .from(categories); // console.log("Listing cats"); // console.log(dbCategories); // console.log(dbCategories.map(({ ...category }) => ({ // ...category // }))); return { categories: dbCategories.map(({ ...category }) => ({ ...category })), }; }), get: authedProcedure .input( z.object({ categoryId: z.string(), }), ) .output(zGetCategoryResponseSchema) .query(async ({ input, ctx }) => { const res = await ctx.db .select({ id: categories.id, name: categories.name }) .from(categories) .where( and( conditionFromInput(input, ctx.user.id), eq(categories.userId, ctx.user.id), ), ); if (res.length == 0) { throw new TRPCError({ code: "NOT_FOUND" }); } const numEntriesWithCategory = res.reduce< Record >( (acc, curr) => { if (curr.categorizedBy) { acc[curr.categorizedBy]++; } return acc; }, { ai: 0, human: 0 }, ); return { id: res[0].id, name: res[0].name, numEntries: 420 }; }), create: authedProcedure .input(zCategorySchema) .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 category"); return createCategory(input, ctx); }), update: authedProcedure .input(zUpdateCategoryRequestSchema) .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({ categoryId: 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(categories) .where(conditionFromInput(input, ctx.user.id)); if (res.changes == 0) { throw new TRPCError({ code: "NOT_FOUND" }); } }), });