aboutsummaryrefslogtreecommitdiffstats
path: root/packages/trpc
diff options
context:
space:
mode:
authorMohamedBassem <me@mbassem.com>2024-03-15 13:32:05 +0000
committerMohamedBassem <me@mbassem.com>2024-03-15 13:32:05 +0000
commita9e4ec0922b5da71aaae7f6a037f124c86b38edc (patch)
tree67d3470ef6ef2f7bbd6971614251697b10c172a1 /packages/trpc
parentaa8df79ab48c2e2d9f8b10fbdf4d71197f41303e (diff)
downloadkarakeep-a9e4ec0922b5da71aaae7f6a037f124c86b38edc.tar.zst
refactor: Move tag fetching to trpc to reuse in the mobile app
Diffstat (limited to 'packages/trpc')
-rw-r--r--packages/trpc/routers/_app.ts2
-rw-r--r--packages/trpc/routers/tags.ts99
2 files changed, 101 insertions, 0 deletions
diff --git a/packages/trpc/routers/_app.ts b/packages/trpc/routers/_app.ts
index 6e5dd91d..780fd76d 100644
--- a/packages/trpc/routers/_app.ts
+++ b/packages/trpc/routers/_app.ts
@@ -3,12 +3,14 @@ import { adminAppRouter } from "./admin";
import { apiKeysAppRouter } from "./apiKeys";
import { bookmarksAppRouter } from "./bookmarks";
import { listsAppRouter } from "./lists";
+import { tagsAppRouter } from "./tags";
import { usersAppRouter } from "./users";
export const appRouter = router({
bookmarks: bookmarksAppRouter,
apiKeys: apiKeysAppRouter,
users: usersAppRouter,
lists: listsAppRouter,
+ tags: tagsAppRouter,
admin: adminAppRouter,
});
// export type definition of API
diff --git a/packages/trpc/routers/tags.ts b/packages/trpc/routers/tags.ts
new file mode 100644
index 00000000..af11f34c
--- /dev/null
+++ b/packages/trpc/routers/tags.ts
@@ -0,0 +1,99 @@
+import { experimental_trpcMiddleware, TRPCError } from "@trpc/server";
+import { and, eq } from "drizzle-orm";
+import { z } from "zod";
+
+import { bookmarks, bookmarkTags, tagsOnBookmarks } from "@hoarder/db/schema";
+
+import type { Context } from "../index";
+import { authedProcedure, router } from "../index";
+
+function conditionFromInput(input: { tagName: string } | { tagId: string }) {
+ if ("tagName" in input) {
+ return eq(bookmarkTags.name, input.tagName);
+ } else {
+ return eq(bookmarkTags.id, input.tagId);
+ }
+}
+
+const ensureTagOwnership = experimental_trpcMiddleware<{
+ ctx: Context;
+ input: { tagName: string } | { tagId: string };
+}>().create(async (opts) => {
+ const tag = await opts.ctx.db.query.bookmarkTags.findFirst({
+ where: conditionFromInput(opts.input),
+ columns: {
+ userId: true,
+ },
+ });
+ if (!opts.ctx.user) {
+ throw new TRPCError({
+ code: "UNAUTHORIZED",
+ message: "User is not authorized",
+ });
+ }
+ if (!tag) {
+ throw new TRPCError({
+ code: "NOT_FOUND",
+ message: "Tag not found",
+ });
+ }
+ if (tag.userId != opts.ctx.user.id) {
+ throw new TRPCError({
+ code: "FORBIDDEN",
+ message: "User is not allowed to access resource",
+ });
+ }
+
+ return opts.next();
+});
+
+export const tagsAppRouter = router({
+ get: authedProcedure
+ .input(
+ z
+ .object({
+ tagId: z.string(),
+ })
+ .or(
+ z.object({
+ tagName: z.string(),
+ }),
+ ),
+ )
+ .output(
+ z.object({
+ id: z.string(),
+ name: z.string(),
+ bookmarks: z.array(z.string()),
+ }),
+ )
+ .use(ensureTagOwnership)
+ .query(async ({ input, ctx }) => {
+ const res = await ctx.db
+ .select({
+ id: bookmarkTags.id,
+ name: bookmarkTags.name,
+ bookmarkId: bookmarks.id,
+ })
+ .from(bookmarkTags)
+ .leftJoin(tagsOnBookmarks, eq(bookmarkTags.id, tagsOnBookmarks.tagId))
+ .leftJoin(bookmarks, eq(tagsOnBookmarks.bookmarkId, bookmarks.id))
+ .where(
+ and(
+ conditionFromInput(input),
+ eq(bookmarkTags.userId, ctx.user.id),
+ eq(bookmarks.archived, false),
+ ),
+ );
+
+ if (res.length == 0) {
+ throw new TRPCError({ code: "NOT_FOUND" });
+ }
+
+ return {
+ id: res[0].id,
+ name: res[0].name,
+ bookmarks: res.flatMap((t) => t.bookmarkId ? [t.bookmarkId] : []),
+ };
+ }),
+});