Skip to content

Commit 08c4dc9

Browse files
fix(confluence): fixed confluence tools & block (#304)
* chore: update better-auth * testing confluence, auth config * confluence working * confluence PR ready * Remove node_modules from tracking * generated docs, remove outdated dependency --------- Co-authored-by: Adam Gough <adam_gough@brown.edu>
1 parent 7a36d21 commit 08c4dc9

16 files changed

Lines changed: 8949 additions & 11661 deletions

File tree

docs/content/docs/tools/confluence.mdx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ Retrieve content from Confluence pages using the Confluence API.
6060
| `accessToken` | string | Yes | OAuth access token for Confluence |
6161
| `domain` | string | Yes | Your Confluence domain \(e.g., yourcompany.atlassian.net\) |
6262
| `pageId` | string | Yes | Confluence page ID to retrieve |
63+
| `cloudId` | string | No | Confluence Cloud ID for the instance. If not provided, it will be fetched using the domain. |
6364

6465
#### Output
6566

@@ -84,6 +85,7 @@ Update a Confluence page using the Confluence API.
8485
| `title` | string | No | New title for the page |
8586
| `content` | string | No | New content for the page in Confluence storage format |
8687
| `version` | number | No | Version number of the page \(required for preventing conflicts\) |
88+
| `cloudId` | string | No | Confluence Cloud ID for the instance. If not provided, it will be fetched using the domain. |
8789

8890
#### Output
8991

@@ -92,6 +94,7 @@ Update a Confluence page using the Confluence API.
9294
| `ts` | string |
9395
| `pageId` | string |
9496
| `title` | string |
97+
| `body` | string |
9598
| `success` | string |
9699

97100

Lines changed: 117 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { NextResponse } from 'next/server'
2+
import { getConfluenceCloudId } from '@/tools/confluence/utils'
23

34
export async function POST(request: Request) {
45
try {
5-
const { domain, accessToken, pageId } = await request.json()
6+
const { domain, accessToken, pageId, cloudId: providedCloudId } = await request.json()
67

78
if (!domain) {
89
return NextResponse.json({ error: 'Domain is required' }, { status: 400 })
@@ -16,30 +17,22 @@ export async function POST(request: Request) {
1617
return NextResponse.json({ error: 'Page ID is required' }, { status: 400 })
1718
}
1819

19-
// Log request details for debugging
20-
console.log('Request details:', {
21-
domain,
22-
tokenLength: accessToken ? accessToken.length : 0,
23-
pageId,
24-
})
20+
// Use provided cloudId or fetch it if not provided
21+
const cloudId = providedCloudId || await getConfluenceCloudId(domain, accessToken)
2522

26-
// Build the URL - using the same format as retrieve.ts
27-
const url = `https://${domain}/wiki/api/v2/pages/${pageId}?expand=body.view`
23+
// Build the URL for the Confluence API
24+
const url = `https://api.atlassian.com/ex/confluence/${cloudId}/wiki/api/v2/pages/${pageId}?expand=body.storage,body.view,body.atlas_doc_format`
2825

29-
console.log(`Fetching Confluence page from: ${url}`)
3026

3127
// Make the request to Confluence API
3228
const response = await fetch(url, {
3329
method: 'GET',
3430
headers: {
35-
'Content-Type': 'application/json',
36-
Accept: 'application/json',
37-
Authorization: `Bearer ${accessToken}`,
31+
'Accept': 'application/json',
32+
'Authorization': `Bearer ${accessToken}`,
3833
},
3934
})
4035

41-
console.log('Response status:', response.status, response.statusText)
42-
4336
if (!response.ok) {
4437
console.error(`Confluence API error: ${response.status} ${response.statusText}`)
4538
let errorMessage
@@ -50,35 +43,30 @@ export async function POST(request: Request) {
5043
errorMessage = errorData.message || `Failed to fetch Confluence page (${response.status})`
5144
} catch (e) {
5245
console.error('Could not parse error response as JSON:', e)
53-
54-
// Try to get the response text for more context
55-
try {
56-
const text = await response.text()
57-
console.error('Response text:', text)
58-
errorMessage = `Failed to fetch Confluence page: ${response.status} ${response.statusText}`
59-
} catch (textError) {
60-
errorMessage = `Failed to fetch Confluence page: ${response.status} ${response.statusText}`
61-
}
46+
errorMessage = `Failed to fetch Confluence page: ${response.status} ${response.statusText}`
6247
}
6348

6449
return NextResponse.json({ error: errorMessage }, { status: response.status })
6550
}
6651

6752
const data = await response.json()
68-
console.log(`Successfully fetched page: ${data.id} - ${data.title}`)
6953

54+
// If body is empty, try to provide a minimal valid response
7055
return NextResponse.json({
71-
file: {
72-
id: data.id,
73-
name: data.title,
74-
mimeType: 'confluence/page',
75-
url: data._links?.webui || '',
76-
modifiedTime: data.version?.createdAt || '',
77-
spaceId: data.spaceId,
78-
webViewLink: data._links?.webui || '',
79-
content: data.body?.view?.value || '',
80-
},
56+
id: data.id,
57+
title: data.title,
58+
body: {
59+
view: {
60+
value: data.body?.storage?.value ||
61+
data.body?.view?.value ||
62+
data.body?.atlas_doc_format?.value ||
63+
data.content || // try alternative fields
64+
data.description ||
65+
`Content for page ${data.title}` // fallback content
66+
}
67+
}
8168
})
69+
8270
} catch (error) {
8371
console.error('Error fetching Confluence page:', error)
8472
return NextResponse.json(
@@ -87,3 +75,97 @@ export async function POST(request: Request) {
8775
)
8876
}
8977
}
78+
79+
export async function PUT(request: Request) {
80+
try {
81+
const body = await request.json()
82+
83+
const {
84+
domain,
85+
accessToken,
86+
pageId,
87+
cloudId: providedCloudId,
88+
title,
89+
body: pageBody,
90+
version
91+
} = body
92+
93+
if (!domain) {
94+
return NextResponse.json({ error: 'Domain is required' }, { status: 400 })
95+
}
96+
97+
if (!accessToken) {
98+
return NextResponse.json({ error: 'Access token is required' }, { status: 400 })
99+
}
100+
101+
if (!pageId) {
102+
return NextResponse.json({ error: 'Page ID is required' }, { status: 400 })
103+
}
104+
105+
const cloudId = providedCloudId || await getConfluenceCloudId(domain, accessToken)
106+
107+
// First, get the current page to check its version
108+
const currentPageUrl = `https://api.atlassian.com/ex/confluence/${cloudId}/wiki/api/v2/pages/${pageId}`
109+
const currentPageResponse = await fetch(currentPageUrl, {
110+
headers: {
111+
'Accept': 'application/json',
112+
'Authorization': `Bearer ${accessToken}`,
113+
}
114+
})
115+
116+
if (!currentPageResponse.ok) {
117+
throw new Error(`Failed to fetch current page: ${currentPageResponse.status}`)
118+
}
119+
120+
const currentPage = await currentPageResponse.json()
121+
const currentVersion = currentPage.version.number
122+
123+
// Build the update body with incremented version
124+
const updateBody: any = {
125+
id: pageId,
126+
version: {
127+
number: currentVersion + 1,
128+
message: version?.message || 'Updated via API'
129+
},
130+
title: title,
131+
body: {
132+
representation: 'storage',
133+
value: pageBody?.value || ''
134+
},
135+
status: 'current'
136+
}
137+
138+
const response = await fetch(currentPageUrl, {
139+
method: 'PUT',
140+
headers: {
141+
'Accept': 'application/json',
142+
'Content-Type': 'application/json',
143+
'Authorization': `Bearer ${accessToken}`,
144+
},
145+
body: JSON.stringify(updateBody),
146+
})
147+
148+
if (!response.ok) {
149+
const errorData = await response.json().catch(() => null)
150+
console.error('Confluence API error response:', {
151+
status: response.status,
152+
statusText: response.statusText,
153+
error: JSON.stringify(errorData, null, 2)
154+
})
155+
const errorMessage = errorData?.message ||
156+
(errorData?.errors && JSON.stringify(errorData.errors)) ||
157+
`Failed to update Confluence page (${response.status})`
158+
return NextResponse.json({ error: errorMessage }, { status: response.status })
159+
}
160+
161+
const data = await response.json()
162+
return NextResponse.json(data)
163+
164+
} catch (error) {
165+
console.error('Error updating Confluence page:', error)
166+
return NextResponse.json(
167+
{ error: (error as Error).message || 'Internal server error' },
168+
{ status: 500 }
169+
)
170+
}
171+
}

sim/app/api/auth/oauth/confluence/pages/route.ts

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { NextResponse } from 'next/server'
2+
import { getConfluenceCloudId } from '@/tools/confluence/utils'
23

34
export async function POST(request: Request) {
45
try {
5-
const { domain, accessToken, title, limit = 50 } = await request.json()
6+
const { domain, accessToken, title, cloudId: providedCloudId,limit = 50 } = await request.json()
67

78
if (!domain) {
89
return NextResponse.json({ error: 'Domain is required' }, { status: 400 })
@@ -12,16 +13,11 @@ export async function POST(request: Request) {
1213
return NextResponse.json({ error: 'Access token is required' }, { status: 400 })
1314
}
1415

15-
// Log request details for debugging
16-
console.log('Request details:', {
17-
domain,
18-
tokenLength: accessToken ? accessToken.length : 0,
19-
hasTitle: !!title,
20-
limit,
21-
})
16+
// Use provided cloudId or fetch it if not provided
17+
const cloudId = providedCloudId || await getConfluenceCloudId(domain, accessToken)
2218

2319
// Build the URL with query parameters
24-
const baseUrl = `https://${domain}/wiki/api/v2/pages`
20+
const baseUrl = `https://api.atlassian.com/ex/confluence/${cloudId}/wiki/api/v2/pages`
2521
const queryParams = new URLSearchParams()
2622

2723
if (limit) {

sim/app/w/[id]/components/workflow-block/components/sub-block/components/credential-selector/components/oauth-required-modal.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,8 @@ const SCOPE_DESCRIPTIONS: Record<string, string> = {
4444
'https://www.googleapis.com/auth/userinfo.email': 'View your email address',
4545
'https://www.googleapis.com/auth/userinfo.profile': 'View your basic profile info',
4646
'https://www.googleapis.com/auth/spreadsheets': 'View and manage your Google Sheets',
47-
'read:confluence-content.all': 'Read Confluence content',
4847
'read:page:confluence': 'Read Confluence pages',
49-
'write:confluence-content': 'Write Confluence content',
48+
'write:page:confluence': 'Write Confluence pages',
5049
'read:me': 'Read your profile information',
5150
'database.read': 'Read your database',
5251
'database.write': 'Write to your database',

sim/blocks/blocks/confluence.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,7 @@ export const ConfluenceBlock: BlockConfig<ConfluenceResponse> = {
4141
serviceId: 'confluence',
4242
requiredScopes: [
4343
'read:page:confluence',
44-
'read:confluence-content.all',
45-
'write:confluence-content',
44+
'write:page:confluence',
4645
'read:me',
4746
'offline_access',
4847
],

sim/lib/auth.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -446,10 +446,9 @@ export const auth = betterAuth({
446446
userInfoUrl: 'https://api.atlassian.com/me',
447447
scopes: [
448448
'read:page:confluence',
449-
'read:confluence-content.all',
449+
'write:page:confluence',
450450
'read:me',
451451
'offline_access',
452-
'write:confluence-content',
453452
],
454453
responseType: 'code',
455454
pkce: true,

sim/lib/oauth.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -190,10 +190,9 @@ export const OAUTH_PROVIDERS: Record<string, OAuthProviderConfig> = {
190190
baseProviderIcon: (props) => ConfluenceIcon(props),
191191
scopes: [
192192
'read:page:confluence',
193-
'read:confluence-content.all',
193+
'write:page:confluence',
194194
'read:me',
195195
'offline_access',
196-
'write:confluence-content',
197196
],
198197
},
199198
},

0 commit comments

Comments
 (0)