From 01dac5f7013dcc02a27596fe00bf2fad7ac5166e Mon Sep 17 00:00:00 2001 From: laxjovial Date: Tue, 30 Jun 2026 07:59:45 +0100 Subject: [PATCH 1/2] feat: add OpenAPI schema validation middleware and fix mismatches (fixes #842) --- package.json | 1 + src/app.controller.ts | 6 +++++- src/main.ts | 36 ++++++++++++++++++++++++++++++++++++ 3 files changed, 42 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 613e45a8..61dd1002 100644 --- a/package.json +++ b/package.json @@ -135,6 +135,7 @@ "csurf": "^1.2.2", "dataloader": "^2.2.3", "express": "^5.2.1", + "express-openapi-validator": "^5.3.9", "express-session": "^1.19.0", "fast-xml-parser": "^5.2.5", "fluent-ffmpeg": "^2.1.3", diff --git a/src/app.controller.ts b/src/app.controller.ts index 8ed9f082..367c8511 100644 --- a/src/app.controller.ts +++ b/src/app.controller.ts @@ -19,6 +19,10 @@ export class AppController { }, }) getStatus() { - return { message: 'TeachLink API is running', timestamp: new Date().toISOString() }; + return { + success: true, + message: 'TeachLink API is running', + data: { timestamp: new Date().toISOString() } + }; } } diff --git a/src/main.ts b/src/main.ts index ad3bdec5..a993801c 100644 --- a/src/main.ts +++ b/src/main.ts @@ -9,6 +9,8 @@ import { RedisStore } from 'connect-redis'; import Redis from 'ioredis'; import { AppModule } from './app.module'; +import * as OpenApiValidator from 'express-openapi-validator'; +import { join } from 'path'; import './tracing/opentelemetry'; import { CorrelationIdMiddleware } from './middleware/correlation-id'; @@ -311,6 +313,40 @@ async function bootstrapWorker(): Promise { // ========================= app.useGlobalInterceptors(new LocaleInterceptor(), new PaginationInterceptor()); + // ========================= + // OPENAPI VALIDATION + // ========================= + const apiSpecPath = join(process.cwd(), 'docs/api/openapi-spec.json'); + app.use( + OpenApiValidator.middleware({ + apiSpec: apiSpecPath, + validateRequests: true, + validateResponses: process.env.NODE_ENV !== 'production', + ignorePaths: /.*\/api\/docs.*/, // ignore swagger docs + }), + ); + + app.use((err: any, req: Request, res: Response, next: NextFunction) => { + if (err.status === 400 && err.errors) { + return res.status(400).json({ + success: false, + message: 'Validation failed', + errors: err.errors.map((e: any) => ({ + field: e.path, + message: e.message, + })), + }); + } + if (err.status === 500 && err.errors && typeof err.message === 'string' && err.message.toLowerCase().includes('response')) { + logger.warn(`Response validation deviation: ${JSON.stringify(err.errors)}`); + return res.status(500).json({ + success: false, + message: 'Internal server error', + }); + } + next(err); + }); + // ========================= // SWAGGER // ========================= From e19da201cc5a03529c2421342f5577eccdda71f5 Mon Sep 17 00:00:00 2001 From: laxjovial Date: Thu, 2 Jul 2026 10:07:12 +0100 Subject: [PATCH 2/2] chore: fix lint and prettier errors --- src/app.controller.ts | 8 ++++---- src/main.ts | 7 ++++++- src/workers/base/base.worker.ts | 2 +- src/workers/orchestration/worker-orchestration.service.ts | 2 +- 4 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/app.controller.ts b/src/app.controller.ts index 367c8511..e28dab99 100644 --- a/src/app.controller.ts +++ b/src/app.controller.ts @@ -19,10 +19,10 @@ export class AppController { }, }) getStatus() { - return { - success: true, - message: 'TeachLink API is running', - data: { timestamp: new Date().toISOString() } + return { + success: true, + message: 'TeachLink API is running', + data: { timestamp: new Date().toISOString() }, }; } } diff --git a/src/main.ts b/src/main.ts index a993801c..b13d7f0e 100644 --- a/src/main.ts +++ b/src/main.ts @@ -337,7 +337,12 @@ async function bootstrapWorker(): Promise { })), }); } - if (err.status === 500 && err.errors && typeof err.message === 'string' && err.message.toLowerCase().includes('response')) { + if ( + err.status === 500 && + err.errors && + typeof err.message === 'string' && + err.message.toLowerCase().includes('response') + ) { logger.warn(`Response validation deviation: ${JSON.stringify(err.errors)}`); return res.status(500).json({ success: false, diff --git a/src/workers/base/base.worker.ts b/src/workers/base/base.worker.ts index e4f58cec..70f16101 100644 --- a/src/workers/base/base.worker.ts +++ b/src/workers/base/base.worker.ts @@ -1,4 +1,4 @@ -import { Logger, Inject } from '@nestjs/common'; +import { Logger } from '@nestjs/common'; import { Job } from 'bull'; import Redis from 'ioredis'; import { getSharedRedisClient } from '../../config/cache.config'; diff --git a/src/workers/orchestration/worker-orchestration.service.ts b/src/workers/orchestration/worker-orchestration.service.ts index 91275f5d..59eb3c13 100644 --- a/src/workers/orchestration/worker-orchestration.service.ts +++ b/src/workers/orchestration/worker-orchestration.service.ts @@ -1,4 +1,4 @@ -import { Injectable, Logger, OnModuleInit, OnModuleDestroy, Inject } from '@nestjs/common'; +import { Injectable, Logger, OnModuleInit, OnModuleDestroy } from '@nestjs/common'; import { EventEmitter2 } from '@nestjs/event-emitter'; import { Job } from 'bull'; import { BaseWorker } from '../base/base.worker';