const express = require('express'); const path = require('path'); const yaml = require('js-yaml'); const fetch = require('node-fetch'); const app = express(); const PORT = process.env.PORT || 3000; app.use(express.static(path.join(__dirname, 'public'))); app.use(express.json({ limit: '2mb' })); app.get('/api/health', (req, res) => { res.json({ status: 'ok' }); }); function buildGraphFromPaths(pathsObject) { const httpMethods = new Set(['get','put','post','delete','options','head','patch','trace']); const nodeIdSet = new Set(); const linkKeySet = new Set(); const nodes = []; const links = []; const endpoints = []; const operations = []; // Add root node "/" const rootId = '/'; nodeIdSet.add(rootId); nodes.push({ id: rootId }); for (const pathKey of Object.keys(pathsObject || {})) { const pathItem = pathsObject[pathKey] || {}; // Collect endpoints for (const method of Object.keys(pathItem)) { const lower = method.toLowerCase(); if (httpMethods.has(lower)) { const op = pathItem[method] || {}; const summary = op.summary || ''; const description = op.description || ''; const opParams = [].concat(pathItem.parameters || [], op.parameters || []).filter(Boolean); const parameters = opParams.map(p => ({ name: p.name, in: p.in, required: !!p.required, schema: p.schema || null })); const hasJsonBody = !!(op.requestBody && op.requestBody.content && op.requestBody.content['application/json']); operations.push({ method: lower.toUpperCase(), path: pathKey, summary, description, parameters, hasJsonBody }); endpoints.push({ method: lower.toUpperCase(), path: pathKey }); } } // Build nodes/links from URL segments const segments = pathKey.split('/').filter(Boolean); if (segments.length === 0) continue; // Ensure nodes for (const seg of segments) { if (!nodeIdSet.has(seg)) { nodeIdSet.add(seg); nodes.push({ id: seg }); } } // Link from root "/" to first segment if (segments.length > 0) { const firstSeg = segments[0]; const rootKey = `${rootId}|${firstSeg}`; if (!linkKeySet.has(rootKey)) { linkKeySet.add(rootKey); links.push({ source: rootId, target: firstSeg }); } } // Create sequential links along the path for (let i = 0; i < segments.length - 1; i++) { const source = segments[i]; const target = segments[i + 1]; const key = `${source}|${target}`; if (!linkKeySet.has(key)) { linkKeySet.add(key); links.push({ source, target }); } } } return { nodes, links, endpoints, operations }; } app.post('/api/parse', (req, res) => { try { const { yaml: yamlText } = req.body || {}; if (typeof yamlText !== 'string' || yamlText.trim() === '') { return res.status(400).json({ error: 'Missing yaml string in body' }); } const spec = yaml.load(yamlText); if (!spec || typeof spec !== 'object') { return res.status(400).json({ error: 'Invalid YAML content' }); } const pathsObject = spec.paths || {}; const graph = buildGraphFromPaths(pathsObject); const servers = Array.isArray(spec.servers) ? spec.servers.map(s => s && s.url).filter(Boolean) : []; res.json({ graph, info: { title: spec.info && spec.info.title, version: spec.info && spec.info.version }, servers }); } catch (err) { res.status(400).json({ error: err.message || 'Failed to parse YAML' }); } }); // Simple proxy endpoint to call target server // Body: { baseUrl, method, path, headers, query, body } app.post('/api/proxy', async (req, res) => { try { const { baseUrl, method, path: relPath, headers = {}, query = {}, body } = req.body || {}; if (!baseUrl || !method || !relPath) { return res.status(400).json({ error: 'Missing baseUrl, method, or path' }); } const url = new URL(relPath.replace(/\s+/g, ''), baseUrl.endsWith('/') ? baseUrl : baseUrl + '/'); for (const [k, v] of Object.entries(query || {})) { if (v !== undefined && v !== null && v !== '') url.searchParams.append(k, String(v)); } const fetchOpts = { method: method.toUpperCase(), headers: { ...headers } }; if (body !== undefined && body !== null && method.toUpperCase() !== 'GET') { if (!fetchOpts.headers['content-type'] && !fetchOpts.headers['Content-Type']) { fetchOpts.headers['Content-Type'] = 'application/json'; } fetchOpts.body = typeof body === 'string' ? body : JSON.stringify(body); } const upstream = await fetch(url.toString(), fetchOpts); const contentType = upstream.headers.get('content-type') || ''; res.status(upstream.status); if (contentType.includes('application/json')) { const data = await upstream.json().catch(() => null); res.json({ status: upstream.status, headers: Object.fromEntries(upstream.headers), data }); } else { const text = await upstream.text(); res.json({ status: upstream.status, headers: Object.fromEntries(upstream.headers), data: text }); } } catch (err) { res.status(502).json({ error: err.message || 'Proxy request failed' }); } }); app.listen(PORT, () => { console.log(`Server listening on http://localhost:${PORT}`); });