lifetracker/packages/trpc/routers/days.ts

176 lines
5.4 KiB
TypeScript

import { experimental_trpcMiddleware, TRPCError } from "@trpc/server";
import { and, desc, eq, inArray, notExists } from "drizzle-orm";
import { z } from "zod";
import { db, SqliteError } from "@lifetracker/db";
import { categories, days, hours, users } from "@lifetracker/db/schema";
import {
zDaySchema, ZDay
} from "@lifetracker/shared/types/days";
import type { Context } from "../index";
import { authedProcedure, router } from "../index";
import { dateFromInput, hoursListInTimezone } from "@lifetracker/shared/utils/days";
import { closestIndexTo, format } from "date-fns";
import { TZDate } from "@date-fns/tz";
import { hoursAppRouter } from "./hours";
async function createDay(date: string, ctx: Context) {
return await ctx.db.transaction(async (trx) => {
try {
// Create the Day object
const dayRes = await trx
.insert(days)
.values({
userId: ctx.user!.id,
date: date,
})
.returning({
id: days.id,
date: days.date,
mood: days.mood,
comment: days.comment,
});
return dayRes;
} catch (e) {
if (e instanceof SqliteError) {
if (e.code == "SQLITE_CONSTRAINT_UNIQUE") {
throw new TRPCError({
code: "BAD_REQUEST",
message: "This day already exists",
});
}
}
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message: "Something went wrong",
});
}
});
}
async function createHour(day, time, ctx,) {
const newHour = (await ctx.db.insert(hours).values({
dayId: day.id,
time: time,
userId: ctx.user!.id,
}).returning());
console.log(newHour);
return newHour[0];
}
async function getTimezone(ctx: Context) {
const dbTimezone = await ctx.db.select({
timezone: users.timezone
}).from(users).where(eq(users.id, ctx.user!.id));
return dbTimezone[0].timezone as string;
}
async function getDay(input: { dateQuery: string }, ctx: Context, date: string) {
const dayRes = await ctx.db.select({
id: days.id,
date: days.date,
mood: days.mood,
comment: days.comment,
})
.from(days)
.where(eq(days.date, date));
const day = dayRes.length == 0
? (await createDay(date, ctx))[0]
: (dayRes[0]);
const dayHours = await Promise.all(
Array.from({ length: 24 }).map(async function (_, i) {
const existing = await ctx.db.select({
dayId: hours.dayId,
time: hours.time,
userId: hours.userId,
}).from(hours)
.where(and(
eq(hours.userId, ctx.user!.id),
eq(hours.dayId, day.id),
eq(hours.time, i)
))
return existing.length == 0 ? createHour(day, i, ctx) : existing[0];
}));
return {
hours: dayHours,
...day
}
}
export const daysAppRouter = router({
get: authedProcedure
.input(z.object({
dateQuery: z.string(),
}))
.output(zDaySchema)
.query(async ({ input, ctx }) => {
const timezone = await getTimezone(ctx);
const date = dateFromInput({
dateQuery: input.dateQuery,
timezone: timezone
});
const allHours = hoursListInTimezone({
timezone,
...input
});
const dayRange = [...new Set(allHours.map(({ date: date, time: _time }) => date))];
const allDayIds = await Promise.all(dayRange.map(async function (date) {
const dayObj = await getDay(input, ctx, date);
return {
id: dayObj.id,
date: dayObj.date
}
}));
const dayHours = await Promise.all(allHours.map(async function ({ date, time }) {
const dayId = allDayIds.find((_value: { id, date }) => date == date)!.id;
const hourMatch = await ctx.db.select().from(hours)
.where(and(
eq(hours.time, time),
eq(hours.dayId, dayId)));
// console.log("Search values:: ", `d: ${date}, t: ${time}, dayId: ${dayId}`)
// console.log("hourMatch", hourMatch[0]);
return hourMatch;
}));
return {
...await getDay(input, ctx, date),
hours: dayHours.flat(),
};
}),
update: authedProcedure
.input(
z.object({
mood: z.string().optional().or(z.number()),
comment: z.string().optional(),
dateQuery: z.string(),
}),
)
.mutation(async ({ input, ctx }) => {
const { dateQuery, ...updatedProps } = input;
// Convert mood to number, if it exists
if (updatedProps.mood) {
updatedProps.mood = parseInt(updatedProps.mood);
}
await ctx.db
.update(days)
.set(updatedProps)
.where(eq(days.date, dateFromInput({ dateQuery: dateQuery, timezone: ctx.user.timezone })));
}),
});