Merge pull request #60 from builderz-labs/feat/openapi-docs
feat: OpenAPI 3.1 documentation with Scalar UI
This commit is contained in:
commit
e88942e8f8
File diff suppressed because it is too large
Load Diff
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "mission-control",
|
||||
"version": "1.2.0",
|
||||
"description": "OpenClaw Mission Control \u2014 open-source agent orchestration dashboard",
|
||||
"description": "OpenClaw Mission Control — open-source agent orchestration dashboard",
|
||||
"scripts": {
|
||||
"dev": "next dev --hostname 127.0.0.1",
|
||||
"build": "next build",
|
||||
|
|
@ -16,6 +16,7 @@
|
|||
"quality:gate": "pnpm test:all"
|
||||
},
|
||||
"dependencies": {
|
||||
"@scalar/api-reference-react": "^0.8.66",
|
||||
"@xyflow/react": "^12.10.0",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"better-sqlite3": "^12.6.2",
|
||||
|
|
|
|||
2577
pnpm-lock.yaml
2577
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,19 @@
|
|||
import { NextResponse } from 'next/server'
|
||||
import { readFileSync } from 'fs'
|
||||
import { join } from 'path'
|
||||
|
||||
let cachedSpec: string | null = null
|
||||
|
||||
export async function GET() {
|
||||
if (!cachedSpec) {
|
||||
const specPath = join(process.cwd(), 'openapi.json')
|
||||
cachedSpec = readFileSync(specPath, 'utf-8')
|
||||
}
|
||||
|
||||
return new NextResponse(cachedSpec, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Cache-Control': 'public, max-age=3600',
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
'use client'
|
||||
|
||||
import { ApiReferenceReact } from '@scalar/api-reference-react'
|
||||
import '@scalar/api-reference-react/style.css'
|
||||
|
||||
export default function DocsPage() {
|
||||
return (
|
||||
<div className="h-screen">
|
||||
<ApiReferenceReact
|
||||
configuration={{
|
||||
url: '/api/docs',
|
||||
theme: 'kepler',
|
||||
darkMode: true,
|
||||
hideModels: false,
|
||||
hideDownloadButton: false,
|
||||
defaultHttpClient: {
|
||||
targetKey: 'shell',
|
||||
clientKey: 'curl',
|
||||
},
|
||||
metaData: {
|
||||
title: 'Mission Control API Docs',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -91,8 +91,8 @@ export function middleware(request: NextRequest) {
|
|||
}
|
||||
}
|
||||
|
||||
// Allow login page and auth API without session
|
||||
if (pathname === '/login' || pathname.startsWith('/api/auth/')) {
|
||||
// Allow login page, auth API, and docs without session
|
||||
if (pathname === '/login' || pathname.startsWith('/api/auth/') || pathname === '/api/docs' || pathname === '/docs') {
|
||||
return applySecurityHeaders(NextResponse.next())
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,36 @@
|
|||
import { test, expect } from '@playwright/test'
|
||||
|
||||
test.describe('OpenAPI Documentation', () => {
|
||||
test('GET /api/docs returns valid OpenAPI 3.1 JSON', async ({ request }) => {
|
||||
const res = await request.get('/api/docs')
|
||||
expect(res.status()).toBe(200)
|
||||
expect(res.headers()['content-type']).toContain('application/json')
|
||||
|
||||
const spec = await res.json()
|
||||
expect(spec.openapi).toMatch(/^3\.1/)
|
||||
expect(spec.info).toBeDefined()
|
||||
expect(spec.info.title).toBe('Mission Control API')
|
||||
expect(spec.info.version).toBeDefined()
|
||||
expect(spec.paths).toBeDefined()
|
||||
})
|
||||
|
||||
test('GET /api/docs includes key paths', async ({ request }) => {
|
||||
const res = await request.get('/api/docs')
|
||||
const spec = await res.json()
|
||||
|
||||
// Verify core paths exist
|
||||
const paths = Object.keys(spec.paths)
|
||||
expect(paths).toContain('/api/agents')
|
||||
expect(paths).toContain('/api/tasks')
|
||||
expect(paths).toContain('/api/tokens')
|
||||
expect(paths).toContain('/api/auth/login')
|
||||
})
|
||||
|
||||
test('GET /api/docs is accessible without auth', async ({ request }) => {
|
||||
// No API key header, no session cookie
|
||||
const res = await request.get('/api/docs', { headers: {} })
|
||||
expect(res.status()).toBe(200)
|
||||
const spec = await res.json()
|
||||
expect(spec.openapi).toBeDefined()
|
||||
})
|
||||
})
|
||||
Loading…
Reference in New Issue