From 1a81cf75f10fd67d98bbb60565255e108e6cd975 Mon Sep 17 00:00:00 2001 From: Apple Date: Mon, 1 Dec 2025 03:43:06 -0800 Subject: [PATCH] feat: add unified API proxy layer, debug endpoint, and systemd service for node-guardian --- .../src/app/api/_debug/api-config/route.ts | 16 +++ apps/web/src/lib/apiProxy.ts | 111 ++++++++++++++++++ scripts/node-guardian.service | 23 ++++ 3 files changed, 150 insertions(+) create mode 100644 apps/web/src/app/api/_debug/api-config/route.ts create mode 100644 apps/web/src/lib/apiProxy.ts create mode 100644 scripts/node-guardian.service diff --git a/apps/web/src/app/api/_debug/api-config/route.ts b/apps/web/src/app/api/_debug/api-config/route.ts new file mode 100644 index 00000000..49dc6d7b --- /dev/null +++ b/apps/web/src/app/api/_debug/api-config/route.ts @@ -0,0 +1,16 @@ +import { NextResponse } from 'next/server'; +import { getApiConfig } from '@/lib/apiProxy'; + +/** + * Debug endpoint to check API configuration + * GET /api/_debug/api-config + */ +export async function GET() { + const config = getApiConfig(); + + return NextResponse.json({ + ...config, + timestamp: new Date().toISOString(), + }); +} + diff --git a/apps/web/src/lib/apiProxy.ts b/apps/web/src/lib/apiProxy.ts new file mode 100644 index 00000000..001e2306 --- /dev/null +++ b/apps/web/src/lib/apiProxy.ts @@ -0,0 +1,111 @@ +/** + * Unified API Proxy Layer for DAARION.city + * + * Provides consistent access to backend services: + * - city-service (main API) + * - dagi-router (AI routing) + * - swapper-service (model management) + */ + +// Environment configuration +const CITY_API_BASE_URL = process.env.INTERNAL_API_URL || process.env.CITY_SERVICE_URL || 'http://daarion-city-service:7001'; +const DAGI_ROUTER_API_URL = process.env.DAGI_ROUTER_API_URL || 'http://dagi-router:9102'; +const SWAPPER_API_BASE_URL = process.env.SWAPPER_API_BASE_URL || 'http://swapper-service:8890'; + +/** + * Get API configuration (for debugging) + */ +export function getApiConfig() { + return { + CITY_API_BASE_URL, + DAGI_ROUTER_API_URL, + SWAPPER_API_BASE_URL, + NODE_ENV: process.env.NODE_ENV, + }; +} + +/** + * Proxy request to city-service + */ +export async function proxyCity( + path: string, + init: RequestInit = {} +): Promise { + const url = `${CITY_API_BASE_URL}${path.startsWith('/') ? path : `/${path}`}`; + + console.log(`[proxyCity] ${init.method || 'GET'} ${url}`); + + try { + const response = await fetch(url, { + ...init, + headers: { + ...init.headers, + }, + }); + + console.log(`[proxyCity] Response: ${response.status}`); + return response; + } catch (error) { + console.error(`[proxyCity] Error:`, error); + throw error; + } +} + +/** + * Proxy request to dagi-router + */ +export async function proxyRouter( + path: string, + init: RequestInit = {} +): Promise { + const url = `${DAGI_ROUTER_API_URL}${path.startsWith('/') ? path : `/${path}`}`; + + console.log(`[proxyRouter] ${init.method || 'GET'} ${url}`); + + try { + const response = await fetch(url, init); + console.log(`[proxyRouter] Response: ${response.status}`); + return response; + } catch (error) { + console.error(`[proxyRouter] Error:`, error); + throw error; + } +} + +/** + * Proxy request to swapper-service + */ +export async function proxySwapper( + path: string, + init: RequestInit = {} +): Promise { + const url = `${SWAPPER_API_BASE_URL}${path.startsWith('/') ? path : `/${path}`}`; + + console.log(`[proxySwapper] ${init.method || 'GET'} ${url}`); + + try { + const response = await fetch(url, init); + console.log(`[proxySwapper] Response: ${response.status}`); + return response; + } catch (error) { + console.error(`[proxySwapper] Error:`, error); + throw error; + } +} + +/** + * Helper to safely parse JSON response + */ +export async function safeJsonParse(response: Response, fallback: T): Promise { + try { + if (!response.ok) { + console.warn(`[safeJsonParse] Non-OK response: ${response.status}`); + return fallback; + } + return await response.json(); + } catch (error) { + console.error(`[safeJsonParse] Error parsing JSON:`, error); + return fallback; + } +} + diff --git a/scripts/node-guardian.service b/scripts/node-guardian.service new file mode 100644 index 00000000..50f121ee --- /dev/null +++ b/scripts/node-guardian.service @@ -0,0 +1,23 @@ +[Unit] +Description=DAARION Node Guardian Loop +After=network.target docker.service +Requires=docker.service + +[Service] +Type=simple +WorkingDirectory=/opt/microdao-daarion +ExecStart=/usr/bin/python3 /opt/microdao-daarion/scripts/node-guardian-loop.py --node-id=node-1-hetzner-gex44 +Restart=always +RestartSec=10 +Environment=CITY_API_URL=http://localhost:7001 +Environment=SWAPPER_URL=http://localhost:8890 +Environment=PYTHONUNBUFFERED=1 + +# Logging +StandardOutput=journal +StandardError=journal +SyslogIdentifier=node-guardian + +[Install] +WantedBy=multi-user.target +