From 1b504f431872ce5d5c12f714dcf911f2ef505882 Mon Sep 17 00:00:00 2001 From: Evert Prants Date: Sun, 23 Jul 2023 11:12:21 +0300 Subject: [PATCH] s3 bucket logic for assets --- .dockerignore | 1 + .gitignore | 1 + apps/assets/src/assets.controller.spec.ts | 2 +- apps/assets/src/assets.controller.ts | 34 +- apps/assets/src/assets.module.ts | 35 +- apps/assets/src/assets.service.ts | 8 - apps/assets/src/config/s3.config.ts | 8 + .../src/database/entities/asset.entity.ts | 56 + .../migrations/20230722190600_assets.ts | 22 + .../interfaces/upload-request.interface.ts | 13 + apps/assets/src/knexfile.ts | 5 + apps/assets/src/main.ts | 2 + apps/assets/src/services/assets.service.ts | 137 ++ apps/assets/src/services/s3.service.ts | 78 ++ apps/catalog/src/catalog.controller.ts | 20 + apps/catalog/src/catalog.module.ts | 1 + .../database/entities/content-asset.entity.ts | 4 + .../20230722091907_content-asset.ts | 1 + apps/catalog/src/enums/asset-source.enum.ts | 4 + .../create-content-request.interface.ts | 2 + .../src/services/create-content.service.ts | 78 +- apps/freeblox-web-service/src/app.module.ts | 21 +- .../src/decorators/privilege.decorator.ts | 4 + .../require-privileges.decorator.ts | 14 + .../src/guards/auth.guard.ts | 30 +- .../src/guards/privileges.guard.ts | 25 + .../src/middleware/user.middleware.ts | 34 + .../src/services/assets/assets.controller.ts | 61 + .../src/services/assets/assets.module.ts | 14 + .../services/catalog/catalog.controller.ts | 24 +- .../catalog/dtos/content-asset.dto.ts | 6 + .../catalog/dtos/content-response.dto.ts | 4 + docker-compose.yml | 19 + libs/shared/src/index.ts | 1 + libs/shared/src/utils/match-privileges.ts | 21 + package.json | 7 + pnpm-lock.yaml | 1168 ++++++++++++++++- 37 files changed, 1881 insertions(+), 84 deletions(-) delete mode 100644 apps/assets/src/assets.service.ts create mode 100644 apps/assets/src/config/s3.config.ts create mode 100644 apps/assets/src/database/entities/asset.entity.ts create mode 100644 apps/assets/src/database/migrations/20230722190600_assets.ts create mode 100644 apps/assets/src/interfaces/upload-request.interface.ts create mode 100644 apps/assets/src/knexfile.ts create mode 100644 apps/assets/src/services/assets.service.ts create mode 100644 apps/assets/src/services/s3.service.ts create mode 100644 apps/catalog/src/enums/asset-source.enum.ts create mode 100644 apps/freeblox-web-service/src/decorators/privilege.decorator.ts create mode 100644 apps/freeblox-web-service/src/decorators/require-privileges.decorator.ts create mode 100644 apps/freeblox-web-service/src/guards/privileges.guard.ts create mode 100644 apps/freeblox-web-service/src/middleware/user.middleware.ts create mode 100644 apps/freeblox-web-service/src/services/assets/assets.controller.ts create mode 100644 apps/freeblox-web-service/src/services/assets/assets.module.ts create mode 100644 apps/freeblox-web-service/src/services/catalog/dtos/content-asset.dto.ts create mode 100644 libs/shared/src/utils/match-privileges.ts diff --git a/.dockerignore b/.dockerignore index 20cbe5e..dab59b5 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,4 +1,5 @@ /database +/storage /dist /node_modules /.env diff --git a/.gitignore b/.gitignore index e964fee..fc7b58d 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ /node_modules /database /private +/storage # Logs logs diff --git a/apps/assets/src/assets.controller.spec.ts b/apps/assets/src/assets.controller.spec.ts index f180deb..de7e7ee 100644 --- a/apps/assets/src/assets.controller.spec.ts +++ b/apps/assets/src/assets.controller.spec.ts @@ -1,6 +1,6 @@ import { Test, TestingModule } from '@nestjs/testing'; import { AssetsController } from './assets.controller'; -import { AssetsService } from './assets.service'; +import { AssetsService } from './services/assets.service'; describe('AssetsController', () => { let assetsController: AssetsController; diff --git a/apps/assets/src/assets.controller.ts b/apps/assets/src/assets.controller.ts index 387186d..52d7694 100644 --- a/apps/assets/src/assets.controller.ts +++ b/apps/assets/src/assets.controller.ts @@ -1,12 +1,36 @@ -import { Controller, Get } from '@nestjs/common'; -import { AssetsService } from './assets.service'; +import { Controller } from '@nestjs/common'; +import { AssetsService } from './services/assets.service'; +import { MessagePattern } from '@nestjs/microservices'; +import { AssetUploadRequest } from './interfaces/upload-request.interface'; +import { UserInfo } from '@freeblox/shared'; @Controller() export class AssetsController { constructor(private readonly assetsService: AssetsService) {} - @Get() - getHello(): string { - return this.assetsService.getHello(); + @MessagePattern('assets.upload') + async uploadAsset({ + body, + user, + }: { + body: AssetUploadRequest; + user?: UserInfo; + }) { + return this.assetsService.uploadFile(body, user); + } + + @MessagePattern('assets.info.byId') + async assetInfo({ id, user }: { id: string; user?: UserInfo }) { + return this.assetsService.getAssetInfoById(id, user); + } + + @MessagePattern('assets.download.byId') + async downloadAsset({ id, user }: { id: string; user?: UserInfo }) { + return this.assetsService.downloadAssetById(id, user); + } + + @MessagePattern('assets.delete.byId') + async deleteAsset({ id }: { id: string }) { + return this.assetsService.deleteAsset(id); } } diff --git a/apps/assets/src/assets.module.ts b/apps/assets/src/assets.module.ts index 9a410b9..e56d9eb 100644 --- a/apps/assets/src/assets.module.ts +++ b/apps/assets/src/assets.module.ts @@ -1,29 +1,42 @@ -import { Module } from '@nestjs/common'; +import { Module, OnModuleInit } from '@nestjs/common'; import { AssetsController } from './assets.controller'; -import { AssetsService } from './assets.service'; +import { AssetsService } from './services/assets.service'; import { makeKnex, makeTypeOrm, natsClient } from '@freeblox/shared'; import { ConfigModule, ConfigService } from '@nestjs/config'; import { ClientsModule } from '@nestjs/microservices'; import { TypeOrmModule } from '@nestjs/typeorm'; +import knex from 'knex'; +import { AssetEntity } from './database/entities/asset.entity'; +import { s3Config } from './config/s3.config'; +import { S3Service } from './services/s3.service'; + +const entities = [AssetEntity]; @Module({ imports: [ ConfigModule.forRoot({ ignoreEnvFile: process.env.NODE_ENV === 'development', - load: [makeKnex('assets', __dirname), makeTypeOrm('assets')], + load: [makeKnex('assets', __dirname), makeTypeOrm('assets'), s3Config], }), TypeOrmModule.forRootAsync({ imports: [ConfigModule], inject: [ConfigService], - useFactory: (config: ConfigService) => config.get('typeorm'), + useFactory: (config: ConfigService) => ({ + ...config.get('typeorm'), + entities, + }), }), - ClientsModule.register([ - natsClient('assets'), - natsClient('auth'), - natsClient('player'), - ]), + TypeOrmModule.forFeature(entities), + ClientsModule.register([natsClient('assets'), natsClient('auth')]), ], controllers: [AssetsController], - providers: [AssetsService], + providers: [S3Service, AssetsService], }) -export class AssetsModule {} +export class AssetsModule implements OnModuleInit { + constructor(private readonly config: ConfigService) {} + + async onModuleInit() { + const knexInstance = knex(this.config.get('knex')); + await knexInstance.migrate.latest(); + } +} diff --git a/apps/assets/src/assets.service.ts b/apps/assets/src/assets.service.ts deleted file mode 100644 index bb84bff..0000000 --- a/apps/assets/src/assets.service.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { Injectable } from '@nestjs/common'; - -@Injectable() -export class AssetsService { - getHello(): string { - return 'Hello World!'; - } -} diff --git a/apps/assets/src/config/s3.config.ts b/apps/assets/src/config/s3.config.ts new file mode 100644 index 0000000..b497a1e --- /dev/null +++ b/apps/assets/src/config/s3.config.ts @@ -0,0 +1,8 @@ +import { registerAs } from '@nestjs/config'; + +export const s3Config = registerAs('s3', () => ({ + endpoint: process.env.S3_ENDPOINT, + bucket: process.env.S3_BUCKET, + region: process.env.S3_REGION, + forcePathStyle: !!process.env.S3_ENDPOINT, +})); diff --git a/apps/assets/src/database/entities/asset.entity.ts b/apps/assets/src/database/entities/asset.entity.ts new file mode 100644 index 0000000..8a5a89a --- /dev/null +++ b/apps/assets/src/database/entities/asset.entity.ts @@ -0,0 +1,56 @@ +import { MetaEntity } from '@freeblox/shared'; +import { Exclude, Expose } from 'class-transformer'; +import { + Column, + DeleteDateColumn, + Entity, + PrimaryGeneratedColumn, +} from 'typeorm'; + +@Entity('assets') +@Expose() +export class AssetEntity extends MetaEntity { + @PrimaryGeneratedColumn('uuid') + id: string; + + @Column({ nullable: true, name: 'user_id' }) + userId: string; + + @Column({ nullable: true, name: 'asset_tag' }) + assetTag: string; + + @Column({ name: 'source_uri' }) + @Exclude() + sourceUri: string; + + @Column({ nullable: true }) + @Exclude() + source: string; + + @Column() + @Exclude() + originalname: string; + + @Column({ default: 'application/octet-stream' }) + mimetype: string; + + @Column({ unsigned: true }) + filesize: number; + + @Column({ default: false }) + @Exclude() + public: boolean; + + @Column({ nullable: true, name: 'upload_ip' }) + @Exclude() + uploadIp: string; + + @DeleteDateColumn({ name: 'deleted_at' }) + @Exclude() + deletedAt: Date; + + @Expose() + get extension() { + return this.originalname.split('.').at(-1); + } +} diff --git a/apps/assets/src/database/migrations/20230722190600_assets.ts b/apps/assets/src/database/migrations/20230722190600_assets.ts new file mode 100644 index 0000000..4ea7bd6 --- /dev/null +++ b/apps/assets/src/database/migrations/20230722190600_assets.ts @@ -0,0 +1,22 @@ +import { Knex } from 'knex'; + +export async function up(knex: Knex): Promise { + return knex.schema.createTable('assets', (table) => { + table.uuid('id').primary().defaultTo(knex.raw('uuid_generate_v4()')); + table.uuid('user_id').nullable(); + table.text('asset_tag').nullable(); + table.text('source_uri').notNullable(); + table.text('source').nullable(); + table.text('originalname').notNullable(); + table.string('mimetype').notNullable(); + table.integer('filesize').unsigned().notNullable(); + table.text('upload_ip').nullable(); + table.boolean('public').defaultTo(false); + table.timestamps(true, true); + table.timestamp('deleted_at'); + }); +} + +export async function down(knex: Knex): Promise { + return knex.schema.dropTable('assets'); +} diff --git a/apps/assets/src/interfaces/upload-request.interface.ts b/apps/assets/src/interfaces/upload-request.interface.ts new file mode 100644 index 0000000..8b8e996 --- /dev/null +++ b/apps/assets/src/interfaces/upload-request.interface.ts @@ -0,0 +1,13 @@ +export interface AssetUploadRequest { + id?: string; + userId?: string; + assetTag?: string; + + buffer: Buffer; + originalname: string; + mimetype: string; + filesize: number; + + public?: boolean; + uploadIp?: string; +} diff --git a/apps/assets/src/knexfile.ts b/apps/assets/src/knexfile.ts new file mode 100644 index 0000000..c392fd7 --- /dev/null +++ b/apps/assets/src/knexfile.ts @@ -0,0 +1,5 @@ +import { getKnex } from '../../../libs/shared/src/'; + +module.exports = { + development: getKnex('assets', __dirname, ['.ts', '.js']), +}; diff --git a/apps/assets/src/main.ts b/apps/assets/src/main.ts index 0a545a5..f4816e4 100644 --- a/apps/assets/src/main.ts +++ b/apps/assets/src/main.ts @@ -1,6 +1,7 @@ import { NestFactory } from '@nestjs/core'; import { AssetsModule } from './assets.module'; import { MicroserviceOptions, Transport } from '@nestjs/microservices'; +import { HttpRpcExceptionFilter } from '@freeblox/shared'; async function bootstrap() { const app = await NestFactory.createMicroservice( @@ -13,6 +14,7 @@ async function bootstrap() { }, ); + app.useGlobalFilters(new HttpRpcExceptionFilter()); await app.listen(); } bootstrap(); diff --git a/apps/assets/src/services/assets.service.ts b/apps/assets/src/services/assets.service.ts new file mode 100644 index 0000000..57b7ff9 --- /dev/null +++ b/apps/assets/src/services/assets.service.ts @@ -0,0 +1,137 @@ +import { Injectable } from '@nestjs/common'; +import { S3Service } from './s3.service'; +import { AssetUploadRequest } from '../interfaces/upload-request.interface'; +import { + NotFoundRpcException, + UnauthorizedRpcException, + UserInfo, +} from '@freeblox/shared'; +import { InjectEntityManager } from '@nestjs/typeorm'; +import { EntityManager } from 'typeorm'; +import { AssetEntity } from '../database/entities/asset.entity'; +import { ConfigService } from '@nestjs/config'; +import { instanceToPlain } from 'class-transformer'; + +@Injectable() +export class AssetsService { + constructor( + @InjectEntityManager() private manager: EntityManager, + private readonly s3Service: S3Service, + private readonly config: ConfigService, + ) {} + + /** + * Upload a new asset. + * @param body Upload info + * @param user User + * @returns Uploaded asset + */ + async uploadFile(body: AssetUploadRequest, user?: UserInfo) { + const file = Buffer.from(body.buffer as any, 'base64'); + const uploaded = this.manager.transaction(async (manager) => { + const asset = manager.create(AssetEntity, { + id: body.id, + userId: body.userId || user?.sub, + assetTag: body.assetTag, + sourceUri: 'fblxassetid://stub', + uploadIp: body.uploadIp, + public: body.public, + originalname: body.originalname, + mimetype: body.mimetype, + filesize: body.filesize, + }); + + // Create ID if not provided + await manager.save(AssetEntity, asset); + + // Upload to S3 + const key = `fblxassetid-${asset.id}`; + const bucket = this.config.get('s3.bucket'); + await this.s3Service.uploadFile(key, file, bucket); + + // Save S3 keys + asset.source = bucket; + asset.sourceUri = key; + return manager.save(AssetEntity, asset); + }); + + return instanceToPlain(uploaded); + } + + /** + * Get asset info by ID. + * @param id Asset ID + * @param user (optional) User + * @returns Asset info + */ + async getAssetInfoById(id: string, user?: UserInfo) { + const asset = await this.manager.findOne(AssetEntity, { + where: { + deletedAt: null, + id: id, + }, + }); + + if (!asset) { + throw new NotFoundRpcException('Asset not found'); + } + + if (!user && !asset.public) { + throw new UnauthorizedRpcException('Unauthorized'); + } + + return instanceToPlain(asset); + } + + /** + * Download asset by ID. + * @param id Asset ID + * @param user (optional) User + * @returns Buffer + */ + async downloadAssetById(id: string, user?: UserInfo) { + const asset = await this.manager.findOne(AssetEntity, { + where: { + deletedAt: null, + id: id, + }, + }); + + if (!asset) { + throw new NotFoundRpcException('Asset not found'); + } + + if (!user && !asset.public) { + throw new UnauthorizedRpcException('Unauthorized'); + } + + const url = await this.s3Service.getFileUrl(asset.sourceUri, asset.source); + return { + url, + mimetype: asset.mimetype, + filename: `${asset.id}.${asset.extension}`, + }; + } + + /** + * Delete asset. + * @param id Asset ID + */ + async deleteAsset(id: string) { + const asset = await this.manager.findOne(AssetEntity, { + where: { + deletedAt: null, + id: id, + }, + }); + + if (!asset) { + throw new NotFoundRpcException('Asset not found'); + } + + await this.manager.softRemove(AssetEntity, asset); + try { + await this.s3Service.deleteFile(asset.sourceUri, asset.source); + } catch {} + } +} diff --git a/apps/assets/src/services/s3.service.ts b/apps/assets/src/services/s3.service.ts new file mode 100644 index 0000000..c255228 --- /dev/null +++ b/apps/assets/src/services/s3.service.ts @@ -0,0 +1,78 @@ +import { Injectable, OnModuleInit } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { + DeleteObjectCommand, + PutObjectCommand, + S3Client, +} from '@aws-sdk/client-s3'; +import { BadRequestRpcException } from '@freeblox/shared'; +import { HttpRequest } from '@aws-sdk/protocol-http'; +import { S3RequestPresigner } from '@aws-sdk/s3-request-presigner'; +import { parseUrl } from '@aws-sdk/url-parser'; +import { formatUrl } from '@aws-sdk/util-format-url'; +import { Hash } from '@aws-sdk/hash-node'; +import { fromEnv } from '@aws-sdk/credential-providers'; + +@Injectable() +export class S3Service implements OnModuleInit { + private s3: S3Client; + private presigner: S3RequestPresigner; + + constructor(private config: ConfigService) {} + + onModuleInit() { + const s3Config = this.config.get('s3'); + this.s3 = new S3Client({ + ...s3Config, + }); + this.presigner = new S3RequestPresigner({ + credentials: fromEnv(), + region: s3Config.region, + sha256: Hash.bind(null, 'sha256'), + }); + } + + async uploadFile(key: string, data: Buffer, bucket?: string) { + try { + const command = new PutObjectCommand({ + Key: key, + Bucket: bucket || this.config.get('s3.bucket'), + Body: data, + }); + await this.s3.send(command); + } catch (error) { + throw new BadRequestRpcException(`Upload failed: ${error.message}`); + } + } + + async getFileUrl(key: string, bucket?: string) { + const s3Config = this.config.get('s3'); + bucket = bucket || s3Config.bucket; + + let url: ReturnType; + if (s3Config.endpoint) { + url = parseUrl(`${s3Config.endpoint}/${bucket}/${key}`); + } else { + url = parseUrl( + `https://${bucket}.s3${ + s3Config.region ? '-' + s3Config.region : '' + }.amazonaws.com/${key}`, + ); + } + + const signedUrlObject = await this.presigner.presign(new HttpRequest(url)); + return formatUrl(signedUrlObject); + } + + async deleteFile(key: string, bucket?: string) { + try { + const command = new DeleteObjectCommand({ + Key: key, + Bucket: bucket || this.config.get('s3.bucket'), + }); + await this.s3.send(command); + } catch { + throw new BadRequestRpcException('Delete failed'); + } + } +} diff --git a/apps/catalog/src/catalog.controller.ts b/apps/catalog/src/catalog.controller.ts index 3adbc60..12c528b 100644 --- a/apps/catalog/src/catalog.controller.ts +++ b/apps/catalog/src/catalog.controller.ts @@ -96,6 +96,26 @@ export class CatalogController { ); } + @MessagePattern('catalog.items.appendToRevision') + async appendToRevision({ + id, + body, + user, + files, + }: { + id: number; + body: CreateContentRevisionRequest; + user: UserInfo; + files?: Express.Multer.File[]; + }) { + return this.createContentService.addFilesToLatestRevision( + id, + body.files, + files, + user, + ); + } + @MessagePattern('catalog.items.update') async updateItem({ id, diff --git a/apps/catalog/src/catalog.module.ts b/apps/catalog/src/catalog.module.ts index ac7a928..a254631 100644 --- a/apps/catalog/src/catalog.module.ts +++ b/apps/catalog/src/catalog.module.ts @@ -60,6 +60,7 @@ const entities = [ natsClient('catalog'), natsClient('auth'), natsClient('player'), + natsClient('assets'), ]), ], controllers: [CatalogController], diff --git a/apps/catalog/src/database/entities/content-asset.entity.ts b/apps/catalog/src/database/entities/content-asset.entity.ts index efcd197..7fa985e 100644 --- a/apps/catalog/src/database/entities/content-asset.entity.ts +++ b/apps/catalog/src/database/entities/content-asset.entity.ts @@ -9,6 +9,7 @@ import { import { ContentAssetType } from '../../enums/content-asset-type.enum'; import { ContentRevisionEntity } from './content-revision.entity'; import { ContentEntity } from './content.entity'; +import { AssetSource } from '../../enums/asset-source.enum'; @Entity('content_asset') @Expose() @@ -31,6 +32,9 @@ export class ContentAssetEntity { @Column({ name: 'type_name', nullable: true }) typeName?: string; + @Column({ type: 'enum', enum: AssetSource, default: AssetSource.USER }) + source: AssetSource; + @Column({ default: 0 }) index: number; diff --git a/apps/catalog/src/database/migrations/20230722091907_content-asset.ts b/apps/catalog/src/database/migrations/20230722091907_content-asset.ts index ef16cf3..2d5a711 100644 --- a/apps/catalog/src/database/migrations/20230722091907_content-asset.ts +++ b/apps/catalog/src/database/migrations/20230722091907_content-asset.ts @@ -10,6 +10,7 @@ export async function up(knex: Knex): Promise { table.string('type').notNullable().index(); table.string('type_name').nullable(); + table.enum('source', ['user', 'generated']).notNullable().defaultTo('user'); table.integer('index').notNullable().defaultTo(0); table.foreign('content_id').references('content.id').onDelete('CASCADE'); diff --git a/apps/catalog/src/enums/asset-source.enum.ts b/apps/catalog/src/enums/asset-source.enum.ts new file mode 100644 index 0000000..f0d8f0e --- /dev/null +++ b/apps/catalog/src/enums/asset-source.enum.ts @@ -0,0 +1,4 @@ +export enum AssetSource { + USER = 'user', + GENERATED = 'generated', +} diff --git a/apps/catalog/src/interfaces/create-content-request.interface.ts b/apps/catalog/src/interfaces/create-content-request.interface.ts index 94907a8..54cdfd1 100644 --- a/apps/catalog/src/interfaces/create-content-request.interface.ts +++ b/apps/catalog/src/interfaces/create-content-request.interface.ts @@ -1,3 +1,4 @@ +import { AssetSource } from '../enums/asset-source.enum'; import { ContentAssetType } from '../enums/content-asset-type.enum'; import { ContentType } from '../enums/content-type.enum'; import { Currency } from '../enums/currency.enum'; @@ -6,6 +7,7 @@ import { Privacy } from '../enums/privacy.enum'; export interface ContentAssetTypeProperties { type: ContentAssetType; typeName?: string; + source?: AssetSource; } export interface CreateContentFiles { diff --git a/apps/catalog/src/services/create-content.service.ts b/apps/catalog/src/services/create-content.service.ts index c0b5483..acde0aa 100644 --- a/apps/catalog/src/services/create-content.service.ts +++ b/apps/catalog/src/services/create-content.service.ts @@ -36,14 +36,15 @@ import { ModeratorAction } from '../enums/moderation-action.enum'; import { instanceToPlain } from 'class-transformer'; import { ContentAssetEntity } from '../database/entities/content-asset.entity'; import { ContentAssetType } from '../enums/content-asset-type.enum'; -import { randomUUID } from 'crypto'; import { lastValueFrom } from 'rxjs'; +import { AssetSource } from '../enums/asset-source.enum'; @Injectable() export class CreateContentService { constructor( @InjectEntityManager() private manager: EntityManager, @Inject('auth') private authClient: ClientProxy, + @Inject('assets') private assetsClient: ClientProxy, @Inject('catalog') private client: ClientProxy, ) {} @@ -300,7 +301,14 @@ export class CreateContentService { }); // Save assets - await this.uploadAssets(content, revision, body.files, files, manager); + await this.uploadAssets( + content, + revision, + body.files, + files, + manager, + user, + ); // Moderator review await this.createPendingReview(content, revision, manager); @@ -311,6 +319,45 @@ export class CreateContentService { return instanceToPlain(newRevision); } + /** + * Add files to current revision, usually generated thumnails / icons. + * @param contentId Content ID + * @param types File types + * @param files Files + * @param user User + * @returns Current revision + */ + async addFilesToLatestRevision( + contentId: number, + types: CreateContentFiles = {}, + files: Express.Multer.File[] = [], + user?: UserInfo, + ) { + const latestRevision = await this.manager.findOne(ContentRevisionEntity, { + where: { + content: { id: contentId }, + }, + order: { createdAt: 'DESC' }, + relations: ['assets', 'content'], + }); + + if (!latestRevision || !!latestRevision.deletedAt) { + throw new BadRequestRpcException('No valid revision exists'); + } + + // Save assets + await this.uploadAssets( + latestRevision.content, + latestRevision, + types, + files, + undefined, + user, + ); + + return instanceToPlain(latestRevision); + } + /** * Create a pending moderator review for content revision. * @private @@ -343,6 +390,7 @@ export class CreateContentService { types: CreateContentFiles = {}, files: Express.Multer.File[] = [], manager = this.manager, + user?: UserInfo, ) { const typeKeys = Object.keys(types); if (!typeKeys.length) return []; @@ -359,6 +407,12 @@ export class CreateContentService { ); } + if (!!user && fileType.source === AssetSource.GENERATED) { + throw new BadRequestRpcException( + `File ${fileKey} cannot be source of generated - Users cannot upload generated assets`, + ); + } + const allowed = await this.checkAssetType( content.type, fileType, @@ -376,17 +430,33 @@ export class CreateContentService { const asset = manager.create(ContentAssetEntity, { content, revision, - assetId: randomUUID(), type: fileType.type, typeName: fileType.typeName, + source: fileType.source, index: fileIndex++, }); const assetErrors = await validate(asset); if (assetErrors?.length) new ValidationRpcException(); - // TODO: actually upload the files somewhere // TODO: convert file types into universal formats + const assetObject = await lastValueFrom( + this.assetsClient.send('assets.upload', { + body: { + id: asset.assetId, + userId: user?.sub, + assetTag: content.id, + originalname: uploadedFile.originalname, + mimetype: uploadedFile.mimetype, + filesize: uploadedFile.size, + buffer: Buffer.from(uploadedFile.buffer).toString('base64'), + public: true, + }, + user, + }), + ); + + asset.assetId = assetObject.id; await manager.save(ContentAssetEntity, asset); createdAssets.push(asset); diff --git a/apps/freeblox-web-service/src/app.module.ts b/apps/freeblox-web-service/src/app.module.ts index 2c470fb..ad3d2c5 100644 --- a/apps/freeblox-web-service/src/app.module.ts +++ b/apps/freeblox-web-service/src/app.module.ts @@ -1,26 +1,25 @@ -import { Module } from '@nestjs/common'; +import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { natsClient } from '@freeblox/shared'; import { ClientsModule } from '@nestjs/microservices'; import { AuthModule } from './services/auth/auth.module'; import { CatalogModule } from './services/catalog/catalog.module'; +import { UserMiddleware } from './middleware/user.middleware'; +import { AssetsModule } from './services/assets/assets.module'; @Module({ imports: [ - ClientsModule.register([ - natsClient('auth'), - natsClient('catalog'), - natsClient('game'), - natsClient('session'), - natsClient('player'), - natsClient('server'), - natsClient('session'), - ]), + ClientsModule.register([natsClient('auth')]), AuthModule, CatalogModule, + AssetsModule, ], controllers: [AppController], providers: [AppService], }) -export class AppModule {} +export class AppModule implements NestModule { + configure(consumer: MiddlewareConsumer) { + consumer.apply(UserMiddleware).forRoutes('*'); + } +} diff --git a/apps/freeblox-web-service/src/decorators/privilege.decorator.ts b/apps/freeblox-web-service/src/decorators/privilege.decorator.ts new file mode 100644 index 0000000..bfc7a07 --- /dev/null +++ b/apps/freeblox-web-service/src/decorators/privilege.decorator.ts @@ -0,0 +1,4 @@ +import { SetMetadata } from '@nestjs/common'; + +export const Privilege = (...privileges: string[]) => + SetMetadata('privileges', privileges); diff --git a/apps/freeblox-web-service/src/decorators/require-privileges.decorator.ts b/apps/freeblox-web-service/src/decorators/require-privileges.decorator.ts new file mode 100644 index 0000000..6e2f04f --- /dev/null +++ b/apps/freeblox-web-service/src/decorators/require-privileges.decorator.ts @@ -0,0 +1,14 @@ +import { UseGuards, applyDecorators } from '@nestjs/common'; +import { Privilege } from './privilege.decorator'; +import { PrivilegesGuard } from '../guards/privileges.guard'; +import { ApiBearerAuth, ApiForbiddenResponse } from '@nestjs/swagger'; + +export const RequirePrivileges = (...privileges: string[]) => + applyDecorators( + Privilege(...privileges), + UseGuards(PrivilegesGuard), + ApiBearerAuth(), + ApiForbiddenResponse({ + description: `Privileges required: ${privileges.join(' or ')}`, + }), + ); diff --git a/apps/freeblox-web-service/src/guards/auth.guard.ts b/apps/freeblox-web-service/src/guards/auth.guard.ts index b33bb55..7db4f0b 100644 --- a/apps/freeblox-web-service/src/guards/auth.guard.ts +++ b/apps/freeblox-web-service/src/guards/auth.guard.ts @@ -1,34 +1,10 @@ -import { - CanActivate, - ExecutionContext, - HttpException, - HttpStatus, - Inject, - Injectable, -} from '@nestjs/common'; -import { ClientProxy } from '@nestjs/microservices'; -import { Request, Response } from 'express'; -import { lastValueFrom } from 'rxjs'; +import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common'; +import { Response } from 'express'; @Injectable() export class AuthGuard implements CanActivate { - constructor(@Inject('auth') private authClient: ClientProxy) {} - async canActivate(context: ExecutionContext): Promise { - const request = context.switchToHttp().getRequest() as Request; const response = context.switchToHttp().getResponse() as Response; - if (!request.headers.authorization) return false; - - // Verify token by auth microservice - const [, token] = request.headers.authorization.split(' '); - const user = await lastValueFrom( - this.authClient.send('auth.verify', { token }), - ).catch((err) => { - throw new HttpException(err.response, err.status || HttpStatus.FORBIDDEN); - }); - - // Add token contents to locals - response.locals.user = user; - return true; + return !!response.locals.user; } } diff --git a/apps/freeblox-web-service/src/guards/privileges.guard.ts b/apps/freeblox-web-service/src/guards/privileges.guard.ts new file mode 100644 index 0000000..40c8472 --- /dev/null +++ b/apps/freeblox-web-service/src/guards/privileges.guard.ts @@ -0,0 +1,25 @@ +import { UserInfo, matchPrivileges } from '@freeblox/shared'; +import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common'; +import { Reflector } from '@nestjs/core'; +import { Response } from 'express'; + +@Injectable() +export class PrivilegesGuard implements CanActivate { + constructor(private reflector: Reflector) {} + + async canActivate(context: ExecutionContext): Promise { + const privileges = this.reflector.get( + 'privileges', + context.getHandler(), + ); + + if (!privileges) { + return true; + } + + const response = context.switchToHttp().getResponse() as Response; + const user = response.locals.user as UserInfo; + if (!user) return false; + return matchPrivileges(privileges, user.privileges || []); + } +} diff --git a/apps/freeblox-web-service/src/middleware/user.middleware.ts b/apps/freeblox-web-service/src/middleware/user.middleware.ts new file mode 100644 index 0000000..4f2d160 --- /dev/null +++ b/apps/freeblox-web-service/src/middleware/user.middleware.ts @@ -0,0 +1,34 @@ +import { + HttpException, + HttpStatus, + Inject, + Injectable, + NestMiddleware, +} from '@nestjs/common'; +import { ClientProxy } from '@nestjs/microservices'; +import { Request, Response, NextFunction } from 'express'; +import { lastValueFrom } from 'rxjs'; + +@Injectable() +export class UserMiddleware implements NestMiddleware { + constructor(@Inject('auth') private authClient: ClientProxy) {} + + async use(req: Request, res: Response, next: NextFunction) { + if (!req.headers.authorization) return next(); + + // Verify token by auth microservice + const [, token] = req.headers.authorization.split(' '); + const user = await lastValueFrom( + this.authClient.send('auth.verify', { token }), + ).catch((err) => { + throw new HttpException( + err.response, + Number(err.status) || HttpStatus.FORBIDDEN, + ); + }); + + // Add token contents to locals + res.locals.user = user; + next(); + } +} diff --git a/apps/freeblox-web-service/src/services/assets/assets.controller.ts b/apps/freeblox-web-service/src/services/assets/assets.controller.ts new file mode 100644 index 0000000..6604ee2 --- /dev/null +++ b/apps/freeblox-web-service/src/services/assets/assets.controller.ts @@ -0,0 +1,61 @@ +import { + Controller, + UseInterceptors, + ClassSerializerInterceptor, + Inject, + Get, + Param, + ParseUUIDPipe, + Res, +} from '@nestjs/common'; +import { ClientProxy } from '@nestjs/microservices'; +import { ApiTags, ApiOperation } from '@nestjs/swagger'; +import { User } from '../../decorators/user.decorator'; +import { UserInfo } from '@freeblox/shared'; +import { lastValueFrom } from 'rxjs'; +import type { Response } from 'express'; +import { HttpService } from '@nestjs/axios'; + +@Controller({ + version: '1', + path: 'assets', +}) +@ApiTags('Assets') +@UseInterceptors(ClassSerializerInterceptor) +export class AssetsController { + constructor( + @Inject('assets') private assets: ClientProxy, + private http: HttpService, + ) {} + + @Get(':assetId') + @ApiOperation({ summary: 'Get asset info' }) + async getAsset( + @Param('assetId', new ParseUUIDPipe()) assetId: string, + @User() user?: UserInfo, + ) { + return this.assets.send('assets.info.byId', { id: assetId, user }); + } + + @Get(':assetId/download') + @ApiOperation({ summary: 'Download asset' }) + async downloadAsset( + @Param('assetId', new ParseUUIDPipe()) assetId: string, + @Res() res: Response, + @User() user?: UserInfo, + ) { + const download = await lastValueFrom( + this.assets.send('assets.download.byId', { id: assetId, user }), + ); + + const { data } = await lastValueFrom( + this.http.get(download.url, { responseType: 'stream' }), + ); + + res.set({ + 'Content-Type': download.mimetype, + 'Content-Disposition': `attachment; filename="${download.filename}"`, + }); + data.pipe(res); + } +} diff --git a/apps/freeblox-web-service/src/services/assets/assets.module.ts b/apps/freeblox-web-service/src/services/assets/assets.module.ts new file mode 100644 index 0000000..f35b843 --- /dev/null +++ b/apps/freeblox-web-service/src/services/assets/assets.module.ts @@ -0,0 +1,14 @@ +import { natsClient } from '@freeblox/shared'; +import { Module } from '@nestjs/common'; +import { ClientsModule } from '@nestjs/microservices'; +import { AssetsController } from './assets.controller'; +import { HttpModule } from '@nestjs/axios'; + +@Module({ + imports: [ + ClientsModule.register([natsClient('assets'), natsClient('auth')]), + HttpModule, + ], + controllers: [AssetsController], +}) +export class AssetsModule {} diff --git a/apps/freeblox-web-service/src/services/catalog/catalog.controller.ts b/apps/freeblox-web-service/src/services/catalog/catalog.controller.ts index bf34b45..647c851 100644 --- a/apps/freeblox-web-service/src/services/catalog/catalog.controller.ts +++ b/apps/freeblox-web-service/src/services/catalog/catalog.controller.ts @@ -7,34 +7,28 @@ import { Post, Body, UploadedFiles, - UseGuards, Patch, Param, ParseIntPipe, } from '@nestjs/common'; import { ClientProxy } from '@nestjs/microservices'; -import { - ApiBearerAuth, - ApiOkResponse, - ApiOperation, - ApiTags, -} from '@nestjs/swagger'; +import { ApiOkResponse, ApiOperation, ApiTags } from '@nestjs/swagger'; import { User } from '../../decorators/user.decorator'; import { UserInfo } from '@freeblox/shared'; import { CreateContentDto } from './dtos/create-content.dto'; import { AnyFilesInterceptor } from '@nestjs/platform-express'; -import { AuthGuard } from '../../guards/auth.guard'; import { CreateContentRevisionDto } from './dtos/create-content-revision.dto'; import { ContentResponseDto } from './dtos/content-response.dto'; import { ContentAssetType } from 'apps/catalog/src/enums/content-asset-type.enum'; import { lastValueFrom } from 'rxjs'; import { CategoryResponseDto } from './dtos/category-response.dto'; +import { ContentAssetDto } from './dtos/content-asset.dto'; +import { RequirePrivileges } from '../../decorators/require-privileges.decorator'; @Controller({ version: '1', path: 'catalog', }) -@ApiBearerAuth() @ApiTags('Catalog') @UseInterceptors(ClassSerializerInterceptor) export class CatalogController { @@ -50,7 +44,7 @@ export class CatalogController { @Post('content') @ApiOperation({ summary: 'Create new content' }) @ApiOkResponse({ type: ContentResponseDto }) - @UseGuards(AuthGuard) + @RequirePrivileges('create:*', 'contentedit') @UseInterceptors(AnyFilesInterceptor()) async createContent( @User() user: UserInfo, @@ -63,7 +57,6 @@ export class CatalogController { @Get('content/:id') @ApiOperation({ summary: 'Get content details' }) @ApiOkResponse({ type: ContentResponseDto }) - @UseGuards(AuthGuard) async getContent( @Param('id', new ParseIntPipe()) id: number, @User() user: UserInfo, @@ -73,8 +66,7 @@ export class CatalogController { @Get('content/:id/thumbnail') @ApiOperation({ summary: 'Get content thumbnail ID' }) - @ApiOkResponse({ type: ContentResponseDto }) - @UseGuards(AuthGuard) + @ApiOkResponse({ type: ContentAssetDto }) async getContentThumbnail( @Param('id', new ParseIntPipe()) id: number, @User() user: UserInfo, @@ -93,7 +85,7 @@ export class CatalogController { @Patch('content/:id') @ApiOperation({ summary: 'Update content details' }) @ApiOkResponse({ type: ContentResponseDto }) - @UseGuards(AuthGuard) + @RequirePrivileges('create:*', 'contentedit') async updateContent( @Param('id', new ParseIntPipe()) id: number, @User() user: UserInfo, @@ -106,7 +98,7 @@ export class CatalogController { @ApiOperation({ summary: 'Create a new revision (upload new content for item)', }) - @UseGuards(AuthGuard) + @RequirePrivileges('create:*', 'contentedit') @UseInterceptors(AnyFilesInterceptor()) async createContentRevision( @Param('id', new ParseIntPipe()) id: number, @@ -127,7 +119,7 @@ export class CatalogController { @ApiOperation({ summary: 'Publish content', }) - @UseGuards(AuthGuard) + @RequirePrivileges('create:*', 'contentedit') async publishContent( @Param('id', new ParseIntPipe()) id: number, @User() user: UserInfo, diff --git a/apps/freeblox-web-service/src/services/catalog/dtos/content-asset.dto.ts b/apps/freeblox-web-service/src/services/catalog/dtos/content-asset.dto.ts new file mode 100644 index 0000000..28b9d63 --- /dev/null +++ b/apps/freeblox-web-service/src/services/catalog/dtos/content-asset.dto.ts @@ -0,0 +1,6 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class ContentAssetDto { + @ApiProperty() + assetId: string; +} diff --git a/apps/freeblox-web-service/src/services/catalog/dtos/content-response.dto.ts b/apps/freeblox-web-service/src/services/catalog/dtos/content-response.dto.ts index 4bccd90..98e8b9c 100644 --- a/apps/freeblox-web-service/src/services/catalog/dtos/content-response.dto.ts +++ b/apps/freeblox-web-service/src/services/catalog/dtos/content-response.dto.ts @@ -1,6 +1,7 @@ import { ApiProperty } from '@nestjs/swagger'; import { UserResponseDto } from './user-response.dto'; import { Currency } from 'apps/catalog/src/enums/currency.enum'; +import { ContentAssetDto } from './content-asset.dto'; export class ContentPriceResponseDto { @ApiProperty() @@ -64,4 +65,7 @@ export class ContentResponseDto { @ApiProperty({ type: UserResponseDto }) user: UserResponseDto; + + @ApiProperty({ type: ContentAssetDto, isArray: true }) + assets: ContentAssetDto[]; } diff --git a/docker-compose.yml b/docker-compose.yml index 852a7bd..d9f2e41 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -158,6 +158,11 @@ services: - POSTGRES_HOST=postgres - POSTGRES_USER=freeblox - POSTGRES_PASSWORD=FREEBLOXDataBaseDEV@123 + - S3_ENDPOINT=http://minio:9000 + - S3_BUCKET=freeblox-assets + - S3_REGION=eu-central-1 + - AWS_ACCESS_KEY_ID=freeblox@freeblox.gg + - AWS_SECRET_ACCESS_KEY=password volumes: - ./apps:/usr/src/app/apps - ./libs:/usr/src/app/libs @@ -189,6 +194,20 @@ services: environment: - PGADMIN_DEFAULT_EMAIL=freeblox@freeblox.gg - PGADMIN_DEFAULT_PASSWORD=password + minio: + container_name: fblx-minio + image: minio/minio + ports: + - '9000:9000' + - '9001:9001' + networks: + - fblx + environment: + - MINIO_ROOT_USER=freeblox@freeblox.gg + - MINIO_ROOT_PASSWORD=password + volumes: + - ./storage:/data + command: server --console-address ":9001" /data networks: fblx: volumes: diff --git a/libs/shared/src/index.ts b/libs/shared/src/index.ts index 67b5916..e097032 100644 --- a/libs/shared/src/index.ts +++ b/libs/shared/src/index.ts @@ -3,6 +3,7 @@ export * from './shared.service'; export * from './utils/nats-client'; export * from './utils/tokens'; export * from './utils/parse-boolean'; +export * from './utils/match-privileges'; export * from './database/make-typeorm'; export * from './database/make-knex'; export * from './database/metaentity'; diff --git a/libs/shared/src/utils/match-privileges.ts b/libs/shared/src/utils/match-privileges.ts new file mode 100644 index 0000000..bf99007 --- /dev/null +++ b/libs/shared/src/utils/match-privileges.ts @@ -0,0 +1,21 @@ +export const matchPrivileges = (required: string[], privileges: string[]) => { + let someAvailable = false; + for (const want of required) { + if (someAvailable) break; + if (privileges.includes(want)) { + someAvailable = true; + continue; + } + + if (want.endsWith('*')) { + const asteriskLess = want.replace('*', ''); + + if (privileges.some((item) => item.startsWith(asteriskLess))) { + someAvailable = true; + continue; + } + } + } + + return someAvailable; +}; diff --git a/package.json b/package.json index 82cbe6b..34dea92 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,13 @@ "test:e2e": "jest --config ./apps/freeblox-web-service/test/jest-e2e.json" }, "dependencies": { + "@aws-sdk/client-s3": "^3.374.0", + "@aws-sdk/credential-providers": "^3.370.0", + "@aws-sdk/hash-node": "^3.374.0", + "@aws-sdk/protocol-http": "^3.374.0", + "@aws-sdk/s3-request-presigner": "^3.375.0", + "@aws-sdk/url-parser": "^3.374.0", + "@aws-sdk/util-format-url": "^3.370.0", "@nestjs/axios": "^3.0.0", "@nestjs/common": "^10.0.3", "@nestjs/config": "^3.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index adb7d6b..37a174d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5,6 +5,27 @@ settings: excludeLinksFromLockfile: false dependencies: + '@aws-sdk/client-s3': + specifier: ^3.374.0 + version: 3.374.0 + '@aws-sdk/credential-providers': + specifier: ^3.370.0 + version: 3.370.0 + '@aws-sdk/hash-node': + specifier: ^3.374.0 + version: 3.374.0 + '@aws-sdk/protocol-http': + specifier: ^3.374.0 + version: 3.374.0 + '@aws-sdk/s3-request-presigner': + specifier: ^3.375.0 + version: 3.375.0 + '@aws-sdk/url-parser': + specifier: ^3.374.0 + version: 3.374.0 + '@aws-sdk/util-format-url': + specifier: ^3.370.0 + version: 3.370.0 '@nestjs/axios': specifier: ^3.0.0 version: 3.0.0(@nestjs/common@10.0.3)(axios@1.4.0)(reflect-metadata@0.1.13)(rxjs@7.8.1) @@ -212,6 +233,714 @@ packages: - chokidar dev: true + /@aws-crypto/crc32@3.0.0: + resolution: {integrity: sha512-IzSgsrxUcsrejQbPVilIKy16kAT52EwB6zSaI+M3xxIhKh5+aldEyvI+z6erM7TCLB2BJsFrtHjp6/4/sr+3dA==} + dependencies: + '@aws-crypto/util': 3.0.0 + '@aws-sdk/types': 3.370.0 + tslib: 1.14.1 + dev: false + + /@aws-crypto/crc32c@3.0.0: + resolution: {integrity: sha512-ENNPPManmnVJ4BTXlOjAgD7URidbAznURqD0KvfREyc4o20DPYdEldU1f5cQ7Jbj0CJJSPaMIk/9ZshdB3210w==} + dependencies: + '@aws-crypto/util': 3.0.0 + '@aws-sdk/types': 3.370.0 + tslib: 1.14.1 + dev: false + + /@aws-crypto/ie11-detection@3.0.0: + resolution: {integrity: sha512-341lBBkiY1DfDNKai/wXM3aujNBkXR7tq1URPQDL9wi3AUbI80NR74uF1TXHMm7po1AcnFk8iu2S2IeU/+/A+Q==} + dependencies: + tslib: 1.14.1 + dev: false + + /@aws-crypto/sha1-browser@3.0.0: + resolution: {integrity: sha512-NJth5c997GLHs6nOYTzFKTbYdMNA6/1XlKVgnZoaZcQ7z7UJlOgj2JdbHE8tiYLS3fzXNCguct77SPGat2raSw==} + dependencies: + '@aws-crypto/ie11-detection': 3.0.0 + '@aws-crypto/supports-web-crypto': 3.0.0 + '@aws-crypto/util': 3.0.0 + '@aws-sdk/types': 3.370.0 + '@aws-sdk/util-locate-window': 3.310.0 + '@aws-sdk/util-utf8-browser': 3.259.0 + tslib: 1.14.1 + dev: false + + /@aws-crypto/sha256-browser@3.0.0: + resolution: {integrity: sha512-8VLmW2B+gjFbU5uMeqtQM6Nj0/F1bro80xQXCW6CQBWgosFWXTx77aeOF5CAIAmbOK64SdMBJdNr6J41yP5mvQ==} + dependencies: + '@aws-crypto/ie11-detection': 3.0.0 + '@aws-crypto/sha256-js': 3.0.0 + '@aws-crypto/supports-web-crypto': 3.0.0 + '@aws-crypto/util': 3.0.0 + '@aws-sdk/types': 3.370.0 + '@aws-sdk/util-locate-window': 3.310.0 + '@aws-sdk/util-utf8-browser': 3.259.0 + tslib: 1.14.1 + dev: false + + /@aws-crypto/sha256-js@3.0.0: + resolution: {integrity: sha512-PnNN7os0+yd1XvXAy23CFOmTbMaDxgxXtTKHybrJ39Y8kGzBATgBFibWJKH6BhytLI/Zyszs87xCOBNyBig6vQ==} + dependencies: + '@aws-crypto/util': 3.0.0 + '@aws-sdk/types': 3.370.0 + tslib: 1.14.1 + dev: false + + /@aws-crypto/supports-web-crypto@3.0.0: + resolution: {integrity: sha512-06hBdMwUAb2WFTuGG73LSC0wfPu93xWwo5vL2et9eymgmu3Id5vFAHBbajVWiGhPO37qcsdCap/FqXvJGJWPIg==} + dependencies: + tslib: 1.14.1 + dev: false + + /@aws-crypto/util@3.0.0: + resolution: {integrity: sha512-2OJlpeJpCR48CC8r+uKVChzs9Iungj9wkZrl8Z041DWEWvyIHILYKCPNzJghKsivj+S3mLo6BVc7mBNzdxA46w==} + dependencies: + '@aws-sdk/types': 3.370.0 + '@aws-sdk/util-utf8-browser': 3.259.0 + tslib: 1.14.1 + dev: false + + /@aws-sdk/client-cognito-identity@3.370.0: + resolution: {integrity: sha512-/dQFXT8y0WUD/731cdLjCrxNxH7Wtg2uZx7PggevTZs9Yr2fdGPSHehIYfvpCvi59yeG9T2Cl8sFnxXL1OEx4A==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-crypto/sha256-browser': 3.0.0 + '@aws-crypto/sha256-js': 3.0.0 + '@aws-sdk/client-sts': 3.370.0 + '@aws-sdk/credential-provider-node': 3.370.0 + '@aws-sdk/middleware-host-header': 3.370.0 + '@aws-sdk/middleware-logger': 3.370.0 + '@aws-sdk/middleware-recursion-detection': 3.370.0 + '@aws-sdk/middleware-signing': 3.370.0 + '@aws-sdk/middleware-user-agent': 3.370.0 + '@aws-sdk/types': 3.370.0 + '@aws-sdk/util-endpoints': 3.370.0 + '@aws-sdk/util-user-agent-browser': 3.370.0 + '@aws-sdk/util-user-agent-node': 3.370.0 + '@smithy/config-resolver': 1.0.2 + '@smithy/fetch-http-handler': 1.0.2 + '@smithy/hash-node': 1.0.2 + '@smithy/invalid-dependency': 1.0.2 + '@smithy/middleware-content-length': 1.0.2 + '@smithy/middleware-endpoint': 1.0.3 + '@smithy/middleware-retry': 1.0.4 + '@smithy/middleware-serde': 1.0.2 + '@smithy/middleware-stack': 1.0.2 + '@smithy/node-config-provider': 1.0.2 + '@smithy/node-http-handler': 1.0.3 + '@smithy/protocol-http': 1.1.1 + '@smithy/smithy-client': 1.0.4 + '@smithy/types': 1.1.1 + '@smithy/url-parser': 1.0.2 + '@smithy/util-base64': 1.0.2 + '@smithy/util-body-length-browser': 1.0.2 + '@smithy/util-body-length-node': 1.0.2 + '@smithy/util-defaults-mode-browser': 1.0.2 + '@smithy/util-defaults-mode-node': 1.0.2 + '@smithy/util-retry': 1.0.4 + '@smithy/util-utf8': 1.0.2 + tslib: 2.6.0 + transitivePeerDependencies: + - aws-crt + dev: false + + /@aws-sdk/client-s3@3.374.0: + resolution: {integrity: sha512-1qhzOHN48DPAvHi/G2FQfx1DXpbiOfOUqJvnSCRKKo7UaPPN1426Ufw2qizTRiLo/suK2ABCb7spbTxqEFjvLw==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-crypto/sha1-browser': 3.0.0 + '@aws-crypto/sha256-browser': 3.0.0 + '@aws-crypto/sha256-js': 3.0.0 + '@aws-sdk/client-sts': 3.370.0 + '@aws-sdk/credential-provider-node': 3.370.0 + '@aws-sdk/hash-stream-node': 3.374.0 + '@aws-sdk/middleware-bucket-endpoint': 3.370.0 + '@aws-sdk/middleware-expect-continue': 3.370.0 + '@aws-sdk/middleware-flexible-checksums': 3.374.0 + '@aws-sdk/middleware-host-header': 3.370.0 + '@aws-sdk/middleware-location-constraint': 3.370.0 + '@aws-sdk/middleware-logger': 3.370.0 + '@aws-sdk/middleware-recursion-detection': 3.370.0 + '@aws-sdk/middleware-sdk-s3': 3.370.0 + '@aws-sdk/middleware-signing': 3.370.0 + '@aws-sdk/middleware-ssec': 3.370.0 + '@aws-sdk/middleware-user-agent': 3.370.0 + '@aws-sdk/signature-v4-multi-region': 3.370.0 + '@aws-sdk/types': 3.370.0 + '@aws-sdk/util-endpoints': 3.370.0 + '@aws-sdk/util-user-agent-browser': 3.370.0 + '@aws-sdk/util-user-agent-node': 3.370.0 + '@aws-sdk/xml-builder': 3.310.0 + '@smithy/config-resolver': 1.0.2 + '@smithy/eventstream-serde-browser': 1.0.2 + '@smithy/eventstream-serde-config-resolver': 1.0.2 + '@smithy/eventstream-serde-node': 1.0.2 + '@smithy/fetch-http-handler': 1.0.2 + '@smithy/hash-blob-browser': 1.0.2 + '@smithy/hash-node': 1.0.2 + '@smithy/invalid-dependency': 1.0.2 + '@smithy/md5-js': 1.0.2 + '@smithy/middleware-content-length': 1.0.2 + '@smithy/middleware-endpoint': 1.0.3 + '@smithy/middleware-retry': 1.0.4 + '@smithy/middleware-serde': 1.0.2 + '@smithy/middleware-stack': 1.0.2 + '@smithy/node-config-provider': 1.0.2 + '@smithy/node-http-handler': 1.0.3 + '@smithy/protocol-http': 1.1.1 + '@smithy/smithy-client': 1.0.4 + '@smithy/types': 1.1.1 + '@smithy/url-parser': 1.0.2 + '@smithy/util-base64': 1.0.2 + '@smithy/util-body-length-browser': 1.0.2 + '@smithy/util-body-length-node': 1.0.2 + '@smithy/util-defaults-mode-browser': 1.0.2 + '@smithy/util-defaults-mode-node': 1.0.2 + '@smithy/util-retry': 1.0.4 + '@smithy/util-stream': 1.0.2 + '@smithy/util-utf8': 1.0.2 + '@smithy/util-waiter': 1.0.2 + fast-xml-parser: 4.2.5 + tslib: 2.6.0 + transitivePeerDependencies: + - '@aws-sdk/signature-v4-crt' + - aws-crt + dev: false + + /@aws-sdk/client-sso-oidc@3.370.0: + resolution: {integrity: sha512-jAYOO74lmVXylQylqkPrjLzxvUnMKw476JCUTvCO6Q8nv3LzCWd76Ihgv/m9Q4M2Tbqi1iP2roVK5bstsXzEjA==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-crypto/sha256-browser': 3.0.0 + '@aws-crypto/sha256-js': 3.0.0 + '@aws-sdk/middleware-host-header': 3.370.0 + '@aws-sdk/middleware-logger': 3.370.0 + '@aws-sdk/middleware-recursion-detection': 3.370.0 + '@aws-sdk/middleware-user-agent': 3.370.0 + '@aws-sdk/types': 3.370.0 + '@aws-sdk/util-endpoints': 3.370.0 + '@aws-sdk/util-user-agent-browser': 3.370.0 + '@aws-sdk/util-user-agent-node': 3.370.0 + '@smithy/config-resolver': 1.0.2 + '@smithy/fetch-http-handler': 1.0.2 + '@smithy/hash-node': 1.0.2 + '@smithy/invalid-dependency': 1.0.2 + '@smithy/middleware-content-length': 1.0.2 + '@smithy/middleware-endpoint': 1.0.3 + '@smithy/middleware-retry': 1.0.4 + '@smithy/middleware-serde': 1.0.2 + '@smithy/middleware-stack': 1.0.2 + '@smithy/node-config-provider': 1.0.2 + '@smithy/node-http-handler': 1.0.3 + '@smithy/protocol-http': 1.1.1 + '@smithy/smithy-client': 1.0.4 + '@smithy/types': 1.1.1 + '@smithy/url-parser': 1.0.2 + '@smithy/util-base64': 1.0.2 + '@smithy/util-body-length-browser': 1.0.2 + '@smithy/util-body-length-node': 1.0.2 + '@smithy/util-defaults-mode-browser': 1.0.2 + '@smithy/util-defaults-mode-node': 1.0.2 + '@smithy/util-retry': 1.0.4 + '@smithy/util-utf8': 1.0.2 + tslib: 2.6.0 + transitivePeerDependencies: + - aws-crt + dev: false + + /@aws-sdk/client-sso@3.370.0: + resolution: {integrity: sha512-0Ty1iHuzNxMQtN7nahgkZr4Wcu1XvqGfrQniiGdKKif9jG/4elxsQPiydRuQpFqN6b+bg7wPP7crFP1uTxx2KQ==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-crypto/sha256-browser': 3.0.0 + '@aws-crypto/sha256-js': 3.0.0 + '@aws-sdk/middleware-host-header': 3.370.0 + '@aws-sdk/middleware-logger': 3.370.0 + '@aws-sdk/middleware-recursion-detection': 3.370.0 + '@aws-sdk/middleware-user-agent': 3.370.0 + '@aws-sdk/types': 3.370.0 + '@aws-sdk/util-endpoints': 3.370.0 + '@aws-sdk/util-user-agent-browser': 3.370.0 + '@aws-sdk/util-user-agent-node': 3.370.0 + '@smithy/config-resolver': 1.0.2 + '@smithy/fetch-http-handler': 1.0.2 + '@smithy/hash-node': 1.0.2 + '@smithy/invalid-dependency': 1.0.2 + '@smithy/middleware-content-length': 1.0.2 + '@smithy/middleware-endpoint': 1.0.3 + '@smithy/middleware-retry': 1.0.4 + '@smithy/middleware-serde': 1.0.2 + '@smithy/middleware-stack': 1.0.2 + '@smithy/node-config-provider': 1.0.2 + '@smithy/node-http-handler': 1.0.3 + '@smithy/protocol-http': 1.1.1 + '@smithy/smithy-client': 1.0.4 + '@smithy/types': 1.1.1 + '@smithy/url-parser': 1.0.2 + '@smithy/util-base64': 1.0.2 + '@smithy/util-body-length-browser': 1.0.2 + '@smithy/util-body-length-node': 1.0.2 + '@smithy/util-defaults-mode-browser': 1.0.2 + '@smithy/util-defaults-mode-node': 1.0.2 + '@smithy/util-retry': 1.0.4 + '@smithy/util-utf8': 1.0.2 + tslib: 2.6.0 + transitivePeerDependencies: + - aws-crt + dev: false + + /@aws-sdk/client-sts@3.370.0: + resolution: {integrity: sha512-utFxOPWIzbN+3kc415Je2o4J72hOLNhgR2Gt5EnRSggC3yOnkC4GzauxG8n7n5gZGBX45eyubHyPOXLOIyoqQA==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-crypto/sha256-browser': 3.0.0 + '@aws-crypto/sha256-js': 3.0.0 + '@aws-sdk/credential-provider-node': 3.370.0 + '@aws-sdk/middleware-host-header': 3.370.0 + '@aws-sdk/middleware-logger': 3.370.0 + '@aws-sdk/middleware-recursion-detection': 3.370.0 + '@aws-sdk/middleware-sdk-sts': 3.370.0 + '@aws-sdk/middleware-signing': 3.370.0 + '@aws-sdk/middleware-user-agent': 3.370.0 + '@aws-sdk/types': 3.370.0 + '@aws-sdk/util-endpoints': 3.370.0 + '@aws-sdk/util-user-agent-browser': 3.370.0 + '@aws-sdk/util-user-agent-node': 3.370.0 + '@smithy/config-resolver': 1.0.2 + '@smithy/fetch-http-handler': 1.0.2 + '@smithy/hash-node': 1.0.2 + '@smithy/invalid-dependency': 1.0.2 + '@smithy/middleware-content-length': 1.0.2 + '@smithy/middleware-endpoint': 1.0.3 + '@smithy/middleware-retry': 1.0.4 + '@smithy/middleware-serde': 1.0.2 + '@smithy/middleware-stack': 1.0.2 + '@smithy/node-config-provider': 1.0.2 + '@smithy/node-http-handler': 1.0.3 + '@smithy/protocol-http': 1.1.1 + '@smithy/smithy-client': 1.0.4 + '@smithy/types': 1.1.1 + '@smithy/url-parser': 1.0.2 + '@smithy/util-base64': 1.0.2 + '@smithy/util-body-length-browser': 1.0.2 + '@smithy/util-body-length-node': 1.0.2 + '@smithy/util-defaults-mode-browser': 1.0.2 + '@smithy/util-defaults-mode-node': 1.0.2 + '@smithy/util-retry': 1.0.4 + '@smithy/util-utf8': 1.0.2 + fast-xml-parser: 4.2.5 + tslib: 2.6.0 + transitivePeerDependencies: + - aws-crt + dev: false + + /@aws-sdk/credential-provider-cognito-identity@3.370.0: + resolution: {integrity: sha512-OjNAN72+QoyJAmOayi47AlFzpQc4E59LWRE2GKgH0F1pEgr3t34T0/EHusCoxUjOz5mRRXrKjNlHVC7ezOFEcg==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/client-cognito-identity': 3.370.0 + '@aws-sdk/types': 3.370.0 + '@smithy/property-provider': 1.0.2 + '@smithy/types': 1.1.1 + tslib: 2.6.0 + transitivePeerDependencies: + - aws-crt + dev: false + + /@aws-sdk/credential-provider-env@3.370.0: + resolution: {integrity: sha512-raR3yP/4GGbKFRPP5hUBNkEmTnzxI9mEc2vJAJrcv4G4J4i/UP6ELiLInQ5eO2/VcV/CeKGZA3t7d1tsJ+jhCg==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/types': 3.370.0 + '@smithy/property-provider': 1.0.2 + '@smithy/types': 1.1.1 + tslib: 2.6.0 + dev: false + + /@aws-sdk/credential-provider-ini@3.370.0: + resolution: {integrity: sha512-eJyapFKa4NrC9RfTgxlXnXfS9InG/QMEUPPVL+VhG7YS6nKqetC1digOYgivnEeu+XSKE0DJ7uZuXujN2Y7VAQ==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/credential-provider-env': 3.370.0 + '@aws-sdk/credential-provider-process': 3.370.0 + '@aws-sdk/credential-provider-sso': 3.370.0 + '@aws-sdk/credential-provider-web-identity': 3.370.0 + '@aws-sdk/types': 3.370.0 + '@smithy/credential-provider-imds': 1.0.2 + '@smithy/property-provider': 1.0.2 + '@smithy/shared-ini-file-loader': 1.0.2 + '@smithy/types': 1.1.1 + tslib: 2.6.0 + transitivePeerDependencies: + - aws-crt + dev: false + + /@aws-sdk/credential-provider-node@3.370.0: + resolution: {integrity: sha512-gkFiotBFKE4Fcn8CzQnMeab9TAR06FEAD02T4ZRYW1xGrBJOowmje9dKqdwQFHSPgnWAP+8HoTA8iwbhTLvjNA==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/credential-provider-env': 3.370.0 + '@aws-sdk/credential-provider-ini': 3.370.0 + '@aws-sdk/credential-provider-process': 3.370.0 + '@aws-sdk/credential-provider-sso': 3.370.0 + '@aws-sdk/credential-provider-web-identity': 3.370.0 + '@aws-sdk/types': 3.370.0 + '@smithy/credential-provider-imds': 1.0.2 + '@smithy/property-provider': 1.0.2 + '@smithy/shared-ini-file-loader': 1.0.2 + '@smithy/types': 1.1.1 + tslib: 2.6.0 + transitivePeerDependencies: + - aws-crt + dev: false + + /@aws-sdk/credential-provider-process@3.370.0: + resolution: {integrity: sha512-0BKFFZmUO779Xdw3u7wWnoWhYA4zygxJbgGVSyjkOGBvdkbPSTTcdwT1KFkaQy2kOXYeZPl+usVVRXs+ph4ejg==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/types': 3.370.0 + '@smithy/property-provider': 1.0.2 + '@smithy/shared-ini-file-loader': 1.0.2 + '@smithy/types': 1.1.1 + tslib: 2.6.0 + dev: false + + /@aws-sdk/credential-provider-sso@3.370.0: + resolution: {integrity: sha512-PFroYm5hcPSfC/jkZnCI34QFL3I7WVKveVk6/F3fud/cnP8hp6YjA9NiTNbqdFSzsyoiN/+e5fZgNKih8vVPTA==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/client-sso': 3.370.0 + '@aws-sdk/token-providers': 3.370.0 + '@aws-sdk/types': 3.370.0 + '@smithy/property-provider': 1.0.2 + '@smithy/shared-ini-file-loader': 1.0.2 + '@smithy/types': 1.1.1 + tslib: 2.6.0 + transitivePeerDependencies: + - aws-crt + dev: false + + /@aws-sdk/credential-provider-web-identity@3.370.0: + resolution: {integrity: sha512-CFaBMLRudwhjv1sDzybNV93IaT85IwS+L8Wq6VRMa0mro1q9rrWsIZO811eF+k0NEPfgU1dLH+8Vc2qhw4SARQ==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/types': 3.370.0 + '@smithy/property-provider': 1.0.2 + '@smithy/types': 1.1.1 + tslib: 2.6.0 + dev: false + + /@aws-sdk/credential-providers@3.370.0: + resolution: {integrity: sha512-K5yUHJPB2QJKWzKoz1YCE2xJDvYL6bvCRyoT0mRPWbITrDjFuWxbe1QXWcMymwQIyzOITAnZq5fvj456KhPATg==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/client-cognito-identity': 3.370.0 + '@aws-sdk/client-sso': 3.370.0 + '@aws-sdk/client-sts': 3.370.0 + '@aws-sdk/credential-provider-cognito-identity': 3.370.0 + '@aws-sdk/credential-provider-env': 3.370.0 + '@aws-sdk/credential-provider-ini': 3.370.0 + '@aws-sdk/credential-provider-node': 3.370.0 + '@aws-sdk/credential-provider-process': 3.370.0 + '@aws-sdk/credential-provider-sso': 3.370.0 + '@aws-sdk/credential-provider-web-identity': 3.370.0 + '@aws-sdk/types': 3.370.0 + '@smithy/credential-provider-imds': 1.0.2 + '@smithy/property-provider': 1.0.2 + '@smithy/types': 1.1.1 + tslib: 2.6.0 + transitivePeerDependencies: + - aws-crt + dev: false + + /@aws-sdk/hash-node@3.374.0: + resolution: {integrity: sha512-5GmU64bwoQhkebMv7NzHa+Mw+p7ZmrKz9e3A6hKClxVGeZFE/+jME46gMuFYzO0iz3WqX4CCzUVOhNbS0x8EMQ==} + engines: {node: '>=14.0.0'} + deprecated: This package has moved to @smithy/hash-node + dependencies: + '@smithy/hash-node': 1.0.2 + tslib: 2.6.0 + dev: false + + /@aws-sdk/hash-stream-node@3.374.0: + resolution: {integrity: sha512-Ta7YEFcgc+d4Rt7foV/fbgnXP8IgMAb+JVzZVYcHTwQf836+PdjGfKbamYkh8cM2xE47hzZqPe+BacCjePqH7g==} + engines: {node: '>=14.0.0'} + deprecated: This package has moved to @smithy/hash-stream-node + dependencies: + '@smithy/hash-stream-node': 1.0.2 + tslib: 2.6.0 + dev: false + + /@aws-sdk/middleware-bucket-endpoint@3.370.0: + resolution: {integrity: sha512-B36+fOeJVO0D9cjR92Ob6Ki2FTzyTQ/uKk8w+xtur6W6zYVOPU4IQNpNZvN3Ykt4jitR2uUnVSlBb3sXHHhdFA==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/types': 3.370.0 + '@aws-sdk/util-arn-parser': 3.310.0 + '@smithy/protocol-http': 1.1.1 + '@smithy/types': 1.1.1 + '@smithy/util-config-provider': 1.0.2 + tslib: 2.6.0 + dev: false + + /@aws-sdk/middleware-expect-continue@3.370.0: + resolution: {integrity: sha512-OlFIpXa53obLryHyrqedE2Cp8lp2k+1Vjd++hlZFDFJncRlWZMxoXSyl6shQPqhIiGnNW4vt7tG5xE4jg4NAvw==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/types': 3.370.0 + '@smithy/protocol-http': 1.1.1 + '@smithy/types': 1.1.1 + tslib: 2.6.0 + dev: false + + /@aws-sdk/middleware-flexible-checksums@3.374.0: + resolution: {integrity: sha512-NVXqMiYrEvpbAK0jTOy791dkJAz+JQkIX8lgl/BgnNXvXFDP2wOW5JT830LX27bMhs/yzt1nJSLvgnSCuhOKtg==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-crypto/crc32': 3.0.0 + '@aws-crypto/crc32c': 3.0.0 + '@aws-sdk/types': 3.370.0 + '@smithy/is-array-buffer': 1.0.2 + '@smithy/protocol-http': 1.1.1 + '@smithy/types': 1.1.1 + '@smithy/util-utf8': 1.0.2 + tslib: 2.6.0 + dev: false + + /@aws-sdk/middleware-host-header@3.370.0: + resolution: {integrity: sha512-CPXOm/TnOFC7KyXcJglICC7OiA7Kj6mT3ChvEijr56TFOueNHvJdV4aNIFEQy0vGHOWtY12qOWLNto/wYR1BAQ==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/types': 3.370.0 + '@smithy/protocol-http': 1.1.1 + '@smithy/types': 1.1.1 + tslib: 2.6.0 + dev: false + + /@aws-sdk/middleware-location-constraint@3.370.0: + resolution: {integrity: sha512-NlDZEbBOF1IN7svUTcjbLodkUctt9zsfDI8+DqNlklRs5lsPb91WYvahOfjFO/EvACixa+a5d3cCumMCaIq4Cw==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/types': 3.370.0 + '@smithy/types': 1.1.1 + tslib: 2.6.0 + dev: false + + /@aws-sdk/middleware-logger@3.370.0: + resolution: {integrity: sha512-cQMq9SaZ/ORmTJPCT6VzMML7OxFdQzNkhMAgKpTDl+tdPWynlHF29E5xGoSzROnThHlQPCjogU0NZ8AxI0SWPA==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/types': 3.370.0 + '@smithy/types': 1.1.1 + tslib: 2.6.0 + dev: false + + /@aws-sdk/middleware-recursion-detection@3.370.0: + resolution: {integrity: sha512-L7ZF/w0lAAY/GK1khT8VdoU0XB7nWHk51rl/ecAg64J70dHnMOAg8n+5FZ9fBu/xH1FwUlHOkwlodJOgzLJjtg==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/types': 3.370.0 + '@smithy/protocol-http': 1.1.1 + '@smithy/types': 1.1.1 + tslib: 2.6.0 + dev: false + + /@aws-sdk/middleware-sdk-s3@3.370.0: + resolution: {integrity: sha512-DPYXtveWBDS5MzSHWTThg2KkLaOzZkCgPejjEuw3yl4ljsHawDs/ZIVCtmWXlBIS2lLCaBMpCV+t9psuJ/6/zQ==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/types': 3.370.0 + '@aws-sdk/util-arn-parser': 3.310.0 + '@smithy/protocol-http': 1.1.1 + '@smithy/types': 1.1.1 + tslib: 2.6.0 + dev: false + + /@aws-sdk/middleware-sdk-sts@3.370.0: + resolution: {integrity: sha512-ykbsoVy0AJtVbuhAlTAMcaz/tCE3pT8nAp0L7CQQxSoanRCvOux7au0KwMIQVhxgnYid4dWVF6d00SkqU5MXRA==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/middleware-signing': 3.370.0 + '@aws-sdk/types': 3.370.0 + '@smithy/types': 1.1.1 + tslib: 2.6.0 + dev: false + + /@aws-sdk/middleware-signing@3.370.0: + resolution: {integrity: sha512-Dwr/RTCWOXdm394wCwICGT2VNOTMRe4IGPsBRJAsM24pm+EEqQzSS3Xu/U/zF4exuxqpMta4wec4QpSarPNTxA==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/types': 3.370.0 + '@smithy/property-provider': 1.0.2 + '@smithy/protocol-http': 1.1.1 + '@smithy/signature-v4': 1.0.2 + '@smithy/types': 1.1.1 + '@smithy/util-middleware': 1.0.2 + tslib: 2.6.0 + dev: false + + /@aws-sdk/middleware-ssec@3.370.0: + resolution: {integrity: sha512-NIosfLS7mxCNdGYnuy76W9qP3f3YWVTusUA+uv+s6rnwG+Z2UheXCf1wpnJKzxORA8pioSP7ylZ8w2A0reCgYQ==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/types': 3.370.0 + '@smithy/types': 1.1.1 + tslib: 2.6.0 + dev: false + + /@aws-sdk/middleware-user-agent@3.370.0: + resolution: {integrity: sha512-2+3SB6MtMAq1+gVXhw0Y3ONXuljorh6ijnxgTpv+uQnBW5jHCUiAS8WDYiDEm7i9euJPbvJfM8WUrSMDMU6Cog==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/types': 3.370.0 + '@aws-sdk/util-endpoints': 3.370.0 + '@smithy/protocol-http': 1.1.1 + '@smithy/types': 1.1.1 + tslib: 2.6.0 + dev: false + + /@aws-sdk/protocol-http@3.374.0: + resolution: {integrity: sha512-9WpRUbINdGroV3HiZZIBoJvL2ndoWk39OfwxWs2otxByppJZNN14bg/lvCx5e8ggHUti7IBk5rb0nqQZ4m05pg==} + engines: {node: '>=14.0.0'} + deprecated: This package has moved to @smithy/protocol-http + dependencies: + '@smithy/protocol-http': 1.1.1 + tslib: 2.6.0 + dev: false + + /@aws-sdk/s3-request-presigner@3.375.0: + resolution: {integrity: sha512-C6hAM7Vl7DjO5Txs+9infZlEqelVH3piCu+GPox+x2IiELOdQ/PeaOcHnmaejNyAwF/oS0OYbRmdCI04bUYQ8Q==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/signature-v4-multi-region': 3.370.0 + '@aws-sdk/types': 3.370.0 + '@aws-sdk/util-format-url': 3.370.0 + '@smithy/middleware-endpoint': 1.0.3 + '@smithy/protocol-http': 1.1.1 + '@smithy/smithy-client': 1.0.4 + '@smithy/types': 1.1.1 + tslib: 2.6.0 + transitivePeerDependencies: + - '@aws-sdk/signature-v4-crt' + dev: false + + /@aws-sdk/signature-v4-multi-region@3.370.0: + resolution: {integrity: sha512-Q3NQopPDnHbJXMhtYl0Mfy5U2o76K6tzhdnYRcrYImY0ze/zOkCQI7KPC4588PuyvAXCdQ02cmCPPjYD55UeNg==} + engines: {node: '>=14.0.0'} + peerDependencies: + '@aws-sdk/signature-v4-crt': ^3.118.0 + peerDependenciesMeta: + '@aws-sdk/signature-v4-crt': + optional: true + dependencies: + '@aws-sdk/types': 3.370.0 + '@smithy/protocol-http': 1.1.1 + '@smithy/signature-v4': 1.0.2 + '@smithy/types': 1.1.1 + tslib: 2.6.0 + dev: false + + /@aws-sdk/token-providers@3.370.0: + resolution: {integrity: sha512-EyR2ZYr+lJeRiZU2/eLR+mlYU9RXLQvNyGFSAekJKgN13Rpq/h0syzXVFLP/RSod/oZenh/fhVZ2HwlZxuGBtQ==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/client-sso-oidc': 3.370.0 + '@aws-sdk/types': 3.370.0 + '@smithy/property-provider': 1.0.2 + '@smithy/shared-ini-file-loader': 1.0.2 + '@smithy/types': 1.1.1 + tslib: 2.6.0 + transitivePeerDependencies: + - aws-crt + dev: false + + /@aws-sdk/types@3.370.0: + resolution: {integrity: sha512-8PGMKklSkRKjunFhzM2y5Jm0H2TBu7YRNISdYzXLUHKSP9zlMEYagseKVdmox0zKHf1LXVNuSlUV2b6SRrieCQ==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/types': 1.1.1 + tslib: 2.6.0 + dev: false + + /@aws-sdk/url-parser@3.374.0: + resolution: {integrity: sha512-RC3yEj4iqw5vbCmR4IQ3rhmFQilwHtWO1mZ9kRTUxfJCge3TVlrZzj9PRW3hxlYKdu3xZjSvCgX3ip8SFKXtbw==} + deprecated: This package has moved to @smithy/url-parser + dependencies: + '@smithy/url-parser': 1.0.2 + tslib: 2.6.0 + dev: false + + /@aws-sdk/util-arn-parser@3.310.0: + resolution: {integrity: sha512-jL8509owp/xB9+Or0pvn3Fe+b94qfklc2yPowZZIFAkFcCSIdkIglz18cPDWnYAcy9JGewpMS1COXKIUhZkJsA==} + engines: {node: '>=14.0.0'} + dependencies: + tslib: 2.6.0 + dev: false + + /@aws-sdk/util-endpoints@3.370.0: + resolution: {integrity: sha512-5ltVAnM79nRlywwzZN5i8Jp4tk245OCGkKwwXbnDU+gq7zT3CIOsct1wNZvmpfZEPGt/bv7/NyRcjP+7XNsX/g==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/types': 3.370.0 + tslib: 2.6.0 + dev: false + + /@aws-sdk/util-format-url@3.370.0: + resolution: {integrity: sha512-6ik8UTT5hNlMnATrqWiQWnIZ0EFW8wVsRGyYUYw/geB3lQ+WAWflpJg+gZiJnc5EY4R0aOzRVm02W8EUeH8f5g==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/types': 3.370.0 + '@smithy/querystring-builder': 1.0.2 + '@smithy/types': 1.1.1 + tslib: 2.6.0 + dev: false + + /@aws-sdk/util-locate-window@3.310.0: + resolution: {integrity: sha512-qo2t/vBTnoXpjKxlsC2e1gBrRm80M3bId27r0BRB2VniSSe7bL1mmzM+/HFtujm0iAxtPM+aLEflLJlJeDPg0w==} + engines: {node: '>=14.0.0'} + dependencies: + tslib: 2.6.0 + dev: false + + /@aws-sdk/util-user-agent-browser@3.370.0: + resolution: {integrity: sha512-028LxYZMQ0DANKhW+AKFQslkScZUeYlPmSphrCIXgdIItRZh6ZJHGzE7J/jDsEntZOrZJsjI4z0zZ5W2idj04w==} + dependencies: + '@aws-sdk/types': 3.370.0 + '@smithy/types': 1.1.1 + bowser: 2.11.0 + tslib: 2.6.0 + dev: false + + /@aws-sdk/util-user-agent-node@3.370.0: + resolution: {integrity: sha512-33vxZUp8vxTT/DGYIR3PivQm07sSRGWI+4fCv63Rt7Q++fO24E0kQtmVAlikRY810I10poD6rwILVtITtFSzkg==} + engines: {node: '>=14.0.0'} + peerDependencies: + aws-crt: '>=1.0.0' + peerDependenciesMeta: + aws-crt: + optional: true + dependencies: + '@aws-sdk/types': 3.370.0 + '@smithy/node-config-provider': 1.0.2 + '@smithy/types': 1.1.1 + tslib: 2.6.0 + dev: false + + /@aws-sdk/util-utf8-browser@3.259.0: + resolution: {integrity: sha512-UvFa/vR+e19XookZF8RzFZBrw2EUkQWxiBW0yYQAhvk3C+QVGl0H3ouca8LDBlBfQKXwmW3huo/59H8rwb1wJw==} + dependencies: + tslib: 2.6.0 + dev: false + + /@aws-sdk/xml-builder@3.310.0: + resolution: {integrity: sha512-TqELu4mOuSIKQCqj63fGVs86Yh+vBx5nHRpWKNUNhB2nPTpfbziTs5c1X358be3peVWA4wPxW7Nt53KIg1tnNw==} + engines: {node: '>=14.0.0'} + dependencies: + tslib: 2.6.0 + dev: false + /@babel/code-frame@7.22.5: resolution: {integrity: sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ==} engines: {node: '>=6.9.0'} @@ -1303,6 +2032,424 @@ packages: '@sinonjs/commons': 3.0.0 dev: true + /@smithy/abort-controller@1.0.2: + resolution: {integrity: sha512-tb2h0b+JvMee+eAxTmhnyqyNk51UXIK949HnE14lFeezKsVJTB30maan+CO2IMwnig2wVYQH84B5qk6ylmKCuA==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/types': 1.1.1 + tslib: 2.6.0 + dev: false + + /@smithy/chunked-blob-reader-native@1.0.2: + resolution: {integrity: sha512-ychahynhO3kMhw/nWX3AAVaMeGezsH6ugc6UZ/P9DABgYcPkDMOmtZOOe3yGI9OYuLB/ZG4y+Gd0eHv5ClEdNw==} + dependencies: + '@smithy/util-base64': 1.0.2 + tslib: 2.6.0 + dev: false + + /@smithy/chunked-blob-reader@1.0.2: + resolution: {integrity: sha512-B2x76NIPqC883lvnISprpO2eDlI41SznmoDTehoPbVpVcI2A7Nwg3nYA+p8XTpFF06cIFgjmOs9M0il2HquFQQ==} + dependencies: + tslib: 2.6.0 + dev: false + + /@smithy/config-resolver@1.0.2: + resolution: {integrity: sha512-8Bk7CgnVKg1dn5TgnjwPz2ebhxeR7CjGs5yhVYH3S8x0q8yPZZVWwpRIglwXaf5AZBzJlNO1lh+lUhMf2e73zQ==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/types': 1.1.1 + '@smithy/util-config-provider': 1.0.2 + '@smithy/util-middleware': 1.0.2 + tslib: 2.6.0 + dev: false + + /@smithy/credential-provider-imds@1.0.2: + resolution: {integrity: sha512-fLjCya+JOu2gPJpCiwSUyoLvT8JdNJmOaTOkKYBZoGf7CzqR6lluSyI+eboZnl/V0xqcfcqBG4tgqCISmWS3/w==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/node-config-provider': 1.0.2 + '@smithy/property-provider': 1.0.2 + '@smithy/types': 1.1.1 + '@smithy/url-parser': 1.0.2 + tslib: 2.6.0 + dev: false + + /@smithy/eventstream-codec@1.0.2: + resolution: {integrity: sha512-eW/XPiLauR1VAgHKxhVvgvHzLROUgTtqat2lgljztbH8uIYWugv7Nz+SgCavB+hWRazv2iYgqrSy74GvxXq/rg==} + dependencies: + '@aws-crypto/crc32': 3.0.0 + '@smithy/types': 1.1.1 + '@smithy/util-hex-encoding': 1.0.2 + tslib: 2.6.0 + dev: false + + /@smithy/eventstream-serde-browser@1.0.2: + resolution: {integrity: sha512-8bDImzBewLQrIF6hqxMz3eoYwEus2E5JrEwKnhpkSFkkoj8fDSKiLeP/26xfcaoVJgZXB8M1c6jSEZiY3cUMsw==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/eventstream-serde-universal': 1.0.2 + '@smithy/types': 1.1.1 + tslib: 2.6.0 + dev: false + + /@smithy/eventstream-serde-config-resolver@1.0.2: + resolution: {integrity: sha512-SeiJ5pfrXzkGP4WCt9V3Pimfr3OM85Nyh9u/V4J6E0O2dLOYuqvSuKdVnktV0Tcmuu1ZYbt78Th0vfetnSEcdQ==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/types': 1.1.1 + tslib: 2.6.0 + dev: false + + /@smithy/eventstream-serde-node@1.0.2: + resolution: {integrity: sha512-jqSfi7bpOBHqgd5OgUtCX0wAVhPqxlVdqcj2c4gHaRRXcbpCmK0DRDg7P+Df0h4JJVvTqI6dy2c0YhHk5ehPCw==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/eventstream-serde-universal': 1.0.2 + '@smithy/types': 1.1.1 + tslib: 2.6.0 + dev: false + + /@smithy/eventstream-serde-universal@1.0.2: + resolution: {integrity: sha512-cQ9bT0j0x49cp8TQ1yZSnn4+9qU0WQSTkoucl3jKRoTZMzNYHg62LQao6HTQ3Jgd77nAXo00c7hqUEjHXwNA+A==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/eventstream-codec': 1.0.2 + '@smithy/types': 1.1.1 + tslib: 2.6.0 + dev: false + + /@smithy/fetch-http-handler@1.0.2: + resolution: {integrity: sha512-kynyofLf62LvR8yYphPPdyHb8fWG3LepFinM/vWUTG2Q1pVpmPCM530ppagp3+q2p+7Ox0UvSqldbKqV/d1BpA==} + dependencies: + '@smithy/protocol-http': 1.1.1 + '@smithy/querystring-builder': 1.0.2 + '@smithy/types': 1.1.1 + '@smithy/util-base64': 1.0.2 + tslib: 2.6.0 + dev: false + + /@smithy/hash-blob-browser@1.0.2: + resolution: {integrity: sha512-6SFzZ18aZNplDTvmbUhaxB83TVPGhe0FEAQInYQIj2lQd5Qraw2/KEE8HIfW4UxqxcoTSb0aYS0PqdUhI+dttQ==} + dependencies: + '@smithy/chunked-blob-reader': 1.0.2 + '@smithy/chunked-blob-reader-native': 1.0.2 + '@smithy/types': 1.1.1 + tslib: 2.6.0 + dev: false + + /@smithy/hash-node@1.0.2: + resolution: {integrity: sha512-K6PKhcUNrJXtcesyzhIvNlU7drfIU7u+EMQuGmPw6RQDAg/ufUcfKHz4EcUhFAodUmN+rrejhRG9U6wxjeBOQA==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/types': 1.1.1 + '@smithy/util-buffer-from': 1.0.2 + '@smithy/util-utf8': 1.0.2 + tslib: 2.6.0 + dev: false + + /@smithy/hash-stream-node@1.0.2: + resolution: {integrity: sha512-XH3h6f+pChVFzYVWJa/TGME/NWk/r+AuXSBIhLvQjYcZbeoKy1LhVH2yVFVouN9EASkgLj0KvOVGjMS3c5pG6Q==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/types': 1.1.1 + '@smithy/util-utf8': 1.0.2 + tslib: 2.6.0 + dev: false + + /@smithy/invalid-dependency@1.0.2: + resolution: {integrity: sha512-B1Y3Tsa6dfC+Vvb+BJMhTHOfFieeYzY9jWQSTR1vMwKkxsymD0OIAnEw8rD/RiDj/4E4RPGFdx9Mdgnyd6Bv5Q==} + dependencies: + '@smithy/types': 1.1.1 + tslib: 2.6.0 + dev: false + + /@smithy/is-array-buffer@1.0.2: + resolution: {integrity: sha512-pkyBnsBRpe+c/6ASavqIMRBdRtZNJEVJOEzhpxZ9JoAXiZYbkfaSMRA/O1dUxGdJ653GHONunnZ4xMo/LJ7utQ==} + engines: {node: '>=14.0.0'} + dependencies: + tslib: 2.6.0 + dev: false + + /@smithy/md5-js@1.0.2: + resolution: {integrity: sha512-0yUgIvIUt63Rb5+ErZTraQguc4Vu3Fw7NKJL0ozLnj1hcYDrt45pfQjUMztKBE7ve32vCnuSOA4LCAe3fudHZA==} + dependencies: + '@smithy/types': 1.1.1 + '@smithy/util-utf8': 1.0.2 + tslib: 2.6.0 + dev: false + + /@smithy/middleware-content-length@1.0.2: + resolution: {integrity: sha512-pa1/SgGIrSmnEr2c9Apw7CdU4l/HW0fK3+LKFCPDYJrzM0JdYpqjQzgxi31P00eAkL0EFBccpus/p1n2GF9urw==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/protocol-http': 1.1.1 + '@smithy/types': 1.1.1 + tslib: 2.6.0 + dev: false + + /@smithy/middleware-endpoint@1.0.3: + resolution: {integrity: sha512-GsWvTXMFjSgl617PCE2km//kIjjtvMRrR2GAuRDIS9sHiLwmkS46VWaVYy+XE7ubEsEtzZ5yK2e8TKDR6Qr5Lw==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/middleware-serde': 1.0.2 + '@smithy/types': 1.1.1 + '@smithy/url-parser': 1.0.2 + '@smithy/util-middleware': 1.0.2 + tslib: 2.6.0 + dev: false + + /@smithy/middleware-retry@1.0.4: + resolution: {integrity: sha512-G7uRXGFL8c3F7APnoIMTtNAHH8vT4F2qVnAWGAZaervjupaUQuRRHYBLYubK0dWzOZz86BtAXKieJ5p+Ni2Xpg==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/protocol-http': 1.1.1 + '@smithy/service-error-classification': 1.0.3 + '@smithy/types': 1.1.1 + '@smithy/util-middleware': 1.0.2 + '@smithy/util-retry': 1.0.4 + tslib: 2.6.0 + uuid: 8.3.2 + dev: false + + /@smithy/middleware-serde@1.0.2: + resolution: {integrity: sha512-T4PcdMZF4xme6koUNfjmSZ1MLi7eoFeYCtodQNQpBNsS77TuJt1A6kt5kP/qxrTvfZHyFlj0AubACoaUqgzPeg==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/types': 1.1.1 + tslib: 2.6.0 + dev: false + + /@smithy/middleware-stack@1.0.2: + resolution: {integrity: sha512-H7/uAQEcmO+eDqweEFMJ5YrIpsBwmrXSP6HIIbtxKJSQpAcMGY7KrR2FZgZBi1FMnSUOh+rQrbOyj5HQmSeUBA==} + engines: {node: '>=14.0.0'} + dependencies: + tslib: 2.6.0 + dev: false + + /@smithy/node-config-provider@1.0.2: + resolution: {integrity: sha512-HU7afWpTToU0wL6KseGDR2zojeyjECQfr8LpjAIeHCYIW7r360ABFf4EaplaJRMVoC3hD9FeltgI3/NtShOqCg==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/property-provider': 1.0.2 + '@smithy/shared-ini-file-loader': 1.0.2 + '@smithy/types': 1.1.1 + tslib: 2.6.0 + dev: false + + /@smithy/node-http-handler@1.0.3: + resolution: {integrity: sha512-PcPUSzTbIb60VCJCiH0PU0E6bwIekttsIEf5Aoo/M0oTfiqsxHTn0Rcij6QoH6qJy6piGKXzLSegspXg5+Kq6g==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/abort-controller': 1.0.2 + '@smithy/protocol-http': 1.1.1 + '@smithy/querystring-builder': 1.0.2 + '@smithy/types': 1.1.1 + tslib: 2.6.0 + dev: false + + /@smithy/property-provider@1.0.2: + resolution: {integrity: sha512-pXDPyzKX8opzt38B205kDgaxda6LHcTfPvTYQZnwP6BAPp1o9puiCPjeUtkKck7Z6IbpXCPUmUQnzkUzWTA42Q==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/types': 1.1.1 + tslib: 2.6.0 + dev: false + + /@smithy/protocol-http@1.1.1: + resolution: {integrity: sha512-mFLFa2sSvlUxm55U7B4YCIsJJIMkA6lHxwwqOaBkral1qxFz97rGffP/mmd4JDuin1EnygiO5eNJGgudiUgmDQ==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/types': 1.1.1 + tslib: 2.6.0 + dev: false + + /@smithy/querystring-builder@1.0.2: + resolution: {integrity: sha512-6P/xANWrtJhMzTPUR87AbXwSBuz1SDHIfL44TFd/GT3hj6rA+IEv7rftEpPjayUiWRocaNnrCPLvmP31mobOyA==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/types': 1.1.1 + '@smithy/util-uri-escape': 1.0.2 + tslib: 2.6.0 + dev: false + + /@smithy/querystring-parser@1.0.2: + resolution: {integrity: sha512-IWxwxjn+KHWRRRB+K2Ngl+plTwo2WSgc2w+DvLy0DQZJh9UGOpw40d6q97/63GBlXIt4TEt5NbcFrO30CKlrsA==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/types': 1.1.1 + tslib: 2.6.0 + dev: false + + /@smithy/service-error-classification@1.0.3: + resolution: {integrity: sha512-2eglIYqrtcUnuI71yweu7rSfCgt6kVvRVf0C72VUqrd0LrV1M0BM0eYN+nitp2CHPSdmMI96pi+dU9U/UqAMSA==} + engines: {node: '>=14.0.0'} + dev: false + + /@smithy/shared-ini-file-loader@1.0.2: + resolution: {integrity: sha512-bdQj95VN+lCXki+P3EsDyrkpeLn8xDYiOISBGnUG/AGPYJXN8dmp4EhRRR7XOoLoSs8anZHR4UcGEOzFv2jwGw==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/types': 1.1.1 + tslib: 2.6.0 + dev: false + + /@smithy/signature-v4@1.0.2: + resolution: {integrity: sha512-rpKUhmCuPmpV5dloUkOb9w1oBnJatvKQEjIHGmkjRGZnC3437MTdzWej9TxkagcZ8NRRJavYnEUixzxM1amFig==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/eventstream-codec': 1.0.2 + '@smithy/is-array-buffer': 1.0.2 + '@smithy/types': 1.1.1 + '@smithy/util-hex-encoding': 1.0.2 + '@smithy/util-middleware': 1.0.2 + '@smithy/util-uri-escape': 1.0.2 + '@smithy/util-utf8': 1.0.2 + tslib: 2.6.0 + dev: false + + /@smithy/smithy-client@1.0.4: + resolution: {integrity: sha512-gpo0Xl5Nyp9sgymEfpt7oa9P2q/GlM3VmQIdm+FeH0QEdYOQx3OtvwVmBYAMv2FIPWxkMZlsPYRTnEiBTK5TYg==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/middleware-stack': 1.0.2 + '@smithy/types': 1.1.1 + '@smithy/util-stream': 1.0.2 + tslib: 2.6.0 + dev: false + + /@smithy/types@1.1.1: + resolution: {integrity: sha512-tMpkreknl2gRrniHeBtdgQwaOlo39df8RxSrwsHVNIGXULy5XP6KqgScUw2m12D15wnJCKWxVhCX+wbrBW/y7g==} + engines: {node: '>=14.0.0'} + dependencies: + tslib: 2.6.0 + dev: false + + /@smithy/url-parser@1.0.2: + resolution: {integrity: sha512-0JRsDMQe53F6EHRWksdcavKDRjyqp8vrjakg8EcCUOa7PaFRRB1SO/xGZdzSlW1RSTWQDEksFMTCEcVEKmAoqA==} + dependencies: + '@smithy/querystring-parser': 1.0.2 + '@smithy/types': 1.1.1 + tslib: 2.6.0 + dev: false + + /@smithy/util-base64@1.0.2: + resolution: {integrity: sha512-BCm15WILJ3SL93nusoxvJGMVfAMWHZhdeDZPtpAaskozuexd0eF6szdz4kbXaKp38bFCSenA6bkUHqaE3KK0dA==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/util-buffer-from': 1.0.2 + tslib: 2.6.0 + dev: false + + /@smithy/util-body-length-browser@1.0.2: + resolution: {integrity: sha512-Xh8L06H2anF5BHjSYTg8hx+Itcbf4SQZnVMl4PIkCOsKtneMJoGjPRLy17lEzfoh/GOaa0QxgCP6lRMQWzNl4w==} + dependencies: + tslib: 2.6.0 + dev: false + + /@smithy/util-body-length-node@1.0.2: + resolution: {integrity: sha512-nXHbZsUtvZeyfL4Ceds9nmy2Uh2AhWXohG4vWHyjSdmT8cXZlJdmJgnH6SJKDjyUecbu+BpKeVvSrA4cWPSOPA==} + engines: {node: '>=14.0.0'} + dependencies: + tslib: 2.6.0 + dev: false + + /@smithy/util-buffer-from@1.0.2: + resolution: {integrity: sha512-lHAYIyrBO9RANrPvccnPjU03MJnWZ66wWuC5GjWWQVfsmPwU6m00aakZkzHdUT6tGCkGacXSgArP5wgTgA+oCw==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/is-array-buffer': 1.0.2 + tslib: 2.6.0 + dev: false + + /@smithy/util-config-provider@1.0.2: + resolution: {integrity: sha512-HOdmDm+3HUbuYPBABLLHtn8ittuRyy+BSjKOA169H+EMc+IozipvXDydf+gKBRAxUa4dtKQkLraypwppzi+PRw==} + engines: {node: '>=14.0.0'} + dependencies: + tslib: 2.6.0 + dev: false + + /@smithy/util-defaults-mode-browser@1.0.2: + resolution: {integrity: sha512-J1u2PO235zxY7dg0+ZqaG96tFg4ehJZ7isGK1pCBEA072qxNPwIpDzUVGnLJkHZvjWEGA8rxIauDtXfB0qxeAg==} + engines: {node: '>= 10.0.0'} + dependencies: + '@smithy/property-provider': 1.0.2 + '@smithy/types': 1.1.1 + bowser: 2.11.0 + tslib: 2.6.0 + dev: false + + /@smithy/util-defaults-mode-node@1.0.2: + resolution: {integrity: sha512-9/BN63rlIsFStvI+AvljMh873Xw6bbI6b19b+PVYXyycQ2DDQImWcjnzRlHW7eP65CCUNGQ6otDLNdBQCgMXqg==} + engines: {node: '>= 10.0.0'} + dependencies: + '@smithy/config-resolver': 1.0.2 + '@smithy/credential-provider-imds': 1.0.2 + '@smithy/node-config-provider': 1.0.2 + '@smithy/property-provider': 1.0.2 + '@smithy/types': 1.1.1 + tslib: 2.6.0 + dev: false + + /@smithy/util-hex-encoding@1.0.2: + resolution: {integrity: sha512-Bxydb5rMJorMV6AuDDMOxro3BMDdIwtbQKHpwvQFASkmr52BnpDsWlxgpJi8Iq7nk1Bt4E40oE1Isy/7ubHGzg==} + engines: {node: '>=14.0.0'} + dependencies: + tslib: 2.6.0 + dev: false + + /@smithy/util-middleware@1.0.2: + resolution: {integrity: sha512-vtXK7GOR2BoseCX8NCGe9SaiZrm9M2lm/RVexFGyPuafTtry9Vyv7hq/vw8ifd/G/pSJ+msByfJVb1642oQHKw==} + engines: {node: '>=14.0.0'} + dependencies: + tslib: 2.6.0 + dev: false + + /@smithy/util-retry@1.0.4: + resolution: {integrity: sha512-RnZPVFvRoqdj2EbroDo3OsnnQU8eQ4AlnZTOGusbYKybH3269CFdrZfZJloe60AQjX7di3J6t/79PjwCLO5Khw==} + engines: {node: '>= 14.0.0'} + dependencies: + '@smithy/service-error-classification': 1.0.3 + tslib: 2.6.0 + dev: false + + /@smithy/util-stream@1.0.2: + resolution: {integrity: sha512-qyN2M9QFMTz4UCHi6GnBfLOGYKxQZD01Ga6nzaXFFC51HP/QmArU72e4kY50Z/EtW8binPxspP2TAsGbwy9l3A==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/fetch-http-handler': 1.0.2 + '@smithy/node-http-handler': 1.0.3 + '@smithy/types': 1.1.1 + '@smithy/util-base64': 1.0.2 + '@smithy/util-buffer-from': 1.0.2 + '@smithy/util-hex-encoding': 1.0.2 + '@smithy/util-utf8': 1.0.2 + tslib: 2.6.0 + dev: false + + /@smithy/util-uri-escape@1.0.2: + resolution: {integrity: sha512-k8C0BFNS9HpBMHSgUDnWb1JlCQcFG+PPlVBq9keP4Nfwv6a9Q0yAfASWqUCtzjuMj1hXeLhn/5ADP6JxnID1Pg==} + engines: {node: '>=14.0.0'} + dependencies: + tslib: 2.6.0 + dev: false + + /@smithy/util-utf8@1.0.2: + resolution: {integrity: sha512-V4cyjKfJlARui0dMBfWJMQAmJzoW77i4N3EjkH/bwnE2Ngbl4tqD2Y0C/xzpzY/J1BdxeCKxAebVFk8aFCaSCw==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/util-buffer-from': 1.0.2 + tslib: 2.6.0 + dev: false + + /@smithy/util-waiter@1.0.2: + resolution: {integrity: sha512-+jq4/Vd9ejPzR45qwYSePyjQbqYP9QqtyZYsFVyfzRnbGGC0AjswOh7txcxroafuEBExK4qE+L/QZA8wWXsJYw==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/abort-controller': 1.0.2 + '@smithy/types': 1.1.1 + tslib: 2.6.0 + dev: false + /@sqltools/formatter@1.2.5: resolution: {integrity: sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw==} dev: false @@ -2108,6 +3255,10 @@ packages: transitivePeerDependencies: - supports-color + /bowser@2.11.0: + resolution: {integrity: sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==} + dev: false + /brace-expansion@1.1.11: resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} dependencies: @@ -2963,6 +4114,13 @@ packages: /fast-safe-stringify@2.1.1: resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} + /fast-xml-parser@4.2.5: + resolution: {integrity: sha512-B9/wizE4WngqQftFPmdaMYlXoJlJOYxGQOanC77fq9k8+Z0v5dDSVh+3glErdIROP//s/jgb7ZuxKfB8nVyo0g==} + hasBin: true + dependencies: + strnum: 1.0.5 + dev: false + /fastq@1.15.0: resolution: {integrity: sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==} dependencies: @@ -5306,6 +6464,10 @@ packages: engines: {node: '>=8'} dev: true + /strnum@1.0.5: + resolution: {integrity: sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==} + dev: false + /superagent@8.0.9: resolution: {integrity: sha512-4C7Bh5pyHTvU33KpZgwrNKh/VQnvgtCSqPRfJAUdmrtSYePVzVg4E4OzsrbkhJj9O7SO6Bnv75K/F8XVZT8YHA==} engines: {node: '>=6.4.0 <13 || >=14'} @@ -5622,7 +6784,6 @@ packages: /tslib@1.14.1: resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} - dev: true /tslib@2.5.3: resolution: {integrity: sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w==} @@ -5804,6 +6965,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==} + hasBin: true + dev: false + /uuid@9.0.0: resolution: {integrity: sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==} hasBin: true