* Lists documents using CQL search via the v1 API (used when label filtering is enabled).
( cloudId: string, accessToken: string, domain: string, spaceKeys: string[], contentType: string, labelFilter: string, maxPages: number, cursor?: string, syncContext?: Record<string, unknown> )
| 511 | * Lists documents using CQL search via the v1 API (used when label filtering is enabled). |
| 512 | */ |
| 513 | async function listDocumentsViaCql( |
| 514 | cloudId: string, |
| 515 | accessToken: string, |
| 516 | domain: string, |
| 517 | spaceKeys: string[], |
| 518 | contentType: string, |
| 519 | labelFilter: string, |
| 520 | maxPages: number, |
| 521 | cursor?: string, |
| 522 | syncContext?: Record<string, unknown> |
| 523 | ): Promise<ExternalDocumentList> { |
| 524 | const labels = labelFilter |
| 525 | .split(',') |
| 526 | .map((l) => l.trim()) |
| 527 | .filter(Boolean) |
| 528 | |
| 529 | // Build CQL query |
| 530 | let cql = buildSpaceClause(spaceKeys) |
| 531 | |
| 532 | if (contentType === 'blogpost') { |
| 533 | cql += ' AND type="blogpost"' |
| 534 | } else if (contentType === 'page' || !contentType) { |
| 535 | cql += ' AND type="page"' |
| 536 | } |
| 537 | // contentType === 'all' — no type filter |
| 538 | |
| 539 | if (labels.length === 1) { |
| 540 | cql += ` AND label="${escapeCql(labels[0])}"` |
| 541 | } else if (labels.length > 1) { |
| 542 | const labelList = labels.map((l) => `"${escapeCql(l)}"`).join(',') |
| 543 | cql += ` AND label in (${labelList})` |
| 544 | } |
| 545 | |
| 546 | const limit = maxPages > 0 ? Math.min(maxPages, 50) : 50 |
| 547 | const start = cursor ? Number(cursor) : 0 |
| 548 | |
| 549 | const queryParams = new URLSearchParams() |
| 550 | queryParams.append('cql', cql) |
| 551 | queryParams.append('limit', String(limit)) |
| 552 | queryParams.append('start', String(start)) |
| 553 | queryParams.append('expand', 'version,metadata.labels') |
| 554 | |
| 555 | const url = `https://api.atlassian.com/ex/confluence/${cloudId}/wiki/rest/api/content/search?${queryParams.toString()}` |
| 556 | |
| 557 | logger.info(`Searching Confluence via CQL: ${cql}`, { start, limit }) |
| 558 | |
| 559 | const response = await fetchWithRetry(url, { |
| 560 | method: 'GET', |
| 561 | headers: { |
| 562 | Accept: 'application/json', |
| 563 | Authorization: `Bearer ${accessToken}`, |
| 564 | }, |
| 565 | }) |
| 566 | |
| 567 | if (!response.ok) { |
| 568 | const errorText = await response.text() |
| 569 | logger.error('Failed to search Confluence via CQL', { |
| 570 | status: response.status, |
no test coverage detected