Compare commits

...

2 Commits

Author SHA1 Message Date
Christian Gick
ed2d16f845 chore: Remove node_modules, dist, .env from tracking
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 19:05:53 +02:00
Christian Gick
bdbb39a0f5 feat(API-11): Route API calls through AgilitonAPI gateway
Add gateway-first pattern: when AGILITON_API_KEY is set, route all
external API calls through the gateway with X-API-Key auth. Falls back
to direct API access when gateway is unavailable.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 19:05:20 +02:00

View File

@@ -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 {