mirror of
https://github.com/saltbo/agent-kanban.git
synced 2026-09-19 09:01:47 +08:00
feat(api): add interactive OpenAPI documentation (#264)
Co-authored-by: jarvis <jarvis@agents.realmroot.dev>
This commit is contained in:
@@ -44,6 +44,7 @@
|
||||
"@base-ui/react": "^1.3.0",
|
||||
"@fontsource-variable/geist": "^5.2.8",
|
||||
"@realmroot/enbor-sdk": "0.3.0",
|
||||
"@scalar/hono-api-reference": "^0.12.0",
|
||||
"@tanstack/react-query": "^5.100.9",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
|
||||
Generated
+70
@@ -26,6 +26,9 @@ importers:
|
||||
'@realmroot/enbor-sdk':
|
||||
specifier: 0.3.0
|
||||
version: 0.3.0
|
||||
'@scalar/hono-api-reference':
|
||||
specifier: ^0.12.0
|
||||
version: 0.12.0(hono@4.13.5)
|
||||
'@tanstack/react-query':
|
||||
specifier: ^5.100.9
|
||||
version: 5.102.8(react@19.2.8)
|
||||
@@ -2066,6 +2069,32 @@ packages:
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@scalar/client-side-rendering@0.3.10':
|
||||
resolution: {integrity: sha512-FlUByzYhsas54zIvsiTJ7UgAZiVUQmErWvrQm+CKTBnIvSmIb5eWowJ4/O0bbkUa8rsOIdmCcX+HtX+EDdf0CA==}
|
||||
engines: {node: '>=22'}
|
||||
|
||||
'@scalar/helpers@0.11.2':
|
||||
resolution: {integrity: sha512-IgHW/hIj1XAvsPIBKiOgmK87qbyDjaQQg9S/auXU4MUtvnwKKpOeIOJc0NC71FkMdr4Mok0SSVI748cmlptoXQ==}
|
||||
engines: {node: '>=22'}
|
||||
|
||||
'@scalar/hono-api-reference@0.12.0':
|
||||
resolution: {integrity: sha512-K8WA0NTDnwCmTvh33AmkHuoolDuxEW7Jst/Am7+tcijPgFcsGE0l3O7Vk9RkNMMQT643Vy9nJDyhfnkMIWA5vQ==}
|
||||
engines: {node: '>=22'}
|
||||
peerDependencies:
|
||||
hono: ^4.12.5
|
||||
|
||||
'@scalar/schemas@0.8.4':
|
||||
resolution: {integrity: sha512-gbx/UjAswjJc+OZp/vcVRIopjqoJ62pOkrKLvVHa1MuMRoVxcPyNzW2/SQmnvdJku7C7a6PsKaPUQ6gZXGA4eg==}
|
||||
engines: {node: '>=22'}
|
||||
|
||||
'@scalar/types@0.18.3':
|
||||
resolution: {integrity: sha512-hPLLxVt/ah4RpCVp5UrLEpnBEgTwf+RvPtRiSEty3QqfBJUfADMXHz+oWK6UAa/vALg2AWuJLCD8hRlyQ2ET9w==}
|
||||
engines: {node: '>=22'}
|
||||
|
||||
'@scalar/validation@0.6.3':
|
||||
resolution: {integrity: sha512-j3s9XPv8Wo1EG5/naBi4XGF0uXYQ2A3QZNI0NKWgCMxRHVrDJGlZEYymcHDHY7fquRm73P8U3c8xqJqB5yad6w==}
|
||||
engines: {node: '>=20'}
|
||||
|
||||
'@sec-ant/readable-stream@0.4.1':
|
||||
resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==}
|
||||
|
||||
@@ -4300,6 +4329,10 @@ packages:
|
||||
os: [darwin, linux, win32, freebsd, openbsd, netbsd, sunos, android]
|
||||
hasBin: true
|
||||
|
||||
tagged-tag@1.0.0:
|
||||
resolution: {integrity: sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==}
|
||||
engines: {node: '>=20'}
|
||||
|
||||
tailwind-merge@3.6.0:
|
||||
resolution: {integrity: sha512-uxL7qAVQriqRQPAyK3pj66VqskWqoZ37PW94jwOTwNfq/z9oyu1V+eqrZqtR2+fCiXdYOZe/Modt8GtvqNzu+w==}
|
||||
|
||||
@@ -4380,6 +4413,10 @@ packages:
|
||||
peerDependencies:
|
||||
tailwindcss: '>=4.0.0-0'
|
||||
|
||||
type-fest@5.9.0:
|
||||
resolution: {integrity: sha512-yANm3Jr3GiJ1qgJlxGAVxTOIcEOk1rhQHamlXtnrCK7EHP4HeM9OGxtMg/W7HFdrVzw/ZWJKGVIJusVH85sLtw==}
|
||||
engines: {node: '>=20'}
|
||||
|
||||
type-is@2.1.0:
|
||||
resolution: {integrity: sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==}
|
||||
engines: {node: '>= 18'}
|
||||
@@ -6462,6 +6499,33 @@ snapshots:
|
||||
'@rollup/rollup-win32-x64-msvc@4.63.1':
|
||||
optional: true
|
||||
|
||||
'@scalar/client-side-rendering@0.3.10':
|
||||
dependencies:
|
||||
'@scalar/schemas': 0.8.4
|
||||
'@scalar/types': 0.18.3
|
||||
'@scalar/validation': 0.6.3
|
||||
|
||||
'@scalar/helpers@0.11.2': {}
|
||||
|
||||
'@scalar/hono-api-reference@0.12.0(hono@4.13.5)':
|
||||
dependencies:
|
||||
'@scalar/client-side-rendering': 0.3.10
|
||||
hono: 4.13.5
|
||||
|
||||
'@scalar/schemas@0.8.4':
|
||||
dependencies:
|
||||
'@scalar/helpers': 0.11.2
|
||||
'@scalar/validation': 0.6.3
|
||||
|
||||
'@scalar/types@0.18.3':
|
||||
dependencies:
|
||||
'@scalar/helpers': 0.11.2
|
||||
nanoid: 5.1.16
|
||||
type-fest: 5.9.0
|
||||
zod: 4.5.4
|
||||
|
||||
'@scalar/validation@0.6.3': {}
|
||||
|
||||
'@sec-ant/readable-stream@0.4.1': {}
|
||||
|
||||
'@sindresorhus/is@7.2.0': {}
|
||||
@@ -8951,6 +9015,8 @@ snapshots:
|
||||
|
||||
systeminformation@5.33.6: {}
|
||||
|
||||
tagged-tag@1.0.0: {}
|
||||
|
||||
tailwind-merge@3.6.0: {}
|
||||
|
||||
tailwindcss@4.3.3: {}
|
||||
@@ -9019,6 +9085,10 @@ snapshots:
|
||||
dependencies:
|
||||
tailwindcss: 4.3.3
|
||||
|
||||
type-fest@5.9.0:
|
||||
dependencies:
|
||||
tagged-tag: 1.0.0
|
||||
|
||||
type-is@2.1.0:
|
||||
dependencies:
|
||||
content-type: 2.1.0
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { Scalar } from "@scalar/hono-api-reference";
|
||||
import { RESOURCE_SCOPES } from "@server/auth/realmroot";
|
||||
import { akPublicUrl, akResource } from "@server/config/serviceUrls";
|
||||
import type { Env } from "@server/env";
|
||||
@@ -27,6 +28,30 @@ export function registerResourceServerRoutes(app: Api): void {
|
||||
});
|
||||
});
|
||||
app.get("/api/openapi.json", (c) => c.json(toolboxDocument(c.env)));
|
||||
app.get(
|
||||
"/api/docs",
|
||||
Scalar({
|
||||
url: "/api/openapi.json",
|
||||
pageTitle: "Agent Kanban API Docs",
|
||||
theme: "default",
|
||||
darkMode: true,
|
||||
customCss: `
|
||||
:root {
|
||||
--scalar-font: "Geist", ui-sans-serif, system-ui, sans-serif;
|
||||
--scalar-font-code: "Geist Mono", ui-monospace, monospace;
|
||||
--scalar-color-accent: #0891b2;
|
||||
--scalar-background-accent: rgba(8, 145, 178, 0.1);
|
||||
}
|
||||
.dark-mode {
|
||||
--scalar-color-accent: #22d3ee;
|
||||
--scalar-background-1: #09090b;
|
||||
--scalar-background-2: #18181b;
|
||||
--scalar-background-3: #27272a;
|
||||
--scalar-background-accent: rgba(34, 211, 238, 0.1);
|
||||
}
|
||||
`,
|
||||
}),
|
||||
);
|
||||
app.get("/api/toolbox/openapi.json", (c) => c.notFound());
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,24 @@ const env = {
|
||||
const resource = `${env.AK_PUBLIC_ORIGIN}/api`;
|
||||
|
||||
describe("Resource Server HTTP capabilities", () => {
|
||||
it("serves interactive API documentation for the canonical OpenAPI document", async () => {
|
||||
const docs = await api.request("/api/docs", {}, env);
|
||||
const openapi = await api.request("/api/openapi.json", {}, env);
|
||||
|
||||
expect(docs.status).toBe(200);
|
||||
expect(docs.headers.get("content-type")).toContain("text/html");
|
||||
const html = await docs.text();
|
||||
expect(html).toContain("Agent Kanban API Docs");
|
||||
expect(html).toContain("/api/openapi.json");
|
||||
|
||||
expect(openapi.status).toBe(200);
|
||||
expect(openapi.headers.get("content-type")).toContain("application/json");
|
||||
await expect(openapi.json()).resolves.toMatchObject({
|
||||
openapi: "3.1.0",
|
||||
info: { title: "Agent Kanban API" },
|
||||
});
|
||||
});
|
||||
|
||||
it("[spec: resource-server/discovery] publishes protected-resource and Toolbox discovery over HTTP", async () => {
|
||||
const metadata = await api.request("/.well-known/oauth-protected-resource/api", {}, env);
|
||||
const service = await api.request("/api", {}, env);
|
||||
|
||||
Reference in New Issue
Block a user