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 }
    );
  }
}