import { experimental_trpcMiddleware, TRPCError } from "@trpc/server"; import { and, desc, eq, inArray, notExists } from "drizzle-orm"; import { date, z } from "zod"; import { SqliteError } from "@lifetracker/db"; import { categories, days, hours, colors, measurements, metrics } from "@lifetracker/db/schema"; import { zDaySchema, ZDay, ZHour, zHourSchema } from "@lifetracker/shared/types/days"; import type { Context } from "../index"; import { authedProcedure, router } from "../index"; import { format } from "date-fns"; import { TZDate } from "@date-fns/tz"; import { dateFromInput } from "@lifetracker/shared/utils/days"; import { BetterSQLite3Database } from "drizzle-orm/better-sqlite3"; export async function hourColors(hour: ZHour, ctx: Context) { const categoryColor = await ctx.db.select() .from(colors) .leftJoin(categories, eq(categories.id, hour.categoryId)) .where(and( eq(colors.id, categories.colorId), eq(colors.userId, ctx.user!.id) )) if (!categoryColor[0]) { return { background: "inherit", foreground: "inherit" } } else { return { background: categoryColor[0].color.hexcode, foreground: categoryColor[0].color.inverse, } } } export async function hourJoinsQuery( ctx: Context, dayId: string, time: number, ) { const hourMatch = await ctx.db.select({ id: hours.id, dayId: hours.dayId, time: hours.time, categoryId: hours.categoryId, categoryCode: categories.code, categoryName: categories.name, categoryDesc: categories.description, comment: hours.comment, date: days.date, }).from(hours) .leftJoin(categories, eq(categories.id, hours.categoryId)) .leftJoin(days, eq(days.id, hours.dayId)) .where(and( eq(hours.time, time), eq(hours.dayId, dayId) )); const hourMeasurements = await ctx.db.select({ id: measurements.id, metricId: measurements.metricId, value: measurements.value, unit: metrics.unit, icon: metrics.icon, metricName: metrics.name, metricType: metrics.type, }) .from(measurements) .leftJoin(metrics, eq(metrics.id, measurements.metricId)) .where(eq(measurements.hourId, hourMatch[0].id)); const dayHour = { measurements: hourMeasurements, ...hourMatch[0], ...(await hourColors(hourMatch[0], ctx)), }; return dayHour; }; export const hoursAppRouter = router({ get: authedProcedure .input(z.object({ dateQuery: z.string(), time: z.number() })) .output(zHourSchema) .query(async ({ input, ctx }) => { const date = dateFromInput({ dateQuery: input.dateQuery }); const hourRes = await getHourSelectQuery(ctx, date, input.time, and(eq(hours.time, input.time), eq(days.date, date)) ); return { date: format(date, "yyyy-MM-dd"), ...hourRes[0] }; }), update: authedProcedure .input( z.object({ date: z.string(), hourTime: z.number(), dayId: z.string(), code: z.string().nullish(), comment: z.string().nullable().optional(), }), ) .output(zHourSchema) .mutation(async ({ input, ctx }) => { const { code, ...updatedProps } = input; let dateCondition; if (input.dayId) { dateCondition = eq(days.id, input.dayId) } else { throw new TRPCError({ code: "BAD_REQUEST", message: "dayId is required" }); } const category = code == "" ? [{ id: null, name: null }] : await ctx.db.select( { id: categories.id, name: categories.name, description: categories.description } ) .from(categories) .where( and( eq(categories.code, code), eq(categories.userId, ctx.user!.id), ) ); const newProps = { categoryId: category[0].id, code: code, ...updatedProps }; if (newProps.comment == "") { newProps.comment = null } const hourRes = await ctx.db .update(hours) .set(newProps) .where( and( eq(hours.time, input.hourTime), eq(hours.dayId, input.dayId), eq(hours.userId, ctx.user!.id) ) ).returning(); // return { // date: input.date, // ...hourRes[0] // }; return hourJoinsQuery(ctx, input.dayId, input.hourTime); }), });