diff options
| author | Mohamed Bassem <me@mbassem.com> | 2024-11-03 17:09:47 +0000 |
|---|---|---|
| committer | Mohamed Bassem <me@mbassem.com> | 2024-11-03 17:09:47 +0000 |
| commit | cf1a25131fd45ab7c9a72b837be525c24457cd8b (patch) | |
| tree | cb8b3d4a57a4ce06e500e4c7ceea43924d64a5d4 /packages/trpc/routers | |
| parent | 2efc7c8c01866fafad5322fb8783d94821e32ff1 (diff) | |
| download | karakeep-cf1a25131fd45ab7c9a72b837be525c24457cd8b.tar.zst | |
feature: Add support for subscribing to RSS feeds. Fixes #202
Diffstat (limited to 'packages/trpc/routers')
| -rw-r--r-- | packages/trpc/routers/_app.ts | 2 | ||||
| -rw-r--r-- | packages/trpc/routers/feeds.ts | 121 |
2 files changed, 123 insertions, 0 deletions
diff --git a/packages/trpc/routers/_app.ts b/packages/trpc/routers/_app.ts index 01c92e6a..ea1e0ca8 100644 --- a/packages/trpc/routers/_app.ts +++ b/packages/trpc/routers/_app.ts @@ -2,6 +2,7 @@ import { router } from "../index"; import { adminAppRouter } from "./admin"; import { apiKeysAppRouter } from "./apiKeys"; import { bookmarksAppRouter } from "./bookmarks"; +import { feedsAppRouter } from "./feeds"; import { listsAppRouter } from "./lists"; import { promptsAppRouter } from "./prompts"; import { tagsAppRouter } from "./tags"; @@ -15,6 +16,7 @@ export const appRouter = router({ tags: tagsAppRouter, prompts: promptsAppRouter, admin: adminAppRouter, + feeds: feedsAppRouter, }); // export type definition of API export type AppRouter = typeof appRouter; diff --git a/packages/trpc/routers/feeds.ts b/packages/trpc/routers/feeds.ts new file mode 100644 index 00000000..a8025dfb --- /dev/null +++ b/packages/trpc/routers/feeds.ts @@ -0,0 +1,121 @@ +import { experimental_trpcMiddleware, TRPCError } from "@trpc/server"; +import { and, eq } from "drizzle-orm"; +import { z } from "zod"; + +import { rssFeedsTable } from "@hoarder/db/schema"; +import { FeedQueue } from "@hoarder/shared/queues"; +import { + zFeedSchema, + zNewFeedSchema, + zUpdateFeedSchema, +} from "@hoarder/shared/types/feeds"; + +import { authedProcedure, Context, router } from "../index"; + +export const ensureFeedOwnership = experimental_trpcMiddleware<{ + ctx: Context; + input: { feedId: string }; +}>().create(async (opts) => { + const feed = await opts.ctx.db.query.rssFeedsTable.findFirst({ + where: eq(rssFeedsTable.id, opts.input.feedId), + columns: { + userId: true, + }, + }); + if (!opts.ctx.user) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "User is not authorized", + }); + } + if (!feed) { + throw new TRPCError({ + code: "NOT_FOUND", + message: "Feed not found", + }); + } + if (feed.userId != opts.ctx.user.id) { + throw new TRPCError({ + code: "FORBIDDEN", + message: "User is not allowed to access resource", + }); + } + + return opts.next(); +}); + +export const feedsAppRouter = router({ + create: authedProcedure + .input(zNewFeedSchema) + .output(zFeedSchema) + .mutation(async ({ input, ctx }) => { + const [feed] = await ctx.db + .insert(rssFeedsTable) + .values({ + name: input.name, + url: input.url, + userId: ctx.user.id, + }) + .returning(); + return feed; + }), + update: authedProcedure + .input(zUpdateFeedSchema) + .output(zFeedSchema) + .use(ensureFeedOwnership) + .mutation(async ({ input, ctx }) => { + const feed = await ctx.db + .update(rssFeedsTable) + .set({ + name: input.name, + url: input.url, + }) + .where( + and( + eq(rssFeedsTable.userId, ctx.user.id), + eq(rssFeedsTable.id, input.feedId), + ), + ) + .returning(); + if (feed.length == 0) { + throw new TRPCError({ code: "NOT_FOUND" }); + } + return feed[0]; + }), + list: authedProcedure + .output(z.object({ feeds: z.array(zFeedSchema) })) + .query(async ({ ctx }) => { + const feeds = await ctx.db.query.rssFeedsTable.findMany({ + where: eq(rssFeedsTable.userId, ctx.user.id), + }); + return { feeds }; + }), + delete: authedProcedure + .input( + z.object({ + feedId: z.string(), + }), + ) + .use(ensureFeedOwnership) + .mutation(async ({ input, ctx }) => { + const res = await ctx.db + .delete(rssFeedsTable) + .where( + and( + eq(rssFeedsTable.userId, ctx.user.id), + eq(rssFeedsTable.id, input.feedId), + ), + ); + if (res.changes == 0) { + throw new TRPCError({ code: "NOT_FOUND" }); + } + }), + fetchNow: authedProcedure + .input(z.object({ feedId: z.string() })) + .use(ensureFeedOwnership) + .mutation(async ({ input }) => { + await FeedQueue.enqueue({ + feedId: input.feedId, + }); + }), +}); |
