From 4be0738752a74f93a6d88d47cf6636a2e6a2cc29 Mon Sep 17 00:00:00 2001 From: feyishola Date: Sat, 24 Jan 2026 13:23:46 +0100 Subject: [PATCH] feat(chat): implement threads for chat feature --- apps/api/src/app.module.ts | 12 +- apps/api/src/auth/guards/roles.guard.ts | 29 + .../auth/interfaces/auth-request.interface.ts | 9 + .../booking-lifecycle.orchestrator.spec.ts | 50 +- .../booking-lifecycle.orchestrator.ts | 5 +- apps/api/src/chat/chat.controller.spec.ts | 20 + apps/api/src/chat/chat.controller.ts | 155 ++++ apps/api/src/chat/chat.module.ts | 9 + apps/api/src/chat/chat.service.spec.ts | 18 + apps/api/src/chat/chat.service.ts | 253 ++++++ apps/api/src/chat/dto/create-chat.dto.ts | 1 + apps/api/src/chat/dto/create-message.dto.ts | 14 + apps/api/src/chat/dto/message-response.dto.ts | 27 + .../src/chat/dto/paginated-response.dto.ts | 45 ++ .../api/src/chat/dto/pagination-params.dto.ts | 31 + apps/api/src/chat/dto/thread-response.dto.ts | 33 + apps/api/src/chat/dto/update-chat.dto.ts | 4 + apps/api/src/chat/entities/chat.entity.ts | 1 + apps/api/src/chat/entities/message.entity.ts | 80 ++ apps/api/src/chat/entities/thread.entity.ts | 107 +++ apps/api/src/main.ts | 17 +- .../1769080000000-CreateKycTable.ts | 2 +- ...089000000-CreateBookingAndSessionTables.ts | 4 +- apps/api/src/sessions/sessions.controller.ts | 5 +- .../api/src/sessions/sessions.service.spec.ts | 65 +- apps/api/src/sessions/sessions.service.ts | 15 +- apps/api/src/sessions/sessions.swagger.ts | 80 +- apps/api/src/tests/sessions.e2e-spec.ts | 18 +- .../src/tests/sessions.integration.spec.ts | 39 +- apps/api/src/users/entities/user.entity.ts | 13 + apps/api/src/users/enums/user-role.enum.ts | 5 + apps/api/src/users/profile-controller.ts | 17 +- apps/api/src/users/profile.service.ts | 8 +- apps/api/src/users/users.module.ts | 2 +- .../validators/iana-ttimezone.validator.ts | 11 +- apps/api/src/verify/dto/create-kyc.dto.ts | 2 +- apps/api/src/verify/dto/update-kyc.dto.ts | 2 +- apps/api/src/verify/dto/webhook-kyc.dto.ts | 2 +- apps/api/src/verify/entities/kyc.entity.ts | 13 +- apps/api/src/verify/verify.controller.ts | 8 +- apps/api/src/verify/verify.module.ts | 2 +- apps/api/src/verify/verify.service.ts | 21 +- apps/api/src/verify/webhook.controller.ts | 2 +- apps/api/test/auth.e2e-spec.ts | 20 +- apps/api/test/kyc.e2e-spec.ts | 16 +- package.json | 19 - yarn.lock | 717 ++++++++---------- 47 files changed, 1418 insertions(+), 610 deletions(-) create mode 100644 apps/api/src/auth/guards/roles.guard.ts create mode 100644 apps/api/src/auth/interfaces/auth-request.interface.ts create mode 100644 apps/api/src/chat/chat.controller.spec.ts create mode 100644 apps/api/src/chat/chat.controller.ts create mode 100644 apps/api/src/chat/chat.module.ts create mode 100644 apps/api/src/chat/chat.service.spec.ts create mode 100644 apps/api/src/chat/chat.service.ts create mode 100644 apps/api/src/chat/dto/create-chat.dto.ts create mode 100644 apps/api/src/chat/dto/create-message.dto.ts create mode 100644 apps/api/src/chat/dto/message-response.dto.ts create mode 100644 apps/api/src/chat/dto/paginated-response.dto.ts create mode 100644 apps/api/src/chat/dto/pagination-params.dto.ts create mode 100644 apps/api/src/chat/dto/thread-response.dto.ts create mode 100644 apps/api/src/chat/dto/update-chat.dto.ts create mode 100644 apps/api/src/chat/entities/chat.entity.ts create mode 100644 apps/api/src/chat/entities/message.entity.ts create mode 100644 apps/api/src/chat/entities/thread.entity.ts create mode 100644 apps/api/src/users/enums/user-role.enum.ts diff --git a/apps/api/src/app.module.ts b/apps/api/src/app.module.ts index 54a70a4..9ab0da1 100644 --- a/apps/api/src/app.module.ts +++ b/apps/api/src/app.module.ts @@ -9,14 +9,11 @@ import { ListingsModule } from './listings/listings.module'; import { MentorProfilesModule } from './mentor-profiles/mentor-profiles.module'; import { SkillsModule } from './skills/skills.module'; import { UsersModule } from './users/users.module'; -<<<<<<< HEAD -import { SessionsModule } from './sessions/sessions.module'; -======= import { AuthModule } from './auth/auth.module'; import { VerifyModule } from './verify/verify.module'; import rateLimitConfig from './config/rate-limit.config'; // Import the rate limit config ->>>>>>> upstream/main import { BookingsModule } from './bookings/bookings.module'; +import { ChatModule } from './chat/chat.module'; @Module({ imports: [ @@ -25,19 +22,16 @@ import { BookingsModule } from './bookings/bookings.module'; load: [rateLimitConfig], // Load the rate limit configuration }), DatabaseModule, - RedisModule, + RedisModule, HealthModule, AuthModule, UsersModule, MentorProfilesModule, SkillsModule, ListingsModule, -<<<<<<< HEAD - SessionsModule, -======= VerifyModule, ->>>>>>> upstream/main BookingsModule, + ChatModule, ], controllers: [AppController], providers: [AppService], diff --git a/apps/api/src/auth/guards/roles.guard.ts b/apps/api/src/auth/guards/roles.guard.ts new file mode 100644 index 0000000..dd09867 --- /dev/null +++ b/apps/api/src/auth/guards/roles.guard.ts @@ -0,0 +1,29 @@ +import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common'; +import { Reflector } from '@nestjs/core'; +import { Request } from 'express'; + +interface AuthUser { + id: string; + role?: string; + roles?: string[]; +} + +@Injectable() +export class RolesGuard implements CanActivate { + constructor(private reflector: Reflector) {} + + canActivate(context: ExecutionContext): boolean { + const requiredRoles = + this.reflector.get('roles', context.getHandler()) ?? []; + + type RequestWithUser = Request & { user?: AuthUser }; + const request = context.switchToHttp().getRequest(); + const user = request.user; + + if (!user) return false; + + const userRoles = user.roles ?? (user.role ? [user.role] : []); + + return requiredRoles.some((role) => userRoles.includes(role)); + } +} diff --git a/apps/api/src/auth/interfaces/auth-request.interface.ts b/apps/api/src/auth/interfaces/auth-request.interface.ts new file mode 100644 index 0000000..c79b5bb --- /dev/null +++ b/apps/api/src/auth/interfaces/auth-request.interface.ts @@ -0,0 +1,9 @@ +import { Request } from 'express'; + +export interface AuthRequest extends Request { + user: { + id: string; + role: string; + roles?: string[]; + }; +} diff --git a/apps/api/src/bookings/booking-lifecycle.orchestrator.spec.ts b/apps/api/src/bookings/booking-lifecycle.orchestrator.spec.ts index 6991dd1..042ee3e 100644 --- a/apps/api/src/bookings/booking-lifecycle.orchestrator.spec.ts +++ b/apps/api/src/bookings/booking-lifecycle.orchestrator.spec.ts @@ -67,7 +67,9 @@ describe('BookingLifecycleOrchestrator', () => { status: BookingStatus.ACCEPTED, }; - jest.spyOn(bookingsService, 'acceptBooking').mockResolvedValue(acceptedBooking); + jest + .spyOn(bookingsService, 'acceptBooking') + .mockResolvedValue(acceptedBooking); jest.spyOn(sessionsService, 'createFromBooking').mockResolvedValue({ id: '550e8400-e29b-41d4-a716-446655440000', bookingId: mockBooking.id, @@ -85,8 +87,12 @@ describe('BookingLifecycleOrchestrator', () => { const result = await orchestrator.acceptBooking(mockBooking.id); expect(result.status).toBe(BookingStatus.ACCEPTED); - expect(bookingsService.acceptBooking).toHaveBeenCalledWith(mockBooking.id); - expect(sessionsService.createFromBooking).toHaveBeenCalledWith(mockBooking.id); + expect(bookingsService.acceptBooking).toHaveBeenCalledWith( + mockBooking.id, + ); + expect(sessionsService.createFromBooking).toHaveBeenCalledWith( + mockBooking.id, + ); }); it('should fail if session creation fails', async () => { @@ -95,14 +101,16 @@ describe('BookingLifecycleOrchestrator', () => { status: BookingStatus.ACCEPTED, }; - jest.spyOn(bookingsService, 'acceptBooking').mockResolvedValue(acceptedBooking); + jest + .spyOn(bookingsService, 'acceptBooking') + .mockResolvedValue(acceptedBooking); jest .spyOn(sessionsService, 'createFromBooking') .mockRejectedValue(new Error('Session creation failed')); - await expect( - orchestrator.acceptBooking(mockBooking.id), - ).rejects.toThrow('Session creation failed'); + await expect(orchestrator.acceptBooking(mockBooking.id)).rejects.toThrow( + 'Session creation failed', + ); }); }); @@ -113,12 +121,16 @@ describe('BookingLifecycleOrchestrator', () => { status: BookingStatus.DECLINED, }; - jest.spyOn(bookingsService, 'declineBooking').mockResolvedValue(declinedBooking); + jest + .spyOn(bookingsService, 'declineBooking') + .mockResolvedValue(declinedBooking); const result = await orchestrator.declineBooking(mockBooking.id); expect(result.status).toBe(BookingStatus.DECLINED); - expect(bookingsService.declineBooking).toHaveBeenCalledWith(mockBooking.id); + expect(bookingsService.declineBooking).toHaveBeenCalledWith( + mockBooking.id, + ); expect(sessionsService.createFromBooking).not.toHaveBeenCalled(); }); }); @@ -130,12 +142,16 @@ describe('BookingLifecycleOrchestrator', () => { status: BookingStatus.CANCELLED, }; - jest.spyOn(bookingsService, 'cancelBooking').mockResolvedValue(cancelledBooking); + jest + .spyOn(bookingsService, 'cancelBooking') + .mockResolvedValue(cancelledBooking); const result = await orchestrator.cancelBooking(mockBooking.id); expect(result.status).toBe(BookingStatus.CANCELLED); - expect(bookingsService.cancelBooking).toHaveBeenCalledWith(mockBooking.id); + expect(bookingsService.cancelBooking).toHaveBeenCalledWith( + mockBooking.id, + ); }); }); @@ -146,7 +162,9 @@ describe('BookingLifecycleOrchestrator', () => { status: BookingStatus.ACCEPTED, }; - jest.spyOn(bookingsService, 'acceptBooking').mockResolvedValue(acceptedBooking); + jest + .spyOn(bookingsService, 'acceptBooking') + .mockResolvedValue(acceptedBooking); jest.spyOn(sessionsService, 'createFromBooking').mockResolvedValue({ id: '550e8400-e29b-41d4-a716-446655440000', bookingId: mockBooking.id, @@ -163,8 +181,12 @@ describe('BookingLifecycleOrchestrator', () => { await orchestrator.acceptBooking(mockBooking.id); - expect(bookingsService.acceptBooking).toHaveBeenCalledWith(mockBooking.id); - expect(sessionsService.createFromBooking).toHaveBeenCalledWith(mockBooking.id); + expect(bookingsService.acceptBooking).toHaveBeenCalledWith( + mockBooking.id, + ); + expect(sessionsService.createFromBooking).toHaveBeenCalledWith( + mockBooking.id, + ); }); }); }); diff --git a/apps/api/src/bookings/booking-lifecycle.orchestrator.ts b/apps/api/src/bookings/booking-lifecycle.orchestrator.ts index 7de7c7d..e289677 100644 --- a/apps/api/src/bookings/booking-lifecycle.orchestrator.ts +++ b/apps/api/src/bookings/booking-lifecycle.orchestrator.ts @@ -39,7 +39,10 @@ export class BookingLifecycleOrchestrator { } catch (error) { // If session creation fails, we have a data consistency issue // Log it and re-throw so the error is visible - console.error(`Failed to create session for booking ${bookingId}:`, error); + console.error( + `Failed to create session for booking ${bookingId}:`, + error, + ); throw error; } diff --git a/apps/api/src/chat/chat.controller.spec.ts b/apps/api/src/chat/chat.controller.spec.ts new file mode 100644 index 0000000..95ebc91 --- /dev/null +++ b/apps/api/src/chat/chat.controller.spec.ts @@ -0,0 +1,20 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { ChatController } from './chat.controller'; +import { ChatService } from './chat.service'; + +describe('ChatController', () => { + let controller: ChatController; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [ChatController], + providers: [ChatService], + }).compile(); + + controller = module.get(ChatController); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); +}); diff --git a/apps/api/src/chat/chat.controller.ts b/apps/api/src/chat/chat.controller.ts new file mode 100644 index 0000000..98c86cf --- /dev/null +++ b/apps/api/src/chat/chat.controller.ts @@ -0,0 +1,155 @@ +import { + Controller, + Get, + Post, + Body, + Param, + Query, + UseGuards, + Req, + ParseUUIDPipe, +} from '@nestjs/common'; +import { + ApiTags, + ApiOperation, + ApiResponse, + ApiBearerAuth, + ApiParam, +} from '@nestjs/swagger'; + +import { ChatService } from './chat.service'; +import { CreateMessageDto } from './dto/create-message.dto'; +import { PaginationParamsDto } from './dto/pagination-params.dto'; +import { PaginatedResponseDto } from './dto/paginated-response.dto'; +import { ThreadResponseDto } from './dto/thread-response.dto'; +import { MessageResponseDto } from './dto/message-response.dto'; + +import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard'; +import { RolesGuard } from '../auth/guards/roles.guard'; +import { Roles } from '../auth/decorators/roles.decorator'; +import { UserRole } from '../users/enums/user-role.enum'; +import type { AuthRequest } from '../auth/interfaces/auth-request.interface'; + +@ApiTags('chat') +@Controller('chat') +@UseGuards(JwtAuthGuard, RolesGuard) +@ApiBearerAuth() +export class ChatController { + constructor(private readonly chatService: ChatService) {} + + @Get('threads') + @Roles(UserRole.MENTOR, UserRole.MENTEE) + @ApiOperation({ + summary: 'Get user threads', + description: 'Retrieve all threads for the authenticated user', + }) + @ApiResponse({ + status: 200, + description: 'Threads retrieved successfully', + type: PaginatedResponseDto, + }) + async getUserThreads( + @Req() req: AuthRequest, + @Query() paginationParams: PaginationParamsDto, + ) { + return this.chatService.getUserThreads(req.user.id, paginationParams); + } + + @Get('threads/:threadId') + @Roles(UserRole.MENTOR, UserRole.MENTEE) + @ApiOperation({ + summary: 'Get thread details', + description: 'Retrieve details of a specific thread', + }) + @ApiParam({ + name: 'threadId', + description: 'Thread ID', + example: '123e4567-e89b-12d3-a456-426614174000', + }) + @ApiResponse({ + status: 200, + description: 'Thread retrieved successfully', + type: ThreadResponseDto, + }) + async getThread( + @Req() req: AuthRequest, + @Param('threadId', ParseUUIDPipe) threadId: string, + ) { + return this.chatService.getThread(threadId, req.user.id); + } + + @Get('threads/:threadId/messages') + @Roles(UserRole.MENTOR, UserRole.MENTEE) + @ApiOperation({ + summary: 'Get thread messages', + description: 'Retrieve messages from a specific thread with pagination', + }) + @ApiParam({ + name: 'threadId', + description: 'Thread ID', + example: '123e4567-e89b-12d3-a456-426614174000', + }) + @ApiResponse({ + status: 200, + description: 'Messages retrieved successfully', + type: PaginatedResponseDto, + }) + async getThreadMessages( + @Req() req: AuthRequest, + @Param('threadId', ParseUUIDPipe) threadId: string, + @Query() paginationParams: PaginationParamsDto, + ) { + return this.chatService.getThreadMessages( + threadId, + req.user.id, + paginationParams, + ); + } + + @Post('threads/:threadId/messages') + @Roles(UserRole.MENTOR, UserRole.MENTEE) + @ApiOperation({ + summary: 'Send a message', + description: 'Send a message in a thread', + }) + @ApiParam({ + name: 'threadId', + description: 'Thread ID', + example: '123e4567-e89b-12d3-a456-426614174000', + }) + @ApiResponse({ + status: 201, + description: 'Message sent successfully', + type: MessageResponseDto, + }) + async sendMessage( + @Req() req: AuthRequest, + @Param('threadId', ParseUUIDPipe) threadId: string, + @Body() createMessageDto: CreateMessageDto, + ) { + return this.chatService.sendMessage( + threadId, + req.user.id, + createMessageDto, + ); + } + + @Post('threads/:threadId/read') + @Roles(UserRole.MENTOR, UserRole.MENTEE) + @ApiOperation({ + summary: 'Mark thread as read', + description: 'Mark all messages in a thread as read', + }) + @ApiParam({ + name: 'threadId', + description: 'Thread ID', + example: '123e4567-e89b-12d3-a456-426614174000', + }) + async markThreadAsRead( + @Req() req: AuthRequest, + @Param('threadId', ParseUUIDPipe) threadId: string, + ) { + await this.chatService.markThreadAsRead(threadId, req.user.id); + return { message: 'Thread marked as read' }; + } +} diff --git a/apps/api/src/chat/chat.module.ts b/apps/api/src/chat/chat.module.ts new file mode 100644 index 0000000..d0159c0 --- /dev/null +++ b/apps/api/src/chat/chat.module.ts @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common'; +import { ChatService } from './chat.service'; +import { ChatController } from './chat.controller'; + +@Module({ + controllers: [ChatController], + providers: [ChatService], +}) +export class ChatModule {} diff --git a/apps/api/src/chat/chat.service.spec.ts b/apps/api/src/chat/chat.service.spec.ts new file mode 100644 index 0000000..110cd7d --- /dev/null +++ b/apps/api/src/chat/chat.service.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { ChatService } from './chat.service'; + +describe('ChatService', () => { + let service: ChatService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ChatService], + }).compile(); + + service = module.get(ChatService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/apps/api/src/chat/chat.service.ts b/apps/api/src/chat/chat.service.ts new file mode 100644 index 0000000..abfe2b1 --- /dev/null +++ b/apps/api/src/chat/chat.service.ts @@ -0,0 +1,253 @@ +import { + Injectable, + NotFoundException, + ForbiddenException, +} from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository, FindOptionsWhere, DataSource, Not } from 'typeorm'; +import { Thread } from './entities/thread.entity'; +import { Message } from './entities/message.entity'; +import { CreateMessageDto } from './dto/create-message.dto'; +// import { User } from '../users/entities/user.entity'; +import { PaginationParamsDto } from './dto/pagination-params.dto'; +import { PaginatedResponseDto } from './dto/paginated-response.dto'; + +type ThreadWithUnreadCount = Thread & { + unreadCount: number; +}; + +@Injectable() +export class ChatService { + constructor( + @InjectRepository(Thread) + private readonly threadRepository: Repository, + @InjectRepository(Message) + private readonly messageRepository: Repository, + private readonly dataSource: DataSource, + ) {} + + async findOrCreateThread( + mentorId: string, + menteeId: string, + ): Promise { + // Check if thread already exists + const existingThread = await this.threadRepository.findOne({ + where: [ + { mentor: { id: mentorId }, mentee: { id: menteeId } }, + { mentor: { id: menteeId }, mentee: { id: mentorId } }, + ], + relations: ['mentor', 'mentee'], + }); + + if (existingThread) { + return existingThread; + } + + // Create new thread + const thread = this.threadRepository.create({ + mentor: { id: mentorId }, + mentee: { id: menteeId }, + }); + + return await this.threadRepository.save(thread); + } + + async createThreadOnBooking( + mentorId: string, + menteeId: string, + ): Promise { + // This method is specifically called when a booking is created + return await this.findOrCreateThread(mentorId, menteeId); + } + + async sendMessage( + threadId: string, + senderId: string, + createMessageDto: CreateMessageDto, + ): Promise { + const thread = await this.threadRepository.findOne({ + where: { id: threadId }, + relations: ['mentor', 'mentee'], + }); + + if (!thread) { + throw new NotFoundException('Thread not found'); + } + + if (!thread.isParticipant(senderId)) { + throw new ForbiddenException('You are not a participant in this thread'); + } + + // Use transaction to ensure consistency + return await this.dataSource.transaction(async (manager) => { + const message = manager.create(Message, { + thread: { id: threadId }, + sender: { id: senderId }, + body: createMessageDto.body, + }); + + const savedMessage = await manager.save(message); + + // Update thread's last message preview and timestamp + thread.lastMessagePreview = this.truncatePreview(createMessageDto.body); + thread.lastMessageAt = new Date(); + await manager.save(thread); + + return savedMessage; + }); + } + + async getThreadMessages( + threadId: string, + userId: string, + paginationParams: PaginationParamsDto, + ): Promise> { + const thread = await this.threadRepository.findOne({ + where: { id: threadId }, + relations: ['mentor', 'mentee'], + }); + + if (!thread) { + throw new NotFoundException('Thread not found'); + } + + if (!thread.isParticipant(userId)) { + throw new ForbiddenException('You are not a participant in this thread'); + } + + const { page, limit } = paginationParams; + const skip = (page - 1) * limit; + + const [messages, total] = await this.messageRepository.findAndCount({ + where: { thread: { id: threadId } }, + relations: ['sender'], + order: { createdAt: 'DESC' }, + skip, + take: limit, + }); + + // Mark messages as read if they were sent by the other participant + const unreadMessages = messages.filter( + (msg) => !msg.isRead && msg.sender.id !== userId, + ); + + if (unreadMessages.length > 0) { + const unreadIds = unreadMessages.map((msg) => msg.id); + await this.messageRepository.update(unreadIds, { + isRead: true, + readAt: new Date(), + }); + } + + const totalPages = Math.ceil(total / limit); + + return { + items: messages.reverse(), // Return in chronological order + page, + limit, + total, + totalPages, + hasNext: page < totalPages, + hasPrevious: page > 1, + }; + } + + async getUserThreads( + userId: string, + paginationParams: PaginationParamsDto, + ): Promise> { + const { page, limit } = paginationParams; + const skip = (page - 1) * limit; + + const [threads, total] = await this.threadRepository.findAndCount({ + where: [ + { mentor: { id: userId } }, + { mentee: { id: userId } }, + ] as FindOptionsWhere[], + relations: ['mentor', 'mentee', 'messages'], + order: { lastMessageAt: 'DESC', updatedAt: 'DESC' }, + skip, + take: limit, + }); + + // Calculate unread counts for each thread and attach to the entity instance + const threadsWithUnreadCount: ThreadWithUnreadCount[] = await Promise.all( + threads.map(async (thread): Promise => { + const unreadCount = await this.messageRepository.count({ + where: { + thread: { id: thread.id }, + sender: { id: Not(userId) }, + isRead: false, + }, + }); + + // Mutate the entity instance to preserve class methods (isParticipant, getOtherParticipant) + const t = thread as ThreadWithUnreadCount; + t.unreadCount = unreadCount; + return t; + }), + ); + + const totalPages = Math.ceil(total / limit); + + return { + items: threadsWithUnreadCount, + page, + limit, + total, + totalPages, + hasNext: page < totalPages, + hasPrevious: page > 1, + }; + } + + async getThread(threadId: string, userId: string): Promise { + const thread = await this.threadRepository.findOne({ + where: { id: threadId }, + relations: ['mentor', 'mentee'], + }); + + if (!thread) { + throw new NotFoundException('Thread not found'); + } + + if (!thread.isParticipant(userId)) { + throw new ForbiddenException('You are not a participant in this thread'); + } + + return thread; + } + + async markThreadAsRead(threadId: string, userId: string): Promise { + const thread = await this.threadRepository.findOne({ + where: { id: threadId }, + }); + + if (!thread) { + throw new NotFoundException('Thread not found'); + } + + if (!thread.isParticipant(userId)) { + throw new ForbiddenException('You are not a participant in this thread'); + } + + await this.messageRepository.update( + { + thread: { id: threadId }, + sender: { id: Not(userId) }, + isRead: false, + }, + { + isRead: true, + readAt: new Date(), + }, + ); + } + + private truncatePreview(text: string, maxLength: number = 100): string { + if (text.length <= maxLength) { + return text; + } + return text.substring(0, maxLength) + '...'; + } +} diff --git a/apps/api/src/chat/dto/create-chat.dto.ts b/apps/api/src/chat/dto/create-chat.dto.ts new file mode 100644 index 0000000..d9fd901 --- /dev/null +++ b/apps/api/src/chat/dto/create-chat.dto.ts @@ -0,0 +1 @@ +export class CreateChatDto {} diff --git a/apps/api/src/chat/dto/create-message.dto.ts b/apps/api/src/chat/dto/create-message.dto.ts new file mode 100644 index 0000000..9ca1cc3 --- /dev/null +++ b/apps/api/src/chat/dto/create-message.dto.ts @@ -0,0 +1,14 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty, IsString, MaxLength } from 'class-validator'; + +export class CreateMessageDto { + @ApiProperty({ + description: 'Message content', + example: 'Hello! Looking forward to our session tomorrow.', + maxLength: 2000, + }) + @IsNotEmpty() + @IsString() + @MaxLength(2000) + body!: string; +} diff --git a/apps/api/src/chat/dto/message-response.dto.ts b/apps/api/src/chat/dto/message-response.dto.ts new file mode 100644 index 0000000..b1ccd28 --- /dev/null +++ b/apps/api/src/chat/dto/message-response.dto.ts @@ -0,0 +1,27 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class MessageResponseDto { + @ApiProperty() + id!: string; + + @ApiProperty() + threadId!: string; + + @ApiProperty() + senderId!: string; + + @ApiProperty() + senderName!: string; + + @ApiProperty() + body!: string; + + @ApiProperty() + isRead!: boolean; + + @ApiProperty() + createdAt!: Date; + + @ApiProperty() + readAt!: Date | null; +} diff --git a/apps/api/src/chat/dto/paginated-response.dto.ts b/apps/api/src/chat/dto/paginated-response.dto.ts new file mode 100644 index 0000000..862a1d1 --- /dev/null +++ b/apps/api/src/chat/dto/paginated-response.dto.ts @@ -0,0 +1,45 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class PaginatedResponseDto { + @ApiProperty({ + description: 'Array of items', + isArray: true, + }) + items!: T[]; + + @ApiProperty({ + description: 'Current page number', + example: 1, + }) + page!: number; + + @ApiProperty({ + description: 'Number of items per page', + example: 20, + }) + limit!: number; + + @ApiProperty({ + description: 'Total number of items', + example: 45, + }) + total!: number; + + @ApiProperty({ + description: 'Total number of pages', + example: 3, + }) + totalPages!: number; + + @ApiProperty({ + description: 'Whether there is a next page', + example: true, + }) + hasNext!: boolean; + + @ApiProperty({ + description: 'Whether there is a previous page', + example: false, + }) + hasPrevious!: boolean; +} diff --git a/apps/api/src/chat/dto/pagination-params.dto.ts b/apps/api/src/chat/dto/pagination-params.dto.ts new file mode 100644 index 0000000..afaf5a8 --- /dev/null +++ b/apps/api/src/chat/dto/pagination-params.dto.ts @@ -0,0 +1,31 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { Type } from 'class-transformer'; +import { IsOptional, IsInt, Min, Max } from 'class-validator'; + +export class PaginationParamsDto { + @ApiProperty({ + description: 'Page number (1-indexed)', + example: 1, + required: false, + default: 1, + }) + @Type(() => Number) + @IsOptional() + @IsInt() + @Min(1) + page = 1; + + @ApiProperty({ + description: 'Number of items per page', + example: 20, + required: false, + default: 20, + maximum: 100, + }) + @Type(() => Number) + @IsOptional() + @IsInt() + @Min(1) + @Max(100) + limit = 20; +} diff --git a/apps/api/src/chat/dto/thread-response.dto.ts b/apps/api/src/chat/dto/thread-response.dto.ts new file mode 100644 index 0000000..80fe26b --- /dev/null +++ b/apps/api/src/chat/dto/thread-response.dto.ts @@ -0,0 +1,33 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class ThreadResponseDto { + @ApiProperty() + id!: string; + + @ApiProperty() + mentorId!: string; + + @ApiProperty() + mentorName!: string; + + @ApiProperty() + menteeId!: string; + + @ApiProperty() + menteeName!: string; + + @ApiProperty() + lastMessagePreview!: string | null; + + @ApiProperty() + lastMessageAt!: Date | null; + + @ApiProperty() + unreadCount!: number; + + @ApiProperty() + createdAt!: Date; + + @ApiProperty() + updatedAt!: Date; +} diff --git a/apps/api/src/chat/dto/update-chat.dto.ts b/apps/api/src/chat/dto/update-chat.dto.ts new file mode 100644 index 0000000..18c3be9 --- /dev/null +++ b/apps/api/src/chat/dto/update-chat.dto.ts @@ -0,0 +1,4 @@ +import { PartialType } from '@nestjs/mapped-types'; +import { CreateChatDto } from './create-chat.dto'; + +export class UpdateChatDto extends PartialType(CreateChatDto) {} diff --git a/apps/api/src/chat/entities/chat.entity.ts b/apps/api/src/chat/entities/chat.entity.ts new file mode 100644 index 0000000..7e881f9 --- /dev/null +++ b/apps/api/src/chat/entities/chat.entity.ts @@ -0,0 +1 @@ +export class Chat {} diff --git a/apps/api/src/chat/entities/message.entity.ts b/apps/api/src/chat/entities/message.entity.ts new file mode 100644 index 0000000..35b5743 --- /dev/null +++ b/apps/api/src/chat/entities/message.entity.ts @@ -0,0 +1,80 @@ +import { + Entity, + PrimaryGeneratedColumn, + Column, + CreateDateColumn, + UpdateDateColumn, + ManyToOne, + Index, +} from 'typeorm'; +import { Thread } from './thread.entity'; +import { User } from '../../users/entities/user.entity'; +import { ApiProperty } from '@nestjs/swagger'; + +@Entity('messages') +@Index(['thread', 'createdAt']) // For efficient message retrieval by thread +export class Message { + @ApiProperty({ + description: 'Message ID', + example: '123e4567-e89b-12d3-a456-426614174000', + }) + @PrimaryGeneratedColumn('uuid') + id!: string; + + @ApiProperty({ + description: 'Thread containing this message', + type: () => Thread, + }) + @ManyToOne(() => Thread, (thread) => thread.messages, { + onDelete: 'CASCADE', + nullable: false, + }) + thread!: Thread; + + @ApiProperty({ + description: 'User who sent the message', + type: () => User, + }) + @ManyToOne(() => User, (user) => user.sentMessages, { + onDelete: 'CASCADE', + nullable: false, + }) + sender!: User; + + @ApiProperty({ + description: 'Message content', + example: 'Hello! Looking forward to our session tomorrow.', + maxLength: 2000, + }) + @Column({ type: 'text' }) + body!: string; + + @ApiProperty({ + description: 'Whether the message has been read', + example: false, + }) + @Column({ default: false }) + isRead!: boolean; + + @ApiProperty({ + description: 'Timestamp when message was read', + example: '2024-01-15T10:31:00.000Z', + nullable: true, + }) + @Column({ type: 'timestamptz', nullable: true }) + readAt!: Date; + + @ApiProperty({ + description: 'Message creation timestamp', + example: '2024-01-15T10:30:00.000Z', + }) + @CreateDateColumn() + createdAt!: Date; + + @ApiProperty({ + description: 'Message last update timestamp', + example: '2024-01-15T10:30:00.000Z', + }) + @UpdateDateColumn() + updatedAt!: Date; +} diff --git a/apps/api/src/chat/entities/thread.entity.ts b/apps/api/src/chat/entities/thread.entity.ts new file mode 100644 index 0000000..bbb37fd --- /dev/null +++ b/apps/api/src/chat/entities/thread.entity.ts @@ -0,0 +1,107 @@ +import { + Entity, + PrimaryGeneratedColumn, + Column, + CreateDateColumn, + UpdateDateColumn, + ManyToOne, + OneToMany, + Unique, +} from 'typeorm'; +import { User } from '../../users/entities/user.entity'; +import { Message } from './message.entity'; +import { ApiProperty } from '@nestjs/swagger'; + +@Entity('threads') +@Unique(['mentor', 'mentee']) // Prevent duplicate threads per mentor-mentee pair +export class Thread { + @ApiProperty({ + description: 'Thread ID', + example: '123e4567-e89b-12d3-a456-426614174000', + }) + @PrimaryGeneratedColumn('uuid') + id!: string; + + @ApiProperty({ + description: 'Mentor participating in the thread', + type: () => User, + }) + @ManyToOne(() => User, (user) => user.mentorThreads, { + onDelete: 'CASCADE', + nullable: false, + }) + mentor!: User; + + @ApiProperty({ + description: 'Mentee participating in the thread', + type: () => User, + }) + @ManyToOne(() => User, (user) => user.menteeThreads, { + onDelete: 'CASCADE', + nullable: false, + }) + mentee!: User; + + @ApiProperty({ + description: 'Messages in the thread', + type: () => [Message], + isArray: true, + }) + @OneToMany(() => Message, (message) => message.thread, { + cascade: true, + }) + messages!: Message[]; + + @ApiProperty({ + description: 'Last message content for preview', + example: 'Looking forward to our session tomorrow!', + nullable: true, + }) + @Column({ type: 'text', nullable: true }) + lastMessagePreview!: string; + + @ApiProperty({ + description: 'Timestamp of the last message', + example: '2024-01-15T10:30:00.000Z', + nullable: true, + }) + @Column({ type: 'timestamptz', nullable: true }) + lastMessageAt!: Date; + + @ApiProperty({ + description: 'Whether the thread is archived', + example: false, + }) + @Column({ default: false }) + isArchived!: boolean; + + @ApiProperty({ + description: 'Thread creation timestamp', + example: '2024-01-01T00:00:00.000Z', + }) + @CreateDateColumn() + createdAt!: Date; + + @ApiProperty({ + description: 'Thread last update timestamp', + example: '2024-01-01T00:00:00.000Z', + }) + @UpdateDateColumn() + updatedAt!: Date; + + // Helper method to check if user is participant + isParticipant(userId: string): boolean { + return this.mentor.id === userId || this.mentee.id === userId; + } + + // Helper method to get other participant + getOtherParticipant(userId: string): User { + if (this.mentor.id === userId) { + return this.mentee; + } + if (this.mentee.id === userId) { + return this.mentor; + } + throw new Error('User is not a participant in this thread'); + } +} diff --git a/apps/api/src/main.ts b/apps/api/src/main.ts index e586baf..6395c80 100644 --- a/apps/api/src/main.ts +++ b/apps/api/src/main.ts @@ -23,11 +23,13 @@ async function bootstrap() { // Configure CORS dynamically const corsOrigins = configService.get('CORS_ORIGINS', '*'); - app.use(cors({ - origin: corsOrigins.split(',').map(origin => origin.trim()), // Split origins and trim whitespace - methods: 'GET,HEAD,PUT,PATCH,POST,DELETE', - credentials: true, - })); + app.use( + cors({ + origin: corsOrigins.split(',').map((origin) => origin.trim()), // Split origins and trim whitespace + methods: 'GET,HEAD,PUT,PATCH,POST,DELETE', + credentials: true, + }), + ); // Configure rate limiting const limiter = rateLimit({ @@ -38,12 +40,11 @@ async function bootstrap() { legacyHeaders: false, // Disable the `X-RateLimit-*` headers }); app.use(limiter); - -// eslint-disable-next-line @typescript-eslint/no-unsafe-call + app.useGlobalPipes(new GlobalValidationPipe()); app.useGlobalFilters(new GlobalExceptionFilter()); - // eslint-disable-next-line @typescript-eslint/no-unsafe-call + app.useGlobalInterceptors(new LoggingInterceptor()); // Swagger setup diff --git a/apps/api/src/migrations/1769080000000-CreateKycTable.ts b/apps/api/src/migrations/1769080000000-CreateKycTable.ts index 0bc80b4..906b89e 100644 --- a/apps/api/src/migrations/1769080000000-CreateKycTable.ts +++ b/apps/api/src/migrations/1769080000000-CreateKycTable.ts @@ -78,4 +78,4 @@ export class CreateKycTable1769080000000 implements MigrationInterface { public async down(queryRunner: QueryRunner): Promise { await queryRunner.dropTable('kyc', true); } -} \ No newline at end of file +} diff --git a/apps/api/src/migrations/1769089000000-CreateBookingAndSessionTables.ts b/apps/api/src/migrations/1769089000000-CreateBookingAndSessionTables.ts index 0c52cbe..16785b4 100644 --- a/apps/api/src/migrations/1769089000000-CreateBookingAndSessionTables.ts +++ b/apps/api/src/migrations/1769089000000-CreateBookingAndSessionTables.ts @@ -1,8 +1,6 @@ import { MigrationInterface, QueryRunner } from 'typeorm'; -export class CreateBookingAndSessionTables1769089000000 - implements MigrationInterface -{ +export class CreateBookingAndSessionTables1769089000000 implements MigrationInterface { name = 'CreateBookingAndSessionTables1769089000000'; public async up(queryRunner: QueryRunner): Promise { diff --git a/apps/api/src/sessions/sessions.controller.ts b/apps/api/src/sessions/sessions.controller.ts index e90fba7..9bdb37e 100644 --- a/apps/api/src/sessions/sessions.controller.ts +++ b/apps/api/src/sessions/sessions.controller.ts @@ -64,7 +64,10 @@ export class SessionsController { type: SessionResponseDto, }) @ApiResponse({ status: 401, description: 'Unauthorized' }) - @ApiResponse({ status: 403, description: 'Forbidden - not session participant' }) + @ApiResponse({ + status: 403, + description: 'Forbidden - not session participant', + }) @ApiResponse({ status: 404, description: 'Session not found' }) @ApiResponse({ status: 400, diff --git a/apps/api/src/sessions/sessions.service.spec.ts b/apps/api/src/sessions/sessions.service.spec.ts index 5fa8277..c3a88bc 100644 --- a/apps/api/src/sessions/sessions.service.spec.ts +++ b/apps/api/src/sessions/sessions.service.spec.ts @@ -84,8 +84,8 @@ describe('SessionsService', () => { it('should create a session from an accepted booking', async () => { jest.spyOn(bookingRepository, 'findOne').mockResolvedValue(mockBooking); jest.spyOn(sessionRepository, 'findOne').mockResolvedValue(null); - jest.spyOn(sessionRepository, 'create').mockReturnValue(mockSession as Session); - jest.spyOn(sessionRepository, 'save').mockResolvedValue(mockSession as Session); + jest.spyOn(sessionRepository, 'create').mockReturnValue(mockSession); + jest.spyOn(sessionRepository, 'save').mockResolvedValue(mockSession); const result = await service.createFromBooking(mockBooking.id); @@ -117,26 +117,26 @@ describe('SessionsService', () => { const draftBooking = { ...mockBooking, status: BookingStatus.DRAFT }; jest.spyOn(bookingRepository, 'findOne').mockResolvedValue(draftBooking); - await expect( - service.createFromBooking(draftBooking.id), - ).rejects.toThrow(BadRequestException); + await expect(service.createFromBooking(draftBooking.id)).rejects.toThrow( + BadRequestException, + ); }); it('should prevent duplicate session creation', async () => { jest.spyOn(bookingRepository, 'findOne').mockResolvedValue(mockBooking); - jest.spyOn(sessionRepository, 'findOne').mockResolvedValue(mockSession as Session); + jest.spyOn(sessionRepository, 'findOne').mockResolvedValue(mockSession); - await expect( - service.createFromBooking(mockBooking.id), - ).rejects.toThrow(BadRequestException); + await expect(service.createFromBooking(mockBooking.id)).rejects.toThrow( + BadRequestException, + ); }); it('should throw NotFoundException for non-existent booking', async () => { jest.spyOn(bookingRepository, 'findOne').mockResolvedValue(null); - await expect( - service.createFromBooking('invalid-id'), - ).rejects.toThrow(NotFoundException); + await expect(service.createFromBooking('invalid-id')).rejects.toThrow( + NotFoundException, + ); }); }); @@ -147,8 +147,10 @@ describe('SessionsService', () => { status: SessionStatus.IN_PROGRESS, }; - jest.spyOn(sessionRepository, 'findOne').mockResolvedValue(mockSession as Session); - jest.spyOn(sessionRepository, 'save').mockResolvedValue(startedSession as Session); + jest.spyOn(sessionRepository, 'findOne').mockResolvedValue(mockSession); + jest + .spyOn(sessionRepository, 'save') + .mockResolvedValue(startedSession as Session); const result = await service.startSession( mockSession.id, @@ -165,8 +167,10 @@ describe('SessionsService', () => { status: SessionStatus.IN_PROGRESS, }; - jest.spyOn(sessionRepository, 'findOne').mockResolvedValue(mockSession as Session); - jest.spyOn(sessionRepository, 'save').mockResolvedValue(startedSession as Session); + jest.spyOn(sessionRepository, 'findOne').mockResolvedValue(mockSession); + jest + .spyOn(sessionRepository, 'save') + .mockResolvedValue(startedSession as Session); const result = await service.startSession( mockSession.id, @@ -178,7 +182,7 @@ describe('SessionsService', () => { }); it('should prevent unauthorized user from starting session', async () => { - jest.spyOn(sessionRepository, 'findOne').mockResolvedValue(mockSession as Session); + jest.spyOn(sessionRepository, 'findOne').mockResolvedValue(mockSession); await expect( service.startSession(mockSession.id, 'unauthorized-user-id'), @@ -215,7 +219,9 @@ describe('SessionsService', () => { jest .spyOn(sessionRepository, 'findOne') .mockResolvedValue(inProgressSession as Session); - jest.spyOn(sessionRepository, 'save').mockResolvedValue(completedSession as Session); + jest + .spyOn(sessionRepository, 'save') + .mockResolvedValue(completedSession as Session); const result = await service.completeSession( mockSession.id, @@ -241,7 +247,7 @@ describe('SessionsService', () => { }); it('should prevent completing non-in_progress session', async () => { - jest.spyOn(sessionRepository, 'findOne').mockResolvedValue(mockSession as Session); + jest.spyOn(sessionRepository, 'findOne').mockResolvedValue(mockSession); await expect( service.completeSession(mockSession.id, mockSession.mentorProfileId), @@ -251,7 +257,7 @@ describe('SessionsService', () => { describe('findOne', () => { it('should find session for authorized mentee', async () => { - jest.spyOn(sessionRepository, 'findOne').mockResolvedValue(mockSession as Session); + jest.spyOn(sessionRepository, 'findOne').mockResolvedValue(mockSession); const result = await service.findOne( mockSession.id, @@ -263,7 +269,7 @@ describe('SessionsService', () => { }); it('should find session for authorized mentor', async () => { - jest.spyOn(sessionRepository, 'findOne').mockResolvedValue(mockSession as Session); + jest.spyOn(sessionRepository, 'findOne').mockResolvedValue(mockSession); const result = await service.findOne( mockSession.id, @@ -275,7 +281,7 @@ describe('SessionsService', () => { }); it('should deny access to unauthorized user', async () => { - jest.spyOn(sessionRepository, 'findOne').mockResolvedValue(mockSession as Session); + jest.spyOn(sessionRepository, 'findOne').mockResolvedValue(mockSession); await expect( service.findOne(mockSession.id, 'unauthorized-user-id'), @@ -307,7 +313,9 @@ describe('SessionsService', () => { jest .spyOn(sessionRepository, 'findOne') .mockResolvedValueOnce(scheduledSession as Session); - jest.spyOn(sessionRepository, 'save').mockResolvedValueOnce(inProgressSession as Session); + jest + .spyOn(sessionRepository, 'save') + .mockResolvedValueOnce(inProgressSession as Session); const step1 = await service.startSession( mockSession.id, @@ -319,7 +327,9 @@ describe('SessionsService', () => { jest .spyOn(sessionRepository, 'findOne') .mockResolvedValueOnce(inProgressSession as Session); - jest.spyOn(sessionRepository, 'save').mockResolvedValueOnce(completedSession as Session); + jest + .spyOn(sessionRepository, 'save') + .mockResolvedValueOnce(completedSession as Session); const step2 = await service.completeSession( mockSession.id, @@ -349,12 +359,13 @@ describe('SessionsService', () => { it('should copy timestamps exactly from booking', async () => { jest.spyOn(bookingRepository, 'findOne').mockResolvedValue(mockBooking); jest.spyOn(sessionRepository, 'findOne').mockResolvedValue(null); - jest.spyOn(sessionRepository, 'create').mockReturnValue(mockSession as Session); - jest.spyOn(sessionRepository, 'save').mockResolvedValue(mockSession as Session); + jest.spyOn(sessionRepository, 'create').mockReturnValue(mockSession); + jest.spyOn(sessionRepository, 'save').mockResolvedValue(mockSession); await service.createFromBooking(mockBooking.id); - const createCall = jest.spyOn(sessionRepository, 'create').mock.calls[0][0]; + const createCall = jest.spyOn(sessionRepository, 'create').mock + .calls[0][0]; expect(createCall.startTime).toEqual(mockBooking.startTime); expect(createCall.endTime).toEqual(mockBooking.endTime); }); diff --git a/apps/api/src/sessions/sessions.service.ts b/apps/api/src/sessions/sessions.service.ts index 4de6b66..cb020fe 100644 --- a/apps/api/src/sessions/sessions.service.ts +++ b/apps/api/src/sessions/sessions.service.ts @@ -118,7 +118,8 @@ export class SessionsService { // Ownership check: user must be part of the session const isMentee = session.menteeUserId === userId; - const isMentor = mentorProfileId && session.mentorProfileId === mentorProfileId; + const isMentor = + mentorProfileId && session.mentorProfileId === mentorProfileId; if (!isMentee && !isMentor) { throw new ForbiddenException( @@ -159,9 +160,7 @@ export class SessionsService { // Ownership check: only the mentor can complete if (session.mentorProfileId !== mentorProfileId) { - throw new ForbiddenException( - 'Only the mentor can complete this session', - ); + throw new ForbiddenException('Only the mentor can complete this session'); } // State validation: only in_progress sessions can be completed @@ -216,7 +215,9 @@ export class SessionsService { query.andWhere('session.id = :sessionId', { sessionId }); } - const sessions = await query.orderBy('session.start_time', 'DESC').getMany(); + const sessions = await query + .orderBy('session.start_time', 'DESC') + .getMany(); if (sessionId && sessions.length === 0) { throw new NotFoundException('Session not found'); @@ -245,7 +246,9 @@ export class SessionsService { query.andWhere('session.id = :sessionId', { sessionId }); } - const sessions = await query.orderBy('session.start_time', 'DESC').getMany(); + const sessions = await query + .orderBy('session.start_time', 'DESC') + .getMany(); if (sessionId && sessions.length === 0) { throw new NotFoundException('Session not found'); diff --git a/apps/api/src/sessions/sessions.swagger.ts b/apps/api/src/sessions/sessions.swagger.ts index f4481aa..98ed92d 100644 --- a/apps/api/src/sessions/sessions.swagger.ts +++ b/apps/api/src/sessions/sessions.swagger.ts @@ -1,35 +1,35 @@ /** * Session Lifecycle & Completion Documentation - * + * * ## Overview * Sessions represent actual mentorship meetings tied to accepted bookings. * Sessions follow a strict lifecycle: scheduled → in_progress → completed - * + * * ## Key Characteristics * - Auto-created when a booking is accepted * - 1:1 relationship with bookings (unique constraint on booking_id) * - Timestamps always match their source booking * - RBAC enforcement: mentee can start, mentor can complete * - Extensible for future features (reviews, notifications, analytics) - * + * * ## Session States - * + * * ### SCHEDULED * - Initial state when session is created from accepted booking * - Both mentor and mentee can transition to IN_PROGRESS - * + * * ### IN_PROGRESS * - Session has been started by mentor or mentee * - Only mentor can transition to COMPLETED * - Mentee cannot complete sessions - * + * * ### COMPLETED * - Session marked complete by mentor * - Triggers side-effects (unlocks reviews, sends notifications, etc.) * - Terminal state (no further transitions allowed) - * + * * ## Allowed Transitions - * + * * | From | To | Allowed For | Reason | * |------|-----|------------|--------| * | SCHEDULED | IN_PROGRESS | Mentee + Mentor | Either party can start | @@ -37,77 +37,77 @@ * | SCHEDULED | COMPLETED | ✗ Blocked | Must go through IN_PROGRESS | * | COMPLETED | * | ✗ Blocked | Terminal state | * | IN_PROGRESS | SCHEDULED | ✗ Blocked | No state reversal allowed | - * + * * ## Endpoint Summary - * + * * ### Mentee Endpoints * - `PATCH /sessions/{id}/start` - Start a session (mentee or mentor role) * - `GET /sessions/mentee/my-sessions` - List mentee's sessions * - `GET /sessions/mentee/{id}` - Get session details - * - * ### Mentor Endpoints + * + * ### Mentor Endpoints * - `PATCH /sessions/{id}/start` - Start a session (mentee or mentor role) * - `PATCH /sessions/{id}/complete` - Complete session (mentor only) * - `GET /sessions/mentor/my-sessions` - List mentor's sessions * - `GET /sessions/mentor/{id}` - Get session details - * + * * ## Side-Effects on Completion - * + * * When a session transitions to COMPLETED, the following prepare for future features: - * + * * 1. **Review Eligibility** - Mentee can now leave reviews * 2. **Notifications** - Events triggered for both parties * 3. **Analytics** - Session completion logged for metrics * 4. **Webhooks** - External integrations notified - * + * * These are currently stubbed and will be extended without modifying core logic. - * + * * ## RBAC Enforcement - * + * * ### Session Access * - Users can only access sessions they participate in * - Mentee can only see their own mentee sessions * - Mentor can only see their own mentor sessions * - Unauthorized access returns 403 Forbidden - * + * * ### Session Transitions * - **START (scheduled → in_progress)**: Mentee OR Mentor * - **COMPLETE (in_progress → completed)**: Mentor ONLY * - Mentee attempting to complete returns 403 Forbidden - * + * * ## Booking Integration - * + * * Sessions are automatically created from accepted bookings: - * + * * ``` * Booking (DRAFT) * ↓ - * Booking.accept() + * Booking.accept() * ↓ * Booking (ACCEPTED) → Session auto-created * ↓ * Session (SCHEDULED) * ``` - * + * * **Important**: Sessions are NEVER created for: * - Draft bookings - * - Declined bookings + * - Declined bookings * - Cancelled bookings - * + * * Session creation is internal and never exposed as a public/manual endpoint. - * + * * ## Timestamp Guarantees - * + * * Session timestamps MUST exactly match booking timestamps: * - `session.startTime = booking.startTime` * - `session.endTime = booking.endTime` - * + * * No rescheduling or time modifications are allowed. - * + * * ## Example Workflows - * + * * ### Complete Happy Path - * + * * 1. Mentee views booking → clicks "Request" * 2. Mentor receives notification → reviews booking * 3. Mentor clicks "Accept" → Session auto-created (SCHEDULED) @@ -115,18 +115,18 @@ * 5. Mentee and mentor have conversation * 6. Mentor clicks "End Session" → `PATCH /sessions/{id}/complete` → Session (COMPLETED) * 7. Mentee now eligible to leave review - * + * * ### Declined Booking Path - * + * * 1. Mentor views booking → clicks "Decline" * 2. Booking transitions to DECLINED * 3. No session is created * 4. Both parties notified - * + * * ## API Response Format - * + * * All session endpoints return SessionResponseDto: - * + * * ```json * { * "id": "550e8400-e29b-41d4-a716-446655440000", @@ -142,16 +142,16 @@ * "updatedAt": "2026-01-22T09:30:00Z" * } * ``` - * + * * ## Error Responses - * + * * | Code | Scenario | * |------|----------| * | 400 | Invalid state transition (e.g., trying to complete SCHEDULED session) | * | 401 | Missing/invalid authentication headers | * | 403 | Insufficient permissions (e.g., mentee trying to complete) | * | 404 | Session or booking not found | - * + * */ export const SESSION_SWAGGER_DOCUMENTATION = { diff --git a/apps/api/src/tests/sessions.e2e-spec.ts b/apps/api/src/tests/sessions.e2e-spec.ts index e94d189..520b900 100644 --- a/apps/api/src/tests/sessions.e2e-spec.ts +++ b/apps/api/src/tests/sessions.e2e-spec.ts @@ -109,12 +109,10 @@ describe('Sessions E2E', () => { status: SessionStatus.IN_PROGRESS, }; - jest - .spyOn(sessionsService, 'completeSession') - .mockResolvedValue({ - ...completedSession, - status: SessionStatus.COMPLETED, - }); + jest.spyOn(sessionsService, 'completeSession').mockResolvedValue({ + ...completedSession, + status: SessionStatus.COMPLETED, + }); const response = await request(app.getHttpServer()) .patch(`/sessions/${mockSessionId}/complete`) @@ -146,9 +144,7 @@ describe('Sessions E2E', () => { }); it('should prevent mentee from accessing other mentee sessions', async () => { - jest - .spyOn(sessionsService, 'findMenteeSession') - .mockResolvedValue([]); + jest.spyOn(sessionsService, 'findMenteeSession').mockResolvedValue([]); await request(app.getHttpServer()) .get(`/sessions/mentee/${mockSessionId}`) @@ -172,9 +168,7 @@ describe('Sessions E2E', () => { }); it('should prevent mentor from accessing other mentor sessions', async () => { - jest - .spyOn(sessionsService, 'findMentorSession') - .mockResolvedValue([]); + jest.spyOn(sessionsService, 'findMentorSession').mockResolvedValue([]); await request(app.getHttpServer()) .get(`/sessions/mentor/${mockSessionId}`) diff --git a/apps/api/src/tests/sessions.integration.spec.ts b/apps/api/src/tests/sessions.integration.spec.ts index aa3a41a..4a775fa 100644 --- a/apps/api/src/tests/sessions.integration.spec.ts +++ b/apps/api/src/tests/sessions.integration.spec.ts @@ -92,8 +92,12 @@ describe('Session Lifecycle Integration', () => { status: BookingStatus.ACCEPTED, }); jest.spyOn(sessionRepository, 'findOne').mockResolvedValue(null); - jest.spyOn(sessionRepository, 'create').mockReturnValue(mockSession as Session); - jest.spyOn(sessionRepository, 'save').mockResolvedValue(mockSession as Session); + jest + .spyOn(sessionRepository, 'create') + .mockReturnValue(mockSession as Session); + jest + .spyOn(sessionRepository, 'save') + .mockResolvedValue(mockSession as Session); await bookingsService.acceptBooking(mockBookingId); @@ -181,7 +185,9 @@ describe('Session Lifecycle Integration', () => { jest .spyOn(sessionRepository, 'findOne') .mockResolvedValue(scheduledSession as Session); - jest.spyOn(sessionRepository, 'save').mockResolvedValue(inProgressSession as Session); + jest + .spyOn(sessionRepository, 'save') + .mockResolvedValue(inProgressSession as Session); const startResult = await sessionsService.startSession( mockSessionId, @@ -217,7 +223,9 @@ describe('Session Lifecycle Integration', () => { jest .spyOn(sessionRepository, 'findOne') .mockResolvedValue(scheduledSession as Session); - jest.spyOn(sessionRepository, 'save').mockResolvedValue(inProgressSession as Session); + jest + .spyOn(sessionRepository, 'save') + .mockResolvedValue(inProgressSession as Session); const startResult = await sessionsService.startSession( mockSessionId, @@ -230,7 +238,9 @@ describe('Session Lifecycle Integration', () => { jest .spyOn(sessionRepository, 'findOne') .mockResolvedValue(inProgressSession as Session); - jest.spyOn(sessionRepository, 'save').mockResolvedValue(completedSession as Session); + jest + .spyOn(sessionRepository, 'save') + .mockResolvedValue(completedSession as Session); const completeResult = await sessionsService.completeSession( mockSessionId, @@ -240,7 +250,9 @@ describe('Session Lifecycle Integration', () => { }); it('should prevent unauthorized users from accessing sessions', async () => { - jest.spyOn(sessionRepository, 'findOne').mockResolvedValue(mockSession as Session); + jest + .spyOn(sessionRepository, 'findOne') + .mockResolvedValue(mockSession as Session); const unauthorizedUserId = '550e8400-e29b-41d4-a716-446655440099'; const unauthorizedMentorId = '550e8400-e29b-41d4-a716-446655440099'; @@ -295,7 +307,9 @@ describe('Session Lifecycle Integration', () => { jest .spyOn(sessionRepository, 'findOne') .mockResolvedValue(scheduledSession as Session); - jest.spyOn(sessionRepository, 'save').mockResolvedValue(inProgressSession as Session); + jest + .spyOn(sessionRepository, 'save') + .mockResolvedValue(inProgressSession as Session); const result1 = await sessionsService.startSession( mockSessionId, @@ -324,12 +338,17 @@ describe('Session Lifecycle Integration', () => { it('should ensure session timestamps exactly match booking times', async () => { jest.spyOn(bookingRepository, 'findOne').mockResolvedValue(mockBooking); jest.spyOn(sessionRepository, 'findOne').mockResolvedValue(null); - jest.spyOn(sessionRepository, 'create').mockReturnValue(mockSession as Session); - jest.spyOn(sessionRepository, 'save').mockResolvedValue(mockSession as Session); + jest + .spyOn(sessionRepository, 'create') + .mockReturnValue(mockSession as Session); + jest + .spyOn(sessionRepository, 'save') + .mockResolvedValue(mockSession as Session); await sessionsService.createFromBooking(mockBookingId); - const createCall = jest.spyOn(sessionRepository, 'create').mock.calls[0][0]; + const createCall = jest.spyOn(sessionRepository, 'create').mock + .calls[0][0]; expect(createCall.startTime).toEqual(mockBooking.startTime); expect(createCall.endTime).toEqual(mockBooking.endTime); expect(createCall.startTime).toEqual(mockSession.startTime); diff --git a/apps/api/src/users/entities/user.entity.ts b/apps/api/src/users/entities/user.entity.ts index 82485a5..556d164 100644 --- a/apps/api/src/users/entities/user.entity.ts +++ b/apps/api/src/users/entities/user.entity.ts @@ -4,8 +4,12 @@ import { Column, CreateDateColumn, UpdateDateColumn, + OneToMany, } from 'typeorm'; +import { Thread } from '../../chat/entities/thread.entity'; +import { Message } from '../../chat/entities/message.entity'; + @Entity('users') export class User { @PrimaryGeneratedColumn('uuid') @@ -32,6 +36,15 @@ export class User { @Column({ nullable: true }) emailVerifiedAt?: Date; + @OneToMany(() => Thread, (thread) => thread.mentor) + mentorThreads: Thread[] = []; + + @OneToMany(() => Thread, (thread) => thread.mentee) + menteeThreads: Thread[] = []; + + @OneToMany(() => Message, (message) => message.sender) + sentMessages: Message[] = []; + @CreateDateColumn() createdAt!: Date; diff --git a/apps/api/src/users/enums/user-role.enum.ts b/apps/api/src/users/enums/user-role.enum.ts new file mode 100644 index 0000000..819d5ec --- /dev/null +++ b/apps/api/src/users/enums/user-role.enum.ts @@ -0,0 +1,5 @@ +export enum UserRole { + MENTOR = 'MENTOR', + MENTEE = 'MENTEE', + ADMIN = 'ADMIN', +} diff --git a/apps/api/src/users/profile-controller.ts b/apps/api/src/users/profile-controller.ts index 2ef73f2..32ed279 100644 --- a/apps/api/src/users/profile-controller.ts +++ b/apps/api/src/users/profile-controller.ts @@ -1,12 +1,4 @@ - -import { - Body, - Controller, - Get, - Put, - Req, - UseGuards, -} from '@nestjs/common'; +import { Body, Controller, Get, Put, Req, UseGuards } from '@nestjs/common'; import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard'; import { ApiTags, ApiBearerAuth } from '@nestjs/swagger'; import { UpdateProfileDto } from './dto/update-profile.dto'; @@ -19,15 +11,12 @@ export class ProfilesController { constructor(private readonly profilesService: ProfilesService) {} @Get('me') - getMe(@Req() req:any) { + getMe(@Req() req: any) { return this.profilesService.getMe(req.user.id); } @Put('me') - updateMe( - @Req() req: any, - @Body() dto: UpdateProfileDto, - ) { + updateMe(@Req() req: any, @Body() dto: UpdateProfileDto) { return this.profilesService.updateMe(req.user.id, dto); } } diff --git a/apps/api/src/users/profile.service.ts b/apps/api/src/users/profile.service.ts index a0da6c6..cdf94b1 100644 --- a/apps/api/src/users/profile.service.ts +++ b/apps/api/src/users/profile.service.ts @@ -53,17 +53,13 @@ export class ProfilesService { for (const key of Object.keys(socials)) { if (!allowed.includes(key)) { - throw new BadRequestException( - `Unsupported social key: ${key}`, - ); + throw new BadRequestException(`Unsupported social key: ${key}`); } try { new URL(socials[key]); } catch { - throw new BadRequestException( - `Invalid URL for ${key}`, - ); + throw new BadRequestException(`Invalid URL for ${key}`); } } } diff --git a/apps/api/src/users/users.module.ts b/apps/api/src/users/users.module.ts index 6bb75c2..0412356 100644 --- a/apps/api/src/users/users.module.ts +++ b/apps/api/src/users/users.module.ts @@ -6,7 +6,7 @@ import { Profile } from './entities/profile.entity'; import { ProfilesService } from './profile.service'; @Module({ imports: [TypeOrmModule.forFeature([User, Profile])], - controllers: [ProfilesController], + controllers: [ProfilesController], providers: [ProfilesService], imports: [TypeOrmModule.forFeature([User])], exports: [TypeOrmModule], diff --git a/apps/api/src/users/validators/iana-ttimezone.validator.ts b/apps/api/src/users/validators/iana-ttimezone.validator.ts index d818746..0efa12d 100644 --- a/apps/api/src/users/validators/iana-ttimezone.validator.ts +++ b/apps/api/src/users/validators/iana-ttimezone.validator.ts @@ -1,12 +1,7 @@ -import { - registerDecorator, - ValidationOptions, -} from 'class-validator'; +import { registerDecorator, ValidationOptions } from 'class-validator'; -export function IsIanaTimezone( - validationOptions?: ValidationOptions, -) { - return function (object: Object, propertyName: string) { +export function IsIanaTimezone(validationOptions?: ValidationOptions) { + return function (object: object, propertyName: string) { registerDecorator({ name: 'IsIanaTimezone', target: object.constructor, diff --git a/apps/api/src/verify/dto/create-kyc.dto.ts b/apps/api/src/verify/dto/create-kyc.dto.ts index abcb212..10d922e 100644 --- a/apps/api/src/verify/dto/create-kyc.dto.ts +++ b/apps/api/src/verify/dto/create-kyc.dto.ts @@ -23,4 +23,4 @@ export class CreateKycDto { @IsOptional() @IsString() updatedBy?: string; -} \ No newline at end of file +} diff --git a/apps/api/src/verify/dto/update-kyc.dto.ts b/apps/api/src/verify/dto/update-kyc.dto.ts index d6d5227..c81f76a 100644 --- a/apps/api/src/verify/dto/update-kyc.dto.ts +++ b/apps/api/src/verify/dto/update-kyc.dto.ts @@ -20,4 +20,4 @@ export class UpdateKycDto { @IsOptional() @IsString() updatedBy?: string; -} \ No newline at end of file +} diff --git a/apps/api/src/verify/dto/webhook-kyc.dto.ts b/apps/api/src/verify/dto/webhook-kyc.dto.ts index 07783cc..a01984b 100644 --- a/apps/api/src/verify/dto/webhook-kyc.dto.ts +++ b/apps/api/src/verify/dto/webhook-kyc.dto.ts @@ -19,4 +19,4 @@ export class WebhookKycDto { @IsOptional() @IsString() reason?: string; -} \ No newline at end of file +} diff --git a/apps/api/src/verify/entities/kyc.entity.ts b/apps/api/src/verify/entities/kyc.entity.ts index 6dcabc4..fc64b6a 100644 --- a/apps/api/src/verify/entities/kyc.entity.ts +++ b/apps/api/src/verify/entities/kyc.entity.ts @@ -1,4 +1,13 @@ -import { Entity, Column, PrimaryGeneratedColumn, ManyToOne, JoinColumn, Unique, CreateDateColumn, UpdateDateColumn } from 'typeorm'; +import { + Entity, + Column, + PrimaryGeneratedColumn, + ManyToOne, + JoinColumn, + Unique, + CreateDateColumn, + UpdateDateColumn, +} from 'typeorm'; import { User } from '../../users/entities/user.entity'; export enum KycStatus { @@ -42,4 +51,4 @@ export class Kyc { @UpdateDateColumn() updatedAt!: Date; -} \ No newline at end of file +} diff --git a/apps/api/src/verify/verify.controller.ts b/apps/api/src/verify/verify.controller.ts index f77ea78..da3a9ab 100644 --- a/apps/api/src/verify/verify.controller.ts +++ b/apps/api/src/verify/verify.controller.ts @@ -41,6 +41,10 @@ export class VerifyController { @CurrentMentorProfile() currentUser: User, ) { this.logger.log(`Admin ${currentUser.id} updating KYC for user ${userId}`); - return await this.verifyService.updateKycByAdmin(userId, updateKycDto, currentUser.id); + return await this.verifyService.updateKycByAdmin( + userId, + updateKycDto, + currentUser.id, + ); } -} \ No newline at end of file +} diff --git a/apps/api/src/verify/verify.module.ts b/apps/api/src/verify/verify.module.ts index 94d3cda..10b3053 100644 --- a/apps/api/src/verify/verify.module.ts +++ b/apps/api/src/verify/verify.module.ts @@ -12,4 +12,4 @@ import { User } from '../users/entities/user.entity'; controllers: [VerifyController, WebhookController], exports: [VerifyService], }) -export class VerifyModule {} \ No newline at end of file +export class VerifyModule {} diff --git a/apps/api/src/verify/verify.service.ts b/apps/api/src/verify/verify.service.ts index 4d4fab4..20d9935 100644 --- a/apps/api/src/verify/verify.service.ts +++ b/apps/api/src/verify/verify.service.ts @@ -1,4 +1,9 @@ -import { Injectable, NotFoundException, BadRequestException, Logger } from '@nestjs/common'; +import { + Injectable, + NotFoundException, + BadRequestException, + Logger, +} from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { User } from '../users/entities/user.entity'; @@ -36,7 +41,7 @@ export class VerifyService { const user = await this.userRepository.findOne({ where: { id: userId }, }); - + if (!user) { throw new NotFoundException(`User with ID ${userId} not found`); } @@ -52,11 +57,15 @@ export class VerifyService { return await this.kycRepository.save(kyc); } - async updateKycByAdmin(userId: string, updateKycDto: UpdateKycDto, adminId: string): Promise { + async updateKycByAdmin( + userId: string, + updateKycDto: UpdateKycDto, + adminId: string, + ): Promise { const user = await this.userRepository.findOne({ where: { id: userId }, }); - + if (!user) { throw new NotFoundException(`User with ID ${userId} not found`); } @@ -97,7 +106,7 @@ export class VerifyService { const user = await this.userRepository.findOne({ where: { id: userId }, }); - + if (!user) { throw new NotFoundException(`User with ID ${userId} not found`); } @@ -137,4 +146,4 @@ export class VerifyService { relations: ['user'], }); } -} \ No newline at end of file +} diff --git a/apps/api/src/verify/webhook.controller.ts b/apps/api/src/verify/webhook.controller.ts index d6776a6..40da1b0 100644 --- a/apps/api/src/verify/webhook.controller.ts +++ b/apps/api/src/verify/webhook.controller.ts @@ -22,4 +22,4 @@ export class WebhookController { this.logger.log(`Received KYC webhook for user ${webhookKycDto.userId}`); return await this.verifyService.handleWebhook(webhookKycDto); } -} \ No newline at end of file +} diff --git a/apps/api/test/auth.e2e-spec.ts b/apps/api/test/auth.e2e-spec.ts index b1d1cd8..5dd5d02 100644 --- a/apps/api/test/auth.e2e-spec.ts +++ b/apps/api/test/auth.e2e-spec.ts @@ -59,8 +59,12 @@ describe('Authentication E2E', () => { expect(response.body.tokens).toHaveProperty('refreshToken'); // Verify tokens are valid JWT - expect(response.body.tokens.accessToken).toMatch(/^[\w-]+\.[\w-]+\.[\w-]+$/); - expect(response.body.tokens.refreshToken).toMatch(/^[\w-]+\.[\w-]+\.[\w-]+$/); + expect(response.body.tokens.accessToken).toMatch( + /^[\w-]+\.[\w-]+\.[\w-]+$/, + ); + expect(response.body.tokens.refreshToken).toMatch( + /^[\w-]+\.[\w-]+\.[\w-]+$/, + ); }); it('should fail when registering with duplicate email', async () => { @@ -163,9 +167,7 @@ describe('Authentication E2E', () => { describe('/auth/login (POST)', () => { beforeEach(async () => { // Register a user before each login test - await request(app.getHttpServer()) - .post('/auth/register') - .send(testUser); + await request(app.getHttpServer()).post('/auth/register').send(testUser); }); it('should successfully login with valid credentials', async () => { @@ -318,7 +320,9 @@ describe('Authentication E2E', () => { .expect(201); const accessToken = registerResponse.body.tokens.accessToken; - const accessSecret = process.env.JWT_ACCESS_SECRET || 'your-super-secret-access-key-change-in-production'; + const accessSecret = + process.env.JWT_ACCESS_SECRET || + 'your-super-secret-access-key-change-in-production'; // This should not throw const decoded = jwt.verify(accessToken, accessSecret) as any; @@ -334,7 +338,9 @@ describe('Authentication E2E', () => { .expect(201); const refreshToken = registerResponse.body.tokens.refreshToken; - const refreshSecret = process.env.JWT_REFRESH_SECRET || 'your-super-secret-refresh-key-change-in-production'; + const refreshSecret = + process.env.JWT_REFRESH_SECRET || + 'your-super-secret-refresh-key-change-in-production'; // This should not throw const decoded = jwt.verify(refreshToken, refreshSecret) as any; diff --git a/apps/api/test/kyc.e2e-spec.ts b/apps/api/test/kyc.e2e-spec.ts index 02d0b98..9c28172 100644 --- a/apps/api/test/kyc.e2e-spec.ts +++ b/apps/api/test/kyc.e2e-spec.ts @@ -33,9 +33,9 @@ describe('KYC E2E Tests', () => { user.lastName = 'User'; user.avatarUrl = undefined; user.isActive = true; - + user = await dataSource.getRepository(User).save(user); - + // In a real scenario, we would need to create a proper JWT token for testing // For now, we'll mock authentication authToken = 'mock-token'; @@ -56,7 +56,7 @@ describe('KYC E2E Tests', () => { .get('/verify/kyc/me') .set('Authorization', `Bearer ${authToken}`) .expect(200) - .then(response => { + .then((response) => { expect(response.body).toEqual({ id: expect.any(String), user: { @@ -102,7 +102,7 @@ describe('KYC E2E Tests', () => { }) .set('Authorization', `Bearer ${adminToken}`) .expect(200) - .then(response => { + .then((response) => { expect(response.body.status).toBe(KycStatus.VERIFIED); expect(response.body.reason).toBe('Document verified'); expect(response.body.provider).toBe('mock-provider'); @@ -132,11 +132,13 @@ describe('KYC E2E Tests', () => { reason: 'Successfully verified by external provider', }) .expect(200) - .then(response => { + .then((response) => { expect(response.body.status).toBe(KycStatus.VERIFIED); expect(response.body.provider).toBe('external-provider'); expect(response.body.externalRef).toBe('ext-ref-123'); - expect(response.body.reason).toBe('Successfully verified by external provider'); + expect(response.body.reason).toBe( + 'Successfully verified by external provider', + ); }); }); @@ -151,4 +153,4 @@ describe('KYC E2E Tests', () => { .expect(400); }); }); -}); \ No newline at end of file +}); diff --git a/package.json b/package.json index 5633731..feb1751 100755 --- a/package.json +++ b/package.json @@ -41,32 +41,13 @@ "@nestjs/common": "^11.0.1", "@nestjs/config": "^4.0.2", "@nestjs/core": "^11.0.1", -<<<<<<< HEAD -======= - "class-transformer": "^0.5.1", - "class-validator": "^0.14.3", ->>>>>>> upstream/main "@nestjs/platform-express": "^11.1.12", "@nestjs/swagger": "^11.2.5", "@nestjs/typeorm": "^11.0.0", - "@nestjs/platform-express": "^11.1.12", - "@nestjs/swagger": "^11.2.5", "@types/express-rate-limit": "^6.0.2", "@types/multer": "^2.0.0", "class-transformer": "^0.5.1", "class-validator": "^0.14.3", -<<<<<<< HEAD -======= - "ioredis": "^5.9.2", - "cors": "^2.8.6", - "express-rate-limit": "^8.2.1", - "helmet": "^8.1.0", - "@types/luxon": "^3.7.1", - "@types/multer": "^2.0.0", - "class-transformer": "^0.5.1", - "class-validator": "^0.14.3", - "luxon": "^3.7.2", ->>>>>>> upstream/main "multer": "^2.0.2", "pg": "^8.17.2", "reflect-metadata": "^0.2.2", diff --git a/yarn.lock b/yarn.lock index 1490db8..66f24cb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -74,7 +74,7 @@ resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.6.tgz" integrity sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg== -"@babel/core@^7.0.0", "@babel/core@^7.0.0 || ^8.0.0-0", "@babel/core@^7.0.0-0", "@babel/core@^7.11.0 || ^8.0.0-0", "@babel/core@^7.11.0 || ^8.0.0-beta.1", "@babel/core@^7.23.9", "@babel/core@^7.27.4", "@babel/core@>=7.0.0-beta.0 <8": +"@babel/core@^7.23.9", "@babel/core@^7.27.4": version "7.28.6" resolved "https://registry.npmjs.org/@babel/core/-/core-7.28.6.tgz" integrity sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw== @@ -500,6 +500,28 @@ dependencies: "@jridgewell/trace-mapping" "0.3.9" +"@emnapi/core@^1.4.3": + version "1.8.1" + resolved "https://registry.yarnpkg.com/@emnapi/core/-/core-1.8.1.tgz#fd9efe721a616288345ffee17a1f26ac5dd01349" + integrity sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg== + dependencies: + "@emnapi/wasi-threads" "1.1.0" + tslib "^2.4.0" + +"@emnapi/runtime@^1.4.3": + version "1.8.1" + resolved "https://registry.yarnpkg.com/@emnapi/runtime/-/runtime-1.8.1.tgz#550fa7e3c0d49c5fb175a116e8cd70614f9a22a5" + integrity sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg== + dependencies: + tslib "^2.4.0" + +"@emnapi/wasi-threads@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz#60b2102fddc9ccb78607e4a3cf8403ea69be41bf" + integrity sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ== + dependencies: + tslib "^2.4.0" + "@eslint-community/eslint-utils@^4.8.0", "@eslint-community/eslint-utils@^4.9.1": version "4.9.1" resolved "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz" @@ -550,7 +572,7 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" -"@eslint/js@^9.18.0", "@eslint/js@9.39.2": +"@eslint/js@9.39.2", "@eslint/js@^9.18.0": version "9.39.2" resolved "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz" integrity sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA== @@ -568,11 +590,6 @@ "@eslint/core" "^0.17.0" levn "^0.4.1" -"@faker-js/faker@>=8.4.1": - version "10.2.0" - resolved "https://registry.npmjs.org/@faker-js/faker/-/faker-10.2.0.tgz" - integrity sha512-rTXwAsIxpCqzUnZvrxVh3L0QA0NzToqWBLAhV+zDV3MIIwiQhAZHMdPCIaj5n/yADu/tyk12wIPgL6YHGXJP+g== - "@humanfs/core@^0.19.1": version "0.19.1" resolved "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz" @@ -757,11 +774,6 @@ resolved "https://registry.npmjs.org/@inquirer/type/-/type-3.0.10.tgz" integrity sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA== -"@ioredis/commands@1.5.0": - version "1.5.0" - resolved "https://registry.npmjs.org/@ioredis/commands/-/commands-1.5.0.tgz" - integrity sha512-eUgLqrMf8nJkZxT24JvVRrQya1vZkQh8BBeYNwGDqa5I0VUi8ACx7uFvAaLxintokpTenkK6DASvo/bvNbBGow== - "@isaacs/balanced-match@^4.0.1": version "4.0.1" resolved "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz" @@ -988,7 +1000,7 @@ jest-haste-map "30.2.0" slash "^3.0.0" -"@jest/transform@^29.0.0 || ^30.0.0", "@jest/transform@30.2.0": +"@jest/transform@30.2.0": version "30.2.0" resolved "https://registry.npmjs.org/@jest/transform/-/transform-30.2.0.tgz" integrity sha512-XsauDV82o5qXbhalKxD7p4TZYYdwcaEXC77PPD2HixEFF+6YGppjrAAQurTl2ECWcEomHBMMNS9AH3kcCFx8jA== @@ -1009,7 +1021,7 @@ slash "^3.0.0" write-file-atomic "^5.0.1" -"@jest/types@^29.0.0 || ^30.0.0", "@jest/types@30.2.0": +"@jest/types@30.2.0": version "30.2.0" resolved "https://registry.npmjs.org/@jest/types/-/types-30.2.0.tgz" integrity sha512-H9xg1/sfVvyfU7o3zMfBEjQ1gcsdeTMgqHoYdN79tuLqfTtuu7WckRA1R5whDwOzxaZAeMKTYWqP+WCAi0CHsg== @@ -1056,14 +1068,6 @@ resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz" integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og== -"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.23", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25", "@jridgewell/trace-mapping@^0.3.28": - version "0.3.31" - resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz" - integrity sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw== - dependencies: - "@jridgewell/resolve-uri" "^3.1.0" - "@jridgewell/sourcemap-codec" "^1.4.14" - "@jridgewell/trace-mapping@0.3.9": version "0.3.9" resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz" @@ -1072,6 +1076,14 @@ "@jridgewell/resolve-uri" "^3.0.3" "@jridgewell/sourcemap-codec" "^1.4.10" +"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.23", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25", "@jridgewell/trace-mapping@^0.3.28": + version "0.3.31" + resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz" + integrity sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + "@lukeed/csprng@^1.0.0": version "1.1.0" resolved "https://registry.npmjs.org/@lukeed/csprng/-/csprng-1.1.0.tgz" @@ -1082,6 +1094,15 @@ resolved "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.16.0.tgz" integrity sha512-xgAyonlVVS+q7Vc7qLW0UrJU7rSFcETRWsqdXZtjzRU8dF+6CkozTK4V4y1LwOX7j8r/vHphjDeMeGI4tNGeGA== +"@napi-rs/wasm-runtime@^0.2.11": + version "0.2.12" + resolved "https://registry.yarnpkg.com/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz#3e78a8b96e6c33a6c517e1894efbd5385a7cb6f2" + integrity sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ== + dependencies: + "@emnapi/core" "^1.4.3" + "@emnapi/runtime" "^1.4.3" + "@tybys/wasm-util" "^0.10.0" + "@nestjs/cli@^11.0.0": version "11.0.16" resolved "https://registry.npmjs.org/@nestjs/cli/-/cli-11.0.16.tgz" @@ -1106,16 +1127,16 @@ webpack "5.104.1" webpack-node-externals "3.0.0" -"@nestjs/common@^10.0.0 || ^11.0.0", "@nestjs/common@^11.0.0", "@nestjs/common@^11.0.1": +"@nestjs/common@^11.0.1": version "11.1.12" resolved "https://registry.npmjs.org/@nestjs/common/-/common-11.1.12.tgz" integrity sha512-v6U3O01YohHO+IE3EIFXuRuu3VJILWzyMmSYZXpyBbnp0hk0mFyHxK2w3dF4I5WnbwiRbWlEXdeXFvPQ7qaZzw== dependencies: + uid "2.0.2" file-type "21.3.0" iterare "1.2.1" load-esm "1.0.3" tslib "2.8.1" - uid "2.0.2" "@nestjs/config@^4.0.2": version "4.0.2" @@ -1126,24 +1147,24 @@ dotenv-expand "12.0.1" lodash "4.17.21" -"@nestjs/core@^10.0.0 || ^11.0.0", "@nestjs/core@^11.0.0", "@nestjs/core@^11.0.1": +"@nestjs/core@^11.0.1": version "11.1.12" resolved "https://registry.npmjs.org/@nestjs/core/-/core-11.1.12.tgz" integrity sha512-97DzTYMf5RtGAVvX1cjwpKRiCUpkeQ9CCzSAenqkAhOmNVVFaApbhuw+xrDt13rsCa2hHVOYPrV4dBgOYMJjsA== dependencies: + uid "2.0.2" "@nuxt/opencollective" "0.4.1" fast-safe-stringify "2.1.1" iterare "1.2.1" path-to-regexp "8.3.0" tslib "2.8.1" - uid "2.0.2" "@nestjs/mapped-types@2.1.0": 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== -"@nestjs/platform-express@^11.0.0", "@nestjs/platform-express@^11.1.12": +"@nestjs/platform-express@^11.1.12": version "11.1.12" resolved "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-11.1.12.tgz" integrity sha512-GYK/vHI0SGz5m8mxr7v3Urx8b9t78Cf/dj5aJMZlGd9/1D9OI1hAl00BaphjEXINUJ/BQLxIlF2zUjrYsd6enQ== @@ -1202,7 +1223,7 @@ "@nodelib/fs.stat" "2.0.5" run-parallel "^1.1.9" -"@nodelib/fs.stat@^2.0.2", "@nodelib/fs.stat@2.0.5": +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": version "2.0.5" resolved "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz" integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== @@ -1301,6 +1322,13 @@ resolved "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz" integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA== +"@tybys/wasm-util@^0.10.0": + version "0.10.1" + resolved "https://registry.yarnpkg.com/@tybys/wasm-util/-/wasm-util-0.10.1.tgz#ecddd3205cf1e2d5274649ff0eedd2991ed7f414" + integrity sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg== + dependencies: + tslib "^2.4.0" + "@types/babel__core@^7.20.5": version "7.20.5" resolved "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz" @@ -1369,7 +1397,7 @@ "@types/eslint" "*" "@types/estree" "*" -"@types/eslint@*", "@types/eslint@>=8.0.0": +"@types/eslint@*": version "9.6.1" resolved "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz" integrity sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag== @@ -1452,11 +1480,6 @@ resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz" integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== -"@types/luxon@^3.7.1": - version "3.7.1" - resolved "https://registry.npmjs.org/@types/luxon/-/luxon-3.7.1.tgz" - integrity sha512-H3iskjFIAn5SlJU7OuxUmTEpebK6TKB8rxZShDslBMZJ5u9S//KM1sbdAisiSrqwLQncVjnpi2OK2J51h+4lsg== - "@types/methods@^1.1.4": version "1.1.4" resolved "https://registry.npmjs.org/@types/methods/-/methods-1.1.4.tgz" @@ -1469,7 +1492,7 @@ dependencies: "@types/express" "*" -"@types/node@*", "@types/node@^22.10.7", "@types/node@>=18": +"@types/node@*", "@types/node@^22.10.7": version "22.19.7" resolved "https://registry.npmjs.org/@types/node/-/node-22.19.7.tgz" integrity sha512-MciR4AKGHWl7xwxkBa6xUGxQJ4VBOmPTF7sL+iGzuahOFaO0jHCsuEfS80pan1ef4gWId1oWOweIhrDEYLuaOw== @@ -1565,7 +1588,7 @@ natural-compare "^1.4.0" ts-api-utils "^2.4.0" -"@typescript-eslint/parser@^8.53.1", "@typescript-eslint/parser@8.53.1": +"@typescript-eslint/parser@8.53.1": version "8.53.1" resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.53.1.tgz" integrity sha512-nm3cvFN9SqZGXjmw5bZ6cGmvJSyJPn0wU9gHAZZHDnZl2wF9PhHv78Xf06E0MaNk4zLVHL8hb2/c32XvyJOLQg== @@ -1593,7 +1616,7 @@ "@typescript-eslint/types" "8.53.1" "@typescript-eslint/visitor-keys" "8.53.1" -"@typescript-eslint/tsconfig-utils@^8.53.1", "@typescript-eslint/tsconfig-utils@8.53.1": +"@typescript-eslint/tsconfig-utils@8.53.1", "@typescript-eslint/tsconfig-utils@^8.53.1": version "8.53.1" resolved "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.53.1.tgz" integrity sha512-qfvLXS6F6b1y43pnf0pPbXJ+YoXIC7HKg0UGZ27uMIemKMKA6XH2DTxsEDdpdN29D+vHV07x/pnlPNVLhdhWiA== @@ -1609,7 +1632,7 @@ debug "^4.4.3" ts-api-utils "^2.4.0" -"@typescript-eslint/types@^8.53.1", "@typescript-eslint/types@8.53.1": +"@typescript-eslint/types@8.53.1", "@typescript-eslint/types@^8.53.1": version "8.53.1" resolved "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.53.1.tgz" integrity sha512-jr/swrr2aRmUAUjW5/zQHbMaui//vQlsZcJKijZf3M26bnmLj8LyZUpj8/Rd6uzaek06OWsqdofN/Thenm5O8A== @@ -1652,12 +1675,104 @@ resolved "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz" integrity sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g== +"@unrs/resolver-binding-android-arm-eabi@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz#9f5b04503088e6a354295e8ea8fe3cb99e43af81" + integrity sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw== + +"@unrs/resolver-binding-android-arm64@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz#7414885431bd7178b989aedc4d25cccb3865bc9f" + integrity sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g== + +"@unrs/resolver-binding-darwin-arm64@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz#b4a8556f42171fb9c9f7bac8235045e82aa0cbdf" + integrity sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g== + +"@unrs/resolver-binding-darwin-x64@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz#fd4d81257b13f4d1a083890a6a17c00de571f0dc" + integrity sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ== + +"@unrs/resolver-binding-freebsd-x64@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz#d2513084d0f37c407757e22f32bd924a78cfd99b" + integrity sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw== + +"@unrs/resolver-binding-linux-arm-gnueabihf@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz#844d2605d057488d77fab09705f2866b86164e0a" + integrity sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw== + +"@unrs/resolver-binding-linux-arm-musleabihf@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz#204892995cefb6bd1d017d52d097193bc61ddad3" + integrity sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw== + +"@unrs/resolver-binding-linux-arm64-gnu@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz#023eb0c3aac46066a10be7a3f362e7b34f3bdf9d" + integrity sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ== + +"@unrs/resolver-binding-linux-arm64-musl@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz#9e6f9abb06424e3140a60ac996139786f5d99be0" + integrity sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w== + +"@unrs/resolver-binding-linux-ppc64-gnu@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz#b111417f17c9d1b02efbec8e08398f0c5527bb44" + integrity sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA== + +"@unrs/resolver-binding-linux-riscv64-gnu@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz#92ffbf02748af3e99873945c9a8a5ead01d508a9" + integrity sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ== + +"@unrs/resolver-binding-linux-riscv64-musl@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz#0bec6f1258fc390e6b305e9ff44256cb207de165" + integrity sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew== + +"@unrs/resolver-binding-linux-s390x-gnu@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz#577843a084c5952f5906770633ccfb89dac9bc94" + integrity sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg== + +"@unrs/resolver-binding-linux-x64-gnu@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz#36fb318eebdd690f6da32ac5e0499a76fa881935" + integrity sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w== + +"@unrs/resolver-binding-linux-x64-musl@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz#bfb9af75f783f98f6a22c4244214efe4df1853d6" + integrity sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA== + +"@unrs/resolver-binding-wasm32-wasi@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz#752c359dd875684b27429500d88226d7cc72f71d" + integrity sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ== + dependencies: + "@napi-rs/wasm-runtime" "^0.2.11" + +"@unrs/resolver-binding-win32-arm64-msvc@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz#ce5735e600e4c2fbb409cd051b3b7da4a399af35" + integrity sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw== + +"@unrs/resolver-binding-win32-ia32-msvc@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz#72fc57bc7c64ec5c3de0d64ee0d1810317bc60a6" + integrity sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ== + "@unrs/resolver-binding-win32-x64-msvc@1.11.1": version "1.11.1" resolved "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz" integrity sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g== -"@webassemblyjs/ast@^1.14.1", "@webassemblyjs/ast@1.14.1": +"@webassemblyjs/ast@1.14.1", "@webassemblyjs/ast@^1.14.1": version "1.14.1" resolved "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz" integrity sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ== @@ -1758,7 +1873,7 @@ "@webassemblyjs/wasm-gen" "1.14.1" "@webassemblyjs/wasm-parser" "1.14.1" -"@webassemblyjs/wasm-parser@^1.14.1", "@webassemblyjs/wasm-parser@1.14.1": +"@webassemblyjs/wasm-parser@1.14.1", "@webassemblyjs/wasm-parser@^1.14.1": version "1.14.1" resolved "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz" integrity sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ== @@ -1788,6 +1903,14 @@ resolved "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz" integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== +JSONStream@^1.3.5: + version "1.3.5" + resolved "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz" + integrity sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ== + dependencies: + jsonparse "^1.2.0" + through ">=2.2.7 <3" + accepts@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz" @@ -1813,18 +1936,11 @@ acorn-walk@^8.1.1: dependencies: acorn "^8.11.0" -"acorn@^6.0.0 || ^7.0.0 || ^8.0.0", acorn@^8.11.0, acorn@^8.14.0, acorn@^8.15.0, acorn@^8.4.1: +acorn@^8.11.0, acorn@^8.15.0, acorn@^8.4.1: version "8.15.0" resolved "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz" integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg== -ajv-formats@^2.1.1: - version "2.1.1" - resolved "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz" - integrity sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA== - dependencies: - ajv "^8.0.0" - ajv-formats@3.0.1: version "3.0.1" resolved "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz" @@ -1832,6 +1948,13 @@ ajv-formats@3.0.1: dependencies: ajv "^8.0.0" +ajv-formats@^2.1.1: + version "2.1.1" + resolved "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz" + integrity sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA== + dependencies: + ajv "^8.0.0" + ajv-keywords@^3.5.2: version "3.5.2" resolved "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz" @@ -1844,17 +1967,17 @@ ajv-keywords@^5.1.0: dependencies: fast-deep-equal "^3.1.3" -ajv@^6.12.4: - version "6.12.6" - resolved "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz" - integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== +ajv@8.17.1, ajv@^8.0.0, ajv@^8.11.0, ajv@^8.9.0: + version "8.17.1" + resolved "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz" + integrity sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g== dependencies: - fast-deep-equal "^3.1.1" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.4.1" - uri-js "^4.2.2" + fast-deep-equal "^3.1.3" + fast-uri "^3.0.1" + json-schema-traverse "^1.0.0" + require-from-string "^2.0.2" -ajv@^6.12.5, ajv@^6.9.1: +ajv@^6.12.4, ajv@^6.12.5: version "6.12.6" resolved "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz" integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== @@ -1864,16 +1987,6 @@ ajv@^6.12.5, ajv@^6.9.1: json-schema-traverse "^0.4.1" uri-js "^4.2.2" -ajv@^8.0.0, ajv@^8.11.0, ajv@^8.8.2, ajv@^8.9.0, ajv@8.17.1: - version "8.17.1" - resolved "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz" - integrity sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g== - dependencies: - fast-deep-equal "^3.1.3" - fast-uri "^3.0.1" - json-schema-traverse "^1.0.0" - require-from-string "^2.0.2" - ansi-colors@4.1.3: version "4.1.3" resolved "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz" @@ -1903,14 +2016,7 @@ ansi-regex@^6.0.1: resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz" integrity sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg== -ansi-styles@^4.0.0: - version "4.3.0" - resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz" - integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== - dependencies: - color-convert "^2.0.1" - -ansi-styles@^4.1.0: +ansi-styles@^4.0.0, ansi-styles@^4.1.0: version "4.3.0" resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz" integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== @@ -1922,17 +2028,12 @@ ansi-styles@^5.2.0: resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz" integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== -ansi-styles@^6.1.0: - version "6.2.3" - resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz" - integrity sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg== - -ansi-styles@^6.2.1: +ansi-styles@^6.1.0, ansi-styles@^6.2.1: version "6.2.3" resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz" integrity sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg== -ansis@^4.2.0, ansis@4.2.0: +ansis@4.2.0, ansis@^4.2.0: version "4.2.0" resolved "https://registry.npmjs.org/ansis/-/ansis-4.2.0.tgz" integrity sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig== @@ -1999,7 +2100,7 @@ available-typed-arrays@^1.0.7: dependencies: possible-typed-array-names "^1.0.0" -"babel-jest@^29.0.0 || ^30.0.0", babel-jest@30.2.0: +babel-jest@30.2.0: version "30.2.0" resolved "https://registry.npmjs.org/babel-jest/-/babel-jest-30.2.0.tgz" integrity sha512-0YiBEOxWqKkSQWL9nNGGEgndoeL0ZpWrbLMNL5u/Kaxrli3Eaxlt3ZtIDktEvXt4L/R9r3ODr2zKwGM/2BjxVw== @@ -2125,7 +2226,7 @@ braces@^3.0.3, braces@~3.0.2: dependencies: fill-range "^7.1.1" -browserslist@^4.24.0, browserslist@^4.28.1, "browserslist@>= 4.21.0": +browserslist@^4.24.0, browserslist@^4.28.1: version "4.28.1" resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz" integrity sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA== @@ -2229,23 +2330,7 @@ caniuse-lite@^1.0.30001759: resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001765.tgz" integrity sha512-LWcNtSyZrakjECqmpP4qdg0MMGdN368D7X8XvvAqOcqMv0RxnlqVKZl2V6/mBR68oYMxOZPLw/gO7DuisMHUvQ== -chalk@^4.0.0: - version "4.1.2" - resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz" - integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - -chalk@^4.1.0: - version "4.1.2" - resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz" - integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - -chalk@^4.1.2: +chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.2: version "4.1.2" resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== @@ -2268,6 +2353,13 @@ chardet@^2.1.1: resolved "https://registry.npmjs.org/chardet/-/chardet-2.1.1.tgz" integrity sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ== +chokidar@4.0.3, chokidar@^4.0.1: + version "4.0.3" + resolved "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz" + integrity sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA== + dependencies: + readdirp "^4.0.1" + chokidar@^3.5.1: version "3.6.0" resolved "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz" @@ -2283,13 +2375,6 @@ chokidar@^3.5.1: optionalDependencies: fsevents "~2.3.2" -chokidar@^4.0.0, chokidar@^4.0.1, chokidar@4.0.3: - version "4.0.3" - resolved "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz" - integrity sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA== - dependencies: - readdirp "^4.0.1" - chrome-trace-event@^1.0.2: version "1.0.4" resolved "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz" @@ -2305,12 +2390,12 @@ cjs-module-lexer@^2.1.0: resolved "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.2.0.tgz" integrity sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ== -class-transformer@*, "class-transformer@^0.4.0 || ^0.5.0", class-transformer@^0.5.1, class-transformer@>=0.4.1: +class-transformer@^0.5.1: version "0.5.1" resolved "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz" integrity sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw== -class-validator@*, "class-validator@^0.13.0 || ^0.14.0", class-validator@^0.14.3, class-validator@>=0.13.2: +class-validator@^0.14.3: version "0.14.3" resolved "https://registry.npmjs.org/class-validator/-/class-validator-0.14.3.tgz" integrity sha512-rXXekcjofVN1LTOSw+u4u9WXVEUvNBVjORW154q/IdmYWy1nMbOU9aNtZB0t8m+FJQ9q91jlr2f9CwwUFdFMRA== @@ -2383,11 +2468,6 @@ clone@^1.0.2: resolved "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz" integrity sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg== -cluster-key-slot@^1.1.0: - version "1.1.2" - resolved "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz" - integrity sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA== - co@^4.6.0: version "4.6.0" resolved "https://registry.npmjs.org/co/-/co-4.6.0.tgz" @@ -2422,6 +2502,11 @@ combined-stream@^1.0.8: dependencies: delayed-stream "~1.0.0" +commander@4.1.1: + version "4.1.1" + resolved "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz" + integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== + commander@^14.0.2: version "14.0.2" resolved "https://registry.npmjs.org/commander/-/commander-14.0.2.tgz" @@ -2432,11 +2517,6 @@ commander@^2.20.0: resolved "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== -commander@4.1.1: - version "4.1.1" - resolved "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz" - integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== - comment-json@4.4.1: version "4.4.1" resolved "https://registry.npmjs.org/comment-json/-/comment-json-4.4.1.tgz" @@ -2508,8 +2588,8 @@ conventional-commits-parser@^5.0.0: resolved "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-5.0.0.tgz" integrity sha512-ZPMl0ZJbw74iS9LuX9YIAiW8pfM5p3yh2o/NbXHbkFuZzY5jvdi5jFycEOkmBW5H5I7nA+D6f3UcsCLP2vvSEA== dependencies: - is-text-path "^2.0.0" JSONStream "^1.3.5" + is-text-path "^2.0.0" meow "^12.0.1" split2 "^4.0.0" @@ -2538,14 +2618,6 @@ core-util-is@^1.0.3: resolved "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz" integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== -cors@^2.8.6: - version "2.8.6" - resolved "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz" - integrity sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw== - dependencies: - object-assign "^4" - vary "^1" - cors@2.8.5: version "2.8.5" resolved "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz" @@ -2571,7 +2643,7 @@ cosmiconfig@^8.2.0: parse-json "^5.2.0" path-type "^4.0.0" -cosmiconfig@^9.0.0, cosmiconfig@>=9: +cosmiconfig@^9.0.0: version "9.0.0" resolved "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz" integrity sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg== @@ -2605,7 +2677,7 @@ dayjs@^1.11.19: resolved "https://registry.npmjs.org/dayjs/-/dayjs-1.11.19.tgz" integrity sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw== -debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.3.7, debug@^4.4.0, debug@^4.4.3: +debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.7, debug@^4.4.0, debug@^4.4.3: version "4.4.3" resolved "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz" integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== @@ -2648,11 +2720,6 @@ delayed-stream@~1.0.0: resolved "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz" integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== -denque@^2.1.0: - version "2.1.0" - resolved "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz" - integrity sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw== - depd@^2.0.0, depd@~2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz" @@ -2695,7 +2762,7 @@ dotenv-expand@12.0.1: dependencies: dotenv "^16.4.5" -dotenv@^16.4.5, dotenv@16.4.7: +dotenv@16.4.7, dotenv@^16.4.5: version "16.4.7" resolved "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz" integrity sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ== @@ -2857,7 +2924,7 @@ escape-string-regexp@^4.0.0: resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz" integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== -eslint-config-prettier@^10.0.1, "eslint-config-prettier@>= 7.0.0 <10.0.0 || >=10.1.0": +eslint-config-prettier@^10.0.1: version "10.1.8" resolved "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz" integrity sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w== @@ -2870,14 +2937,6 @@ eslint-plugin-prettier@^5.2.2: prettier-linter-helpers "^1.0.1" synckit "^0.11.12" -eslint-scope@^8.4.0: - version "8.4.0" - resolved "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz" - integrity sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg== - dependencies: - esrecurse "^4.3.0" - estraverse "^5.2.0" - eslint-scope@5.1.1: version "5.1.1" resolved "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz" @@ -2886,6 +2945,14 @@ eslint-scope@5.1.1: esrecurse "^4.3.0" estraverse "^4.1.1" +eslint-scope@^8.4.0: + version "8.4.0" + resolved "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz" + integrity sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg== + dependencies: + esrecurse "^4.3.0" + estraverse "^5.2.0" + eslint-visitor-keys@^3.4.3: version "3.4.3" resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz" @@ -2896,7 +2963,7 @@ eslint-visitor-keys@^4.2.1: resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz" integrity sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ== -"eslint@^6.0.0 || ^7.0.0 || >=8.0.0", "eslint@^8.57.0 || ^9.0.0", eslint@^9.18.0, eslint@>=7.0.0, eslint@>=8.0.0: +eslint@^9.18.0: version "9.39.2" resolved "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz" integrity sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw== @@ -3014,7 +3081,7 @@ exit-x@^0.2.2: resolved "https://registry.npmjs.org/exit-x/-/exit-x-0.2.2.tgz" integrity sha512-+I6B/IkJc1o/2tiURyz/ivu/O0nKNEArIUB5O7zBrlDVJr22SCLH3xTeEry428LvFhRzIA1g8izguxJ/gbNcVQ== -expect@^30.0.0, expect@30.2.0: +expect@30.2.0, expect@^30.0.0: version "30.2.0" resolved "https://registry.npmjs.org/expect/-/expect-30.2.0.tgz" integrity sha512-u/feCi0GPsI+988gU2FLcsHyAHTU0MX1Wg68NhAnN7z/+C5wqG+CY8J53N9ioe8RXgaoz0nBR/TYMf3AycUuPw== @@ -3026,14 +3093,14 @@ expect@^30.0.0, expect@30.2.0: jest-mock "30.2.0" jest-util "30.2.0" -express-rate-limit@*, express-rate-limit@^8.2.1: +express-rate-limit@*: version "8.2.1" resolved "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.2.1.tgz" integrity sha512-PCZEIEIxqwhzw4KF0n7QF4QqruVTcF73O5kFKUnGOyjbCCgizBBiFaYpd/fnBLUMPw/BWw9OsiN7GgrNYr7j6g== dependencies: ip-address "10.0.1" -"express@>= 4.11", "express@>=4.0.0 || >=5.0.0-beta", express@5.2.1: +express@5.2.1: version "5.2.1" resolved "https://registry.npmjs.org/express/-/express-5.2.1.tgz" integrity sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw== @@ -3088,7 +3155,7 @@ fast-glob@^3.3.3: merge2 "^1.3.0" micromatch "^4.0.8" -fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0, fast-json-stable-stringify@2.x: +fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== @@ -3098,7 +3165,7 @@ fast-levenshtein@^2.0.6: resolved "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz" integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== -fast-safe-stringify@^2.1.1, fast-safe-stringify@2.1.1: +fast-safe-stringify@2.1.1, fast-safe-stringify@^2.1.1: version "2.1.1" resolved "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz" integrity sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA== @@ -3163,15 +3230,7 @@ finalhandler@^2.1.0: parseurl "^1.3.3" statuses "^2.0.1" -find-up@^4.0.0: - version "4.1.0" - resolved "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz" - integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== - dependencies: - locate-path "^5.0.0" - path-exists "^4.0.0" - -find-up@^4.1.0: +find-up@^4.0.0, find-up@^4.1.0: version "4.1.0" resolved "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz" integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== @@ -3296,6 +3355,11 @@ fs.realpath@^1.0.0: resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== +fsevents@^2.3.3, fsevents@~2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + function-bind@^1.1.2: version "1.1.2" resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz" @@ -3359,7 +3423,7 @@ git-raw-commits@^4.0.0: meow "^12.0.1" split2 "^4.0.0" -glob-parent@^5.1.2: +glob-parent@^5.1.2, glob-parent@~5.1.2: version "5.1.2" resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz" integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== @@ -3373,31 +3437,21 @@ glob-parent@^6.0.2: dependencies: is-glob "^4.0.3" -glob-parent@~5.1.2: - version "5.1.2" - resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz" - integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== - dependencies: - is-glob "^4.0.1" - glob-to-regexp@^0.4.1: version "0.4.1" resolved "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz" integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== -glob@^10.3.10: - version "10.5.0" - resolved "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz" - integrity sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg== +glob@13.0.0: + version "13.0.0" + resolved "https://registry.npmjs.org/glob/-/glob-13.0.0.tgz" + integrity sha512-tvZgpqk6fz4BaNZ66ZsRaZnbHvP/jG3uKJvAZOwEVUL4RTA5nJeeLYfyN9/VA8NX/V3IBG+hkeuGpKjvELkVhA== dependencies: - foreground-child "^3.1.0" - jackspeak "^3.1.2" - minimatch "^9.0.4" + minimatch "^10.1.1" minipass "^7.1.2" - package-json-from-dist "^1.0.0" - path-scurry "^1.11.1" + path-scurry "^2.0.0" -glob@^10.5.0: +glob@^10.3.10, glob@^10.5.0: version "10.5.0" resolved "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz" integrity sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg== @@ -3409,19 +3463,7 @@ glob@^10.5.0: package-json-from-dist "^1.0.0" path-scurry "^1.11.1" -glob@^7.1.3: - version "7.2.3" - resolved "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz" - integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== - 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" - -glob@^7.1.4: +glob@^7.1.3, glob@^7.1.4: version "7.2.3" resolved "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz" integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== @@ -3433,15 +3475,6 @@ glob@^7.1.4: once "^1.3.0" path-is-absolute "^1.0.0" -glob@13.0.0: - version "13.0.0" - resolved "https://registry.npmjs.org/glob/-/glob-13.0.0.tgz" - integrity sha512-tvZgpqk6fz4BaNZ66ZsRaZnbHvP/jG3uKJvAZOwEVUL4RTA5nJeeLYfyN9/VA8NX/V3IBG+hkeuGpKjvELkVhA== - dependencies: - minimatch "^10.1.1" - minipass "^7.1.2" - path-scurry "^2.0.0" - global-directory@^4.0.1: version "4.0.1" resolved "https://registry.npmjs.org/global-directory/-/global-directory-4.0.1.tgz" @@ -3512,11 +3545,6 @@ hasown@^2.0.2: dependencies: function-bind "^1.1.2" -helmet@^8.1.0: - version "8.1.0" - resolved "https://registry.npmjs.org/helmet/-/helmet-8.1.0.tgz" - integrity sha512-jOiHyAZsmnr8LqoPGmCjYAaiuWwjAPLgY8ZX2XrmHawt99/u1y6RgrZMTeoPfpUbV96HOalYgz1qzkRbw54Pmg== - html-escaper@^2.0.0: version "2.0.2" resolved "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz" @@ -3599,7 +3627,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.4, inherits@2: +inherits@2, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.4: version "2.0.4" resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -3609,21 +3637,6 @@ ini@4.1.1: resolved "https://registry.npmjs.org/ini/-/ini-4.1.1.tgz" integrity sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g== -ioredis@^5.0.4, ioredis@^5.9.2: - version "5.9.2" - resolved "https://registry.npmjs.org/ioredis/-/ioredis-5.9.2.tgz" - integrity sha512-tAAg/72/VxOUW7RQSX1pIxJVucYKcjFjfvj60L57jrZpYCHC3XN0WCQ3sNYL4Gmvv+7GPvTAjc+KSdeNuE8oWQ== - dependencies: - "@ioredis/commands" "1.5.0" - cluster-key-slot "^1.1.0" - debug "^4.3.4" - denque "^2.1.0" - lodash.defaults "^4.2.0" - lodash.isarguments "^3.1.0" - redis-errors "^1.2.0" - redis-parser "^3.0.0" - standard-as-callback "^2.1.0" - ip-address@10.0.1: version "10.0.1" resolved "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz" @@ -3997,7 +4010,7 @@ jest-resolve-dependencies@30.2.0: jest-regex-util "30.0.1" jest-snapshot "30.2.0" -jest-resolve@*, jest-resolve@30.2.0: +jest-resolve@30.2.0: version "30.2.0" resolved "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.2.0.tgz" integrity sha512-TCrHSxPlx3tBY3hWNtRQKbtgLhsXa1WmbJEqBlTBrGafd5fiQFByy2GNCEoGR+Tns8d15GaL9cxEzKOO3GEb2A== @@ -4094,7 +4107,7 @@ jest-snapshot@30.2.0: semver "^7.7.2" synckit "^0.11.8" -"jest-util@^29.0.0 || ^30.0.0", jest-util@30.2.0: +jest-util@30.2.0: version "30.2.0" resolved "https://registry.npmjs.org/jest-util/-/jest-util-30.2.0.tgz" integrity sha512-QKNsM0o3Xe6ISQU869e+DhG+4CK/48aHYdJZGlFQVTjnbvgpcKyxpzk29fGiO7i/J8VENZ+d2iGnSsvmuHywlA== @@ -4132,15 +4145,6 @@ jest-watcher@30.2.0: jest-util "30.2.0" string-length "^4.0.2" -jest-worker@^27.4.5: - version "27.5.1" - resolved "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz" - integrity sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg== - dependencies: - "@types/node" "*" - merge-stream "^2.0.0" - supports-color "^8.0.0" - jest-worker@30.2.0: version "30.2.0" resolved "https://registry.npmjs.org/jest-worker/-/jest-worker-30.2.0.tgz" @@ -4152,7 +4156,16 @@ jest-worker@30.2.0: merge-stream "^2.0.0" supports-color "^8.1.1" -"jest@^29.0.0 || ^30.0.0", jest@^30.0.0: +jest-worker@^27.4.5: + version "27.5.1" + resolved "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz" + integrity sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg== + dependencies: + "@types/node" "*" + merge-stream "^2.0.0" + supports-color "^8.0.0" + +jest@^30.0.0: version "30.2.0" resolved "https://registry.npmjs.org/jest/-/jest-30.2.0.tgz" integrity sha512-F26gjC0yWN8uAA5m5Ss8ZQf5nDHWGlN/xWZIh8S5SRbsEKBovwZhxGd6LJlbZYxBgCYOtreSUyb8hpXyGC5O4A== @@ -4162,7 +4175,7 @@ jest-worker@30.2.0: import-local "^3.2.0" jest-cli "30.2.0" -jiti@*, jiti@^2.6.1: +jiti@^2.6.1: version "2.6.1" resolved "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz" integrity sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ== @@ -4172,6 +4185,13 @@ js-tokens@^4.0.0: resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== +js-yaml@4.1.1, js-yaml@^4.1.0, js-yaml@^4.1.1: + version "4.1.1" + resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz" + integrity sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA== + dependencies: + argparse "^2.0.1" + js-yaml@^3.13.1: version "3.14.2" resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz" @@ -4180,13 +4200,6 @@ js-yaml@^3.13.1: argparse "^1.0.7" esprima "^4.0.0" -js-yaml@^4.1.0, js-yaml@^4.1.1, js-yaml@4.1.1: - version "4.1.1" - resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz" - integrity sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA== - dependencies: - argparse "^2.0.1" - jsesc@^3.0.2: version "3.1.0" resolved "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz" @@ -4241,14 +4254,6 @@ jsonparse@^1.2.0: resolved "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz" integrity sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg== -JSONStream@^1.3.5: - version "1.3.5" - resolved "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz" - integrity sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ== - dependencies: - jsonparse "^1.2.0" - through ">=2.2.7 <3" - keyv@^4.5.4: version "4.5.4" resolved "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz" @@ -4352,16 +4357,6 @@ lodash.camelcase@^4.3.0: resolved "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz" integrity sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA== -lodash.defaults@^4.2.0: - version "4.2.0" - resolved "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz" - integrity sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ== - -lodash.isarguments@^3.1.0: - version "3.1.0" - resolved "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz" - integrity sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg== - lodash.isplainobject@^4.0.6: version "4.0.6" resolved "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz" @@ -4407,7 +4402,7 @@ lodash.upperfirst@^4.3.1: resolved "https://registry.npmjs.org/lodash.upperfirst/-/lodash.upperfirst-4.3.1.tgz" integrity sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg== -lodash@^4.17.21, lodash@4.17.21: +lodash@4.17.21, lodash@^4.17.21: version "4.17.21" resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -4455,11 +4450,6 @@ lru-cache@^5.1.1: dependencies: yallist "^3.0.2" -luxon@^3.7.2: - version "3.7.2" - resolved "https://registry.npmjs.org/luxon/-/luxon-3.7.2.tgz" - integrity sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew== - magic-string@0.30.17: version "0.30.17" resolved "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz" @@ -4491,16 +4481,16 @@ math-intrinsics@^1.1.0: resolved "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz" integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== -media-typer@^1.1.0: - version "1.1.0" - resolved "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz" - integrity sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw== - media-typer@0.3.0: version "0.3.0" resolved "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz" integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== +media-typer@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz" + integrity sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw== + memfs@^3.4.1: version "3.5.3" resolved "https://registry.npmjs.org/memfs/-/memfs-3.5.3.tgz" @@ -4541,24 +4531,17 @@ micromatch@^4.0.0, micromatch@^4.0.8: braces "^3.0.3" picomatch "^2.3.1" -mime-db@^1.54.0: - version "1.54.0" - resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz" - integrity sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ== - mime-db@1.52.0: version "1.52.0" resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz" integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== -mime-types@^2.1.12: - version "2.1.35" - resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz" - integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== - dependencies: - mime-db "1.52.0" +mime-db@^1.54.0: + version "1.54.0" + resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz" + integrity sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ== -mime-types@^2.1.27: +mime-types@^2.1.12, mime-types@^2.1.27, mime-types@~2.1.24: version "2.1.35" resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz" integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== @@ -4572,13 +4555,6 @@ mime-types@^3.0.0, mime-types@^3.0.2: dependencies: mime-db "^1.54.0" -mime-types@~2.1.24: - version "2.1.35" - resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz" - integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== - dependencies: - mime-db "1.52.0" - mime@2.6.0: version "2.6.0" resolved "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz" @@ -4608,14 +4584,7 @@ minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2: dependencies: brace-expansion "^1.1.7" -minimatch@^9.0.4: - version "9.0.5" - resolved "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz" - integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== - dependencies: - brace-expansion "^2.0.1" - -minimatch@^9.0.5: +minimatch@^9.0.4, minimatch@^9.0.5: version "9.0.5" resolved "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz" integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== @@ -4649,7 +4618,7 @@ ms@^2.1.3: resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== -multer@^2.0.2, multer@2.0.2: +multer@2.0.2, multer@^2.0.2: version "2.0.2" resolved "https://registry.npmjs.org/multer/-/multer-2.0.2.tgz" integrity sha512-u7f2xaZ/UG8oLXHvtF/oWTRvT44p9ecwBBqTwgJVq0+4BW1g8OW01TyMEGWBHbyMOYVHXslaut7qEQ1meATXgw== @@ -4922,7 +4891,7 @@ path-scurry@^2.0.0: lru-cache "^11.0.0" minipass "^7.1.2" -path-to-regexp@^8.0.0, path-to-regexp@8.3.0: +path-to-regexp@8.3.0, path-to-regexp@^8.0.0: version "8.3.0" resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz" integrity sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA== @@ -4968,7 +4937,7 @@ pg-types@2.2.0: postgres-date "~1.0.4" postgres-interval "^1.1.0" -pg@^8.17.2, pg@^8.5.1, pg@>=8.0: +pg@^8.17.2: version "8.17.2" resolved "https://registry.npmjs.org/pg/-/pg-8.17.2.tgz" integrity sha512-vjbKdiBJRqzcYw1fNU5KuHyYvdJ1qpcQg1CeBrHFqV1pWgHeVR6j/+kX0E1AAXfyuLUGY1ICrN2ELKA/z2HWzw== @@ -4993,26 +4962,16 @@ picocolors@^1.1.1: resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz" integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== -picomatch@^2.0.4: - version "2.3.1" - resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz" - integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== - -picomatch@^2.2.1: - version "2.3.1" - resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz" - integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== +picomatch@4.0.2, picomatch@^4.0.2: + version "4.0.2" + resolved "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz" + integrity sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg== -picomatch@^2.3.1: +picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1: version "2.3.1" resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== -"picomatch@^3 || ^4", picomatch@^4.0.2, picomatch@4.0.2: - version "4.0.2" - resolved "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz" - integrity sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg== - picomatch@^4.0.3: version "4.0.3" resolved "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz" @@ -5079,12 +5038,12 @@ prettier-linter-helpers@^1.0.1: dependencies: fast-diff "^1.1.2" -prettier@^3.4.2, prettier@>=3.0.0: +prettier@^3.4.2: version "3.8.1" resolved "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz" integrity sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg== -pretty-format@^30.0.0, pretty-format@30.2.0: +pretty-format@30.2.0, pretty-format@^30.0.0: version "30.2.0" resolved "https://registry.npmjs.org/pretty-format/-/pretty-format-30.2.0.tgz" integrity sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA== @@ -5179,19 +5138,7 @@ readdirp@~3.6.0: dependencies: picomatch "^2.2.1" -redis-errors@^1.0.0, redis-errors@^1.2.0: - version "1.2.0" - resolved "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz" - integrity sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w== - -redis-parser@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz" - integrity sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A== - dependencies: - redis-errors "^1.0.0" - -"reflect-metadata@^0.1.12 || ^0.2.0", "reflect-metadata@^0.1.13 || ^0.2.0", reflect-metadata@^0.2.2: +reflect-metadata@^0.2.2: version "0.2.2" resolved "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz" integrity sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q== @@ -5283,13 +5230,6 @@ run-parallel@^1.1.9: dependencies: queue-microtask "^1.2.2" -rxjs@^7.1.0, rxjs@^7.2.0, rxjs@^7.8.1: - version "7.8.2" - resolved "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz" - integrity sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA== - dependencies: - tslib "^2.1.0" - rxjs@7.8.1: version "7.8.1" resolved "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz" @@ -5297,6 +5237,13 @@ rxjs@7.8.1: dependencies: tslib "^2.1.0" +rxjs@^7.8.1: + version "7.8.2" + resolved "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz" + integrity sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA== + dependencies: + tslib "^2.1.0" + safe-buffer@^5.1.0, safe-buffer@^5.2.1, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz" @@ -5316,17 +5263,7 @@ schema-utils@^3.1.1: ajv "^6.12.5" ajv-keywords "^3.5.2" -schema-utils@^4.3.0: - version "4.3.3" - resolved "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz" - integrity sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA== - dependencies: - "@types/json-schema" "^7.0.9" - ajv "^8.9.0" - ajv-formats "^2.1.1" - ajv-keywords "^5.1.0" - -schema-utils@^4.3.3: +schema-utils@^4.3.0, schema-utils@^4.3.3: version "4.3.3" resolved "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz" integrity sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA== @@ -5458,12 +5395,7 @@ side-channel@^1.1.0: side-channel-map "^1.0.1" side-channel-weakmap "^1.0.2" -signal-exit@^3.0.2: - version "3.0.7" - resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz" - integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== - -signal-exit@^3.0.3: +signal-exit@^3.0.2, signal-exit@^3.0.3: version "3.0.7" resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== @@ -5491,14 +5423,6 @@ smob@^1.4.0, smob@^1.5.0: resolved "https://registry.npmjs.org/smob/-/smob-1.5.0.tgz" integrity sha512-g6T+p7QO8npa+/hNx9ohv1E5pVCmWrVCUzUXJyLdMmftX6ER0oiWY/w9knEonLpnOp6b6FenKnMfR8gqwWdwig== -source-map-support@^0.5.12, source-map-support@^0.5.21, source-map-support@~0.5.20: - version "0.5.21" - resolved "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz" - integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== - dependencies: - buffer-from "^1.0.0" - source-map "^0.6.0" - source-map-support@0.5.13: version "0.5.13" resolved "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz" @@ -5507,21 +5431,24 @@ source-map-support@0.5.13: buffer-from "^1.0.0" source-map "^0.6.0" -source-map@^0.6.0: - version "0.6.1" - resolved "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz" - integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== - -source-map@^0.6.1: - version "0.6.1" - resolved "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz" - integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== +source-map-support@^0.5.12, source-map-support@^0.5.21, source-map-support@~0.5.20: + version "0.5.21" + resolved "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz" + integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" -source-map@^0.7.4, source-map@0.7.4: +source-map@0.7.4, source-map@^0.7.4: version "0.7.4" resolved "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz" integrity sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA== +source-map@^0.6.0, source-map@^0.6.1: + version "0.6.1" + resolved "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + split2@^4.0.0, split2@^4.1.0: version "4.2.0" resolved "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz" @@ -5544,11 +5471,6 @@ stack-utils@^2.0.6: dependencies: escape-string-regexp "^2.0.0" -standard-as-callback@^2.1.0: - version "2.1.0" - resolved "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz" - integrity sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A== - statuses@^2.0.1, statuses@^2.0.2, statuses@~2.0.2: version "2.0.2" resolved "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz" @@ -5564,13 +5486,6 @@ streamsearch@^1.1.0: resolved "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz" integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg== -string_decoder@^1.1.1: - version "1.3.0" - resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz" - integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== - dependencies: - safe-buffer "~5.2.0" - string-argv@^0.3.2: version "0.3.2" resolved "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz" @@ -5628,6 +5543,13 @@ string-width@^8.0.0: get-east-asian-width "^1.3.0" strip-ansi "^7.1.0" +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + "strip-ansi-cjs@npm:strip-ansi@^6.0.1": version "6.0.1" resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" @@ -5712,14 +5634,7 @@ supports-color@^7.1.0: dependencies: has-flag "^4.0.0" -supports-color@^8.0.0: - version "8.1.1" - resolved "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz" - integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== - dependencies: - has-flag "^4.0.0" - -supports-color@^8.1.1: +supports-color@^8.0.0, supports-color@^8.1.1: version "8.1.1" resolved "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz" integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== @@ -5731,7 +5646,7 @@ supports-preserve-symlinks-flag@^1.0.0: resolved "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== -swagger-ui-dist@>=5.0.0, swagger-ui-dist@5.31.0: +swagger-ui-dist@5.31.0, swagger-ui-dist@>=5.0.0: version "5.31.0" resolved "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.31.0.tgz" integrity sha512-zSUTIck02fSga6rc0RZP3b7J7wgHXwLea8ZjgLA3Vgnb8QeOl3Wou2/j5QkzSGeoz6HusP/coYuJl33aQxQZpg== @@ -5902,7 +5817,7 @@ ts-node-dev@^2.0.0: ts-node "^10.4.0" tsconfig "^7.0.0" -ts-node@^10.4.0, ts-node@^10.7.0, ts-node@^10.9.2, ts-node@>=9.0.0: +ts-node@^10.4.0, ts-node@^10.9.2: version "10.9.2" resolved "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz" integrity sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ== @@ -5931,7 +5846,7 @@ tsconfig-paths-webpack-plugin@4.2.0: tapable "^2.2.1" tsconfig-paths "^4.1.2" -tsconfig-paths@^4.1.2, tsconfig-paths@^4.2.0, tsconfig-paths@4.2.0: +tsconfig-paths@4.2.0, tsconfig-paths@^4.1.2, tsconfig-paths@^4.2.0: version "4.2.0" resolved "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz" integrity sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg== @@ -5950,7 +5865,7 @@ tsconfig@^7.0.0: strip-bom "^3.0.0" strip-json-comments "^2.0.0" -tslib@^2.0.3, tslib@^2.1.0, tslib@^2.8.1, tslib@2.8.1: +tslib@2.8.1, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.4.0, tslib@^2.8.1: version "2.8.1" resolved "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz" integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== @@ -6022,7 +5937,7 @@ typeorm-extension@^3.7.1: smob "^1.5.0" yargs "^18.0.0" -typeorm@^0.3.0, typeorm@^0.3.28, typeorm@~0.3.0: +typeorm@^0.3.28: version "0.3.28" resolved "https://registry.npmjs.org/typeorm/-/typeorm-0.3.28.tgz" integrity sha512-6GH7wXhtfq2D33ZuRXYwIsl/qM5685WZcODZb7noOOcRMteM9KF2x2ap3H0EBjnSV0VO4gNAfJT5Ukp0PkOlvg== @@ -6053,7 +5968,7 @@ typescript-eslint@^8.20.0: "@typescript-eslint/typescript-estree" "8.53.1" "@typescript-eslint/utils" "8.53.1" -typescript@*, typescript@^5.7.3, typescript@>=2.7, "typescript@>=4.3 <6", typescript@>=4.8.2, typescript@>=4.8.4, "typescript@>=4.8.4 <6.0.0", typescript@>=4.9.5, typescript@>=5, typescript@>3.6.0, typescript@5.9.3: +typescript@5.9.3, typescript@^5.7.3: version "5.9.3" resolved "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz" integrity sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw== @@ -6203,7 +6118,7 @@ webpack-sources@^3.3.3: resolved "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.3.tgz" integrity sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg== -webpack@^5.0.0, webpack@^5.1.0, webpack@^5.11.0, webpack@5.104.1: +webpack@5.104.1: version "5.104.1" resolved "https://registry.npmjs.org/webpack/-/webpack-5.104.1.tgz" integrity sha512-Qphch25abbMNtekmEGJmeRUhLDbe+QfiWTiqpKYkpCOWY64v9eyl+KRRLmqOFA2AvKPpc9DC6+u2n76tQLBoaA== @@ -6342,7 +6257,7 @@ yaml@^2.8.1: resolved "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz" integrity sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A== -yargs-parser@^21.1.1, yargs-parser@21.1.1: +yargs-parser@21.1.1, yargs-parser@^21.1.1: version "21.1.1" resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz" integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==