update swagger for api

This commit is contained in:
root
2026-05-26 18:19:56 -04:00
parent 797e01eece
commit 8b3eb588f2
5 changed files with 1228 additions and 10 deletions
+4 -1
View File
@@ -41,8 +41,10 @@
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
"resend": "^3.2.0", "resend": "^3.2.0",
"socket.io": "^4.7.5", "socket.io": "^4.7.5",
"swagger-ui-express": "^5.0.1",
"twilio": "^5.1.0", "twilio": "^5.1.0",
"zod": "^3.23.0" "zod": "^3.23.0",
"zod-to-json-schema": "^3.25.2"
}, },
"devDependencies": { "devDependencies": {
"@types/bcryptjs": "^2.4.6", "@types/bcryptjs": "^2.4.6",
@@ -58,6 +60,7 @@
"@types/react": "^18.3.1", "@types/react": "^18.3.1",
"@types/react-dom": "^18.3.0", "@types/react-dom": "^18.3.0",
"@types/supertest": "^6.0.2", "@types/supertest": "^6.0.2",
"@types/swagger-ui-express": "^4.1.8",
"supertest": "^7.0.0", "supertest": "^7.0.0",
"ts-node-dev": "^2.0.0", "ts-node-dev": "^2.0.0",
"typescript": "^5.4.0", "typescript": "^5.4.0",
+14 -8
View File
@@ -2,6 +2,8 @@ import express from 'express'
import cors from 'cors' import cors from 'cors'
import helmet from 'helmet' import helmet from 'helmet'
import morgan from 'morgan' import morgan from 'morgan'
import swaggerUi from 'swagger-ui-express'
import { openApiDocument } from './swagger/openapi'
import { getLegacyStorageRoots, getStorageRoot } from './lib/storage' import { getLegacyStorageRoots, getStorageRoot } from './lib/storage'
import { authLimiter, apiLimiter, publicLimiter, adminLimiter } from './middleware/rateLimiter' import { authLimiter, apiLimiter, publicLimiter, adminLimiter } from './middleware/rateLimiter'
@@ -100,6 +102,10 @@ export function createApp() {
app.use('/storage', express.static(legacyRoot)) app.use('/storage', express.static(legacyRoot))
} }
// Swagger UI — mounted before helmet so its assets are not blocked by CSP
app.use('/docs', swaggerUi.serve, swaggerUi.setup(openApiDocument, { customSiteTitle: 'RentalDriveGo API Docs' }))
app.get('/api/v1/openapi.json', (_req, res) => res.json(openApiDocument))
// Webhook must use raw body BEFORE express.json() // Webhook must use raw body BEFORE express.json()
app.use('/api/v1/webhooks', express.raw({ type: 'application/json' }), webhookRouter) app.use('/api/v1/webhooks', express.raw({ type: 'application/json' }), webhookRouter)
@@ -141,14 +147,14 @@ export function createApp() {
}) })
app.get(`${v1}/docs`, (_req, res) => { app.get(`${v1}/docs`, (_req, res) => {
res.json({ name: 'rentaldrivego-api', version: '1.0.0', baseUrl: v1, docsUrl: '/docs', routes: routeDocs }) res.json({
}) name: 'rentaldrivego-api',
version: '1.0.0',
app.get('/docs', (_req, res) => { baseUrl: v1,
const rows = routeDocs routes: routeDocs,
.map((r) => `<tr><td>${r.method}</td><td><code>${r.path}</code></td><td>${r.description}</td></tr>`) swaggerUi: '/docs',
.join('') openApi: '/api/v1/openapi.json',
res.type('html').send(`<!doctype html><html lang="en"><head><meta charset="utf-8"/><title>RentalDriveGo API Docs</title></head><body><h1>RentalDriveGo API</h1><table><thead><tr><th>Method</th><th>Path</th><th>Description</th></tr></thead><tbody>${rows}</tbody></table></body></html>`) })
}) })
// ─── Error handler ────────────────────────────────────────── // ─── Error handler ──────────────────────────────────────────
File diff suppressed because it is too large Load Diff
@@ -17,5 +17,14 @@ describe('GET /health', () => {
expect(res.status).toBe(200) expect(res.status).toBe(200)
expect(res.body.name).toBe('rentaldrivego-api') expect(res.body.name).toBe('rentaldrivego-api')
expect(Array.isArray(res.body.routes)).toBe(true) expect(Array.isArray(res.body.routes)).toBe(true)
expect(res.body.swaggerUi).toBe('/docs')
expect(res.body.openApi).toBe('/api/v1/openapi.json')
})
it('GET /api/v1/openapi.json returns the OpenAPI document', async () => {
const res = await request(app).get('/api/v1/openapi.json')
expect(res.status).toBe(200)
expect(res.body.openapi).toBe('3.0.3')
expect(res.body.info?.title).toBe('RentalDriveGo API')
}) })
}) })
+55 -1
View File
@@ -72,8 +72,10 @@
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
"resend": "^3.2.0", "resend": "^3.2.0",
"socket.io": "^4.7.5", "socket.io": "^4.7.5",
"swagger-ui-express": "^5.0.1",
"twilio": "^5.1.0", "twilio": "^5.1.0",
"zod": "^3.23.0" "zod": "^3.23.0",
"zod-to-json-schema": "^3.25.2"
}, },
"devDependencies": { "devDependencies": {
"@types/bcryptjs": "^2.4.6", "@types/bcryptjs": "^2.4.6",
@@ -89,6 +91,7 @@
"@types/react": "^18.3.1", "@types/react": "^18.3.1",
"@types/react-dom": "^18.3.0", "@types/react-dom": "^18.3.0",
"@types/supertest": "^6.0.2", "@types/supertest": "^6.0.2",
"@types/swagger-ui-express": "^4.1.8",
"supertest": "^7.0.0", "supertest": "^7.0.0",
"ts-node-dev": "^2.0.0", "ts-node-dev": "^2.0.0",
"typescript": "^5.4.0", "typescript": "^5.4.0",
@@ -2692,6 +2695,13 @@
"win32" "win32"
] ]
}, },
"node_modules/@scarf/scarf": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz",
"integrity": "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==",
"hasInstallScript": true,
"license": "Apache-2.0"
},
"node_modules/@selderee/plugin-htmlparser2": { "node_modules/@selderee/plugin-htmlparser2": {
"version": "0.11.0", "version": "0.11.0",
"resolved": "https://registry.npmjs.org/@selderee/plugin-htmlparser2/-/plugin-htmlparser2-0.11.0.tgz", "resolved": "https://registry.npmjs.org/@selderee/plugin-htmlparser2/-/plugin-htmlparser2-0.11.0.tgz",
@@ -3167,6 +3177,17 @@
"@types/superagent": "^8.1.0" "@types/superagent": "^8.1.0"
} }
}, },
"node_modules/@types/swagger-ui-express": {
"version": "4.1.8",
"resolved": "https://registry.npmjs.org/@types/swagger-ui-express/-/swagger-ui-express-4.1.8.tgz",
"integrity": "sha512-AhZV8/EIreHFmBV5wAs0gzJUNq9JbbSXgJLQubCC0jtIo6prnI9MIRRxnU4MZX9RB9yXxF1V4R7jtLl/Wcj31g==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/express": "*",
"@types/serve-static": "*"
}
},
"node_modules/@types/tough-cookie": { "node_modules/@types/tough-cookie": {
"version": "4.0.5", "version": "4.0.5",
"resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz",
@@ -9495,6 +9516,30 @@
"integrity": "sha512-djbJ/vZKZO+gPoSDThGNpKDO+o+bAeA4XQKovvkNCqnIS2t+S4qnLAGQhyyrulhCFRl1WWzAp0wUDV8PpTVU3g==", "integrity": "sha512-djbJ/vZKZO+gPoSDThGNpKDO+o+bAeA4XQKovvkNCqnIS2t+S4qnLAGQhyyrulhCFRl1WWzAp0wUDV8PpTVU3g==",
"license": "ISC" "license": "ISC"
}, },
"node_modules/swagger-ui-dist": {
"version": "5.32.6",
"resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.32.6.tgz",
"integrity": "sha512-75ttZNaYCLoFPnozPZcTUU6mS3wKT8l7WLjU5zJSHFeJa23i5vtnze6IiCl4jDMPeQTXVXIgovq4M11NNfQvSA==",
"license": "Apache-2.0",
"dependencies": {
"@scarf/scarf": "=1.4.0"
}
},
"node_modules/swagger-ui-express": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-5.0.1.tgz",
"integrity": "sha512-SrNU3RiBGTLLmFU8GIJdOdanJTl4TOmT27tt3bWWHppqYmAZ6IDuEuBvMU6nZq0zLEe6b/1rACXCgLZqO6ZfrA==",
"license": "MIT",
"dependencies": {
"swagger-ui-dist": ">=5.0.0"
},
"engines": {
"node": ">= v0.10.32"
},
"peerDependencies": {
"express": ">=4.0.0 || >=5.0.0-beta"
}
},
"node_modules/tailwindcss": { "node_modules/tailwindcss": {
"version": "3.4.19", "version": "3.4.19",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz",
@@ -10688,6 +10733,15 @@
"url": "https://github.com/sponsors/colinhacks" "url": "https://github.com/sponsors/colinhacks"
} }
}, },
"node_modules/zod-to-json-schema": {
"version": "3.25.2",
"resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz",
"integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==",
"license": "ISC",
"peerDependencies": {
"zod": "^3.25.28 || ^4"
}
},
"packages/database": { "packages/database": {
"name": "@rentaldrivego/database", "name": "@rentaldrivego/database",
"version": "1.0.0", "version": "1.0.0",