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 , eq , isNull , min } 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,51 @@ 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 ( { minSortOrder : min ( workflowFolder . sortOrder ) } )
77+ . from ( workflowFolder )
78+ . where ( and ( eq ( workflowFolder . workspaceId , targetWorkspaceId ) , folderParentCondition ) ) ,
79+ tx
80+ . select ( { minSortOrder : min ( workflow . sortOrder ) } )
81+ . from ( workflow )
82+ . where ( and ( eq ( workflow . workspaceId , targetWorkspaceId ) , workflowParentCondition ) ) ,
83+ ] )
84+
85+ const minSortOrder = [ folderResult ?. minSortOrder , workflowResult ?. minSortOrder ] . reduce <
86+ number | null
87+ > ( ( currentMin , candidate ) => {
88+ if ( candidate == null ) return currentMin
89+ if ( currentMin == null ) return candidate
90+ return Math . min ( currentMin , candidate )
91+ } , null )
92+ const sortOrder = minSortOrder != null ? minSortOrder - 1 : 0
6893
69- // Create the new root folder
7094 await tx . insert ( workflowFolder ) . values ( {
7195 id : newFolderId ,
7296 userId : session . user . id ,
7397 workspaceId : targetWorkspaceId ,
7498 name,
7599 color : color || sourceFolder . color ,
76- parentId : parentId || sourceFolder . parentId ,
77- sortOrder : sourceFolder . sortOrder ,
100+ parentId : targetParentId ,
101+ sortOrder,
78102 isExpanded : false ,
79103 createdAt : now ,
80104 updatedAt : now ,
81105 } )
82106
83- // Recursively duplicate child folders
84107 const folderMapping = new Map < string , string > ( [ [ sourceFolderId , newFolderId ] ] )
85108 await duplicateFolderStructure (
86109 tx ,
@@ -96,7 +119,6 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id:
96119 return { newFolderId, folderMapping }
97120 } )
98121
99- // Step 2: Duplicate workflows
100122 const workflowStats = await duplicateWorkflowsInFolderTree (
101123 sourceFolder . workspaceId ,
102124 targetWorkspaceId ,
@@ -173,7 +195,6 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id:
173195 }
174196}
175197
176- // Helper to recursively duplicate folder structure
177198async function duplicateFolderStructure (
178199 tx : any ,
179200 sourceFolderId : string ,
@@ -184,7 +205,6 @@ async function duplicateFolderStructure(
184205 timestamp : Date ,
185206 folderMapping : Map < string , string >
186207) : Promise < void > {
187- // Get all child folders
188208 const childFolders = await tx
189209 . select ( )
190210 . from ( workflowFolder )
@@ -195,7 +215,6 @@ async function duplicateFolderStructure(
195215 )
196216 )
197217
198- // Create each child folder and recurse
199218 for ( const childFolder of childFolders ) {
200219 const newChildFolderId = crypto . randomUUID ( )
201220 folderMapping . set ( childFolder . id , newChildFolderId )
@@ -213,7 +232,6 @@ async function duplicateFolderStructure(
213232 updatedAt : timestamp ,
214233 } )
215234
216- // Recurse for this child's children
217235 await duplicateFolderStructure (
218236 tx ,
219237 childFolder . id ,
@@ -227,7 +245,6 @@ async function duplicateFolderStructure(
227245 }
228246}
229247
230- // Helper to duplicate all workflows in a folder tree
231248async function duplicateWorkflowsInFolderTree (
232249 sourceWorkspaceId : string ,
233250 targetWorkspaceId : string ,
@@ -237,17 +254,14 @@ async function duplicateWorkflowsInFolderTree(
237254) : Promise < { total : number ; succeeded : number ; failed : number } > {
238255 const stats = { total : 0 , succeeded : 0 , failed : 0 }
239256
240- // Process each folder in the mapping
241257 for ( const [ oldFolderId , newFolderId ] of folderMapping . entries ( ) ) {
242- // Get workflows in this folder
243258 const workflowsInFolder = await db
244259 . select ( )
245260 . from ( workflow )
246261 . where ( and ( eq ( workflow . folderId , oldFolderId ) , eq ( workflow . workspaceId , sourceWorkspaceId ) ) )
247262
248263 stats . total += workflowsInFolder . length
249264
250- // Duplicate each workflow
251265 for ( const sourceWorkflow of workflowsInFolder ) {
252266 try {
253267 await duplicateWorkflow ( {
0 commit comments