diff --git a/frontend/package.json b/frontend/package.json
index 905bb63b..524f4a9d 100644
--- a/frontend/package.json
+++ b/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",
diff --git a/frontend/scripts/build-openapi.mjs b/frontend/scripts/build-openapi.mjs
index de64b86e..f89e1d66 100644
--- a/frontend/scripts/build-openapi.mjs
+++ b/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');
diff --git a/frontend/src/pages/api-docs/endpoints.js b/frontend/src/pages/api-docs/endpoints.ts
similarity index 98%
rename from frontend/src/pages/api-docs/endpoints.js
rename to frontend/src/pages/api-docs/endpoints.ts
index 4efeefb3..6fc4e5a5 100644
--- a/frontend/src/pages/api-docs/endpoints.js
+++ b/frontend/src/pages/api-docs/endpoints.ts
@@ -1,29 +1,59 @@
-export function safeInlineHtml(input) {
- if (!input) return '';
- const escape = (s) => s.replace(/&/g, '&').replace(//g, '>');
- const open = '';
- const close = '';
- 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 += '' + escape(input.slice(oIdx + open.length, cIdx)) + '';
- 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 const sections = [
+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: 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',
-};
diff --git a/web/controller/api_docs_test.go b/web/controller/api_docs_test.go
index f91e4b7a..ad1f8d08 100644
--- a/web/controller/api_docs_test.go
+++ b/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)
}
}