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),
};
}),
});
|