From e8a7b6ea7e68088a51db4ef3550c8f3ba725665d Mon Sep 17 00:00:00 2001 From: abdegenius Date: Sat, 24 Jan 2026 09:49:57 +0100 Subject: [PATCH] feat: added swagger docs --- drips/package-lock.json | 100 ++++++++++++++++++- drips/package.json | 2 + drips/src/bookings/bookings.controller.ts | 23 +++++ drips/src/bookings/dto/booking-query.dto.ts | 21 ++++ drips/src/bookings/dto/create-booking.dto.ts | 17 ++++ drips/src/main.ts | 10 ++ 6 files changed, 171 insertions(+), 2 deletions(-) diff --git a/drips/package-lock.json b/drips/package-lock.json index f4f42b0..5afd9e5 100644 --- a/drips/package-lock.json +++ b/drips/package-lock.json @@ -13,12 +13,14 @@ "@nestjs/config": "^4.0.2", "@nestjs/core": "^11.0.1", "@nestjs/platform-express": "^11.0.1", + "@nestjs/swagger": "^11.2.5", "@nestjs/typeorm": "^11.0.0", "class-transformer": "^0.5.1", "class-validator": "^0.14.3", "pg": "^8.17.2", "reflect-metadata": "^0.2.2", "rxjs": "^7.8.1", + "swagger-ui-express": "^5.0.1", "typeorm": "^0.3.28" }, "devDependencies": { @@ -2064,6 +2066,12 @@ "node": ">=8" } }, + "node_modules/@microsoft/tsdoc": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.16.0.tgz", + "integrity": "sha512-xgAyonlVVS+q7Vc7qLW0UrJU7rSFcETRWsqdXZtjzRU8dF+6CkozTK4V4y1LwOX7j8r/vHphjDeMeGI4tNGeGA==", + "license": "MIT" + }, "node_modules/@napi-rs/wasm-runtime": { "version": "0.2.12", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", @@ -2215,6 +2223,26 @@ } } }, + "node_modules/@nestjs/mapped-types": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@nestjs/mapped-types/-/mapped-types-2.1.0.tgz", + "integrity": "sha512-W+n+rM69XsFdwORF11UqJahn4J3xi4g/ZEOlJNL6KoW5ygWSmBB2p0S2BZ4FQeS/NDH72e6xIcu35SfJnE8bXw==", + "license": "MIT", + "peerDependencies": { + "@nestjs/common": "^10.0.0 || ^11.0.0", + "class-transformer": "^0.4.0 || ^0.5.0", + "class-validator": "^0.13.0 || ^0.14.0", + "reflect-metadata": "^0.1.12 || ^0.2.0" + }, + "peerDependenciesMeta": { + "class-transformer": { + "optional": true + }, + "class-validator": { + "optional": true + } + } + }, "node_modules/@nestjs/platform-express": { "version": "11.1.12", "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-11.1.12.tgz", @@ -2334,6 +2362,45 @@ "tslib": "^2.1.0" } }, + "node_modules/@nestjs/swagger": { + "version": "11.2.5", + "resolved": "https://registry.npmjs.org/@nestjs/swagger/-/swagger-11.2.5.tgz", + "integrity": "sha512-wCykbEybMqiYcvkyzPW4SbXKcwra9AGdajm0MvFgKR3W+gd1hfeKlo67g/s9QCRc/mqUU4KOE5Qtk7asMeFuiA==", + "license": "MIT", + "dependencies": { + "@microsoft/tsdoc": "0.16.0", + "@nestjs/mapped-types": "2.1.0", + "js-yaml": "4.1.1", + "lodash": "4.17.21", + "path-to-regexp": "8.3.0", + "swagger-ui-dist": "5.31.0" + }, + "peerDependencies": { + "@fastify/static": "^8.0.0 || ^9.0.0", + "@nestjs/common": "^11.0.1", + "@nestjs/core": "^11.0.1", + "class-transformer": "*", + "class-validator": "*", + "reflect-metadata": "^0.1.12 || ^0.2.0" + }, + "peerDependenciesMeta": { + "@fastify/static": { + "optional": true + }, + "class-transformer": { + "optional": true + }, + "class-validator": { + "optional": true + } + } + }, + "node_modules/@nestjs/swagger/node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" + }, "node_modules/@nestjs/testing": { "version": "11.1.12", "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-11.1.12.tgz", @@ -2437,6 +2504,13 @@ "url": "https://opencollective.com/pkgr" } }, + "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/@sinclair/typebox": { "version": "0.34.48", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.48.tgz", @@ -3767,7 +3841,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, "license": "Python-2.0" }, "node_modules/array-timsort": { @@ -7064,7 +7137,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", - "dev": true, "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -9037,6 +9109,30 @@ "node": ">=8" } }, + "node_modules/swagger-ui-dist": { + "version": "5.31.0", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.31.0.tgz", + "integrity": "sha512-zSUTIck02fSga6rc0RZP3b7J7wgHXwLea8ZjgLA3Vgnb8QeOl3Wou2/j5QkzSGeoz6HusP/coYuJl33aQxQZpg==", + "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-observable": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz", diff --git a/drips/package.json b/drips/package.json index 8c2c6d5..35d3712 100644 --- a/drips/package.json +++ b/drips/package.json @@ -24,12 +24,14 @@ "@nestjs/config": "^4.0.2", "@nestjs/core": "^11.0.1", "@nestjs/platform-express": "^11.0.1", + "@nestjs/swagger": "^11.2.5", "@nestjs/typeorm": "^11.0.0", "class-transformer": "^0.5.1", "class-validator": "^0.14.3", "pg": "^8.17.2", "reflect-metadata": "^0.2.2", "rxjs": "^7.8.1", + "swagger-ui-express": "^5.0.1", "typeorm": "^0.3.28" }, "devDependencies": { diff --git a/drips/src/bookings/bookings.controller.ts b/drips/src/bookings/bookings.controller.ts index 91e04eb..1d3c3ff 100644 --- a/drips/src/bookings/bookings.controller.ts +++ b/drips/src/bookings/bookings.controller.ts @@ -9,28 +9,46 @@ import { UseGuards, Request, } from '@nestjs/common'; +import { + ApiTags, + ApiOperation, + ApiResponse, + ApiBearerAuth, +} from '@nestjs/swagger'; import { BookingsService } from './bookings.service'; import { CreateBookingDto } from './dto/create-booking.dto'; import { UpdateBookingDto } from './dto/update-booking.dto'; import { BookingQueryDto } from './dto/booking-query.dto'; +@ApiTags('Bookings') +@ApiBearerAuth() @Controller('bookings') export class BookingsController { constructor(private readonly bookingsService: BookingsService) {} @Post() + @ApiOperation({ summary: 'Create a new booking' }) + @ApiResponse({ status: 201, description: 'The booking has been successfully created.' }) + @ApiResponse({ status: 400, description: 'Bad Request.' }) + @ApiResponse({ status: 401, description: 'Unauthorized.' }) async createBooking(@Request() req, @Body() createDto: CreateBookingDto) { const userId = req.user.id; // From auth guard return this.bookingsService.createBooking(userId, createDto); } @Patch(':id/confirm') + @ApiOperation({ summary: 'Confirm a booking' }) + @ApiResponse({ status: 200, description: 'The booking has been confirmed.' }) + @ApiResponse({ status: 404, description: 'Booking not found.' }) async confirmBooking(@Request() req, @Param('id') id: string) { const userId = req.user.id; return this.bookingsService.confirmBooking(id, userId); } @Patch(':id/cancel') + @ApiOperation({ summary: 'Cancel a booking' }) + @ApiResponse({ status: 200, description: 'The booking has been cancelled.' }) + @ApiResponse({ status: 404, description: 'Booking not found.' }) async cancelBooking( @Request() req, @Param('id') id: string, @@ -41,6 +59,8 @@ export class BookingsController { } @Get() + @ApiOperation({ summary: 'Get all bookings for the authenticated user' }) + @ApiResponse({ status: 200, description: 'Return all bookings.' }) async getBookings(@Request() req, @Query() query: BookingQueryDto) { const userId = req.user.id; const userRole = req.user.role; // 'mentee' or 'mentor' @@ -48,6 +68,9 @@ export class BookingsController { } @Get(':id') + @ApiOperation({ summary: 'Get a booking by ID' }) + @ApiResponse({ status: 200, description: 'Return the booking.' }) + @ApiResponse({ status: 404, description: 'Booking not found.' }) async getBookingById(@Request() req, @Param('id') id: string) { const userId = req.user.id; return this.bookingsService.getBookingById(id, userId); diff --git a/drips/src/bookings/dto/booking-query.dto.ts b/drips/src/bookings/dto/booking-query.dto.ts index 9e70b2c..8329d7a 100644 --- a/drips/src/bookings/dto/booking-query.dto.ts +++ b/drips/src/bookings/dto/booking-query.dto.ts @@ -1,23 +1,44 @@ import { IsEnum, IsOptional, IsDateString, IsUUID } from 'class-validator'; +import { ApiPropertyOptional } from '@nestjs/swagger'; import { BookingStatus } from '../entities/booking.entity'; export class BookingQueryDto { + @ApiPropertyOptional({ + enum: BookingStatus, + description: 'Filter by booking status', + }) @IsEnum(BookingStatus) @IsOptional() status?: BookingStatus; + @ApiPropertyOptional({ + description: 'Filter by start date', + example: '2026-01-01T00:00:00Z', + }) @IsDateString() @IsOptional() startDate?: string; + @ApiPropertyOptional({ + description: 'Filter by end date', + example: '2026-01-31T23:59:59Z', + }) @IsDateString() @IsOptional() endDate?: string; + @ApiPropertyOptional({ + description: 'Filter by mentor ID', + example: '550e8400-e29b-41d4-a716-446655440000', + }) @IsUUID() @IsOptional() mentorId?: string; + @ApiPropertyOptional({ + description: 'Filter by mentee ID', + example: '660e8400-e29b-41d4-a716-446655440000', + }) @IsUUID() @IsOptional() menteeId?: string; diff --git a/drips/src/bookings/dto/create-booking.dto.ts b/drips/src/bookings/dto/create-booking.dto.ts index 571a138..d9bedf8 100644 --- a/drips/src/bookings/dto/create-booking.dto.ts +++ b/drips/src/bookings/dto/create-booking.dto.ts @@ -5,20 +5,37 @@ import { IsString, IsUUID, } from 'class-validator'; +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; export class CreateBookingDto { + @ApiProperty({ + description: 'The ID of the mentor to book with', + example: '550e8400-e29b-41d4-a716-446655440000', + }) @IsUUID() @IsNotEmpty() mentorId: string; + @ApiProperty({ + description: 'The start time of the booking', + example: '2026-01-25T10:00:00Z', + }) @IsDateString() @IsNotEmpty() startTime: string; + @ApiProperty({ + description: 'The end time of the booking', + example: '2026-01-25T11:00:00Z', + }) @IsDateString() @IsNotEmpty() endTime: string; + @ApiPropertyOptional({ + description: 'Optional notes for the booking', + example: 'I want to discuss NestJS Swagger implementation', + }) @IsString() @IsOptional() notes?: string; diff --git a/drips/src/main.ts b/drips/src/main.ts index 90a498a..459b773 100644 --- a/drips/src/main.ts +++ b/drips/src/main.ts @@ -14,6 +14,16 @@ async function bootstrap() { }), ); + const { DocumentBuilder, SwaggerModule } = await import('@nestjs/swagger'); + const config = new DocumentBuilder() + .setTitle('SkillSync Drips API') + .setDescription('The SkillSync Drips API documentation') + .setVersion('1.0') + .addBearerAuth() + .build(); + const document = SwaggerModule.createDocument(app, config); + SwaggerModule.setup('docs', app, document); + await app.listen(process.env.PORT ?? 3000, () => { console.log(`Server running on http://localhost:${process.env.PORT ?? 3000}`); });