Forráskód Böngészése

refactor(frontend): port api-docs/endpoints to TypeScript

endpoints.js was the only remaining JS file under src/. It's a pure data
file describing every panel API surface for the in-panel Swagger docs;
scripts/build-openapi.mjs reads it at build time to emit
public/openapi.json.

Convert it to endpoints.ts with explicit interfaces:
  HttpMethod, ParamLocation, ParamType,
  EndpointParam, Endpoint, SubscriptionHeader, Section

Type-checking surfaced shapes the .js had silently accepted:
  - 'in' values beyond plain 'body' — 'body (form)', 'body (json)',
    'body (multipart)' for non-JSON request bodies
  - 'type' arrays — 'integer[]', 'object[]'
  - Subscription section's subHeader documenting response headers
All four are now part of the union types so the existing data type-checks.

Dead exports removed:
  - safeInlineHtml — unused since the docs page switched to Swagger UI
  - methodColors — unused

Build pipeline:
  - scripts/build-openapi.mjs imports endpoints.ts directly
  - gen:api runs via Node 22's native --experimental-strip-types; no
    tsx/ts-node dependency added
  - --disable-warning=ExperimentalWarning silences just the strip-types
    notice while keeping deprecation warnings intact
MHSanaei 17 órája
szülő
commit
20edaee8ed

+ 1 - 1
frontend/package.json

@@ -14,7 +14,7 @@
     "preview": "vite preview",
     "lint": "eslint src",
     "typecheck": "tsc --noEmit",
-    "gen:api": "node scripts/build-openapi.mjs"
+    "gen:api": "node --experimental-strip-types --disable-warning=ExperimentalWarning scripts/build-openapi.mjs"
   },
   "dependencies": {
     "@ant-design/icons": "^6.2.3",

+ 1 - 1
frontend/scripts/build-openapi.mjs

@@ -3,7 +3,7 @@ import { writeFileSync } from 'node:fs';
 import { join, dirname } from 'node:path';
 import { fileURLToPath, pathToFileURL } from 'node:url';
 
-import { sections } from '../src/pages/api-docs/endpoints.js';
+import { sections } from '../src/pages/api-docs/endpoints.ts';
 
 const __dirname = dirname(fileURLToPath(import.meta.url));
 const outPath = join(__dirname, '..', 'public', 'openapi.json');

+ 54 - 33
frontend/src/pages/api-docs/endpoints.js → frontend/src/pages/api-docs/endpoints.ts

@@ -1,29 +1,59 @@
-export function safeInlineHtml(input) {
-  if (!input) return '';
-  const escape = (s) => s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
-  const open = '<code>';
-  const close = '</code>';
-  let out = '';
-  let i = 0;
-  while (i < input.length) {
-    const oIdx = input.indexOf(open, i);
-    if (oIdx === -1) {
-      out += escape(input.slice(i));
-      break;
-    }
-    out += escape(input.slice(i, oIdx));
-    const cIdx = input.indexOf(close, oIdx + open.length);
-    if (cIdx === -1) {
-      out += escape(input.slice(oIdx));
-      break;
-    }
-    out += '<code>' + escape(input.slice(oIdx + open.length, cIdx)) + '</code>';
-    i = cIdx + close.length;
-  }
-  return out;
+export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'WS';
+export type ParamLocation =
+  | 'path'
+  | 'query'
+  | 'header'
+  | 'body'
+  | 'body (form)'
+  | 'body (json)'
+  | 'body (multipart)';
+export type ParamType =
+  | 'string'
+  | 'integer'
+  | 'integer[]'
+  | 'number'
+  | 'boolean'
+  | 'object'
+  | 'object[]'
+  | 'array'
+  | 'file';
+
+export interface EndpointParam {
+  name: string;
+  in: ParamLocation;
+  type: ParamType;
+  desc?: string;
+  optional?: boolean;
+  defaultValue?: string | number | boolean;
+}
+
+export interface Endpoint {
+  method: HttpMethod;
+  path: string;
+  summary: string;
+  description?: string;
+  deprecated?: boolean;
+  params?: EndpointParam[];
+  body?: string;
+  response?: string;
+  errorResponse?: string;
+  errorStatus?: number;
+}
+
+export interface SubscriptionHeader {
+  name: string;
+  desc: string;
+}
+
+export interface Section {
+  id: string;
+  title: string;
+  description?: string;
+  subHeader?: SubscriptionHeader[];
+  endpoints: Endpoint[];
 }
 
-export const sections = [
+export const sections: readonly Section[] = [
   {
     id: 'authentication',
     title: 'Authentication',
@@ -975,12 +1005,3 @@ export const sections = [
     ],
   },
 ];
-
-export const methodColors = {
-  GET: 'blue',
-  POST: 'green',
-  PUT: 'orange',
-  PATCH: 'orange',
-  DELETE: 'red',
-  WS: 'purple',
-};

+ 6 - 6
web/controller/api_docs_test.go

@@ -16,10 +16,10 @@ type routeDef struct {
 // routePattern matches route registrations like g.GET("/path", handler) or api.GET("/path", handler)
 var routePattern = regexp.MustCompile(`\b(g|api)\.(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS)\("([^"]+)"`)
 
-// docRoutePattern matches { method: 'X', path: 'Y' ... } entries in endpoints.js.
+// docRoutePattern matches { method: 'X', path: 'Y' ... } entries in endpoints.ts.
 var docRoutePattern = regexp.MustCompile(`method:\s*'([A-Z]+)'\s*,\s*path:\s*'([^']+)'`)
 
-// buildDocSet parses frontend/src/pages/api-docs/endpoints.js and returns the
+// buildDocSet parses frontend/src/pages/api-docs/endpoints.ts and returns the
 // set of documented "METHOD PATH" keys. WS pseudo-routes and subscription
 // placeholders (paths starting with /{...}) are skipped because they aren't
 // registered on the main Gin engine.
@@ -29,10 +29,10 @@ func buildDocSet(t *testing.T) map[string]bool {
 	if err != nil {
 		t.Fatalf("failed to get current dir: %v", err)
 	}
-	endpointsPath := filepath.Join(controllerDir, "..", "..", "frontend", "src", "pages", "api-docs", "endpoints.js")
+	endpointsPath := filepath.Join(controllerDir, "..", "..", "frontend", "src", "pages", "api-docs", "endpoints.ts")
 	data, err := os.ReadFile(endpointsPath)
 	if err != nil {
-		t.Fatalf("failed to read endpoints.js at %s: %v", endpointsPath, err)
+		t.Fatalf("failed to read endpoints.ts at %s: %v", endpointsPath, err)
 	}
 	docSet := make(map[string]bool)
 	for _, m := range docRoutePattern.FindAllStringSubmatch(string(data), -1) {
@@ -150,7 +150,7 @@ func TestAPIRoutesDocumented(t *testing.T) {
 			foundInDoc++
 		} else {
 			missingFromDocs++
-			t.Errorf("Route not documented in endpoints.js: %s %s", r.Method, r.Path)
+			t.Errorf("Route not documented in endpoints.ts: %s %s", r.Method, r.Path)
 		}
 	}
 
@@ -158,6 +158,6 @@ func TestAPIRoutesDocumented(t *testing.T) {
 		len(sourceSet), len(docSet), foundInDoc, missingFromDocs)
 
 	if missingFromDocs > 0 {
-		t.Errorf("Found %d undocumented route(s). Update endpoints.js to match.", missingFromDocs)
+		t.Errorf("Found %d undocumented route(s). Update endpoints.ts to match.", missingFromDocs)
 	}
 }