diff --git a/apps/http-backend/src/routes/userRoutes/userRoutes.ts b/apps/http-backend/src/routes/userRoutes/userRoutes.ts index 84c073c..490bf4d 100644 --- a/apps/http-backend/src/routes/userRoutes/userRoutes.ts +++ b/apps/http-backend/src/routes/userRoutes/userRoutes.ts @@ -12,6 +12,7 @@ import { NodeSchema, NodeUpdateSchema, TriggerUpdateSchema, + workflowUpdateSchema, } from "@repo/common/zod"; import { GoogleSheetsNodeExecutor } from "@repo/nodes"; const router: Router = Router(); @@ -133,56 +134,6 @@ router.get( } ); -// //------------------------------ GET CREDENTIALS ----------------------------- - -// router.get('/getCredentials/:type', -// userMiddleware, -// async (req: AuthRequest, res) =>{ -// try{ -// console.log("user from getcredentials: ",req.user) -// if(!req.user){ -// return res.status(statusCodes.BAD_REQUEST).json({ -// message: "User is not Loggedin" -// }) -// } -// const userId = req.user.sub; -// const type = req.params.type -// console.log(userId," -userid") -// if(!type || !userId){ -// return res.status(statusCodes.BAD_REQUEST).json({ -// message: "Incorrect type Input", -// }); -// } -// const executor = new GoogleSheetsNodeExecutor() -// const response = await executor.getAllCredentials(userId,type) -// // console.log( typeof(response)); -// // console.log("response: ",response) -// const authUrl = typeof response === 'string' ? response : null -// // console.log(authUrl); - -// const credentials = response instanceof Object ? response : null -// // console.log(credentials) -// if(authUrl){ -// return res.status(statusCodes.OK).json({ -// message: "Credentials not found create credentials using this auth url", -// Data: authUrl, -// }); -// } -// else return res.status(statusCodes.OK).json({ -// message: "Credentials Fetched succesfully", -// Data: credentials, -// }); -// } -// catch(e){ -// console.log("Error Fetching the credentials ", e instanceof Error ? e.message : "Unkown reason"); -// return res -// .status(statusCodes.INTERNAL_SERVER_ERROR) -// .json({ message: "Internal server from fetching the credentials" }); -// } -// } -// ); - -//------------------------------ GET CREDENTIALS ----------------------------- router.get( "/getCredentials/:type", @@ -290,31 +241,23 @@ router.post( "/create/workflow", userMiddleware, async (req: AuthRequest, res) => { - try { - if (!req.user) { - return res.status(statusCodes.BAD_REQUEST).json({ - message: "User is not logged in ", - }); - } - const Data = req.body; - const ParsedData = WorkflowSchema.safeParse(Data); - const UserID = req.user.id; - // const UserID = "343c9a0a-9c3f-40d0-81de-9a5969e03f92"; - // Ensure that the required fields are present in the parsed data and create the workflow properly. - if (!ParsedData.success) { - return res.status(statusCodes.BAD_REQUEST).json({ - message: "Incorrect Workflow Inputs", - }); - } + + if (!req.user) { + return res.status(statusCodes.BAD_REQUEST).json({ + message: "User is not logged in ", + }); + } + const Data = req.body; + const ParsedData = WorkflowSchema.safeParse(Data); + const UserID = req.user.id; + // const UserID = "343c9a0a-9c3f-40d0-81de-9a5969e03f92"; + // Ensure that the required fields are present in the parsed data and create the workflow properly. + if (!ParsedData.success) { + return res.status(statusCodes.BAD_REQUEST).json({ + message: "Incorrect Workflow Inputs", + }); + } try { const createWorkflow = await prismaClient.workflow.create({ - // Example JSON body to test this route: - /* - { - "Name":"workflow-1", - "UserId": "", - "Config":[{}] - } - */ data: { user: { connect: { id: UserID }, @@ -328,13 +271,16 @@ router.post( message: "Workflow Created Successfully", Data: createWorkflow, }); - } catch (e) { + } catch (e: any) { console.log("Internal server error from creating aworkflow", e); return res.status(statusCodes.INTERNAL_SERVER_ERROR).json({ message: "Internal Server Error from CCreating Workflow", + error: e instanceof Error ? e.message : "Unknown error" }); } - } + } + + ); // ------------------------------------ FETCHING WORKFLOWS ----------------------------------- @@ -444,10 +390,52 @@ router.get( } ); -// router.put("/workflow/update" , userMiddleware , (req : AuthRequest , res : Response) => { +router.put("/workflow/update", userMiddleware, async (req: AuthRequest, res: Response) => { -// }) -// ---------------------------------------- INSERTING DATA INTO NODES/ TRIGGER TABLE----------------------------- + const data = req.body; + const parsedData = workflowUpdateSchema.safeParse(data); + + if (!parsedData.success) { + return res.status(statusCodes.BAD_REQUEST).json({ + message: "Incorrect Input", + error: parsedData.error + }) + } + const workflowId = parsedData.data.workflowId; + if (!req.user) { + return res.status(statusCodes.UNAUTHORIZED).json({ + message: "User Not Authenticated" + }) + } + const userId = req.user.id + try { + const workflowValid = await prismaClient.workflow.findFirst({ + where: { id: workflowId, userId: userId } + }) + if (!workflowValid) { + return res.status(statusCodes.BAD_REQUEST).json({ + message: "Didn't find the workflow" + }) + } + const update = await prismaClient.workflow.update({ + where: { id: workflowId, userId: userId }, + data: { + // Nodes: parsedData.data.nodes, + Edges: parsedData.data.edges + } + }) + return res.status(statusCodes.ACCEPTED).json({ + message: "Workflow Updated Succesfuly", + Data: update + }) + } catch (error: any) { + console.log("Error updating workflow:", error); + return res.status(statusCodes.INTERNAL_SERVER_ERROR).json({ + message: "Internal Server Error from Updating workflow", + error: error instanceof Error ? error.message : "Unknown error" + }) + } +}) router.post( "/create/trigger", diff --git a/apps/web/app/lib/api.ts b/apps/web/app/lib/api.ts index 97a6e7d..93f13b4 100644 --- a/apps/web/app/lib/api.ts +++ b/apps/web/app/lib/api.ts @@ -16,11 +16,20 @@ export const api = { headers: { "Content-Type": "application/json" }, } ), - get: async (id: string) => - await axios.get(`${BACKEND_URL}/user/workflow/${id}`, { + get: async (id: string) => { + return await axios.get(`${BACKEND_URL}/user/workflow/${id}`, { withCredentials: true, headers: { "Content-Type": "application/json" }, - }), + }); + }, + put: async (data: any) => { + return await axios.put(`${BACKEND_URL}/user/workflow/update`, + data, + { + withCredentials: true, + headers: { "Content-Type": "application/json" }, + }) + } }, triggers: { getAvailable: async () => @@ -59,7 +68,7 @@ export const api = { }, Credentials: { getCredentials: async (type: string) => { - const res = await axios.get(`${BACKEND_URL}/user/getCredentials/${type}`, { + const res = await axios.get(`${BACKEND_URL}/user/getCredentials/${type}`, { withCredentials: true, headers: { "Content-Type": "application/json" }, }); diff --git a/apps/web/app/workflows/[id]/page.tsx b/apps/web/app/workflows/[id]/page.tsx index e0e5e12..9156602 100644 --- a/apps/web/app/workflows/[id]/page.tsx +++ b/apps/web/app/workflows/[id]/page.tsx @@ -50,6 +50,335 @@ export default function WorkflowCanvas() { const nodeTypes = { customNode: BaseNode, }; + useEffect(() => { + const loadWorkflows = async () => { + try { + const workflows = await api.workflows.get(workflowId); + console.log("This is the New Triggering trigger Data is",workflows.data.Data.Trigger.AvailableTriggerID); + + // 1. SAFEGUARD: Default to empty arrays to prevent crashes + const dbNodes = workflows.data.Data.nodes || []; + const dbEdges = workflows.data.Data.Edges || []; + const Trigger = workflows.data.Data.Trigger; + console.log("This is the trigger Data is",workflows.data.Data); + + console.log("The Data of Trigger is",Trigger) + // 2. CREATE TRIGGER NODE + // We assume the trigger is always the starting point + const triggerNode = { + id: Trigger.id, + type: "customNode", + // Default to top center if no position exists + position: Trigger.position || { x: 250, y: 50 }, + data: { + label: Trigger.name || Trigger.data?.label || "Trigger", + icon: Trigger.data?.icon || "⚡", // Lightning icon for trigger + nodeType: "trigger", + isConfigured: true, + onConfigure: () => handleNodeConfigure({ + id: Trigger.id, + // ... pass your trigger config props here + }) + }, + }; + if (!Trigger) { + + console.error("No trigger found in workflow data"); + return; + } + + // 3. TRANSFORM ACTION NODES + const transformedNodes = dbNodes.map((node: any) => ({ + id: node.id, + type: "customNode", + position: node.position || { x: 0, y: 0 }, + data: { + label: node.data?.label || node.name || "Unknown", + icon: node.data?.icon || "⚙️", + nodeType: "action", + // ... rest of your data mapping + onConfigure: () => handleNodeConfigure({ + id: node.id, + name: node.data?.label || node.Name, + type: "action", + actionType: node.AvailableNodeId + }) + } + })); + + // 4. CALCULATE PLACEHOLDER POSITION + // Find the "last" node to attach the placeholder to. + // If we have actions, use the last action. If not, use the trigger. + const lastNode = transformedNodes.length > 0 + ? transformedNodes[transformedNodes.length - 1] + : triggerNode; + + // Position placeholder 150 pixels below the last node + const placeholderPosition = { + x: lastNode.position.x, + y: lastNode.position.y + 150 + }; + + const actionPlaceholder = { + id: `action-placeholder-${Date.now()}`, + type: "customNode", + position: placeholderPosition, + data: { + label: "Add Action", + icon: "➕", + isPlaceholder: true, + nodeType: "action", + onConfigure: () => setActionOpen(true), + }, + }; + + // 5. COMBINE NODES + const finalNodes = [triggerNode, ...transformedNodes, actionPlaceholder]; + + // 6. MANAGE EDGES + // If we have DB edges, use them. + // If not, we should auto-connect Trigger -> First Node -> Placeholder + let finalEdges = [...dbEdges]; + + // Auto-connect Placeholder to the last node (visual guide) + const placeholderEdge = { + id: `e-${lastNode.id}-${actionPlaceholder.id}`, + source: lastNode.id, + target: actionPlaceholder.id, + type: 'default', // or your custom edge type + animated: true, // Make it dashed/animated to indicate it's temporary + }; + + // Ensure Trigger is connected to first action if DB edges are empty & actions exist + if (dbEdges.length === 0 && transformedNodes.length > 0) { + const triggerEdge = { + id: `e-${triggerNode.id}-${transformedNodes[0].id}`, + source: triggerNode.id, + target: transformedNodes[0].id, + type: 'default' + }; + finalEdges.push(triggerEdge); + } + + finalEdges.push(placeholderEdge); + + setNodes(finalNodes); + setEdges(finalEdges); + + } catch (error) { + console.error("Failed to load workflow:", error); + } + }; + + loadWorkflows(); + }, [workflowId]); + + + // useEffect(() => { + // const loadWorkflows = async () => { + // const workflows = await api.workflows.get(workflowId); + // console.log("This is the Data from workflow form DB",workflows.data.Data) + // const dbNodes = workflows.data.Data.nodes; + // const Trigger = workflows.data.Data.trigger; + // console.log("The Trigger is",Trigger) + // const dbEdges = workflows.data.Data.Edges || []; + // console.log("These are the DbNodes ",dbNodes) + // console.log("These are the DB Edges ",dbEdges) + // const transformedNodes = dbNodes.map((node: any) => ({ + // id: node.id, + // type: "customNode", // ⚠️ This was missing! + // position: node.position || { x: 0, y: 0 }, + // data: { + // label: node.data?.label || node.name || "Unknown", + // icon: node.data?.icon || "⚙️", + // nodeType: node.data?.nodeType || (node.TriggerId ? "trigger" : "action"), + // isPlaceholder: node.data?.isPlaceholder || false, + // isConfigured: node.data?.isConfigured || false, + // config: node.data?.config || node.Config || {}, + + // // ⚠️ CRITICAL: Re-attach callbacks + // onConfigure: () => handleNodeConfigure({ + // id: node.id, + // name: node.data?.label || node.Name, + // type: node.data?.nodeType || (node.TriggerId ? "trigger" : "action"), + // actionType: node.AvailableNodeId || node.AvailableTriggerID + // }) + // } + // })); + // const actionPlaceholder = { + // id: `action-placeholder-${Date.now()}`, + // type: "customNode", + // position: { x: 550, y: 200 }, + // data: { + // label: "Add Action", + // icon: "➕", + // isPlaceholder: true, + // nodeType: "action", + // config: {}, + // onConfigure: () => setActionOpen(true), + // }, + // }; + + // console.log("This Data after Tranforming",transformedNodes) + // setNodes([...transformedNodes, actionPlaceholder]); + // setEdges(dbEdges); + // } + + // loadWorkflows(); + // }, [workflowId]) + + + + // useEffect(() => { + // const loadWorkflows = async () => { + // const workflows = await api.workflows.get(workflowId); + // const dbNodes = workflows.data.Data.nodes; + // // Fix: Normalize dbEdges to always be an array + // let dbEdges = workflows.data.Data.edges; + // if (!Array.isArray(dbEdges)) { + // // Try alternative key (if backend provides Edges sometimes) + // dbEdges = workflows.data.Data.Edges; + // } + // if (!Array.isArray(dbEdges)) { + // dbEdges = []; + // } + + // // Get the trigger (from the "Trigger" key in backend response) + // const trigger = workflows.data.Data.Trigger; + + // // Transform trigger to a node if it exists and not already a node + // let triggerNode = null; + // if (trigger) { + // triggerNode = { + // id: trigger.id || "trigger-node", // fallback in case trigger has no id + // type: "customNode", + // position: trigger.config?.position || { x: 250, y: 50 }, + // data: { + // label: trigger.name || trigger.Name || "Trigger", + // icon: "⚡", + // nodeType: "trigger", + // isPlaceholder: false, + // isConfigured: true, + // config: trigger.config || trigger.Config || {}, + // onConfigure: () => handleNodeConfigure({ + // id: trigger.id, + // name: trigger.name || trigger.Name, + // type: "trigger", + // actionType: trigger.AvailableTriggerID // triggers have this, not actions + // }), + // } + // }; + // } + + // // Transform database nodes to React Flow format, excluding any possible trigger node + // const transformedNodes = dbNodes + // .filter((node: any) => !node.TriggerId) // trigger node will come separately + // .map((node: any) => ({ + // id: node.id, + // type: "customNode", + // position: node.position || node.Config?.position || { x: 0, y: 0 }, + // data: { + // label: node.Name || node.data?.label || "Unknown", + // icon: node.data?.icon || "⚙️", + // nodeType: node.TriggerId ? "trigger" : "action", + // isPlaceholder: false, // Loaded nodes are never placeholders + // isConfigured: node.data?.isConfigured || false, + // config: node.Config || node.data?.config || {}, + // onConfigure: () => handleNodeConfigure({ + // id: node.id, + // name: node.Name || node.data?.label, + // type: node.TriggerId ? "trigger" : "action", + // }) + // } + // })); + + // // Combine trigger node (if present) and transformed nodes + // let allNodes = []; + // if (triggerNode) { + // allNodes.push(triggerNode); + // } + // allNodes = [...allNodes, ...transformedNodes]; + + // // Find action nodes for logic + // const realActionNodes = allNodes.filter((n: any) => n.data.nodeType === "action"); + + // // Find (now explicit) trigger node ref + // const triggerNodeRef = allNodes.find((n: any) => n.data.nodeType === "trigger"); + // let actionPlaceholder: any; + + // if (realActionNodes.length === 0) { + // // No real actions: place after trigger + // actionPlaceholder = { + // id: "action-placeholder-end", + // type: "customNode", + // position: triggerNodeRef + // ? { x: triggerNodeRef.position.x + 300, y: triggerNodeRef.position.y + 150 } + // : { x: 550, y: 200 }, + // data: { + // label: "Add Action", + // icon: "➕", + // isPlaceholder: true, + // nodeType: "action", + // onConfigure: () => setActionOpen(true), + // } + // }; + // } else { + // // Place after last real action + // const lastAction = realActionNodes[realActionNodes.length - 1]; + // actionPlaceholder = { + // id: "action-placeholder-end", + // type: "customNode", + // position: { + // x: lastAction.position.x + 200, + // y: lastAction.position.y + 150 + // }, + // data: { + // label: "Add Action", + // icon: "➕", + // isPlaceholder: true, + // nodeType: "action", + // onConfigure: () => setActionOpen(true), + // } + // }; + // } + + // // Build up-to-date edge array + // let updatedEdges = Array.isArray(dbEdges) ? [...dbEdges] : []; + // if (realActionNodes.length > 0) { + // // Connect last action to action-placeholder + // const lastAction = realActionNodes[realActionNodes.length - 1]; + // updatedEdges = [ + // ...updatedEdges, + // { + // id: `edge-${lastAction.id}-action-placeholder-end`, + // source: lastAction.id, + // target: "action-placeholder-end", + // type: "default" + // } + // ]; + // } else if (triggerNodeRef) { + // // No actions yet, connect trigger to action-placeholder + // updatedEdges = [ + // ...updatedEdges, + // { + // id: `edge-${triggerNodeRef.id}-action-placeholder-end`, + // source: triggerNodeRef.id, + // target: "action-placeholder-end", + // type: "default" + // } + // ]; + // } + + // // Add placeholder node + // allNodes = [...allNodes, actionPlaceholder]; + + // setNodes(allNodes); + // setEdges(updatedEdges); + // }; + + // if (workflowId) loadWorkflows(); + // }, [workflowId]); + + const handleNodeConfigure = (node: any) => { setSelectedNode(node); @@ -70,7 +399,7 @@ export default function WorkflowCanvas() { TriggerId: change.id, Config: { ...(typeof changedNode.data.config === "object" && - changedNode.data.config !== null + changedNode.data.config !== null ? changedNode.data.config : {}), position: change.position, @@ -89,128 +418,6 @@ export default function WorkflowCanvas() { setError(error); } }; - // const handleActionSelection = async (action: any) => { - // try { - // // 1. Find trigger node - // const triggerNode = nodes.find( - // (n) => n.data.nodeType === "trigger" && !n.data.isPlaceholder - // ); - - // if (!triggerNode) { - // throw new Error("No trigger found"); - // } - - // const triggerId = triggerNode.id; - - // // 2. Find existing action nodes - // const existingActionNodes = nodes.filter( - // (n) => n.data.nodeType === "action" && !n.data.isPlaceholder - // ); - - // // 3. Calculate position for new action node - // let newPosition; - // if (existingActionNodes.length === 0) { - // // First action: place below trigger - // newPosition = { - // x: triggerNode.position.x, - // y: triggerNode.position.y + 150 // 150px below trigger - // }; - // } else { - // // Subsequent actions: place below last action - // const lastAction = existingActionNodes[existingActionNodes.length - 1]; - // newPosition = { - // x: lastAction!.position.x, - // y: lastAction!.position.y + 150 // 150px below last action - // }; - // } - - // const sourceNodeId = existingActionNodes.length > 0 - // ? existingActionNodes[existingActionNodes.length - 1]!.id - // : triggerId; - - // // 4. Call API - // const result = await api.nodes.create({ - // Name: action.name, - // AvailableNodeId: action.id, - // Config: { - // Position : newPosition - // }, - // WorkflowId: workflowId, - // Position: 1, // ✅ Dynamic position! - - // }); - - // const actionId = result.data.data.id; - - // // 5. Create action node with calculated position - // const newActionNode = { - // id: actionId, - // type: "customNode", - // position: newPosition, // ✅ Use calculated position - // data: { - // label: action.name, - // icon: action.icon, - // isPlaceholder: false, - // nodeType: "action", // ✅ Fixed from earlier - // config: {}, - // onConfigure: () => console.log("Configure", actionId), - // }, - // }; - - // // 6. Create placeholder below new action - // const placeholderPosition = { - // x: newPosition.x, - // y: newPosition.y + 150 // 150px below new action - // }; - - // const newPlaceholder = { - // id: `action-placeholder-${Date.now()}`, - // type: "customNode", - // position: placeholderPosition, - // data: { - // label: "Add Action", - // icon: "➕", - // isPlaceholder: true, - // nodeType: "action", - // config: {}, - // onConfigure: () => setActionOpen(true), - // }, - // }; - - // // Rest of your code stays the same... - // setNodes((prevNodes) => { - // const filtered = prevNodes.filter( - // (n) => !(n.data.isPlaceholder && n.data.nodeType === "action") - // ); - // return [...filtered, newActionNode, newPlaceholder]; - // }); - - // setEdges((prevEdges) => { - // const filtered = prevEdges.filter((e) => { - // const targetNode = nodes.find(n => n.id === e.target); - // return !(targetNode?.data.isPlaceholder && targetNode?.data.nodeType === "action"); - // }); - - // return [ - // ...filtered, - // { - // id: `e-${sourceNodeId}-${actionId}`, - // source: sourceNodeId, - // target: actionId, - // }, - // { - // id: `e-${actionId}-${newPlaceholder.id}`, - // source: actionId, - // target: newPlaceholder.id, - // }, - // ]; - // }); - - // setActionOpen(false); - // } catch (error: any) { - // setError(error); - // } - // }; const handleActionSelection = async (action: any) => { try { @@ -250,6 +457,7 @@ export default function WorkflowCanvas() { // onConfigure: () => console.log("Configure", actionId), }, + }; // 3. Create NEW placeholder @@ -402,6 +610,28 @@ export default function WorkflowCanvas() { setError(error); } }; + + + const handleSave = async () => { + const payload = { + workflowId: workflowId, + nodes: nodes, + edges: edges + }; + console.log("THe Nodes are ", payload.nodes) + console.log("The payload in handleSave is ", payload); + try { + const response = await api.workflows.put({ + workflowId: workflowId, + nodes : nodes, + edges: edges + }); + // Optionally, you can show a message or update UI on success + console.log("Workflow updated successfully:", response.data); + } catch (error: any) { + setError(error); + } + } return (