diff --git a/src/components/Markdowns/MarkdownEditor.js b/src/components/Markdowns/MarkdownEditor.js index 13613b8..5e9af37 100644 --- a/src/components/Markdowns/MarkdownEditor.js +++ b/src/components/Markdowns/MarkdownEditor.js @@ -42,9 +42,8 @@ const MarkdownEditor = () => { const hasPermission = roles.includes("admin") || roles.includes("creator"); - if (!hasPermission) { + if (!hasPermission) return
Permission Denied
; - } if(isLoading) return

Loading...

; diff --git a/src/components/Modals/PathSettingModal.js b/src/components/Modals/PathSettingModal.js new file mode 100644 index 0000000..6d748ee --- /dev/null +++ b/src/components/Modals/PathSettingModal.js @@ -0,0 +1,363 @@ +import React, { useState, useEffect } from "react"; +import { + useCreateWebhookSetting, + useUpdateWebhookSetting, + useWebhookSettingByPathId, + useWebhooks, + useCreateWebhook, + useUpdateWebhook, + useDeleteWebhook +} from "../../utils/webhook-queries"; + +const PathSettingModal = ({ pathId, isOpen, onClose }) => { + const { data: setting } = useWebhookSettingByPathId(pathId); + const { data: webhooks } = useWebhooks(); + + const createWebhookSetting = useCreateWebhookSetting(); + const updateWebhookSetting = useUpdateWebhookSetting(); + const createWebhook = useCreateWebhook(); + const updateWebhook = useUpdateWebhook(); + const deleteWebhook = useDeleteWebhook(); + + const [url, setUrl] = useState(""); + const [enabled, setEnabled] = useState(false); + const [webhookId, setWebhookId] = useState(-1); + const [isOnMarkdownCreated, setIsOnMarkdownCreated] = useState(false); + const [isOnMarkdownUpdated, setIsOnMarkdownUpdated] = useState(false); + const [isOnMarkdownDeleted, setIsOnMarkdownDeleted] = useState(false); + + const [isOnPathCreated, setIsOnPathCreated] = useState(false); + const [isOnPathUpdated, setIsOnPathUpdated] = useState(false); + const [isOnPathDeleted, setIsOnPathDeleted] = useState(false); + + const [triggerEvents, setTriggerEvents] = useState(0); + const [isRecursive, setIsRecursive] = useState(false); + const [additionalHeaders, setAdditionalHeaders] = useState({}); + const [headerList, setHeaderList] = useState([]); + const handleTriggerEventsUpdate = (eventType, isChecked) => { + setTriggerEvents((prevEvents) => { + let newEvents = prevEvents; + + switch (eventType) { + case "isOnMarkdownCreated": + newEvents = isChecked ? (newEvents | 1) : (newEvents & ~1); + break; + case "isOnMarkdownUpdated": + newEvents = isChecked ? (newEvents | 2) : (newEvents & ~2); + break; + case "isOnMarkdownDeleted": + newEvents = isChecked ? (newEvents | 4) : (newEvents & ~4); + break; + case "isOnPathCreated": + newEvents = isChecked ? (newEvents | 8) : (newEvents & ~8); + break; + case "isOnPathUpdated": + newEvents = isChecked ? (newEvents | 16) : (newEvents & ~16); + break; + case "isOnPathDeleted": + newEvents = isChecked ? (newEvents | 32) : (newEvents & ~32); + break; + default: + break; + } + + return newEvents; + }); + }; + + const assignFromTriggerEvents = (events) => { + setIsOnMarkdownCreated((events & 1) > 0); + setIsOnMarkdownUpdated((events & 2) > 0); + setIsOnMarkdownDeleted((events & 4) > 0); + setIsOnPathCreated((events & 8) > 0); + setIsOnPathUpdated((events & 16) > 0); + setIsOnPathDeleted((events & 32) > 0); + }; + + useEffect(() => { + if (setting && webhooks) { + setEnabled(setting.enabled); + setWebhookId(setting.webhook_id || -1); + const selectedWebhook = webhooks?.find(hook => hook.id === setting.webhook_id); + if (selectedWebhook) { + setUrl(selectedWebhook.hook_url); + } + setTriggerEvents(setting.on_events); + assignFromTriggerEvents(setting.on_events); + setIsRecursive(setting.recursive); + try{ + const headers = setting.additional_header ? + JSON.parse(setting.additional_header) : {}; + setAdditionalHeaders(headers); + setHeaderList(Object.entries(headers).map(([key, value]) => ({key, value }))); + } catch(err) { + setAdditionalHeaders({}); + setHeaderList([]); + } + } else { + setUrl(""); + setEnabled(false); + setWebhookId(-1); + setTriggerEvents(0); + assignFromTriggerEvents(0); + setIsRecursive(false); + setAdditionalHeaders({}); + setHeaderList([]); + } + }, [setting, webhooks]); + + const handleSave = () => { + const payload = { + path_id: pathId, + webhook_id: webhookId, + enabled, + on_events: triggerEvents, + recursive: isRecursive, + additional_header: JSON.stringify(additionalHeaders), + }; + console.log(payload); + if (!setting) { + createWebhookSetting.mutate(payload, { + onSuccess: () => alert("Webhook setting created successfully"), + onError: () => alert("Webhook setting creation failed"), + }); + } else { + updateWebhookSetting.mutate({ id: setting.id, data: payload }, { + onSuccess: () => alert("Webhook setting updated successfully"), + onError: () => alert("Webhook setting update failed"), + }); + } + + onClose(); + }; + + const handleCreateWebhook = () => { + const newUrl = prompt("Enter new webhook URL"); + if (newUrl) { + createWebhook.mutate(newUrl, { + onSuccess: () => alert("Webhook created successfully"), + onError: () => alert("Webhook creation failed"), + }); + } + }; + + const handleUpdateWebhook = () => { + const newUrl = prompt("Enter new webhook URL", url); + if (newUrl && webhookId !== -1) { + updateWebhook.mutate({ id: webhookId, data: { hook_url: newUrl } }, { + onSuccess: () => alert("Webhook updated successfully"), + onError: () => alert("Webhook update failed"), + }); + } + }; + + const handleDeleteWebhook = () => { + if (webhookId !== -1 && window.confirm("Are you sure you want to delete this webhook?")) { + deleteWebhook.mutate(webhookId, { + onSuccess: () => alert("Webhook deleted successfully"), + onError: () => alert("Webhook deletion failed"), + }); + } + }; + + const handleApplyHeaders = () => { + const newHeaders = {}; + headerList.forEach(({ key, value }) => { + if (key.trim()) newHeaders[key] = value; + }); + setAdditionalHeaders(newHeaders); + }; + + const handleHeaderChange = (index, field, value) => { + const updatedHeaders = [...headerList]; + updatedHeaders[index][field] = value; + setHeaderList(updatedHeaders); + }; + const handleAddHeader = () => { + setHeaderList([...headerList, { key: "", value: "" }]) + }; + + return ( +
+
+
+
+

Webhook Settings

+ +
+
+
+
+ +
+
+
+ +
+
+
+ +
+
+ {webhookId !== -1 && ( +
+ + +
+ )} +
+ +
+
+ +
+
+
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+
+
+
+
+ +
+
+ +
+ {headerList.map((header, index) => ( +
+
+ {handleHeaderChange(index, "key", e.target.value)}} + /> +
+
+ handleHeaderChange(index, "value", e.target.value)} + /> +
+
+ ))} + + +
+
+
+
+
+ +
+
+ ); +}; + +export default PathSettingModal; \ No newline at end of file diff --git a/src/components/Navigations/PathNode.js b/src/components/Navigations/PathNode.js index 6586020..651b8ea 100644 --- a/src/components/Navigations/PathNode.js +++ b/src/components/Navigations/PathNode.js @@ -2,8 +2,8 @@ import React, {useState} from "react"; import { Link } from "react-router-dom"; import PermissionGuard from "../PermissionGuard"; import "./PathNode.css"; -import {useDeletePath, useMovePath, usePath, usePaths, useUpdatePath} from "../../utils/path-queries"; -import {useIndexMarkdown, useMarkdownsByPath, useMoveMarkdown} from "../../utils/markdown-queries"; +import {useDeletePath, useMovePath, useUpdatePath} from "../../utils/path-queries"; +import {useIndexMarkdown, useMoveMarkdown} from "../../utils/markdown-queries"; import MarkdownNode from "./MarkdownNode"; const PathNode = ({ path, isRoot = false }) => { @@ -11,12 +11,10 @@ const PathNode = ({ path, isRoot = false }) => { const [isEditing, setIsEditing] = useState(false); const [newName, setNewName] = useState(path.name); -// const { data: childPaths, isLoading: isChildLoading, error: childError } = usePaths(path.id); -// const { data: markdowns, isLoading: isMarkdownLoading, error: markdownError } = useMarkdownsByPath(path.id); const deletePath = useDeletePath(); const updatePath = useUpdatePath(); - const {data: indexMarkdown, isLoading: isIndexLoading, error: indexMarkdownError} = useIndexMarkdown(path.id); + const {data: indexMarkdown} = useIndexMarkdown(path.id); const movePath = useMovePath(); const moveMarkdown = useMoveMarkdown(); @@ -33,7 +31,7 @@ const PathNode = ({ path, isRoot = false }) => { const handleSave = () => { console.log(`handleSave ${path.id}`); updatePath.mutate({id: path.id, data: {name: newName}}, { - onsuccess: () => setIsEditing(false), + onSuccess: () => setIsEditing(false), onError: err => alert("failed to update this path"), }) }; diff --git a/src/components/PathManager.js b/src/components/PathManager.js index 677fa25..7d84dce 100644 --- a/src/components/PathManager.js +++ b/src/components/PathManager.js @@ -5,6 +5,8 @@ import { useQueryClient } from "react-query"; import "./PathManager.css"; import {fetch_} from "../utils/request-utils"; import {ConfigContext} from "../ConfigProvider"; +import PathSettingModal from "./Modals/PathSettingModal"; + const PathManager = ({ currentPathId = 1, onPathChange }) => { const [currentPath, setCurrentPath] = useState([{ name: "Root", id: 1 }]); @@ -17,6 +19,11 @@ const PathManager = ({ currentPathId = 1, onPathChange }) => { const { data: subPaths, isLoading: isSubPathsLoading, error: subPathsError } = usePaths(currentPathId); const createPath = useCreatePath(); const config = useContext(ConfigContext).config; + const [isPathSettingModalOpen, setIsPathSettingModalModalOpen] = useState(false); + + const handleSettingClick = () => { + setIsPathSettingModalModalOpen(true); + } const buildPath = async (pathId) => { const path = []; @@ -104,8 +111,8 @@ const PathManager = ({ currentPathId = 1, onPathChange }) => { return (
-
-
+
+
{currentPath.map((path, index) => ( { ))}
+
+      +
+
+ + setIsPathSettingModalModalOpen(false)} + /> +
@@ -144,6 +168,7 @@ const PathManager = ({ currentPathId = 1, onPathChange }) => { Create "{searchTerm}"
+
{dropdownActive && (
diff --git a/src/utils/path-queries.js b/src/utils/path-queries.js index b0b4ecd..6e98222 100644 --- a/src/utils/path-queries.js +++ b/src/utils/path-queries.js @@ -21,23 +21,16 @@ export const usePaths = (parent_id) => { } } ); -} +}; export const usePath = (id) => { const config = useConfig(); - const queryClient = useQueryClient(); - const cachedData = queryClient.getQueryData(["path", id]); - - return useQuery( ["path", id], () => fetch_(`${config.BACKEND_HOST}/api/path/${id}`), { - enabled: !!id, - onSuccess: (data) => { - console.log(`path ${id} - ${cachedData}` ); - } + enabled: !!id } ); }; @@ -53,7 +46,6 @@ export const useCreatePath = () => { }), { onSuccess: (res, variables) => { - console.log(JSON.stringify(variables)); queryClient.invalidateQueries(["paths", variables.parent_id]); queryClient.invalidateQueries("tree"); }, diff --git a/src/utils/webhook-queries.js b/src/utils/webhook-queries.js new file mode 100644 index 0000000..1959661 --- /dev/null +++ b/src/utils/webhook-queries.js @@ -0,0 +1,170 @@ +import {fetch_ } from "./request-utils" +import {useConfig} from "../ConfigProvider"; +import {useMutation, useQuery, useQueryClient} from "react-query"; + +export const useWebhooks = () =>{ + const queryClient = useQueryClient(); + const config = useConfig(); + return useQuery( + "webhooks", + () => fetch_(`${config.BACKEND_HOST}/api/webhook/`), + { + onSuccess: (data) => { + if(data) + queryClient.setQueryData("webhooks", data); + } + } + ); +}; + +export const useCreateWebhook = () =>{ + const config = useConfig(); + const queryClient = useQueryClient(); + + return useMutation( + (data) => fetch_(`${config.BACKEND_HOST}/api/webhook/`, { + method: "POST", + body: JSON.stringify({ + "hook_url": data + }), + }), + { + onSuccess: () => { + queryClient.invalidateQueries("webhooks"); + } + } + ); +}; + +export const useUpdateWebhook = () =>{ + const config = useConfig(); + const queryClient = useQueryClient(); + return useMutation( + ({id, data}) => fetch_(`${config.BACKEND_HOST}/api/webhook/${id}`, { + method: "PATCH", + body: JSON.stringify(data) + }), + { + onSuccess: () => { + queryClient.invalidateQueries("webhooks"); + } + } + ); +}; + +export const useDeleteWebhook = () => { + const config = useConfig(); + const queryClient = useQueryClient(); + return useMutation( + (id) => fetch_(`${config.BACKEND_HOST}/api/webhook/${id}`, { + method: "DELETE", + }), + { + onSuccess: () => { + queryClient.invalidateQueries("webhooks"); + } + } + ) +} + +export const useWebhookSettings = () => { + const config = useConfig(); + const queryClient = useQueryClient(); + return useQuery( + "webhook_setting", + () => fetch_(`${config.BACKEND_HOST}/api/webhook/setting/`), + { + onSuccess: (data) => { + if(data){ + for(const setting of data){ + queryClient.setQueryData(["webhook_setting", setting.id], setting); + queryClient.setQueryData(["webhook_setting_path_id", setting.path_id], setting); + } + } + } + } + ); +}; + +export const useWebhookSetting = (setting_id) => { + const config = useConfig(); + const queryClient = useQueryClient(); + return useQuery( + ["webhook_setting", setting_id], + () => fetch_(`${config.BACKEND_HOST}/api/webhook/setting/${setting_id}`), + { + enabled: !!setting_id, + onSuccess: (res) => { + if(res) + queryClient.setQueryData(["webhook_setting_path_id", res.path_id], res); + } + }); +}; + +export const useWebhookSettingByPathId = (pathId) => { + const config = useConfig(); + const queryClient = useQueryClient(); + return useQuery( + ["webhook_setting_path_id", pathId], + () => fetch_(`${config.BACKEND_HOST}/api/webhook/setting/path/${pathId}`), + { + enabled: !!pathId, + onSuccess: (res) => { + if(res) + queryClient.setQueryData(["webhook_setting", res.id], res); + } + }); +}; + +export const useCreateWebhookSetting = () => { + const config = useConfig(); + const queryClient = useQueryClient(); + return useMutation( + (data) => fetch_(`${config.BACKEND_HOST}/api/webhook/setting/`, { + method: "POST", + body: JSON.stringify(data) + }),{ + onSuccess: (res) => { + queryClient.invalidateQueries(["webhook_setting", res.id]); + queryClient.invalidateQueries(["webhook_setting_path_id", res.path_id]); + queryClient.invalidateQueries("webhook_setting"); + } + } + ); +}; + +export const useUpdateWebhookSetting = () => { + const config = useConfig(); + const queryClient = useQueryClient(); + return useMutation( + ({id, data}) => fetch_(`${config.BACKEND_HOST}/api/webhook/setting/${id}`, { + method: "PATCH", + body: JSON.stringify(data) + }),{ + onSuccess: (res, variables) => { + queryClient.invalidateQueries(["webhook_setting", variables.id]); + queryClient.invalidateQueries(["webhook_setting_path_id", variables.path_id]); + queryClient.invalidateQueries("webhook_setting"); + } + } + ); +}; + + +export const useDeleteWebhookSetting = () => { + const config = useConfig(); + const queryClient = useQueryClient(); + return useMutation( + (id) => fetch_(`${config.BACKEND_HOST}/api/webhook/setting/${id}`, { + method: "DELETE", + }), + { + onSuccess: (res, variables) => { + queryClient.invalidateQueries(["webhook_setting", variables.id]); + queryClient.invalidateQueries(["webhook_setting_path_id", variables.path_id]); + queryClient.invalidateQueries("webhook_setting"); + } + } + ); +}; +