@@ -86,59 +86,79 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
8686 const baseUrl = `https://api.atlassian.com/ex/confluence/${ cloudIdValidation . sanitized } /wiki/api/v2/spaces`
8787 const PAGE_LIMIT = 250
8888 const MAX_PAGES = 20
89- const spaces : { id : string ; name : string ; key : string } [ ] = [ ]
90- let cursor : string | undefined
91- let pageCount = 0
92-
93- while ( pageCount < MAX_PAGES ) {
94- const params = new URLSearchParams ( { limit : String ( PAGE_LIMIT ) } )
95- if ( cursor ) params . set ( 'cursor' , cursor )
96- const url = `${ baseUrl } ?${ params . toString ( ) } `
97-
98- const response = await fetch ( url , {
99- method : 'GET' ,
100- headers : {
101- Accept : 'application/json' ,
102- Authorization : `Bearer ${ accessToken } ` ,
103- } ,
104- } )
10589
106- if ( ! response . ok ) {
107- const errorText = await response . text ( )
108- logger . error ( 'Confluence API error response:' , {
109- status : response . status ,
110- statusText : response . statusText ,
111- error : errorText ,
90+ /**
91+ * Confluence v2 `/spaces` defaults to `status=current` and treats `status`
92+ * as a single-value enum, so archived spaces never surface from one call.
93+ * Listing both surfaces archived spaces in the dropdown — they would
94+ * otherwise only be reachable by typing the space key manually, even
95+ * though sync works against archived spaces just fine.
96+ */
97+ async function fetchAllPages ( status : 'current' | 'archived' ) : Promise < {
98+ spaces : { id : string ; name : string ; key : string ; status : string } [ ]
99+ capped : boolean
100+ } > {
101+ const collected : { id : string ; name : string ; key : string ; status : string } [ ] = [ ]
102+ let cursor : string | undefined
103+ let pageCount = 0
104+
105+ while ( pageCount < MAX_PAGES ) {
106+ const params = new URLSearchParams ( { limit : String ( PAGE_LIMIT ) , status } )
107+ if ( cursor ) params . set ( 'cursor' , cursor )
108+ const url = `${ baseUrl } ?${ params . toString ( ) } `
109+
110+ const response = await fetch ( url , {
111+ method : 'GET' ,
112+ headers : { Accept : 'application/json' , Authorization : `Bearer ${ accessToken } ` } ,
112113 } )
113- return NextResponse . json (
114- { error : parseAtlassianErrorMessage ( response . status , response . statusText , errorText ) } ,
115- { status : response . status }
116- )
117- }
118114
119- const data = await response . json ( )
120- for ( const space of data . results || [ ] ) {
121- spaces . push ( { id : space . id , name : space . name , key : space . key } )
115+ if ( ! response . ok ) {
116+ const errorText = await response . text ( )
117+ throw new Error (
118+ parseAtlassianErrorMessage ( response . status , response . statusText , errorText )
119+ )
120+ }
121+
122+ const data = await response . json ( )
123+ for ( const space of data . results || [ ] ) {
124+ collected . push ( { id : space . id , name : space . name , key : space . key , status } )
125+ }
126+
127+ const nextLink = data . _links ?. next as string | undefined
128+ if ( ! nextLink ) return { spaces : collected , capped : false }
129+ try {
130+ cursor = new URL ( nextLink , 'https://placeholder' ) . searchParams . get ( 'cursor' ) || undefined
131+ } catch {
132+ cursor = undefined
133+ }
134+ if ( ! cursor ) return { spaces : collected , capped : false }
135+ pageCount += 1
122136 }
123137
124- const nextLink = data . _links ?. next as string | undefined
125- if ( ! nextLink ) break
126- try {
127- cursor = new URL ( nextLink , 'https://placeholder' ) . searchParams . get ( 'cursor' ) || undefined
128- } catch {
129- cursor = undefined
130- }
131- if ( ! cursor ) break
132- pageCount += 1
138+ return { spaces : collected , capped : true }
139+ }
140+
141+ let currentResult : Awaited < ReturnType < typeof fetchAllPages > >
142+ let archivedResult : Awaited < ReturnType < typeof fetchAllPages > >
143+ try {
144+ ; [ currentResult , archivedResult ] = await Promise . all ( [
145+ fetchAllPages ( 'current' ) ,
146+ fetchAllPages ( 'archived' ) ,
147+ ] )
148+ } catch ( error ) {
149+ logger . error ( 'Confluence API error response' , { error : ( error as Error ) . message } )
150+ return NextResponse . json ( { error : ( error as Error ) . message } , { status : 502 } )
133151 }
134152
135- if ( pageCount >= MAX_PAGES ) {
153+ if ( currentResult . capped || archivedResult . capped ) {
136154 logger . warn ( 'Confluence space listing hit pagination cap' , {
137155 cap : MAX_PAGES * PAGE_LIMIT ,
138- returned : spaces . length ,
156+ currentCount : currentResult . spaces . length ,
157+ archivedCount : archivedResult . spaces . length ,
139158 } )
140159 }
141160
161+ const spaces = [ ...currentResult . spaces , ...archivedResult . spaces ]
142162 return NextResponse . json ( { spaces } )
143163 } catch ( error ) {
144164 logger . error ( 'Error listing Confluence spaces:' , error )
0 commit comments