11import { db } from '@sim/db'
22import { permissions , workflow , workflowFolder } from '@sim/db/schema'
33import { createLogger } from '@sim/logger'
4- import { and , asc , eq , gt , inArray , isNull , min } from 'drizzle-orm'
4+ import { and , asc , eq , gt , gte , inArray , isNull , lt , min , or } from 'drizzle-orm'
55import { type NextRequest , NextResponse } from 'next/server'
66import { z } from 'zod'
77import { AuditAction , AuditResourceType , recordAudit } from '@/lib/audit/log'
@@ -72,11 +72,43 @@ export async function GET(request: NextRequest) {
7272 // Fetch limit+1 to detect if there are more pages
7373 const fetchLimit = limit + 1
7474
75+ // Build cursor condition for keyset pagination
76+ // Cursor is base64-encoded JSON: { s: sortOrder, c: createdAt, i: id }
77+ let cursorCondition = null
78+ if ( cursor ) {
79+ try {
80+ const decoded = JSON . parse ( Buffer . from ( cursor , 'base64' ) . toString ( ) )
81+ const cursorSortOrder = decoded . s
82+ const cursorCreatedAt = new Date ( decoded . c )
83+ const cursorId = decoded . i
84+ // Keyset pagination for ORDER BY sortOrder ASC, createdAt ASC, id ASC:
85+ // (sortOrder > cursorSortOrder) OR
86+ // (sortOrder = cursorSortOrder AND createdAt > cursorCreatedAt) OR
87+ // (sortOrder = cursorSortOrder AND createdAt = cursorCreatedAt AND id > cursorId)
88+ cursorCondition = or (
89+ gt ( workflow . sortOrder , cursorSortOrder ) ,
90+ and (
91+ eq ( workflow . sortOrder , cursorSortOrder ) ,
92+ or (
93+ gt ( workflow . createdAt , cursorCreatedAt ) ,
94+ and ( eq ( workflow . createdAt , cursorCreatedAt ) , gt ( workflow . id , cursorId ) )
95+ )
96+ )
97+ )
98+ } catch {
99+ // Invalid cursor - ignore and return first page
100+ }
101+ }
102+
75103 if ( workspaceId ) {
104+ const whereClause = cursorCondition
105+ ? and ( eq ( workflow . workspaceId , workspaceId ) , cursorCondition )
106+ : eq ( workflow . workspaceId , workspaceId )
107+
76108 workflows = await db
77109 . select ( )
78110 . from ( workflow )
79- . where ( eq ( workflow . workspaceId , workspaceId ) )
111+ . where ( whereClause )
80112 . orderBy ( ...orderByClause )
81113 . limit ( fetchLimit )
82114 } else {
@@ -88,18 +120,28 @@ export async function GET(request: NextRequest) {
88120 if ( workspaceIds . length === 0 ) {
89121 return NextResponse . json ( { data : [ ] } , { status : 200 } )
90122 }
123+ const whereClause = cursorCondition
124+ ? and ( inArray ( workflow . workspaceId , workspaceIds ) , cursorCondition )
125+ : inArray ( workflow . workspaceId , workspaceIds )
126+
91127 workflows = await db
92128 . select ( )
93129 . from ( workflow )
94- . where ( inArray ( workflow . workspaceId , workspaceIds ) )
130+ . where ( whereClause )
95131 . orderBy ( ...orderByClause )
96132 . limit ( fetchLimit )
97133 }
98134
99- // Determine if there are more results and set cursor
135+ // Determine if there are more results and compute next cursor
100136 const hasMore = workflows . length > limit
101137 const data = hasMore ? workflows . slice ( 0 , limit ) : workflows
102- const nextCursor = hasMore ? data [ data . length - 1 ] ?. id : null
138+ let nextCursor = null
139+ if ( hasMore && data . length > 0 ) {
140+ const last = data [ data . length - 1 ]
141+ nextCursor = Buffer . from (
142+ JSON . stringify ( { s : last . sortOrder , c : last . createdAt , i : last . id } )
143+ ) . toString ( 'base64' )
144+ }
103145
104146 return NextResponse . json ( { data, nextCursor } , { status : 200 } )
105147 } catch ( error : any ) {
0 commit comments