Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/http-backend/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ app.use(cookieParser());

app.use("/user" , userRouter)
app.use('/node', sheetRouter)
app.use('/oauth/google', googleAuth)
app.use('/auth/google', googleAuth)

Copilot AI Mar 9, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changing the mounted OAuth router path to /auth/google appears to break existing OAuth URLs/redirect URIs that still use /oauth/google/... (e.g. apps/http-backend/src/routes/google_callback.ts defaults to http://localhost:3002/oauth/google/callback). Please update the redirect URI defaults and any frontend/back-end links to match the new mount path (or keep the old path as an alias).

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for old route references in the codebase
rg -n 'oauth/google' --type ts --type tsx --type js
# Search for new route references
rg -n 'auth/google' --type ts --type tsx --type js

Repository: Dev-Pross/BuildFlow

Length of output: 118


🏁 Script executed:

#!/bin/bash
# Search for old route references in the codebase (broader search)
echo "=== Searching for 'oauth/google' references ==="
rg -n 'oauth/google' -t ts -t js

echo -e "\n=== Searching for 'auth/google' references ==="
rg -n 'auth/google' -t ts -t js

echo -e "\n=== Checking the index.ts file context ==="
head -50 apps/http-backend/src/index.ts

Repository: Dev-Pross/BuildFlow

Length of output: 3051


🏁 Script executed:

#!/bin/bash
# Check the googleAuth handler to see what routes it exposes
echo "=== Content of google_callback.ts ==="
cat -n apps/http-backend/src/routes/google_callback.ts

echo -e "\n=== Checking for any callback endpoint definitions ==="
rg -n 'callback|router\.' apps/http-backend/src/routes/google_callback.ts

Repository: Dev-Pross/BuildFlow

Length of output: 11672


Critical: Route change breaks OAuth callback flow—update GOOGLE_REDIRECT_URI environment variable.

The Google OAuth route was moved from /oauth/google to /auth/google, but the default GOOGLE_REDIRECT_URI in google_callback.ts (line 22) still points to http://localhost:3002/oauth/google/callback. This causes a mismatch: Google will attempt to redirect to /oauth/google/callback, but the server callback endpoint is now at /auth/google/callback.

Update the GOOGLE_REDIRECT_URI environment variable to http://localhost:3002/auth/google/callback and ensure it matches the redirect URI registered in your Google Cloud Console OAuth application settings.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/http-backend/src/index.ts` at line 37, The Google OAuth redirect URI is
mismatched because the route was changed to use app.use('/auth/google',
googleAuth) but the environment value used in google_callback.ts
(GOOGLE_REDIRECT_URI) still points to
http://localhost:3002/oauth/google/callback; update the GOOGLE_REDIRECT_URI to
http://localhost:3002/auth/google/callback (and update any .env or config
loading code that populates GOOGLE_REDIRECT_URI) and confirm the same redirect
URI is registered in the Google Cloud Console for the OAuth client so the
callback handled by the googleAuth route (and its callback handler in
google_callback.ts) receives the OAuth response.

app.use('/execute', execRouter)

const PORT= 3002
Expand Down
35 changes: 20 additions & 15 deletions apps/http-backend/src/routes/userRoutes/userRoutes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,7 @@ router.get(
try {
if (!req.user)
return res
.status(statusCodes.BAD_GATEWAY)
.status(statusCodes.UNAUTHORIZED)
.json({ message: "User isnot logged in /not authorized" });
const userId = req.user.sub;

Expand All @@ -373,7 +373,7 @@ router.get(
},
include: {
Trigger: true,
nodes: { orderBy: { position: "asc" } },
nodes: { orderBy: { stage: "asc" } },
},
});
if (!getWorkflow) {
Expand Down Expand Up @@ -441,6 +441,9 @@ router.put("/workflow/update", userMiddleware, async (req: AuthRequest, res: Res
}
})

//------------------------------------------ TRIGGER AND NODE CREATION ------------------------------

//TRIGGER CREATION
router.post(
"/create/trigger",
userMiddleware,
Expand All @@ -465,6 +468,7 @@ router.post(
AvailableTriggerID: dataSafe.data.AvailableTriggerID,
config: dataSafe.data.Config,
workflowId: dataSafe.data.WorkflowId,
Position: dataSafe.data.Position || {}
// trigger type pettla db lo ledu aa column
},
});
Expand Down Expand Up @@ -499,6 +503,7 @@ router.post(
}
);

//NODE CREATION
router.post(
"/create/node",
userMiddleware,
Expand All @@ -510,7 +515,7 @@ router.post(
});
}
const data = req.body;
console.log(" from http-backeden", data);
// console.log(" from http-backeden", data);

const dataSafe = NodeSchema.safeParse(data);
console.log("The error is ", dataSafe.error);
Expand All @@ -524,19 +529,18 @@ router.post(
// Config must be valid JSON (not an empty string)
// const stage = dataSafe.data.Position
console.log("This is from the backend log of positions", dataSafe.data.position)
const { credentialId, ...restConfig } = dataSafe.data.Config;
const createdNode = await prismaClient.node.create({
data: {
name: dataSafe.data.Name,
workflowId: dataSafe.data.WorkflowId,
config: restConfig || {},
config: dataSafe.data.Config || {},
stage: Number(dataSafe.data.stage ?? 0),
position: {
x: dataSafe.data.position.x,
y: dataSafe.data.position.y
},
AvailableNodeID: dataSafe.data.AvailableNodeId,
CredentialsID: credentialId
CredentialsID: dataSafe.data.CredentialId
},
});

Expand All @@ -556,8 +560,7 @@ router.post(

// ------------------------- UPDATE NODES AND TRIGGES ---------------------------

router.put(
"/update/node",
router.put("/update/node",
userMiddleware,
async (req: AuthRequest, res: Response) => {
try {
Expand All @@ -578,10 +581,11 @@ router.put(
const updateNode = await prismaClient.node.update({
where: { id: dataSafe.data.NodeId },
data: {
position: dataSafe.data.position,
config: dataSafe.data.Config ,
CredentialsID: dataSafe.data.Config?.credentialId || null
},

...(dataSafe.data.position !== undefined ? { position: dataSafe.data.position } : {}),
...(dataSafe.data.Config !== undefined ? { config: dataSafe.data.Config } : {}),
...(dataSafe.data.Config?.credentialId ? { CredentialsID: dataSafe.data.Config.credentialId } : {})
}
Comment on lines +584 to +588

Copilot AI Mar 9, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The updated node update logic only sets CredentialsID when Config.credentialId is truthy, but never clears it when credentials are removed/emptied in the config. This can leave a stale credential relationship in the DB. Consider explicitly setting CredentialsID to null when Config is provided and credentialId is missing/empty (or accept a top-level credential field for updates).

Copilot uses AI. Check for mistakes.
});

if (updateNode)
Expand All @@ -598,8 +602,7 @@ router.put(
}
);

router.put(
"/update/trigger",
router.put("/update/trigger",
userMiddleware,
async (req: AuthRequest, res: Response) => {
try {
Expand All @@ -619,7 +622,9 @@ router.put(
const updatedTrigger = await prismaClient.trigger.update({
where: { id: dataSafe.data.TriggerId },
data: {
config: dataSafe.data.Config,
...(dataSafe.data.Config !== undefined ? { config: dataSafe.data.Config} : {}) ,
...(dataSafe.data.CredentialID !== undefined ? { CredentialsID: dataSafe.data.CredentialID} : {}),
...(dataSafe.data.Position !== undefined ? {Position: dataSafe.data.Position} : {})
},
});

Expand Down
29 changes: 16 additions & 13 deletions apps/web/app/components/providers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
import * as React from "react"
import { ThemeProvider as NextThemesProvider } from "next-themes"
import { Provider as ReduxProvider } from "react-redux"
import { store } from "@/store"
import { persistor, store } from "@/store"
import { SessionProvider, useSession } from "next-auth/react"
import { useAppDispatch } from "../hooks/redux"
import { userAction } from "@/store/slices/userSlice"
import { PersistGate } from "redux-persist/lib/integration/react"

function SessionSync(){
const { status, data } = useSession();
Expand Down Expand Up @@ -34,18 +35,20 @@ export function Providers({ children }: { children: React.ReactNode }) {
return (

<ReduxProvider store={store}>
<SessionProvider>
<NextThemesProvider
attribute="class"
defaultTheme="system"
enableSystem
disableTransitionOnChange
enableColorScheme
>
<SessionSync />
{children}
</NextThemesProvider>
</SessionProvider>
<PersistGate loading={null} persistor={persistor}>
<SessionProvider>
<NextThemesProvider
attribute="class"
defaultTheme="system"
enableSystem
disableTransitionOnChange
enableColorScheme
>
<SessionSync />
{children}
</NextThemesProvider>
</SessionProvider>
</PersistGate>
</ReduxProvider>
)
}
83 changes: 83 additions & 0 deletions apps/web/app/hooks/useAutoSave.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
"use client"
import { useEffect, useState } from "react";
import { useAppDispatch, useAppSelector } from "./redux";
import { api } from "../lib/api";
import { store } from "@/store";
import { workflowActions } from "@/store/slices/workflowSlice";

type Status = "saved" | "error" | "saving"
export function useAutoSave(workflowId: string){
const [saveStatus, setSaveStatus] = useState<Status>("saved")
const dispatch = useAppDispatch()
const isChangedState = useAppSelector(s=> s.workflow.isChanged)
const hasUnChanged = isChangedState.trigger || isChangedState.edges || isChangedState.nodes;

const displayStatus = saveStatus === 'saving' ? 'Saving...' :
saveStatus === 'error' ? 'Save Error' :
hasUnChanged ? 'Unsaved Changes' : 'Saved'

const batchSave = async()=>{
const { data, isChanged, changedNodeIds } = store.getState().workflow

const anyChanges = isChanged.edges || isChanged.nodes || isChanged.trigger;

if(!anyChanges || !data.workflowId) return;

setSaveStatus("saving")

try{
if(isChanged.nodes && data.nodes){
const changedNodes = data.nodes.filter(n => changedNodeIds.includes(n.NodeId))
await Promise.all(
changedNodes.map(node => api.nodes.update({
NodeId: node.NodeId,
Config: node.Config,
position: node.position
}))
)
}
if(isChanged.trigger){
const trigger = data.trigger;
if(trigger)
await api.triggers.update({
TriggerId: trigger.TriggerId,
Config: trigger.Config,
Position: trigger.position
})
}
if(isChanged.edges){
const edges = data.edges;
if(edges)
await api.workflows.put({workflowId: workflowId,edges})
}
setSaveStatus("saved")
dispatch(workflowActions.markSynced())
}catch(e){
setSaveStatus("error")
console.error("error while auto saving: ", e instanceof Error ? e.message : "")
}
}

useEffect(()=>{
const interval = setInterval(batchSave,30000);

const handleBeforeUnload = (e: BeforeUnloadEvent) =>{
const { isChanged } = store.getState().workflow
if(isChanged.edges || isChanged.nodes || isChanged.trigger){
e.preventDefault()
batchSave();
}
Comment on lines +64 to +69

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Async batchSave() in beforeunload won't complete before page unload.

beforeunload fires synchronously, and calling an async function here doesn't block the browser from navigating away. While e.preventDefault() shows a confirmation dialog, the batchSave() promise won't resolve in time, potentially losing user data.

Consider using navigator.sendBeacon for fire-and-forget persistence, or warn users about unsaved changes instead of attempting a save.

Alternative: warn only, don't attempt async save
        const handleBeforeUnload = (e: BeforeUnloadEvent) =>{
            const { isChanged } = store.getState().workflow
            if(isChanged.edges || isChanged.nodes || isChanged.trigger){
                e.preventDefault()
-                batchSave();
+                // Browser will show "unsaved changes" dialog
+                // Async save won't complete before unload; rely on interval/manual save
            }
        }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const handleBeforeUnload = (e: BeforeUnloadEvent) =>{
const { isChanged } = store.getState().workflow
if(isChanged.edges || isChanged.nodes || isChanged.trigger){
e.preventDefault()
batchSave();
}
const handleBeforeUnload = (e: BeforeUnloadEvent) =>{
const { isChanged } = store.getState().workflow
if(isChanged.edges || isChanged.nodes || isChanged.trigger){
e.preventDefault()
// Browser will show "unsaved changes" dialog
// Async save won't complete before unload; rely on interval/manual save
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/app/hooks/useAutoSave.ts` around lines 64 - 69, The beforeunload
handler (handleBeforeUnload) currently calls the async batchSave() which won't
complete before the page unload; change the handler to avoid awaiting async
work: detect unsaved changes via store.getState().workflow.isChanged and either
(A) send a synchronous fire-and-forget save using navigator.sendBeacon with the
minimal payload instead of batchSave(), or (B) remove the async call and simply
set e.preventDefault()/e.returnValue to show a confirmation dialog and let the
user choose to stay and trigger batchSave elsewhere; update handleBeforeUnload
to use navigator.sendBeacon when available and fall back to the
confirmation-only behavior so data isn't lost.

}

window.addEventListener("beforeunload", handleBeforeUnload)

return ()=>{
clearInterval(interval);
window.removeEventListener("beforeunload", handleBeforeUnload)
batchSave()
}
Comment on lines +74 to +78

Copilot AI Mar 9, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

batchSave() is invoked in the effect cleanup; since batchSave calls setSaveStatus, this can trigger React warnings about setting state on an unmounted component. Consider avoiding state updates during unmount (e.g. guard with an isMounted ref, or split the persistence side-effect from the UI status updates).

Copilot uses AI. Check for mistakes.
}, [workflowId])
Comment on lines +74 to +79

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Cleanup effect calls async batchSave() which won't complete.

When the component unmounts, React won't wait for the batchSave() promise to resolve. This means the final save attempt may be aborted mid-flight. Consider tracking in-flight requests with a ref and only cleaning up after completion, or accept that cleanup saves are best-effort.

Additionally, batchSave is not in the dependency array. Since batchSave is defined inside the component and captures workflowId in its closure, if workflowId changed without re-running the effect, stale closures could occur. However, since workflowId is in the deps, the effect re-runs correctly. Consider wrapping batchSave in useCallback with proper deps for clarity.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/app/hooks/useAutoSave.ts` around lines 74 - 79, The cleanup effect
currently calls batchSave() without awaiting it and omits batchSave from the
deps, risking aborted in-flight saves and stale closures; modify the hook so
batchSave is memoized with useCallback (including workflowId and any other
captured state) and track in-flight requests with a ref (e.g., isSavingRef) so
the cleanup handler (that clears interval and removes handleBeforeUnload) can
either await completion of the in-flight save before returning or explicitly
document that the final save is best-effort; update the effect dependency array
to include the memoized batchSave and ensure handleBeforeUnload/interval
teardown only proceeds after checking the in-flight ref or awaiting the promise
when feasible.


return { saveStatus, batchSave, displayStatus}

}
6 changes: 3 additions & 3 deletions apps/web/app/lib/api.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// lib/api.ts
import axios from "axios";
import { BACKEND_URL, NodeUpdateSchema } from "@repo/common/zod";
import { BACKEND_URL, NodeSchema, NodeUpdateSchema, workflowUpdateSchema } from "@repo/common/zod";
import { TriggerUpdateSchema } from "@repo/common/zod";
import z from "zod";
import { getCredentials } from "../workflow/lib/config";
Expand Down Expand Up @@ -31,7 +31,7 @@ export const api = {
headers: { "Content-Type": "application/json" },
});
},
put: async (data: any) => {
put: async (data: z.infer<typeof workflowUpdateSchema>) => {
return await axios.put(`${BACKEND_URL}/user/workflow/update`,
data,
{
Expand Down Expand Up @@ -70,7 +70,7 @@ export const api = {
withCredentials: true,
headers: { "Content-Type": "application/json" },
}),
create: async (data: any) =>
create: async (data: z.infer<typeof NodeSchema>) =>
await axios.post(`${BACKEND_URL}/user/create/node`, data, {
withCredentials: true,
headers: { "Content-Type": "application/json" },
Expand Down
Loading