docs(backend): Setup swagger-autogen

This commit is contained in:
Tibo De Peuter 2025-03-07 20:00:54 +01:00 committed by Tibo De Peuter
parent 3cd7496989
commit ab8ece2a76
Signed by: tdpeuter
GPG key ID: 38297DE43F75FFE2
9 changed files with 929 additions and 19 deletions

View file

@ -25,6 +25,7 @@
"js-yaml": "^4.1.0",
"loki-logger-ts": "^1.0.2",
"response-time": "^2.3.3",
"swagger-ui-express": "^5.0.1",
"uuid": "^11.1.0",
"winston": "^3.17.0",
"winston-loki": "^6.1.3"
@ -34,6 +35,7 @@
"@types/express": "^5.0.0",
"@types/node": "^22.13.4",
"@types/response-time": "^2.3.8",
"@types/swagger-ui-express": "^4.1.8",
"globals": "^15.15.0",
"ts-node": "^10.9.2",
"tsx": "^4.19.3",

View file

@ -16,6 +16,8 @@ import { getLogger, Logger } from './logging/initalize.js';
import { responseTimeLogger } from './logging/responseTimeLogger.js';
import responseTime from 'response-time';
import { EnvVars, getNumericEnvVar } from './util/envvars.js';
import swaggerMiddleware from "./swagger";
import swaggerUi from "swagger-ui-express";
const logger: Logger = getLogger();
@ -23,7 +25,6 @@ const app: Express = express();
const port: string | number = getNumericEnvVar(EnvVars.Port);
app.use(express.json());
app.use(responseTime(responseTimeLogger));
// TODO Replace with Express routes
app.get('/', (_, res: Response) => {
@ -33,17 +34,24 @@ app.get('/', (_, res: Response) => {
});
});
app.use('/student', studentRouter);
app.use('/group', groupRouter);
app.use('/assignment', assignmentRouter);
app.use('/submission', submissionRouter);
app.use('/class', classRouter);
app.use('/question', questionRouter);
app.use('/login', loginRouter);
// Routes
app.use('/student', studentRouter /* #swagger.tags = ['Student'] */);
app.use('/group', groupRouter /* #swagger.tags = ['Group'] */);
app.use('/assignment', assignmentRouter /* #swagger.tags = ['Assignment'] */);
app.use('/submission', submissionRouter /* #swagger.tags = ['Submission'] */);
app.use('/class', classRouter /* #swagger.tags = ['Class'] */);
app.use('/question', questionRouter /* #swagger.tags = ['Question'] */);
app.use('/login', loginRouter /* #swagger.tags = ['Login'] */);
app.use('/theme', themeRoutes);
app.use('/learningPath', learningPathRoutes);
app.use('/learningObject', learningObjectRoutes);
app.use('/theme', themeRoutes /* #swagger.tags = ['Theme'] */);
app.use('/learningPath', learningPathRoutes /* #swagger.tags = ['Learning Path'] */);
app.use('/learningObject', learningObjectRoutes /* #swagger.tags = ['Learning Object'] */);
// Add response time loggin
app.use(responseTime(responseTimeLogger));
// Swagger UI for API documentation
app.use('/api-docs', swaggerUi.serve, swaggerMiddleware);
async function startServer() {
await initORM();

7
backend/src/swagger.ts Normal file
View file

@ -0,0 +1,7 @@
import { RequestHandler } from 'express';
import swaggerUi from 'swagger-ui-express';
import swaggerDocument from '../../docs/api/swagger.json';
const swaggerMiddleware: RequestHandler = swaggerUi.setup(swaggerDocument);
export default swaggerMiddleware;

View file

@ -3,6 +3,7 @@
"include": ["src/**/*.ts"],
"compilerOptions": {
"rootDir": "./src",
"outDir": "./dist"
"outDir": "./dist",
"resolveJsonModule": true
}
}

28
docs/api/generate.ts Normal file
View file

@ -0,0 +1,28 @@
import swaggerAutogen from "swagger-autogen";
const doc = {
info: {
version: '0.1.0',
title: 'Dwengo-1 Backend API',
description: 'Dwengo-1 Backend API using Express, based on VZW Dwengo',
license: {
name: 'MIT',
url: 'https://github.com/SELab-2/Dwengo-1/blob/336496ab6352ee3f8bf47490c90b5cf81526cef6/LICENSE'
}
},
servers: [
{
url: 'http://localhost:3000/',
},
{
url: 'https://sel2-1.ugent.be/api'
}
]
};
const outputFile = './swagger.json';
const routes = [
'../../backend/src/app.ts'
];
swaggerAutogen({ openapi: '3.1.0' })(outputFile, routes, doc);

712
docs/api/swagger.json Normal file
View file

@ -0,0 +1,712 @@
{
"openapi": "3.1.0",
"info": {
"version": "0.1.0",
"title": "Dwengo-1 Backend API",
"description": "Dwengo-1 Backend API using Express, based on VZW Dwengo",
"license": {
"name": "MIT",
"url": "https://github.com/SELab-2/Dwengo-1/blob/336496ab6352ee3f8bf47490c90b5cf81526cef6/LICENSE"
}
},
"servers": [
{
"url": "http://localhost:3000/"
},
{
"url": "https://sel2-1.ugent.be/api"
}
],
"paths": {
"/": {
"get": {
"description": "",
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/student/": {
"get": {
"tags": [
"Student"
],
"description": "",
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/student/{id}": {
"get": {
"tags": [
"Student"
],
"description": "",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/student/{id}/classes": {
"get": {
"tags": [
"Student"
],
"description": "",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/student/{id}/submissions": {
"get": {
"tags": [
"Student"
],
"description": "",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/student/{id}/assignments": {
"get": {
"tags": [
"Student"
],
"description": "",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/student/{id}/groups": {
"get": {
"tags": [
"Student"
],
"description": "",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/group/": {
"get": {
"tags": [
"Group"
],
"description": "",
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/group/{id}": {
"get": {
"tags": [
"Group"
],
"description": "",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/group/{id}/question": {
"get": {
"tags": [
"Group"
],
"description": "",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/assignment/": {
"get": {
"tags": [
"Assignment"
],
"description": "",
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/assignment/{id}": {
"get": {
"tags": [
"Assignment"
],
"description": "",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/assignment/{id}/submissions": {
"get": {
"tags": [
"Assignment"
],
"description": "",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/assignment/{id}/groups": {
"get": {
"tags": [
"Assignment"
],
"description": "",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/assignment/{id}/questions": {
"get": {
"tags": [
"Assignment"
],
"description": "",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/submission/": {
"get": {
"tags": [
"Submission"
],
"description": "",
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/submission/{id}": {
"get": {
"tags": [
"Submission"
],
"description": "",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/class/": {
"get": {
"tags": [
"Class"
],
"description": "",
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/class/{id}": {
"get": {
"tags": [
"Class"
],
"description": "",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/class/{id}/invitations": {
"get": {
"tags": [
"Class"
],
"description": "",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/class/{id}/assignments": {
"get": {
"tags": [
"Class"
],
"description": "",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/class/{id}/students": {
"get": {
"tags": [
"Class"
],
"description": "",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/question/": {
"get": {
"tags": [
"Question"
],
"description": "",
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/question/{id}": {
"get": {
"tags": [
"Question"
],
"description": "",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/question/{id}/answers": {
"get": {
"tags": [
"Question"
],
"description": "",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/login/": {
"get": {
"tags": [
"Login"
],
"description": "",
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/theme/": {
"get": {
"tags": [
"Theme"
],
"description": "",
"parameters": [
{
"name": "language",
"in": "query",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/theme/{theme}": {
"get": {
"tags": [
"Theme"
],
"description": "",
"parameters": [
{
"name": "theme",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "OK"
},
"404": {
"description": "Not Found"
}
}
}
},
"/learningPath/": {
"get": {
"tags": [
"Learning Path"
],
"description": "",
"parameters": [
{
"name": "hruid",
"in": "query",
"schema": {
"type": "string"
}
},
{
"name": "theme",
"in": "query",
"schema": {
"type": "string"
}
},
{
"name": "search",
"in": "query",
"schema": {
"type": "string"
}
},
{
"name": "language",
"in": "query",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "OK"
},
"404": {
"description": "Not Found"
},
"500": {
"description": "Internal Server Error"
}
}
}
},
"/learningObject/": {
"get": {
"tags": [
"Learning Object"
],
"description": "",
"parameters": [
{
"name": "hruid",
"in": "query",
"schema": {
"type": "string"
}
},
{
"name": "full",
"in": "query",
"schema": {
"type": "string"
}
},
{
"name": "language",
"in": "query",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "OK"
},
"400": {
"description": "Bad Request"
},
"500": {
"description": "Internal Server Error"
}
}
}
},
"/learningObject/{hruid}": {
"get": {
"tags": [
"Learning Object"
],
"description": "",
"parameters": [
{
"name": "hruid",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "language",
"in": "query",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "OK"
},
"400": {
"description": "Bad Request"
},
"500": {
"description": "Internal Server Error"
}
}
}
}
}
}

14
docs/package.json Normal file
View file

@ -0,0 +1,14 @@
{
"name": "dwengo-1-docs",
"version": "0.0.1",
"description": "Documentation for Dwengo-1",
"private": true,
"scripts": {
"build": "npm run architecture && npm run swagger",
"architecture": "python3 -m venv .venv && source .venv/bin/activate && pip install -r docs/requirements.txt && python docs/architecture/schema.py",
"swagger": "tsx api/generate.ts"
},
"devDependencies": {
"swagger-autogen": "^2.23.7"
}
}

148
package-lock.json generated
View file

@ -10,7 +10,8 @@
"license": "MIT",
"workspaces": [
"backend",
"frontend"
"frontend",
"docs"
],
"devDependencies": {
"@eslint/compat": "^1.2.6",
@ -39,6 +40,7 @@
"js-yaml": "^4.1.0",
"loki-logger-ts": "^1.0.2",
"response-time": "^2.3.3",
"swagger-ui-express": "^5.0.1",
"uuid": "^11.1.0",
"winston": "^3.17.0",
"winston-loki": "^6.1.3"
@ -48,6 +50,7 @@
"@types/express": "^5.0.0",
"@types/node": "^22.13.4",
"@types/response-time": "^2.3.8",
"@types/swagger-ui-express": "^4.1.8",
"globals": "^15.15.0",
"ts-node": "^10.9.2",
"tsx": "^4.19.3",
@ -82,6 +85,13 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"docs": {
"name": "dwengo-1-docs",
"version": "0.0.1",
"devDependencies": {
"swagger-autogen": "^2.23.7"
}
},
"frontend": {
"name": "dwengo-1-frontend",
"version": "0.0.1",
@ -1556,6 +1566,13 @@
"linux"
]
},
"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/@sec-ant/readable-stream": {
"version": "0.4.1",
"dev": true,
@ -1739,6 +1756,17 @@
"@types/send": "*"
}
},
"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": {
"version": "4.0.5",
"dev": true,
@ -3300,6 +3328,16 @@
"dev": true,
"license": "MIT"
},
"node_modules/deepmerge": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
"integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/default-browser": {
"version": "5.2.1",
"dev": true,
@ -3415,6 +3453,10 @@
"resolved": "backend",
"link": true
},
"node_modules/dwengo-1-docs": {
"resolved": "docs",
"link": true
},
"node_modules/dwengo-1-frontend": {
"resolved": "frontend",
"link": true
@ -4355,8 +4397,8 @@
},
"node_modules/fs.realpath": {
"version": "1.0.0",
"license": "ISC",
"optional": true
"devOptional": true,
"license": "ISC"
},
"node_modules/function-bind": {
"version": "1.1.2",
@ -4806,8 +4848,8 @@
},
"node_modules/inflight": {
"version": "1.0.6",
"devOptional": true,
"license": "ISC",
"optional": true,
"dependencies": {
"once": "^1.3.0",
"wrappy": "1"
@ -6230,8 +6272,8 @@
},
"node_modules/path-is-absolute": {
"version": "1.0.1",
"devOptional": true,
"license": "MIT",
"optional": true,
"engines": {
"node": ">=0.10.0"
}
@ -7649,6 +7691,102 @@
"version": "1.0.0",
"dev": true
},
"node_modules/swagger-autogen": {
"version": "2.23.7",
"resolved": "https://registry.npmjs.org/swagger-autogen/-/swagger-autogen-2.23.7.tgz",
"integrity": "sha512-vr7uRmuV0DCxWc0wokLJAwX3GwQFJ0jwN+AWk0hKxre2EZwusnkGSGdVFd82u7fQLgwSTnbWkxUL7HXuz5LTZQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"acorn": "^7.4.1",
"deepmerge": "^4.2.2",
"glob": "^7.1.7",
"json5": "^2.2.3"
}
},
"node_modules/swagger-autogen/node_modules/acorn": {
"version": "7.4.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz",
"integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==",
"dev": true,
"license": "MIT",
"bin": {
"acorn": "bin/acorn"
},
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/swagger-autogen/node_modules/brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dev": true,
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"node_modules/swagger-autogen/node_modules/glob": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
"deprecated": "Glob versions prior to v9 are no longer supported",
"dev": true,
"license": "ISC",
"dependencies": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.1.1",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
},
"engines": {
"node": "*"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/swagger-autogen/node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dev": true,
"license": "ISC",
"dependencies": {
"brace-expansion": "^1.1.7"
},
"engines": {
"node": "*"
}
},
"node_modules/swagger-ui-dist": {
"version": "5.20.0",
"resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.20.0.tgz",
"integrity": "sha512-V5pozVTZxivdoQq/SQWxj3A4cOu5opk9MEbcZANX3Pj8X8xCrD1QCtBT7744Pz9msOvt0nnmy9JvM/9PGonCdg==",
"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/symbol-tree": {
"version": "3.2.4",
"dev": true,

View file

@ -8,13 +8,13 @@
"build": "npm run build --ws",
"format": "npm run format --ws",
"format-check": "npm run format-check --ws",
"generate-docs": "python3 -m venv .venv && source .venv/bin/activate && pip install -r docs/requirements.txt && python docs/architecture/schema.py",
"lint": "npm run lint --ws",
"test:unit": "npm run test:unit --ws"
},
"workspaces": [
"backend",
"frontend"
"frontend",
"docs"
],
"repository": {
"type": "git",