Skip to content

Commit 8f71684

Browse files
authored
v0.3.2: improvement + fix + feat
v0.3.2: improvement + fix + feat
2 parents aade4bf + 92fe353 commit 8f71684

File tree

80 files changed

+5341
-1734
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

80 files changed

+5341
-1734
lines changed

apps/sim/app/api/organizations/[id]/workspaces/route.ts

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ 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'
6-
import { member, permissions, user, workspace, workspaceMember } from '@/db/schema'
6+
import { member, permissions, user, workspace } from '@/db/schema'
77

88
const logger = createLogger('OrganizationWorkspacesAPI')
99

@@ -116,10 +116,9 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
116116
id: workspace.id,
117117
name: workspace.name,
118118
ownerId: workspace.ownerId,
119-
createdAt: workspace.createdAt,
120119
isOwner: eq(workspace.ownerId, memberId),
121120
permissionType: permissions.permissionType,
122-
joinedAt: workspaceMember.joinedAt,
121+
createdAt: permissions.createdAt,
123122
})
124123
.from(workspace)
125124
.leftJoin(
@@ -130,10 +129,6 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
130129
eq(permissions.userId, memberId)
131130
)
132131
)
133-
.leftJoin(
134-
workspaceMember,
135-
and(eq(workspaceMember.workspaceId, workspace.id), eq(workspaceMember.userId, memberId))
136-
)
137132
.where(
138133
or(
139134
// Member owns the workspace
@@ -148,7 +143,7 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
148143
name: workspace.name,
149144
isOwner: workspace.isOwner,
150145
permission: workspace.permissionType,
151-
joinedAt: workspace.joinedAt,
146+
joinedAt: workspace.createdAt,
152147
createdAt: workspace.createdAt,
153148
}))
154149

apps/sim/app/api/organizations/invitations/accept/route.ts

Lines changed: 3 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { getSession } from '@/lib/auth'
55
import { env } from '@/lib/env'
66
import { createLogger } from '@/lib/logs/console-logger'
77
import { db } from '@/db'
8-
import { invitation, member, permissions, workspaceInvitation, workspaceMember } from '@/db/schema'
8+
import { invitation, member, permissions, workspaceInvitation } from '@/db/schema'
99

1010
const logger = createLogger('OrganizationInvitationAcceptance')
1111

@@ -135,18 +135,6 @@ export async function GET(req: NextRequest) {
135135
wsInvitation.expiresAt &&
136136
new Date().toISOString() <= wsInvitation.expiresAt.toISOString()
137137
) {
138-
// Check if user isn't already a member of the workspace
139-
const existingWorkspaceMember = await tx
140-
.select()
141-
.from(workspaceMember)
142-
.where(
143-
and(
144-
eq(workspaceMember.workspaceId, wsInvitation.workspaceId),
145-
eq(workspaceMember.userId, session.user.id)
146-
)
147-
)
148-
.limit(1)
149-
150138
// Check if user doesn't already have permissions on the workspace
151139
const existingPermission = await tx
152140
.select()
@@ -160,17 +148,7 @@ export async function GET(req: NextRequest) {
160148
)
161149
.limit(1)
162150

163-
if (existingWorkspaceMember.length === 0 && existingPermission.length === 0) {
164-
// Add user as workspace member
165-
await tx.insert(workspaceMember).values({
166-
id: randomUUID(),
167-
workspaceId: wsInvitation.workspaceId,
168-
userId: session.user.id,
169-
role: wsInvitation.role,
170-
joinedAt: new Date(),
171-
updatedAt: new Date(),
172-
})
173-
151+
if (existingPermission.length === 0) {
174152
// Add workspace permissions
175153
await tx.insert(permissions).values({
176154
id: randomUUID(),
@@ -311,17 +289,6 @@ export async function POST(req: NextRequest) {
311289
wsInvitation.expiresAt &&
312290
new Date().toISOString() <= wsInvitation.expiresAt.toISOString()
313291
) {
314-
const existingWorkspaceMember = await tx
315-
.select()
316-
.from(workspaceMember)
317-
.where(
318-
and(
319-
eq(workspaceMember.workspaceId, wsInvitation.workspaceId),
320-
eq(workspaceMember.userId, session.user.id)
321-
)
322-
)
323-
.limit(1)
324-
325292
const existingPermission = await tx
326293
.select()
327294
.from(permissions)
@@ -334,16 +301,7 @@ export async function POST(req: NextRequest) {
334301
)
335302
.limit(1)
336303

337-
if (existingWorkspaceMember.length === 0 && existingPermission.length === 0) {
338-
await tx.insert(workspaceMember).values({
339-
id: randomUUID(),
340-
workspaceId: wsInvitation.workspaceId,
341-
userId: session.user.id,
342-
role: wsInvitation.role,
343-
joinedAt: new Date(),
344-
updatedAt: new Date(),
345-
})
346-
304+
if (existingPermission.length === 0) {
347305
await tx.insert(permissions).values({
348306
id: randomUUID(),
349307
userId: session.user.id,

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
generateCronExpression,
1111
getScheduleTimeValues,
1212
getSubBlockValue,
13+
validateCronExpression,
1314
} from '@/lib/schedules/utils'
1415
import { db } from '@/db'
1516
import { workflowSchedule } from '@/db/schema'
@@ -192,6 +193,18 @@ export async function POST(req: NextRequest) {
192193

193194
cronExpression = generateCronExpression(defaultScheduleType, scheduleValues)
194195

196+
// Additional validation for custom cron expressions
197+
if (defaultScheduleType === 'custom' && cronExpression) {
198+
const validation = validateCronExpression(cronExpression)
199+
if (!validation.isValid) {
200+
logger.error(`[${requestId}] Invalid cron expression: ${validation.error}`)
201+
return NextResponse.json(
202+
{ error: `Invalid cron expression: ${validation.error}` },
203+
{ status: 400 }
204+
)
205+
}
206+
}
207+
195208
nextRunAt = calculateNextRunTime(defaultScheduleType, scheduleValues)
196209

197210
logger.debug(

apps/sim/app/api/templates/[id]/use/route.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{
4343
name: templates.name,
4444
description: templates.description,
4545
state: templates.state,
46+
color: templates.color,
4647
})
4748
.from(templates)
4849
.where(eq(templates.id, id))
@@ -80,6 +81,7 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{
8081
name: `${templateData.name} (copy)`,
8182
description: templateData.description,
8283
state: templateData.state,
84+
color: templateData.color,
8385
userId: session.user.id,
8486
createdAt: now,
8587
updatedAt: now,

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

Lines changed: 47 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import crypto from 'crypto'
2-
import { and, eq } from 'drizzle-orm'
2+
import { eq } from 'drizzle-orm'
33
import { type NextRequest, NextResponse } from 'next/server'
44
import { z } from 'zod'
55
import { getSession } from '@/lib/auth'
66
import { createLogger } from '@/lib/logs/console-logger'
7+
import { getUserEntityPermissions } from '@/lib/permissions/utils'
78
import { db } from '@/db'
89
import { workflow, workflowBlocks, workflowEdges, workflowSubflows } from '@/db/schema'
910
import type { LoopConfig, ParallelConfig, WorkflowState } from '@/stores/workflows/workflow/types'
@@ -24,15 +25,13 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id:
2425
const requestId = crypto.randomUUID().slice(0, 8)
2526
const startTime = Date.now()
2627

27-
try {
28-
const session = await getSession()
29-
if (!session?.user?.id) {
30-
logger.warn(
31-
`[${requestId}] Unauthorized workflow duplication attempt for ${sourceWorkflowId}`
32-
)
33-
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
34-
}
28+
const session = await getSession()
29+
if (!session?.user?.id) {
30+
logger.warn(`[${requestId}] Unauthorized workflow duplication attempt for ${sourceWorkflowId}`)
31+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
32+
}
3533

34+
try {
3635
const body = await req.json()
3736
const { name, description, color, workspaceId, folderId } = DuplicateRequestSchema.parse(body)
3837

@@ -46,19 +45,43 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id:
4645

4746
// Duplicate workflow and all related data in a transaction
4847
const result = await db.transaction(async (tx) => {
49-
// First verify the source workflow exists and user has access
48+
// First verify the source workflow exists
5049
const sourceWorkflow = await tx
5150
.select()
5251
.from(workflow)
53-
.where(and(eq(workflow.id, sourceWorkflowId), eq(workflow.userId, session.user.id)))
52+
.where(eq(workflow.id, sourceWorkflowId))
5453
.limit(1)
5554

5655
if (sourceWorkflow.length === 0) {
57-
throw new Error('Source workflow not found or access denied')
56+
throw new Error('Source workflow not found')
5857
}
5958

6059
const source = sourceWorkflow[0]
6160

61+
// Check if user has permission to access the source workflow
62+
let canAccessSource = false
63+
64+
// Case 1: User owns the workflow
65+
if (source.userId === session.user.id) {
66+
canAccessSource = true
67+
}
68+
69+
// Case 2: User has admin or write permission in the source workspace
70+
if (!canAccessSource && source.workspaceId) {
71+
const userPermission = await getUserEntityPermissions(
72+
session.user.id,
73+
'workspace',
74+
source.workspaceId
75+
)
76+
if (userPermission === 'admin' || userPermission === 'write') {
77+
canAccessSource = true
78+
}
79+
}
80+
81+
if (!canAccessSource) {
82+
throw new Error('Source workflow not found or access denied')
83+
}
84+
6285
// Create the new workflow first (required for foreign key constraints)
6386
await tx.insert(workflow).values({
6487
id: newWorkflowId,
@@ -346,9 +369,18 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id:
346369

347370
return NextResponse.json(result, { status: 201 })
348371
} catch (error) {
349-
if (error instanceof Error && error.message === 'Source workflow not found or access denied') {
350-
logger.warn(`[${requestId}] Source workflow ${sourceWorkflowId} not found or access denied`)
351-
return NextResponse.json({ error: 'Source workflow not found' }, { status: 404 })
372+
if (error instanceof Error) {
373+
if (error.message === 'Source workflow not found') {
374+
logger.warn(`[${requestId}] Source workflow ${sourceWorkflowId} not found`)
375+
return NextResponse.json({ error: 'Source workflow not found' }, { status: 404 })
376+
}
377+
378+
if (error.message === 'Source workflow not found or access denied') {
379+
logger.warn(
380+
`[${requestId}] User ${session.user.id} denied access to source workflow ${sourceWorkflowId}`
381+
)
382+
return NextResponse.json({ error: 'Access denied' }, { status: 403 })
383+
}
352384
}
353385

354386
if (error instanceof z.ZodError) {

apps/sim/app/api/workspaces/[id]/permissions/route.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1+
import crypto from 'crypto'
12
import { and, eq } from 'drizzle-orm'
23
import { type NextRequest, NextResponse } from 'next/server'
34
import { getSession } from '@/lib/auth'
45
import { getUsersWithPermissions, hasWorkspaceAdminAccess } from '@/lib/permissions/utils'
56
import { db } from '@/db'
6-
import { permissions, type permissionTypeEnum, workspaceMember } from '@/db/schema'
7+
import { permissions, type permissionTypeEnum } from '@/db/schema'
78

89
type PermissionType = (typeof permissionTypeEnum.enumValues)[number]
910

@@ -33,18 +34,19 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
3334
}
3435

3536
// Verify the current user has access to this workspace
36-
const userMembership = await db
37+
const userPermission = await db
3738
.select()
38-
.from(workspaceMember)
39+
.from(permissions)
3940
.where(
4041
and(
41-
eq(workspaceMember.workspaceId, workspaceId),
42-
eq(workspaceMember.userId, session.user.id)
42+
eq(permissions.entityId, workspaceId),
43+
eq(permissions.entityType, 'workspace'),
44+
eq(permissions.userId, session.user.id)
4345
)
4446
)
4547
.limit(1)
4648

47-
if (userMembership.length === 0) {
49+
if (userPermission.length === 0) {
4850
return NextResponse.json({ error: 'Workspace not found or access denied' }, { status: 404 })
4951
}
5052

apps/sim/app/api/workspaces/[id]/route.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { and, eq } from 'drizzle-orm'
22
import { type NextRequest, NextResponse } from 'next/server'
33
import { getSession } from '@/lib/auth'
44
import { createLogger } from '@/lib/logs/console-logger'
5-
import { workflow, workspaceMember } from '@/db/schema'
5+
import { workflow } from '@/db/schema'
66

77
const logger = createLogger('WorkspaceByIdAPI')
88

@@ -126,9 +126,6 @@ export async function DELETE(
126126
// workflow_schedule, webhook, marketplace, chat, and memory records
127127
await tx.delete(workflow).where(eq(workflow.workspaceId, workspaceId))
128128

129-
// Delete workspace members
130-
await tx.delete(workspaceMember).where(eq(workspaceMember.workspaceId, workspaceId))
131-
132129
// Delete all permissions associated with this workspace
133130
await tx
134131
.delete(permissions)

0 commit comments

Comments
 (0)