Implement swagger-autogen for API documentation

This commit is contained in:
hypnotoad08 2026-01-09 20:33:12 -05:00
parent 122fc34a75
commit 0694c2fb1c
5 changed files with 7114 additions and 0 deletions

View file

@ -31,3 +31,12 @@ redocly bundle root.yaml > bundled.yaml && \
yq -p yaml -o json bundled.yaml > openapi.json && \ yq -p yaml -o json bundled.yaml > openapi.json && \
redocly build-docs openapi.json redocly build-docs openapi.json
``` ```
The OpenAPI specification is generated using [swagger-autogen](https://swagger-autogen.github.io/).
To regenerate, run:
```
node swagger.js
```
Generates `docs/swagger-output.json`

6874
docs/swagger-output.json Normal file

File diff suppressed because it is too large Load diff

118
swagger.js Normal file
View file

@ -0,0 +1,118 @@
const swaggerAutogen = require('swagger-autogen')({ openapi: '3.0.0' })
const { version } = require('./package.json')
const doc = {
info: {
version: version,
title: 'Audiobookshelf API',
description: 'Self-hosted audiobook and podcast server API'
},
servers: [
{
url: 'http://localhost:3333',
description: 'Local development server'
}
],
tags: [
{ name: 'Libraries', description: 'Library management endpoints' },
{ name: 'Library Items', description: 'Library item management endpoints' },
{ name: 'Users', description: 'User management endpoints' },
{ name: 'Collections', description: 'Collection management endpoints' },
{ name: 'Playlists', description: 'Playlist management endpoints' },
{ name: 'Me', description: 'Current user endpoints' },
{ name: 'Backups', description: 'Backup management endpoints' },
{ name: 'Series', description: 'Series management endpoints' },
{ name: 'Authors', description: 'Author management endpoints' },
{ name: 'Sessions', description: 'Playback session endpoints' },
{ name: 'Podcasts', description: 'Podcast management endpoints' },
{ name: 'Notifications', description: 'Notification management endpoints' },
{ name: 'Email', description: 'Email configuration endpoints' },
{ name: 'Search', description: 'Search endpoints' },
{ name: 'Cache', description: 'Cache management endpoints' },
{ name: 'Tools', description: 'Tool endpoints' },
{ name: 'RSS Feeds', description: 'RSS feed management endpoints' },
{ name: 'Custom Metadata Providers', description: 'Custom metadata provider endpoints' },
{ name: 'Misc', description: 'Miscellaneous endpoints' },
{ name: 'Share', description: 'Media sharing endpoints' },
{ name: 'Stats', description: 'Statistics endpoints' },
{ name: 'API Keys', description: 'API key management endpoints' },
{ name: 'File System', description: 'File system endpoints' }
],
components: {
securitySchemes: {
bearerAuth: {
type: 'http',
scheme: 'bearer',
bearerFormat: 'JWT',
description: 'JWT Authorization header using the Bearer scheme'
},
cookieAuth: {
type: 'apiKey',
in: 'cookie',
name: 'token',
description: 'Session cookie authentication'
}
}
},
security: [{ bearerAuth: [] }, { cookieAuth: [] }]
}
const outputFile = './docs/swagger-output.json'
const routes = ['./server/routers/ApiRouter.js']
swaggerAutogen(outputFile, routes, doc).then((data) => {
const spec = data.data
const fs = require('fs')
const tagMap = {
libraries: ['Libraries', /\/libraries/i],
items: ['Library Items', /\/items/i],
users: ['Users', /\/users/i],
collections: ['Collections', /\/collections/i],
playlists: ['Playlists', /\/playlists/i],
me: ['Me', /\/me/i],
backups: ['Backups', /\/backups/i],
series: ['Series', /\/series/i],
authors: ['Authors', /\/authors/i],
sessions: ['Sessions', /\/sessions|\/session/i],
podcasts: ['Podcasts', /\/podcasts/i],
notifications: ['Notifications', /\/notifications/i],
email: ['Email', /\/emails/i],
search: ['Search', /\/search/i],
cache: ['Cache', /\/cache/i],
tools: ['Tools', /\/tools/i],
feeds: ['RSS Feeds', /\/feeds/i],
metadata: ['Custom Metadata Providers', /\/custom-metadata-providers/i],
share: ['Share', /\/share/i],
stats: ['Stats', /\/stats/i],
apikeys: ['API Keys', /\/api-keys/i],
filesystem: ['File System', /\/filesystem/i],
misc: ['Misc', /.*/]
}
for (const path in spec.paths) {
const pathSpec = spec.paths[path]
for (const method in pathSpec) {
if (['get', 'post', 'put', 'patch', 'delete'].includes(method)) {
const operation = pathSpec[method]
for (const key in tagMap) {
const [tagName, pattern] = tagMap[key]
if (pattern.test(path)) {
if (!operation.tags) operation.tags = []
if (!operation.tags.includes(tagName)) {
operation.tags = [tagName]
}
break
}
}
}
}
}
fs.writeFileSync(outputFile, JSON.stringify(spec, null, 2))
console.log('Swagger documentation generated successfully!')
console.log(`Generated ${Object.keys(spec.paths).length} endpoints`)
})

56
view-docs.js Normal file
View file

@ -0,0 +1,56 @@
#!/usr/bin/env node
/**
* View Swagger documentation in browser
* Opens ReDoc documentation for the generated swagger-output.json
*/
const http = require('http')
const fs = require('fs')
const path = require('path')
const swaggerPath = path.join(__dirname, 'docs', 'swagger-output.json')
const port = 3004
if (!fs.existsSync(swaggerPath)) {
console.error('swagger-output.json not found. Run "node swagger.js" first.')
process.exit(1)
}
const html = `
<!DOCTYPE html>
<html>
<head>
<title>Audiobookshelf API Documentation</title>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700" rel="stylesheet">
<style>
body {
margin: 0;
padding: 0;
}
</style>
</head>
<body>
<redoc spec-url='/swagger.json'></redoc>
<script src="https://cdn.jsdelivr.net/npm/redoc@next/bundles/redoc.standalone.js"> </script>
</body>
</html>
`
const server = http.createServer((req, res) => {
if (req.url === '/') {
res.writeHead(200, { 'Content-Type': 'text/html' })
res.end(html)
} else if (req.url === '/swagger.json') {
res.writeHead(200, { 'Content-Type': 'application/json' })
res.end(fs.readFileSync(swaggerPath))
} else {
res.writeHead(404)
res.end('Not found')
}
})
server.listen(port, () => {
console.log(`ReDoc API documentation: http://localhost:${port}`)
})

57
view-swagger.js Normal file
View file

@ -0,0 +1,57 @@
#!/usr/bin/env node
/**
* View Swagger documentation in browser
* Opens ReDoc documentation for the generated swagger-output.json
*/
const http = require('http')
const fs = require('fs')
const path = require('path')
const { spawn } = require('child_process')
const swaggerPath = path.join(__dirname, 'docs', 'swagger-output.json')
const port = 3004
if (!fs.existsSync(swaggerPath)) {
console.error('swagger-output.json not found. Run "node swagger.js" first.')
process.exit(1)
}
const html = `
<!DOCTYPE html>
<html>
<head>
<title>Audiobookshelf API Documentation</title>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700" rel="stylesheet">
<style>
body {
margin: 0;
padding: 0;
}
</style>
</head>
<body>
<redoc spec-url='/swagger.json'></redoc>
<script src="https://cdn.jsdelivr.net/npm/redoc@next/bundles/redoc.standalone.js"> </script>
</body>
</html>
`
const server = http.createServer((req, res) => {
if (req.url === '/') {
res.writeHead(200, { 'Content-Type': 'text/html' })
res.end(html)
} else if (req.url === '/swagger.json') {
res.writeHead(200, { 'Content-Type': 'application/json' })
res.end(fs.readFileSync(swaggerPath))
} else {
res.writeHead(404)
res.end('Not found')
}
})
server.listen(port, () => {
console.log(`ReDoc API documentation: http://localhost:${port}`)
})