From 136f126296af65f50da598d084d1485c0e40437a Mon Sep 17 00:00:00 2001
From: Mohamed Bassem <me@mbassem.com>
Date: Sun, 27 Apr 2025 00:02:20 +0100
Subject: 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
---
 .../dashboard/rules/RuleEngineEventSelector.tsx    | 107 +++++++++++++++++++++
 1 file changed, 107 insertions(+)
 create mode 100644 apps/web/components/dashboard/rules/RuleEngineEventSelector.tsx

(limited to 'apps/web/components/dashboard/rules/RuleEngineEventSelector.tsx')

diff --git a/apps/web/components/dashboard/rules/RuleEngineEventSelector.tsx b/apps/web/components/dashboard/rules/RuleEngineEventSelector.tsx
new file mode 100644
index 00000000..ae37945e
--- /dev/null
+++ b/apps/web/components/dashboard/rules/RuleEngineEventSelector.tsx
@@ -0,0 +1,107 @@
+import { Card, CardContent } from "@/components/ui/card";
+import {
+  Select,
+  SelectContent,
+  SelectItem,
+  SelectTrigger,
+  SelectValue,
+} from "@/components/ui/select";
+import { useTranslation } from "react-i18next";
+
+import type { RuleEngineEvent } from "@karakeep/shared/types/rules";
+
+import { BookmarkListSelector } from "../lists/BookmarkListSelector";
+import { TagAutocomplete } from "../tags/TagAutocomplete";
+
+interface EventSelectorProps {
+  value: RuleEngineEvent;
+  onChange: (event: RuleEngineEvent) => void;
+}
+
+export function EventSelector({ value, onChange }: EventSelectorProps) {
+  const { t } = useTranslation();
+  const handleTypeChange = (type: RuleEngineEvent["type"]) => {
+    switch (type) {
+      case "bookmarkAdded":
+        onChange({ type: "bookmarkAdded" });
+        break;
+      case "tagAdded":
+        onChange({ type: "tagAdded", tagId: "" });
+        break;
+      case "tagRemoved":
+        onChange({ type: "tagRemoved", tagId: "" });
+        break;
+      case "addedToList":
+        onChange({ type: "addedToList", listId: "" });
+        break;
+      case "removedFromList":
+        onChange({ type: "removedFromList", listId: "" });
+        break;
+      case "favourited":
+        onChange({ type: "favourited" });
+        break;
+      case "archived":
+        onChange({ type: "archived" });
+        break;
+      default: {
+        const _exhaustiveCheck: never = type;
+        return null;
+      }
+    }
+  };
+
+  return (
+    <Card>
+      <CardContent className="p-4">
+        <div className="flex gap-4">
+          <Select value={value.type} onValueChange={handleTypeChange}>
+            <SelectTrigger id="event-type">
+              <SelectValue placeholder="Select event type" />
+            </SelectTrigger>
+            <SelectContent>
+              <SelectItem value="bookmarkAdded">
+                {t("settings.rules.events_types.bookmark_added")}
+              </SelectItem>
+              <SelectItem value="tagAdded">
+                {t("settings.rules.events_types.tag_added")}
+              </SelectItem>
+              <SelectItem value="tagRemoved">
+                {t("settings.rules.events_types.tag_removed")}
+              </SelectItem>
+              <SelectItem value="addedToList">
+                {t("settings.rules.events_types.added_to_list")}
+              </SelectItem>
+              <SelectItem value="removedFromList">
+                {t("settings.rules.events_types.removed_from_list")}
+              </SelectItem>
+              <SelectItem value="favourited">
+                {t("settings.rules.events_types.favourited")}
+              </SelectItem>
+              <SelectItem value="archived">
+                {t("settings.rules.events_types.archived")}
+              </SelectItem>
+            </SelectContent>
+          </Select>
+
+          {/* Additional fields based on event type */}
+          {(value.type === "tagAdded" || value.type === "tagRemoved") && (
+            <TagAutocomplete
+              className="w-full"
+              tagId={value.tagId}
+              onChange={(t) => onChange({ type: value.type, tagId: t ?? "" })}
+            />
+          )}
+
+          {(value.type === "addedToList" ||
+            value.type === "removedFromList") && (
+            <BookmarkListSelector
+              listTypes={["manual"]}
+              value={value.listId}
+              onChange={(l) => onChange({ type: value.type, listId: l })}
+            />
+          )}
+        </div>
+      </CardContent>
+    </Card>
+  );
+}
-- 
cgit v1.2.3-70-g09d2

