fase(21): openapi documentation with scalar
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
622
dist/api/openapi.js
vendored
Normal file
622
dist/api/openapi.js
vendored
Normal file
@@ -0,0 +1,622 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.openApiSpec = void 0;
|
||||
exports.createApiDocsRouter = createApiDocsRouter;
|
||||
/**
|
||||
* OpenAPI 3.1 specification for ABE API.
|
||||
* Uses @asteasolutions/zod-to-openapi to generate from Zod schemas.
|
||||
*/
|
||||
const express_1 = require("express");
|
||||
const zod_1 = require("zod");
|
||||
const zod_to_openapi_1 = require("@asteasolutions/zod-to-openapi");
|
||||
const express_api_reference_1 = require("@scalar/express-api-reference");
|
||||
// Extend Zod with OpenAPI metadata support
|
||||
(0, zod_to_openapi_1.extendZodWithOpenApi)(zod_1.z);
|
||||
// ─── Registry ─────────────────────────────────────────────────────────────────
|
||||
const registry = new zod_to_openapi_1.OpenAPIRegistry();
|
||||
// ─── Reusable schemas ─────────────────────────────────────────────────────────
|
||||
const ErrorSchema = registry.register('Error', zod_1.z.object({ error: zod_1.z.string() }).openapi('Error'));
|
||||
// Auth schemas
|
||||
const RegisterRequestSchema = registry.register('RegisterRequest', zod_1.z.object({
|
||||
email: zod_1.z.string().email(),
|
||||
password: zod_1.z.string().min(8),
|
||||
name: zod_1.z.string().optional(),
|
||||
}).openapi('RegisterRequest'));
|
||||
const LoginRequestSchema = registry.register('LoginRequest', zod_1.z.object({
|
||||
email: zod_1.z.string().email(),
|
||||
password: zod_1.z.string(),
|
||||
}).openapi('LoginRequest'));
|
||||
const UserSchema = registry.register('User', zod_1.z.object({
|
||||
id: zod_1.z.string(),
|
||||
email: zod_1.z.string(),
|
||||
name: zod_1.z.string().nullable(),
|
||||
role: zod_1.z.enum(['owner', 'admin', 'member', 'viewer']),
|
||||
createdAt: zod_1.z.number(),
|
||||
}).openapi('User'));
|
||||
// Session schemas
|
||||
const SessionStatusSchema = zod_1.z.enum(['running', 'completed', 'failed', 'stopped']);
|
||||
const CrawlSessionSchema = registry.register('CrawlSession', zod_1.z.object({
|
||||
id: zod_1.z.string(),
|
||||
url: zod_1.z.string().url(),
|
||||
status: SessionStatusSchema,
|
||||
seed: zod_1.z.number(),
|
||||
maxStates: zod_1.z.number(),
|
||||
statesVisited: zod_1.z.number(),
|
||||
createdAt: zod_1.z.number(),
|
||||
completedAt: zod_1.z.number().nullable(),
|
||||
}).openapi('CrawlSession'));
|
||||
const StartSessionRequestSchema = registry.register('StartSessionRequest', zod_1.z.object({
|
||||
url: zod_1.z.string().url(),
|
||||
seed: zod_1.z.number().optional(),
|
||||
maxStates: zod_1.z.number().optional(),
|
||||
maxDepth: zod_1.z.number().optional(),
|
||||
allowedDomains: zod_1.z.array(zod_1.z.string()).optional(),
|
||||
excludedPaths: zod_1.z.array(zod_1.z.string()).optional(),
|
||||
}).openapi('StartSessionRequest'));
|
||||
// Finding schemas
|
||||
const SeveritySchema = zod_1.z.enum(['low', 'medium', 'high', 'critical']);
|
||||
const FindingStatusSchema = zod_1.z.enum(['open', 'investigating', 'resolved', 'closed']);
|
||||
const FindingSchema = registry.register('Finding', zod_1.z.object({
|
||||
id: zod_1.z.string(),
|
||||
sessionId: zod_1.z.string(),
|
||||
severity: SeveritySchema,
|
||||
type: zod_1.z.string(),
|
||||
description: zod_1.z.string(),
|
||||
status: FindingStatusSchema,
|
||||
createdAt: zod_1.z.number(),
|
||||
resolvedAt: zod_1.z.number().nullable(),
|
||||
}).openapi('Finding'));
|
||||
// Report schemas
|
||||
const ReportFormatSchema = zod_1.z.enum(['pdf', 'html', 'json']);
|
||||
const ReportSchema = registry.register('Report', zod_1.z.object({
|
||||
id: zod_1.z.string(),
|
||||
format: ReportFormatSchema,
|
||||
status: zod_1.z.enum(['pending', 'completed', 'failed']),
|
||||
createdAt: zod_1.z.number(),
|
||||
completedAt: zod_1.z.number().nullable(),
|
||||
}).openapi('Report'));
|
||||
// Schedule schemas
|
||||
const ScheduleSchema = registry.register('Schedule', zod_1.z.object({
|
||||
id: zod_1.z.string(),
|
||||
name: zod_1.z.string(),
|
||||
url: zod_1.z.string(),
|
||||
cronExpression: zod_1.z.string(),
|
||||
enabled: zod_1.z.boolean(),
|
||||
lastRunAt: zod_1.z.number().nullable(),
|
||||
nextRunAt: zod_1.z.number().nullable(),
|
||||
createdAt: zod_1.z.number(),
|
||||
}).openapi('Schedule'));
|
||||
// Integration schemas
|
||||
const IntegrationTypeSchema = zod_1.z.enum(['slack', 'github', 'jira', 'webhook']);
|
||||
const IntegrationSchema = registry.register('Integration', zod_1.z.object({
|
||||
id: zod_1.z.string(),
|
||||
type: IntegrationTypeSchema,
|
||||
name: zod_1.z.string(),
|
||||
enabled: zod_1.z.boolean(),
|
||||
createdAt: zod_1.z.number(),
|
||||
}).openapi('Integration'));
|
||||
// Visual comparison schemas
|
||||
const ComparisonStatusSchema = zod_1.z.enum(['passed', 'failed', 'new_state', 'pending']);
|
||||
const VisualComparisonSchema = registry.register('VisualComparison', zod_1.z.object({
|
||||
id: zod_1.z.string(),
|
||||
session_id: zod_1.z.string(),
|
||||
state_id: zod_1.z.string(),
|
||||
baseline_id: zod_1.z.string().nullable(),
|
||||
current_screenshot_path: zod_1.z.string(),
|
||||
diff_screenshot_path: zod_1.z.string().nullable(),
|
||||
diff_pixels: zod_1.z.number().nullable(),
|
||||
diff_percent: zod_1.z.number().nullable(),
|
||||
status: ComparisonStatusSchema,
|
||||
created_at: zod_1.z.number(),
|
||||
}).openapi('VisualComparison'));
|
||||
// License schema
|
||||
const LicensePlanSchema = zod_1.z.enum(['free', 'pro', 'enterprise']);
|
||||
const LicenseStatusSchema = registry.register('LicenseStatus', zod_1.z.object({
|
||||
plan: LicensePlanSchema,
|
||||
valid: zod_1.z.boolean(),
|
||||
expiresAt: zod_1.z.string().nullable(),
|
||||
features: zod_1.z.array(zod_1.z.string()),
|
||||
}).openapi('LicenseStatus'));
|
||||
// ─── Route registrations ───────────────────────────────────────────────────────
|
||||
const bearerAuth = registry.registerComponent('securitySchemes', 'BearerAuth', {
|
||||
type: 'http',
|
||||
scheme: 'bearer',
|
||||
});
|
||||
// Auth endpoints
|
||||
registry.registerPath({
|
||||
method: 'post',
|
||||
path: '/api/auth/register',
|
||||
summary: 'Register a new user',
|
||||
tags: ['Auth'],
|
||||
request: { body: { content: { 'application/json': { schema: RegisterRequestSchema } } } },
|
||||
responses: {
|
||||
201: { description: 'User registered', content: { 'application/json': { schema: UserSchema } } },
|
||||
400: { description: 'Validation error', content: { 'application/json': { schema: ErrorSchema } } },
|
||||
},
|
||||
});
|
||||
registry.registerPath({
|
||||
method: 'post',
|
||||
path: '/api/auth/login',
|
||||
summary: 'Login',
|
||||
tags: ['Auth'],
|
||||
request: { body: { content: { 'application/json': { schema: LoginRequestSchema } } } },
|
||||
responses: {
|
||||
200: { description: 'Login successful', content: { 'application/json': { schema: UserSchema } } },
|
||||
401: { description: 'Invalid credentials', content: { 'application/json': { schema: ErrorSchema } } },
|
||||
},
|
||||
});
|
||||
registry.registerPath({
|
||||
method: 'post',
|
||||
path: '/api/auth/logout',
|
||||
summary: 'Logout',
|
||||
tags: ['Auth'],
|
||||
security: [{ [bearerAuth.name]: [] }],
|
||||
responses: { 200: { description: 'Logged out' } },
|
||||
});
|
||||
registry.registerPath({
|
||||
method: 'get',
|
||||
path: '/api/auth/me',
|
||||
summary: 'Get current user',
|
||||
tags: ['Auth'],
|
||||
security: [{ [bearerAuth.name]: [] }],
|
||||
responses: {
|
||||
200: { description: 'Current user', content: { 'application/json': { schema: UserSchema } } },
|
||||
401: { description: 'Not authenticated', content: { 'application/json': { schema: ErrorSchema } } },
|
||||
},
|
||||
});
|
||||
registry.registerPath({
|
||||
method: 'get',
|
||||
path: '/api/auth/setup-required',
|
||||
summary: 'Check if first-run setup is required',
|
||||
tags: ['Auth'],
|
||||
responses: {
|
||||
200: {
|
||||
description: 'Setup status',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: zod_1.z.object({ required: zod_1.z.boolean() }),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
// Sessions endpoints
|
||||
registry.registerPath({
|
||||
method: 'get',
|
||||
path: '/api/sessions',
|
||||
summary: 'List all crawl sessions',
|
||||
tags: ['Sessions'],
|
||||
security: [{ [bearerAuth.name]: [] }],
|
||||
responses: {
|
||||
200: {
|
||||
description: 'List of sessions',
|
||||
content: { 'application/json': { schema: zod_1.z.array(CrawlSessionSchema) } },
|
||||
},
|
||||
},
|
||||
});
|
||||
registry.registerPath({
|
||||
method: 'post',
|
||||
path: '/api/sessions',
|
||||
summary: 'Start a new crawl session',
|
||||
tags: ['Sessions'],
|
||||
security: [{ [bearerAuth.name]: [] }],
|
||||
request: { body: { content: { 'application/json': { schema: StartSessionRequestSchema } } } },
|
||||
responses: {
|
||||
201: { description: 'Session started', content: { 'application/json': { schema: CrawlSessionSchema } } },
|
||||
400: { description: 'Validation error', content: { 'application/json': { schema: ErrorSchema } } },
|
||||
},
|
||||
});
|
||||
registry.registerPath({
|
||||
method: 'get',
|
||||
path: '/api/sessions/{id}',
|
||||
summary: 'Get session by ID',
|
||||
tags: ['Sessions'],
|
||||
security: [{ [bearerAuth.name]: [] }],
|
||||
request: { params: zod_1.z.object({ id: zod_1.z.string() }) },
|
||||
responses: {
|
||||
200: { description: 'Session details', content: { 'application/json': { schema: CrawlSessionSchema } } },
|
||||
404: { description: 'Not found', content: { 'application/json': { schema: ErrorSchema } } },
|
||||
},
|
||||
});
|
||||
registry.registerPath({
|
||||
method: 'delete',
|
||||
path: '/api/sessions/{id}',
|
||||
summary: 'Stop a crawl session',
|
||||
tags: ['Sessions'],
|
||||
security: [{ [bearerAuth.name]: [] }],
|
||||
request: { params: zod_1.z.object({ id: zod_1.z.string() }) },
|
||||
responses: {
|
||||
200: { description: 'Session stopped' },
|
||||
404: { description: 'Not found', content: { 'application/json': { schema: ErrorSchema } } },
|
||||
},
|
||||
});
|
||||
// Findings endpoints
|
||||
registry.registerPath({
|
||||
method: 'get',
|
||||
path: '/api/findings',
|
||||
summary: 'List findings',
|
||||
tags: ['Findings'],
|
||||
security: [{ [bearerAuth.name]: [] }],
|
||||
request: {
|
||||
query: zod_1.z.object({
|
||||
severity: zod_1.z.string().optional(),
|
||||
type: zod_1.z.string().optional(),
|
||||
status: zod_1.z.string().optional(),
|
||||
sessionId: zod_1.z.string().optional(),
|
||||
search: zod_1.z.string().optional(),
|
||||
}),
|
||||
},
|
||||
responses: {
|
||||
200: {
|
||||
description: 'List of findings',
|
||||
content: { 'application/json': { schema: zod_1.z.array(FindingSchema) } },
|
||||
},
|
||||
},
|
||||
});
|
||||
registry.registerPath({
|
||||
method: 'get',
|
||||
path: '/api/findings/{id}',
|
||||
summary: 'Get finding by ID',
|
||||
tags: ['Findings'],
|
||||
security: [{ [bearerAuth.name]: [] }],
|
||||
request: { params: zod_1.z.object({ id: zod_1.z.string() }) },
|
||||
responses: {
|
||||
200: { description: 'Finding details', content: { 'application/json': { schema: FindingSchema } } },
|
||||
404: { description: 'Not found', content: { 'application/json': { schema: ErrorSchema } } },
|
||||
},
|
||||
});
|
||||
registry.registerPath({
|
||||
method: 'post',
|
||||
path: '/api/findings/{id}/resolve',
|
||||
summary: 'Resolve a finding',
|
||||
tags: ['Findings'],
|
||||
security: [{ [bearerAuth.name]: [] }],
|
||||
request: { params: zod_1.z.object({ id: zod_1.z.string() }) },
|
||||
responses: {
|
||||
200: { description: 'Finding resolved', content: { 'application/json': { schema: FindingSchema } } },
|
||||
404: { description: 'Not found', content: { 'application/json': { schema: ErrorSchema } } },
|
||||
},
|
||||
});
|
||||
registry.registerPath({
|
||||
method: 'get',
|
||||
path: '/api/findings/stats',
|
||||
summary: 'Get finding statistics',
|
||||
tags: ['Findings'],
|
||||
security: [{ [bearerAuth.name]: [] }],
|
||||
responses: {
|
||||
200: {
|
||||
description: 'Statistics',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: zod_1.z.object({
|
||||
total: zod_1.z.number(),
|
||||
bySeverity: zod_1.z.record(zod_1.z.string(), zod_1.z.number()),
|
||||
byType: zod_1.z.record(zod_1.z.string(), zod_1.z.number()),
|
||||
byStatus: zod_1.z.record(zod_1.z.string(), zod_1.z.number()),
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
// Reports endpoints
|
||||
registry.registerPath({
|
||||
method: 'post',
|
||||
path: '/api/reports',
|
||||
summary: 'Generate a report',
|
||||
tags: ['Reports'],
|
||||
security: [{ [bearerAuth.name]: [] }],
|
||||
request: {
|
||||
body: {
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: zod_1.z.object({
|
||||
sessionId: zod_1.z.string().optional(),
|
||||
format: ReportFormatSchema,
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
responses: {
|
||||
202: { description: 'Report generation started', content: { 'application/json': { schema: ReportSchema } } },
|
||||
},
|
||||
});
|
||||
registry.registerPath({
|
||||
method: 'get',
|
||||
path: '/api/reports',
|
||||
summary: 'List reports',
|
||||
tags: ['Reports'],
|
||||
security: [{ [bearerAuth.name]: [] }],
|
||||
responses: {
|
||||
200: { description: 'Reports list', content: { 'application/json': { schema: zod_1.z.array(ReportSchema) } } },
|
||||
},
|
||||
});
|
||||
registry.registerPath({
|
||||
method: 'get',
|
||||
path: '/api/reports/{id}/download',
|
||||
summary: 'Download report file',
|
||||
tags: ['Reports'],
|
||||
security: [{ [bearerAuth.name]: [] }],
|
||||
request: { params: zod_1.z.object({ id: zod_1.z.string() }) },
|
||||
responses: {
|
||||
200: { description: 'Report file (PDF, HTML or JSON)' },
|
||||
404: { description: 'Not found', content: { 'application/json': { schema: ErrorSchema } } },
|
||||
},
|
||||
});
|
||||
// Schedules endpoints
|
||||
registry.registerPath({
|
||||
method: 'get',
|
||||
path: '/api/schedules',
|
||||
summary: 'List schedules',
|
||||
tags: ['Scheduling'],
|
||||
security: [{ [bearerAuth.name]: [] }],
|
||||
responses: {
|
||||
200: { description: 'Schedules list', content: { 'application/json': { schema: zod_1.z.array(ScheduleSchema) } } },
|
||||
},
|
||||
});
|
||||
registry.registerPath({
|
||||
method: 'post',
|
||||
path: '/api/schedules',
|
||||
summary: 'Create a schedule',
|
||||
tags: ['Scheduling'],
|
||||
security: [{ [bearerAuth.name]: [] }],
|
||||
request: {
|
||||
body: {
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: zod_1.z.object({
|
||||
name: zod_1.z.string(),
|
||||
url: zod_1.z.string().url(),
|
||||
cronExpression: zod_1.z.string(),
|
||||
config: zod_1.z.record(zod_1.z.string(), zod_1.z.unknown()).optional(),
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
responses: {
|
||||
201: { description: 'Schedule created', content: { 'application/json': { schema: ScheduleSchema } } },
|
||||
400: { description: 'Invalid cron expression', content: { 'application/json': { schema: ErrorSchema } } },
|
||||
},
|
||||
});
|
||||
registry.registerPath({
|
||||
method: 'patch',
|
||||
path: '/api/schedules/{id}/toggle',
|
||||
summary: 'Enable or disable a schedule',
|
||||
tags: ['Scheduling'],
|
||||
security: [{ [bearerAuth.name]: [] }],
|
||||
request: { params: zod_1.z.object({ id: zod_1.z.string() }) },
|
||||
responses: {
|
||||
200: { description: 'Toggled', content: { 'application/json': { schema: ScheduleSchema } } },
|
||||
404: { description: 'Not found', content: { 'application/json': { schema: ErrorSchema } } },
|
||||
},
|
||||
});
|
||||
registry.registerPath({
|
||||
method: 'delete',
|
||||
path: '/api/schedules/{id}',
|
||||
summary: 'Delete a schedule',
|
||||
tags: ['Scheduling'],
|
||||
security: [{ [bearerAuth.name]: [] }],
|
||||
request: { params: zod_1.z.object({ id: zod_1.z.string() }) },
|
||||
responses: {
|
||||
200: { description: 'Deleted' },
|
||||
404: { description: 'Not found', content: { 'application/json': { schema: ErrorSchema } } },
|
||||
},
|
||||
});
|
||||
// Integrations endpoints
|
||||
registry.registerPath({
|
||||
method: 'get',
|
||||
path: '/api/integrations',
|
||||
summary: 'List integrations',
|
||||
tags: ['Integrations'],
|
||||
security: [{ [bearerAuth.name]: [] }],
|
||||
responses: {
|
||||
200: { description: 'Integrations list', content: { 'application/json': { schema: zod_1.z.array(IntegrationSchema) } } },
|
||||
},
|
||||
});
|
||||
registry.registerPath({
|
||||
method: 'post',
|
||||
path: '/api/integrations',
|
||||
summary: 'Create an integration',
|
||||
tags: ['Integrations'],
|
||||
security: [{ [bearerAuth.name]: [] }],
|
||||
request: {
|
||||
body: {
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: zod_1.z.object({
|
||||
type: IntegrationTypeSchema,
|
||||
name: zod_1.z.string(),
|
||||
config: zod_1.z.record(zod_1.z.string(), zod_1.z.unknown()),
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
responses: {
|
||||
201: { description: 'Integration created', content: { 'application/json': { schema: IntegrationSchema } } },
|
||||
},
|
||||
});
|
||||
// Visual regression endpoints
|
||||
registry.registerPath({
|
||||
method: 'get',
|
||||
path: '/api/visual/comparisons',
|
||||
summary: 'List visual comparisons',
|
||||
tags: ['Visual Regression'],
|
||||
security: [{ [bearerAuth.name]: [] }],
|
||||
request: {
|
||||
query: zod_1.z.object({
|
||||
sessionId: zod_1.z.string().optional(),
|
||||
status: ComparisonStatusSchema.optional(),
|
||||
}),
|
||||
},
|
||||
responses: {
|
||||
200: {
|
||||
description: 'Comparisons list',
|
||||
content: { 'application/json': { schema: zod_1.z.array(VisualComparisonSchema) } },
|
||||
},
|
||||
},
|
||||
});
|
||||
registry.registerPath({
|
||||
method: 'post',
|
||||
path: '/api/visual/baselines/{comparisonId}/approve',
|
||||
summary: 'Approve a comparison as baseline',
|
||||
tags: ['Visual Regression'],
|
||||
security: [{ [bearerAuth.name]: [] }],
|
||||
request: { params: zod_1.z.object({ comparisonId: zod_1.z.string() }) },
|
||||
responses: {
|
||||
200: {
|
||||
description: 'Approved',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: zod_1.z.object({ baselineId: zod_1.z.string(), status: zod_1.z.literal('approved') }),
|
||||
},
|
||||
},
|
||||
},
|
||||
404: { description: 'Not found', content: { 'application/json': { schema: ErrorSchema } } },
|
||||
},
|
||||
});
|
||||
registry.registerPath({
|
||||
method: 'post',
|
||||
path: '/api/visual/baselines/{comparisonId}/reject',
|
||||
summary: 'Reject a comparison',
|
||||
tags: ['Visual Regression'],
|
||||
security: [{ [bearerAuth.name]: [] }],
|
||||
request: { params: zod_1.z.object({ comparisonId: zod_1.z.string() }) },
|
||||
responses: {
|
||||
200: {
|
||||
description: 'Rejected',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: zod_1.z.object({ status: zod_1.z.literal('rejected') }),
|
||||
},
|
||||
},
|
||||
},
|
||||
404: { description: 'Not found', content: { 'application/json': { schema: ErrorSchema } } },
|
||||
},
|
||||
});
|
||||
registry.registerPath({
|
||||
method: 'post',
|
||||
path: '/api/visual/baselines/approve-all',
|
||||
summary: 'Approve all new-state comparisons as baselines',
|
||||
tags: ['Visual Regression'],
|
||||
security: [{ [bearerAuth.name]: [] }],
|
||||
request: {
|
||||
body: {
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: zod_1.z.object({ sessionId: zod_1.z.string().optional() }),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
responses: {
|
||||
200: {
|
||||
description: 'Bulk approved',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: zod_1.z.object({ approved: zod_1.z.number() }),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
// License endpoints
|
||||
registry.registerPath({
|
||||
method: 'get',
|
||||
path: '/api/license/status',
|
||||
summary: 'Get license status',
|
||||
tags: ['License'],
|
||||
security: [{ [bearerAuth.name]: [] }],
|
||||
responses: {
|
||||
200: { description: 'License status', content: { 'application/json': { schema: LicenseStatusSchema } } },
|
||||
},
|
||||
});
|
||||
registry.registerPath({
|
||||
method: 'post',
|
||||
path: '/api/license/activate',
|
||||
summary: 'Activate a license key',
|
||||
tags: ['License'],
|
||||
security: [{ [bearerAuth.name]: [] }],
|
||||
request: {
|
||||
body: {
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: zod_1.z.object({ key: zod_1.z.string() }),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
responses: {
|
||||
200: { description: 'License activated', content: { 'application/json': { schema: LicenseStatusSchema } } },
|
||||
400: { description: 'Invalid key', content: { 'application/json': { schema: ErrorSchema } } },
|
||||
},
|
||||
});
|
||||
// Health endpoints
|
||||
registry.registerPath({
|
||||
method: 'get',
|
||||
path: '/health/live',
|
||||
summary: 'Liveness probe',
|
||||
tags: ['Health'],
|
||||
responses: {
|
||||
200: {
|
||||
description: 'Process alive',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: zod_1.z.object({ status: zod_1.z.literal('ok'), uptime: zod_1.z.number() }),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
registry.registerPath({
|
||||
method: 'get',
|
||||
path: '/health/ready',
|
||||
summary: 'Readiness probe',
|
||||
tags: ['Health'],
|
||||
responses: {
|
||||
200: {
|
||||
description: 'Ready',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: zod_1.z.object({ status: zod_1.z.literal('ready'), db: zod_1.z.string() }),
|
||||
},
|
||||
},
|
||||
},
|
||||
503: {
|
||||
description: 'Not ready',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: zod_1.z.object({ status: zod_1.z.literal('not_ready'), db: zod_1.z.string(), error: zod_1.z.string() }),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
// ─── Generate spec ─────────────────────────────────────────────────────────────
|
||||
const generator = new zod_to_openapi_1.OpenApiGeneratorV31(registry.definitions);
|
||||
exports.openApiSpec = generator.generateDocument({
|
||||
openapi: '3.1.0',
|
||||
info: {
|
||||
title: 'ABE — Autonomous Bug Explorer API',
|
||||
version: '1.0.0',
|
||||
description: 'ABE is an enterprise self-hosted platform for autonomous web application bug discovery. ' +
|
||||
'This API allows you to manage crawl sessions, review findings, generate reports, and configure integrations.',
|
||||
},
|
||||
servers: [{ url: 'http://localhost:3001', description: 'Local development' }],
|
||||
});
|
||||
// ─── Express Router ────────────────────────────────────────────────────────────
|
||||
function createApiDocsRouter() {
|
||||
const router = (0, express_1.Router)();
|
||||
// Serve the raw OpenAPI JSON spec
|
||||
router.get('/openapi.json', (_req, res) => {
|
||||
res.json(exports.openApiSpec);
|
||||
});
|
||||
// Serve Scalar UI
|
||||
router.use('/', (0, express_api_reference_1.apiReference)({
|
||||
spec: { content: exports.openApiSpec },
|
||||
theme: 'purple',
|
||||
}));
|
||||
return router;
|
||||
}
|
||||
3
dist/api/server.js
vendored
3
dist/api/server.js
vendored
@@ -17,6 +17,7 @@ const requestId_1 = require("./middleware/requestId");
|
||||
const notFound_1 = require("./middleware/notFound");
|
||||
const errorHandler_1 = require("./middleware/errorHandler");
|
||||
const router_1 = require("./router");
|
||||
const openapi_1 = require("./openapi");
|
||||
function createServer(deps) {
|
||||
const app = (0, express_1.default)();
|
||||
// 1. Request ID — must be first so all logs have requestId
|
||||
@@ -58,6 +59,8 @@ function createServer(deps) {
|
||||
});
|
||||
// 7. Module routes
|
||||
app.use('/api', (0, router_1.createRouter)(deps));
|
||||
// 7b. API documentation (no auth required)
|
||||
app.use('/api-docs', (0, openapi_1.createApiDocsRouter)());
|
||||
// 8. 404 handler
|
||||
app.use(notFound_1.notFoundMiddleware);
|
||||
// 9. Global error handler — always last
|
||||
|
||||
Reference in New Issue
Block a user