aboutsummaryrefslogtreecommitdiffstats
path: root/packages/trpc/routers/feeds.ts
blob: a8025dfb6fa8cc8b504c96e8a37d7f74acdb411d (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
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,
      });
    }),
});