File size: 3,130 Bytes
6111b2b | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 | /**
* API: OpenAPI Spec
* GET — returns the parsed openapi.yaml as structured JSON catalog
*/
import { NextResponse } from "next/server";
import fs from "fs";
import path from "path";
import yaml from "js-yaml";
import { sanitizeErrorMessage } from "@omniroute/open-sse/utils/error";
let cachedSpec: { data: any; mtime: number } | null = null;
const OPENAPI_SPEC_CANDIDATES = [
path.join(/* turbopackIgnore: true */ process.cwd(), "docs", "reference", "openapi.yaml"),
path.join(/* turbopackIgnore: true */ process.cwd(), "app", "docs", "reference", "openapi.yaml"),
// Legacy locations kept as fallback for old standalone bundles.
path.join(/* turbopackIgnore: true */ process.cwd(), "docs", "openapi.yaml"),
path.join(/* turbopackIgnore: true */ process.cwd(), "app", "docs", "openapi.yaml"),
];
export async function GET() {
try {
let specPath = "";
for (const p of OPENAPI_SPEC_CANDIDATES) {
if (fs.existsSync(p)) {
specPath = p;
break;
}
}
if (!specPath) {
return NextResponse.json({ error: "openapi.yaml not found" }, { status: 404 });
}
const stat = fs.statSync(specPath);
const mtime = stat.mtimeMs;
// Use cache if file hasn't changed
if (cachedSpec && cachedSpec.mtime === mtime) {
return NextResponse.json(cachedSpec.data);
}
const content = fs.readFileSync(specPath, "utf-8");
const raw: any = yaml.load(content);
// Build a structured catalog
const catalog: any = {
info: raw.info || {},
servers: raw.servers || [],
tags: Array.isArray(raw.tags) ? raw.tags : [],
endpoints: [] as any[],
schemas: Object.keys(raw.components?.schemas || {}),
};
// Parse paths into flat endpoint list
const paths = raw.paths || {};
for (const [pathStr, methods] of Object.entries(paths as Record<string, any>)) {
if (!methods || typeof methods !== "object") continue;
for (const [method, spec] of Object.entries(methods as Record<string, any>)) {
if (["get", "post", "put", "patch", "delete"].includes(method) && spec) {
catalog.endpoints.push({
method: method.toUpperCase(),
path: pathStr,
tags: Array.isArray(spec.tags) ? spec.tags : [],
summary: spec.summary || "",
description: spec.description || "",
security: spec.security ? true : false,
parameters: spec.parameters || [],
requestBody: spec.requestBody ? true : false,
responses: Object.keys(spec.responses || {}),
loopbackOnly: spec["x-loopback-only"] === true,
alwaysProtected: spec["x-always-protected"] === true,
internal: spec["x-internal"] === true,
});
}
}
}
cachedSpec = { data: catalog, mtime };
return NextResponse.json(catalog);
} catch (error: any) {
return NextResponse.json(
{ error: sanitizeErrorMessage(error) || "Failed to parse OpenAPI spec" },
{ status: 500 }
);
}
}
|