Compare commits
2 Commits
9f7c8faf60
...
ed2d16f845
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ed2d16f845 | ||
|
|
bdbb39a0f5 |
@@ -1,12 +1,24 @@
|
|||||||
/**
|
/**
|
||||||
* Confluence Cloud REST API v2 client.
|
* Confluence Cloud REST API v2 client — routes through AgilitonAPI gateway.
|
||||||
* Uses basic auth with Atlassian API token.
|
* Falls back to direct Confluence access if AGILITON_API_KEY is not set.
|
||||||
|
*
|
||||||
|
* Gateway: AGILITON_API_KEY + AGILITON_API_URL
|
||||||
|
* Direct: CONFLUENCE_USER/JIRA_USER + CONFLUENCE_API_TOKEN/JIRA_API_TOKEN
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// Gateway config
|
||||||
|
const GATEWAY_URL = (process.env.AGILITON_API_URL || 'https://api.agiliton.cloud').replace(/\/$/, '');
|
||||||
|
const GATEWAY_KEY = process.env.AGILITON_API_KEY || '';
|
||||||
|
|
||||||
|
// Direct config (fallback)
|
||||||
const BASE_URL = process.env.CONFLUENCE_URL || 'https://agiliton.atlassian.net/wiki';
|
const BASE_URL = process.env.CONFLUENCE_URL || 'https://agiliton.atlassian.net/wiki';
|
||||||
const API_V2 = `${BASE_URL}/api/v2`;
|
const API_V2 = `${BASE_URL}/api/v2`;
|
||||||
const API_V1 = `${BASE_URL}/rest/api`;
|
const API_V1 = `${BASE_URL}/rest/api`;
|
||||||
|
|
||||||
|
function useGateway(): boolean {
|
||||||
|
return !!GATEWAY_KEY;
|
||||||
|
}
|
||||||
|
|
||||||
function getAuth(): string {
|
function getAuth(): string {
|
||||||
const user = process.env.CONFLUENCE_USER || process.env.JIRA_USER;
|
const user = process.env.CONFLUENCE_USER || process.env.JIRA_USER;
|
||||||
const token = process.env.CONFLUENCE_API_TOKEN || process.env.JIRA_API_TOKEN;
|
const token = process.env.CONFLUENCE_API_TOKEN || process.env.JIRA_API_TOKEN;
|
||||||
@@ -17,17 +29,25 @@ function getAuth(): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function request(url: string, options: RequestInit = {}): Promise<any> {
|
async function request(url: string, options: RequestInit = {}): Promise<any> {
|
||||||
|
let finalUrl = url;
|
||||||
const headers: Record<string, string> = {
|
const headers: Record<string, string> = {
|
||||||
'Authorization': `Basic ${getAuth()}`,
|
|
||||||
'Accept': 'application/json',
|
'Accept': 'application/json',
|
||||||
...(options.headers as Record<string, string> || {}),
|
...(options.headers as Record<string, string> || {}),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (useGateway()) {
|
||||||
|
// Route through gateway — translate Confluence URLs to gateway paths
|
||||||
|
finalUrl = toGatewayUrl(url);
|
||||||
|
headers['X-API-Key'] = GATEWAY_KEY;
|
||||||
|
} else {
|
||||||
|
headers['Authorization'] = `Basic ${getAuth()}`;
|
||||||
|
}
|
||||||
|
|
||||||
if (options.body && typeof options.body === 'string') {
|
if (options.body && typeof options.body === 'string') {
|
||||||
headers['Content-Type'] = 'application/json';
|
headers['Content-Type'] = 'application/json';
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await fetch(url, { ...options, headers });
|
const response = await fetch(finalUrl, { ...options, headers });
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const text = await response.text().catch(() => '');
|
const text = await response.text().catch(() => '');
|
||||||
@@ -38,6 +58,37 @@ async function request(url: string, options: RequestInit = {}): Promise<any> {
|
|||||||
return response.json();
|
return response.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Translate a direct Confluence URL to a gateway URL.
|
||||||
|
* /wiki/api/v2/spaces → /confluence/spaces
|
||||||
|
* /wiki/rest/api/content/search → /confluence/search
|
||||||
|
* /wiki/rest/api/space → /confluence/space
|
||||||
|
*/
|
||||||
|
function toGatewayUrl(url: string): string {
|
||||||
|
// Extract path from full URL
|
||||||
|
let path = url;
|
||||||
|
if (path.startsWith('http')) {
|
||||||
|
const u = new URL(path);
|
||||||
|
path = u.pathname + u.search;
|
||||||
|
}
|
||||||
|
|
||||||
|
// v2 API: /wiki/api/v2/... → /confluence/...
|
||||||
|
const v2Match = path.match(/\/api\/v2\/(.*)$/);
|
||||||
|
if (v2Match) return `${GATEWAY_URL}/confluence/${v2Match[1]}`;
|
||||||
|
|
||||||
|
// v1 API: /wiki/rest/api/content/search → /confluence/search
|
||||||
|
const searchMatch = path.match(/\/rest\/api\/content\/search(.*)$/);
|
||||||
|
if (searchMatch) return `${GATEWAY_URL}/confluence/search${searchMatch[1]}`;
|
||||||
|
|
||||||
|
// v1 API: /wiki/rest/api/space → /confluence/space
|
||||||
|
const spaceMatch = path.match(/\/rest\/api\/space(.*)$/);
|
||||||
|
if (spaceMatch) return `${GATEWAY_URL}/confluence/space${spaceMatch[1]}`;
|
||||||
|
|
||||||
|
// Fallback: strip /wiki prefix and use as-is
|
||||||
|
const stripped = path.replace(/^\/wiki/, '');
|
||||||
|
return `${GATEWAY_URL}/confluence${stripped}`;
|
||||||
|
}
|
||||||
|
|
||||||
// --- Types ---
|
// --- Types ---
|
||||||
|
|
||||||
export interface Space {
|
export interface Space {
|
||||||
|
|||||||
Reference in New Issue
Block a user