import fs from 'fs' import path from 'path' export default defineEventHandler(async (event) => { try { const body = await readBody(event) const { collections, config } = body if (!collections || !Array.isArray(collections)) { throw createError({ statusCode: 400, statusMessage: 'Collections array is required' }) } // Set appropriate headers setHeader(event, 'Content-Type', 'application/json') // Generate OpenAPI specification from collections const openApiSpec = generateOpenApiFromCollections(collections, config) // Save the generated spec to file await saveOpenApiToFile(openApiSpec, 'openapi-collection.json') return { statusCode: 200, message: 'OpenAPI specification generated successfully', data: { filename: 'openapi-collection.json', url: '/openapi-coll.json', spec: openApiSpec } } } catch (error) { console.error('Error generating OpenAPI from collections:', error) throw createError({ statusCode: 500, statusMessage: 'Failed to generate OpenAPI specification', data: { error: error.message } }) } }) async function saveOpenApiToFile(spec, filename) { const docsDir = path.join(process.cwd(), 'docs') // Ensure docs directory exists if (!fs.existsSync(docsDir)) { fs.mkdirSync(docsDir, { recursive: true }) } const filePath = path.join(docsDir, filename) fs.writeFileSync(filePath, JSON.stringify(spec, null, 2), 'utf-8') } function generateOpenApiFromCollections(collections, config = {}) { const spec = { openapi: '3.0.3', info: { title: config.title || 'Generated API Documentation from Collections', description: config.description || 'API documentation generated from saved collections', version: config.version || '1.0.0', contact: config.contact || { name: 'API Support', email: 'support@example.com' } }, servers: [ { url: '{protocol}://{host}:{port}/api', description: 'API Server', variables: { protocol: { enum: ['http', 'https'], default: 'http' }, host: { default: 'localhost' }, port: { default: '3000' } } } ], tags: [], paths: {}, components: { schemas: { SuccessResponse: { type: 'object', properties: { statusCode: { type: 'integer', example: 200 }, message: { type: 'string', example: 'Operation successful' }, data: { type: 'object', additionalProperties: true } } }, ErrorResponse: { type: 'object', properties: { statusCode: { type: 'integer', example: 400 }, message: { type: 'string', example: 'Error message' }, errors: { type: 'object', additionalProperties: { type: 'array', items: { type: 'string' } } } } } }, securitySchemes: { bearerAuth: { type: 'http', scheme: 'bearer', bearerFormat: 'JWT', description: 'JWT Bearer token authentication' } } }, security: [ { bearerAuth: [] } ] } // Create tags from collections spec.tags = collections.map(collection => ({ name: slugify(collection.name), description: collection.description || `Endpoints from ${collection.name} collection` })) // Process each collection and its requests collections.forEach(collection => { const tagName = slugify(collection.name) collection.requests.forEach(request => { const path = extractPathFromUrl(request.url) const method = request.method.toLowerCase() if (!spec.paths[path]) { spec.paths[path] = {} } // Generate operation from request const operation = generateOperationFromRequest(request, tagName) spec.paths[path][method] = operation }) }) return spec } function generateOperationFromRequest(request, tagName) { const operation = { tags: [tagName], summary: request.name || `${request.method} request`, description: request.description || `${request.method} request to ${request.url}`, operationId: generateOperationId(request.method, request.name), parameters: [], responses: { 200: { description: 'Successful response', content: { 'application/json': { schema: { $ref: '#/components/schemas/SuccessResponse' } } } }, 400: { description: 'Bad request', content: { 'application/json': { schema: { $ref: '#/components/schemas/ErrorResponse' } } } }, 401: { description: 'Unauthorized', content: { 'application/json': { schema: { $ref: '#/components/schemas/ErrorResponse' } } } }, 500: { description: 'Internal server error', content: { 'application/json': { schema: { $ref: '#/components/schemas/ErrorResponse' } } } } } } // Add query parameters if (request.params && Array.isArray(request.params)) { request.params.forEach(param => { if (param.active && param.key) { operation.parameters.push({ name: param.key, in: 'query', description: param.description || `${param.key} parameter`, required: false, schema: { type: 'string', example: param.value || '' } }) } }) } // Add headers as parameters if (request.headers && Array.isArray(request.headers)) { request.headers.forEach(header => { if (header.active && header.key && !isStandardHeader(header.key)) { operation.parameters.push({ name: header.key, in: 'header', description: header.description || `${header.key} header`, required: false, schema: { type: 'string', example: header.value || '' } }) } }) } // Add request body for POST, PUT, PATCH methods if (['post', 'put', 'patch'].includes(request.method.toLowerCase())) { if (request.body && request.body.raw) { let bodySchema try { // Try to parse JSON and infer schema const parsedBody = JSON.parse(request.body.raw) bodySchema = inferSchemaFromObject(parsedBody) } catch { // Fallback to string if not valid JSON bodySchema = { type: 'string', example: request.body.raw } } operation.requestBody = { required: true, content: { 'application/json': { schema: bodySchema } } } } } // Add security if auth is configured if (request.auth && request.auth.type !== 'none') { operation.security = [{ bearerAuth: [] }] } return operation } function extractPathFromUrl(url) { try { const urlObj = new URL(url) return urlObj.pathname } catch { // If URL is invalid, try to extract path-like string const pathMatch = url.match(/(?:https?:\/\/[^\/]+)?(.*)/) return pathMatch ? pathMatch[1] : url } } function slugify(text) { return text .toLowerCase() .replace(/[^a-z0-9]+/g, '-') .replace(/^-+|-+$/g, '') } function generateOperationId(method, name) { const cleanName = name.replace(/[^a-zA-Z0-9]/g, '') return `${method.toLowerCase()}${cleanName}` } function isStandardHeader(headerName) { const standardHeaders = [ 'accept', 'accept-encoding', 'accept-language', 'authorization', 'cache-control', 'connection', 'content-length', 'content-type', 'cookie', 'host', 'user-agent', 'referer', 'origin' ] return standardHeaders.includes(headerName.toLowerCase()) } function inferSchemaFromObject(obj) { if (obj === null) { return { type: 'null' } } if (Array.isArray(obj)) { return { type: 'array', items: obj.length > 0 ? inferSchemaFromObject(obj[0]) : { type: 'string' } } } if (typeof obj === 'object') { const properties = {} const required = [] Object.keys(obj).forEach(key => { properties[key] = inferSchemaFromObject(obj[key]) if (obj[key] !== null && obj[key] !== undefined) { required.push(key) } }) const schema = { type: 'object', properties } if (required.length > 0) { schema.required = required } return schema } if (typeof obj === 'string') { return { type: 'string', example: obj } } if (typeof obj === 'number') { return Number.isInteger(obj) ? { type: 'integer', example: obj } : { type: 'number', example: obj } } if (typeof obj === 'boolean') { return { type: 'boolean', example: obj } } return { type: 'string' } }