From 60ee383cfb86cce0bb8df3d83ee9d1ba203b9e5d Mon Sep 17 00:00:00 2001 From: Ari <127914863+isaAJ05@users.noreply.github.com> Date: Thu, 28 May 2026 22:34:10 -0500 Subject: [PATCH 1/4] =?UTF-8?q?[CU-86e1jnzkv]:=20feat(gateway):=20ranking?= =?UTF-8?q?=20reports=20=E2=80=94=20include=20projectCode,=20Spanish=20Exc?= =?UTF-8?q?el=20export,=20header=20styling=20and=20filters;=20fix=20ExcelJ?= =?UTF-8?q?S=20import?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ranking-report/get-ranking-report.dto.ts | 43 ++ .../src/modules/events/events.controller.ts | 337 +++++++++++++ .../src/modules/events/events.module.ts | 28 ++ .../src/modules/events/events.service.ts | 466 +++++++++++++++++- 4 files changed, 871 insertions(+), 3 deletions(-) create mode 100644 apps/gateway/src/modules/events/dto/ranking-report/get-ranking-report.dto.ts diff --git a/apps/gateway/src/modules/events/dto/ranking-report/get-ranking-report.dto.ts b/apps/gateway/src/modules/events/dto/ranking-report/get-ranking-report.dto.ts new file mode 100644 index 0000000..e919b9c --- /dev/null +++ b/apps/gateway/src/modules/events/dto/ranking-report/get-ranking-report.dto.ts @@ -0,0 +1,43 @@ +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { Transform, Type } from 'class-transformer'; +import { IsEnum, IsInt, IsOptional, IsPositive } from 'class-validator'; + +/** + * Supported output formats for ranking report generation. + * - json: API payload for UI and integrations. + * - excel: downloadable XLSX document. + */ +export enum RankingReportFormat { + JSON = 'json', + EXCEL = 'excel', +} + +/** + * Query contract used by admin and public ranking report endpoints. + * + * Notes: + * - categoryId is optional and narrows the report to a single category. + * - category_id is accepted as an alias for backward compatibility. + * - format defaults to JSON when omitted. + */ +export class GetRankingReportDto { + @ApiPropertyOptional({ + description: 'Filter ranking by category (course/degree) id', + example: 3, + }) + @IsOptional() + @Transform(({ value, obj }) => value ?? obj?.category_id) + @Type(() => Number) + @IsInt() + @IsPositive() + categoryId?: number; + + @ApiPropertyOptional({ + description: 'Output format for ranking report', + enum: RankingReportFormat, + example: RankingReportFormat.JSON, + }) + @IsOptional() + @IsEnum(RankingReportFormat) + format?: RankingReportFormat = RankingReportFormat.JSON; +} \ No newline at end of file diff --git a/apps/gateway/src/modules/events/events.controller.ts b/apps/gateway/src/modules/events/events.controller.ts index cbb4c86..567f8ba 100644 --- a/apps/gateway/src/modules/events/events.controller.ts +++ b/apps/gateway/src/modules/events/events.controller.ts @@ -9,11 +9,13 @@ import { Query, ParseIntPipe, ForbiddenException, + Res, } from '@nestjs/common'; import { ApiBearerAuth, ApiOperation, ApiParam, + ApiQuery, ApiResponse, ApiTags, ApiSecurity, @@ -61,6 +63,12 @@ import { } from './dto/event-catalog.dto'; import { CreateRankingEventDTO } from './dto/ranking-event/create-ranking-event.dto'; import { UpdateRankingEventDTO } from './dto/ranking-event/update-ranking-event.dto'; +import { + GetRankingReportDto, + RankingReportFormat, +} from './dto/ranking-report/get-ranking-report.dto'; +import { Response } from 'express'; +import { RankingReportResult } from './events.service'; @ApiTags('events') @ApiSecurity('JWT-auth') @@ -68,6 +76,27 @@ import { UpdateRankingEventDTO } from './dto/ranking-event/update-ranking-event. export class EventsController { constructor(private readonly eventsService: EventService) {} + private normalizeFilePart(value: string): string { + const normalized = value + .normalize('NFD') + .replace(/[\u0300-\u036f]/g, '') + .toLowerCase() + .replace(/[^a-z0-9]+/g, '_') + .replace(/^_+|_+$/g, ''); + + return normalized || 'general'; + } + + private buildRankingFileName( + report: RankingReportResult, + scope: 'admin' | 'public', + ): string { + const categoryName = + report.items[0]?.category || (report.categoryId ? `categoria_${report.categoryId}` : 'general'); + + return `ranking_${this.normalizeFilePart(categoryName)}_${this.normalizeFilePart(report.eventName)}_${scope}.xlsx`; + } + // ===================== // EVENT LISTING ENDPOINTS // ===================== @@ -170,6 +199,24 @@ export class EventsController { status: 401, description: 'Unauthorized - Authentication required', }) + @ApiResponse({ + status: 400, + description: 'Validation error - query parameters are out of range or malformed', + content: { + 'application/json': { + examples: { + limitTooHigh: { + summary: 'limit exceeds the allowed maximum', + value: { + statusCode: 400, + message: 'Validation failed: limit: limit must not be greater than 50', + timestamp: '2026-05-29T00:18:18.737Z', + }, + }, + }, + }, + }, + }) async listMyEvents( @GetUser('id') userId: number, @Query() query: ListMyEventsDTO, @@ -774,6 +821,296 @@ export class EventsController { return this.eventsService.deleteRankingEvent(id); } + @RequirePermission('manage:events') + @Get(':eventId/rankings') + @ApiOperation({ + summary: 'Get full ranking report (admin)', + description: + 'Returns full ranking report for an event with optional category filter in json or excel format.', + }) + @ApiParam({ name: 'eventId', description: 'Event ID', type: Number }) + @ApiQuery({ + name: 'categoryId', + required: false, + type: Number, + description: 'Optional category (degree/course) filter', + example: 3, + }) + @ApiQuery({ + name: 'category_id', + required: false, + type: Number, + description: 'Alias for categoryId', + example: 3, + }) + @ApiQuery({ + name: 'format', + required: false, + enum: RankingReportFormat, + description: 'Response format. Use excel for downloadable XLSX file', + example: RankingReportFormat.JSON, + }) + @ApiResponse({ + status: 200, + description: 'Ranking report generated (JSON)', + content: { + 'application/json': { + examples: { + adminRankingJson: { + summary: 'Admin JSON response', + value: { + eventId: 10, + eventName: 'Hackathon 2026', + scope: 'admin', + categoryId: 3, + configuration: { + visiblePublic: true, + positions: 10, + gradeVisible: true, + }, + items: [ + { + projectId: 42, + projectName: 'EcoVision', + participantNames: ['Ana Perez', 'Luis Ramos'], + categoryId: 3, + category: 'Ingenieria de Sistemas', + position: 1, + individualGrades: [19.2, 18.7, 19.8], + averageGrade: 19.23, + }, + { + projectId: 51, + projectName: 'CivicFlow', + participantNames: ['Maria Soto', 'Diego Leon'], + categoryId: 3, + category: 'Ingenieria de Sistemas', + position: 2, + individualGrades: [18.4, 18.8, 18.5], + averageGrade: 18.57, + }, + ], + }, + }, + }, + }, + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': { + schema: { + type: 'string', + format: 'binary', + }, + }, + }, + headers: { + 'Content-Disposition': { + description: + 'Present when format=excel. Example: attachment; filename="ranking-event-10-admin.xlsx"', + schema: { + type: 'string', + }, + }, + }, + }) + /** + * Admin ranking report endpoint. + * + * Behavior: + * - Requires admin-level permission. + * - Returns full ranking rows without public visibility restrictions. + * - Supports JSON payload or downloadable XLSX output. + */ + async getAdminRankingReport( + @Param('eventId', ParseIntPipe) eventId: number, + @Query() query: GetRankingReportDto, + @Res() response: Response, + ) { + const report = await this.eventsService.generateRankingReport( + eventId, + query, + 'admin', + ); + + if (query.format === RankingReportFormat.EXCEL) { + const buffer = await this.eventsService.generateRankingExcelBuffer(report); + const fileName = this.buildRankingFileName(report, 'admin'); + + response.setHeader( + 'Content-Type', + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + ); + response.setHeader( + 'Content-Disposition', + `attachment; filename="${fileName}"`, + ); + response.send(buffer); + return; + } + + response.json(report); + } + + @Public() + @Get(':eventId/rankings/public') + @ApiOperation({ + summary: 'Get public ranking report', + description: + 'Returns public ranking report, respecting ranking visibility, positions limit, and grade visibility in json or excel format.', + }) + @ApiParam({ name: 'eventId', description: 'Event ID', type: Number }) + @ApiQuery({ + name: 'categoryId', + required: false, + type: Number, + description: 'Optional category (degree/course) filter', + example: 3, + }) + @ApiQuery({ + name: 'category_id', + required: false, + type: Number, + description: 'Alias for categoryId', + example: 3, + }) + @ApiQuery({ + name: 'format', + required: false, + enum: RankingReportFormat, + description: 'Response format. Use excel for downloadable XLSX file', + example: RankingReportFormat.JSON, + }) + @ApiResponse({ + status: 200, + description: 'Public ranking report generated (JSON or Excel)', + content: { + 'application/json': { + examples: { + publicRankingJsonVisibleGrades: { + summary: 'Public JSON (grades visible)', + value: { + eventId: 10, + eventName: 'Hackathon 2026', + scope: 'public', + categoryId: 3, + configuration: { + visiblePublic: true, + positions: 3, + gradeVisible: true, + }, + items: [ + { + projectId: 42, + projectName: 'EcoVision', + participantNames: ['Ana Perez', 'Luis Ramos'], + categoryId: 3, + category: 'Ingenieria de Sistemas', + position: 1, + individualGrades: [19.2, 18.7, 19.8], + averageGrade: 19.23, + }, + { + projectId: 51, + projectName: 'CivicFlow', + participantNames: ['Maria Soto', 'Diego Leon'], + categoryId: 3, + category: 'Ingenieria de Sistemas', + position: 2, + individualGrades: [18.4, 18.8, 18.5], + averageGrade: 18.57, + }, + ], + }, + }, + publicRankingJsonHiddenGrades: { + summary: 'Public JSON (grades hidden)', + value: { + eventId: 10, + eventName: 'Hackathon 2026', + scope: 'public', + categoryId: 3, + configuration: { + visiblePublic: true, + positions: 3, + gradeVisible: false, + }, + items: [ + { + projectId: 42, + projectName: 'EcoVision', + participantNames: ['Ana Perez', 'Luis Ramos'], + categoryId: 3, + category: 'Ingenieria de Sistemas', + position: 1, + }, + { + projectId: 51, + projectName: 'CivicFlow', + participantNames: ['Maria Soto', 'Diego Leon'], + categoryId: 3, + category: 'Ingenieria de Sistemas', + position: 2, + }, + ], + }, + }, + }, + }, + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': { + schema: { + type: 'string', + format: 'binary', + }, + }, + }, + headers: { + 'Content-Disposition': { + description: + 'Present when format=excel. Example: attachment; filename="ranking-event-10-public.xlsx"', + schema: { + type: 'string', + }, + }, + }, + }) + @ApiResponse({ status: 403, description: 'Public ranking is not enabled' }) + /** + * Public ranking report endpoint. + * + * Behavior: + * - Anonymous access is allowed. + * - Enforces RankingConfiguration visibility rules. + * - Applies position limits and grade visibility before responding. + * - Supports JSON payload or downloadable XLSX output. + */ + async getPublicRankingReport( + @Param('eventId', ParseIntPipe) eventId: number, + @Query() query: GetRankingReportDto, + @Res() response: Response, + ) { + const report = await this.eventsService.generateRankingReport( + eventId, + query, + 'public', + ); + + if (query.format === RankingReportFormat.EXCEL) { + const buffer = await this.eventsService.generateRankingExcelBuffer(report); + const fileName = this.buildRankingFileName(report, 'public'); + + response.setHeader( + 'Content-Type', + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + ); + response.setHeader( + 'Content-Disposition', + `attachment; filename="${fileName}"`, + ); + response.send(buffer); + return; + } + + response.json(report); + } + @Get(':id/my-project') @ApiOperation({ summary: 'Get my project for event' }) @ApiResponse({ diff --git a/apps/gateway/src/modules/events/events.module.ts b/apps/gateway/src/modules/events/events.module.ts index e3821f3..20325d9 100644 --- a/apps/gateway/src/modules/events/events.module.ts +++ b/apps/gateway/src/modules/events/events.module.ts @@ -17,11 +17,23 @@ import { INVITATION_SERVICE_NAME, protobufPackage as invitationProtobufPackage, } from '@app/common/generated/invitation'; +import { + EVALUATION_SERVICE_NAME, + protobufPackage as evaluationProtobufPackage, +} from '@app/common/generated/evaluation'; import { FetchConfirmedJurorMembersUseCase } from './use-cases/fetch-confirmed-juror-members.use-case'; import { FetchJurorUsersUseCase } from './use-cases/fetch-juror-users.use-case'; import { FetchJurorAssignedProjectsUseCase } from './use-cases/fetch-juror-assigned-projects.use-case'; import { ListConfirmedJurorsByEventUseCase } from './use-cases/list-confirmed-jurors-by-event.use-case'; +/** + * Events module composition. + * + * Ranking report support requires: + * - event-service for event metadata and ranking configuration. + * - evaluation-service for project stats and tie-break records. + * - project-service (via ProjectsModule) for project + participant data. + */ @Module({ imports: [ ClientsModule.registerAsync([ @@ -70,6 +82,22 @@ import { ListConfirmedJurorsByEventUseCase } from './use-cases/list-confirmed-ju }), inject: [ConfigService], }, + { + name: EVALUATION_SERVICE_NAME, + imports: [ConfigModule], + useFactory: (configService: ConfigService) => ({ + transport: Transport.GRPC, + options: { + package: evaluationProtobufPackage, + protoPath: join( + process.cwd(), + 'libs/common/src/protos/evaluation.proto', + ), + url: configService.get('EVALUATION_SERVICE_URL'), + }, + }), + inject: [ConfigService], + }, ]), ProjectsModule, ], diff --git a/apps/gateway/src/modules/events/events.service.ts b/apps/gateway/src/modules/events/events.service.ts index a007d77..30599bb 100644 --- a/apps/gateway/src/modules/events/events.service.ts +++ b/apps/gateway/src/modules/events/events.service.ts @@ -1,11 +1,13 @@ import { BadRequestException, + ForbiddenException, Injectable, Inject, OnModuleInit, } from '@nestjs/common'; import { ClientGrpc } from '@nestjs/microservices'; import { firstValueFrom } from 'rxjs'; +import * as ExcelJS from 'exceljs'; import { CreateEventDTO } from './dto/events/create-event.dto'; import { DeleteEventDTO } from './dto/events/delete-event.dto'; import { GetEventDTO } from './dto/events/get-event.dto'; @@ -139,17 +141,73 @@ import { } from '@app/common/generated/event'; import { CreateRankingEventDTO } from './dto/ranking-event/create-ranking-event.dto'; import { UpdateRankingEventDTO } from './dto/ranking-event/update-ranking-event.dto'; +import { + EVALUATION_SERVICE_NAME, + EvaluationServiceClient, + TIE_BREAK_SERVICE_NAME, + TieBreakServiceClient, +} from '@app/common/generated/evaluation'; +import { ProjectComplete } from '@app/common/generated/project'; +import { + GetRankingReportDto, + RankingReportFormat, +} from './dto/ranking-report/get-ranking-report.dto'; + +type RankingScope = 'admin' | 'public'; + +/** + * Internal normalized row used while building and sorting ranking data. + */ +interface RankingReportRow { + projectId: number; + projectName: string; + projectCode?: string; + participantNames: string[]; + categoryId: number; + category: string; + position: number; + individualGrades: number[]; + averageGrade: number; + tieBreakOrder?: number; +} + +export interface RankingReportResult { + eventId: number; + eventName: string; + scope: RankingScope; + categoryId?: number; + configuration: { + visiblePublic: boolean; + positions: number; + gradeVisible: boolean; + }; + items: Array<{ + projectId: number; + projectCode?: string; + projectName: string; + participantNames: string[]; + categoryId: number; + category: string; + position: number; + individualGrades?: number[]; + averageGrade?: number; + }>; +} @Injectable() export class EventService implements OnModuleInit { private eventService!: EventServiceClient; private authService!: AuthServiceClient; + private evaluationService!: EvaluationServiceClient; + private tieBreakService!: TieBreakServiceClient; private statusCache: EventStatusMapping[] | null = null; private rolesCache: any[] | null = null; constructor( @Inject(EVENT_SERVICE_NAME) private readonly eventClient: ClientGrpc, @Inject(AUTH_SERVICE_NAME) private readonly authClient: ClientGrpc, + @Inject(EVALUATION_SERVICE_NAME) + private readonly evaluationClient: ClientGrpc, private readonly projectsService: ProjectsService, private readonly listConfirmedJurorsByEventUseCase: ListConfirmedJurorsByEventUseCase, ) {} @@ -159,6 +217,408 @@ export class EventService implements OnModuleInit { this.eventClient.getService(EVENT_SERVICE_NAME); this.authService = this.authClient.getService(AUTH_SERVICE_NAME); + this.evaluationService = + this.evaluationClient.getService( + EVALUATION_SERVICE_NAME, + ); + this.tieBreakService = + this.evaluationClient.getService( + TIE_BREAK_SERVICE_NAME, + ); + } + + /** + * Normalizes category labels used in ranking outputs. + */ + private normalizeCategoryName(name?: string): string { + if (!name || !name.trim()) { + return 'Sin categoria'; + } + return name.trim(); + } + + /** + * Produces a unified participant list for a project, including both + * confirmed and pending members. + */ + private getProjectParticipantNames(project: ProjectComplete): string[] { + const confirmed = (project.participants ?? []) + .map((participant) => { + const firstName = participant.firstName?.trim() ?? ''; + const lastName = participant.lastName?.trim() ?? ''; + const fullName = `${firstName} ${lastName}`.trim(); + return fullName || participant.email || ''; + }) + .filter((value) => value.length > 0); + + const pending = (project.pendingParticipants ?? []) + .map((participant) => { + const firstName = participant.firstName?.trim() ?? ''; + const lastName = participant.lastName?.trim() ?? ''; + const fullName = `${firstName} ${lastName}`.trim(); + return fullName || participant.email || ''; + }) + .filter((value) => value.length > 0); + + return [...confirmed, ...pending]; + } + + /** + * Paginates through all projects for an event (optionally by category). + */ + private async listAllProjectsByEvent( + eventId: number, + categoryId?: number, + ): Promise { + let currentPage = 1; + const itemsPerPage = 50; + let totalPages = 1; + const projects: ProjectComplete[] = []; + + do { + const response = await this.projectsService.listProjectsByEvent(eventId, { + courseId: categoryId, + currentPage, + itemsPerPage, + }); + + projects.push(...(response.items ?? [])); + totalPages = response.totalPages || 1; + currentPage += 1; + } while (currentPage <= totalPages); + + return projects; + } + + /** + * Ranking comparator rules: + * 1) Higher average first. + * 2) Tie-break order from tie-break table (lower order wins). + * 3) Stable lexical fallback by project name. + * 4) Final numeric fallback by project id. + */ + private compareRankingRows(a: RankingReportRow, b: RankingReportRow): number { + if (a.averageGrade !== b.averageGrade) { + return b.averageGrade - a.averageGrade; + } + + const tieA = a.tieBreakOrder ?? Number.POSITIVE_INFINITY; + const tieB = b.tieBreakOrder ?? Number.POSITIVE_INFINITY; + + if (tieA !== tieB) { + return tieA - tieB; + } + + const nameComparison = a.projectName.localeCompare(b.projectName, 'es', { + sensitivity: 'base', + }); + if (nameComparison !== 0) { + return nameComparison; + } + + return a.projectId - b.projectId; + } + + /** + * Fetches all evaluations created by a specific evaluator in an event. + * + * The evaluation-service enforces a max page size of 50, so this method + * paginates until all pages are consumed. + */ + private async getAllEvaluationsByEvaluator( + eventId: number, + evaluatorId: number, + ) { + const pageSize = 50; + let page = 1; + let totalPages = 1; + const evaluations: Array<{ projectId: number; grade: number }> = []; + + do { + const response = await firstValueFrom( + this.evaluationService.findEvaluationsByEvaluator({ + userId: evaluatorId, + eventId, + page, + limit: pageSize, + }), + ); + + for (const evaluation of response.evaluations ?? []) { + evaluations.push({ + projectId: evaluation.projectId, + grade: evaluation.grade, + }); + } + + totalPages = response.meta?.totalPages ?? 1; + page += 1; + } while (page <= totalPages); + + return evaluations; + } + + async generateRankingReport( + eventId: number, + query: GetRankingReportDto, + scope: RankingScope, + ): Promise { + // Format validation is kept explicit so non-dto callers are also safe. + const format = query.format ?? RankingReportFormat.JSON; + if ( + format !== RankingReportFormat.JSON && + format !== RankingReportFormat.EXCEL + ) { + throw new BadRequestException('Invalid format. Use json or excel'); + } + + const [eventResponse, rankingConfigResponse, categoriesResponse] = + await Promise.all([ + // Intentionally uses direct getEvent gRPC call to avoid extra catalog + // enrichment queries that are not needed for ranking generation. + firstValueFrom(this.eventService.getEvent({ id: eventId })), + this.getRankingEventByEventId(eventId), + this.listCategoriesByEvent(eventId, { + eventId, + page: 1, + limit: 50, + }), + ]); + + const eventName = + eventResponse.event?.name?.trim() || `Evento ${String(eventId)}`; + const rankingConfig = rankingConfigResponse.rankingEvent; + + if (!rankingConfig) { + throw new BadRequestException( + `Ranking configuration not found for event ${String(eventId)}`, + ); + } + + if (scope === 'public' && !rankingConfig.visiblePublic) { + throw new ForbiddenException( + 'Public ranking is not enabled for this event', + ); + } + + const categories = categoriesResponse.categories ?? []; + const categoryNameById = new Map( + categories.map((category) => [ + category.id, + this.normalizeCategoryName(category.name), + ]), + ); + + const projects = await this.listAllProjectsByEvent(eventId, query.categoryId); + + const tieBreakResponse = await firstValueFrom( + this.tieBreakService.listTieBreaks({ + eventId, + categoryId: query.categoryId, + }), + ); + + const tieBreakByProjectAndCategory = new Map(); + for (const tieBreak of tieBreakResponse.tiebreaks ?? []) { + tieBreakByProjectAndCategory.set( + `${String(tieBreak.projectId)}:${String(tieBreak.categoryId)}`, + tieBreak.tiebreakOrder, + ); + } + + const evaluatorProjectGrades = new Map>(); + const rows: RankingReportRow[] = []; + + // Build ranking candidates by combining project metadata + evaluation stats. + for (const project of projects) { + const stats = await firstValueFrom( + this.evaluationService.getProjectStats({ projectId: project.id }), + ); + + const individualGrades: number[] = []; + for (const evaluatorId of stats.evaluatorIds ?? []) { + if (!evaluatorProjectGrades.has(evaluatorId)) { + const evaluatorEvaluations = + await this.getAllEvaluationsByEvaluator(eventId, evaluatorId); + + const projectGrades = new Map(); + for (const evaluation of evaluatorEvaluations) { + if (!projectGrades.has(evaluation.projectId)) { + projectGrades.set(evaluation.projectId, evaluation.grade); + } + } + + evaluatorProjectGrades.set(evaluatorId, projectGrades); + } + + const grade = evaluatorProjectGrades.get(evaluatorId)?.get(project.id); + if (grade !== undefined) { + individualGrades.push(grade); + } + } + + const categoryId = project.courseId; + const categoryName = + categoryNameById.get(categoryId) || `Categoria ${String(categoryId)}`; + const tieBreakOrder = tieBreakByProjectAndCategory.get( + `${String(project.id)}:${String(categoryId)}`, + ); + + rows.push({ + projectId: project.id, + projectCode: project.projectCode, + projectName: project.name, + participantNames: this.getProjectParticipantNames(project), + categoryId, + category: categoryName, + position: 0, + individualGrades, + averageGrade: stats.averageGrade ?? 0, + tieBreakOrder, + }); + } + + // Sorting determines final ranking positions. + rows.sort((a, b) => this.compareRankingRows(a, b)); + rows.forEach((row, index) => { + row.position = index + 1; + }); + + // Public scope can expose only top-N rows depending on configuration. + let visibleRows = rows; + if (scope === 'public' && rankingConfig.positions > 0) { + visibleRows = rows.slice(0, rankingConfig.positions); + } + + // In public mode, grade fields are conditional by ranking configuration. + const showGrades = scope === 'admin' || rankingConfig.gradeVisible; + + return { + eventId, + eventName, + scope, + categoryId: query.categoryId, + configuration: { + visiblePublic: rankingConfig.visiblePublic, + positions: rankingConfig.positions, + gradeVisible: rankingConfig.gradeVisible, + }, + items: visibleRows.map((row) => ({ + projectId: row.projectId, + projectCode: row.projectCode, + projectName: row.projectName, + participantNames: row.participantNames, + categoryId: row.categoryId, + category: row.category, + position: row.position, + individualGrades: showGrades ? row.individualGrades : undefined, + averageGrade: showGrades ? row.averageGrade : undefined, + })), + }; + } + + /** + * Builds an XLSX buffer from ranking JSON data. + * The controller is responsible for setting download headers. + */ + async generateRankingExcelBuffer(report: RankingReportResult): Promise { + const workbook = new ExcelJS.Workbook(); + const worksheet = workbook.addWorksheet('Ranking'); + + worksheet.columns = [ + { header: 'Posición', key: 'position', width: 10 }, + { header: 'Código', key: 'projectCode', width: 10 }, + { header: 'Proyecto', key: 'projectName', width: 32 }, + { header: 'Categoría', key: 'category', width: 24 }, + { header: 'Participantes', key: 'participantNames', width: 48 }, + { header: 'Calificaciones', key: 'individualGrades', width: 28 }, + { header: 'Puntaje Final', key: 'averageGrade', width: 14 }, + ]; + + for (const item of report.items) { + worksheet.addRow({ + position: item.position, + projectCode: item.projectCode || '', + projectName: item.projectName, + category: item.category, + participantNames: item.participantNames.join(', '), + individualGrades: item.individualGrades?.length + ? item.individualGrades.join(', ') + : '', + averageGrade: + item.averageGrade !== undefined + ? Number(item.averageGrade.toFixed(2)) + : '', + }); + } + + worksheet.views = [{ state: 'frozen', ySplit: 1 }]; + worksheet.autoFilter = 'A1:G1'; + + const headerRow = worksheet.getRow(1); + headerRow.height = 20; + headerRow.eachCell((cell) => { + cell.font = { bold: true, color: { argb: 'FFFFFFFF' } }; + cell.fill = { + type: 'pattern', + pattern: 'solid', + fgColor: { argb: 'FF5B9BD5' }, + }; + cell.alignment = { + horizontal: 'center', + vertical: 'middle', + wrapText: true, + }; + cell.border = { + top: { style: 'thin', color: { argb: 'FF9CC2E5' } }, + left: { style: 'thin', color: { argb: 'FF9CC2E5' } }, + bottom: { style: 'thin', color: { argb: 'FF9CC2E5' } }, + right: { style: 'thin', color: { argb: 'FF9CC2E5' } }, + }; + }); + + for (let rowIndex = 2; rowIndex <= worksheet.rowCount; rowIndex += 1) { + const row = worksheet.getRow(rowIndex); + row.eachCell((cell, columnNumber) => { + cell.alignment = { + horizontal: columnNumber <= 2 ? 'center' : 'left', + vertical: 'middle', + wrapText: true, + }; + cell.border = { + top: { style: 'thin', color: { argb: 'FFD9E2F3' } }, + left: { style: 'thin', color: { argb: 'FFD9E2F3' } }, + bottom: { style: 'thin', color: { argb: 'FFD9E2F3' } }, + right: { style: 'thin', color: { argb: 'FFD9E2F3' } }, + }; + if (rowIndex % 2 === 0) { + cell.fill = { + type: 'pattern', + pattern: 'solid', + fgColor: { argb: 'FFF7FBFF' }, + }; + } + }); + } + + worksheet.getColumn(1).width = 10; + worksheet.getColumn(1).alignment = { + horizontal: 'center', + vertical: 'middle', + }; + worksheet.getColumn(2).width = 10; + worksheet.getColumn(2).alignment = { + horizontal: 'center', + vertical: 'middle', + }; + worksheet.getColumn(3).width = 32; + worksheet.getColumn(4).width = 24; + worksheet.getColumn(5).width = 48; + worksheet.getColumn(6).width = 28; + worksheet.getColumn(7).width = 14; + + const rawBuffer = await workbook.xlsx.writeBuffer(); + return Buffer.from(rawBuffer); } private mapCourseForFrontend( @@ -200,12 +660,12 @@ export class EventService implements OnModuleInit { this.listCategoriesByEvent(eventId, { eventId, page: 1, - limit: 1000, + limit: 50, }), this.listEventInscriptionDetails({ eventId, page: 1, - limit: 1000, + limit: 50, }), ]); @@ -215,7 +675,7 @@ export class EventService implements OnModuleInit { this.listCategoryAwards({ categoryId: category.id, page: 1, - limit: 1000, + limit: 50, }), ), ); From bd252a95d587ba046f573a4c48d087071ea5be24 Mon Sep 17 00:00:00 2001 From: Ari <127914863+isaAJ05@users.noreply.github.com> Date: Fri, 29 May 2026 04:38:36 -0500 Subject: [PATCH 2/4] [CU-86e1jnzkv]: add name update to endpoint --- apps/gateway/src/modules/events/events.controller.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/gateway/src/modules/events/events.controller.ts b/apps/gateway/src/modules/events/events.controller.ts index 567f8ba..677a13c 100644 --- a/apps/gateway/src/modules/events/events.controller.ts +++ b/apps/gateway/src/modules/events/events.controller.ts @@ -799,7 +799,7 @@ export class EventsController { } @RequirePermission('manage:events') - @Patch('ranking-config/:id') + @Patch('update-ranking-config/:id') @ApiOperation({ summary: 'Update ranking configuration' }) @ApiParam({ name: 'id', description: 'Ranking config ID', type: Number }) @ApiResponse({ status: 200, description: 'Ranking configuration updated' }) From f3299712df60b06025a493c4468587e72de8c15f Mon Sep 17 00:00:00 2001 From: Ari <127914863+isaAJ05@users.noreply.github.com> Date: Fri, 29 May 2026 04:59:45 -0500 Subject: [PATCH 3/4] [CU-86e1jnzkv]: excel import --- apps/gateway/src/modules/events/events.service.ts | 4 ++-- package.json | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/gateway/src/modules/events/events.service.ts b/apps/gateway/src/modules/events/events.service.ts index 30599bb..5113383 100644 --- a/apps/gateway/src/modules/events/events.service.ts +++ b/apps/gateway/src/modules/events/events.service.ts @@ -557,7 +557,7 @@ export class EventService implements OnModuleInit { const headerRow = worksheet.getRow(1); headerRow.height = 20; - headerRow.eachCell((cell) => { + headerRow.eachCell((cell: ExcelJS.Cell) => { cell.font = { bold: true, color: { argb: 'FFFFFFFF' } }; cell.fill = { type: 'pattern', @@ -579,7 +579,7 @@ export class EventService implements OnModuleInit { for (let rowIndex = 2; rowIndex <= worksheet.rowCount; rowIndex += 1) { const row = worksheet.getRow(rowIndex); - row.eachCell((cell, columnNumber) => { + row.eachCell((cell: ExcelJS.Cell, columnNumber: number) => { cell.alignment = { horizontal: columnNumber <= 2 ? 'center' : 'left', vertical: 'middle', diff --git a/package.json b/package.json index 9081467..9847463 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "class-transformer": "^0.5.1", "class-validator": "^0.14.2", "cookie-parser": "^1.4.7", + "exceljs": "^4.4.0", "express": "^5.1.0", "gray-matter": "^4.0.3", "handlebars": "^4.7.8", From a956f196c6fc8616737fe28d7620d85d2a8490de Mon Sep 17 00:00:00 2001 From: Ari <127914863+isaAJ05@users.noreply.github.com> Date: Fri, 29 May 2026 05:21:35 -0500 Subject: [PATCH 4/4] [CU-86e1jnzkv]: import excel --- pnpm-lock.yaml | 441 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 441 insertions(+) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d8ec99c..7a8d62e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -65,6 +65,9 @@ importers: cookie-parser: specifier: ^1.4.7 version: 1.4.7 + exceljs: + specifier: ^4.4.0 + version: 4.4.0 express: specifier: ^5.1.0 version: 5.1.0 @@ -607,6 +610,12 @@ packages: resolution: {integrity: sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@fast-csv/format@4.3.5': + resolution: {integrity: sha512-8iRn6QF3I8Ak78lNAa+Gdl5MJJBM5vRHivFtMRUWINdevNo00K7OXxS2PshawLKTejVwieIlPmK5YlLu6w4u8A==} + + '@fast-csv/parse@4.3.6': + resolution: {integrity: sha512-uRsLYksqpbDmWaSmzvJcuApSEe38+6NQZBUsuAyMZKqHxH0g1wcJgsKUvN3WC8tewaqFjBMMGrkHmC+T7k8LvA==} + '@grpc/grpc-js@1.14.0': resolution: {integrity: sha512-N8Jx6PaYzcTRNzirReJCtADVoq4z7+1KQ4E70jTg/koQiMoUSN1kbNjPOqpPbhMFhfU1/l7ixspPl8dNY+FoUg==} engines: {node: '>=12.10.0'} @@ -1499,6 +1508,9 @@ packages: '@types/multer@2.0.0': resolution: {integrity: sha512-C3Z9v9Evij2yST3RSBktxP9STm6OdMc5uR1xF1SGr98uv8dUlAL2hqwrZ3GVB3uyMyiegnscEK6PGtYvNrjTjw==} + '@types/node@14.18.63': + resolution: {integrity: sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==} + '@types/node@22.18.6': resolution: {integrity: sha512-r8uszLPpeIWbNKtvWRt/DbVi5zbqZyj1PTmhRMqBMvDnaz1QpmSKujUtJLrqGZeoM8v72MfYggDceY4K1itzWQ==} @@ -1797,6 +1809,18 @@ packages: arch@3.0.0: resolution: {integrity: sha512-AmIAC+Wtm2AU8lGfTtHsw0Y9Qtftx2YXEEtiBP10xFUtMOA+sHHx6OAddyL52mUKh1vsXQ6/w1mVDptZCyUt4Q==} + archiver-utils@2.1.0: + resolution: {integrity: sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==} + engines: {node: '>= 6'} + + archiver-utils@3.0.4: + resolution: {integrity: sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw==} + engines: {node: '>= 10'} + + archiver@5.3.2: + resolution: {integrity: sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==} + engines: {node: '>= 10'} + arg@4.1.3: resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} @@ -1815,6 +1839,9 @@ packages: asap@2.0.6: resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} + async@3.2.6: + resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} + asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} @@ -1871,6 +1898,10 @@ packages: resolution: {integrity: sha512-cU8v/EGSrnH+HnxV2z0J7/blxH8gq7Xh2JFT6Aroax7UohdmiJJlxApMxtKfuI7z68NvvVcmR78k2LbT6efhRg==} engines: {node: '>= 18'} + big-integer@1.6.52: + resolution: {integrity: sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==} + engines: {node: '>=0.6'} + bin-version-check@5.1.0: resolution: {integrity: sha512-bYsvMqJ8yNGILLz1KP9zKLzQ6YpljV3ln1gqhuLkUtyfGi3qXKGuK2p+U4NAvjVFzDFiBBtOpCOSFNuYYEGZ5g==} engines: {node: '>=12'} @@ -1879,9 +1910,15 @@ packages: resolution: {integrity: sha512-nk5wEsP4RiKjG+vF+uG8lFsEn4d7Y6FVDamzzftSunXOoOcOOkzcWdKVlGgFFwlUQCj63SgnUkLLGF8v7lufhw==} engines: {node: '>=12'} + binary@0.3.0: + resolution: {integrity: sha512-D4H1y5KYwpJgK8wk1Cue5LLPgmwHKYSChkbspQg5JtVuR5ulGckxfR62H3AE9UDkdMC8yyXlqYihuz3Aqg2XZg==} + bl@4.1.0: resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} + bluebird@3.4.7: + resolution: {integrity: sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==} + body-parser@2.2.0: resolution: {integrity: sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==} engines: {node: '>=18'} @@ -1917,9 +1954,17 @@ packages: buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + buffer-indexof-polyfill@1.0.2: + resolution: {integrity: sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A==} + engines: {node: '>=0.10'} + buffer@5.7.1: resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + buffers@0.1.1: + resolution: {integrity: sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ==} + engines: {node: '>=0.2.0'} + busboy@1.6.0: resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} engines: {node: '>=10.16.0'} @@ -1971,6 +2016,9 @@ packages: resolution: {integrity: sha512-zlOQ80VrQ2Ue+ymH5OuM/DlDq64mEm+B9UTdHULv5osUMD6HalNTblf2b1u/m6QecjsnOkBpqVZ+XPwIVsy7Ng==} engines: {node: '>=12.13'} + chainsaw@0.1.0: + resolution: {integrity: sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ==} + chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} @@ -2081,6 +2129,10 @@ packages: component-emitter@1.3.1: resolution: {integrity: sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==} + compress-commons@4.1.2: + resolution: {integrity: sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==} + engines: {node: '>= 10'} + concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} @@ -2174,6 +2226,15 @@ packages: typescript: optional: true + crc-32@1.2.2: + resolution: {integrity: sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==} + engines: {node: '>=0.8'} + hasBin: true + + crc32-stream@4.0.3: + resolution: {integrity: sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==} + engines: {node: '>= 10'} + create-jest@29.7.0: resolution: {integrity: sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -2186,6 +2247,9 @@ packages: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} + dayjs@1.11.21: + resolution: {integrity: sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA==} + debug@4.4.3: resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} engines: {node: '>=6.0'} @@ -2286,6 +2350,9 @@ packages: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} + duplexer2@0.1.4: + resolution: {integrity: sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==} + eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} @@ -2319,6 +2386,9 @@ packages: resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} engines: {node: '>= 0.8'} + end-of-stream@1.4.5: + resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} + enhanced-resolve@5.18.3: resolution: {integrity: sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==} engines: {node: '>=10.13.0'} @@ -2450,6 +2520,10 @@ packages: resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} engines: {node: '>=0.8.x'} + exceljs@4.4.0: + resolution: {integrity: sha512-XctvKaEMaj1Ii9oDOqbW/6e1gXknSY4g/aLCDicOXqBE4M0nRWkUu0PTp++UPNzoFY12BNHMfs/VadKIS6llvg==} + engines: {node: '>=8.3.0'} + execa@5.1.1: resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} engines: {node: '>=10'} @@ -2485,6 +2559,10 @@ packages: resolution: {integrity: sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==} engines: {node: '>=8.0.0'} + fast-csv@4.3.6: + resolution: {integrity: sha512-2RNSpuwwsJGP0frGsOmTb9oUF+VkFSM4SyLTDgwf2ciHWTarN0lQTC+F2f/t5J9QjW+c65VFIAAu85GsvMIusw==} + engines: {node: '>=10.0.0'} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -2610,6 +2688,9 @@ packages: resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} engines: {node: '>= 0.8'} + fs-constants@1.0.0: + resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} + fs-extra@10.1.0: resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} engines: {node: '>=12'} @@ -2625,6 +2706,11 @@ packages: engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] + fstream@1.0.12: + resolution: {integrity: sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==} + engines: {node: '>=0.6'} + deprecated: This package is no longer supported. + function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} @@ -2787,6 +2873,9 @@ packages: resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} engines: {node: '>= 4'} + immediate@3.0.6: + resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==} + import-fresh@3.3.1: resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} engines: {node: '>=6'} @@ -2879,6 +2968,9 @@ packages: resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} engines: {node: '>=10'} + isarray@1.0.0: + resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} + isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} @@ -3101,6 +3193,9 @@ packages: resolution: {integrity: sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==} engines: {node: '>=12', npm: '>=6'} + jszip@3.10.1: + resolution: {integrity: sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==} + jwa@1.4.2: resolution: {integrity: sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==} @@ -3122,6 +3217,10 @@ packages: resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} engines: {node: '>=6'} + lazystream@1.0.1: + resolution: {integrity: sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==} + engines: {node: '>= 0.6.3'} + leven@3.1.0: resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} engines: {node: '>=6'} @@ -3133,9 +3232,15 @@ packages: libphonenumber-js@1.12.22: resolution: {integrity: sha512-nzdkDyqlcLV754o1RrOJxh8kycG+63odJVUqnK4dxhw7buNkdTqJc/a/CE0h599dTJgFbzvr6GEOemFBSBryAA==} + lie@3.3.0: + resolution: {integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==} + lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + listenercount@1.0.1: + resolution: {integrity: sha512-3mk/Zag0+IJxeDrxSgaDPy4zZ3w05PRZeJNnlWhzFz5OkX49J4krc+A8X2d2M69vGMBEX0uyl8M+W+8gH+kBqQ==} + load-esm@1.0.2: resolution: {integrity: sha512-nVAvWk/jeyrWyXEAs84mpQCYccxRqgKY4OznLuJhJCa0XsPSfdOIr2zvBZEj3IHEHbX97jjscKRRV539bW0Gpw==} engines: {node: '>=13.2.0'} @@ -3155,15 +3260,40 @@ packages: lodash.camelcase@4.3.0: resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} + lodash.defaults@4.2.0: + resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==} + + lodash.difference@4.5.0: + resolution: {integrity: sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==} + + lodash.escaperegexp@4.1.2: + resolution: {integrity: sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==} + + lodash.flatten@4.4.0: + resolution: {integrity: sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==} + + lodash.groupby@4.6.0: + resolution: {integrity: sha512-5dcWxm23+VAoz+awKmBaiBvzox8+RqMgFhi7UvX9DHZr2HdxHXM/Wrf8cfKpsW37RNrvtPn6hSwNqurSILbmJw==} + lodash.includes@4.3.0: resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==} lodash.isboolean@3.0.3: resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==} + lodash.isequal@4.5.0: + resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==} + deprecated: This package is deprecated. Use require('node:util').isDeepStrictEqual instead. + + lodash.isfunction@3.0.9: + resolution: {integrity: sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==} + lodash.isinteger@4.0.4: resolution: {integrity: sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==} + lodash.isnil@4.0.0: + resolution: {integrity: sha512-up2Mzq3545mwVnMhTDMdfoG1OurpA/s5t88JmQX809eH3C8491iu2sfKhTfhQtKY78oPNhiaHJUpT/dUDAAtng==} + lodash.isnumber@3.0.3: resolution: {integrity: sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==} @@ -3173,6 +3303,9 @@ packages: lodash.isstring@4.0.1: resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==} + lodash.isundefined@3.0.1: + resolution: {integrity: sha512-MXB1is3s899/cD8jheYYE2V9qTHwKvt+npCwpD+1Sxm3Q3cECXCiYHjeHWXNwr6Q0SOBPrYUDxendrO6goVTEA==} + lodash.kebabcase@4.1.1: resolution: {integrity: sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g==} @@ -3194,6 +3327,12 @@ packages: lodash.startcase@4.4.0: resolution: {integrity: sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==} + lodash.union@4.6.0: + resolution: {integrity: sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==} + + lodash.uniq@4.5.0: + resolution: {integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==} + lodash.upperfirst@4.3.1: resolution: {integrity: sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg==} @@ -3314,6 +3453,10 @@ packages: minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + minimatch@5.1.9: + resolution: {integrity: sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==} + engines: {node: '>=10'} + minimatch@9.0.5: resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} engines: {node: '>=16 || 14 >=14.17'} @@ -3455,6 +3598,9 @@ packages: package-json-from-dist@1.0.1: resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + pako@1.0.11: + resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==} + parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} @@ -3576,6 +3722,9 @@ packages: typescript: optional: true + process-nextick-args@2.0.1: + resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + prompts@2.4.2: resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} engines: {node: '>= 6'} @@ -3626,10 +3775,16 @@ packages: react-is@18.3.1: resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + readable-stream@2.3.8: + resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} + readable-stream@3.6.2: resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} engines: {node: '>= 6'} + readdir-glob@1.1.3: + resolution: {integrity: sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==} + readdirp@4.1.2: resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} engines: {node: '>= 14.18.0'} @@ -3689,6 +3844,11 @@ packages: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + rimraf@2.7.1: + resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==} + deprecated: Rimraf versions prior to v4 are no longer supported + hasBin: true + router@2.2.0: resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} engines: {node: '>= 18'} @@ -3702,12 +3862,19 @@ packages: rxjs@7.8.2: resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} + safe-buffer@5.1.2: + resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} + safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + saxes@5.0.1: + resolution: {integrity: sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==} + engines: {node: '>=10'} + schema-utils@3.3.0: resolution: {integrity: sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==} engines: {node: '>= 10.13.0'} @@ -3752,6 +3919,9 @@ packages: resolution: {integrity: sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==} engines: {node: '>= 18'} + setimmediate@1.0.5: + resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==} + setprototypeof@1.2.0: resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} @@ -3853,6 +4023,9 @@ packages: resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} engines: {node: '>=12'} + string_decoder@1.1.1: + resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} + string_decoder@1.3.0: resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} @@ -3935,6 +4108,10 @@ packages: resolution: {integrity: sha512-ZL6DDuAlRlLGghwcfmSn9sK3Hr6ArtyudlSAiCqQ6IfE+b+HHbydbYDIG15IfS5do+7XQQBdBiubF/cV2dnDzg==} engines: {node: '>=6'} + tar-stream@2.2.0: + resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} + engines: {node: '>=6'} + tar-stream@3.1.7: resolution: {integrity: sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==} @@ -3972,6 +4149,10 @@ packages: tinyexec@1.0.1: resolution: {integrity: sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==} + tmp@0.2.7: + resolution: {integrity: sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw==} + engines: {node: '>=14.14'} + tmpl@1.0.5: resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} @@ -3987,6 +4168,9 @@ packages: resolution: {integrity: sha512-kh9LVIWH5CnL63Ipf0jhlBIy0UsrMj/NJDfpsy1SqOXlLKEVyXXYrnFxFT1yOOYVGBSApeVnjPw/sBz5BfEjAQ==} engines: {node: '>=14.16'} + traverse@0.3.9: + resolution: {integrity: sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ==} + tree-kill@1.2.2: resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} hasBin: true @@ -4137,6 +4321,9 @@ packages: resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} engines: {node: '>= 0.8'} + unzipper@0.10.14: + resolution: {integrity: sha512-ti4wZj+0bQTiX2KmKWuwj7lhV+2n//uXEotUmGuQqrbVZSEGFMbI68+c6JCQ8aAmUWYvtHEz2A8K6wXvueR/6g==} + update-browserslist-db@1.1.3: resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==} hasBin: true @@ -4153,6 +4340,11 @@ packages: resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} engines: {node: '>= 0.4.0'} + uuid@8.3.2: + resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} + deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028). + hasBin: true + v8-compile-cache-lib@3.0.1: resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} @@ -4227,6 +4419,9 @@ packages: resolution: {integrity: sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + xmlchars@2.2.0: + resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} + xtend@4.0.2: resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} engines: {node: '>=0.4'} @@ -4262,6 +4457,10 @@ packages: resolution: {integrity: sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==} engines: {node: '>=18'} + zip-stream@4.1.1: + resolution: {integrity: sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==} + engines: {node: '>= 10'} + snapshots: '@angular-devkit/core@19.2.15(chokidar@4.0.3)': @@ -4823,6 +5022,25 @@ snapshots: '@eslint/core': 0.15.2 levn: 0.4.1 + '@fast-csv/format@4.3.5': + dependencies: + '@types/node': 14.18.63 + lodash.escaperegexp: 4.1.2 + lodash.isboolean: 3.0.3 + lodash.isequal: 4.5.0 + lodash.isfunction: 3.0.9 + lodash.isnil: 4.0.0 + + '@fast-csv/parse@4.3.6': + dependencies: + '@types/node': 14.18.63 + lodash.escaperegexp: 4.1.2 + lodash.groupby: 4.6.0 + lodash.isfunction: 3.0.9 + lodash.isnil: 4.0.0 + lodash.isundefined: 3.0.1 + lodash.uniq: 4.5.0 + '@grpc/grpc-js@1.14.0': dependencies: '@grpc/proto-loader': 0.8.0 @@ -5760,6 +5978,8 @@ snapshots: dependencies: '@types/express': 5.0.3 + '@types/node@14.18.63': {} + '@types/node@22.18.6': dependencies: undici-types: 6.21.0 @@ -6166,6 +6386,42 @@ snapshots: arch@3.0.0: {} + archiver-utils@2.1.0: + dependencies: + glob: 7.2.3 + graceful-fs: 4.2.11 + lazystream: 1.0.1 + lodash.defaults: 4.2.0 + lodash.difference: 4.5.0 + lodash.flatten: 4.4.0 + lodash.isplainobject: 4.0.6 + lodash.union: 4.6.0 + normalize-path: 3.0.0 + readable-stream: 2.3.8 + + archiver-utils@3.0.4: + dependencies: + glob: 7.2.3 + graceful-fs: 4.2.11 + lazystream: 1.0.1 + lodash.defaults: 4.2.0 + lodash.difference: 4.5.0 + lodash.flatten: 4.4.0 + lodash.isplainobject: 4.0.6 + lodash.union: 4.6.0 + normalize-path: 3.0.0 + readable-stream: 3.6.2 + + archiver@5.3.2: + dependencies: + archiver-utils: 2.1.0 + async: 3.2.6 + buffer-crc32: 0.2.13 + readable-stream: 3.6.2 + readdir-glob: 1.1.3 + tar-stream: 2.2.0 + zip-stream: 4.1.1 + arg@4.1.3: {} argparse@1.0.10: @@ -6180,6 +6436,8 @@ snapshots: asap@2.0.6: {} + async@3.2.6: {} + asynckit@0.4.0: {} axios@1.13.2: @@ -6260,6 +6518,8 @@ snapshots: node-addon-api: 8.5.0 node-gyp-build: 4.8.4 + big-integer@1.6.52: {} + bin-version-check@5.1.0: dependencies: bin-version: 6.0.0 @@ -6271,12 +6531,19 @@ snapshots: execa: 5.1.1 find-versions: 5.1.0 + binary@0.3.0: + dependencies: + buffers: 0.1.1 + chainsaw: 0.1.0 + bl@4.1.0: dependencies: buffer: 5.7.1 inherits: 2.0.4 readable-stream: 3.6.2 + bluebird@3.4.7: {} + body-parser@2.2.0: dependencies: bytes: 3.1.2 @@ -6326,11 +6593,15 @@ snapshots: buffer-from@1.1.2: {} + buffer-indexof-polyfill@1.0.2: {} + buffer@5.7.1: dependencies: base64-js: 1.5.1 ieee754: 1.2.1 + buffers@0.1.1: {} + busboy@1.6.0: dependencies: streamsearch: 1.1.0 @@ -6384,6 +6655,10 @@ snapshots: case-anything@2.1.13: {} + chainsaw@0.1.0: + dependencies: + traverse: 0.3.9 + chalk@4.1.2: dependencies: ansi-styles: 4.3.0 @@ -6478,6 +6753,13 @@ snapshots: component-emitter@1.3.1: {} + compress-commons@4.1.2: + dependencies: + buffer-crc32: 0.2.13 + crc32-stream: 4.0.3 + normalize-path: 3.0.0 + readable-stream: 3.6.2 + concat-map@0.0.1: {} concat-stream@2.0.0: @@ -6561,6 +6843,13 @@ snapshots: optionalDependencies: typescript: 5.9.2 + crc-32@1.2.2: {} + + crc32-stream@4.0.3: + dependencies: + crc-32: 1.2.2 + readable-stream: 3.6.2 + create-jest@29.7.0(@types/node@22.18.6)(ts-node@10.9.2(@swc/core@1.13.5)(@types/node@22.18.6)(typescript@5.9.2)): dependencies: '@jest/types': 29.6.3 @@ -6584,6 +6873,8 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 + dayjs@1.11.21: {} + debug@4.4.3: dependencies: ms: 2.1.3 @@ -6651,6 +6942,10 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 + duplexer2@0.1.4: + dependencies: + readable-stream: 2.3.8 + eastasianwidth@0.2.0: {} ecdsa-sig-formatter@1.0.11: @@ -6676,6 +6971,10 @@ snapshots: encodeurl@2.0.0: {} + end-of-stream@1.4.5: + dependencies: + once: 1.4.0 + enhanced-resolve@5.18.3: dependencies: graceful-fs: 4.2.11 @@ -6812,6 +7111,18 @@ snapshots: events@3.3.0: {} + exceljs@4.4.0: + dependencies: + archiver: 5.3.2 + dayjs: 1.11.21 + fast-csv: 4.3.6 + jszip: 3.10.1 + readable-stream: 3.6.2 + saxes: 5.0.1 + tmp: 0.2.7 + unzipper: 0.10.14 + uuid: 8.3.2 + execa@5.1.1: dependencies: cross-spawn: 7.0.6 @@ -6885,6 +7196,11 @@ snapshots: dependencies: pure-rand: 6.1.0 + fast-csv@4.3.6: + dependencies: + '@fast-csv/format': 4.3.5 + '@fast-csv/parse': 4.3.6 + fast-deep-equal@3.1.3: {} fast-diff@1.3.0: {} @@ -7029,6 +7345,8 @@ snapshots: fresh@2.0.0: {} + fs-constants@1.0.0: {} + fs-extra@10.1.0: dependencies: graceful-fs: 4.2.11 @@ -7042,6 +7360,13 @@ snapshots: fsevents@2.3.3: optional: true + fstream@1.0.12: + dependencies: + graceful-fs: 4.2.11 + inherits: 2.0.4 + mkdirp: 0.5.6 + rimraf: 2.7.1 + function-bind@1.1.2: {} gensync@1.0.0-beta.2: {} @@ -7222,6 +7547,8 @@ snapshots: ignore@7.0.5: {} + immediate@3.0.6: {} + import-fresh@3.3.1: dependencies: parent-module: 1.0.1 @@ -7285,6 +7612,8 @@ snapshots: is-unicode-supported@0.1.0: {} + isarray@1.0.0: {} + isexe@2.0.0: {} istanbul-lib-coverage@3.2.2: {} @@ -7699,6 +8028,13 @@ snapshots: ms: 2.1.3 semver: 7.7.2 + jszip@3.10.1: + dependencies: + lie: 3.3.0 + pako: 1.0.11 + readable-stream: 2.3.8 + setimmediate: 1.0.5 + jwa@1.4.2: dependencies: buffer-equal-constant-time: 1.0.1 @@ -7720,6 +8056,10 @@ snapshots: kleur@3.0.3: {} + lazystream@1.0.1: + dependencies: + readable-stream: 2.3.8 + leven@3.1.0: {} levn@0.4.1: @@ -7729,8 +8069,14 @@ snapshots: libphonenumber-js@1.12.22: {} + lie@3.3.0: + dependencies: + immediate: 3.0.6 + lines-and-columns@1.2.4: {} + listenercount@1.0.1: {} + load-esm@1.0.2: {} loader-runner@4.3.0: {} @@ -7745,18 +8091,36 @@ snapshots: lodash.camelcase@4.3.0: {} + lodash.defaults@4.2.0: {} + + lodash.difference@4.5.0: {} + + lodash.escaperegexp@4.1.2: {} + + lodash.flatten@4.4.0: {} + + lodash.groupby@4.6.0: {} + lodash.includes@4.3.0: {} lodash.isboolean@3.0.3: {} + lodash.isequal@4.5.0: {} + + lodash.isfunction@3.0.9: {} + lodash.isinteger@4.0.4: {} + lodash.isnil@4.0.0: {} + lodash.isnumber@3.0.3: {} lodash.isplainobject@4.0.6: {} lodash.isstring@4.0.1: {} + lodash.isundefined@3.0.1: {} + lodash.kebabcase@4.1.1: {} lodash.memoize@4.1.2: {} @@ -7771,6 +8135,10 @@ snapshots: lodash.startcase@4.4.0: {} + lodash.union@4.6.0: {} + + lodash.uniq@4.5.0: {} + lodash.upperfirst@4.3.1: {} lodash@4.17.21: {} @@ -7859,6 +8227,10 @@ snapshots: dependencies: brace-expansion: 1.1.12 + minimatch@5.1.9: + dependencies: + brace-expansion: 2.0.2 + minimatch@9.0.5: dependencies: brace-expansion: 2.0.2 @@ -7988,6 +8360,8 @@ snapshots: package-json-from-dist@1.0.1: {} + pako@1.0.11: {} + parent-module@1.0.1: dependencies: callsites: 3.1.0 @@ -8088,6 +8462,8 @@ snapshots: transitivePeerDependencies: - magicast + process-nextick-args@2.0.1: {} + prompts@2.4.2: dependencies: kleur: 3.0.3 @@ -8147,12 +8523,26 @@ snapshots: react-is@18.3.1: {} + readable-stream@2.3.8: + dependencies: + core-util-is: 1.0.3 + inherits: 2.0.4 + isarray: 1.0.0 + process-nextick-args: 2.0.1 + safe-buffer: 5.1.2 + string_decoder: 1.1.1 + util-deprecate: 1.0.2 + readable-stream@3.6.2: dependencies: inherits: 2.0.4 string_decoder: 1.3.0 util-deprecate: 1.0.2 + readdir-glob@1.1.3: + dependencies: + minimatch: 5.1.9 + readdirp@4.1.2: {} redis@5.9.0: @@ -8200,6 +8590,10 @@ snapshots: reusify@1.1.0: {} + rimraf@2.7.1: + dependencies: + glob: 7.2.3 + router@2.2.0: dependencies: debug: 4.4.3 @@ -8222,10 +8616,16 @@ snapshots: dependencies: tslib: 2.8.1 + safe-buffer@5.1.2: {} + safe-buffer@5.2.1: {} safer-buffer@2.1.2: {} + saxes@5.0.1: + dependencies: + xmlchars: 2.2.0 + schema-utils@3.3.0: dependencies: '@types/json-schema': 7.0.15 @@ -8287,6 +8687,8 @@ snapshots: transitivePeerDependencies: - supports-color + setimmediate@1.0.5: {} + setprototypeof@1.2.0: {} shebang-command@2.0.0: @@ -8392,6 +8794,10 @@ snapshots: emoji-regex: 9.2.2 strip-ansi: 7.1.2 + string_decoder@1.1.1: + dependencies: + safe-buffer: 5.1.2 + string_decoder@1.3.0: dependencies: safe-buffer: 5.2.1 @@ -8473,6 +8879,14 @@ snapshots: tapable@2.2.3: {} + tar-stream@2.2.0: + dependencies: + bl: 4.1.0 + end-of-stream: 1.4.5 + fs-constants: 1.0.0 + inherits: 2.0.4 + readable-stream: 3.6.2 + tar-stream@3.1.7: dependencies: b4a: 1.7.2 @@ -8515,6 +8929,8 @@ snapshots: tinyexec@1.0.1: {} + tmp@0.2.7: {} + tmpl@1.0.5: {} to-regex-range@5.0.1: @@ -8529,6 +8945,8 @@ snapshots: '@tokenizer/token': 0.3.0 ieee754: 1.2.1 + traverse@0.3.9: {} + tree-kill@1.2.2: {} ts-api-utils@2.1.0(typescript@5.9.2): @@ -8673,6 +9091,19 @@ snapshots: unpipe@1.0.0: {} + unzipper@0.10.14: + dependencies: + big-integer: 1.6.52 + binary: 0.3.0 + bluebird: 3.4.7 + buffer-indexof-polyfill: 1.0.2 + duplexer2: 0.1.4 + fstream: 1.0.12 + graceful-fs: 4.2.11 + listenercount: 1.0.1 + readable-stream: 2.3.8 + setimmediate: 1.0.5 + update-browserslist-db@1.1.3(browserslist@4.26.2): dependencies: browserslist: 4.26.2 @@ -8687,6 +9118,8 @@ snapshots: utils-merge@1.0.1: {} + uuid@8.3.2: {} + v8-compile-cache-lib@3.0.1: {} v8-to-istanbul@9.3.0: @@ -8781,6 +9214,8 @@ snapshots: imurmurhash: 0.1.4 signal-exit: 3.0.7 + xmlchars@2.2.0: {} + xtend@4.0.2: {} y18n@5.0.8: {} @@ -8809,3 +9244,9 @@ snapshots: yocto-queue@0.1.0: {} yoctocolors-cjs@2.1.3: {} + + zip-stream@4.1.1: + dependencies: + archiver-utils: 3.0.4 + compress-commons: 4.1.2 + readable-stream: 3.6.2