11import { db } from '@sim/db'
22import { workflow , workflowFolder } from '@sim/db/schema'
33import { createLogger } from '@sim/logger'
4- import { and , eq } from 'drizzle-orm'
4+ import { and , asc , eq , isNull } from 'drizzle-orm'
55import { type NextRequest , NextResponse } from 'next/server'
66import { z } from 'zod'
77import { AuditAction , AuditResourceType , recordAudit } from '@/lib/audit/log'
@@ -37,7 +37,6 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id:
3737
3838 logger . info ( `[${ requestId } ] Duplicating folder ${ sourceFolderId } for user ${ session . user . id } ` )
3939
40- // Verify the source folder exists
4140 const sourceFolder = await db
4241 . select ( )
4342 . from ( workflowFolder )
@@ -48,7 +47,6 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id:
4847 throw new Error ( 'Source folder not found' )
4948 }
5049
51- // Check if user has permission to access the source folder
5250 const userPermission = await getUserEntityPermissions (
5351 session . user . id ,
5452 'workspace' ,
@@ -61,26 +59,55 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id:
6159
6260 const targetWorkspaceId = workspaceId || sourceFolder . workspaceId
6361
64- // Step 1: Duplicate folder structure
6562 const { newFolderId, folderMapping } = await db . transaction ( async ( tx ) => {
6663 const newFolderId = crypto . randomUUID ( )
6764 const now = new Date ( )
65+ const targetParentId = parentId ?? sourceFolder . parentId
66+
67+ const folderParentCondition = targetParentId
68+ ? eq ( workflowFolder . parentId , targetParentId )
69+ : isNull ( workflowFolder . parentId )
70+ const workflowParentCondition = targetParentId
71+ ? eq ( workflow . folderId , targetParentId )
72+ : isNull ( workflow . folderId )
73+
74+ const [ [ folderResult ] , [ workflowResult ] ] = await Promise . all ( [
75+ tx
76+ . select ( { sortOrder : workflowFolder . sortOrder } )
77+ . from ( workflowFolder )
78+ . where ( and ( eq ( workflowFolder . workspaceId , targetWorkspaceId ) , folderParentCondition ) )
79+ . orderBy ( asc ( workflowFolder . sortOrder ) )
80+ . limit ( 1 ) ,
81+ tx
82+ . select ( { sortOrder : workflow . sortOrder } )
83+ . from ( workflow )
84+ . where ( and ( eq ( workflow . workspaceId , targetWorkspaceId ) , workflowParentCondition ) )
85+ . orderBy ( asc ( workflow . sortOrder ) )
86+ . limit ( 1 ) ,
87+ ] )
88+
89+ const minSortOrder = [ folderResult ?. sortOrder , workflowResult ?. sortOrder ] . reduce <
90+ number | null
91+ > ( ( currentMin , candidate ) => {
92+ if ( candidate == null ) return currentMin
93+ if ( currentMin == null ) return candidate
94+ return Math . min ( currentMin , candidate )
95+ } , null )
96+ const sortOrder = minSortOrder != null ? minSortOrder - 1 : 0
6897
69- // Create the new root folder
7098 await tx . insert ( workflowFolder ) . values ( {
7199 id : newFolderId ,
72100 userId : session . user . id ,
73101 workspaceId : targetWorkspaceId ,
74102 name,
75103 color : color || sourceFolder . color ,
76- parentId : parentId || sourceFolder . parentId ,
77- sortOrder : sourceFolder . sortOrder ,
104+ parentId : targetParentId ,
105+ sortOrder,
78106 isExpanded : false ,
79107 createdAt : now ,
80108 updatedAt : now ,
81109 } )
82110
83- // Recursively duplicate child folders
84111 const folderMapping = new Map < string , string > ( [ [ sourceFolderId , newFolderId ] ] )
85112 await duplicateFolderStructure (
86113 tx ,
@@ -96,7 +123,6 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id:
96123 return { newFolderId, folderMapping }
97124 } )
98125
99- // Step 2: Duplicate workflows
100126 const workflowStats = await duplicateWorkflowsInFolderTree (
101127 sourceFolder . workspaceId ,
102128 targetWorkspaceId ,
@@ -173,7 +199,6 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id:
173199 }
174200}
175201
176- // Helper to recursively duplicate folder structure
177202async function duplicateFolderStructure (
178203 tx : any ,
179204 sourceFolderId : string ,
@@ -184,7 +209,6 @@ async function duplicateFolderStructure(
184209 timestamp : Date ,
185210 folderMapping : Map < string , string >
186211) : Promise < void > {
187- // Get all child folders
188212 const childFolders = await tx
189213 . select ( )
190214 . from ( workflowFolder )
@@ -195,7 +219,6 @@ async function duplicateFolderStructure(
195219 )
196220 )
197221
198- // Create each child folder and recurse
199222 for ( const childFolder of childFolders ) {
200223 const newChildFolderId = crypto . randomUUID ( )
201224 folderMapping . set ( childFolder . id , newChildFolderId )
@@ -213,7 +236,6 @@ async function duplicateFolderStructure(
213236 updatedAt : timestamp ,
214237 } )
215238
216- // Recurse for this child's children
217239 await duplicateFolderStructure (
218240 tx ,
219241 childFolder . id ,
@@ -227,7 +249,6 @@ async function duplicateFolderStructure(
227249 }
228250}
229251
230- // Helper to duplicate all workflows in a folder tree
231252async function duplicateWorkflowsInFolderTree (
232253 sourceWorkspaceId : string ,
233254 targetWorkspaceId : string ,
@@ -237,17 +258,14 @@ async function duplicateWorkflowsInFolderTree(
237258) : Promise < { total : number ; succeeded : number ; failed : number } > {
238259 const stats = { total : 0 , succeeded : 0 , failed : 0 }
239260
240- // Process each folder in the mapping
241261 for ( const [ oldFolderId , newFolderId ] of folderMapping . entries ( ) ) {
242- // Get workflows in this folder
243262 const workflowsInFolder = await db
244263 . select ( )
245264 . from ( workflow )
246265 . where ( and ( eq ( workflow . folderId , oldFolderId ) , eq ( workflow . workspaceId , sourceWorkspaceId ) ) )
247266
248267 stats . total += workflowsInFolder . length
249268
250- // Duplicate each workflow
251269 for ( const sourceWorkflow of workflowsInFolder ) {
252270 try {
253271 await duplicateWorkflow ( {
0 commit comments