diff options
| author | Mohamed Bassem <me@mbassem.com> | 2025-08-31 13:09:45 +0000 |
|---|---|---|
| committer | Mohamed Bassem <me@mbassem.com> | 2025-08-31 16:52:21 +0000 |
| commit | 15efda6dfc71f4a5e593fba93d349236baee3ea4 (patch) | |
| tree | a64541ec2f6d1f58739ce5b118c4ec027908e055 /packages/trpc/models/feeds.ts | |
| parent | ce9a006aa9667cf1b9598a3e5cee0a7b0f900e2f (diff) | |
| download | karakeep-15efda6dfc71f4a5e593fba93d349236baee3ea4.tar.zst | |
refactor: Move feed object into models
Diffstat (limited to 'packages/trpc/models/feeds.ts')
| -rw-r--r-- | packages/trpc/models/feeds.ts | 119 |
1 files changed, 119 insertions, 0 deletions
diff --git a/packages/trpc/models/feeds.ts b/packages/trpc/models/feeds.ts new file mode 100644 index 00000000..eab1ad65 --- /dev/null +++ b/packages/trpc/models/feeds.ts @@ -0,0 +1,119 @@ +import { TRPCError } from "@trpc/server"; +import { and, eq } from "drizzle-orm"; +import { z } from "zod"; + +import { rssFeedsTable } from "@karakeep/db/schema"; +import { + zFeedSchema, + zNewFeedSchema, + zUpdateFeedSchema, +} from "@karakeep/shared/types/feeds"; + +import { AuthedContext } from ".."; +import { PrivacyAware } from "./privacy"; + +export class Feed implements PrivacyAware { + constructor( + protected ctx: AuthedContext, + private feed: typeof rssFeedsTable.$inferSelect, + ) {} + + static async fromId(ctx: AuthedContext, id: string): Promise<Feed> { + const feed = await ctx.db.query.rssFeedsTable.findFirst({ + where: eq(rssFeedsTable.id, id), + }); + + if (!feed) { + throw new TRPCError({ + code: "NOT_FOUND", + message: "Feed not found", + }); + } + + // If it exists but belongs to another user, throw forbidden error + if (feed.userId !== ctx.user.id) { + throw new TRPCError({ + code: "FORBIDDEN", + message: "User is not allowed to access resource", + }); + } + + return new Feed(ctx, feed); + } + + static async create( + ctx: AuthedContext, + input: z.infer<typeof zNewFeedSchema>, + ): Promise<Feed> { + const [result] = await ctx.db + .insert(rssFeedsTable) + .values({ + name: input.name, + url: input.url, + userId: ctx.user.id, + enabled: input.enabled, + }) + .returning(); + + return new Feed(ctx, result); + } + + static async getAll(ctx: AuthedContext): Promise<Feed[]> { + const feeds = await ctx.db.query.rssFeedsTable.findMany({ + where: eq(rssFeedsTable.userId, ctx.user.id), + }); + + return feeds.map((f) => new Feed(ctx, f)); + } + + ensureCanAccess(ctx: AuthedContext): void { + if (this.feed.userId !== ctx.user.id) { + throw new TRPCError({ + code: "FORBIDDEN", + message: "User is not allowed to access resource", + }); + } + } + + async delete(): Promise<void> { + const res = await this.ctx.db + .delete(rssFeedsTable) + .where( + and( + eq(rssFeedsTable.id, this.feed.id), + eq(rssFeedsTable.userId, this.ctx.user.id), + ), + ); + + if (res.changes === 0) { + throw new TRPCError({ code: "NOT_FOUND" }); + } + } + + async update(input: z.infer<typeof zUpdateFeedSchema>): Promise<void> { + const result = await this.ctx.db + .update(rssFeedsTable) + .set({ + name: input.name, + url: input.url, + enabled: input.enabled, + }) + .where( + and( + eq(rssFeedsTable.id, this.feed.id), + eq(rssFeedsTable.userId, this.ctx.user.id), + ), + ) + .returning(); + + if (result.length === 0) { + throw new TRPCError({ code: "NOT_FOUND" }); + } + + this.feed = result[0]; + } + + asPublicFeed(): z.infer<typeof zFeedSchema> { + return this.feed; + } +} |
