diff --git a/package-lock.json b/package-lock.json index 99f35d0..859ffbd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "@nestjs/mongoose": "^10.0.2", "@nestjs/passport": "^10.0.3", "@nestjs/platform-express": "^10.0.0", + "@nestjs/swagger": "^7.3.1", "@semantic-release/git": "^10.0.1", "@semantic-release/github": "^10.0.2", "@types/moment": "^2.13.0", @@ -1830,6 +1831,11 @@ "node": ">=8" } }, + "node_modules/@microsoft/tsdoc": { + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.14.2.tgz", + "integrity": "sha512-9b8mPpKrfeGRuhFH5iO1iwCLeIIsV6+H1sRfxbkoGXIyQE2BTsPd9zqSqQJ+pv5sJ/hT5M1zvOFL02MnEezFug==" + }, "node_modules/@mongodb-js/saslprep": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.4.tgz", @@ -2018,6 +2024,25 @@ "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0" } }, + "node_modules/@nestjs/mapped-types": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nestjs/mapped-types/-/mapped-types-2.0.5.tgz", + "integrity": "sha512-bSJv4pd6EY99NX9CjBIyn4TVDoSit82DUZlL4I3bqNfy5Gt+gXTa86i3I/i0iIV9P4hntcGM5GyO+FhZAhxtyg==", + "peerDependencies": { + "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.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/mongoose": { "version": "10.0.2", "resolved": "https://registry.npmjs.org/@nestjs/mongoose/-/mongoose-10.0.2.tgz", @@ -2075,6 +2100,38 @@ "typescript": ">=4.8.2" } }, + "node_modules/@nestjs/swagger": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@nestjs/swagger/-/swagger-7.3.1.tgz", + "integrity": "sha512-LUC4mr+5oAleEC/a2j8pNRh1S5xhKXJ1Gal5ZdRjt9XebQgbngXCdW7JTA9WOEcwGtFZN9EnKYdquzH971LZfw==", + "dependencies": { + "@microsoft/tsdoc": "^0.14.2", + "@nestjs/mapped-types": "2.0.5", + "js-yaml": "4.1.0", + "lodash": "4.17.21", + "path-to-regexp": "3.2.0", + "swagger-ui-dist": "5.11.2" + }, + "peerDependencies": { + "@fastify/static": "^6.0.0 || ^7.0.0", + "@nestjs/common": "^9.0.0 || ^10.0.0", + "@nestjs/core": "^9.0.0 || ^10.0.0", + "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/testing": { "version": "10.3.0", "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-10.3.0.tgz", @@ -14883,6 +14940,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/swagger-ui-dist": { + "version": "5.11.2", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.11.2.tgz", + "integrity": "sha512-jQG0cRgJNMZ7aCoiFofnoojeSaa/+KgWaDlfgs8QN+BXoGMpxeMVY5OEnjq4OlNvF3yjftO8c9GRAgcHlO+u7A==" + }, "node_modules/symbol-observable": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz", @@ -17383,6 +17445,11 @@ "resolved": "https://registry.npmjs.org/@lukeed/csprng/-/csprng-1.1.0.tgz", "integrity": "sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==" }, + "@microsoft/tsdoc": { + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.14.2.tgz", + "integrity": "sha512-9b8mPpKrfeGRuhFH5iO1iwCLeIIsV6+H1sRfxbkoGXIyQE2BTsPd9zqSqQJ+pv5sJ/hT5M1zvOFL02MnEezFug==" + }, "@mongodb-js/saslprep": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.4.tgz", @@ -17487,6 +17554,12 @@ "jsonwebtoken": "9.0.2" } }, + "@nestjs/mapped-types": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nestjs/mapped-types/-/mapped-types-2.0.5.tgz", + "integrity": "sha512-bSJv4pd6EY99NX9CjBIyn4TVDoSit82DUZlL4I3bqNfy5Gt+gXTa86i3I/i0iIV9P4hntcGM5GyO+FhZAhxtyg==", + "requires": {} + }, "@nestjs/mongoose": { "version": "10.0.2", "resolved": "https://registry.npmjs.org/@nestjs/mongoose/-/mongoose-10.0.2.tgz", @@ -17524,6 +17597,19 @@ "pluralize": "8.0.0" } }, + "@nestjs/swagger": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@nestjs/swagger/-/swagger-7.3.1.tgz", + "integrity": "sha512-LUC4mr+5oAleEC/a2j8pNRh1S5xhKXJ1Gal5ZdRjt9XebQgbngXCdW7JTA9WOEcwGtFZN9EnKYdquzH971LZfw==", + "requires": { + "@microsoft/tsdoc": "^0.14.2", + "@nestjs/mapped-types": "2.0.5", + "js-yaml": "4.1.0", + "lodash": "4.17.21", + "path-to-regexp": "3.2.0", + "swagger-ui-dist": "5.11.2" + } + }, "@nestjs/testing": { "version": "10.3.0", "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-10.3.0.tgz", @@ -26839,6 +26925,11 @@ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==" }, + "swagger-ui-dist": { + "version": "5.11.2", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.11.2.tgz", + "integrity": "sha512-jQG0cRgJNMZ7aCoiFofnoojeSaa/+KgWaDlfgs8QN+BXoGMpxeMVY5OEnjq4OlNvF3yjftO8c9GRAgcHlO+u7A==" + }, "symbol-observable": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz", diff --git a/package.json b/package.json index 83f466f..05372a9 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "@nestjs/mongoose": "^10.0.2", "@nestjs/passport": "^10.0.3", "@nestjs/platform-express": "^10.0.0", + "@nestjs/swagger": "^7.3.1", "@semantic-release/git": "^10.0.1", "@semantic-release/github": "^10.0.2", "@types/moment": "^2.13.0", diff --git a/src/app.controller.ts b/src/app.controller.ts index cce879e..c73d6d8 100644 --- a/src/app.controller.ts +++ b/src/app.controller.ts @@ -1,12 +1,7 @@ -import { Controller, Get } from '@nestjs/common'; +import { Controller } from '@nestjs/common'; import { AppService } from './app.service'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} - - @Get() - getHello(): string { - return this.appService.getHello(); - } } diff --git a/src/app.service.ts b/src/app.service.ts index f8c0626..7263d33 100644 --- a/src/app.service.ts +++ b/src/app.service.ts @@ -1,8 +1,4 @@ import { Injectable } from '@nestjs/common'; @Injectable() -export class AppService { - getHello(): string { - return ''; - } -} +export class AppService {} diff --git a/src/authentication/DTOs/LoginCallbackDTO.ts b/src/authentication/DTOs/LoginCallbackDTO.ts index efc10fb..2c4b788 100644 --- a/src/authentication/DTOs/LoginCallbackDTO.ts +++ b/src/authentication/DTOs/LoginCallbackDTO.ts @@ -1,10 +1,19 @@ import { Injectable } from '@nestjs/common'; +import { ApiProperty } from '@nestjs/swagger'; import { IsString } from 'class-validator'; @Injectable() export class LoginCallbackDTO { @IsString() + @ApiProperty({ + type: String, + description: "le code 'state' envoyé par le fournisseur d'identité", + }) state: string; @IsString() + @ApiProperty({ + type: String, + description: "le 'code' envoyé par le fournisseur d'identité", + }) code: string; } diff --git a/src/authentication/DTOs/LogoutCallbackDTO.ts b/src/authentication/DTOs/LogoutCallbackDTO.ts index a6d7d62..0be1f49 100644 --- a/src/authentication/DTOs/LogoutCallbackDTO.ts +++ b/src/authentication/DTOs/LogoutCallbackDTO.ts @@ -1,8 +1,13 @@ import { Injectable } from '@nestjs/common'; +import { ApiProperty } from '@nestjs/swagger'; import { IsString } from 'class-validator'; @Injectable() export class LogoutCallbackDTO { @IsString() + @ApiProperty({ + type: String, + description: "le code 'state' envoyé par le fournisseur d'identité", + }) state: string; } diff --git a/src/authentication/authentication.controller.ts b/src/authentication/authentication.controller.ts index dcb33e9..4abd8b2 100644 --- a/src/authentication/authentication.controller.ts +++ b/src/authentication/authentication.controller.ts @@ -17,6 +17,12 @@ import { JwtService } from '@nestjs/jwt'; import * as moment from 'moment'; import { LoginCallbackDTO } from './DTOs/LoginCallbackDTO'; import { LogoutCallbackDTO } from './DTOs/LogoutCallbackDTO'; +import { + ApiOkResponse, + ApiUnauthorizedResponse, + ApiNotFoundResponse, + ApiResponse, +} from '@nestjs/swagger'; @Controller('authentication') export class AuthenticationController { @@ -28,12 +34,16 @@ export class AuthenticationController { ) {} @Get('whereami') + @ApiOkResponse({ description: "retoune 'RIE' ou 'INTERNET' " }) whereami(@Headers('webconf-user-region') userAgent: string) { return userAgent; } @Get('login_authorize') @Redirect('', 302) + @ApiOkResponse({ + description: "retourne l'url de redirection", + }) loginAuthorize( @Res({ passthrough: true }) response: Response, @Query('room') room: string, @@ -54,6 +64,16 @@ export class AuthenticationController { } @Get('login_callback') + @ApiOkResponse({ + description: 'retourne un objet {roomName, jwt, accessToken}', + }) + @ApiUnauthorizedResponse({ + description: "le paramètre state recu n'est pas le meme envoyé", + }) + @ApiNotFoundResponse({ + description: + "erreur lors de récupération de l'accessToken ou userinfo d'agentConnect", + }) async loginCallback( @Query() query: LoginCallbackDTO, @Req() request: Request, @@ -97,6 +117,7 @@ export class AuthenticationController { @Get('logout') @Redirect('', 302) + @ApiResponse({ status: 302, description: 'redirection vers cerbère' }) logout( @Req() request: Request, @Res({ passthrough: true }) response: Response, @@ -114,7 +135,11 @@ export class AuthenticationController { } @Get('logout_callback') - // @Redirect('', 302) + @ApiOkResponse({ description: "retourne l'url /" }) + @ApiUnauthorizedResponse({ + description: + "le state de retour n'est pas la meme que celle qui a été envoyé", + }) logoutCallback( @Query() query: LogoutCallbackDTO, @Req() request: Request, @@ -135,6 +160,8 @@ export class AuthenticationController { } @Get('refreshToken') + @ApiOkResponse({ description: 'retourne { accessToken }' }) + @ApiUnauthorizedResponse({ description: 'veuillez vous authentifier' }) async refreshToken( @Req() request: Request, @Res({ passthrough: true }) response: Response, diff --git a/src/authentication/authentication.service.ts b/src/authentication/authentication.service.ts index c00ff91..fc909a4 100644 --- a/src/authentication/authentication.service.ts +++ b/src/authentication/authentication.service.ts @@ -33,7 +33,7 @@ export class AuthenticationService { "la variable state envoyé n'est pas celle reçue {/authentication/login_callback} route", ); throw new UnauthorizedException( - "le paramètre state recu n'est pas le meme envoyé", + "le paramètre state reçu n'est pas le meme envoyé", ); } diff --git a/src/conference/DTOs/byEmail.dto.ts b/src/conference/DTOs/byEmail.dto.ts index 3e1d6da..143d66f 100644 --- a/src/conference/DTOs/byEmail.dto.ts +++ b/src/conference/DTOs/byEmail.dto.ts @@ -1,4 +1,5 @@ import { Injectable } from '@nestjs/common'; +import { ApiProperty } from '@nestjs/swagger'; import { IsEmail, Matches } from 'class-validator'; @Injectable() @@ -13,8 +14,10 @@ export class ByEmailDTO { ), { message: "le nom de conférence n'est pas valide" }, ) + @ApiProperty({ type: String }) roomName: string; @IsEmail() + @ApiProperty({ type: String }) email: string; } diff --git a/src/conference/DTOs/conference.dto.ts b/src/conference/DTOs/conference.dto.ts index ce5cf66..e072650 100644 --- a/src/conference/DTOs/conference.dto.ts +++ b/src/conference/DTOs/conference.dto.ts @@ -1,4 +1,5 @@ import { Injectable } from '@nestjs/common'; +import { ApiProperty } from '@nestjs/swagger'; import { Matches } from 'class-validator'; @Injectable() @@ -13,5 +14,6 @@ export class roomNameDTO { ), { message: "le nom de conférence n'est pas valide" }, ) + @ApiProperty({ type: String, description: 'nom de la conférence' }) roomName: string; } diff --git a/src/conference/conference.controller.ts b/src/conference/conference.controller.ts index 4a4ffc7..8769c61 100644 --- a/src/conference/conference.controller.ts +++ b/src/conference/conference.controller.ts @@ -2,17 +2,44 @@ import { ByEmailDTO } from './DTOs/byEmail.dto'; import { roomNameDTO } from './DTOs/conference.dto'; import { ConferenceService } from './conference.service'; import { Body, Controller, Get, Headers, Param, Post } from '@nestjs/common'; +import { + ApiBadRequestResponse, + ApiBearerAuth, + ApiBody, + ApiNotFoundResponse, + ApiOkResponse, + ApiUnauthorizedResponse, +} from '@nestjs/swagger'; @Controller('') export class ConferenceController { constructor(private readonly conferenceService: ConferenceService) {} @Get('/roomExists/:roomName') + @ApiOkResponse({ description: 'retourne roomName si la conférence existe' }) + @ApiNotFoundResponse({ + description: "retourne 404 si la conférence n'existe pas", + }) roomExists(@Param() params: roomNameDTO) { return this.conferenceService.roomExists(params.roomName); } @Get('/:roomName') + @ApiOkResponse({ + description: 'retourne roomName si la conférence est déja ouverte', + }) + @ApiOkResponse({ + description: "retourne roomName et jwt si la conférence n'est pas ouverte", + }) + @ApiNotFoundResponse({ + description: "retourne 404 si la conférence n'existe pas", + }) + @ApiUnauthorizedResponse({ + description: + "veuillez vous authentifier pour accéder à la webconf de l'Etat", + }) + @ApiBody({ type: roomNameDTO }) + @ApiBearerAuth() getRoomAccessToken( @Param() params: roomNameDTO, @Headers('webconf-user-region') webconfUserRegion: string, @@ -27,6 +54,20 @@ export class ConferenceController { } @Post('conference/create/byemail') + @ApiOkResponse({ + description: "retourne { isWhitelisted: true, sended: 'email sended' }", + }) + @ApiOkResponse({ + description: "retourne roomName et jwt si la conférence n'est pas ouverte", + }) + @ApiBadRequestResponse({ + description: "erreur de l'envoi de l'email", + }) + @ApiUnauthorizedResponse({ + description: + "retourne { isWhitelisted: false } si l'émail n'est pas autorisé", + }) + @ApiBody({ type: ByEmailDTO }) async getRoomAccessTokenByEmail( @Body() body: ByEmailDTO, @Headers('host') host: string, diff --git a/src/conference/conference.service.ts b/src/conference/conference.service.ts index f675db4..4232c0f 100644 --- a/src/conference/conference.service.ts +++ b/src/conference/conference.service.ts @@ -96,7 +96,7 @@ export class ConferenceService { }); if (!exists) { - return { isWhitelisted: false }; + throw new UnauthorizedException({ isWhitelisted: false }); } const { roomName, jwt } = this.sendToken(room); diff --git a/src/feedback/DTOs/feedback.dto.ts b/src/feedback/DTOs/feedback.dto.ts index 9ad5422..af9b0e4 100644 --- a/src/feedback/DTOs/feedback.dto.ts +++ b/src/feedback/DTOs/feedback.dto.ts @@ -1,4 +1,5 @@ import { Injectable } from '@nestjs/common'; +import { ApiProperty } from '@nestjs/swagger'; import { Type } from 'class-transformer'; import { IsNotEmptyObject, @@ -16,10 +17,20 @@ class Rating { @IsOptional() @Min(0, { message: 'inv doit etre supérieur à 0' }) @Max(5, { message: 'inv doit etre inférieur à 5' }) + @ApiProperty({ + type: Number, + required: false, + description: 'qty doit etre un entier supérieur à 0 et inférieur à 5', + }) inv: number; @IsNumber({}, { message: 'qty doit etre un nombre' }) @Min(1, { message: 'qty doit etre supérieur à 1' }) @Max(5, { message: 'qty doit etre inférieur à 5' }) + @ApiProperty({ + type: Number, + required: true, + description: 'qty doit etre un entier supérieur à 1 et inférieur à 5', + }) qty: number; } @@ -28,14 +39,17 @@ export class FeedbackDTO { @IsNumber({}, { message: 'isVPN doit etre un nombre' }) @Min(-1, { message: 'isVPN doit etre supérieur à -1' }) @Max(1, { message: 'isVPN doit etre inférieur à 1' }) + @ApiProperty({ type: Number, description: 'doit etre -1 , 0 ou 1' }) isVPN: number; @IsObject() @IsNotEmptyObject() @ValidateNested() @Type(() => Rating) + @ApiProperty({ type: Rating }) rt: Rating; @IsString({ message: 'com doit etre une chaine de caractères' }) + @ApiProperty({ type: String }) com: string; } diff --git a/src/feedback/feedback.controller.ts b/src/feedback/feedback.controller.ts index 8de246c..15aa825 100644 --- a/src/feedback/feedback.controller.ts +++ b/src/feedback/feedback.controller.ts @@ -7,10 +7,15 @@ import { Req, BadRequestException, Headers, - Get, } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { Request } from 'express'; +import { + ApiBadRequestResponse, + ApiBody, + ApiNotFoundResponse, + ApiOkResponse, +} from '@nestjs/swagger'; @Controller('feedback') export class FeedbackController { constructor( @@ -18,12 +23,17 @@ export class FeedbackController { private configService: ConfigService, ) {} - @Get('whereami') - whereami(@Req() req: Request) { - return this.feedbackService.whereami(req); - } - @Post() + @ApiOkResponse({ description: '' }) + @ApiBadRequestResponse({ description: 'le serveur jmmc ne repond pas' }) + @ApiBadRequestResponse({ + description: 'vous ne pouvez pas déposer deux avis pour la meme session', + }) + @ApiNotFoundResponse({ + description: + "une erreur s'est produite pendant la recherche de l'identifiant et le nom de la conférence", + }) + @ApiBody({ type: FeedbackDTO }) createFeedback( @Req() req: Request, @Body() body: FeedbackDTO, diff --git a/src/feedback/feedback.service.ts b/src/feedback/feedback.service.ts index 55c0226..530d705 100644 --- a/src/feedback/feedback.service.ts +++ b/src/feedback/feedback.service.ts @@ -8,7 +8,6 @@ import { InjectModel } from '@nestjs/mongoose'; import { Feedback } from 'src/schemas/Feedback.schema'; import { Model } from 'mongoose'; import { ConfigService } from '@nestjs/config'; -import { Request } from 'express'; import { HttpService } from '@nestjs/axios'; import { catchError, firstValueFrom } from 'rxjs'; import { Logger } from '@nestjs/common'; @@ -22,10 +21,6 @@ export class FeedbackService { private readonly httpService: HttpService, ) {} - whereami(req: Request) { - return req.headers['webconf-user-region']; - } - async createFeedback(body: FeedbackDTO, jmmc_id: string, ip: string) { const { data } = await firstValueFrom( this.httpService @@ -61,10 +56,10 @@ export class FeedbackService { } } else { this.logger.error( - "une erreur s'est produite pendant la recherche de l'identifiant et le nm de la conférence", + "une erreur s'est produite pendant la recherche de l'identifiant et le nom de la conférence", ); throw new NotFoundException( - "une erreur s'est produite pendant la recherche de l'identifiant et le nm de la conférence", + "une erreur s'est produite pendant la recherche de l'identifiant et le nom de la conférence", ); } } diff --git a/src/main.ts b/src/main.ts index 1c2c029..e1e0189 100644 --- a/src/main.ts +++ b/src/main.ts @@ -4,9 +4,19 @@ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import * as cookieParser from 'cookie-parser'; import { ValidationPipe } from '@nestjs/common'; +import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger'; async function bootstrap() { const app = await NestFactory.create(AppModule); + const config = new DocumentBuilder() + .setTitle("webconf de l'Etat") + .setDescription("la spécification openApi de la webconf de l'Etat") + .setVersion('1.0') + .addTag("webconf de l'Etat") + .addBearerAuth() + .build(); + const document = SwaggerModule.createDocument(app, config); + SwaggerModule.setup('api', app, document); app.enableCors({ // origin: process.env.CORS_ORIGIN, credentials: true, diff --git a/src/stats/stats.controller.ts b/src/stats/stats.controller.ts index 1370d27..aa6b653 100644 --- a/src/stats/stats.controller.ts +++ b/src/stats/stats.controller.ts @@ -1,11 +1,20 @@ import { StatsService } from './stats.service'; import { Controller, Get } from '@nestjs/common'; +import { ApiNotFoundResponse, ApiResponse } from '@nestjs/swagger'; @Controller('stats') export class StatsController { constructor(private statsService: StatsService) {} @Get('/homePage') + @ApiResponse({ + status: 200, + description: + 'retourne le nombre de participants et le nombre de conférences ouvertes', + }) + @ApiNotFoundResponse({ + description: "le serveur jicofo n'est pas disponible", + }) async homePageStats() { return this.statsService.homePageStats(); }