aboutsummaryrefslogtreecommitdiffstats
path: root/packages/db/schema.ts
diff options
context:
space:
mode:
authorMohamed Bassem <me@mbassem.com>2025-04-27 00:02:20 +0100
committerGitHub <noreply@github.com>2025-04-27 00:02:20 +0100
commit136f126296af65f50da598d084d1485c0e40437a (patch)
tree2725c7932ebbcb9b48b5af98eb9b72329a400260 /packages/db/schema.ts
parentca47be7fe7be128f459c37614a04902a873fe289 (diff)
downloadkarakeep-136f126296af65f50da598d084d1485c0e40437a.tar.zst
feat: Implement generic rule engine (#1318)
* Add schema for the new rule engine * Add rule engine backend logic * Implement the worker logic and event firing * Implement the UI changesfor the rule engine * Ensure that when a referenced list or tag are deleted, the corresponding event/action is * Dont show smart lists in rule engine events * Add privacy validations for attached tag and list ids * Move the rules logic into a models
Diffstat (limited to 'packages/db/schema.ts')
-rw-r--r--packages/db/schema.ts118
1 files changed, 117 insertions, 1 deletions
diff --git a/packages/db/schema.ts b/packages/db/schema.ts
index dd65370b..bedcf9ad 100644
--- a/packages/db/schema.ts
+++ b/packages/db/schema.ts
@@ -3,6 +3,7 @@ import { createId } from "@paralleldrive/cuid2";
import { relations } from "drizzle-orm";
import {
AnySQLiteColumn,
+ foreignKey,
index,
integer,
primaryKey,
@@ -283,6 +284,7 @@ export const bookmarkTags = sqliteTable(
},
(bt) => [
unique().on(bt.userId, bt.name),
+ unique("bookmarkTags_userId_id_idx").on(bt.userId, bt.id),
index("bookmarkTags_name_idx").on(bt.name),
index("bookmarkTags_userId_idx").on(bt.userId),
],
@@ -332,7 +334,10 @@ export const bookmarkLists = sqliteTable(
{ onDelete: "set null" },
),
},
- (bl) => [index("bookmarkLists_userId_idx").on(bl.userId)],
+ (bl) => [
+ index("bookmarkLists_userId_idx").on(bl.userId),
+ unique("bookmarkLists_userId_id_idx").on(bl.userId, bl.id),
+ ],
);
export const bookmarksInLists = sqliteTable(
@@ -444,12 +449,87 @@ export const config = sqliteTable("config", {
value: text("value").notNull(),
});
+export const ruleEngineRulesTable = sqliteTable(
+ "ruleEngineRules",
+ {
+ id: text("id")
+ .notNull()
+ .primaryKey()
+ .$defaultFn(() => createId()),
+ enabled: integer("enabled", { mode: "boolean" }).notNull().default(true),
+ name: text("name").notNull(),
+ description: text("description"),
+ event: text("event").notNull(),
+ condition: text("condition").notNull(),
+
+ // References
+ userId: text("userId")
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+
+ listId: text("listId"),
+ tagId: text("tagId"),
+ },
+ (rl) => [
+ index("ruleEngine_userId_idx").on(rl.userId),
+
+ // Ensures correct ownership
+ foreignKey({
+ columns: [rl.userId, rl.tagId],
+ foreignColumns: [bookmarkTags.userId, bookmarkTags.id],
+ name: "ruleEngineRules_userId_tagId_fk",
+ }).onDelete("cascade"),
+ foreignKey({
+ columns: [rl.userId, rl.listId],
+ foreignColumns: [bookmarkLists.userId, bookmarkLists.id],
+ name: "ruleEngineRules_userId_listId_fk",
+ }).onDelete("cascade"),
+ ],
+);
+
+export const ruleEngineActionsTable = sqliteTable(
+ "ruleEngineActions",
+ {
+ id: text("id")
+ .notNull()
+ .primaryKey()
+ .$defaultFn(() => createId()),
+ userId: text("userId")
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+ ruleId: text("ruleId")
+ .notNull()
+ .references(() => ruleEngineRulesTable.id, { onDelete: "cascade" }),
+ action: text("action").notNull(),
+
+ // References
+ listId: text("listId"),
+ tagId: text("tagId"),
+ },
+ (rl) => [
+ index("ruleEngineActions_userId_idx").on(rl.userId),
+ index("ruleEngineActions_ruleId_idx").on(rl.ruleId),
+ // Ensures correct ownership
+ foreignKey({
+ columns: [rl.userId, rl.tagId],
+ foreignColumns: [bookmarkTags.userId, bookmarkTags.id],
+ name: "ruleEngineActions_userId_tagId_fk",
+ }).onDelete("cascade"),
+ foreignKey({
+ columns: [rl.userId, rl.listId],
+ foreignColumns: [bookmarkLists.userId, bookmarkLists.id],
+ name: "ruleEngineActions_userId_listId_fk",
+ }).onDelete("cascade"),
+ ],
+);
+
// Relations
export const userRelations = relations(users, ({ many }) => ({
tags: many(bookmarkTags),
bookmarks: many(bookmarks),
webhooks: many(webhooksTable),
+ rules: many(ruleEngineRulesTable),
}));
export const bookmarkRelations = relations(bookmarks, ({ many, one }) => ({
@@ -472,6 +552,7 @@ export const bookmarkRelations = relations(bookmarks, ({ many, one }) => ({
tagsOnBookmarks: many(tagsOnBookmarks),
bookmarksInLists: many(bookmarksInLists),
assets: many(assets),
+ rssFeeds: many(rssFeedImportsTable),
}));
export const assetRelations = relations(assets, ({ one }) => ({
@@ -548,3 +629,38 @@ export const webhooksRelations = relations(webhooksTable, ({ one }) => ({
references: [users.id],
}),
}));
+
+export const ruleEngineRulesRelations = relations(
+ ruleEngineRulesTable,
+ ({ one, many }) => ({
+ user: one(users, {
+ fields: [ruleEngineRulesTable.userId],
+ references: [users.id],
+ }),
+ actions: many(ruleEngineActionsTable),
+ }),
+);
+
+export const ruleEngineActionsTableRelations = relations(
+ ruleEngineActionsTable,
+ ({ one }) => ({
+ rule: one(ruleEngineRulesTable, {
+ fields: [ruleEngineActionsTable.ruleId],
+ references: [ruleEngineRulesTable.id],
+ }),
+ }),
+);
+
+export const rssFeedImportsTableRelations = relations(
+ rssFeedImportsTable,
+ ({ one }) => ({
+ rssFeed: one(rssFeedsTable, {
+ fields: [rssFeedImportsTable.rssFeedId],
+ references: [rssFeedsTable.id],
+ }),
+ bookmark: one(bookmarks, {
+ fields: [rssFeedImportsTable.bookmarkId],
+ references: [bookmarks.id],
+ }),
+ }),
+);