aboutsummaryrefslogtreecommitdiffstats
path: root/packages/trpc/routers/rules.ts
blob: 5def8003dcf1bc31c3ae9bed8e79ac3f2f78e0a9 (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
import { experimental_trpcMiddleware, TRPCError } from "@trpc/server";
import { and, eq, inArray } from "drizzle-orm";
import { z } from "zod";

import { bookmarkTags } from "@karakeep/db/schema";
import {
  RuleEngineRule,
  zNewRuleEngineRuleSchema,
  zRuleEngineRuleSchema,
  zUpdateRuleEngineRuleSchema,
} from "@karakeep/shared/types/rules";

import { AuthedContext, authedProcedure, router } from "../index";
import { List } from "../models/lists";
import { RuleEngineRuleModel } from "../models/rules";

const ensureRuleOwnership = experimental_trpcMiddleware<{
  ctx: AuthedContext;
  input: { id: string };
}>().create(async (opts) => {
  const rule = await RuleEngineRuleModel.fromId(opts.ctx, opts.input.id);
  return opts.next({
    ctx: {
      ...opts.ctx,
      rule,
    },
  });
});

const ensureTagListOwnership = experimental_trpcMiddleware<{
  ctx: AuthedContext;
  input: Omit<RuleEngineRule, "id">;
}>().create(async (opts) => {
  const tagIds = [
    ...(opts.input.event.type === "tagAdded" ||
    opts.input.event.type === "tagRemoved"
      ? [opts.input.event.tagId]
      : []),
    ...(opts.input.condition.type === "hasTag"
      ? [opts.input.condition.tagId]
      : []),
    ...opts.input.actions.flatMap((a) =>
      a.type == "addTag" || a.type == "removeTag" ? [a.tagId] : [],
    ),
  ];

  const validateTags = async () => {
    if (tagIds.length == 0) {
      return;
    }
    const userTags = await opts.ctx.db.query.bookmarkTags.findMany({
      where: and(
        eq(bookmarkTags.userId, opts.ctx.user.id),
        inArray(bookmarkTags.id, tagIds),
      ),
      columns: {
        id: true,
      },
    });
    if (tagIds.some((t) => userTags.find((u) => u.id == t) == null)) {
      throw new TRPCError({
        code: "NOT_FOUND",
        message: "Tag not found",
      });
    }
  };

  const listIds = [
    ...(opts.input.event.type === "addedToList" ||
    opts.input.event.type === "removedFromList"
      ? [opts.input.event.listId]
      : []),
    ...opts.input.actions.flatMap((a) =>
      a.type == "addToList" || a.type == "removeFromList" ? [a.listId] : [],
    ),
  ];

  const [_tags, _lists] = await Promise.all([
    validateTags(),
    Promise.all(listIds.map((l) => List.fromId(opts.ctx, l))),
  ]);
  return opts.next();
});

export const rulesAppRouter = router({
  create: authedProcedure
    .input(zNewRuleEngineRuleSchema)
    .output(zRuleEngineRuleSchema)
    .use(ensureTagListOwnership)
    .mutation(async ({ input, ctx }) => {
      const newRule = await RuleEngineRuleModel.create(ctx, input);
      return newRule.rule;
    }),
  update: authedProcedure
    .input(zUpdateRuleEngineRuleSchema)
    .output(zRuleEngineRuleSchema)
    .use(ensureRuleOwnership)
    .use(ensureTagListOwnership)
    .mutation(async ({ ctx, input }) => {
      await ctx.rule.update(input);
      return ctx.rule.rule;
    }),
  delete: authedProcedure
    .input(z.object({ id: z.string() }))
    .use(ensureRuleOwnership)
    .mutation(async ({ ctx }) => {
      await ctx.rule.delete();
    }),
  list: authedProcedure
    .output(
      z.object({
        rules: z.array(zRuleEngineRuleSchema),
      }),
    )
    .query(async ({ ctx }) => {
      return {
        rules: (await RuleEngineRuleModel.getAll(ctx)).map((r) => r.rule),
      };
    }),
});