lifetracker/packages/trpc/routers/categories.ts

267 lines
8.3 KiB
TypeScript

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<typeof zCategorySchema>,
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<ZCategorizedByEnum, number>
>(
(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" });
}
}),
});