Add Swagger integration and console logging for debugging

Integrated Swagger module in the backend for API documentation. Added console logs for better traceability and debugging in service and controller methods.
This commit is contained in:
Mathis H (Avnyr) 2024-10-15 15:15:55 +02:00
parent 6f0f209e00
commit ff649ebdbf
Signed by: Mathis
GPG Key ID: DD9E0666A747D126
9 changed files with 403 additions and 351 deletions

View File

@ -1,14 +1,14 @@
import { import {
Body, Body,
Controller, Controller,
Delete, Delete,
Get, Get,
HttpCode, HttpCode,
HttpStatus, HttpStatus,
Patch, Patch,
Post, Post,
UnauthorizedException, UnauthorizedException,
UseGuards, UseGuards,
} from "@nestjs/common"; } from "@nestjs/common";
import { SignInDto, SignUpDto } from "apps/backend/src/app/auth/auth.dto"; import { SignInDto, SignUpDto } from "apps/backend/src/app/auth/auth.dto";
import { AuthService } from "apps/backend/src/app/auth/auth.service"; import { AuthService } from "apps/backend/src/app/auth/auth.service";
@ -16,52 +16,50 @@ import { UserGuard } from "./auth.guard";
@Controller("auth") @Controller("auth")
export class AuthController { export class AuthController {
constructor(private readonly authService: AuthService) {} constructor(private readonly authService: AuthService) { }
//TODO Initial account validation for admin privileges //TODO Initial account validation for admin privileges
//POST signup //POST signup
@HttpCode(HttpStatus.CREATED) @HttpCode(HttpStatus.CREATED)
@Post("signup") @Post("signup")
async signUp(@Body() dto: SignUpDto) { async signUp(@Body() dto: SignUpDto) {
console.log(dto); return this.authService.doRegister(dto);
return this.authService.doRegister(dto); }
}
//POST signin //POST signin
@HttpCode(HttpStatus.OK) @HttpCode(HttpStatus.OK)
@Post("signin") @Post("signin")
async signIn(@Body() dto: SignInDto) { async signIn(@Body() dto: SignInDto) {
console.log(dto); return this.authService.doLogin(dto);
return this.authService.doLogin(dto); }
} //GET me -- Get current user data via jwt
//GET me -- Get current user data via jwt @HttpCode(HttpStatus.OK)
@HttpCode(HttpStatus.OK) @Get("me")
@Get("me") @UseGuards(UserGuard)
@UseGuards(UserGuard) async getMe(@Body() body: object) {
async getMe(@Body() body: object) { // @ts-ignore
// @ts-ignore const targetId = body.sourceUserId;
const targetId = body.sourceUserId; const userData = await this.authService.fetchUserById(targetId);
const userData = await this.authService.fetchUserById(targetId); if (!userData) {
if (!userData) { throw new UnauthorizedException();
throw new UnauthorizedException(); }
} return userData;
return userData; }
} //DELETE me
//DELETE me @HttpCode(HttpStatus.FOUND)
@HttpCode(HttpStatus.FOUND) @Delete("me")
@Delete("me") @UseGuards(UserGuard)
@UseGuards(UserGuard) async deleteMe(@Body() body: object) {
async deleteMe(@Body() body: object) { // @ts-ignore
// @ts-ignore const targetId = body.sourceUserId;
const targetId = body.sourceUserId; try {
try { await this.authService.deleteUser(targetId);
await this.authService.deleteUser(targetId); } catch (err) {
} catch (err) { throw new UnauthorizedException();
throw new UnauthorizedException(); }
} }
}
/* /*
//PATCH me //PATCH me
@HttpCode(HttpStatus.OK) @HttpCode(HttpStatus.OK)
@Patch("me") @Patch("me")

View File

@ -31,7 +31,6 @@ export class SignUpDto {
@IsNotEmpty() @IsNotEmpty()
@IsStrongPassword({ @IsStrongPassword({
minLength: 6, minLength: 6,
minSymbols: 1,
}) })
password: string; password: string;
} }
@ -46,7 +45,6 @@ export class SignInDto {
@IsNotEmpty() @IsNotEmpty()
@IsStrongPassword({ @IsStrongPassword({
minLength: 6, minLength: 6,
minSymbols: 1,
}) })
password: string; password: string;
} }

View File

@ -1,7 +1,7 @@
import { import {
Injectable, Injectable,
OnModuleInit, OnModuleInit,
UnauthorizedException, UnauthorizedException,
} from "@nestjs/common"; } from "@nestjs/common";
import { SignInDto, SignUpDto } from "apps/backend/src/app/auth/auth.dto"; import { SignInDto, SignUpDto } from "apps/backend/src/app/auth/auth.dto";
import { CredentialsService } from "apps/backend/src/app/credentials/credentials.service"; import { CredentialsService } from "apps/backend/src/app/credentials/credentials.service";
@ -11,103 +11,102 @@ import { eq } from "drizzle-orm";
@Injectable() @Injectable()
export class AuthService implements OnModuleInit { export class AuthService implements OnModuleInit {
constructor( constructor(
private db: DbService, private db: DbService,
private credentials: CredentialsService, private credentials: CredentialsService,
) {} ) { }
//TODO Initial account validation for admin privileges //TODO Initial account validation for admin privileges
async doRegister(data: SignUpDto) { async doRegister(data: SignUpDto) {
console.log(data); const existingUser = await this.db
const existingUser = await this.db .use()
.use() .select()
.select() .from(UsersTable)
.from(UsersTable) .where(eq(UsersTable.email, data.email))
.where(eq(UsersTable.email, data.email)) .prepare("userByEmail")
.prepare("userByEmail") .execute();
.execute(); if (existingUser.length !== 0)
if (existingUser.length !== 0) throw new UnauthorizedException("Already exist");
throw new UnauthorizedException("Already exist"); const query = await this.db
const query = await this.db .use()
.use() .insert(UsersTable)
.insert(UsersTable) .values({
.values({ //firstName: data.firstName,
//firstName: data.firstName, //lastName: data.lastName,
//lastName: data.lastName, email: data.email,
email: data.email, hash: await this.credentials.hash(data.password),
hash: await this.credentials.hash(data.password), })
}) .returning()
.returning() .prepare("insertUser")
.prepare("insertUser") .execute()
.execute() .catch((err) => {
.catch((err) => { console.error(err);
console.error(err); throw new UnauthorizedException(
throw new UnauthorizedException( "Error occurred while inserting user",
"Error occurred while inserting user", err,
err, );
); });
}); return {
return { message: "User created, check your email for validation.",
message: "User created, check your email for validation.", token: await this.credentials.signAuthToken({ sub: query[0].uuid }),
token: await this.credentials.signAuthToken({ sub: query[0].uuid }), };
}; }
}
async doLogin(data: SignInDto) { async doLogin(data: SignInDto) {
const user = await this.db const user = await this.db
.use() .use()
.select() .select()
.from(UsersTable) .from(UsersTable)
.where(eq(UsersTable.email, data.email)) .where(eq(UsersTable.email, data.email))
.prepare("userByEmail") .prepare("userByEmail")
.execute(); .execute();
if (user.length !== 1) if (user.length !== 1)
throw new UnauthorizedException("Invalid credentials"); throw new UnauthorizedException("Invalid credentials");
const passwordMatch = await this.credentials.check( const passwordMatch = await this.credentials.check(
data.password, data.password,
user[0].hash, user[0].hash,
); );
if (!passwordMatch) throw new UnauthorizedException("Invalid credentials"); if (!passwordMatch) throw new UnauthorizedException("Invalid credentials");
const token = await this.credentials.signAuthToken({ sub: user[0].uuid }); const token = await this.credentials.signAuthToken({ sub: user[0].uuid });
return { return {
message: "Login successful", message: "Login successful",
token: token, token: token,
}; };
} }
async fetchUserById(userId: string) { async fetchUserById(userId: string) {
const user = await this.db const user = await this.db
.use() .use()
.select() .select()
.from(UsersTable) .from(UsersTable)
.where(eq(UsersTable.uuid, userId)) .where(eq(UsersTable.uuid, userId))
.prepare("userById") .prepare("userById")
.execute(); .execute();
if (user.length !== 1) { if (user.length !== 1) {
throw new UnauthorizedException("User not found"); throw new UnauthorizedException("User not found");
} }
delete user[0].hash; delete user[0].hash;
//delete user[0].emailCode; //delete user[0].emailCode;
return user[0]; return user[0];
} }
async fetchUsers() { async fetchUsers() {
//TODO Pagination //TODO Pagination
const usersInDb = await this.db.use().select().from(UsersTable); const usersInDb = await this.db.use().select().from(UsersTable);
const result = { const result = {
total: usersInDb.length, total: usersInDb.length,
users: usersInDb.map((user) => { users: usersInDb.map((user) => {
delete user.hash; delete user.hash;
return { return {
...user, ...user,
}; };
}), }),
}; };
console.log(result); console.log(result);
return result; return result;
} }
/* /*
async updateUser(targetId: string, userData: IUserUpdateData) { async updateUser(targetId: string, userData: IUserUpdateData) {
const validationResult = UserUpdateSchema.safeParse(userData); const validationResult = UserUpdateSchema.safeParse(userData);
if (!validationResult.success) { if (!validationResult.success) {
@ -133,26 +132,26 @@ export class AuthService implements OnModuleInit {
} }
*/ */
async deleteUser(targetId: string) { async deleteUser(targetId: string) {
await this.db await this.db
.use() .use()
.delete(UsersTable) .delete(UsersTable)
.where(eq(UsersTable.uuid, targetId)) .where(eq(UsersTable.uuid, targetId))
.prepare("deleteUserById") .prepare("deleteUserById")
.execute() .execute()
.catch((err) => { .catch((err) => {
console.error(err); console.error(err);
throw new UnauthorizedException( throw new UnauthorizedException(
"Error occurred while deleting user", "Error occurred while deleting user",
err, err,
); );
}); });
return true; return true;
} }
async onModuleInit() { async onModuleInit() {
setTimeout(() => { setTimeout(() => {
this.fetchUsers(); this.fetchUsers();
}, 2000); }, 2000);
} }
} }

View File

@ -6,52 +6,51 @@ import { JWTPayload, generateSecret } from "jose";
@Injectable() @Injectable()
export class CredentialsService { export class CredentialsService {
constructor(private readonly configService: ConfigService) {} constructor(private readonly configService: ConfigService) { }
async hash(plaintextPassword: string) { async hash(plaintextPassword: string) {
console.log(plaintextPassword); if (plaintextPassword.length < 6)
if (plaintextPassword.length < 6) throw new BadRequestException("Password is not strong enough !");
throw new BadRequestException("Password is not strong enough !"); return argon.hash(plaintextPassword, {
return argon.hash(plaintextPassword, { secret: Buffer.from(this.configService.get("APP_HASH_SECRET")),
secret: Buffer.from(this.configService.get("APP_HASH_SECRET")), });
}); }
}
async check(plaintextPassword: string, hashedPassword: string) { async check(plaintextPassword: string, hashedPassword: string) {
return argon.verify(hashedPassword, plaintextPassword, { return argon.verify(hashedPassword, plaintextPassword, {
secret: Buffer.from(this.configService.get("APP_HASH_SECRET")), secret: Buffer.from(this.configService.get("APP_HASH_SECRET")),
}); });
} }
async verifyAuthToken(token: string) { async verifyAuthToken(token: string) {
try { try {
const result = await jose.jwtVerify( const result = await jose.jwtVerify(
token, token,
Uint8Array.from(this.configService.get("APP_TOKEN_SECRET")), Uint8Array.from(this.configService.get("APP_TOKEN_SECRET")),
{ {
audience: "auth:user", audience: "auth:user",
issuer: "FabLab", issuer: "FabLab",
}, },
); );
console.log(result); console.log(result);
return result; return result;
} catch (error) { } catch (error) {
console.log(error); console.log(error);
throw new BadRequestException("Invalid token"); throw new BadRequestException("Invalid token");
} }
} }
async signAuthToken(payload: JWTPayload) { async signAuthToken(payload: JWTPayload) {
console.log(this.configService.get("APP_TOKEN_SECRET")); console.log(this.configService.get("APP_TOKEN_SECRET"));
const token = new jose.SignJWT(payload) const token = new jose.SignJWT(payload)
.setProtectedHeader({ alg: "HS512", enc: "A128CBC-HS512" }) .setProtectedHeader({ alg: "HS512", enc: "A128CBC-HS512" })
.setIssuedAt() .setIssuedAt()
.setExpirationTime("5 day") .setExpirationTime("5 day")
.setIssuer("FabLab") .setIssuer("FabLab")
.setAudience("auth:user"); .setAudience("auth:user");
console.log(token); console.log(token);
return await token.sign( return await token.sign(
Uint8Array.from(this.configService.get("APP_TOKEN_SECRET")), Uint8Array.from(this.configService.get("APP_TOKEN_SECRET")),
); );
} }
} }

View File

@ -1,24 +1,24 @@
import { IncomingMessage } from "node:http"; import { IncomingMessage } from "node:http";
import { import {
BadRequestException, BadRequestException,
Body, Body,
Controller, Controller,
DefaultValuePipe, DefaultValuePipe,
Delete, Delete,
Get, Get,
HttpCode, HttpCode,
HttpStatus, HttpStatus,
Param, Param,
ParseIntPipe, ParseIntPipe,
ParseUUIDPipe, ParseUUIDPipe,
Post, Post,
Query, Query,
Req, Req,
Request, Request,
Res, Res,
Response, Response,
StreamableFile, StreamableFile,
UseGuards, UseGuards,
} from "@nestjs/common"; } from "@nestjs/common";
import { CreateFileTypeDto } from "apps/backend/src/app/files/files.dto"; import { CreateFileTypeDto } from "apps/backend/src/app/files/files.dto";
import { AdminGuard, InsertAdminState } from "../auth/auth.guard"; import { AdminGuard, InsertAdminState } from "../auth/auth.guard";
@ -26,130 +26,132 @@ import { FilesService } from "./files.service";
@Controller("files") @Controller("files")
export class FilesController { export class FilesController {
constructor(private readonly filesService: FilesService) {} constructor(private readonly filesService: FilesService) { }
@HttpCode(HttpStatus.OK) @HttpCode(HttpStatus.OK)
@UseGuards(InsertAdminState) @UseGuards(InsertAdminState)
@Post("new") @Post("new")
async saveFile(@Req() req: IncomingMessage, @Res() res: Response) { async saveFile(@Req() req: IncomingMessage, @Res() res: Response) {
let fileBuffer: Buffer = Buffer.from([]); let fileBuffer: Buffer = Buffer.from([]);
req.on("data", (chunk: Buffer) => { req.on("data", (chunk: Buffer) => {
fileBuffer = Buffer.concat([fileBuffer, chunk]); fileBuffer = Buffer.concat([fileBuffer, chunk]);
}); });
req.on("end", async () => { req.on("end", async () => {
try { try {
console.log(fileBuffer); console.log(fileBuffer);
const _fileName = req.headers["file_name"] as string; const _fileName = req.headers["file_name"] as string;
const _groupId = req.headers["group_id"] as string; const _groupId = req.headers["group_id"] as string;
const _uploadedBy = req.headers["uploaded_by"] as string; const _uploadedBy = req.headers["uploaded_by"] as string;
const _machineId = req.headers["machine_id"]; const _machineId = req.headers["machine_id"];
const _isDocumentation = req.headers["is_documentation"] as string; const _isDocumentation = req.headers["is_documentation"] as string;
const _isRestricted = req.headers["is_restricted"] as string; const _isRestricted = req.headers["is_restricted"] as string;
const _isAdmin = Boolean(req.headers["is_admin"] as string | boolean); const _isAdmin = Boolean(req.headers["is_admin"] as string | boolean);
console.log( console.log(
_fileName, _fileName,
_groupId, _groupId,
_uploadedBy, _uploadedBy,
_machineId, _machineId,
_isDocumentation, _isDocumentation,
_isRestricted, _isRestricted,
_isAdmin, _isAdmin,
); );
// Vérifier que les en-têtes nécessaires sont présents // Vérifier que les en-têtes nécessaires sont présents
if (!_fileName || !_machineId) { if (!_fileName || !_machineId) {
throw new BadRequestException("Header(s) manquant(s)"); throw new BadRequestException("Header(s) manquant(s)");
} }
console.log("Header found !"); console.log("Header found !");
const machineId = Array(_machineId); const machineId = Array(_machineId);
const Params = new Map() const Params = new Map()
.set("fileName", _fileName.toString()) .set("fileName", _fileName.toString())
.set("groupId", _groupId.toString() || null) .set("groupId", _groupId.toString() || null)
.set("uploadedBy", _uploadedBy.toString()) .set("uploadedBy", _uploadedBy.toString())
.set("machineId", Array(JSON.parse(machineId.toString()))) .set("machineId", Array(JSON.parse(machineId.toString())))
.set("isDocumentation", false) .set("isDocumentation", false)
.set("isRestricted", false); .set("isRestricted", false);
console.log("Current params :\n", Params); console.log("Current params :\n", Params);
//TODO Integrate a verification if the source is an admin, if that the case then it can define isDocumentation and isRestricted else throw in case of presence of those parameters. //TODO Integrate a verification if the source is an admin, if that the case then it can define isDocumentation and isRestricted else throw in case of presence of those parameters.
if (_isAdmin) { if (_isAdmin) {
Params.set("isDocumentation", Boolean(_isDocumentation)); Params.set("isDocumentation", Boolean(_isDocumentation));
Params.set("isRestricted", Boolean(_isRestricted)); Params.set("isRestricted", Boolean(_isRestricted));
} }
console.log("Executing save procedure..."); console.log("Executing save procedure...");
return ( return (
res res
// @ts-ignore // @ts-ignore
.status(HttpStatus.CREATED) .status(HttpStatus.CREATED)
.send(await this.filesService.save(fileBuffer, Params)) .send(await this.filesService.save(fileBuffer, Params))
); );
} catch (err) { } catch (err) {
console.error(err); console.error(err);
return ( return (
res res
// @ts-ignore // @ts-ignore
.status(err.status || HttpStatus.INTERNAL_SERVER_ERROR) .status(err.status || HttpStatus.INTERNAL_SERVER_ERROR)
.send(err) .send(err)
); );
} }
}); });
req.on("error", (err) => { req.on("error", (err) => {
return ( return (
res res
// @ts-ignore // @ts-ignore
.status(err.status || HttpStatus.INTERNAL_SERVER_ERROR) .status(err.status || HttpStatus.INTERNAL_SERVER_ERROR)
.send(err) .send(err)
); );
}); });
return; return;
} }
@HttpCode(HttpStatus.FOUND) @HttpCode(HttpStatus.FOUND)
@Get("find") @Get("find")
async findMany( async findMany(
@Query("limit", new DefaultValuePipe(20), ParseIntPipe) limit: number, @Query("limit", new DefaultValuePipe(20), ParseIntPipe) limit: number,
@Query("offset", new DefaultValuePipe(0), ParseIntPipe) offset: number, @Query("offset", new DefaultValuePipe(0), ParseIntPipe) offset: number,
@Query("search", new DefaultValuePipe("")) search: string, @Query("search", new DefaultValuePipe("")) search: string,
) { ) {
return this.filesService.search(limit, offset, search); return this.filesService.search(limit, offset, search);
} }
@HttpCode(HttpStatus.FOUND) @HttpCode(HttpStatus.FOUND)
@Get(":fileId") @Get("types")
async getFile(@Param("fileId") fileId: string) { async getTypes() {
return await this.filesService.get(fileId); console.log("Performing request")
} return await this.filesService.getAllFilesTypes();
}
@HttpCode(HttpStatus.OK) @HttpCode(HttpStatus.CREATED)
@UseGuards(AdminGuard) @UseGuards(AdminGuard)
@Delete(":fileId") @Post("types/new")
async deleteFile(@Param("fileId", ParseUUIDPipe) fileId: string) { async newType(@Body() body: CreateFileTypeDto) {
return await this.filesService.deleteFile(fileId); return await this.filesService.createFileType(body.name, body.mime);
} }
@HttpCode(HttpStatus.FOUND) @HttpCode(HttpStatus.ACCEPTED)
@Get("types") @UseGuards(AdminGuard)
async getTypes() { @Delete("types/:typeId")
return await this.filesService.getAllFilesTypes(); async delType(@Param(":typeId", ParseUUIDPipe) typeId: string) {
} return await this.filesService.removeFileType(typeId)
}
@HttpCode(HttpStatus.CREATED) @HttpCode(HttpStatus.FOUND)
@UseGuards(AdminGuard) @Get(":fileId")
@Post("types/new") async getFile(@Param("fileId") fileId: string) {
async newType(@Body() body: CreateFileTypeDto) { return await this.filesService.get(fileId);
return await this.filesService.createFileType(body.name, body.mime); }
}
@HttpCode(HttpStatus.OK)
@UseGuards(AdminGuard)
@Delete(":fileId")
async deleteFile(@Param("fileId", ParseUUIDPipe) fileId: string) {
return await this.filesService.deleteFile(fileId);
}
@HttpCode(HttpStatus.ACCEPTED)
@UseGuards(AdminGuard)
@Delete("types/:typeId")
async delType(@Param(":typeId", ParseUUIDPipe) typeId: string) {
//TODO
}
} }

View File

@ -293,12 +293,14 @@ export class FilesService {
* @return {Promise<Array>} Promise that resolves to an array of file types. * @return {Promise<Array>} Promise that resolves to an array of file types.
*/ */
public async getAllFilesTypes(): Promise<Array<object>> { public async getAllFilesTypes(): Promise<Array<object>> {
return await this.database const result = await this.database
.use() .use()
.select() .select()
.from(FilesTypesTable) .from(FilesTypesTable)
.prepare("getAllFilesTypes") .prepare("getAllFilesTypes")
.execute(); .execute();
console.log(result)
return result;
} }
/** /**

View File

@ -1,20 +1,28 @@
/**
* This is not a production server yet!
* This is only a minimal backend to get started.
*/
import { Logger } from "@nestjs/common"; import { Logger } from "@nestjs/common";
import { NestFactory } from "@nestjs/core"; import { NestFactory } from "@nestjs/core";
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
import helmet from "helmet"; import helmet from "helmet";
import { AppModule } from "./app/app.module"; import { AppModule } from "./app/app.module";
async function bootstrap() { async function bootstrap() {
const config = new DocumentBuilder()
.setTitle('Fab Explorer')
.setDescription("Définition de l'api du FabLab Explorer")
.setVersion('1.0')
.build();
const app = await NestFactory.create(AppModule); const app = await NestFactory.create(AppModule);
const globalPrefix = "api"; const globalPrefix = "api";
app.setGlobalPrefix(globalPrefix); app.setGlobalPrefix(globalPrefix);
app.use(helmet()); app.use(helmet());
const port = process.env.PORT || 3000; const port = process.env.PORT || 3000;
const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('api', app, document);
await app.listen(port); await app.listen(port);
Logger.log( Logger.log(
`🚀 Application is running on: http://localhost:${port}/${globalPrefix}`, `🚀 Application is running on: http://localhost:${port}/${globalPrefix}`,

View File

@ -17,6 +17,7 @@
"@nestjs/core": "^10.4.4", "@nestjs/core": "^10.4.4",
"@nestjs/mapped-types": "*", "@nestjs/mapped-types": "*",
"@nestjs/platform-express": "^10.4.4", "@nestjs/platform-express": "^10.4.4",
"@nestjs/swagger": "^7.4.2",
"@nestjs/throttler": "^6.2.1", "@nestjs/throttler": "^6.2.1",
"@radix-ui/react-accordion": "^1.2.1", "@radix-ui/react-accordion": "^1.2.1",
"@radix-ui/react-alert-dialog": "^1.1.2", "@radix-ui/react-alert-dialog": "^1.1.2",

45
pnpm-lock.yaml generated
View File

@ -26,6 +26,9 @@ importers:
'@nestjs/platform-express': '@nestjs/platform-express':
specifier: ^10.4.4 specifier: ^10.4.4
version: 10.4.4(@nestjs/common@10.4.4(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.1.14)(rxjs@7.8.1))(@nestjs/core@10.4.4) version: 10.4.4(@nestjs/common@10.4.4(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.1.14)(rxjs@7.8.1))(@nestjs/core@10.4.4)
'@nestjs/swagger':
specifier: ^7.4.2
version: 7.4.2(@nestjs/common@10.4.4(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.1.14)(rxjs@7.8.1))(@nestjs/core@10.4.4(@nestjs/common@10.4.4(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.1.14)(rxjs@7.8.1))(@nestjs/platform-express@10.4.4)(reflect-metadata@0.1.14)(rxjs@7.8.1))(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.1.14)
'@nestjs/throttler': '@nestjs/throttler':
specifier: ^6.2.1 specifier: ^6.2.1
version: 6.2.1(@nestjs/common@10.4.4(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.1.14)(rxjs@7.8.1))(@nestjs/core@10.4.4(@nestjs/common@10.4.4(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.1.14)(rxjs@7.8.1))(@nestjs/platform-express@10.4.4)(reflect-metadata@0.1.14)(rxjs@7.8.1))(reflect-metadata@0.1.14) version: 6.2.1(@nestjs/common@10.4.4(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.1.14)(rxjs@7.8.1))(@nestjs/core@10.4.4(@nestjs/common@10.4.4(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.1.14)(rxjs@7.8.1))(@nestjs/platform-express@10.4.4)(reflect-metadata@0.1.14)(rxjs@7.8.1))(reflect-metadata@0.1.14)
@ -1582,6 +1585,9 @@ packages:
resolution: {integrity: sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==} resolution: {integrity: sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==}
engines: {node: '>=8'} engines: {node: '>=8'}
'@microsoft/tsdoc@0.15.0':
resolution: {integrity: sha512-HZpPoABogPvjeJOdzCOSJsXeL/SMCBgBZMVC3X3d7YYp2gf31MfxhUoYUNwf1ERPJOnQc0wkFn9trqI6ZEdZuA==}
'@module-federation/bridge-react-webpack-plugin@0.2.8': '@module-federation/bridge-react-webpack-plugin@0.2.8':
resolution: {integrity: sha512-6G1qTo1HWvRcN5fzE+SZgvgzSPoq5YqNx8hFL8BttJmnd3wj4SUOFiikAsXhdVrzSK+Zuzg6pipkiLH1m+pbtw==} resolution: {integrity: sha512-6G1qTo1HWvRcN5fzE+SZgvgzSPoq5YqNx8hFL8BttJmnd3wj4SUOFiikAsXhdVrzSK+Zuzg6pipkiLH1m+pbtw==}
@ -1712,6 +1718,23 @@ packages:
peerDependencies: peerDependencies:
typescript: '>=4.3.5' typescript: '>=4.3.5'
'@nestjs/swagger@7.4.2':
resolution: {integrity: sha512-Mu6TEn1M/owIvAx2B4DUQObQXqo2028R2s9rSZ/hJEgBK95+doTwS0DjmVA2wTeZTyVtXOoN7CsoM5pONBzvKQ==}
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
'@nestjs/testing@10.4.4': '@nestjs/testing@10.4.4':
resolution: {integrity: sha512-qRGFj51A5RM7JqA8pcyEwSLA3Y0dle/PAZ8oxP0suimoCusRY3Tk7wYqutZdCNj1ATb678SDaUZDHk2pwSv9/g==} resolution: {integrity: sha512-qRGFj51A5RM7JqA8pcyEwSLA3Y0dle/PAZ8oxP0suimoCusRY3Tk7wYqutZdCNj1ATb678SDaUZDHk2pwSv9/g==}
peerDependencies: peerDependencies:
@ -7894,6 +7917,9 @@ packages:
engines: {node: '>=14.0.0'} engines: {node: '>=14.0.0'}
hasBin: true hasBin: true
swagger-ui-dist@5.17.14:
resolution: {integrity: sha512-CVbSfaLpstV65OnSjbXfVd6Sta3q3F7Cj/yYuvHMp1P90LztOLs6PfUnKEVAeiIVQt9u2SaPwv0LiH/OyMjHRw==}
symbol-tree@3.2.4: symbol-tree@3.2.4:
resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==}
@ -9971,6 +9997,8 @@ snapshots:
'@lukeed/csprng@1.1.0': {} '@lukeed/csprng@1.1.0': {}
'@microsoft/tsdoc@0.15.0': {}
'@module-federation/bridge-react-webpack-plugin@0.2.8': '@module-federation/bridge-react-webpack-plugin@0.2.8':
dependencies: dependencies:
'@module-federation/sdk': 0.2.8 '@module-federation/sdk': 0.2.8
@ -10171,6 +10199,21 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- chokidar - chokidar
'@nestjs/swagger@7.4.2(@nestjs/common@10.4.4(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.1.14)(rxjs@7.8.1))(@nestjs/core@10.4.4(@nestjs/common@10.4.4(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.1.14)(rxjs@7.8.1))(@nestjs/platform-express@10.4.4)(reflect-metadata@0.1.14)(rxjs@7.8.1))(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.1.14)':
dependencies:
'@microsoft/tsdoc': 0.15.0
'@nestjs/common': 10.4.4(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.1.14)(rxjs@7.8.1)
'@nestjs/core': 10.4.4(@nestjs/common@10.4.4(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.1.14)(rxjs@7.8.1))(@nestjs/platform-express@10.4.4)(reflect-metadata@0.1.14)(rxjs@7.8.1)
'@nestjs/mapped-types': 2.0.5(@nestjs/common@10.4.4(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.1.14)(rxjs@7.8.1))(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.1.14)
js-yaml: 4.1.0
lodash: 4.17.21
path-to-regexp: 3.3.0
reflect-metadata: 0.1.14
swagger-ui-dist: 5.17.14
optionalDependencies:
class-transformer: 0.5.1
class-validator: 0.14.1
'@nestjs/testing@10.4.4(@nestjs/common@10.4.4(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.1.14)(rxjs@7.8.1))(@nestjs/core@10.4.4(@nestjs/common@10.4.4(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.1.14)(rxjs@7.8.1))(@nestjs/platform-express@10.4.4)(reflect-metadata@0.1.14)(rxjs@7.8.1))(@nestjs/platform-express@10.4.4(@nestjs/common@10.4.4(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.1.14)(rxjs@7.8.1))(@nestjs/core@10.4.4))': '@nestjs/testing@10.4.4(@nestjs/common@10.4.4(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.1.14)(rxjs@7.8.1))(@nestjs/core@10.4.4(@nestjs/common@10.4.4(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.1.14)(rxjs@7.8.1))(@nestjs/platform-express@10.4.4)(reflect-metadata@0.1.14)(rxjs@7.8.1))(@nestjs/platform-express@10.4.4(@nestjs/common@10.4.4(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.1.14)(rxjs@7.8.1))(@nestjs/core@10.4.4))':
dependencies: dependencies:
'@nestjs/common': 10.4.4(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.1.14)(rxjs@7.8.1) '@nestjs/common': 10.4.4(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.1.14)(rxjs@7.8.1)
@ -17863,6 +17906,8 @@ snapshots:
csso: 5.0.5 csso: 5.0.5
picocolors: 1.0.1 picocolors: 1.0.1
swagger-ui-dist@5.17.14: {}
symbol-tree@3.2.4: {} symbol-tree@3.2.4: {}
tailwind-merge@2.5.3: {} tailwind-merge@2.5.3: {}