Skip to content

Commit 941b26a

Browse files
icecrasher321Vikhyath Mondretigreptile-apps[bot]emir-karabegVikhyath Mondreti
authored
improvement(local-storage): remove use of local storage except for oauth and last active workspace id (#497)
* remove local storage usage * remove migration for last active workspace id * Update apps/sim/app/w/[id]/components/workflow-block/components/sub-block/components/file-selector/components/jira-issue-selector.tsx Add fallback for required scopes Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> * add url builder util * fi * fix lint * lint * modify pre commit hook * fix oauth * get last active workspace working again * new workspace logic works * fetch locks * works now * remove empty useEffect * fix loading issue * skip empty workflow syncs * use isWorkspace in transition flag * add logging * add data initialized flag * fix lint * fix: build error by create a server-side utils * remove migration snapshots * reverse search for workspace based on workflow id * fix lint * improvement: loading check and animation * remove unused utils * remove console logs --------- Co-authored-by: Vikhyath Mondreti <vikhyathmondreti@Vikhyaths-Air.attlocal.net> Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> Co-authored-by: Emir Karabeg <emirkarabeg@berkeley.edu> Co-authored-by: Vikhyath Mondreti <vikhyathmondreti@vikhyaths-air.lan>
1 parent 5eb7b88 commit 941b26a

File tree

36 files changed

+2055
-2047
lines changed

36 files changed

+2055
-2047
lines changed

.husky/pre-commit

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
bunx lint-staged
1+
bun lint

apps/sim/app/api/chat/utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { chat, environment as envTable, userStats, workflow } from '@/db/schema'
1111
import { Executor } from '@/executor'
1212
import type { BlockLog } from '@/executor/types'
1313
import { Serializer } from '@/serializer'
14-
import { mergeSubblockState } from '@/stores/workflows/utils'
14+
import { mergeSubblockState } from '@/stores/workflows/server-utils'
1515
import type { WorkflowState } from '@/stores/workflows/workflow/types'
1616

1717
declare global {

apps/sim/app/api/schedules/execute/route.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import { db } from '@/db'
1919
import { environment, userStats, workflow, workflowSchedule } from '@/db/schema'
2020
import { Executor } from '@/executor'
2121
import { Serializer } from '@/serializer'
22-
import { mergeSubblockState } from '@/stores/workflows/utils'
22+
import { mergeSubblockState } from '@/stores/workflows/server-utils'
2323
import type { WorkflowState } from '@/stores/workflows/workflow/types'
2424

2525
// Add dynamic export to prevent caching

apps/sim/app/api/workflows/[id]/execute/route.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { db } from '@/db'
1212
import { environment, userStats } from '@/db/schema'
1313
import { Executor } from '@/executor'
1414
import { Serializer } from '@/serializer'
15-
import { mergeSubblockState } from '@/stores/workflows/utils'
15+
import { mergeSubblockState } from '@/stores/workflows/server-utils'
1616
import type { WorkflowState } from '@/stores/workflows/workflow/types'
1717
import { validateWorkflowAccess } from '../../middleware'
1818
import { createErrorResponse, createSuccessResponse } from '../../utils'
Lines changed: 114 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,32 @@
11
import { and, eq } from 'drizzle-orm'
2-
import { NextResponse } from 'next/server'
2+
import { type NextRequest, NextResponse } from 'next/server'
33
import { getSession } from '@/lib/auth'
44
import { createLogger } from '@/lib/logs/console-logger'
55
import { db } from '@/db'
66
import { workflow, workspaceMember } from '@/db/schema'
77

8-
const logger = createLogger('WorkflowDetailAPI')
8+
const logger = createLogger('WorkflowByIdAPI')
99

10-
export async function GET(request: Request, { params }: { params: Promise<{ id: string }> }) {
10+
/**
11+
* GET /api/workflows/[id]
12+
* Fetch a single workflow by ID
13+
*/
14+
export async function GET(request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
1115
const requestId = crypto.randomUUID().slice(0, 8)
1216
const startTime = Date.now()
17+
const { id: workflowId } = await params
1318

1419
try {
1520
// Get the session
1621
const session = await getSession()
1722
if (!session?.user?.id) {
18-
logger.warn(`[${requestId}] Unauthorized workflow access attempt`)
23+
logger.warn(`[${requestId}] Unauthorized access attempt for workflow ${workflowId}`)
1924
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
2025
}
2126

22-
const { id: workflowId } = await params
27+
const userId = session.user.id
2328

24-
if (!workflowId) {
25-
return NextResponse.json({ error: 'Workflow ID is required' }, { status: 400 })
26-
}
27-
28-
// Fetch the workflow from database
29+
// Fetch the workflow
2930
const workflowData = await db
3031
.select()
3132
.from(workflow)
@@ -38,42 +39,124 @@ export async function GET(request: Request, { params }: { params: Promise<{ id:
3839
}
3940

4041
// Check if user has access to this workflow
41-
// User can access if they own it OR if it's in a workspace they're part of
42-
const canAccess = workflowData.userId === session.user.id
42+
let hasAccess = false
43+
44+
// Case 1: User owns the workflow
45+
if (workflowData.userId === userId) {
46+
hasAccess = true
47+
}
4348

44-
if (!canAccess && workflowData.workspaceId) {
45-
// Check workspace membership
49+
// Case 2: Workflow belongs to a workspace the user is a member of
50+
if (!hasAccess && workflowData.workspaceId) {
4651
const membership = await db
47-
.select()
52+
.select({ id: workspaceMember.id })
4853
.from(workspaceMember)
4954
.where(
5055
and(
5156
eq(workspaceMember.workspaceId, workflowData.workspaceId),
52-
eq(workspaceMember.userId, session.user.id)
57+
eq(workspaceMember.userId, userId)
5358
)
5459
)
5560
.then((rows) => rows[0])
5661

5762
if (membership) {
58-
// User is a member of the workspace, allow access
59-
const elapsed = Date.now() - startTime
60-
logger.info(`[${requestId}] Workflow ${workflowId} fetched in ${elapsed}ms`)
61-
return NextResponse.json({ data: workflowData }, { status: 200 })
63+
hasAccess = true
64+
}
65+
}
66+
67+
if (!hasAccess) {
68+
logger.warn(`[${requestId}] User ${userId} denied access to workflow ${workflowId}`)
69+
return NextResponse.json({ error: 'Access denied' }, { status: 403 })
70+
}
71+
72+
const elapsed = Date.now() - startTime
73+
logger.info(`[${requestId}] Successfully fetched workflow ${workflowId} in ${elapsed}ms`)
74+
75+
return NextResponse.json({ data: workflowData }, { status: 200 })
76+
} catch (error: any) {
77+
const elapsed = Date.now() - startTime
78+
logger.error(`[${requestId}] Error fetching workflow ${workflowId} after ${elapsed}ms`, error)
79+
return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
80+
}
81+
}
82+
83+
/**
84+
* DELETE /api/workflows/[id]
85+
* Delete a workflow by ID
86+
*/
87+
export async function DELETE(
88+
request: NextRequest,
89+
{ params }: { params: Promise<{ id: string }> }
90+
) {
91+
const requestId = crypto.randomUUID().slice(0, 8)
92+
const startTime = Date.now()
93+
const { id: workflowId } = await params
94+
95+
try {
96+
// Get the session
97+
const session = await getSession()
98+
if (!session?.user?.id) {
99+
logger.warn(`[${requestId}] Unauthorized deletion attempt for workflow ${workflowId}`)
100+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
101+
}
102+
103+
const userId = session.user.id
104+
105+
// Fetch the workflow to check ownership/access
106+
const workflowData = await db
107+
.select()
108+
.from(workflow)
109+
.where(eq(workflow.id, workflowId))
110+
.then((rows) => rows[0])
111+
112+
if (!workflowData) {
113+
logger.warn(`[${requestId}] Workflow ${workflowId} not found for deletion`)
114+
return NextResponse.json({ error: 'Workflow not found' }, { status: 404 })
115+
}
116+
117+
// Check if user has permission to delete this workflow
118+
let canDelete = false
119+
120+
// Case 1: User owns the workflow
121+
if (workflowData.userId === userId) {
122+
canDelete = true
123+
}
124+
125+
// Case 2: Workflow belongs to a workspace and user has admin/owner role
126+
if (!canDelete && workflowData.workspaceId) {
127+
const membership = await db
128+
.select({ role: workspaceMember.role })
129+
.from(workspaceMember)
130+
.where(
131+
and(
132+
eq(workspaceMember.workspaceId, workflowData.workspaceId),
133+
eq(workspaceMember.userId, userId)
134+
)
135+
)
136+
.then((rows) => rows[0])
137+
138+
if (membership && (membership.role === 'owner' || membership.role === 'admin')) {
139+
canDelete = true
62140
}
63-
} else if (canAccess) {
64-
// User owns the workflow, allow access
65-
const elapsed = Date.now() - startTime
66-
logger.info(`[${requestId}] Workflow ${workflowId} fetched in ${elapsed}ms`)
67-
return NextResponse.json({ data: workflowData }, { status: 200 })
68141
}
69142

70-
logger.warn(
71-
`[${requestId}] User ${session.user.id} attempted to access workflow ${workflowId} without permission`
72-
)
73-
return NextResponse.json({ error: 'Access denied' }, { status: 403 })
143+
if (!canDelete) {
144+
logger.warn(
145+
`[${requestId}] User ${userId} denied permission to delete workflow ${workflowId}`
146+
)
147+
return NextResponse.json({ error: 'Access denied' }, { status: 403 })
148+
}
149+
150+
// Delete the workflow
151+
await db.delete(workflow).where(eq(workflow.id, workflowId))
152+
153+
const elapsed = Date.now() - startTime
154+
logger.info(`[${requestId}] Successfully deleted workflow ${workflowId} in ${elapsed}ms`)
155+
156+
return NextResponse.json({ success: true }, { status: 200 })
74157
} catch (error: any) {
75158
const elapsed = Date.now() - startTime
76-
logger.error(`[${requestId}] Error fetching workflow after ${elapsed}ms:`, error)
77-
return NextResponse.json({ error: 'Failed to fetch workflow' }, { status: 500 })
159+
logger.error(`[${requestId}] Error deleting workflow ${workflowId} after ${elapsed}ms`, error)
160+
return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
78161
}
79162
}

apps/sim/app/api/workflows/sync/route.ts

Lines changed: 45 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -377,17 +377,17 @@ export async function POST(req: NextRequest) {
377377
processedIds.add(id)
378378
const dbWorkflow = dbWorkflowMap.get(id)
379379

380-
// Handle legacy published workflows migration
380+
// Handle legacy published workflows migration (only if state is provided)
381381
// If client workflow has isPublished but no marketplaceData, create marketplaceData with owner status
382-
if (clientWorkflow.state.isPublished && !clientWorkflow.marketplaceData) {
382+
if (clientWorkflow.state?.isPublished && !clientWorkflow.marketplaceData) {
383383
clientWorkflow.marketplaceData = { id: clientWorkflow.id, status: 'owner' }
384384
}
385385

386386
// Ensure the workflow has the correct workspaceId
387387
const effectiveWorkspaceId = clientWorkflow.workspaceId || workspaceId
388388

389389
if (!dbWorkflow) {
390-
// New workflow - create
390+
// New workflow - create (state is required by schema)
391391
operations.push(
392392
db.insert(workflow).values({
393393
id: clientWorkflow.id,
@@ -417,61 +417,57 @@ export async function POST(req: NextRequest) {
417417
continue // Skip this workflow update and move to the next one
418418
}
419419

420-
// Existing workflow - update if needed
421-
const needsUpdate =
422-
JSON.stringify(dbWorkflow.state) !== JSON.stringify(clientWorkflow.state) ||
423-
dbWorkflow.name !== clientWorkflow.name ||
424-
dbWorkflow.description !== clientWorkflow.description ||
425-
dbWorkflow.color !== clientWorkflow.color ||
426-
dbWorkflow.workspaceId !== effectiveWorkspaceId ||
427-
dbWorkflow.folderId !== (clientWorkflow.folderId || null) ||
420+
// For existing workflows, determine what needs updating
421+
let needsUpdate = false
422+
const updateData: any = {}
423+
424+
// Check metadata changes
425+
if (dbWorkflow.name !== clientWorkflow.name) {
426+
updateData.name = clientWorkflow.name
427+
needsUpdate = true
428+
}
429+
if (dbWorkflow.description !== clientWorkflow.description) {
430+
updateData.description = clientWorkflow.description
431+
needsUpdate = true
432+
}
433+
if (dbWorkflow.color !== clientWorkflow.color) {
434+
updateData.color = clientWorkflow.color
435+
needsUpdate = true
436+
}
437+
if (dbWorkflow.workspaceId !== effectiveWorkspaceId) {
438+
updateData.workspaceId = effectiveWorkspaceId
439+
needsUpdate = true
440+
}
441+
if (dbWorkflow.folderId !== (clientWorkflow.folderId || null)) {
442+
updateData.folderId = clientWorkflow.folderId || null
443+
needsUpdate = true
444+
}
445+
if (
428446
JSON.stringify(dbWorkflow.marketplaceData) !==
429-
JSON.stringify(clientWorkflow.marketplaceData)
447+
JSON.stringify(clientWorkflow.marketplaceData)
448+
) {
449+
updateData.marketplaceData = clientWorkflow.marketplaceData || null
450+
needsUpdate = true
451+
}
430452

431-
if (needsUpdate) {
432-
operations.push(
433-
db
434-
.update(workflow)
435-
.set({
436-
name: clientWorkflow.name,
437-
description: clientWorkflow.description,
438-
color: clientWorkflow.color,
439-
workspaceId: effectiveWorkspaceId,
440-
folderId: clientWorkflow.folderId || null,
441-
state: clientWorkflow.state,
442-
marketplaceData: clientWorkflow.marketplaceData || null,
443-
lastSynced: now,
444-
updatedAt: now,
445-
})
446-
.where(eq(workflow.id, id))
447-
)
453+
// Always update state since we only sync the active workflow with valid state
454+
if (JSON.stringify(dbWorkflow.state) !== JSON.stringify(clientWorkflow.state)) {
455+
updateData.state = clientWorkflow.state
456+
needsUpdate = true
448457
}
449-
}
450-
}
451458

452-
// Handle deletions - workflows in DB but not in client
453-
// Only delete workflows for the current workspace and only those the user can modify
454-
for (const dbWorkflow of dbWorkflows) {
455-
if (
456-
!processedIds.has(dbWorkflow.id) &&
457-
(!workspaceId || dbWorkflow.workspaceId === workspaceId)
458-
) {
459-
// Check if the user has permission to delete this workflow
460-
// Users can delete their own workflows, or any workflow if they're a workspace owner/admin
461-
const canDelete =
462-
dbWorkflow.userId === session.user.id ||
463-
(workspaceId && (userRole === 'owner' || userRole === 'admin' || userRole === 'member'))
459+
if (needsUpdate) {
460+
updateData.lastSynced = now
461+
updateData.updatedAt = now
464462

465-
if (canDelete) {
466-
operations.push(db.delete(workflow).where(eq(workflow.id, dbWorkflow.id)))
467-
} else {
468-
logger.warn(
469-
`[${requestId}] User ${session.user.id} attempted to delete workflow ${dbWorkflow.id} without permission`
470-
)
463+
operations.push(db.update(workflow).set(updateData).where(eq(workflow.id, id)))
471464
}
472465
}
473466
}
474467

468+
// NOTE: We don't delete workflows here since we're only syncing the active workflow
469+
// Other workflows remain untouched in the database
470+
475471
// Execute all operations in parallel
476472
await Promise.all(operations)
477473

apps/sim/app/w/[id]/components/control-bar/control-bar.tsx

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -409,21 +409,25 @@ export function ControlBar() {
409409
/**
410410
* Workflow deletion handler
411411
*/
412-
const handleDeleteWorkflow = () => {
412+
const handleDeleteWorkflow = async () => {
413413
if (!activeWorkflowId) return
414414

415-
// Get remaining workflow IDs
415+
// Navigate to another workflow first
416416
const remainingIds = Object.keys(workflows).filter((id) => id !== activeWorkflowId)
417417

418-
// Navigate before removing the workflow to avoid any state inconsistencies
419418
if (remainingIds.length > 0) {
420419
router.push(`/w/${remainingIds[0]}`)
421420
} else {
422421
router.push('/')
423422
}
424423

425-
// Remove the workflow from the registry
426-
removeWorkflow(activeWorkflowId)
424+
// Remove the workflow from the registry (now async)
425+
try {
426+
await removeWorkflow(activeWorkflowId)
427+
} catch (error) {
428+
// Handle error gracefully - could show user notification instead
429+
logger.error('Failed to delete workflow:', error)
430+
}
427431
}
428432

429433
// /**

0 commit comments

Comments
 (0)