Compare commits
3 Commits
9d3e7ad44a
...
8ff17c552b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8ff17c552b | ||
|
|
a958008add | ||
|
|
a36268302c |
@@ -64,9 +64,14 @@ const Footer = () => {
|
|||||||
|
|
||||||
{/* Bottom Bar */}
|
{/* Bottom Bar */}
|
||||||
<div className="mt-10 sm:mt-16 md:mt-20 pt-6 sm:pt-8 border-t border-stone-100 dark:border-stone-900 flex flex-col md:flex-row justify-between items-center gap-4">
|
<div className="mt-10 sm:mt-16 md:mt-20 pt-6 sm:pt-8 border-t border-stone-100 dark:border-stone-900 flex flex-col md:flex-row justify-between items-center gap-4">
|
||||||
<p className="text-[10px] font-bold text-stone-600 dark:text-stone-400 uppercase tracking-widest">
|
<div className="flex flex-col gap-1">
|
||||||
Built with Next.js, Directus & Passion.
|
<p className="text-[10px] font-bold text-stone-600 dark:text-stone-400 uppercase tracking-widest">
|
||||||
</p>
|
Built with Next.js, Directus & Passion.
|
||||||
|
</p>
|
||||||
|
<p className="text-[10px] text-stone-400 dark:text-stone-600 tracking-wide">
|
||||||
|
{t("aiDisclaimer")}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<div className="w-2 h-2 rounded-full bg-green-500 animate-pulse" />
|
<div className="w-2 h-2 rounded-full bg-green-500 animate-pulse" />
|
||||||
<span className="text-[10px] font-bold text-stone-600 dark:text-stone-400 uppercase tracking-widest">Systems Online</span>
|
<span className="text-[10px] font-bold text-stone-600 dark:text-stone-400 uppercase tracking-widest">Systems Online</span>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { motion, AnimatePresence } from "framer-motion";
|
import { motion } from "framer-motion";
|
||||||
import { BookCheck, Star, ChevronDown, ChevronUp, X } from "lucide-react";
|
import { BookCheck, Star, ChevronDown, ChevronUp } from "lucide-react";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useLocale, useTranslations } from "next-intl";
|
import { useLocale, useTranslations } from "next-intl";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
@@ -48,7 +48,6 @@ const ReadBooks = () => {
|
|||||||
const [reviews, setReviews] = useState<BookReview[]>([]);
|
const [reviews, setReviews] = useState<BookReview[]>([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [expanded, setExpanded] = useState(false);
|
const [expanded, setExpanded] = useState(false);
|
||||||
const [selectedReview, setSelectedReview] = useState<BookReview | null>(null);
|
|
||||||
|
|
||||||
const INITIAL_SHOW = 3;
|
const INITIAL_SHOW = 3;
|
||||||
|
|
||||||
@@ -199,17 +198,9 @@ const ReadBooks = () => {
|
|||||||
|
|
||||||
{/* Review Text (Optional) */}
|
{/* Review Text (Optional) */}
|
||||||
{review.review && (
|
{review.review && (
|
||||||
<div>
|
<p className="text-sm text-stone-700 dark:text-stone-300 leading-relaxed italic">
|
||||||
<p className="text-sm text-stone-700 dark:text-stone-300 leading-relaxed line-clamp-3 italic">
|
“{stripHtml(review.review)}”
|
||||||
“{stripHtml(review.review)}”
|
</p>
|
||||||
</p>
|
|
||||||
<button
|
|
||||||
onClick={() => setSelectedReview(review)}
|
|
||||||
className="text-xs text-liquid-mint dark:text-liquid-sky hover:underline mt-1 font-medium"
|
|
||||||
>
|
|
||||||
{t("readMore", { defaultValue: "Read full review" })}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Finished Date */}
|
{/* Finished Date */}
|
||||||
@@ -249,130 +240,7 @@ const ReadBooks = () => {
|
|||||||
</motion.button>
|
</motion.button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Modal for full review */}
|
|
||||||
<AnimatePresence>
|
|
||||||
{selectedReview && (
|
|
||||||
<>
|
|
||||||
{/* Backdrop */}
|
|
||||||
<motion.div
|
|
||||||
initial={{ opacity: 0 }}
|
|
||||||
animate={{ opacity: 1 }}
|
|
||||||
exit={{ opacity: 0 }}
|
|
||||||
onClick={() => setSelectedReview(null)}
|
|
||||||
className="fixed inset-0 bg-black/70 backdrop-blur-md z-50"
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Modal */}
|
|
||||||
<motion.div
|
|
||||||
initial={{ opacity: 0, scale: 0.95, y: 40 }}
|
|
||||||
animate={{ opacity: 1, scale: 1, y: 0 }}
|
|
||||||
exit={{ opacity: 0, scale: 0.95, y: 40 }}
|
|
||||||
transition={{ type: "spring", damping: 30, stiffness: 400 }}
|
|
||||||
className="fixed inset-x-4 bottom-4 top-20 sm:inset-auto sm:left-1/2 sm:top-1/2 sm:-translate-x-1/2 sm:-translate-y-1/2 sm:w-full sm:max-w-3xl sm:max-h-[85vh] z-50 bg-gradient-to-br from-white via-liquid-sky/5 to-liquid-mint/10 dark:from-stone-900 dark:via-stone-900 dark:to-stone-800 rounded-3xl shadow-2xl border-2 border-liquid-mint/30 dark:border-stone-700 overflow-hidden"
|
|
||||||
>
|
|
||||||
{/* Decorative blob */}
|
|
||||||
<div className="absolute -top-20 -right-20 w-64 h-64 bg-gradient-to-br from-liquid-mint/20 to-liquid-sky/20 dark:from-stone-700/30 dark:to-stone-600/30 rounded-full blur-3xl pointer-events-none" />
|
|
||||||
|
|
||||||
{/* Close button */}
|
|
||||||
<button
|
|
||||||
onClick={() => setSelectedReview(null)}
|
|
||||||
className="absolute top-4 right-4 p-2.5 rounded-full bg-white/80 dark:bg-stone-800/80 backdrop-blur-sm hover:bg-white dark:hover:bg-stone-700 transition-all duration-200 z-10 shadow-lg border border-stone-200 dark:border-stone-600"
|
|
||||||
aria-label="Close"
|
|
||||||
>
|
|
||||||
<X size={20} className="text-stone-600 dark:text-stone-300" />
|
|
||||||
</button>
|
|
||||||
|
|
||||||
{/* Content */}
|
|
||||||
<div className="relative h-full overflow-y-auto overscroll-contain p-6 sm:p-8 md:p-10">
|
|
||||||
<div className="flex flex-col sm:flex-row gap-6 mb-6">
|
|
||||||
{/* Book Cover */}
|
|
||||||
{selectedReview.book_image && (
|
|
||||||
<motion.div
|
|
||||||
initial={{ opacity: 0, x: -20 }}
|
|
||||||
animate={{ opacity: 1, x: 0 }}
|
|
||||||
transition={{ delay: 0.1 }}
|
|
||||||
className="flex-shrink-0 mx-auto sm:mx-0"
|
|
||||||
>
|
|
||||||
<div className="relative w-32 h-48 sm:w-36 sm:h-52 rounded-xl overflow-hidden shadow-2xl border-2 border-white/50 dark:border-stone-700">
|
|
||||||
<Image
|
|
||||||
src={selectedReview.book_image}
|
|
||||||
alt={selectedReview.book_title}
|
|
||||||
fill
|
|
||||||
className="object-cover"
|
|
||||||
sizes="(max-width: 640px) 128px, 144px"
|
|
||||||
priority
|
|
||||||
/>
|
|
||||||
<div className="absolute inset-0 bg-gradient-to-tr from-white/20 via-transparent to-white/10 pointer-events-none" />
|
|
||||||
</div>
|
|
||||||
</motion.div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Book Info */}
|
|
||||||
<motion.div
|
|
||||||
initial={{ opacity: 0, y: 10 }}
|
|
||||||
animate={{ opacity: 1, y: 0 }}
|
|
||||||
transition={{ delay: 0.15 }}
|
|
||||||
className="flex-1 min-w-0"
|
|
||||||
>
|
|
||||||
<h2 className="text-2xl sm:text-3xl font-bold text-stone-900 dark:text-stone-100 mb-2 leading-tight">
|
|
||||||
{selectedReview.book_title}
|
|
||||||
</h2>
|
|
||||||
<p className="text-base sm:text-lg text-stone-600 dark:text-stone-400 mb-4">
|
|
||||||
{selectedReview.book_author}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
{selectedReview.rating && selectedReview.rating > 0 && (
|
|
||||||
<div className="flex items-center gap-3 mb-4">
|
|
||||||
<div className="flex gap-1">
|
|
||||||
{[1, 2, 3, 4, 5].map((star) => (
|
|
||||||
<Star
|
|
||||||
key={star}
|
|
||||||
size={18}
|
|
||||||
className={
|
|
||||||
star <= selectedReview.rating!
|
|
||||||
? "text-amber-500 fill-amber-500"
|
|
||||||
: "text-stone-300 dark:text-stone-600"
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<span className="text-base text-stone-600 dark:text-stone-400 font-semibold">
|
|
||||||
{selectedReview.rating}/5
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{selectedReview.finished_at && (
|
|
||||||
<p className="text-sm text-stone-500 dark:text-stone-400 flex items-center gap-2">
|
|
||||||
<BookCheck size={14} className="opacity-60" />
|
|
||||||
{t("finishedAt")}{" "}
|
|
||||||
{new Date(selectedReview.finished_at).toLocaleDateString(
|
|
||||||
locale === "de" ? "de-DE" : "en-US",
|
|
||||||
{ year: "numeric", month: "long", day: "numeric" }
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</motion.div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Full Review */}
|
|
||||||
{selectedReview.review && (
|
|
||||||
<motion.div
|
|
||||||
initial={{ opacity: 0, y: 20 }}
|
|
||||||
animate={{ opacity: 1, y: 0 }}
|
|
||||||
transition={{ delay: 0.2 }}
|
|
||||||
className="bg-gradient-to-br from-liquid-mint/10 via-liquid-sky/5 to-transparent dark:from-stone-800/50 dark:via-stone-800/30 dark:to-transparent rounded-2xl p-6 border-l-4 border-liquid-mint dark:border-liquid-sky"
|
|
||||||
>
|
|
||||||
<p className="text-base sm:text-lg text-stone-700 dark:text-stone-300 leading-relaxed italic">
|
|
||||||
“{stripHtml(selectedReview.review)}”
|
|
||||||
</p>
|
|
||||||
</motion.div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</motion.div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</AnimatePresence>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -157,6 +157,7 @@
|
|||||||
"privacyPolicy": "Datenschutz",
|
"privacyPolicy": "Datenschutz",
|
||||||
"privacySettings": "Datenschutz-Einstellungen",
|
"privacySettings": "Datenschutz-Einstellungen",
|
||||||
"privacySettingsTitle": "Datenschutz-Banner wieder anzeigen",
|
"privacySettingsTitle": "Datenschutz-Banner wieder anzeigen",
|
||||||
"builtWith": "Built with"
|
"builtWith": "Built with",
|
||||||
|
"aiDisclaimer": "Einige Inhalte dieser Seite können KI-generiert sein."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -160,7 +160,8 @@
|
|||||||
"privacyPolicy": "Privacy policy",
|
"privacyPolicy": "Privacy policy",
|
||||||
"privacySettings": "Privacy settings",
|
"privacySettings": "Privacy settings",
|
||||||
"privacySettingsTitle": "Show privacy settings banner again",
|
"privacySettingsTitle": "Show privacy settings banner again",
|
||||||
"builtWith": "Built with"
|
"builtWith": "Built with",
|
||||||
|
"aiDisclaimer": "Some content on this site may be AI-assisted."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
219
n8n-workflows/book-review.json
Normal file
219
n8n-workflows/book-review.json
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
{
|
||||||
|
"name": "Book Review",
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"rule": {
|
||||||
|
"interval": [
|
||||||
|
{
|
||||||
|
"triggerAtHour": 19
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "n8n-nodes-base.scheduleTrigger",
|
||||||
|
"typeVersion": 1.3,
|
||||||
|
"position": [
|
||||||
|
0,
|
||||||
|
-192
|
||||||
|
],
|
||||||
|
"id": "f0c86dde-aa19-4440-b17c-c572b582da5e",
|
||||||
|
"name": "Schedule Trigger"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"method": "POST",
|
||||||
|
"url": "https://api.hardcover.app/v1/graphql",
|
||||||
|
"authentication": "predefinedCredentialType",
|
||||||
|
"nodeCredentialType": "httpBearerAuth",
|
||||||
|
"sendHeaders": true,
|
||||||
|
"headerParameters": {
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "content-type",
|
||||||
|
"value": "application/json"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"sendBody": true,
|
||||||
|
"bodyParameters": {
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "query",
|
||||||
|
"value": "query GetFinishedBooks { me { user_books(where: {status_id: {_eq: 3}}, limit: 5) { book { id title contributions { author { name } } images { url } } last_read_date updated_at } } }"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
"type": "n8n-nodes-base.httpRequest",
|
||||||
|
"typeVersion": 4.4,
|
||||||
|
"position": [
|
||||||
|
224,
|
||||||
|
-192
|
||||||
|
],
|
||||||
|
"id": "e5c28f64-29ed-40ae-804e-896c10f3bc58",
|
||||||
|
"name": "HTTP Request",
|
||||||
|
"credentials": {
|
||||||
|
"httpBearerAuth": {
|
||||||
|
"id": "Kmf2fBCFkuRuWWZa",
|
||||||
|
"name": "Hardcover"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"jsCode": "const responseData = $input.first().json;\nconst meData = responseData?.data?.me;\nconst userBooks =\n (Array.isArray(meData) && meData[0]?.user_books) || meData?.user_books || [];\n\nconst newBooks = [];\n\nfor (const ub of userBooks) {\n const check = await this.helpers.httpRequest({\n method: \"GET\",\n url:\n \"https://cms.dk0.dev/items/book_reviews?filter[hardcover_id][_eq]=\" +\n ub.book.id +\n \"&fields=id,translations.id&limit=1\",\n headers: {\n Authorization: \"Bearer RF2QytqhcLXuVy6FO3PzWlsoR-ysCTwB\",\n },\n });\n\n const existing = check.data?.[0];\n const hasReview =\n existing && existing.translations && existing.translations.length > 0;\n\n if (!hasReview) {\n newBooks.push({\n json: {\n hardcover_id: String(ub.book.id),\n directus_id: existing ? existing.id : null,\n title: ub.book.title,\n author: ub.book.contributions?.[0]?.author?.name ?? \"Unknown\",\n image: ub.book.images?.[0]?.url ?? null,\n finished_at: ub.last_read_date ?? ub.updated_at ?? null,\n already_in_directus: !!existing,\n },\n });\n }\n}\n\nreturn newBooks.length > 0 ? newBooks[0] : [{ json: { skip: true } }];\n"
|
||||||
|
},
|
||||||
|
"type": "n8n-nodes-base.code",
|
||||||
|
"typeVersion": 2,
|
||||||
|
"position": [
|
||||||
|
448,
|
||||||
|
-192
|
||||||
|
],
|
||||||
|
"id": "60380362-e954-40ee-b0d0-7bc1edbaf9d3",
|
||||||
|
"name": "Filter books"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"conditions": {
|
||||||
|
"options": {
|
||||||
|
"caseSensitive": true,
|
||||||
|
"leftValue": "",
|
||||||
|
"typeValidation": "strict",
|
||||||
|
"version": 3
|
||||||
|
},
|
||||||
|
"conditions": [
|
||||||
|
{
|
||||||
|
"id": "b356ade3-5cf0-40dd-bb47-e977f354e803",
|
||||||
|
"leftValue": "={{ $json.skip }}",
|
||||||
|
"rightValue": "={{ $json.skip }}",
|
||||||
|
"operator": {
|
||||||
|
"type": "boolean",
|
||||||
|
"operation": "true",
|
||||||
|
"singleValue": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"combinator": "and"
|
||||||
|
},
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
"type": "n8n-nodes-base.if",
|
||||||
|
"typeVersion": 2.3,
|
||||||
|
"position": [
|
||||||
|
672,
|
||||||
|
-192
|
||||||
|
],
|
||||||
|
"id": "45f65c65-ae6a-46b0-9d96-46f0a32e59db",
|
||||||
|
"name": "If"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"jsCode": "const book = $input.first().json;\nif (book.skip) return [{ json: { skip: true } }];\n\nconst parts = [];\nparts.push(\"Du hilfst jemandem eine Buchbewertung zu schreiben.\");\nparts.push(\"Das Buch ist \" + book.title + \" von \" + book.author + \".\");\nparts.push(\"Erstelle 4 kurze spezifische Fragen zum Buch.\");\nparts.push(\"Die Fragen sollen helfen eine Review zu schreiben.\");\nparts.push(\"Frage auf Deutsch.\");\nparts.push(\"Antworte NUR als JSON Array mit 4 Strings. Verwende keine Bindestriche, Em-Dashes oder Gedankenstriche (–, —, -).\");\nconst prompt = parts.join(\" \");\n\nconst aiResponse = await this.helpers.httpRequest({\n method: \"POST\",\n url: \"https://openrouter.ai/api/v1/chat/completions\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: \"Bearer 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\",\n },\n body: {\n model: \"openrouter/free\",\n messages: [{ role: \"user\", content: prompt }],\n },\n});\n\nconst aiText = aiResponse.choices?.[0]?.message?.content ?? \"[]\";\nconst match = aiText.match(/\\[[\\s\\S]*\\]/);\n\nconst f1 = \"Wie hat dir das Buch gefallen?\";\nconst f2 = \"Was war der beste Teil?\";\nconst f3 = \"Was hast du mitgenommen?\";\nconst f4 = \"Wem empfiehlst du es?\";\nconst fallback = [f1, f2, f3, f4];\n\nconst questions = match ? JSON.parse(match[0]) : fallback;\n\nreturn [{ json: { ...book, questions } }];\n"
|
||||||
|
},
|
||||||
|
"type": "n8n-nodes-base.code",
|
||||||
|
"typeVersion": 2,
|
||||||
|
"position": [
|
||||||
|
896,
|
||||||
|
-192
|
||||||
|
],
|
||||||
|
"id": "b56ab681-90d8-4376-9408-dc3302ab55bd",
|
||||||
|
"name": "ai"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"chatId": "145931600",
|
||||||
|
"text": "={{ '📚 ' + $json.title + ' von ' + $json.author + '\\n\\nBeantworte bitte:\\n\\n1. ' + $json.questions[0] + '\\n2. ' + $json.questions[1] + '\\n3. ' + $json.questions[2] + '\\n4. ' + $json.questions[3] + '\\n\\n⭐ Bewertung (1-5)?\\n\\nAntworte so (kopiere und ergänze):\\n\\n/review' + $json.hardcover_id + ' Hier deine Antworten als Text' }}",
|
||||||
|
"additionalFields": {}
|
||||||
|
},
|
||||||
|
"type": "n8n-nodes-base.telegram",
|
||||||
|
"typeVersion": 1.2,
|
||||||
|
"position": [
|
||||||
|
1136,
|
||||||
|
-208
|
||||||
|
],
|
||||||
|
"id": "13087afe-8a1d-457f-a1f1-e0aa64fc0e26",
|
||||||
|
"name": "Send a text message",
|
||||||
|
"webhookId": "eaa44b55-b3b1-4747-9b6a-dfc920910b4b",
|
||||||
|
"credentials": {
|
||||||
|
"telegramApi": {
|
||||||
|
"id": "ADurvy9EKUDzbDdq",
|
||||||
|
"name": "DK0_Server"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"pinData": {},
|
||||||
|
"connections": {
|
||||||
|
"Schedule Trigger": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "HTTP Request",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"HTTP Request": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Filter books",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Filter books": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "If",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"If": {
|
||||||
|
"main": [
|
||||||
|
[],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "ai",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"ai": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Send a text message",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"active": true,
|
||||||
|
"settings": {
|
||||||
|
"executionOrder": "v1",
|
||||||
|
"binaryMode": "separate",
|
||||||
|
"availableInMCP": false
|
||||||
|
},
|
||||||
|
"versionId": "4c605d70-0428-4611-9ad8-d9452c2660a7",
|
||||||
|
"meta": {
|
||||||
|
"templateCredsSetupCompleted": true,
|
||||||
|
"instanceId": "cb28e4db755465d5826da179e87f69603d81f833414cc52c327be9183a217b8d"
|
||||||
|
},
|
||||||
|
"id": "FDQ5Qmk9POy4Ajdd",
|
||||||
|
"tags": []
|
||||||
|
}
|
||||||
141
n8n-workflows/currently-reading.json
Normal file
141
n8n-workflows/currently-reading.json
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
{
|
||||||
|
"name": "reading",
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"path": "/hardcover/currently-reading",
|
||||||
|
"responseMode": "responseNode",
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
"type": "n8n-nodes-base.webhook",
|
||||||
|
"typeVersion": 2.1,
|
||||||
|
"position": [
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"id": "3e611a99-cbf7-48a6-b75b-f136ac76055f",
|
||||||
|
"name": "Webhook",
|
||||||
|
"webhookId": "02c226fd-2d1a-450c-9941-ff438dc5c987"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"method": "POST",
|
||||||
|
"url": "https://api.hardcover.app/v1/graphql",
|
||||||
|
"authentication": "genericCredentialType",
|
||||||
|
"genericAuthType": "httpBearerAuth",
|
||||||
|
"sendQuery": true,
|
||||||
|
"queryParameters": {
|
||||||
|
"parameters": [
|
||||||
|
{}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"sendHeaders": true,
|
||||||
|
"headerParameters": {
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "content-type",
|
||||||
|
"value": "application/json"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"sendBody": true,
|
||||||
|
"bodyParameters": {
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "query",
|
||||||
|
"value": "query GetCurrentlyReading { me { user_books(where: {status_id: {_eq: 2}}) { user_book_reads(limit: 1, order_by: {started_at: desc}) { progress } edition { title image { url } book { contributions { author { name } } } } } } }"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
"type": "n8n-nodes-base.httpRequest",
|
||||||
|
"typeVersion": 4.3,
|
||||||
|
"position": [
|
||||||
|
288,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"id": "b2a74fcb-93a9-4a28-905f-076a51a80a98",
|
||||||
|
"name": "HTTP Request",
|
||||||
|
"credentials": {
|
||||||
|
"httpBearerAuth": {
|
||||||
|
"id": "Kmf2fBCFkuRuWWZa",
|
||||||
|
"name": "Hardcover"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"jsCode": "// Hardcover API Response kommt als GraphQL Response\n// Die Response ist ein Array: [{ data: { me: [{ user_books: [...] }] } }]\nconst graphqlResponse = $input.all()[0].json;\n\n// Extrahiere die Daten - Response-Struktur: [{ data: { me: [{ user_books: [...] }] } }]\nconst responseData = Array.isArray(graphqlResponse) ? graphqlResponse[0] : graphqlResponse;\nconst meData = responseData?.data?.me;\nconst userBooks = (Array.isArray(meData) && meData[0]?.user_books) || meData?.user_books || [];\n\nif (!userBooks || userBooks.length === 0) {\n return {\n json: {\n currentlyReading: null\n }\n };\n}\n\n// Sortiere nach Fortschritt, falls mehrere Bücher vorhanden sind\nconst sortedBooks = userBooks.sort((a, b) => {\n const progressA = a.user_book_reads?.[0]?.progress || 0;\n const progressB = b.user_book_reads?.[0]?.progress || 0;\n return progressB - progressA; // Höchster zuerst\n});\n\n// Formatiere alle Bücher\nconst formattedBooks = sortedBooks.map(book => {\n const edition = book.edition || {};\n const bookData = edition.book || {};\n const contributions = bookData.contributions || [];\n const authors = contributions\n .filter(c => c.author && c.author.name)\n .map(c => c.author.name);\n \n const readData = book.user_book_reads?.[0] || {};\n const progress = readData.progress || 0;\n const image = edition.image?.url || null;\n\n return {\n title: edition.title || 'Unknown Title',\n authors: authors.length > 0 ? authors : ['Unknown Author'],\n image: image,\n progress: Math.round(progress) || 0, // Progress ist bereits in Prozent (z.B. 65.75)\n startedAt: readData.started_at || null,\n };\n});\n\n// Gib alle Bücher zurück\nreturn {\n json: {\n currentlyReading: formattedBooks.length > 0 ? formattedBooks : null\n }\n};"
|
||||||
|
},
|
||||||
|
"type": "n8n-nodes-base.code",
|
||||||
|
"typeVersion": 2,
|
||||||
|
"position": [
|
||||||
|
592,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"id": "eff96166-8be2-4ece-b338-2b4dec1ee26a",
|
||||||
|
"name": "Code in JavaScript"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
"type": "n8n-nodes-base.respondToWebhook",
|
||||||
|
"typeVersion": 1.5,
|
||||||
|
"position": [
|
||||||
|
944,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"id": "80c59480-69db-4ecb-80f4-ddeec2be8376",
|
||||||
|
"name": "Respond to Webhook"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"pinData": {},
|
||||||
|
"connections": {
|
||||||
|
"Webhook": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "HTTP Request",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"HTTP Request": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Code in JavaScript",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Code in JavaScript": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Respond to Webhook",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"active": true,
|
||||||
|
"settings": {
|
||||||
|
"executionOrder": "v1",
|
||||||
|
"availableInMCP": false
|
||||||
|
},
|
||||||
|
"versionId": "63a2c985-4b40-44ca-a40d-e7048ac5619b",
|
||||||
|
"meta": {
|
||||||
|
"instanceId": "cb28e4db755465d5826da179e87f69603d81f833414cc52c327be9183a217b8d"
|
||||||
|
},
|
||||||
|
"id": "P2itbbCCQVa0C0HTIVGvy",
|
||||||
|
"tags": []
|
||||||
|
}
|
||||||
418
n8n-workflows/docker-callback-handler.json
Normal file
418
n8n-workflows/docker-callback-handler.json
Normal file
@@ -0,0 +1,418 @@
|
|||||||
|
{
|
||||||
|
"name": "Docker Event - Callback Handler",
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"updates": [
|
||||||
|
"callback_query"
|
||||||
|
],
|
||||||
|
"additionalFields": {}
|
||||||
|
},
|
||||||
|
"type": "n8n-nodes-base.telegramTrigger",
|
||||||
|
"typeVersion": 1.2,
|
||||||
|
"position": [
|
||||||
|
-880,
|
||||||
|
288
|
||||||
|
],
|
||||||
|
"id": "a56a5174-3ccf-492f-810b-117be933560c",
|
||||||
|
"name": "Telegram Trigger",
|
||||||
|
"webhookId": "6e70b9ab-b76b-48dc-8e4d-5fe1bf0d7e39",
|
||||||
|
"credentials": {
|
||||||
|
"telegramApi": {
|
||||||
|
"id": "ADurvy9EKUDzbDdq",
|
||||||
|
"name": "DK0_Server"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"jsCode": "const callback = $input.first().json;\nconst data = callback.callback_query?.data || '';\nconst chatId = callback.callback_query?.from?.id;\nconst messageId = callback.callback_query?.message?.message_id;\n\n// Parse: auto:slug, manual:slug, ignore:slug\nconst [action, slug] = data.split(':');\n\nreturn [{\n json: {\n action,\n slug,\n chatId,\n messageId,\n rawCallback: data\n }\n}];"
|
||||||
|
},
|
||||||
|
"type": "n8n-nodes-base.code",
|
||||||
|
"typeVersion": 2,
|
||||||
|
"position": [
|
||||||
|
-656,
|
||||||
|
288
|
||||||
|
],
|
||||||
|
"id": "10e5a475-4194-4919-9186-1eb052fbd79b",
|
||||||
|
"name": "Parse Callback"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"rules": {
|
||||||
|
"values": [
|
||||||
|
{
|
||||||
|
"conditions": {
|
||||||
|
"options": {
|
||||||
|
"caseSensitive": true,
|
||||||
|
"leftValue": ""
|
||||||
|
},
|
||||||
|
"conditions": [
|
||||||
|
{
|
||||||
|
"leftValue": "={{ $json.action }}",
|
||||||
|
"rightValue": "auto",
|
||||||
|
"operator": {
|
||||||
|
"type": "string",
|
||||||
|
"operation": "equals"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"combinator": "and"
|
||||||
|
},
|
||||||
|
"renameOutput": true,
|
||||||
|
"outputKey": "Auto"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"conditions": {
|
||||||
|
"options": {
|
||||||
|
"caseSensitive": true,
|
||||||
|
"leftValue": ""
|
||||||
|
},
|
||||||
|
"conditions": [
|
||||||
|
{
|
||||||
|
"leftValue": "={{ $json.action }}",
|
||||||
|
"rightValue": "manual",
|
||||||
|
"operator": {
|
||||||
|
"type": "string",
|
||||||
|
"operation": "equals"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"combinator": "and"
|
||||||
|
},
|
||||||
|
"renameOutput": true,
|
||||||
|
"outputKey": "Manual"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"conditions": {
|
||||||
|
"options": {
|
||||||
|
"caseSensitive": true,
|
||||||
|
"leftValue": ""
|
||||||
|
},
|
||||||
|
"conditions": [
|
||||||
|
{
|
||||||
|
"leftValue": "={{ $json.action }}",
|
||||||
|
"rightValue": "ignore",
|
||||||
|
"operator": {
|
||||||
|
"type": "string",
|
||||||
|
"operation": "equals"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"combinator": "and"
|
||||||
|
},
|
||||||
|
"renameOutput": true,
|
||||||
|
"outputKey": "Ignore"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
"type": "n8n-nodes-base.switch",
|
||||||
|
"typeVersion": 3.2,
|
||||||
|
"position": [
|
||||||
|
-448,
|
||||||
|
288
|
||||||
|
],
|
||||||
|
"id": "a533e527-b3c5-4946-9a26-6f499c7dd6c5",
|
||||||
|
"name": "Switch Action"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"url": "=https://cms.dk0.dev/items/projects?filter[slug][_eq]={{ $json.slug }}&limit=1",
|
||||||
|
"authentication": "predefinedCredentialType",
|
||||||
|
"nodeCredentialType": "httpBearerAuth",
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
"type": "n8n-nodes-base.httpRequest",
|
||||||
|
"typeVersion": 4.4,
|
||||||
|
"position": [
|
||||||
|
-224,
|
||||||
|
80
|
||||||
|
],
|
||||||
|
"id": "9fc55503-e890-4074-9823-f07001b6948a",
|
||||||
|
"name": "Get Project from CMS"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"url": "=https://git.dk0.dev/api/v1/repos/denshooter/{{ $json.slug }}/commits?limit=3",
|
||||||
|
"authentication": "genericCredentialType",
|
||||||
|
"genericAuthType": "httpHeaderAuth",
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
"type": "n8n-nodes-base.httpRequest",
|
||||||
|
"typeVersion": 4.4,
|
||||||
|
"position": [
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"id": "a3fda0d9-0cc9-4744-be3e-9a95ef44dfb4",
|
||||||
|
"name": "Get Commits"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"url": "=https://git.dk0.dev/api/v1/repos/denshooter/{{ $json.slug }}/contents/README.md",
|
||||||
|
"authentication": "genericCredentialType",
|
||||||
|
"genericAuthType": "httpHeaderAuth",
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
"type": "n8n-nodes-base.httpRequest",
|
||||||
|
"typeVersion": 4.4,
|
||||||
|
"position": [
|
||||||
|
0,
|
||||||
|
128
|
||||||
|
],
|
||||||
|
"id": "7106b8c9-fb20-46d9-9e4e-06882115bf7a",
|
||||||
|
"name": "Get README"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"model": "openrouter/free",
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
"type": "@n8n/n8n-nodes-langchain.lmChatOpenRouter",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [
|
||||||
|
448,
|
||||||
|
192
|
||||||
|
],
|
||||||
|
"id": "9acce2c3-1a26-450f-a263-0dc3a1f1e3cf",
|
||||||
|
"name": "OpenRouter Chat Model"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"promptType": "define",
|
||||||
|
"text": "=Du bist ein technischer Autor für das Portfolio von Dennis (dk0.dev).\n\nNeues eigenes Projekt deployed:\nRepo: {{ $('Parse Callback').item.json.slug }}\n\nREADME:\n{{ $('Get README').first().json.content ? Buffer.from($('Get README').first().json.content, 'base64').toString('utf8').substring(0, 1000) : 'Kein README' }}\n\nLetzte Commits:\n{{ $('Get Commits').first().json.map(c => '- ' + c.commit.message).join('\\n') }}\n\nErstelle eine Portfolio-Beschreibung:\n- Was macht das Projekt (Features, Zweck)\n- Tech-Stack und Architektur\n- Highlights aus den Commits\n- Warum ist es cool/interessant\n\nKategorie: webdev (wenn Web-App), automation (wenn Tool/Script), oder selfhosted\n\nAntworte NUR als JSON:\n{\n \"title_en\": \"Aussagekräftiger Titel\",\n \"title_de\": \"Aussagekräftiger Titel\",\n \"description_en\": \"4-6 Sätze\",\n \"description_de\": \"4-6 Sätze\",\n \"content_en\": \"2-3 Absätze Markdown mit technischen Details\",\n \"content_de\": \"2-3 Absätze Markdown mit technischen Details\",\n \"category\": \"webdev|automation|selfhosted\",\n \"technologies\": [\"Next.js\", \"Docker\", \"...\"]\n}",
|
||||||
|
"batching": {},
|
||||||
|
"prompt": "\n Verwende keine Bindestriche, Em-Dashes oder Gedankenstriche (–, —, -)."
|
||||||
|
},
|
||||||
|
"type": "@n8n/n8n-nodes-langchain.chainLlm",
|
||||||
|
"typeVersion": 1.9,
|
||||||
|
"position": [
|
||||||
|
224,
|
||||||
|
80
|
||||||
|
],
|
||||||
|
"id": "2b011cf8-6ed3-4cb1-ab6f-7727912864fc",
|
||||||
|
"name": "AI: Generate Description"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"jsCode": "const raw = $input.first().json.text ?? \"\";\nconst match = raw.match(/\\{[\\s\\S]*\\}/);\nif (!match) throw new Error(\"No JSON found\");\nconst ai = JSON.parse(match[0]);\nreturn [{ json: ai }];"
|
||||||
|
},
|
||||||
|
"type": "n8n-nodes-base.code",
|
||||||
|
"typeVersion": 2,
|
||||||
|
"position": [
|
||||||
|
448,
|
||||||
|
80
|
||||||
|
],
|
||||||
|
"id": "0cbdcf6e-e5d4-460e-b345-b6d47deed051",
|
||||||
|
"name": "Parse JSON"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"jsCode": "const ai = $input.first().json;\nconst ctx = $('Parse Callback').first().json;\n\nconst body = {\n slug: ctx.slug,\n status: \"draft\",\n featured: false,\n title: ai.title_en,\n category: ai.category,\n technologies: ai.technologies,\n tags: ai.technologies,\n date: new Date().toISOString().slice(0, 10),\n translations: {\n create: [\n {\n languages_code: \"en-US\",\n title: ai.title_en,\n description: ai.description_en,\n content: ai.content_en\n },\n {\n languages_code: \"de-DE\",\n title: ai.title_de,\n description: ai.description_de,\n content: ai.content_de\n }\n ]\n }\n};\n\nconst response = await this.helpers.httpRequest({\n method: \"POST\",\n url: \"https://cms.dk0.dev/items/projects\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Authorization\": \"Bearer RF2QytqhcLXuVy6FO3PzWlsoR-ysCTwB\"\n },\n body\n});\n\nreturn [{ json: response }];"
|
||||||
|
},
|
||||||
|
"type": "n8n-nodes-base.code",
|
||||||
|
"typeVersion": 2,
|
||||||
|
"position": [
|
||||||
|
672,
|
||||||
|
80
|
||||||
|
],
|
||||||
|
"id": "70aecf97-6b70-4f03-99e3-9ee44fc0830b",
|
||||||
|
"name": "Add to Directus"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"chatId": "={{ $('Parse Callback').item.json.chatId }}",
|
||||||
|
"text": "={{ \n'✅ Projekt erstellt: ' + $json.data.title + '\\n\\n' +\n'📝 ' + $('Parse JSON').first().json.description_de.substring(0, 200) + '...\\n\\n' +\n'Status: Draft (ID: ' + $json.data.id + ')\\n\\n' +\n'/publishproject' + $json.data.id + ' — Veröffentlichen\\n' + \n'/deleteproject' + $json.data.id + ' — Löschen' \n}}",
|
||||||
|
"additionalFields": {}
|
||||||
|
},
|
||||||
|
"type": "n8n-nodes-base.telegram",
|
||||||
|
"typeVersion": 1.2,
|
||||||
|
"position": [
|
||||||
|
880,
|
||||||
|
80
|
||||||
|
],
|
||||||
|
"id": "9a353247-7d25-4330-9cbf-580599428ae1",
|
||||||
|
"name": "Notify Success",
|
||||||
|
"webhookId": "b1d7284d-c2e5-4e87-b65d-272f1b9b8d6d"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"chatId": "={{ $json.chatId }}",
|
||||||
|
"text": "✍️ OK, schreib mir jetzt was das Projekt macht (4-6 Sätze).\n\nIch formatiere das dann schön und erstelle einen Draft.",
|
||||||
|
"additionalFields": {}
|
||||||
|
},
|
||||||
|
"type": "n8n-nodes-base.telegram",
|
||||||
|
"typeVersion": 1.2,
|
||||||
|
"position": [
|
||||||
|
-224,
|
||||||
|
288
|
||||||
|
],
|
||||||
|
"id": "9160b847-5f07-4d64-9488-faeaeca926b9",
|
||||||
|
"name": "Ask for Manual Input",
|
||||||
|
"webhookId": "c4cb518d-a2e2-48af-b9b6-c3f645fd37db"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"chatId": "={{ $json.chatId }}",
|
||||||
|
"text": "❌ OK, ignoriert.",
|
||||||
|
"additionalFields": {}
|
||||||
|
},
|
||||||
|
"type": "n8n-nodes-base.telegram",
|
||||||
|
"typeVersion": 1.2,
|
||||||
|
"position": [
|
||||||
|
-224,
|
||||||
|
480
|
||||||
|
],
|
||||||
|
"id": "1624b6f1-8202-4fd2-bd0a-52fa039ca696",
|
||||||
|
"name": "Confirm Ignore",
|
||||||
|
"webhookId": "4c5248f1-4420-403c-a506-2e1968c5579d",
|
||||||
|
"credentials": {
|
||||||
|
"telegramApi": {
|
||||||
|
"id": "ADurvy9EKUDzbDdq",
|
||||||
|
"name": "DK0_Server"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"pinData": {},
|
||||||
|
"connections": {
|
||||||
|
"Telegram Trigger": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Parse Callback",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Parse Callback": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Switch Action",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Switch Action": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Get Project from CMS",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Ask for Manual Input",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Confirm Ignore",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Get Project from CMS": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Get Commits",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Get Commits": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Get README",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Get README": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "AI: Generate Description",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"OpenRouter Chat Model": {
|
||||||
|
"ai_languageModel": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "AI: Generate Description",
|
||||||
|
"type": "ai_languageModel",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"AI: Generate Description": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Parse JSON",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Parse JSON": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Add to Directus",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Add to Directus": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Notify Success",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"active": false,
|
||||||
|
"settings": {
|
||||||
|
"executionOrder": "v1",
|
||||||
|
"binaryMode": "separate",
|
||||||
|
"availableInMCP": false
|
||||||
|
},
|
||||||
|
"versionId": "4636a407-7f8e-4833-9345-9d3296ec9b74",
|
||||||
|
"meta": {
|
||||||
|
"instanceId": "cb28e4db755465d5826da179e87f69603d81f833414cc52c327be9183a217b8d"
|
||||||
|
},
|
||||||
|
"id": "abnrtUuJ7BAWv9Hm",
|
||||||
|
"tags": []
|
||||||
|
}
|
||||||
937
n8n-workflows/docker-event.json
Normal file
937
n8n-workflows/docker-event.json
Normal file
@@ -0,0 +1,937 @@
|
|||||||
|
{
|
||||||
|
"name": "Docker Event (Extended)",
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"httpMethod": "POST",
|
||||||
|
"path": "docker-event",
|
||||||
|
"responseMode": "responseNode",
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
"type": "n8n-nodes-base.webhook",
|
||||||
|
"typeVersion": 2.1,
|
||||||
|
"position": [
|
||||||
|
0,
|
||||||
|
-224
|
||||||
|
],
|
||||||
|
"id": "870fa550-42f6-4e19-a796-f1f044b0cdc8",
|
||||||
|
"name": "Webhook",
|
||||||
|
"webhookId": "e147d70b-79d8-44fd-bbe8-8274cf905b11",
|
||||||
|
"disabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"jsCode": "const data = $input.first().json;\n\nconst container = data.container ?? data.body?.container ?? '';\nconst image = data.image ?? data.body?.image ?? '';\nconst timestamp = data.timestamp ?? data.body?.timestamp ?? '';\n\nconst slug = container.toLowerCase().replace(/[^a-z0-9]+/g, '-');\n\nconst serviceName = container.replace(/[-_]/g, ' ');\n\nreturn [{\n json: {\n container,\n image,\n serviceName,\n timestamp,\n slug \n }\n}];"
|
||||||
|
},
|
||||||
|
"type": "n8n-nodes-base.code",
|
||||||
|
"typeVersion": 2,
|
||||||
|
"position": [
|
||||||
|
224,
|
||||||
|
-224
|
||||||
|
],
|
||||||
|
"id": "aaa6a678-1ad3-4f82-9b01-37e21b47b189",
|
||||||
|
"name": "Kontext aufbereiten",
|
||||||
|
"disabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"conditions": {
|
||||||
|
"options": {
|
||||||
|
"caseSensitive": true,
|
||||||
|
"leftValue": "",
|
||||||
|
"typeValidation": "loose",
|
||||||
|
"version": 3
|
||||||
|
},
|
||||||
|
"conditions": [
|
||||||
|
{
|
||||||
|
"id": "ebe26f0c-d5a7-45c9-9747-afc75b57a41c",
|
||||||
|
"leftValue": "={{ $json.data }}",
|
||||||
|
"rightValue": "[]",
|
||||||
|
"operator": {
|
||||||
|
"type": "string",
|
||||||
|
"operation": "notEndsWith"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"combinator": "and"
|
||||||
|
},
|
||||||
|
"looseTypeValidation": true,
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
"type": "n8n-nodes-base.if",
|
||||||
|
"typeVersion": 2.3,
|
||||||
|
"position": [
|
||||||
|
672,
|
||||||
|
-224
|
||||||
|
],
|
||||||
|
"id": "62197a33-5169-48e1-9539-57c047efb108",
|
||||||
|
"name": "If",
|
||||||
|
"disabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"url": "=https://cms.dk0.dev/items/projects?filter[slug][_eq]={{ $json.slug }}&limit=1",
|
||||||
|
"authentication": "predefinedCredentialType",
|
||||||
|
"nodeCredentialType": "httpBearerAuth",
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
"type": "n8n-nodes-base.httpRequest",
|
||||||
|
"typeVersion": 4.4,
|
||||||
|
"position": [
|
||||||
|
448,
|
||||||
|
-224
|
||||||
|
],
|
||||||
|
"id": "db783886-06b5-4473-8907-dd6c655aa3dd",
|
||||||
|
"name": "Search for Slug",
|
||||||
|
"credentials": {
|
||||||
|
"httpBearerAuth": {
|
||||||
|
"id": "ZtI5e08iryR9m6FG",
|
||||||
|
"name": "Directus"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"disabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"model": "openrouter/free",
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
"type": "@n8n/n8n-nodes-langchain.lmChatOpenRouter",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [
|
||||||
|
976,
|
||||||
|
16
|
||||||
|
],
|
||||||
|
"id": "b9130ff4-359b-4736-9442-1b0ca7d31877",
|
||||||
|
"name": "OpenRouter Chat Model",
|
||||||
|
"credentials": {
|
||||||
|
"openRouterApi": {
|
||||||
|
"id": "8Kdy4RHHwMZ0Cn6x",
|
||||||
|
"name": "OpenRouter"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"disabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"promptType": "define",
|
||||||
|
"text": "= Du bist ein technischer Autor für das Self-Hosting Portfolio von Dennis auf dk0.dev.\n Ein neuer Service wurde auf dem Server deployed:\n \n Container: {{ $('Kontext aufbereiten').item.json.container }}\n Image: {{ $('Kontext aufbereiten').item.json.image }}\n Service: {{ $('Kontext aufbereiten').item.json.serviceName }}\n \n Aufgabe:\n 1. Erkenne ob es sich um ein EIGENES Projekt (z.B. Image enthält \"denshooter\", \"dk0\", \"portfolio\") oder eine \nSELF-HOSTED App handelt.\n 2. Bewerte die \"Coolness\" (1-10) basierend auf:\n - Eigener Code = +3 Punkte\n - Neue/spannende Technologie = +2 Punkte\n - Großes/bekanntes Projekt (Suricata, CrowdStrike-Level) = +3 Punkte\n - Standard Self-Hosted Tool (Nextcloud, Plausible) = +1 Punkt\n - CI/CD Build-Container, Test-Runner = 0 Punkte (ignorieren)\n 3. Erstelle Beschreibung NUR wenn coolness_score >= 6\n \n Antworte NUR als valides JSON:\n {\n \"coolness_score\": 1-10,\n \"notify\": true/false (true wenn >= 7),\n \"reason\": \"Kurze Begründung warum cool oder nicht\",\n \"type\": \"own\" oder \"selfhosted\" oder \"ignore\",\n \"title_en\": \"...\",\n \"title_de\": \"...\",\n \"description_en\": \"...\",\n \"description_de\": \"...\",\n \"content_en\": \"...\",\n \"content_de\": \"...\",\n \"category\": \"selfhosted\" oder \"webdev\" oder \"automation\",\n \"technologies\": [\"Docker\", \"...\"]\n }",
|
||||||
|
"batching": {},
|
||||||
|
"prompt": "\n Verwende keine Bindestriche, Em-Dashes oder Gedankenstriche (–, —, -)."
|
||||||
|
},
|
||||||
|
"type": "@n8n/n8n-nodes-langchain.chainLlm",
|
||||||
|
"typeVersion": 1.9,
|
||||||
|
"position": [
|
||||||
|
896,
|
||||||
|
-224
|
||||||
|
],
|
||||||
|
"id": "77d46075-3342-4e93-8806-07087a2389dc",
|
||||||
|
"name": "Basic LLM Chain",
|
||||||
|
"disabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"jsCode": "const raw = $input.first().json.text ?? \"\";\n\nconst match = raw.match(/\\{[\\s\\S]*\\}/);\nif (!match) throw new Error(\"No JSON found\");\n\nconst ai = JSON.parse(match[0]);\n\nreturn [\n {\n json: ai,\n },\n];\n"
|
||||||
|
},
|
||||||
|
"type": "n8n-nodes-base.code",
|
||||||
|
"typeVersion": 2,
|
||||||
|
"position": [
|
||||||
|
1248,
|
||||||
|
-224
|
||||||
|
],
|
||||||
|
"id": "de5ed311-0d46-4677-963c-711a6ad514e9",
|
||||||
|
"name": "Parse JSON",
|
||||||
|
"disabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"jsCode": "const ai = $('Parse JSON').first().json;\n const ctx = $('Kontext aufbereiten').first().json;\n\n const body = {\n slug: ctx.slug,\n status: \"draft\",\n featured: false,\n title: ai.title_en,\n category: ai.category,\n technologies: ai.technologies,\n tags: ai.technologies,\n date: new Date().toISOString().slice(0, 10),\n translations: {\n create: [\n {\n languages_code: \"en-US\",\n title: ai.title_en,\n description: ai.description_en,\n content: ai.content_en\n },\n {\n languages_code: \"de-DE\",\n title: ai.title_de,\n description: ai.description_de,\n content: ai.content_de\n }\n ]\n }\n };\n\n const response = await this.helpers.httpRequest({\n method: \"POST\",\n url: \"https://cms.dk0.dev/items/projects\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Authorization\": \"Bearer RF2QytqhcLXuVy6FO3PzWlsoR-ysCTwB\"\n },\n body\n });\n\n return [{ json: response }];"
|
||||||
|
},
|
||||||
|
"type": "n8n-nodes-base.code",
|
||||||
|
"typeVersion": 2,
|
||||||
|
"position": [
|
||||||
|
1680,
|
||||||
|
-224
|
||||||
|
],
|
||||||
|
"id": "c47b915d-e4d7-43e9-8ee3-b41389896fa7",
|
||||||
|
"name": "Add to Directus",
|
||||||
|
"disabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"respondWith": "json",
|
||||||
|
"responseBody": "{ \"success\": true }",
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
"type": "n8n-nodes-base.respondToWebhook",
|
||||||
|
"typeVersion": 1.5,
|
||||||
|
"position": [
|
||||||
|
2128,
|
||||||
|
-224
|
||||||
|
],
|
||||||
|
"id": "6cf8f30d-1352-466f-9163-9b4f16b972e0",
|
||||||
|
"name": "Respond to Webhook",
|
||||||
|
"disabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"chatId": "145931600",
|
||||||
|
"text": "={{ \n'🆕 Neuer Service erkannt!\\n\\n' +\n'📦 ' + $('Kontext aufbereiten').first().json.container + '\\n' +\n'🐳 ' + $('Kontext aufbereiten').first().json.image + '\\n\\n' +\n'📝 ' + $('Parse JSON').first().json.title_de + '\\n' + \n$('Parse JSON').first().json.description_de + '\\n\\n' +\n'Status: Draft in Directus erstellt (ID: ' + $json.data.id + ')\\n\\n' +\n('/publishproject_' + $json.data.id).replace(/_/g, '\\\\_') + ' — Veröffentlichen\\n' + \n('/deleteproject_' + $json.data.id).replace(/_/g, '\\\\_') + ' — Löschen' \n}}",
|
||||||
|
"additionalFields": {}
|
||||||
|
},
|
||||||
|
"type": "n8n-nodes-base.telegram",
|
||||||
|
"typeVersion": 1.2,
|
||||||
|
"position": [
|
||||||
|
1904,
|
||||||
|
-224
|
||||||
|
],
|
||||||
|
"id": "b29de3ec-b1ca-40c3-8493-af44e5372fd2",
|
||||||
|
"name": "Send a text message",
|
||||||
|
"webhookId": "c02ccf69-16dc-436e-b1cc-f8fa9dd8d33f",
|
||||||
|
"credentials": {
|
||||||
|
"telegramApi": {
|
||||||
|
"id": "ADurvy9EKUDzbDdq",
|
||||||
|
"name": "DK0_Server"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"disabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"rules": {
|
||||||
|
"values": [
|
||||||
|
{
|
||||||
|
"conditions": {
|
||||||
|
"options": {
|
||||||
|
"caseSensitive": true,
|
||||||
|
"leftValue": "",
|
||||||
|
"typeValidation": "strict",
|
||||||
|
"version": 3
|
||||||
|
},
|
||||||
|
"conditions": [
|
||||||
|
{
|
||||||
|
"leftValue": "={{ $json.notify }}",
|
||||||
|
"rightValue": "true",
|
||||||
|
"operator": {
|
||||||
|
"type": "boolean",
|
||||||
|
"operation": "true",
|
||||||
|
"singleValue": true
|
||||||
|
},
|
||||||
|
"id": "febc397c-b060-4a66-ab9b-1274c8509cc2"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"combinator": "and"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
"type": "n8n-nodes-base.switch",
|
||||||
|
"typeVersion": 3.4,
|
||||||
|
"position": [
|
||||||
|
1456,
|
||||||
|
-224
|
||||||
|
],
|
||||||
|
"id": "5ade115f-e134-4358-8d95-a144eede8d9a",
|
||||||
|
"name": "Switch",
|
||||||
|
"disabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"jsCode": "const data = $input.first().json;\n\nconst container = data.container ?? data.body?.container ?? '';\nconst image = data.image ?? data.body?.image ?? '';\nconst timestamp = data.timestamp ?? data.body?.timestamp ?? '';\n\nconst slug = container.toLowerCase().replace(/[^a-z0-9]+/g, '-');\nconst serviceName = container.replace(/[-_]/g, ' ');\n\n// Detect project type\nlet projectType = 'selfhosted';\nif (image.includes('denshooter') || image.includes('dk0')) {\n projectType = 'own';\n} else if (container.match(/^(act-|gitea-actions-|runner-)/)) {\n projectType = 'cicd';\n}\n\n// Extract repo from image for own projects\nlet repo = null;\nif (projectType === 'own') {\n const match = image.match(/([^/]+):(\\w+)/);\n if (match) repo = match[1];\n}\n\nreturn [{\n json: {\n container,\n image,\n serviceName,\n timestamp,\n slug,\n projectType,\n repo\n }\n}];"
|
||||||
|
},
|
||||||
|
"type": "n8n-nodes-base.code",
|
||||||
|
"typeVersion": 2,
|
||||||
|
"position": [
|
||||||
|
896,
|
||||||
|
768
|
||||||
|
],
|
||||||
|
"id": "fb34f047-5c11-4255-9b45-adb9fe169042",
|
||||||
|
"name": "Parse Context"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"url": "=https://cms.dk0.dev/items/projects?filter[slug][_eq]={{ $json.slug }}&limit=1",
|
||||||
|
"authentication": "predefinedCredentialType",
|
||||||
|
"nodeCredentialType": "httpBearerAuth",
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
"type": "n8n-nodes-base.httpRequest",
|
||||||
|
"typeVersion": 4.4,
|
||||||
|
"position": [
|
||||||
|
1120,
|
||||||
|
768
|
||||||
|
],
|
||||||
|
"id": "acd7a411-2465-4aa3-a7ee-442a79c500f2",
|
||||||
|
"name": "Check if Exists",
|
||||||
|
"credentials": {
|
||||||
|
"httpBearerAuth": {
|
||||||
|
"id": "ZtI5e08iryR9m6FG",
|
||||||
|
"name": "Directus"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"conditions": {
|
||||||
|
"options": {
|
||||||
|
"caseSensitive": true,
|
||||||
|
"leftValue": "",
|
||||||
|
"typeValidation": "loose"
|
||||||
|
},
|
||||||
|
"conditions": [
|
||||||
|
{
|
||||||
|
"leftValue": "={{ $json.data.length }}",
|
||||||
|
"rightValue": "0",
|
||||||
|
"operator": {
|
||||||
|
"type": "number",
|
||||||
|
"operation": "equals"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"combinator": "and"
|
||||||
|
},
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
"type": "n8n-nodes-base.if",
|
||||||
|
"typeVersion": 2.3,
|
||||||
|
"position": [
|
||||||
|
1344,
|
||||||
|
768
|
||||||
|
],
|
||||||
|
"id": "bdcddb94-8676-4467-a370-ad2cf07d09a3",
|
||||||
|
"name": "If New"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"rules": {
|
||||||
|
"values": [
|
||||||
|
{
|
||||||
|
"conditions": {
|
||||||
|
"options": {
|
||||||
|
"caseSensitive": true,
|
||||||
|
"leftValue": ""
|
||||||
|
},
|
||||||
|
"conditions": [
|
||||||
|
{
|
||||||
|
"leftValue": "={{ $('Parse Context').item.json.projectType }}",
|
||||||
|
"rightValue": "own",
|
||||||
|
"operator": {
|
||||||
|
"type": "string",
|
||||||
|
"operation": "equals"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"combinator": "and"
|
||||||
|
},
|
||||||
|
"renameOutput": true,
|
||||||
|
"outputKey": "Own Project"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"conditions": {
|
||||||
|
"options": {
|
||||||
|
"caseSensitive": true,
|
||||||
|
"leftValue": ""
|
||||||
|
},
|
||||||
|
"conditions": [
|
||||||
|
{
|
||||||
|
"leftValue": "={{ $('Parse Context').item.json.projectType }}",
|
||||||
|
"rightValue": "cicd",
|
||||||
|
"operator": {
|
||||||
|
"type": "string",
|
||||||
|
"operation": "equals"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"combinator": "and"
|
||||||
|
},
|
||||||
|
"renameOutput": true,
|
||||||
|
"outputKey": "CI/CD (Ignore)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"conditions": {
|
||||||
|
"options": {
|
||||||
|
"caseSensitive": true,
|
||||||
|
"leftValue": ""
|
||||||
|
},
|
||||||
|
"conditions": [
|
||||||
|
{
|
||||||
|
"leftValue": "={{ $('Parse Context').item.json.projectType }}",
|
||||||
|
"rightValue": "selfhosted",
|
||||||
|
"operator": {
|
||||||
|
"type": "string",
|
||||||
|
"operation": "equals"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"combinator": "and"
|
||||||
|
},
|
||||||
|
"renameOutput": true,
|
||||||
|
"outputKey": "Self-Hosted"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
"type": "n8n-nodes-base.switch",
|
||||||
|
"typeVersion": 3.2,
|
||||||
|
"position": [
|
||||||
|
1568,
|
||||||
|
768
|
||||||
|
],
|
||||||
|
"id": "00786826-8d6b-4e17-aa7f-1afdca38d7a3",
|
||||||
|
"name": "Switch Type"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"url": "=https://git.dk0.dev/api/v1/repos/denshooter/{{ $('Parse Context').item.json.repo }}/commits?limit=1",
|
||||||
|
"authentication": "genericCredentialType",
|
||||||
|
"genericAuthType": "httpHeaderAuth",
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
"type": "n8n-nodes-base.httpRequest",
|
||||||
|
"typeVersion": 4.4,
|
||||||
|
"position": [
|
||||||
|
1776,
|
||||||
|
560
|
||||||
|
],
|
||||||
|
"id": "9ef7f66b-3054-4765-b0a8-7ebb6aa353aa",
|
||||||
|
"name": "Get Last Commit",
|
||||||
|
"credentials": {
|
||||||
|
"httpHeaderAuth": {
|
||||||
|
"id": "YN3oIbok6Fjy5WNW",
|
||||||
|
"name": "gitea api"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"url": "=https://git.dk0.dev/api/v1/repos/denshooter/{{ $('Parse Context').item.json.repo }}/contents/README.md",
|
||||||
|
"authentication": "genericCredentialType",
|
||||||
|
"genericAuthType": "httpHeaderAuth",
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
"type": "n8n-nodes-base.httpRequest",
|
||||||
|
"typeVersion": 4.4,
|
||||||
|
"position": [
|
||||||
|
1840,
|
||||||
|
672
|
||||||
|
],
|
||||||
|
"id": "114fece9-c5f1-4c6b-8272-6f39fb8ce24a",
|
||||||
|
"name": "Get README",
|
||||||
|
"credentials": {
|
||||||
|
"httpHeaderAuth": {
|
||||||
|
"id": "YN3oIbok6Fjy5WNW",
|
||||||
|
"name": "gitea api"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"jsCode": "const ctx = $('Parse Context').first().json;\nconst commit = $('Get Last Commit').first().json[0];\nconst readme = $('Get README').first().json;\n\n// Decode README (base64)\nlet readmeText = '';\ntry {\n readmeText = Buffer.from(readme.content, 'base64').toString('utf8');\n // First 500 chars\n readmeText = readmeText.substring(0, 500).replace(/\\n/g, ' ');\n} catch (e) {\n readmeText = 'No README available';\n}\n\nconst commitMsg = commit?.commit?.message || 'No recent commits';\nconst commitAuthor = commit?.commit?.author?.name || 'Unknown';\n\nreturn [{\n json: {\n container: ctx.container,\n image: ctx.image,\n slug: ctx.slug,\n repo: ctx.repo,\n commitMsg,\n commitAuthor,\n readmeExcerpt: readmeText\n }\n}];"
|
||||||
|
},
|
||||||
|
"type": "n8n-nodes-base.code",
|
||||||
|
"typeVersion": 2,
|
||||||
|
"position": [
|
||||||
|
2192,
|
||||||
|
480
|
||||||
|
],
|
||||||
|
"id": "8810426d-c146-42c9-8ec2-5d8f56934a1f",
|
||||||
|
"name": "Merge Git Data"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"chatId": "145931600",
|
||||||
|
"text": "={{ \n'🚀 Neuer Deploy: ' + $json.container + '\\n' +\n'📦 ' + $json.image + '\\n\\n' +\n'📝 Letzter Commit:\\n' + $json.commitMsg + '\\n' +\n'👤 ' + $json.commitAuthor + '\\n\\n' +\n'📄 README:\\n' + $json.readmeExcerpt + '...\\n\\n' +\n'Was ist das Highlight?' \n}}",
|
||||||
|
"replyMarkup": "inlineKeyboard",
|
||||||
|
"inlineKeyboard": {
|
||||||
|
"rows": [
|
||||||
|
{
|
||||||
|
"row": {
|
||||||
|
"buttons": [
|
||||||
|
{
|
||||||
|
"text": "Selbst beschreiben",
|
||||||
|
"additionalFields": {
|
||||||
|
"callback_data": "={{ 'manual:' + $json.slug }}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "Auto-generieren",
|
||||||
|
"additionalFields": {
|
||||||
|
"callback_data": "={{ 'ignore:' + $json.slug }}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"additionalFields": {}
|
||||||
|
},
|
||||||
|
"type": "n8n-nodes-base.telegram",
|
||||||
|
"typeVersion": 1.2,
|
||||||
|
"position": [
|
||||||
|
2544,
|
||||||
|
592
|
||||||
|
],
|
||||||
|
"id": "d4016ea3-7233-4926-af21-c7b07cc5f39d",
|
||||||
|
"name": "Ask via Telegram",
|
||||||
|
"webhookId": "313376d7-33a6-4c80-938b-e8ebc7ee2d11",
|
||||||
|
"credentials": {
|
||||||
|
"telegramApi": {
|
||||||
|
"id": "ADurvy9EKUDzbDdq",
|
||||||
|
"name": "DK0_Server"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"promptType": "define",
|
||||||
|
"text": "=Du bist ein technischer Autor für dk0.dev.\n\nNeuer Self-Hosted Service:\nContainer: {{ $('Parse Context').item.json.container }}\nImage: {{ $('Parse Context').item.json.image }}\n\nErstelle eine Portfolio-Beschreibung:\n- Was macht die App\n- Warum Self-Hosting besser ist als Cloud\n- Wie sie in die Infrastruktur integriert ist\n\nAntworte NUR als JSON:\n{\n \"title_en\": \"Titel\",\n \"title_de\": \"Titel\",\n \"description_en\": \"4-6 Sätze\",\n \"description_de\": \"4-6 Sätze\",\n \"content_en\": \"2-3 Absätze Markdown\",\n \"content_de\": \"2-3 Absätze Markdown\",\n \"category\": \"selfhosted\",\n \"technologies\": [\"Docker\", \"...\"]\n}",
|
||||||
|
"batching": {},
|
||||||
|
"prompt": "\n Verwende keine Bindestriche, Em-Dashes oder Gedankenstriche (–, —, -)."
|
||||||
|
},
|
||||||
|
"type": "@n8n/n8n-nodes-langchain.chainLlm",
|
||||||
|
"typeVersion": 1.9,
|
||||||
|
"position": [
|
||||||
|
1952,
|
||||||
|
864
|
||||||
|
],
|
||||||
|
"id": "0fd46a9d-40a9-4bb7-be5e-9b32b9a96381",
|
||||||
|
"name": "AI: Self-Hosted"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"chatId": "145931600",
|
||||||
|
"text": "={{ \n'🆕 Self-Hosted Service: ' + $('Parse Context').first().json.serviceName + '\\n\\n' +\n'📝 ' + $json.data.title + '\\n\\n' +\n'Status: Draft erstellt (ID: ' + $json.data.id + ')\\n\\n' +\n'/publishproject' + $json.data.id + ' — Veröffentlichen\\n' + \n'/deleteproject' + $json.data.id + ' — Löschen' \n}}",
|
||||||
|
"additionalFields": {}
|
||||||
|
},
|
||||||
|
"type": "n8n-nodes-base.telegram",
|
||||||
|
"typeVersion": 1.2,
|
||||||
|
"position": [
|
||||||
|
2656,
|
||||||
|
848
|
||||||
|
],
|
||||||
|
"id": "bfaca06b-65ca-41a8-ba8a-1b1aef7ba12d",
|
||||||
|
"name": "Notify Selfhosted",
|
||||||
|
"webhookId": "a7d15c96-41e1-4242-9b5f-0382f4f0d31a",
|
||||||
|
"credentials": {
|
||||||
|
"telegramApi": {
|
||||||
|
"id": "ADurvy9EKUDzbDdq",
|
||||||
|
"name": "DK0_Server"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"respondWith": "json",
|
||||||
|
"responseBody": "{ \"success\": true, \"message\": \"CI/CD container ignored\" }",
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
"type": "n8n-nodes-base.respondToWebhook",
|
||||||
|
"typeVersion": 1.5,
|
||||||
|
"position": [
|
||||||
|
1776,
|
||||||
|
960
|
||||||
|
],
|
||||||
|
"id": "d93818d9-64f9-4f57-ae84-c4280eeb50f0",
|
||||||
|
"name": "Respond (Ignore)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"respondWith": "json",
|
||||||
|
"responseBody": "{ \"success\": true }",
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
"type": "n8n-nodes-base.respondToWebhook",
|
||||||
|
"typeVersion": 1.5,
|
||||||
|
"position": [
|
||||||
|
2880,
|
||||||
|
768
|
||||||
|
],
|
||||||
|
"id": "4f1ad083-e73a-497c-a724-673205254b34",
|
||||||
|
"name": "Respond"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"respondWith": "json",
|
||||||
|
"responseBody": "{ \"success\": true, \"message\": \"Project already exists\" }",
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
"type": "n8n-nodes-base.respondToWebhook",
|
||||||
|
"typeVersion": 1.5,
|
||||||
|
"position": [
|
||||||
|
1568,
|
||||||
|
960
|
||||||
|
],
|
||||||
|
"id": "0b93b3c7-c158-4389-af18-b418aa3b2239",
|
||||||
|
"name": "Respond (Exists)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"httpMethod": "POST",
|
||||||
|
"path": "docker-event",
|
||||||
|
"responseMode": "responseNode",
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
"type": "n8n-nodes-base.webhook",
|
||||||
|
"typeVersion": 2.1,
|
||||||
|
"position": [
|
||||||
|
688,
|
||||||
|
768
|
||||||
|
],
|
||||||
|
"id": "2b1c77d4-9f7f-4758-9e8e-f88195448ba3",
|
||||||
|
"name": "Webhook1",
|
||||||
|
"webhookId": "25d94042-2088-4e09-bfae-645db3d6803f"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"model": "openrouter/free",
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
"type": "@n8n/n8n-nodes-langchain.lmChatOpenRouter",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [
|
||||||
|
1968,
|
||||||
|
1072
|
||||||
|
],
|
||||||
|
"id": "a450227f-f1e5-44f3-a90e-044420042fc4",
|
||||||
|
"name": "OpenRouter Chat Model1",
|
||||||
|
"credentials": {
|
||||||
|
"openRouterApi": {
|
||||||
|
"id": "8Kdy4RHHwMZ0Cn6x",
|
||||||
|
"name": "OpenRouter"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"jsCode": "const raw = $input.first().json.text ?? \"\";\nconst match = raw.match(/\\{[\\s\\S]*\\}/);\nif (!match) throw new Error(\"No JSON found\");\nconst ai = JSON.parse(match[0]);\nreturn [{ json: ai }];"
|
||||||
|
},
|
||||||
|
"type": "n8n-nodes-base.code",
|
||||||
|
"typeVersion": 2,
|
||||||
|
"position": [
|
||||||
|
2224,
|
||||||
|
848
|
||||||
|
],
|
||||||
|
"id": "ca78ecdd-5520-4540-969b-9e7b77bac3b4",
|
||||||
|
"name": "Parse JSON1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"jsCode": "const ai = $input.first().json;\nconst ctx = $('Parse Context').first().json;\n\nconst body = {\n slug: ctx.slug,\n status: \"draft\",\n featured: false,\n title: ai.title_en,\n category: ai.category,\n technologies: ai.technologies,\n tags: ai.technologies,\n date: new Date().toISOString().slice(0, 10),\n translations: {\n create: [\n {\n languages_code: \"en-US\",\n title: ai.title_en,\n description: ai.description_en,\n content: ai.content_en\n },\n {\n languages_code: \"de-DE\",\n title: ai.title_de,\n description: ai.description_de,\n content: ai.content_de\n }\n ]\n }\n};\n\nconst response = await this.helpers.httpRequest({\n method: \"POST\",\n url: \"https://cms.dk0.dev/items/projects\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Authorization\": \"Bearer RF2QytqhcLXuVy6FO3PzWlsoR-ysCTwB\"\n },\n body\n});\n\nreturn [{ json: response }];"
|
||||||
|
},
|
||||||
|
"type": "n8n-nodes-base.code",
|
||||||
|
"typeVersion": 2,
|
||||||
|
"position": [
|
||||||
|
2448,
|
||||||
|
848
|
||||||
|
],
|
||||||
|
"id": "1ac0a31c-68a1-44df-a6b3-203698318cbf",
|
||||||
|
"name": "Add to Directus1"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"pinData": {},
|
||||||
|
"connections": {
|
||||||
|
"Webhook": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Kontext aufbereiten",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Kontext aufbereiten": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Search for Slug",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"If": {
|
||||||
|
"main": [
|
||||||
|
[],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Basic LLM Chain",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Search for Slug": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "If",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"OpenRouter Chat Model": {
|
||||||
|
"ai_languageModel": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Basic LLM Chain",
|
||||||
|
"type": "ai_languageModel",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Basic LLM Chain": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Parse JSON",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Parse JSON": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Switch",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Add to Directus": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Send a text message",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Send a text message": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Respond to Webhook",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Switch": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Add to Directus",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Parse Context": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Check if Exists",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Check if Exists": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "If New",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"If New": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Switch Type",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Respond (Exists)",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Switch Type": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Get Last Commit",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Respond (Ignore)",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "AI: Self-Hosted",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Get Last Commit": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Get README",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Get README": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Merge Git Data",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Merge Git Data": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Ask via Telegram",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Ask via Telegram": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Respond",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"AI: Self-Hosted": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Parse JSON1",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Notify Selfhosted": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Respond",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Webhook1": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Parse Context",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"OpenRouter Chat Model1": {
|
||||||
|
"ai_languageModel": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "AI: Self-Hosted",
|
||||||
|
"type": "ai_languageModel",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Parse JSON1": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Add to Directus1",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Add to Directus1": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Notify Selfhosted",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"active": true,
|
||||||
|
"settings": {
|
||||||
|
"executionOrder": "v1",
|
||||||
|
"binaryMode": "separate",
|
||||||
|
"availableInMCP": false
|
||||||
|
},
|
||||||
|
"versionId": "1e2cf0ca-fe15-4a10-9716-30f85a2c2531",
|
||||||
|
"meta": {
|
||||||
|
"templateCredsSetupCompleted": true,
|
||||||
|
"instanceId": "cb28e4db755465d5826da179e87f69603d81f833414cc52c327be9183a217b8d"
|
||||||
|
},
|
||||||
|
"id": "RARR6MAlJSHAmBp8",
|
||||||
|
"tags": []
|
||||||
|
}
|
||||||
219
n8n-workflows/finished-books.json
Normal file
219
n8n-workflows/finished-books.json
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
{
|
||||||
|
"name": "finishedBooks",
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"rule": {
|
||||||
|
"interval": [
|
||||||
|
{
|
||||||
|
"triggerAtHour": 6
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "n8n-nodes-base.scheduleTrigger",
|
||||||
|
"typeVersion": 1.3,
|
||||||
|
"position": [
|
||||||
|
0,
|
||||||
|
-64
|
||||||
|
],
|
||||||
|
"id": "7170586a-8b80-4614-b186-1b661276fd30",
|
||||||
|
"name": "Schedule Trigger"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"operation": "getAll",
|
||||||
|
"collection": "book_reviews",
|
||||||
|
"itemFields": [
|
||||||
|
"hardcover_id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"type": "@directus/n8n-nodes-directus.directus",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [
|
||||||
|
224,
|
||||||
|
-64
|
||||||
|
],
|
||||||
|
"id": "145cc646-45d1-4ce7-9f04-77debe503ec6",
|
||||||
|
"name": "Get_Existing_Books",
|
||||||
|
"credentials": {
|
||||||
|
"directusApi": {
|
||||||
|
"id": "QnVxKFcSXqpaG86u",
|
||||||
|
"name": "Directus"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"method": "POST",
|
||||||
|
"url": "https://api.hardcover.app/v1/graphql",
|
||||||
|
"authentication": "genericCredentialType",
|
||||||
|
"genericAuthType": "httpBearerAuth",
|
||||||
|
"sendQuery": true,
|
||||||
|
"queryParameters": {
|
||||||
|
"parameters": [
|
||||||
|
{}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"sendHeaders": true,
|
||||||
|
"headerParameters": {
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "content-type",
|
||||||
|
"value": "application/json"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"sendBody": true,
|
||||||
|
"bodyParameters": {
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "query",
|
||||||
|
"value": "query GetReadBooks { me { user_books(where: {status_id: {_eq: 3}}, limit: 10, order_by: {last_read_date: desc}) { last_read_date rating edition { title image { url } book { id contributions { author { name } } } } } } }"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
"type": "n8n-nodes-base.httpRequest",
|
||||||
|
"typeVersion": 4.3,
|
||||||
|
"position": [
|
||||||
|
448,
|
||||||
|
-64
|
||||||
|
],
|
||||||
|
"id": "c2e0f7e4-a30e-4083-b4a9-a1a7e9f8ba3f",
|
||||||
|
"name": "hardcover",
|
||||||
|
"credentials": {
|
||||||
|
"httpBearerAuth": {
|
||||||
|
"id": "Kmf2fBCFkuRuWWZa",
|
||||||
|
"name": "Hardcover"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"jsCode": "// 1. Alle gelesenen Bücher von Hardcover holen\nconst hcData = $input.all()[0]?.json;\nconst hcBooks = hcData?.data?.me?.[0]?.user_books || [];\n// 2. Alle bereits in Directus existierenden IDs holen\nlet existingIds = [];\ntry{\n const existingItems = $('Get_Existing_Books').all();\n existingIds = existingItems.map(item => item.json.hardcover_id?.toString());\n } catch (e) {\n // Falls noch gar keine Bücher in Directus sind, ist die Liste einfach leer\n existingIds = [];\n}\n// 3. Filtern: Nur Bücher behalten, deren ID noch NICHT in Directus ist\nconst newBooks = hcBooks.filter(entry => {\n const id = entry.edition.book.id.toString();\n return !existingIds.includes(id);\n});\n// 4. Die neuen Bücher für Directus formatieren\nreturn newBooks.map(entry => {\n const ed = entry.edition || {};\n return {\n json: {\n book_title: ed.title,\n book_author: ed.book?.contributions?.[0]?.author?.name || \"Unbekannter Autor\",\n book_image: ed.image?.url || null,\n hardcover_id: ed.book?.id?.toString(),\n finished_at: entry.last_read_date,\n rating: entry.rating || null,\n status: \"draft\"\n }\n };\n});"
|
||||||
|
},
|
||||||
|
"type": "n8n-nodes-base.code",
|
||||||
|
"typeVersion": 2,
|
||||||
|
"position": [
|
||||||
|
672,
|
||||||
|
-64
|
||||||
|
],
|
||||||
|
"id": "a0bc4f01-264f-46c3-a667-359983109a72",
|
||||||
|
"name": "removeDuplicates"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"collection": "book_reviews",
|
||||||
|
"collectionFields": {
|
||||||
|
"fields": {
|
||||||
|
"field": [
|
||||||
|
{
|
||||||
|
"name": "status",
|
||||||
|
"value": "={{ $json.status }}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "book_title",
|
||||||
|
"value": "={{ $json.book_title }}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "book_author",
|
||||||
|
"value": "={{ $json.book_author }}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "rating",
|
||||||
|
"value": "={{ $json.rating }}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "book_image",
|
||||||
|
"value": "={{ $json.book_image }}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "hardcover_id",
|
||||||
|
"value": "={{ $json.hardcover_id }}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "finished_at",
|
||||||
|
"value": "={{ $json.finished_at }}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "@directus/n8n-nodes-directus.directus",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [
|
||||||
|
896,
|
||||||
|
-64
|
||||||
|
],
|
||||||
|
"id": "0f3db869-1832-4041-8d1d-2a3d834922f0",
|
||||||
|
"name": "Create an item",
|
||||||
|
"credentials": {
|
||||||
|
"directusApi": {
|
||||||
|
"id": "QnVxKFcSXqpaG86u",
|
||||||
|
"name": "Directus"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"pinData": {},
|
||||||
|
"connections": {
|
||||||
|
"Schedule Trigger": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Get_Existing_Books",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Get_Existing_Books": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "hardcover",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"hardcover": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "removeDuplicates",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"removeDuplicates": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Create an item",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"active": true,
|
||||||
|
"settings": {
|
||||||
|
"executionOrder": "v1",
|
||||||
|
"availableInMCP": false
|
||||||
|
},
|
||||||
|
"versionId": "2fa60722-a717-44da-9047-c867a440609c",
|
||||||
|
"meta": {
|
||||||
|
"templateCredsSetupCompleted": true,
|
||||||
|
"instanceId": "cb28e4db755465d5826da179e87f69603d81f833414cc52c327be9183a217b8d"
|
||||||
|
},
|
||||||
|
"id": "sbpapdCb7OBoRdc_3j0VL",
|
||||||
|
"tags": []
|
||||||
|
}
|
||||||
258
n8n-workflows/portfolio-status.json
Normal file
258
n8n-workflows/portfolio-status.json
Normal file
File diff suppressed because one or more lines are too long
740
n8n-workflows/telegram-cms.json
Normal file
740
n8n-workflows/telegram-cms.json
Normal file
@@ -0,0 +1,740 @@
|
|||||||
|
{
|
||||||
|
"name": "🎯 ULTIMATE Telegram CMS COMPLETE",
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"updates": [
|
||||||
|
"message",
|
||||||
|
"callback_query"
|
||||||
|
],
|
||||||
|
"additionalFields": {}
|
||||||
|
},
|
||||||
|
"type": "n8n-nodes-base.telegramTrigger",
|
||||||
|
"typeVersion": 1.2,
|
||||||
|
"position": [
|
||||||
|
0,
|
||||||
|
240
|
||||||
|
],
|
||||||
|
"id": "telegram-trigger-001",
|
||||||
|
"name": "Telegram Trigger",
|
||||||
|
"webhookId": "telegram-cms-webhook-001",
|
||||||
|
"credentials": {
|
||||||
|
"telegramApi": {
|
||||||
|
"id": "ADurvy9EKUDzbDdq",
|
||||||
|
"name": "DK0_Server"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"jsCode": "const input = $input.first().json;\nconst token = '8166414331:AAGNQ6fn2juD5esaTRxPjtTdSMkwq_oASIc';\n\nif (input.callback_query) {\n const cbq = input.callback_query;\n const chatId = cbq.message.chat.id;\n const data = cbq.data;\n const callbackQueryId = cbq.id;\n \n if (token) {\n try {\n await this.helpers.httpRequest({ \n method: 'POST', \n url: 'https://api.telegram.org/bot' + token + '/answerCallbackQuery', \n headers: { 'Content-Type': 'application/json' }, \n body: { callback_query_id: callbackQueryId } \n });\n } catch(e) {}\n }\n \n const parts = data.split(':');\n const action = parts[0];\n \n if (action === 'start') return [{ json: { action: 'start', chatId } }];\n if (action === 'stats') return [{ json: { action: 'stats', chatId } }];\n if (action === 'list') return [{ json: { action: 'list', type: parts[1], page: parseInt(parts[2] || '1'), chatId } }];\n if (action === 'preview') return [{ json: { action: 'preview', collectionType: parts[1], id: parts[2], chatId } }];\n if (action === 'publish') return [{ json: { action: 'publish', collectionType: parts[1], id: parts[2], chatId } }];\n if (action === 'delete') return [{ json: { action: 'delete', collectionType: parts[1], id: parts[2], chatId } }];\n if (action === 'review_info') return [{ json: { action: 'review_info', id: parts[1], chatId } }];\n \n return [{ json: { action: 'unknown', chatId } }];\n}\n\nconst text = input.message?.text ?? '';\nconst chatId = input.message?.chat?.id;\nlet match;\n\nif (text === '/start') return [{ json: { action: 'start', chatId } }];\nif (text === '/stats') return [{ json: { action: 'stats', chatId } }];\n\nmatch = text.match(/^\\/list\\s+(projects|books)(?:\\s+(\\d+))?/);\nif (match) return [{ json: { action: 'list', type: match[1], page: parseInt(match[2] || '1'), chatId } }];\n\nmatch = text.match(/^\\/preview\\s*(project|book)?(\\d+)/);\nif (match) {\n const typePrefix = match[1] === 'project' ? 'projects' : match[1] === 'book' ? 'book_reviews' : 'projects';\n return [{ json: { action: 'preview', collectionType: typePrefix, id: match[2], chatId } }];\n}\n\nmatch = text.match(/^\\/search\\s+(.+)/);\nif (match) return [{ json: { action: 'search', query: match[1].trim(), chatId } }];\n\nmatch = text.match(/^\\/publish\\s*(project|book)?(\\d+)/);\nif (match) {\n const typePrefix = match[1] === 'book' ? 'books' : 'projects';\n return [{ json: { action: 'publish', collectionType: typePrefix, id: match[2], chatId } }];\n}\n\nmatch = text.match(/^\\/delete\\s*(project|book)?(\\d+)/);\nif (match) {\n const typePrefix = match[1] === 'book' ? 'books' : 'projects';\n return [{ json: { action: 'delete', collectionType: typePrefix, id: match[2], chatId } }];\n}\n\n// .review HC_ID [RATING] -> starts review process with AI questions\nmatch = text.match(/^\\.review\\s+(\\d+)(?:\\s+([1-5]))?/);\nif (match) return [{ json: { action: 'review_info', hardcoverId: match[1], rating: match[2] ? parseInt(match[2]) : 0, chatId } }];\n\n// .answer BOOK_ID RATING your answers -> submit review answers\nmatch = text.match(/^\\.answer\\s+(\\d+)\\s+([1-5])\\s+(.+)/);\nif (match) return [{ json: { action: 'answer_review', bookId: match[1], rating: parseInt(match[2]), answers: match[3].trim(), chatId } }];\n\nmatch = text.match(/^\\.refine\\s+(\\d+)\\s+(.+)/);\nif (match) return [{ json: { action: 'refine_review', id: match[1], feedback: match[2].trim(), chatId } }];\n\nreturn [{ json: { action: 'unknown', chatId } }];\n"
|
||||||
|
},
|
||||||
|
"type": "n8n-nodes-base.code",
|
||||||
|
"typeVersion": 2,
|
||||||
|
"position": [
|
||||||
|
240,
|
||||||
|
240
|
||||||
|
],
|
||||||
|
"id": "global-parser-001",
|
||||||
|
"name": "Global Parser"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"rules": {
|
||||||
|
"values": [
|
||||||
|
{
|
||||||
|
"conditions": {
|
||||||
|
"conditions": [
|
||||||
|
{
|
||||||
|
"leftValue": "={{ $json.action }}",
|
||||||
|
"rightValue": "start",
|
||||||
|
"operator": {
|
||||||
|
"type": "string",
|
||||||
|
"operation": "equals"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"combinator": "and",
|
||||||
|
"options": {
|
||||||
|
"caseSensitive": true,
|
||||||
|
"leftValue": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"renameOutput": true,
|
||||||
|
"outputKey": "start"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"conditions": {
|
||||||
|
"conditions": [
|
||||||
|
{
|
||||||
|
"leftValue": "={{ $json.action }}",
|
||||||
|
"rightValue": "list",
|
||||||
|
"operator": {
|
||||||
|
"type": "string",
|
||||||
|
"operation": "equals"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"combinator": "and",
|
||||||
|
"options": {
|
||||||
|
"caseSensitive": true,
|
||||||
|
"leftValue": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"renameOutput": true,
|
||||||
|
"outputKey": "list"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"conditions": {
|
||||||
|
"conditions": [
|
||||||
|
{
|
||||||
|
"leftValue": "={{ $json.action }}",
|
||||||
|
"rightValue": "search",
|
||||||
|
"operator": {
|
||||||
|
"type": "string",
|
||||||
|
"operation": "equals"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"combinator": "and",
|
||||||
|
"options": {
|
||||||
|
"caseSensitive": true,
|
||||||
|
"leftValue": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"renameOutput": true,
|
||||||
|
"outputKey": "search"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"conditions": {
|
||||||
|
"conditions": [
|
||||||
|
{
|
||||||
|
"leftValue": "={{ $json.action }}",
|
||||||
|
"rightValue": "stats",
|
||||||
|
"operator": {
|
||||||
|
"type": "string",
|
||||||
|
"operation": "equals"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"combinator": "and",
|
||||||
|
"options": {
|
||||||
|
"caseSensitive": true,
|
||||||
|
"leftValue": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"renameOutput": true,
|
||||||
|
"outputKey": "stats"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"conditions": {
|
||||||
|
"conditions": [
|
||||||
|
{
|
||||||
|
"leftValue": "={{ $json.action }}",
|
||||||
|
"rightValue": "preview",
|
||||||
|
"operator": {
|
||||||
|
"type": "string",
|
||||||
|
"operation": "equals"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"combinator": "and",
|
||||||
|
"options": {
|
||||||
|
"caseSensitive": true,
|
||||||
|
"leftValue": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"renameOutput": true,
|
||||||
|
"outputKey": "preview"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"conditions": {
|
||||||
|
"conditions": [
|
||||||
|
{
|
||||||
|
"leftValue": "={{ $json.action }}",
|
||||||
|
"rightValue": "publish",
|
||||||
|
"operator": {
|
||||||
|
"type": "string",
|
||||||
|
"operation": "equals"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"combinator": "and",
|
||||||
|
"options": {
|
||||||
|
"caseSensitive": true,
|
||||||
|
"leftValue": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"renameOutput": true,
|
||||||
|
"outputKey": "publish"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"conditions": {
|
||||||
|
"conditions": [
|
||||||
|
{
|
||||||
|
"leftValue": "={{ $json.action }}",
|
||||||
|
"rightValue": "delete",
|
||||||
|
"operator": {
|
||||||
|
"type": "string",
|
||||||
|
"operation": "equals"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"combinator": "and",
|
||||||
|
"options": {
|
||||||
|
"caseSensitive": true,
|
||||||
|
"leftValue": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"renameOutput": true,
|
||||||
|
"outputKey": "delete"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"conditions": {
|
||||||
|
"conditions": [
|
||||||
|
{
|
||||||
|
"leftValue": "={{ $json.action }}",
|
||||||
|
"rightValue": "delete_review",
|
||||||
|
"operator": {
|
||||||
|
"type": "string",
|
||||||
|
"operation": "equals"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"combinator": "and",
|
||||||
|
"options": {
|
||||||
|
"caseSensitive": true,
|
||||||
|
"leftValue": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"renameOutput": true,
|
||||||
|
"outputKey": "delete_review"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"conditions": {
|
||||||
|
"conditions": [
|
||||||
|
{
|
||||||
|
"leftValue": "={{ $json.action }}",
|
||||||
|
"rightValue": "answer_review",
|
||||||
|
"operator": {
|
||||||
|
"type": "string",
|
||||||
|
"operation": "equals"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"combinator": "and",
|
||||||
|
"options": {
|
||||||
|
"caseSensitive": true,
|
||||||
|
"leftValue": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"renameOutput": true,
|
||||||
|
"outputKey": "answer_review"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"conditions": {
|
||||||
|
"options": {
|
||||||
|
"caseSensitive": true,
|
||||||
|
"leftValue": ""
|
||||||
|
},
|
||||||
|
"conditions": [
|
||||||
|
{
|
||||||
|
"leftValue": "={{ $json.action }}",
|
||||||
|
"rightValue": "refine_review",
|
||||||
|
"operator": {
|
||||||
|
"type": "string",
|
||||||
|
"operation": "equals"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"combinator": "and"
|
||||||
|
},
|
||||||
|
"renameOutput": true,
|
||||||
|
"outputKey": "refine_review"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"conditions": {
|
||||||
|
"conditions": [
|
||||||
|
{
|
||||||
|
"leftValue": "={{ $json.action }}",
|
||||||
|
"rightValue": "unknown",
|
||||||
|
"operator": {
|
||||||
|
"type": "string",
|
||||||
|
"operation": "equals"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"combinator": "and",
|
||||||
|
"options": {
|
||||||
|
"caseSensitive": true,
|
||||||
|
"leftValue": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"renameOutput": true,
|
||||||
|
"outputKey": "unknown"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"conditions": {
|
||||||
|
"conditions": [
|
||||||
|
{
|
||||||
|
"leftValue": "={{ $json.action }}",
|
||||||
|
"rightValue": "review_info",
|
||||||
|
"operator": {
|
||||||
|
"type": "string",
|
||||||
|
"operation": "equals"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"combinator": "and",
|
||||||
|
"options": {
|
||||||
|
"caseSensitive": true,
|
||||||
|
"leftValue": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"renameOutput": true,
|
||||||
|
"outputKey": "review_info"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
"type": "n8n-nodes-base.switch",
|
||||||
|
"typeVersion": 3.2,
|
||||||
|
"position": [
|
||||||
|
480,
|
||||||
|
240
|
||||||
|
],
|
||||||
|
"id": "router-001",
|
||||||
|
"name": "Command Router"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"jsCode": "\ntry {\n var chatId = $input.first().json.chatId;\n var projectsResp = await this.helpers.httpRequest({ method: 'GET', url: 'https://cms.dk0.dev/items/projects?aggregate[count]=id&filter[status][_eq]=draft', headers: { 'Authorization': 'Bearer RF2QytqhcLXuVy6FO3PzWlsoR-ysCTwB' } });\n var draftProjects = (projectsResp && projectsResp.data && projectsResp.data[0] && projectsResp.data[0].count && projectsResp.data[0].count.id) || 0;\n var booksResp = await this.helpers.httpRequest({ method: 'GET', url: 'https://cms.dk0.dev/items/book_reviews?aggregate[count]=id&filter[status][_eq]=draft', headers: { 'Authorization': 'Bearer RF2QytqhcLXuVy6FO3PzWlsoR-ysCTwB' } });\n var draftBooks = (booksResp && booksResp.data && booksResp.data[0] && booksResp.data[0].count && booksResp.data[0].count.id) || 0;\n var message = '\\u{1F3AF} <b>DK0 Portfolio CMS</b>\\n\\n\\u{1F4CA} <b>Status:</b>\\n\\u2022 Draft Projects: ' + draftProjects + '\\n\\u2022 Draft Reviews: ' + draftBooks + '\\n\\nTap a button to navigate.';\n var keyboard = [\n [{ text: '\\u{1F4CB} Projects', callback_data: 'list:projects:1' }, { text: '\\u{1F4DA} Books', callback_data: 'list:books:1' }],\n [{ text: '\\u{1F4CA} Stats', callback_data: 'stats' }]\n ];\n return [{ json: { chatId: chatId, message: message, parseMode: 'HTML', keyboard: keyboard } }];\n} catch (error) {\n return [{ json: { chatId: $input.first().json.chatId, message: '\\u274C Error loading dashboard: ' + error.message, parseMode: 'HTML' } }];\n}\n"
|
||||||
|
},
|
||||||
|
"type": "n8n-nodes-base.code",
|
||||||
|
"typeVersion": 2,
|
||||||
|
"position": [
|
||||||
|
720,
|
||||||
|
-120
|
||||||
|
],
|
||||||
|
"id": "dashboard-001",
|
||||||
|
"name": "Dashboard Handler"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"jsCode": "\ntry {\n var input = $input.first().json;\n var type = input.type;\n var page = input.page || 1;\n var chatId = input.chatId;\n var limit = 5;\n var offset = (page - 1) * limit;\n var collection = type === 'projects' ? 'projects' : 'book_reviews';\n var fields = type === 'projects' ? 'id,slug,category,status,date_created,translations.*' : 'id,book_title,rating,status,finished_at';\n var url = 'https://cms.dk0.dev/items/' + collection + '?limit=' + limit + '&offset=' + offset + '&sort=' + (type === 'projects' ? '-date_created' : '-finished_at') + '&fields=' + fields;\n var response = await this.helpers.httpRequest({ method: 'GET', url: url, headers: { 'Authorization': 'Bearer RF2QytqhcLXuVy6FO3PzWlsoR-ysCTwB' } });\n var items = (response && response.data) || [];\n if (items.length === 0) {\n return [{ json: { chatId: chatId, message: 'No ' + type + ' found.', parseMode: 'HTML', keyboard: [[{ text: '\\u{1F3E0} Home', callback_data: 'start' }]] } }];\n }\n var message = '<b>' + type.toUpperCase() + ' (Page ' + page + ')</b>\\n\\n';\n var keyboard = [];\n items.forEach(function(item, idx) {\n var num = idx + 1;\n var displayNum = (offset || 0) + num;\n if (type === 'projects') {\n var title = (item.translations && item.translations[0] && item.translations[0].title) || item.slug || 'Untitled';\n message += displayNum + '. <b>' + title + '</b>\\n ' + (item.category || 'N/A') + ' | ' + item.status + '\\n\\n';\n } else {\n var stars = '';\n for (var s = 0; s < (item.rating || 0); s++) { stars += '\\u2B50'; }\n message += displayNum + '. <b>' + (item.book_title || 'Untitled') + '</b>\\n ' + stars + ' | ' + item.status + '\\n\\n';\n }\n var row = [\n { text: '\\u{1F441} #' + displayNum, callback_data: 'preview:' + type + ':' + item.id },\n { text: '\\u2705 Pub #' + displayNum, callback_data: 'publish:' + type + ':' + item.id }\n ];\n if (type === 'books' && item.status === 'draft') {\n row.push({ text: '\\u270D\\uFE0F Review #' + displayNum, callback_data: 'review_info:' + item.id });\n }\n row.push({ text: '\\u{1F5D1} Del #' + displayNum, callback_data: 'delete:' + type + ':' + item.id });\n keyboard.push(row);\n });\n var navRow = [];\n if (page > 1) { navRow.push({ text: '\\u2190 Prev', callback_data: 'list:' + type + ':' + (page - 1) }); }\n if (items.length === limit) { navRow.push({ text: 'Next \\u2192', callback_data: 'list:' + type + ':' + (page + 1) }); }\n navRow.push({ text: '\\u{1F3E0} Home', callback_data: 'start' });\n keyboard.push(navRow);\n return [{ json: { chatId: chatId, message: message, parseMode: 'HTML', keyboard: keyboard } }];\n} catch (error) {\n return [{ json: { chatId: $input.first().json.chatId, message: '\\u274C Error fetching list: ' + error.message, parseMode: 'HTML' } }];\n}\n"
|
||||||
|
},
|
||||||
|
"type": "n8n-nodes-base.code",
|
||||||
|
"typeVersion": 2,
|
||||||
|
"position": [
|
||||||
|
720,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"id": "list-handler-001",
|
||||||
|
"name": "List Handler"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"jsCode": "try {\n var input = $input.first().json;\n var query = input.query;\n var chatId = input.chatId;\n var encoded = encodeURIComponent(query);\n var projectsResp = await this.helpers.httpRequest({ method: 'GET', url: 'https://cms.dk0.dev/items/projects?filter[translations][title][_contains]=' + encoded + '&limit=5&fields=id,slug,category,translations.*', headers: { 'Authorization': 'Bearer RF2QytqhcLXuVy6FO3PzWlsoR-ysCTwB' } });\n var booksResp = await this.helpers.httpRequest({ method: 'GET', url: 'https://cms.dk0.dev/items/book_reviews?filter[book_title][_contains]=' + encoded + '&limit=5&fields=id,book_title,book_author,rating', headers: { 'Authorization': 'Bearer RF2QytqhcLXuVy6FO3PzWlsoR-ysCTwB' } });\n var projects = (projectsResp && projectsResp.data) || [];\n var books = (booksResp && booksResp.data) || [];\n if (projects.length === 0 && books.length === 0) {\n return [{ json: { chatId: chatId, message: '\\u{1F50D} No results for \"' + query + '\"', parseMode: 'HTML', keyboard: [[{ text: '\\u{1F3E0} Home', callback_data: 'start' }]] } }];\n }\n var message = '\\u{1F50D} <b>Search: \"' + query + '\"</b>\\n\\n';\n var keyboard = [];\n if (projects.length > 0) {\n message += '\\u{1F4C1} <b>Projects (' + projects.length + '):</b>\\n';\n projects.forEach(function(p) {\n var title = (p.translations && p.translations[0] && p.translations[0].title) || p.slug || 'Untitled';\n message += '\\u2022 ' + title + '\\n';\n keyboard.push([{ text: '\\u{1F441} ' + title, callback_data: 'preview:projects:' + p.id }]);\n });\n message += '\\n';\n }\n if (books.length > 0) {\n message += '\\u{1F4DA} <b>Books (' + books.length + '):</b>\\n';\n books.forEach(function(b) {\n message += '\\u2022 ' + b.book_title + ' by ' + b.book_author + '\\n';\n keyboard.push([{ text: '\\u{1F441} ' + b.book_title, callback_data: 'preview:books:' + b.id }]);\n });\n }\n keyboard.push([{ text: '\\u{1F3E0} Home', callback_data: 'start' }]);\n return [{ json: { chatId: chatId, message: message, parseMode: 'HTML', keyboard: keyboard } }];\n} catch (error) {\n return [{ json: { chatId: $input.first().json.chatId, message: '\\u274C Error searching: ' + error.message, parseMode: 'HTML' } }];\n}"
|
||||||
|
},
|
||||||
|
"type": "n8n-nodes-base.code",
|
||||||
|
"typeVersion": 2,
|
||||||
|
"position": [
|
||||||
|
720,
|
||||||
|
120
|
||||||
|
],
|
||||||
|
"id": "search-handler-001",
|
||||||
|
"name": "Search Handler"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"jsCode": "try {\n var chatId = $input.first().json.chatId;\n var projectsResp = await this.helpers.httpRequest({ method: 'GET', url: 'https://cms.dk0.dev/items/projects?fields=id,category,status,date_created', headers: { 'Authorization': 'Bearer RF2QytqhcLXuVy6FO3PzWlsoR-ysCTwB' } });\n var booksResp = await this.helpers.httpRequest({ method: 'GET', url: 'https://cms.dk0.dev/items/book_reviews?fields=id,rating,status,finished_at', headers: { 'Authorization': 'Bearer RF2QytqhcLXuVy6FO3PzWlsoR-ysCTwB' } });\n var projects = (projectsResp && projectsResp.data) || [];\n var books = (booksResp && booksResp.data) || [];\n var pPublished = projects.filter(function(p) { return p.status === 'published'; }).length;\n var pDraft = projects.filter(function(p) { return p.status === 'draft'; }).length;\n var pArchived = projects.filter(function(p) { return p.status === 'archived'; }).length;\n var bPublished = books.filter(function(b) { return b.status === 'published'; }).length;\n var bDraft = books.filter(function(b) { return b.status === 'draft'; }).length;\n var bAvg = books.length > 0 ? (books.reduce(function(sum, b) { return sum + (b.rating || 0); }, 0) / books.length).toFixed(1) : 0;\n var categories = {};\n projects.forEach(function(p) { if (p.category) { categories[p.category] = (categories[p.category] || 0) + 1; } });\n var message = '\\u{1F4CA} <b>DK0 Portfolio Statistics</b>\\n\\n\\u{1F4C1} <b>Projects:</b>\\n\\u2022 Total: ' + projects.length + '\\n\\u2022 Published: ' + pPublished + '\\n\\u2022 Draft: ' + pDraft + '\\n\\u2022 Archived: ' + pArchived + '\\n\\n\\u{1F4DA} <b>Book Reviews:</b>\\n\\u2022 Total: ' + books.length + '\\n\\u2022 Published: ' + bPublished + '\\n\\u2022 Draft: ' + bDraft + '\\n\\u2022 Avg Rating: ' + bAvg + '/5\\n';\n var catEntries = Object.entries(categories).sort(function(a, b) { return b[1] - a[1]; });\n if (catEntries.length > 0) {\n message += '\\n\\u{1F3F7}\\uFE0F <b>Categories:</b>\\n';\n catEntries.forEach(function(entry) { message += '\\u2022 ' + entry[0] + ': ' + entry[1] + '\\n'; });\n }\n var keyboard = [\n [{ text: '\\u{1F4CB} Projects', callback_data: 'list:projects:1' }, { text: '\\u{1F4DA} Books', callback_data: 'list:books:1' }],\n [{ text: '\\u{1F3E0} Home', callback_data: 'start' }]\n ];\n return [{ json: { chatId: chatId, message: message, parseMode: 'HTML', keyboard: keyboard } }];\n} catch (error) {\n return [{ json: { chatId: $input.first().json.chatId, message: '\\u274C Error loading stats: ' + error.message, parseMode: 'HTML' } }];\n}"
|
||||||
|
},
|
||||||
|
"type": "n8n-nodes-base.code",
|
||||||
|
"typeVersion": 2,
|
||||||
|
"position": [
|
||||||
|
720,
|
||||||
|
240
|
||||||
|
],
|
||||||
|
"id": "stats-handler-001",
|
||||||
|
"name": "Stats Handler"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"jsCode": "\ntry {\n var input = $input.first().json;\n var id = input.id;\n var chatId = input.chatId;\n var collectionType = input.collectionType;\n \n var response, collection;\n \n if (collectionType === 'projects' || collectionType === 'project') {\n response = await this.helpers.httpRequest({ method: 'GET', url: 'https://cms.dk0.dev/items/projects/' + id + '?fields=id,slug,category,status,date_created,translations.*', headers: { 'Authorization': 'Bearer RF2QytqhcLXuVy6FO3PzWlsoR-ysCTwB' }, returnFullResponse: true }).catch(function() { return null; });\n collection = 'projects';\n } else if (collectionType === 'books' || collectionType === 'book') {\n response = await this.helpers.httpRequest({ method: 'GET', url: 'https://cms.dk0.dev/items/book_reviews/' + id + '?fields=id,book_title,book_author,rating,status,hardcover_id,translations.*', headers: { 'Authorization': 'Bearer RF2QytqhcLXuVy6FO3PzWlsoR-ysCTwB' }, returnFullResponse: true }).catch(function() { return null; });\n collection = 'book_reviews';\n } else {\n response = await this.helpers.httpRequest({ method: 'GET', url: 'https://cms.dk0.dev/items/projects/' + id + '?fields=id,slug,category,status,date_created,translations.*', headers: { 'Authorization': 'Bearer RF2QytqhcLXuVy6FO3PzWlsoR-ysCTwB' }, returnFullResponse: true }).catch(function() { return null; });\n collection = 'projects';\n var itemTry = response && response.body && response.body.data;\n if (!itemTry) {\n response = await this.helpers.httpRequest({ method: 'GET', url: 'https://cms.dk0.dev/items/book_reviews/' + id + '?fields=id,book_title,book_author,rating,status,hardcover_id,translations.*', headers: { 'Authorization': 'Bearer RF2QytqhcLXuVy6FO3PzWlsoR-ysCTwB' }, returnFullResponse: true }).catch(function() { return null; });\n collection = 'book_reviews';\n }\n }\n\n var item = response && response.body && response.body.data;\n if (!item) {\n return [{ json: { chatId: chatId, message: '\\u274C Item #' + id + ' not found.', parseMode: 'HTML', keyboard: [[{ text: '\\u{1F3E0} Home', callback_data: 'start' }]] } }];\n }\n \n var message = '\\u{1F441}\\uFE0F <b>Preview #' + id + '</b>\\n\\n';\n if (collection === 'projects') {\n message += '\\u{1F4C1} <b>Type:</b> Project\\n\\u{1F516} <b>Slug:</b> ' + item.slug + '\\n\\u{1F3F7}\\uFE0F <b>Category:</b> ' + (item.category || 'N/A') + '\\n\\u{1F4CA} <b>Status:</b> ' + item.status + '\\n\\n';\n var translations = item.translations || [];\n translations.forEach(function(t) {\n var lang = t.languages_code === 'en-US' ? '\\u{1F1EC}\\u{1F1E7} EN' : '\\u{1F1E9}\\u{1F1EA} DE';\n message += lang + ':\\n<b>Title:</b> ' + (t.title || 'N/A') + '\\n<b>Desc:</b> ' + ((t.description || 'N/A')) + '...\\n\\n';\n });\n } else {\n message += '\\u{1F4DA} <b>Type:</b> Book Review\\n\\u{1F4D6} <b>Title:</b> ' + item.book_title + '\\n\\u270D\\uFE0F <b>Author:</b> ' + item.book_author + '\\n\\u2B50 <b>Rating:</b> ' + item.rating + '/5\\n\\u{1F4CA} <b>Status:</b> ' + item.status + '\\n\\u{1F517} <b>HC-ID:</b> ' + item.hardcover_id + '\\n\\n';\n var translations = item.translations || [];\n translations.forEach(function(t) {\n var lang = t.languages_code === 'en-US' ? '\\u{1F1EC}\\u{1F1E7} EN' : '\\u{1F1E9}\\u{1F1EA} DE';\n message += lang + ':\\n' + ((t.review || 'No review')) + '...\\n\\n';\n });\n }\n var listType = collection === 'projects' ? 'projects' : 'books';\n var keyboard = [\n [{ text: '\\u2705 Publish', callback_data: 'publish:' + listType + ':' + id }, { text: '\\u{1F5D1} Delete', callback_data: 'delete:' + listType + ':' + id }],\n [{ text: '\\u2190 Back', callback_data: 'list:' + listType + ':1' }, { text: '\\u{1F3E0} Home', callback_data: 'start' }]\n ];\n return [{ json: { chatId: chatId, message: message, parseMode: 'HTML', keyboard: keyboard } }];\n} catch (error) {\n return [{ json: { chatId: $input.first().json.chatId, message: '\\u274C Error loading preview: ' + error.message, parseMode: 'HTML' } }];\n}\n"
|
||||||
|
},
|
||||||
|
"type": "n8n-nodes-base.code",
|
||||||
|
"typeVersion": 2,
|
||||||
|
"position": [
|
||||||
|
720,
|
||||||
|
360
|
||||||
|
],
|
||||||
|
"id": "preview-handler-001",
|
||||||
|
"name": "Preview Handler"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"jsCode": "try {\n var input = $input.first().json;\n var id = input.id;\n var chatId = input.chatId;\n var collectionType = input.collectionType;\n \n var url, title, listType;\n \n if (collectionType === 'projects' || collectionType === 'project') {\n url = 'https://cms.dk0.dev/items/projects/' + id;\n title = 'Project';\n listType = 'projects';\n } else {\n url = 'https://cms.dk0.dev/items/book_reviews/' + id;\n title = 'Book Review';\n listType = 'books';\n }\n \n var response;\n try {\n response = await this.helpers.httpRequest({\n method: 'PATCH',\n url: url,\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': 'Bearer RF2QytqhcLXuVy6FO3PzWlsoR-ysCTwB'\n },\n body: { status: 'published' }\n });\n } catch(e) {\n return [{ json: { chatId: chatId, message: '\\u274C <b>Publish fehlgeschlagen</b>\\n\\n' + e.message, parseMode: 'HTML', keyboard: [[{ text: '\\u{1F3E0} Home', callback_data: 'start' }]] } }];\n }\n \n var result = response.data || response;\n if (!result || !result.id) {\n return [{ json: { chatId: chatId, message: '\\u274C <b>Publish fehlgeschlagen</b>\\n\\nKeine Bestaetigung von Directus.', parseMode: 'HTML', keyboard: [[{ text: '\\u{1F3E0} Home', callback_data: 'start' }]] } }];\n }\n \n var keyboard = [[{ text: '\\u{1F4CB} ' + (listType === 'projects' ? 'Projects' : 'Books'), callback_data: 'list:' + listType + ':1' }, { text: '\\u{1F3E0} Home', callback_data: 'start' }]];\n return [{ json: { chatId: chatId, message: '\\u2705 <b>' + title + ' #' + id + ' Published!</b>\\n\\nNow live on dk0.dev.', parseMode: 'HTML', keyboard: keyboard } }];\n} catch (error) {\n return [{ json: { chatId: $input.first().json.chatId, message: '\\u274C Error publishing: ' + error.message, parseMode: 'HTML' } }];\n}"
|
||||||
|
},
|
||||||
|
"type": "n8n-nodes-base.code",
|
||||||
|
"typeVersion": 2,
|
||||||
|
"position": [
|
||||||
|
720,
|
||||||
|
480
|
||||||
|
],
|
||||||
|
"id": "publish-handler-001",
|
||||||
|
"name": "Publish Handler"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"jsCode": "try {\n var input = $input.first().json;\n var id = input.id;\n var chatId = input.chatId;\n var collectionType = input.collectionType;\n \n var response, collection, title;\n \n if (collectionType === 'projects' || collectionType === 'project') {\n response = await this.helpers.httpRequest({ method: 'DELETE', url: 'https://cms.dk0.dev/items/projects/' + id, headers: { 'Authorization': 'Bearer RF2QytqhcLXuVy6FO3PzWlsoR-ysCTwB' }, returnFullResponse: true }).catch(function() { return null; });\n collection = 'projects';\n title = 'Project';\n } else if (collectionType === 'books' || collectionType === 'book') {\n response = await this.helpers.httpRequest({ method: 'DELETE', url: 'https://cms.dk0.dev/items/book_reviews/' + id, headers: { 'Authorization': 'Bearer RF2QytqhcLXuVy6FO3PzWlsoR-ysCTwB' }, returnFullResponse: true }).catch(function() { return null; });\n collection = 'book_reviews';\n title = 'Book Review';\n } else {\n // Fallback\n response = await this.helpers.httpRequest({ method: 'DELETE', url: 'https://cms.dk0.dev/items/projects/' + id, headers: { 'Authorization': 'Bearer RF2QytqhcLXuVy6FO3PzWlsoR-ysCTwB' }, returnFullResponse: true }).catch(function() { return null; });\n collection = 'projects';\n title = 'Project';\n if (!response || response.statusCode >= 400) {\n response = await this.helpers.httpRequest({ method: 'DELETE', url: 'https://cms.dk0.dev/items/book_reviews/' + id, headers: { 'Authorization': 'Bearer RF2QytqhcLXuVy6FO3PzWlsoR-ysCTwB' }, returnFullResponse: true }).catch(function() { return null; });\n collection = 'book_reviews';\n title = 'Book Review';\n }\n }\n\n if (!response || response.statusCode >= 400) {\n return [{ json: { chatId: chatId, message: '\\u274C Item #' + id + ' could not be deleted.', parseMode: 'HTML', keyboard: [[{ text: '\\u{1F3E0} Home', callback_data: 'start' }]] } }];\n }\n var listType = collection === 'projects' ? 'projects' : 'books';\n var keyboard = [[{ text: (collection === 'projects' ? '\\u{1F4CB} Projects' : '\\u{1F4DA} Books'), callback_data: 'list:' + listType + ':1' }, { text: '\\u{1F3E0} Home', callback_data: 'start' }]];\n return [{ json: { chatId: chatId, message: '\\u{1F5D1}\\uFE0F *' + title + ' #' + id + ' Deleted*', parseMode: 'HTML', keyboard: keyboard, collection: collection, itemId: id } }];\n} catch (error) {\n return [{ json: { chatId: $input.first().json.chatId, message: '\\u274C Error deleting: ' + error.message, parseMode: 'HTML' } }];\n}"
|
||||||
|
},
|
||||||
|
"type": "n8n-nodes-base.code",
|
||||||
|
"typeVersion": 2,
|
||||||
|
"position": [
|
||||||
|
720,
|
||||||
|
600
|
||||||
|
],
|
||||||
|
"id": "delete-handler-001",
|
||||||
|
"name": "Delete Handler"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"jsCode": "try {\n var input = $input.first().json;\n var id = input.id;\n var chatId = input.chatId;\n var bookResp = await this.helpers.httpRequest({ method: 'GET', url: 'https://cms.dk0.dev/items/book_reviews/' + id + '?fields=id,book_title,translations.id', headers: { 'Authorization': 'Bearer RF2QytqhcLXuVy6FO3PzWlsoR-ysCTwB' } });\n var book = bookResp && bookResp.data;\n if (!book) {\n return [{ json: { chatId: chatId, message: '\\u274C Book review #' + id + ' not found.', parseMode: 'HTML', keyboard: [[{ text: '\\u{1F3E0} Home', callback_data: 'start' }]] } }];\n }\n var translations = book.translations || [];\n var deletedCount = 0;\n for (var i = 0; i < translations.length; i++) {\n await this.helpers.httpRequest({ method: 'DELETE', url: 'https://cms.dk0.dev/items/book_reviews_translations/' + translations[i].id, headers: { 'Authorization': 'Bearer RF2QytqhcLXuVy6FO3PzWlsoR-ysCTwB' } }).catch(function() {});\n deletedCount++;\n }\n var keyboard = [[{ text: '\\u{1F4DA} Books', callback_data: 'list:books:1' }, { text: '\\u{1F3E0} Home', callback_data: 'start' }]];\n return [{ json: { chatId: chatId, message: '\\u{1F5D1}\\uFE0F Deleted ' + deletedCount + ' review translations for \"' + book.book_title + '\".\\n\\nBook entry still exists.', parseMode: 'HTML', keyboard: keyboard, itemId: id, deletedCount: deletedCount } }];\n} catch (error) {\n return [{ json: { chatId: $input.first().json.chatId, message: '\\u274C Error deleting review: ' + error.message, parseMode: 'HTML' } }];\n}"
|
||||||
|
},
|
||||||
|
"type": "n8n-nodes-base.code",
|
||||||
|
"typeVersion": 2,
|
||||||
|
"position": [
|
||||||
|
720,
|
||||||
|
720
|
||||||
|
],
|
||||||
|
"id": "delete-review-handler-001",
|
||||||
|
"name": "Delete Review Handler"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"jsCode": "try {\n var input = $input.first().json;\n var bookId = input.bookId;\n var rating = input.rating;\n var answers = input.answers;\n var chatId = input.chatId;\n\n var bookResp = await this.helpers.httpRequest({ method: 'GET', url: 'https://cms.dk0.dev/items/book_reviews/' + bookId + '?fields=id,book_title,book_author,rating,translations.id,translations.languages_code,translations.review', headers: { 'Authorization': 'Bearer RF2QytqhcLXuVy6FO3PzWlsoR-ysCTwB' } });\n var bookData = bookResp && bookResp.data ? bookResp.data : bookResp;\n if (!bookData || !bookData.id) {\n return [{ json: { chatId: chatId, message: '\\u274C Buch #' + bookId + ' nicht gefunden.', parseMode: 'HTML', keyboard: [[{ text: '\\u{1F3E0} Home', callback_data: 'start' }]] } }];\n }\n\n var prompt = 'Schreibe eine authentische Buchbewertung. Buch: ' + bookData.book_title + ' von ' + bookData.book_author + '. Rating: ' + rating + '/5. Antworten des Lesers auf Fragen zum Buch: ' + answers + ' Schreibe Ich-Perspektive, 4-6 Saetze pro Sprache. Verwende keine Bindestriche, Em-Dashes oder Gedankenstriche. Antworte NUR als JSON: {\"review_en\": \"English review\", \"review_de\": \"Deutsche Bewertung\"}';\n\n var aiResp = await this.helpers.httpRequest({ method: 'POST', url: 'https://openrouter.ai/api/v1/chat/completions', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer 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' }, body: { model: 'openrouter/free', messages: [{ role: 'user', content: prompt }] } });\n var aiText = (aiResp && aiResp.choices && aiResp.choices[0] && aiResp.choices[0].message && aiResp.choices[0].message.content) || '{}';\n var jsonMatch = aiText.match(/\\{[\\s\\S]*\\}/);\n var ai = jsonMatch ? JSON.parse(jsonMatch[0]) : { review_en: answers, review_de: answers };\n\n // Update rating\n await this.helpers.httpRequest({ method: 'PATCH', url: 'https://cms.dk0.dev/items/book_reviews/' + bookData.id, headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer RF2QytqhcLXuVy6FO3PzWlsoR-ysCTwB' }, body: { rating: rating } });\n\n var translations = bookData.translations || [];\n var enTrans = null, deTrans = null;\n for (var i = 0; i < translations.length; i++) {\n if (translations[i].languages_code === 'en-US') enTrans = translations[i];\n if (translations[i].languages_code === 'de-DE') deTrans = translations[i];\n }\n\n var reviewEn = ai.review_en || answers;\n var reviewDe = ai.review_de || answers;\n\n // Update existing translations (PATCH) or create new ones (POST)\n if (enTrans) {\n await this.helpers.httpRequest({ method: 'PATCH', url: 'https://cms.dk0.dev/items/book_reviews_translations/' + enTrans.id, headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer RF2QytqhcLXuVy6FO3PzWlsoR-ysCTwB' }, body: { review: reviewEn } });\n } else {\n await this.helpers.httpRequest({ method: 'POST', url: 'https://cms.dk0.dev/items/book_reviews_translations', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer RF2QytqhcLXuVy6FO3PzWlsoR-ysCTwB' }, body: { book_reviews_id: bookData.id, languages_code: 'en-US', review: reviewEn } });\n }\n\n if (deTrans) {\n await this.helpers.httpRequest({ method: 'PATCH', url: 'https://cms.dk0.dev/items/book_reviews_translations/' + deTrans.id, headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer RF2QytqhcLXuVy6FO3PzWlsoR-ysCTwB' }, body: { review: reviewDe } });\n } else {\n await this.helpers.httpRequest({ method: 'POST', url: 'https://cms.dk0.dev/items/book_reviews_translations', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer RF2QytqhcLXuVy6FO3PzWlsoR-ysCTwB' }, body: { book_reviews_id: bookData.id, languages_code: 'de-DE', review: reviewDe } });\n }\n\n var showEn = reviewEn;\n var showDe = reviewDe;\n var msg = '\\u2705 <b>Review erstellt!</b>\\n\\n\\u{1F4DA} ' + bookData.book_title + ' (' + rating + '/5)\\n\\n<b>EN:</b> ' + showEn + '\\n\\n<b>DE:</b> ' + showDe;\n var keyboard = [[{ text: '\\u{1F441} Preview', callback_data: 'preview:books:' + bookData.id }, { text: '\\u2705 Publish', callback_data: 'publish:books:' + bookData.id }], [{ text: '\\u{1F4DA} Books', callback_data: 'list:books:1' }]];\n return [{ json: { chatId: chatId, message: msg, parseMode: 'HTML', keyboard: keyboard } }];\n} catch (error) {\n return [{ json: { chatId: $input.first().json.chatId, message: '\\u274C Fehler beim Erstellen der Review: ' + error.message, parseMode: 'HTML' } }];\n}"
|
||||||
|
},
|
||||||
|
"type": "n8n-nodes-base.code",
|
||||||
|
"typeVersion": 2,
|
||||||
|
"position": [
|
||||||
|
720,
|
||||||
|
840
|
||||||
|
],
|
||||||
|
"id": "create-review-handler-001",
|
||||||
|
"name": "Create Review Handler"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"jsCode": "var chatId = $input.first().json.chatId;\nvar message = '\\u2753 <b>Unknown Command</b>\\n\\nUse the buttons below or type:\\n<code>.review HC_ID [RATING]</code> - Start review with AI questions\\n<code>.answer BOOK_ID RATING your answers</code> - Submit review answers\\n<code>.refine ID FEEDBACK</code> - Refine existing review';\nvar keyboard = [\n [{ text: '\\u{1F4CB} Projects', callback_data: 'list:projects:1' }, { text: '\\u{1F4DA} Books', callback_data: 'list:books:1' }],\n [{ text: '\\u{1F4CA} Stats', callback_data: 'stats' }, { text: '\\u{1F3E0} Dashboard', callback_data: 'start' }]\n];\nreturn [{ json: { chatId: chatId, message: message, parseMode: 'HTML', keyboard: keyboard } }];"
|
||||||
|
},
|
||||||
|
"type": "n8n-nodes-base.code",
|
||||||
|
"typeVersion": 2,
|
||||||
|
"position": [
|
||||||
|
720,
|
||||||
|
960
|
||||||
|
],
|
||||||
|
"id": "unknown-handler-001",
|
||||||
|
"name": "Unknown Command Handler"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"method": "POST",
|
||||||
|
"url": "={{ 'https://api.telegram.org/bot8166414331:AAGNQ6fn2juD5esaTRxPjtTdSMkwq_oASIc/sendMessage' }}",
|
||||||
|
"authentication": "none",
|
||||||
|
"sendBody": true,
|
||||||
|
"contentType": "json",
|
||||||
|
"specifyBody": "json",
|
||||||
|
"jsonBody": "={{ { chat_id: $json.chatId, text: $json.message, parse_mode: $json.parseMode || 'HTML', reply_markup: ($json.keyboard && $json.keyboard.length > 0) ? { inline_keyboard: $json.keyboard } : undefined } }}"
|
||||||
|
},
|
||||||
|
"type": "n8n-nodes-base.httpRequest",
|
||||||
|
"typeVersion": 4.2,
|
||||||
|
"position": [
|
||||||
|
960,
|
||||||
|
420
|
||||||
|
],
|
||||||
|
"id": "send-message-001",
|
||||||
|
"name": "Send Message",
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"jsCode": "try {\n var input = $input.first().json;\n var id = input.id;\n var feedback = input.feedback;\n var chatId = input.chatId;\n var bookResp = await this.helpers.httpRequest({ method: 'GET', url: 'https://cms.dk0.dev/items/book_reviews/' + id + '?fields=id,book_title,book_author,rating,translations.id,translations.languages_code,translations.review', headers: { 'Authorization': 'Bearer RF2QytqhcLXuVy6FO3PzWlsoR-ysCTwB' } });\n var bookData = bookResp && bookResp.data ? bookResp.data : bookResp;\n if (!bookData || !bookData.id) {\n return [{ json: { chatId: chatId, message: 'Review #' + id + ' nicht gefunden.', parseMode: 'HTML', keyboard: [[{ text: '\\u{1F3E0} Home', callback_data: 'start' }]] } }];\n }\n var translations = bookData.translations || [];\n var enTrans = null, deTrans = null;\n for (var i = 0; i < translations.length; i++) {\n if (translations[i].languages_code === 'en-US') enTrans = translations[i];\n if (translations[i].languages_code === 'de-DE') deTrans = translations[i];\n }\n var currentEn = enTrans ? enTrans.review : '';\n var currentDe = deTrans ? deTrans.review : '';\n var prompt = 'Du hast eine Buchbewertung fuer \"' + bookData.book_title + '\" von \"' + bookData.book_author + '\" geschrieben. Rating: ' + bookData.rating + '/5. Aktuelle EN-Bewertung: ' + currentEn + ' Aktuelle DE-Bewertung: ' + currentDe + ' Feedback des Lesers: ' + feedback + ' Wichtig: EN und DE sind immer inhaltlich identisch, nur die Sprache unterscheidet sich. Feedback gilt fuer BEIDE Versionen, auch wenn es nur eine Sprache erwaehnt. Ueberarbeite daher immer beide synchron. Ich-Perspektive, 4-6 Saetze pro Sprache. Verwende keine Bindestriche, Em-Dashes oder Gedankenstriche. Antworte NUR als JSON: {\"review_en\": \"...\", \"review_de\": \"...\"}';\n var aiResp = await this.helpers.httpRequest({ method: 'POST', url: 'https://openrouter.ai/api/v1/chat/completions', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer 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' }, body: { model: 'openrouter/free', messages: [{ role: 'user', content: prompt }] } });\n var aiText = (aiResp && aiResp.choices && aiResp.choices[0] && aiResp.choices[0].message && aiResp.choices[0].message.content) || '{}';\n var jsonMatch = aiText.match(/\\{[\\s\\S]*\\}/);\n var ai = jsonMatch ? JSON.parse(jsonMatch[0]) : { review_en: feedback, review_de: feedback };\n var reviewEn = ai.review_en || feedback;\n var reviewDe = ai.review_de || feedback;\n\n // Update existing translations (PATCH) or create new ones (POST)\n if (enTrans) {\n await this.helpers.httpRequest({ method: 'PATCH', url: 'https://cms.dk0.dev/items/book_reviews_translations/' + enTrans.id, headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer RF2QytqhcLXuVy6FO3PzWlsoR-ysCTwB' }, body: { review: reviewEn } });\n } else {\n await this.helpers.httpRequest({ method: 'POST', url: 'https://cms.dk0.dev/items/book_reviews_translations', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer RF2QytqhcLXuVy6FO3PzWlsoR-ysCTwB' }, body: { book_reviews_id: bookData.id, languages_code: 'en-US', review: reviewEn } });\n }\n if (deTrans) {\n await this.helpers.httpRequest({ method: 'PATCH', url: 'https://cms.dk0.dev/items/book_reviews_translations/' + deTrans.id, headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer RF2QytqhcLXuVy6FO3PzWlsoR-ysCTwB' }, body: { review: reviewDe } });\n } else {\n await this.helpers.httpRequest({ method: 'POST', url: 'https://cms.dk0.dev/items/book_reviews_translations', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer RF2QytqhcLXuVy6FO3PzWlsoR-ysCTwB' }, body: { book_reviews_id: bookData.id, languages_code: 'de-DE', review: reviewDe } });\n }\n\n var showEn = reviewEn;\n var showDe = reviewDe;\n var msg = '\\u270F\\uFE0F <b>Review aktualisiert!</b>\\n\\n\\u{1F4DA} ' + bookData.book_title + '\\n\\n<b>EN:</b> ' + showEn + '\\n\\n<b>DE:</b> ' + showDe;\n var keyboard = [[{ text: '\\u{1F441} Preview', callback_data: 'preview:books:' + id }, { text: '\\u2705 Publish', callback_data: 'publish:books:' + id }], [{ text: '\\u{1F4DA} Books', callback_data: 'list:books:1' }]];\n return [{ json: { chatId: chatId, message: msg, parseMode: 'HTML', keyboard: keyboard } }];\n} catch (error) {\n return [{ json: { chatId: $input.first().json.chatId, message: '\\u274C Fehler beim Aktualisieren: ' + error.message, parseMode: 'HTML' } }];\n}"
|
||||||
|
},
|
||||||
|
"type": "n8n-nodes-base.code",
|
||||||
|
"typeVersion": 2,
|
||||||
|
"position": [
|
||||||
|
720,
|
||||||
|
1080
|
||||||
|
],
|
||||||
|
"id": "refine-review-handler-001",
|
||||||
|
"name": "Refine Review Handler"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"jsCode": "try {\n var input = $input.first().json;\n var chatId = input.chatId;\n var bookId = input.id;\n var hardcoverId = input.hardcoverId;\n var rating = input.rating || 0;\n var book;\n\n if (bookId) {\n var resp = await this.helpers.httpRequest({ method: 'GET', url: 'https://cms.dk0.dev/items/book_reviews/' + bookId + '?fields=id,book_title,book_author,hardcover_id,rating', headers: { 'Authorization': 'Bearer RF2QytqhcLXuVy6FO3PzWlsoR-ysCTwB' } });\n book = resp && resp.data;\n } else if (hardcoverId) {\n var resp = await this.helpers.httpRequest({ method: 'GET', url: 'https://cms.dk0.dev/items/book_reviews?filter[hardcover_id][_eq]=' + hardcoverId + '&fields=id,book_title,book_author,hardcover_id,rating&limit=1', headers: { 'Authorization': 'Bearer RF2QytqhcLXuVy6FO3PzWlsoR-ysCTwB' } });\n book = resp && resp.data && resp.data[0];\n }\n\n if (!book) {\n return [{ json: { chatId: chatId, message: '\\u274C Buch nicht gefunden. Pr\\u00fcfe die ID.', parseMode: 'HTML', keyboard: [[{ text: '\\u{1F4DA} Books', callback_data: 'list:books:1' }]] } }];\n }\n\n var prompt = 'Du bist ein Leseberater. Generiere genau 4 persoenliche, tiefgruendige Fragen zum Buch \"' + book.book_title + '\" von ' + book.book_author + ', die einem helfen, eine authentische Bewertung zu schreiben. Die Fragen sollen spezifisch zum Buch sein und zum Nachdenken anregen. Antworte NUR als JSON-Array, keine Erklaerung davor: [\"Frage 1\", \"Frage 2\", \"Frage 3\", \"Frage 4\"]';\n\n var aiResp = await this.helpers.httpRequest({ method: 'POST', url: 'https://openrouter.ai/api/v1/chat/completions', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer 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' }, body: { model: 'openrouter/free', messages: [{ role: 'user', content: prompt }] } });\n\n var aiText = (aiResp && aiResp.choices && aiResp.choices[0] && aiResp.choices[0].message && aiResp.choices[0].message.content) || '[]';\n var questions;\n try {\n var jsonMatch = aiText.match(/\\[[\\s\\S]*\\]/);\n questions = jsonMatch ? JSON.parse(jsonMatch[0]) : ['Was hat dir am besten gefallen?', 'Was hat dich gestoert?', 'Wuerdest du es weiterempfehlen?', 'Welche Szene ist dir im Gedaechtnis geblieben?'];\n } catch(e) {\n questions = ['Was hat dir am besten gefallen?', 'Was hat dich gestoert?', 'Wuerdest du es weiterempfehlen?', 'Welche Szene ist dir im Gedaechtnis geblieben?'];\n }\n\n var ratingInfo = rating > 0 ? '\\n\\u2B50 Dein Rating: ' + rating + '/5' : '\\n\\u2B50 Gib dein Rating (1-5) an';\n var msg = '\\u{1F4D6} <b>Review: ' + book.book_title + '</b>\\n' + book.book_author + ratingInfo + '\\n\\n\\u2753 <b>Beantworte diese Fragen:</b>\\n\\n';\n for (var i = 0; i < questions.length; i++) {\n msg += (i + 1) + '. ' + questions[i] + '\\n';\n }\n msg += '\\n\\u270D\\uFE0F Antworte mit:\\n<code>.answer ' + book.id + ' ' + (rating > 0 ? rating : '5') + ' deine Antworten hier</code>';\n msg += '\\n\\n<i>Beispiel: .answer ' + book.id + ' 4 Die Charakterentwicklung war super...</i>';\n\n var keyboard = [[{ text: '\\u{1F4DA} Books', callback_data: 'list:books:1' }, { text: '\\u{1F3E0} Home', callback_data: 'start' }]];\n return [{ json: { chatId: chatId, message: msg, parseMode: 'HTML', keyboard: keyboard } }];\n} catch(e) {\n return [{ json: { chatId: $input.first().json.chatId, message: '\\u274C Error: ' + e.message, parseMode: 'HTML' } }];\n}\n"
|
||||||
|
},
|
||||||
|
"type": "n8n-nodes-base.code",
|
||||||
|
"typeVersion": 2,
|
||||||
|
"position": [
|
||||||
|
720,
|
||||||
|
960
|
||||||
|
],
|
||||||
|
"id": "review-info-handler-001",
|
||||||
|
"name": "Review Info Handler"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"connections": {
|
||||||
|
"Telegram Trigger": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Global Parser",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Global Parser": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Command Router",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Command Router": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Dashboard Handler",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "List Handler",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Search Handler",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Stats Handler",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Preview Handler",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Publish Handler",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Delete Handler",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Delete Review Handler",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Create Review Handler",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Refine Review Handler",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Unknown Command Handler",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Review Info Handler",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Dashboard Handler": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Send Message",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"List Handler": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Send Message",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Search Handler": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Send Message",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Stats Handler": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Send Message",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Preview Handler": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Send Message",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Publish Handler": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Send Message",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Delete Handler": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Send Message",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Delete Review Handler": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Send Message",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Create Review Handler": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Send Message",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Unknown Command Handler": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Send Message",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Refine Review Handler": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Send Message",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Review Info Handler": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Send Message",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pinData": {},
|
||||||
|
"settings": {
|
||||||
|
"executionOrder": "v1"
|
||||||
|
},
|
||||||
|
"staticData": null,
|
||||||
|
"tags": [],
|
||||||
|
"triggerCount": 1,
|
||||||
|
"updatedAt": "2025-01-21T00:00:00.000Z",
|
||||||
|
"versionId": "1"
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user