diff --git a/apps/api/src/app/app.module.ts b/apps/api/src/app/app.module.ts index 0deb9602f..32836cc88 100644 --- a/apps/api/src/app/app.module.ts +++ b/apps/api/src/app/app.module.ts @@ -46,6 +46,7 @@ import { OrganizerModule } from '../organizer/organizer.module' import { DonationWishModule } from '../donation-wish/donation-wish.module' import { ApiLoggerMiddleware } from './middleware/apilogger.middleware' import { PaypalModule } from '../paypal/paypal.module' +import { ExportModule } from '../export/export.module' @Module({ imports: [ @@ -90,6 +91,7 @@ import { PaypalModule } from '../paypal/paypal.module' OrganizerModule, DonationWishModule, PaypalModule, + ExportModule, ], controllers: [AppController], providers: [ diff --git a/apps/api/src/bank-transactions-file/bank-transactions-file.controller.spec.ts b/apps/api/src/bank-transactions-file/bank-transactions-file.controller.spec.ts index 8604b8aa1..bee52447b 100644 --- a/apps/api/src/bank-transactions-file/bank-transactions-file.controller.spec.ts +++ b/apps/api/src/bank-transactions-file/bank-transactions-file.controller.spec.ts @@ -3,6 +3,7 @@ import { ConfigService } from '@nestjs/config' import { Test, TestingModule } from '@nestjs/testing' import { CampaignService } from '../campaign/campaign.service' import { DonationsService } from '../donations/donations.service' +import { ExportService } from '../export/export.service' import { PersonService } from '../person/person.service' import { PrismaService } from '../prisma/prisma.service' import { S3Service } from '../s3/s3.service' @@ -36,6 +37,7 @@ describe('BankTransactionsFileController', () => { PersonService, PrismaService, S3Service, + ExportService, ], }).compile() diff --git a/apps/api/src/bank-transactions-file/bank-transactions-file.module.ts b/apps/api/src/bank-transactions-file/bank-transactions-file.module.ts index b75f35d51..a95de5527 100644 --- a/apps/api/src/bank-transactions-file/bank-transactions-file.module.ts +++ b/apps/api/src/bank-transactions-file/bank-transactions-file.module.ts @@ -10,12 +10,13 @@ import { CampaignService } from '../campaign/campaign.service' import { ConfigService } from '@nestjs/config' import { StripeModule } from '@golevelup/nestjs-stripe' import { useFactoryService } from './helpers/use-factory-service' +import { ExportService } from '../export/export.service' @Module({ imports: [ StripeModule.forRootAsync(StripeModule, { inject: [ConfigService], - useFactory:useFactoryService.useFactory + useFactory: useFactoryService.useFactory, }), ], controllers: [BankTransactionsFileController], @@ -27,6 +28,7 @@ import { useFactoryService } from './helpers/use-factory-service' VaultService, CampaignService, DonationsService, + ExportService, ], }) export class BankTransactionsFileModule {} diff --git a/apps/api/src/donations/donations.controller.spec.ts b/apps/api/src/donations/donations.controller.spec.ts index 96c7b4404..3435bb288 100644 --- a/apps/api/src/donations/donations.controller.spec.ts +++ b/apps/api/src/donations/donations.controller.spec.ts @@ -12,6 +12,7 @@ import { Prisma, } from '@prisma/client' import { CampaignService } from '../campaign/campaign.service' +import { ExportService } from '../export/export.service' import { PersonService } from '../person/person.service' import { MockPrismaService, prismaMock } from '../prisma/prisma-client.mock' import { VaultService } from '../vault/vault.service' @@ -83,6 +84,7 @@ describe('DonationsController', () => { useValue: stripeMock, }, PersonService, + ExportService, ], }).compile() @@ -199,7 +201,7 @@ describe('DonationsController', () => { } const existingDonation = { ...mockDonation, status: DonationStatus.initial } - const expectedUpdatedDonation = {...existingDonation, status: DonationStatus.succeeded } + const expectedUpdatedDonation = { ...existingDonation, status: DonationStatus.succeeded } prismaMock.donation.findFirst.mockResolvedValueOnce(existingDonation) prismaMock.donation.update.mockResolvedValueOnce(expectedUpdatedDonation) diff --git a/apps/api/src/donations/donations.controller.ts b/apps/api/src/donations/donations.controller.ts index c97d4e7e7..00390d1c9 100644 --- a/apps/api/src/donations/donations.controller.ts +++ b/apps/api/src/donations/donations.controller.ts @@ -9,10 +9,12 @@ import { Post, UnauthorizedException, Query, + Res, } from '@nestjs/common' import { ApiQuery } from '@nestjs/swagger' +import { Response } from 'express' -import { KeycloakTokenParsed } from '../auth/keycloak' +import { isAdmin, KeycloakTokenParsed } from '../auth/keycloak' import { DonationsService } from './donations.service' import { CreateSessionDto } from './dto/create-session.dto' import { CreatePaymentDto } from './dto/create-payment.dto' @@ -27,6 +29,17 @@ import { ApiTags } from '@nestjs/swagger' export class DonationsController { constructor(private readonly donationsService: DonationsService) {} + @Get('export-excel') + @Roles({ + roles: [RealmViewSupporters.role, ViewSupporters.role], + mode: RoleMatchingMode.ANY, + }) + async exportToExcel(@Res() res: Response, @AuthenticatedUser() user: KeycloakTokenParsed) { + if (isAdmin(user)) { + await this.donationsService.exportToExcel(res) + } + } + @Post('create-checkout-session') @Public() createCheckoutSession(@Body() sessionDto: CreateSessionDto) { @@ -67,7 +80,6 @@ export class DonationsController { @Query('status') status?: DonationStatus, @Query() query?: PagingQueryDto, ) { - console.log(query) return this.donationsService.listDonationsPublic( campaignId, status, diff --git a/apps/api/src/donations/donations.module.ts b/apps/api/src/donations/donations.module.ts index 5da2ce899..8084229b6 100644 --- a/apps/api/src/donations/donations.module.ts +++ b/apps/api/src/donations/donations.module.ts @@ -4,6 +4,7 @@ import { ConfigService } from '@nestjs/config' import { useFactoryService } from '../bank-transactions-file/helpers/use-factory-service' import { CampaignModule } from '../campaign/campaign.module' import { CampaignService } from '../campaign/campaign.service' +import { ExportService } from '../export/export.service' import { PersonModule } from '../person/person.module' import { PersonService } from '../person/person.service' import { PrismaService } from '../prisma/prisma.service' @@ -12,6 +13,7 @@ import { VaultService } from '../vault/vault.service' import { DonationsController } from './donations.controller' import { DonationsService } from './donations.service' import { StripePaymentService } from './events/stripe-payment.service' +import { ExportModule } from './../export/export.module' @Module({ imports: [ @@ -22,6 +24,7 @@ import { StripePaymentService } from './events/stripe-payment.service' VaultModule, CampaignModule, PersonModule, + ExportModule, ], controllers: [DonationsController], providers: [ @@ -31,6 +34,7 @@ import { StripePaymentService } from './events/stripe-payment.service' PrismaService, VaultService, PersonService, + ExportService, ], }) export class DonationsModule {} diff --git a/apps/api/src/donations/donations.service.spec.ts b/apps/api/src/donations/donations.service.spec.ts index 1774e9034..7fd211076 100644 --- a/apps/api/src/donations/donations.service.spec.ts +++ b/apps/api/src/donations/donations.service.spec.ts @@ -2,6 +2,7 @@ import { STRIPE_CLIENT_TOKEN } from '@golevelup/nestjs-stripe' import { ConfigService } from '@nestjs/config' import { Test, TestingModule } from '@nestjs/testing' import { CampaignService } from '../campaign/campaign.service' +import { ExportService } from '../export/export.service' import { PersonService } from '../person/person.service' import { MockPrismaService } from '../prisma/prisma-client.mock' import { VaultModule } from '../vault/vault.module' @@ -25,6 +26,7 @@ describe('DonationsService', () => { useValue: jest.fn(), }, PersonService, + ExportService, ], }).compile() diff --git a/apps/api/src/donations/donations.service.ts b/apps/api/src/donations/donations.service.ts index 6dd421917..ce3f4a75a 100644 --- a/apps/api/src/donations/donations.service.ts +++ b/apps/api/src/donations/donations.service.ts @@ -10,11 +10,14 @@ import { PaymentProvider, Prisma, } from '@prisma/client' +import { Response } from 'express' +import { getTemplateByTable } from '../export/helpers/exportableData' import { KeycloakTokenParsed } from '../auth/keycloak' import { CampaignService } from '../campaign/campaign.service' import { PrismaService } from '../prisma/prisma.service' import { VaultService } from '../vault/vault.service' +import { ExportService } from '../export/export.service' import { DonationMetadata } from './dontation-metadata.interface' import { CreateBankPaymentDto } from './dto/create-bank-payment.dto' import { CreatePaymentDto } from './dto/create-payment.dto' @@ -23,6 +26,7 @@ import { UpdatePaymentDto } from './dto/update-payment.dto' import { Person } from '../person/entities/person.entity' import { CreateManyBankPaymentsDto } from './dto/create-many-bank-payments.dto' import { DonationBaseDto, ListDonationsDto } from './dto/list-donations.dto' +import { donationWithPerson, DonationWithPerson } from './validators/donation.validator' @Injectable() export class DonationsService { @@ -32,6 +36,7 @@ export class DonationsService { private campaignService: CampaignService, private prisma: PrismaService, private vaultService: VaultService, + private exportService: ExportService, ) {} async listPrices(type?: Stripe.PriceListParams.Type, active?: boolean): Promise { @@ -213,16 +218,12 @@ export class DonationsService { status?: DonationStatus, pageIndex?: number, pageSize?: number, - ): Promise> { + ): Promise> { const data = await this.prisma.donation.findMany({ where: { status, targetVault: { campaign: { id: campaignId } } }, - orderBy: [{ createdAt: 'desc' }], - include: { - person: { select: { firstName: true, lastName: true } }, - targetVault: { select: { name: true } }, - }, skip: pageIndex && pageSize ? pageIndex * pageSize : undefined, take: pageSize ? pageSize : undefined, + ...donationWithPerson, }) const count = await this.prisma.donation.count({ @@ -296,7 +297,6 @@ export class DonationsService { this.vaultService.incrementVaultAmount(donation.targetVaultId, donation.amount) } catch (error) { Logger.error('Error while importing bank donation. ', error) - throw error } } } @@ -349,9 +349,11 @@ export class DonationsService { }, }) - if (currentDonation.status !== DonationStatus.succeeded - && updatePaymentDto.status === DonationStatus.succeeded - && donation.status === DonationStatus.succeeded) { + if ( + currentDonation.status !== DonationStatus.succeeded && + updatePaymentDto.status === DonationStatus.succeeded && + donation.status === DonationStatus.succeeded + ) { await this.vaultService.incrementVaultAmount( currentDonation.targetVaultId, currentDonation.amount, @@ -412,4 +414,17 @@ export class DonationsService { }, }) } + + async exportToExcel(res: Response) { + const { items } = await this.listDonations() + const donationsMappedForExport = items.map((donation) => ({ + ...donation, + person: donation.person + ? `${donation.person.firstName} ${donation.person.lastName}` + : 'Unknown', + })) + const donationExcelTemplate = getTemplateByTable('donations') + + await this.exportService.exportToExcel(res, donationsMappedForExport, donationExcelTemplate) + } } diff --git a/apps/api/src/donations/validators/donation.validator.ts b/apps/api/src/donations/validators/donation.validator.ts new file mode 100644 index 000000000..3098212d0 --- /dev/null +++ b/apps/api/src/donations/validators/donation.validator.ts @@ -0,0 +1,20 @@ +import { Prisma } from '@prisma/client' + +export const donationWithPerson = Prisma.validator()({ + include: { + person: { + select: { + firstName: true, + lastName: true, + }, + }, + targetVault: { + select: { + name: true, + }, + }, + }, + orderBy: [{ createdAt: 'desc' }], +}) + +export type DonationWithPerson = Prisma.DonationGetPayload diff --git a/apps/api/src/export/export.module.ts b/apps/api/src/export/export.module.ts new file mode 100644 index 000000000..87cfdfc9d --- /dev/null +++ b/apps/api/src/export/export.module.ts @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common' +import { ExportService } from './export.service' +import { PrismaService } from '../prisma/prisma.service' + +@Module({ + providers: [ExportService, PrismaService], + exports: [ExportService], +}) +export class ExportModule {} diff --git a/apps/api/src/export/export.service.ts b/apps/api/src/export/export.service.ts new file mode 100644 index 000000000..c3d07e5c6 --- /dev/null +++ b/apps/api/src/export/export.service.ts @@ -0,0 +1,30 @@ +import { Injectable } from '@nestjs/common' +import { PrismaService } from '../prisma/prisma.service' +import { Response } from 'express' +import { createWorkbook } from './helpers/createWorkbook' +import { ExportableData } from './helpers/exportableData' +import { Template } from './helpers/exportableData' + +@Injectable() +export class ExportService { + constructor(private prisma: PrismaService) {} + + exportToExcel = async (res: Response, data: ExportableData, template: Template) => { + if (!data.length) res.status(404).end('No data to export') + + try { + const workbook = createWorkbook(data, template) + + res.set({ + 'Content-Type': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 'Content-Disposition': 'attachment;', + }) + + workbook.xlsx.write(res).then(() => { + res.status(200).end() + }) + } catch (err) { + throw new Error(err) + } + } +} diff --git a/apps/api/src/export/helpers/createWorkbook.ts b/apps/api/src/export/helpers/createWorkbook.ts new file mode 100644 index 000000000..e6ac8dd0d --- /dev/null +++ b/apps/api/src/export/helpers/createWorkbook.ts @@ -0,0 +1,42 @@ +import excelJs from 'exceljs' +import { ExportableData } from './exportableData' +import { Template } from './exportableData' + +const applySheetDataToRows = (sheet, data: ExportableData) => { + data.forEach((el) => { + sheet.addRow(el) + }) +} + +const handleHeadersRowStyle = (header, style) => { + header.font = style?.font + header.alignment = style?.alignment + header.height = style?.height +} + +const handleBodyRowsStyle = (sheet, style) => { + let rowIndex = 2 + for (rowIndex; rowIndex <= sheet.rowCount; rowIndex++) { + const currentRow = sheet.getRow(rowIndex) + currentRow.alignment = style?.alignment + } +} + +export const createWorkbook = (data: ExportableData, template: Template) => { + const workbook = new excelJs.Workbook() + template.sheets.forEach((sheet) => { + const { title, columns, style } = sheet + + const currentSheet = workbook.addWorksheet(title) + currentSheet.columns = columns + + applySheetDataToRows(currentSheet, data) + + const headerRow = currentSheet.getRow(1) + handleHeadersRowStyle(headerRow, style.header) + + handleBodyRowsStyle(currentSheet, style.body) + }) + + return workbook +} diff --git a/apps/api/src/export/helpers/exportableData.ts b/apps/api/src/export/helpers/exportableData.ts new file mode 100644 index 000000000..f2e899052 --- /dev/null +++ b/apps/api/src/export/helpers/exportableData.ts @@ -0,0 +1,93 @@ +import { Donation, Campaign } from '@prisma/client' +import { Column, Alignment, Style } from 'exceljs' + +type NonEmptyArray = [T, ...T[]] + +export type ExportableData = Donation[] | Campaign[] + +export type ExcelColumns = NonEmptyArray + +type Sheet = { + title?: string + alignment?: Alignment + style: { + header?: Style + body?: Style + } + columns: ExcelColumns +} + +export type Template = { + sheets: Sheet[] +} + +const defaultHeaderStyle = { + font: { size: 16 }, + alignment: { + vertical: 'middle', + horizontal: 'center', + wrapText: false, + } as Alignment, + height: 20, +} + +const defaultBodyStyle = { + alignment: { + vertical: 'middle', + horizontal: 'center', + wrapText: false, + } as Alignment, +} + +const donationsDefaultCellWidth = 30 + +const exportableData = { + donations: { + sheets: [ + { + title: 'Donations', + columns: [ + { header: 'Created At', key: 'createdAt', width: donationsDefaultCellWidth }, + { header: 'Status', key: 'status', width: donationsDefaultCellWidth }, + { header: 'Amount', key: 'amount', width: donationsDefaultCellWidth }, + { header: 'Currency', key: 'currency', width: donationsDefaultCellWidth }, + { header: 'Person', key: 'person', width: donationsDefaultCellWidth }, + ], + style: { + header: defaultHeaderStyle, + body: defaultBodyStyle, + }, + }, + ], + }, +} + +type ValidTableNames = keyof typeof exportableData + +export const getDefaultTemplateAllColumns = (data: ExportableData): Template => { + const headers = Object.keys(data[0]) + const columns = headers.map((key) => ({ + header: key.charAt(0).toLocaleUpperCase() + key.slice(1), + key, + width: donationsDefaultCellWidth, + })) + + const template = { + sheets: [ + { + title: 'Default', + columns, + style: { + header: defaultHeaderStyle, + body: defaultBodyStyle, + }, + }, + ], + } + + return template as unknown as Template +} + +export const getTemplateByTable = (table: ValidTableNames): Template => { + return exportableData[table] as unknown as Template +} diff --git a/package.json b/package.json index 2be1acafd..24def4a6b 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "aws-sdk": "^2.1135.0", "class-transformer": "0.5.1", "class-validator": "0.13.2", + "exceljs": "^4.3.0", "handlebars": "4.7.7", "helmet": "5.0.2", "joi": "17.6.0", diff --git a/yarn.lock b/yarn.lock index 915af4155..d092da1c9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -715,6 +715,35 @@ __metadata: languageName: node linkType: hard +"@fast-csv/format@npm:4.3.5": + version: 4.3.5 + resolution: "@fast-csv/format@npm:4.3.5" + dependencies: + "@types/node": ^14.0.1 + 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 + checksum: 36d5fb76c26179bd1718dffd49fc88f4ab62675464d7ec44c5f1de55b8c454ecdf3faf84fe291df934832233f531d24c810a71cc0c390c7c4df09685d614c65b + languageName: node + linkType: hard + +"@fast-csv/parse@npm:4.3.6": + version: 4.3.6 + resolution: "@fast-csv/parse@npm:4.3.6" + dependencies: + "@types/node": ^14.0.1 + 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 + checksum: 15434d2bc7a71af63a4cc45352a58f6edd69d464233fd6f9a808a8efb9ae326832b4f78d1ddcf9fc3bbf166e9c1dce19671c4de40371a926d60cb7c5a8bad08a + languageName: node + linkType: hard + "@gar/promisify@npm:^1.1.3": version: 1.1.3 resolution: "@gar/promisify@npm:1.1.3" @@ -2879,6 +2908,13 @@ __metadata: languageName: node linkType: hard +"@types/node@npm:^14.0.1": + version: 14.18.34 + resolution: "@types/node@npm:14.18.34" + checksum: 25ac3b456a0b7b82c76b37276ec86845849e8276fc81d1470a87227c105c619e299aa7165b6148aa11a4ea156b1452f6d3327935f3e7dc0067ff54dde0e3d4e0 + languageName: node + linkType: hard + "@types/normalize-package-data@npm:^2.4.0": version: 2.4.1 resolution: "@types/normalize-package-data@npm:2.4.1" @@ -3662,7 +3698,7 @@ __metadata: languageName: node linkType: hard -"archiver@npm:5.3.1": +"archiver@npm:5.3.1, archiver@npm:^5.0.0": version: 5.3.1 resolution: "archiver@npm:5.3.1" dependencies: @@ -4027,6 +4063,13 @@ __metadata: languageName: node linkType: hard +"big-integer@npm:^1.6.17": + version: 1.6.51 + resolution: "big-integer@npm:1.6.51" + checksum: 3d444173d1b2e20747e2c175568bedeebd8315b0637ea95d75fd27830d3b8e8ba36c6af40374f36bdaea7b5de376dcada1b07587cb2a79a928fccdb6e6e3c518 + languageName: node + linkType: hard + "big.js@npm:^5.2.2": version: 5.2.2 resolution: "big.js@npm:5.2.2" @@ -4041,6 +4084,16 @@ __metadata: languageName: node linkType: hard +"binary@npm:~0.3.0": + version: 0.3.0 + resolution: "binary@npm:0.3.0" + dependencies: + buffers: ~0.1.1 + chainsaw: ~0.1.0 + checksum: b4699fda9e2c2981e74a46b0115cf0d472eda9b68c0e9d229ef494e92f29ce81acf0a834415094cffcc340dfee7c4ef8ce5d048c65c18067a7ed850323f777af + languageName: node + linkType: hard + "bl@npm:^4.0.3, bl@npm:^4.1.0": version: 4.1.0 resolution: "bl@npm:4.1.0" @@ -4052,6 +4105,13 @@ __metadata: languageName: node linkType: hard +"bluebird@npm:~3.4.1": + version: 3.4.7 + resolution: "bluebird@npm:3.4.7" + checksum: bffa9dee7d3a41ab15c4f3f24687b49959b4e64e55c058a062176feb8ccefc2163414fb4e1a0f3053bf187600936509660c3ebd168fd9f0e48c7eba23b019466 + languageName: node + linkType: hard + "bn.js@npm:^4.0.0, bn.js@npm:^4.11.9": version: 4.12.0 resolution: "bn.js@npm:4.12.0" @@ -4236,6 +4296,13 @@ __metadata: languageName: node linkType: hard +"buffer-indexof-polyfill@npm:~1.0.0": + version: 1.0.2 + resolution: "buffer-indexof-polyfill@npm:1.0.2" + checksum: fbfb2d69c6bb2df235683126f9dc140150c08ac3630da149913a9971947b667df816a913b6993bc48f4d611999cb99a1589914d34c02dccd2234afda5cb75bbc + languageName: node + linkType: hard + "buffer@npm:4.9.2": version: 4.9.2 resolution: "buffer@npm:4.9.2" @@ -4257,6 +4324,13 @@ __metadata: languageName: node linkType: hard +"buffers@npm:~0.1.1": + version: 0.1.1 + resolution: "buffers@npm:0.1.1" + checksum: ad6f8e483efab39cefd92bdc04edbff6805e4211b002f4d1cfb70c6c472a61cc89fb18c37bcdfdd4ee416ca096e9ff606286698a7d41a18b539bac12fd76d4d5 + languageName: node + linkType: hard + "busboy@npm:^1.0.0": version: 1.6.0 resolution: "busboy@npm:1.6.0" @@ -4387,6 +4461,15 @@ __metadata: languageName: node linkType: hard +"chainsaw@npm:~0.1.0": + version: 0.1.0 + resolution: "chainsaw@npm:0.1.0" + dependencies: + traverse: ">=0.3.0 <0.4" + checksum: 22a96b9fb0cd9fb20813607c0869e61817d1acc81b5d455cc6456b5e460ea1dd52630e0f76b291cf8294bfb6c1fc42e299afb52104af9096242699d6d3aa6d3e + languageName: node + linkType: hard + "chalk@npm:3.0.0": version: 3.0.0 resolution: "chalk@npm:3.0.0" @@ -5255,6 +5338,13 @@ chromedriver@latest: languageName: node linkType: hard +"dayjs@npm:^1.8.34": + version: 1.11.7 + resolution: "dayjs@npm:1.11.7" + checksum: 5003a7c1dd9ed51385beb658231c3548700b82d3548c0cfbe549d85f2d08e90e972510282b7506941452c58d32136d6362f009c77ca55381a09c704e9f177ebb + languageName: node + linkType: hard + "debug@npm:2.6.9": version: 2.6.9 resolution: "debug@npm:2.6.9" @@ -5603,6 +5693,15 @@ chromedriver@latest: languageName: node linkType: hard +"duplexer2@npm:~0.1.4": + version: 0.1.4 + resolution: "duplexer2@npm:0.1.4" + dependencies: + readable-stream: ^2.0.2 + checksum: 744961f03c7f54313f90555ac20284a3fb7bf22fdff6538f041a86c22499560eb6eac9d30ab5768054137cb40e6b18b40f621094e0261d7d8c35a37b7a5ad241 + languageName: node + linkType: hard + "duplexer@npm:^0.1.1": version: 0.1.2 resolution: "duplexer@npm:0.1.2" @@ -6094,6 +6193,23 @@ chromedriver@latest: languageName: node linkType: hard +"exceljs@npm:^4.3.0": + version: 4.3.0 + resolution: "exceljs@npm:4.3.0" + dependencies: + archiver: ^5.0.0 + dayjs: ^1.8.34 + fast-csv: ^4.3.1 + jszip: ^3.5.0 + readable-stream: ^3.6.0 + saxes: ^5.0.1 + tmp: ^0.2.0 + unzipper: ^0.10.11 + uuid: ^8.3.0 + checksum: 5ff98a13ccb3bca4523588feffffded84d93bebf6bbe3b49f894c9f092f6bf42555bb29332de7c73df32f50a770d774abe9e5d19dd2e13ce4b705d5affc6b30c + languageName: node + linkType: hard + "execa@npm:5.1.1, execa@npm:^5.0.0": version: 5.1.1 resolution: "execa@npm:5.1.1" @@ -6267,6 +6383,16 @@ chromedriver@latest: languageName: node linkType: hard +"fast-csv@npm:^4.3.1": + version: 4.3.6 + resolution: "fast-csv@npm:4.3.6" + dependencies: + "@fast-csv/format": 4.3.5 + "@fast-csv/parse": 4.3.6 + checksum: 77ae68c08b368a19f2b6e933160d0c5d4934513ef903577c44c84ea08befe1a6638a9480238dfcd55f14c7ddcafdb772c9408dd9d145eca62a147c503ff493a9 + languageName: node + linkType: hard + "fast-deep-equal@npm:^3.1.1, fast-deep-equal@npm:^3.1.3": version: 3.1.3 resolution: "fast-deep-equal@npm:3.1.3" @@ -6681,6 +6807,18 @@ chromedriver@latest: languageName: node linkType: hard +"fstream@npm:^1.0.12": + version: 1.0.12 + resolution: "fstream@npm:1.0.12" + dependencies: + graceful-fs: ^4.1.2 + inherits: ~2.0.0 + mkdirp: ">=0.5 0" + rimraf: 2 + checksum: e6998651aeb85fd0f0a8a68cec4d05a3ada685ecc4e3f56e0d063d0564a4fc39ad11a856f9020f926daf869fc67f7a90e891def5d48e4cadab875dc313094536 + languageName: node + linkType: hard + "function-bind@npm:^1.1.1": version: 1.1.1 resolution: "function-bind@npm:1.1.1" @@ -6905,7 +7043,7 @@ chromedriver@latest: languageName: node linkType: hard -"graceful-fs@npm:^4.1.15, graceful-fs@npm:^4.1.2, graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.4, graceful-fs@npm:^4.2.6, graceful-fs@npm:^4.2.9": +"graceful-fs@npm:^4.1.15, graceful-fs@npm:^4.1.2, graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.2, graceful-fs@npm:^4.2.4, graceful-fs@npm:^4.2.6, graceful-fs@npm:^4.2.9": version: 4.2.10 resolution: "graceful-fs@npm:4.2.10" checksum: 3f109d70ae123951905d85032ebeae3c2a5a7a997430df00ea30df0e3a6c60cf6689b109654d6fdacd28810a053348c4d14642da1d075049e6be1ba5216218da @@ -7368,6 +7506,13 @@ chromedriver@latest: languageName: node linkType: hard +"immediate@npm:~3.0.5": + version: 3.0.6 + resolution: "immediate@npm:3.0.6" + checksum: f9b3486477555997657f70318cc8d3416159f208bec4cca3ff3442fd266bc23f50f0c9bd8547e1371a6b5e82b821ec9a7044a4f7b944798b25aa3cc6d5e63e62 + languageName: node + linkType: hard + "immutable@npm:^4.0.0": version: 4.1.0 resolution: "immutable@npm:4.1.0" @@ -7428,7 +7573,7 @@ chromedriver@latest: languageName: node linkType: hard -"inherits@npm:2, inherits@npm:2.0.4, inherits@npm:^2.0.1, inherits@npm:^2.0.3, inherits@npm:^2.0.4, inherits@npm:~2.0.3": +"inherits@npm:2, inherits@npm:2.0.4, inherits@npm:^2.0.1, inherits@npm:^2.0.3, inherits@npm:^2.0.4, inherits@npm:~2.0.0, inherits@npm:~2.0.3": version: 2.0.4 resolution: "inherits@npm:2.0.4" checksum: 4a48a733847879d6cf6691860a6b1e3f0f4754176e4d71494c41f3475553768b10f84b5ce1d40fbd0e34e6bfbb864ee35858ad4dd2cf31e02fc4a154b724d7f1 @@ -9023,6 +9168,18 @@ chromedriver@latest: languageName: node linkType: hard +"jszip@npm:^3.5.0": + version: 3.10.1 + resolution: "jszip@npm:3.10.1" + dependencies: + lie: ~3.3.0 + pako: ~1.0.2 + readable-stream: ~2.3.6 + setimmediate: ^1.0.5 + checksum: abc77bfbe33e691d4d1ac9c74c8851b5761fba6a6986630864f98d876f3fcc2d36817dfc183779f32c00157b5d53a016796677298272a714ae096dfe6b1c8b60 + languageName: node + linkType: hard + "juice@npm:^7.0.0": version: 7.0.0 resolution: "juice@npm:7.0.0" @@ -9206,6 +9363,15 @@ chromedriver@latest: languageName: node linkType: hard +"lie@npm:~3.3.0": + version: 3.3.0 + resolution: "lie@npm:3.3.0" + dependencies: + immediate: ~3.0.5 + checksum: 33102302cf19766f97919a6a98d481e01393288b17a6aa1f030a3542031df42736edde8dab29ffdbf90bebeffc48c761eb1d064dc77592ca3ba3556f9fe6d2a8 + languageName: node + linkType: hard + "lilconfig@npm:^2.0.3": version: 2.0.6 resolution: "lilconfig@npm:2.0.6" @@ -9244,6 +9410,13 @@ chromedriver@latest: languageName: node linkType: hard +"listenercount@npm:~1.0.1": + version: 1.0.1 + resolution: "listenercount@npm:1.0.1" + checksum: 0f1c9077cdaf2ebc16473c7d72eb7de6d983898ca42500f03da63c3914b6b312dd5f7a90d2657691ea25adf3fe0ac5a43226e8b2c673fd73415ed038041f4757 + languageName: node + linkType: hard + "listr2@npm:^3.8.2": version: 3.14.0 resolution: "listr2@npm:3.14.0" @@ -9326,6 +9499,13 @@ chromedriver@latest: languageName: node linkType: hard +"lodash.escaperegexp@npm:^4.1.2": + version: 4.1.2 + resolution: "lodash.escaperegexp@npm:4.1.2" + checksum: 6d99452b1cfd6073175a9b741a9b09ece159eac463f86f02ea3bee2e2092923fce812c8d2bf446309cc52d1d61bf9af51c8118b0d7421388e6cead7bd3798f0f + languageName: node + linkType: hard + "lodash.flatten@npm:^4.4.0": version: 4.4.0 resolution: "lodash.flatten@npm:4.4.0" @@ -9333,6 +9513,13 @@ chromedriver@latest: languageName: node linkType: hard +"lodash.groupby@npm:^4.6.0": + version: 4.6.0 + resolution: "lodash.groupby@npm:4.6.0" + checksum: e2d4d13d12790a1cacab3f5f120b7c072a792224e83b2f403218866d18efde76024b2579996dfebb230a61ce06469332e16639103669a35a605287e19ced6b9b + languageName: node + linkType: hard + "lodash.includes@npm:^4.3.0": version: 4.3.0 resolution: "lodash.includes@npm:4.3.0" @@ -9347,6 +9534,20 @@ chromedriver@latest: languageName: node linkType: hard +"lodash.isequal@npm:^4.5.0": + version: 4.5.0 + resolution: "lodash.isequal@npm:4.5.0" + checksum: da27515dc5230eb1140ba65ff8de3613649620e8656b19a6270afe4866b7bd461d9ba2ac8a48dcc57f7adac4ee80e1de9f965d89d4d81a0ad52bb3eec2609644 + languageName: node + linkType: hard + +"lodash.isfunction@npm:^3.0.9": + version: 3.0.9 + resolution: "lodash.isfunction@npm:3.0.9" + checksum: 99e54c34b1e8a9ba75c034deb39cedbd2aca7af685815e67a2a8ec4f73ec9748cda6ebee5a07d7de4b938e90d421fd280e9c385cc190f903ac217ac8aff30314 + languageName: node + linkType: hard + "lodash.isinteger@npm:^4.0.4": version: 4.0.4 resolution: "lodash.isinteger@npm:4.0.4" @@ -9354,6 +9555,13 @@ chromedriver@latest: languageName: node linkType: hard +"lodash.isnil@npm:^4.0.0": + version: 4.0.0 + resolution: "lodash.isnil@npm:4.0.0" + checksum: ebf8df69879badd6ad99c4f64c54c470248df5cf92b208ca730861b1d8ac058da7b632ac811d18b0929d93cbac8d8fc866e781ee816b0142c56952e85edc682f + languageName: node + linkType: hard + "lodash.isnumber@npm:^3.0.3": version: 3.0.3 resolution: "lodash.isnumber@npm:3.0.3" @@ -9375,6 +9583,13 @@ chromedriver@latest: languageName: node linkType: hard +"lodash.isundefined@npm:^3.0.1": + version: 3.0.1 + resolution: "lodash.isundefined@npm:3.0.1" + checksum: 52b4d99a47bd41daa4e2860200258f56b1f2c99263c11a5f607fbbd91d6447fe674bdafc172735d099908a09136d4a0f98cf79715e38ca4b490fdda7162be289 + languageName: node + linkType: hard + "lodash.memoize@npm:4.x, lodash.memoize@npm:^4.1.2": version: 4.1.2 resolution: "lodash.memoize@npm:4.1.2" @@ -10226,7 +10441,7 @@ chromedriver@latest: languageName: node linkType: hard -"mkdirp@npm:^0.5.4": +"mkdirp@npm:>=0.5 0, mkdirp@npm:^0.5.4": version: 0.5.6 resolution: "mkdirp@npm:0.5.6" dependencies: @@ -10825,6 +11040,13 @@ chromedriver@latest: languageName: node linkType: hard +"pako@npm:~1.0.2": + version: 1.0.11 + resolution: "pako@npm:1.0.11" + checksum: 1be2bfa1f807608c7538afa15d6f25baa523c30ec870a3228a89579e474a4d992f4293859524e46d5d87fd30fa17c5edf34dbef0671251d9749820b488660b16 + languageName: node + linkType: hard + "param-case@npm:^2.1.1": version: 2.1.1 resolution: "param-case@npm:2.1.1" @@ -11085,6 +11307,7 @@ chromedriver@latest: class-validator: 0.13.2 eslint: 8.7.0 eslint-config-prettier: 8.3.0 + exceljs: ^4.3.0 handlebars: 4.7.7 helmet: 5.0.2 husky: 7.0.2 @@ -11777,7 +12000,7 @@ chromedriver@latest: languageName: node linkType: hard -"readable-stream@npm:^2.0.0, readable-stream@npm:^2.0.1, readable-stream@npm:^2.0.5, readable-stream@npm:^2.2.2": +"readable-stream@npm:^2.0.0, readable-stream@npm:^2.0.1, readable-stream@npm:^2.0.2, readable-stream@npm:^2.0.5, readable-stream@npm:^2.2.2, readable-stream@npm:~2.3.6": version: 2.3.7 resolution: "readable-stream@npm:2.3.7" dependencies: @@ -12017,25 +12240,25 @@ chromedriver@latest: languageName: node linkType: hard -"rimraf@npm:3.0.2, rimraf@npm:^3.0.0, rimraf@npm:^3.0.2": - version: 3.0.2 - resolution: "rimraf@npm:3.0.2" +"rimraf@npm:2, rimraf@npm:^2.6.3": + version: 2.7.1 + resolution: "rimraf@npm:2.7.1" dependencies: glob: ^7.1.3 bin: - rimraf: bin.js - checksum: 87f4164e396f0171b0a3386cc1877a817f572148ee13a7e113b238e48e8a9f2f31d009a92ec38a591ff1567d9662c6b67fd8818a2dbbaed74bc26a87a2a4a9a0 + rimraf: ./bin.js + checksum: cdc7f6eacb17927f2a075117a823e1c5951792c6498ebcce81ca8203454a811d4cf8900314154d3259bb8f0b42ab17f67396a8694a54cae3283326e57ad250cd languageName: node linkType: hard -"rimraf@npm:^2.6.3": - version: 2.7.1 - resolution: "rimraf@npm:2.7.1" +"rimraf@npm:3.0.2, rimraf@npm:^3.0.0, rimraf@npm:^3.0.2": + version: 3.0.2 + resolution: "rimraf@npm:3.0.2" dependencies: glob: ^7.1.3 bin: - rimraf: ./bin.js - checksum: cdc7f6eacb17927f2a075117a823e1c5951792c6498ebcce81ca8203454a811d4cf8900314154d3259bb8f0b42ab17f67396a8694a54cae3283326e57ad250cd + rimraf: bin.js + checksum: 87f4164e396f0171b0a3386cc1877a817f572148ee13a7e113b238e48e8a9f2f31d009a92ec38a591ff1567d9662c6b67fd8818a2dbbaed74bc26a87a2a4a9a0 languageName: node linkType: hard @@ -12164,6 +12387,15 @@ chromedriver@latest: languageName: node linkType: hard +"saxes@npm:^5.0.1": + version: 5.0.1 + resolution: "saxes@npm:5.0.1" + dependencies: + xmlchars: ^2.2.0 + checksum: 5636b55cf15f7cf0baa73f2797bf992bdcf75d1b39d82c0aa4608555c774368f6ac321cb641fd5f3d3ceb87805122cd47540da6a7b5960fe0dbdb8f8c263f000 + languageName: node + linkType: hard + "schema-utils@npm:^2.6.5": version: 2.7.1 resolution: "schema-utils@npm:2.7.1" @@ -12325,6 +12557,13 @@ chromedriver@latest: languageName: node linkType: hard +"setimmediate@npm:^1.0.5, setimmediate@npm:~1.0.4": + version: 1.0.5 + resolution: "setimmediate@npm:1.0.5" + checksum: c9a6f2c5b51a2dabdc0247db9c46460152ffc62ee139f3157440bd48e7c59425093f42719ac1d7931f054f153e2d26cf37dfeb8da17a794a58198a2705e527fd + languageName: node + linkType: hard + "setprototypeof@npm:1.1.0": version: 1.1.0 resolution: "setprototypeof@npm:1.1.0" @@ -13243,7 +13482,7 @@ chromedriver@latest: languageName: node linkType: hard -"tmp@npm:0.2.1, tmp@npm:~0.2.1": +"tmp@npm:0.2.1, tmp@npm:^0.2.0, tmp@npm:~0.2.1": version: 0.2.1 resolution: "tmp@npm:0.2.1" dependencies: @@ -13298,6 +13537,13 @@ chromedriver@latest: languageName: node linkType: hard +"traverse@npm:>=0.3.0 <0.4": + version: 0.3.9 + resolution: "traverse@npm:0.3.9" + checksum: 982982e4e249e9bbf063732a41fe5595939892758524bbef5d547c67cdf371b13af72b5434c6a61d88d4bb4351d6dabc6e22d832e0d16bc1bc684ef97a1cc59e + languageName: node + linkType: hard + "tree-kill@npm:1.2.2": version: 1.2.2 resolution: "tree-kill@npm:1.2.2" @@ -13706,6 +13952,24 @@ chromedriver@latest: languageName: node linkType: hard +"unzipper@npm:^0.10.11": + version: 0.10.11 + resolution: "unzipper@npm:0.10.11" + dependencies: + big-integer: ^1.6.17 + binary: ~0.3.0 + bluebird: ~3.4.1 + buffer-indexof-polyfill: ~1.0.0 + duplexer2: ~0.1.4 + fstream: ^1.0.12 + graceful-fs: ^4.2.2 + listenercount: ~1.0.1 + readable-stream: ~2.3.6 + setimmediate: ~1.0.4 + checksum: 006cd43ec4d6df47d86aa6b15044a606f50cdcd6a3d6f96f64f54ca0b663c09abb221f76edca0e9592511036d37ea094b1d76ce92c5bf10d7c6eb56f0be678f8 + languageName: node + linkType: hard + "update-browserslist-db@npm:^1.0.5": version: 1.0.5 resolution: "update-browserslist-db@npm:1.0.5" @@ -13811,7 +14075,7 @@ chromedriver@latest: languageName: node linkType: hard -"uuid@npm:8.3.2, uuid@npm:^8.3.2": +"uuid@npm:8.3.2, uuid@npm:^8.3.0, uuid@npm:^8.3.2": version: 8.3.2 resolution: "uuid@npm:8.3.2" bin: @@ -14316,6 +14580,13 @@ chromedriver@latest: languageName: node linkType: hard +"xmlchars@npm:^2.2.0": + version: 2.2.0 + resolution: "xmlchars@npm:2.2.0" + checksum: 8c70ac94070ccca03f47a81fcce3b271bd1f37a591bf5424e787ae313fcb9c212f5f6786e1fa82076a2c632c0141552babcd85698c437506dfa6ae2d58723062 + languageName: node + linkType: hard + "xtend@npm:^4.0.0": version: 4.0.2 resolution: "xtend@npm:4.0.2"