From f044c3a2df4f90a9a4bd2812aa5da486b9193303 Mon Sep 17 00:00:00 2001 From: Antigravity Date: Fri, 27 Mar 2026 16:38:01 +0100 Subject: [PATCH 1/2] feat: enhance /savings/products with dynamic aggregates and analytics --- .../savings/dto/product-details.dto.ts | 3 + .../savings/dto/savings-product.dto.ts | 49 +++++++--- .../entities/savings-product.entity.ts | 3 + .../savings.controller.enhanced.spec.ts | 98 +++++++++++++++++++ .../src/modules/savings/savings.controller.ts | 17 +++- .../src/modules/savings/savings.service.ts | 48 ++++++++- 6 files changed, 198 insertions(+), 20 deletions(-) create mode 100644 backend/src/modules/savings/savings.controller.enhanced.spec.ts diff --git a/backend/src/modules/savings/dto/product-details.dto.ts b/backend/src/modules/savings/dto/product-details.dto.ts index 17801330d..5ab80d708 100644 --- a/backend/src/modules/savings/dto/product-details.dto.ts +++ b/backend/src/modules/savings/dto/product-details.dto.ts @@ -46,6 +46,9 @@ export class ProductDetailsDto { @ApiProperty({ description: 'Product creation timestamp' }) createdAt: Date; + @ApiProperty({ description: 'Risk level classification' }) + riskLevel: string; + @ApiProperty({ description: 'Product last update timestamp' }) updatedAt: Date; } diff --git a/backend/src/modules/savings/dto/savings-product.dto.ts b/backend/src/modules/savings/dto/savings-product.dto.ts index 08df8a6ab..bdb068a0b 100644 --- a/backend/src/modules/savings/dto/savings-product.dto.ts +++ b/backend/src/modules/savings/dto/savings-product.dto.ts @@ -1,27 +1,48 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { SavingsProductType } from '../entities/savings-product.entity'; export class SavingsProductDto { - @ApiProperty({ description: 'On-chain contract identifier' }) - contractId: string; - - @ApiProperty({ description: 'Annual percentage yield (%)' }) - apy: number; + @ApiProperty({ description: 'Product UUID' }) + id: string; @ApiProperty({ description: 'Product name' }) name: string; + @ApiProperty({ enum: SavingsProductType, description: 'Product type' }) + type: SavingsProductType; + @ApiPropertyOptional({ description: 'Product description' }) - description?: string; + description: string | null; - @ApiProperty({ description: 'Risk level: low | medium | high' }) - riskLevel: 'low' | 'medium' | 'high'; + @ApiProperty({ description: 'Annual interest rate (%)' }) + interestRate: number; - @ApiProperty({ description: 'Total value locked as a JS Number' }) - tvlAmount: number; + @ApiProperty({ description: 'Minimum subscription amount' }) + minAmount: number; + + @ApiProperty({ description: 'Maximum subscription amount' }) + maxAmount: number; + + @ApiPropertyOptional({ description: 'Tenure in months' }) + tenureMonths: number | null; + + @ApiPropertyOptional({ description: 'Soroban vault contract ID' }) + contractId: string | null; - @ApiPropertyOptional({ - description: - 'Total value locked as a formatted string (e.g. "1,234,567.89")', + @ApiProperty({ description: 'Whether product is active' }) + isActive: boolean; + + @ApiProperty({ + description: 'Risk level classification (e.g. Low, Medium, High)', }) - tvlAmountFormatted?: string; + riskLevel: string; + + @ApiProperty({ description: 'Total Value Locked (aggregated local balance)' }) + tvlAmount: number; + + @ApiProperty({ description: 'Product creation timestamp' }) + createdAt: Date; + + @ApiProperty({ description: 'Product last update timestamp' }) + updatedAt: Date; } diff --git a/backend/src/modules/savings/entities/savings-product.entity.ts b/backend/src/modules/savings/entities/savings-product.entity.ts index 174a33e48..17e36a84f 100644 --- a/backend/src/modules/savings/entities/savings-product.entity.ts +++ b/backend/src/modules/savings/entities/savings-product.entity.ts @@ -45,6 +45,9 @@ export class SavingsProduct { @Column({ default: true }) isActive: boolean; + @Column({ type: 'varchar', length: 20, default: 'Low' }) + riskLevel: string; + @CreateDateColumn() createdAt: Date; diff --git a/backend/src/modules/savings/savings.controller.enhanced.spec.ts b/backend/src/modules/savings/savings.controller.enhanced.spec.ts new file mode 100644 index 000000000..1ec1f2f7f --- /dev/null +++ b/backend/src/modules/savings/savings.controller.enhanced.spec.ts @@ -0,0 +1,98 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { getRepositoryToken } from '@nestjs/typeorm'; +import { CACHE_MANAGER } from '@nestjs/cache-manager'; +import { ConfigService } from '@nestjs/config'; +import { SavingsController } from './savings.controller'; +import { SavingsService } from './savings.service'; +import { + SavingsProduct, + SavingsProductType, +} from './entities/savings-product.entity'; +import { + UserSubscription, + SubscriptionStatus, +} from './entities/user-subscription.entity'; +import { SavingsGoal } from './entities/savings-goal.entity'; +import { User } from '../user/entities/user.entity'; +import { SavingsService as BlockchainSavingsService } from '../blockchain/savings.service'; +import { PredictiveEvaluatorService } from './services/predictive-evaluator.service'; +import { RpcThrottleGuard } from '../../common/guards/rpc-throttle.guard'; +import { Reflector } from '@nestjs/core'; + +describe('SavingsController (Enhanced)', () => { + let controller: SavingsController; + let service: SavingsService; + + const mockProducts = [ + { + id: 'p1', + name: 'Alpha Pool', + interestRate: 10, + createdAt: new Date('2026-01-01'), + subscriptions: [ + { amount: 100, status: SubscriptionStatus.ACTIVE }, + { amount: 50, status: SubscriptionStatus.ACTIVE }, + ], + riskLevel: 'Low', + }, + { + id: 'p2', + name: 'Beta Pool', + interestRate: 15, + createdAt: new Date('2026-01-02'), + subscriptions: [{ amount: 30, status: SubscriptionStatus.ACTIVE }], + riskLevel: 'Medium', + }, + ]; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [SavingsController], + providers: [ + SavingsService, + { + provide: getRepositoryToken(SavingsProduct), + useValue: { + find: jest.fn().mockResolvedValue(mockProducts), + findOneBy: jest.fn(), + }, + }, + { provide: getRepositoryToken(UserSubscription), useValue: {} }, + { provide: getRepositoryToken(SavingsGoal), useValue: {} }, + { provide: getRepositoryToken(User), useValue: {} }, + { provide: BlockchainSavingsService, useValue: {} }, + { provide: PredictiveEvaluatorService, useValue: {} }, + { provide: ConfigService, useValue: { get: jest.fn() } }, + { provide: CACHE_MANAGER, useValue: { del: jest.fn() } }, + { provide: 'THROTTLER:MODULE_OPTIONS', useValue: {} }, + { provide: 'ThrottlerStorage', useValue: {} }, + Reflector, + ], + }) + .overrideGuard(RpcThrottleGuard) + .useValue({ canActivate: () => true }) + .compile(); + + controller = module.get(SavingsController); + service = module.get(SavingsService); + }); + + it('should return products sorted by APY (interestRate) DESC', async () => { + const result = await controller.getProducts('apy'); + expect(result[0].id).toBe('p2'); // 15% > 10% + expect(result[1].id).toBe('p1'); + }); + + it('should return products sorted by TVL DESC', async () => { + const result = await controller.getProducts('tvl'); + expect(result[0].id).toBe('p1'); // TVL 150 > TVL 30 + expect(result[1].id).toBe('p2'); + expect(result[0].tvlAmount).toBe(150); + }); + + it('should include riskLevel in the response', async () => { + const result = await controller.getProducts(); + expect(result[0].riskLevel).toBeDefined(); + expect(['Low', 'Medium', 'High']).toContain(result[0].riskLevel); + }); +}); diff --git a/backend/src/modules/savings/savings.controller.ts b/backend/src/modules/savings/savings.controller.ts index a9328d875..20fad539a 100644 --- a/backend/src/modules/savings/savings.controller.ts +++ b/backend/src/modules/savings/savings.controller.ts @@ -10,6 +10,7 @@ import { UseGuards, UseInterceptors, Param, + Query, } from '@nestjs/common'; import { CacheInterceptor, CacheKey, CacheTTL } from '@nestjs/cache-manager'; import { @@ -19,6 +20,7 @@ import { ApiBody, ApiBearerAuth, ApiParam, + ApiQuery, } from '@nestjs/swagger'; import { Throttle } from '@nestjs/throttler'; import { SavingsService } from './savings.service'; @@ -28,6 +30,7 @@ import { SavingsGoal } from './entities/savings-goal.entity'; import { SubscribeDto } from './dto/subscribe.dto'; import { CreateGoalDto } from './dto/create-goal.dto'; import { UpdateGoalDto } from './dto/update-goal.dto'; +import { SavingsProductDto } from './dto/savings-product.dto'; import { ProductDetailsDto } from './dto/product-details.dto'; import { JwtAuthGuard } from '../../auth/guards/jwt-auth.guard'; import { CurrentUser } from '../../common/decorators/current-user.decorator'; @@ -47,9 +50,16 @@ export class SavingsController { @CacheKey('pools_all') @CacheTTL(60000) @ApiOperation({ summary: 'List all savings products' }) - @ApiResponse({ status: 200, description: 'List of savings products' }) - async getProducts(): Promise { - return await this.savingsService.findAllProducts(true); + @ApiQuery({ name: 'sort', required: false, enum: ['apy', 'tvl'] }) + @ApiResponse({ + status: 200, + description: 'List of savings products', + type: [SavingsProductDto], + }) + async getProducts( + @Query('sort') sort?: 'apy' | 'tvl', + ): Promise { + return await this.savingsService.findAllProducts(true, sort); } @Get('products/:id') @@ -99,6 +109,7 @@ export class SavingsController { contractId: product.contractId, totalAssets, totalAssetsXlm, + riskLevel: (product as any).riskLevel || 'Low', createdAt: product.createdAt, updatedAt: product.updatedAt, }; diff --git a/backend/src/modules/savings/savings.service.ts b/backend/src/modules/savings/savings.service.ts index 850e8aa1f..3a04d7c5c 100644 --- a/backend/src/modules/savings/savings.service.ts +++ b/backend/src/modules/savings/savings.service.ts @@ -18,6 +18,7 @@ import { import { SavingsGoal, SavingsGoalStatus } from './entities/savings-goal.entity'; import { CreateProductDto } from './dto/create-product.dto'; import { UpdateProductDto } from './dto/update-product.dto'; +import { SavingsProductDto } from './dto/savings-product.dto'; import { GoalProgressDto } from './dto/goal-progress.dto'; import { User } from '../user/entities/user.entity'; import { SavingsService as BlockchainSavingsService } from '../blockchain/savings.service'; @@ -93,11 +94,52 @@ export class SavingsService { return updatedProduct; } - async findAllProducts(activeOnly = false): Promise { - return await this.productRepository.find({ + async findAllProducts( + activeOnly = false, + sort?: 'apy' | 'tvl', + ): Promise { + const products = await this.productRepository.find({ where: activeOnly ? { isActive: true } : undefined, - order: { createdAt: 'DESC' }, + relations: ['subscriptions'], }); + + const dtos: SavingsProductDto[] = products.map((product) => { + // Calculate TVL by summing active subscriptions + const tvlAmount = product.subscriptions + ? product.subscriptions + .filter((s) => s.status === SubscriptionStatus.ACTIVE) + .reduce((sum, s) => sum + Number(s.amount), 0) + : 0; + + return { + id: product.id, + name: product.name, + type: product.type, + description: product.description, + interestRate: Number(product.interestRate), + minAmount: Number(product.minAmount), + maxAmount: Number(product.maxAmount), + tenureMonths: product.tenureMonths, + contractId: product.contractId, + isActive: product.isActive, + riskLevel: (product as any).riskLevel || 'Low', + tvlAmount, + createdAt: product.createdAt, + updatedAt: product.updatedAt, + }; + }); + + // Handle local sorting + if (sort === 'apy') { + dtos.sort((a, b) => b.interestRate - a.interestRate); + } else if (sort === 'tvl') { + dtos.sort((a, b) => b.tvlAmount - a.tvlAmount); + } else { + // Default sort by createdAt DESC + dtos.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime()); + } + + return dtos; } async findOneProduct(id: string): Promise { From 06a986c5ffdef642aa4d5db966a127536004f940 Mon Sep 17 00:00:00 2001 From: Antigravity Date: Fri, 27 Mar 2026 17:13:39 +0100 Subject: [PATCH 2/2] fix: update pnpm-lock.yaml to include pino dependencies from main --- backend/pnpm-lock.yaml | 191 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 191 insertions(+) diff --git a/backend/pnpm-lock.yaml b/backend/pnpm-lock.yaml index 5ae54bb31..2eb5519a5 100644 --- a/backend/pnpm-lock.yaml +++ b/backend/pnpm-lock.yaml @@ -83,6 +83,9 @@ importers: joi: specifier: ^18.0.2 version: 18.0.2 + nestjs-pino: + specifier: ^4.6.1 + version: 4.6.1(@nestjs/common@11.1.14(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(pino-http@11.0.0)(pino@10.3.1)(rxjs@7.8.2) nodemailer: specifier: ^8.0.1 version: 8.0.1 @@ -95,6 +98,9 @@ importers: pg: specifier: ^8.18.0 version: 8.18.0 + pino-http: + specifier: ^11.0.0 + version: 11.0.0 reflect-metadata: specifier: ^0.2.2 version: 0.2.2 @@ -177,6 +183,9 @@ importers: jest: specifier: ^29.7.0 version: 29.7.0(@types/node@22.19.11)(ts-node@10.9.2(@swc/core@1.15.13)(@types/node@22.19.11)(typescript@5.9.3)) + pino-pretty: + specifier: ^13.1.3 + version: 13.1.3 prettier: specifier: ^3.8.1 version: 3.8.1 @@ -1183,6 +1192,9 @@ packages: '@paralleldrive/cuid2@2.3.1': resolution: {integrity: sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw==} + '@pinojs/redact@0.4.0': + resolution: {integrity: sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==} + '@pkgjs/parseargs@0.11.0': resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} @@ -1820,6 +1832,10 @@ packages: asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + atomic-sleep@1.0.0: + resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} + engines: {node: '>=8.0.0'} + available-typed-arrays@1.0.7: resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} engines: {node: '>= 0.4'} @@ -2118,6 +2134,9 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + colorette@2.0.20: + resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} + combined-stream@1.0.8: resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} engines: {node: '>= 0.8'} @@ -2238,6 +2257,9 @@ packages: resolution: {integrity: sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==} engines: {node: '>= 6'} + dateformat@4.6.3: + resolution: {integrity: sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==} + dayjs@1.11.19: resolution: {integrity: sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==} @@ -2412,6 +2434,9 @@ packages: resolution: {integrity: sha512-EuJWwlHPZ1LbADuKTClvHtwbaFn4rOD+dRAbWysqEOXRc2Uui0hJInNJrsdH0c+OhJA4nrCBdSkW4DD5YxAo6A==} engines: {node: '>=8.10.0'} + end-of-stream@1.4.5: + resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} + enhanced-resolve@5.19.0: resolution: {integrity: sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg==} engines: {node: '>=10.13.0'} @@ -2609,6 +2634,9 @@ packages: extend-object@1.0.0: resolution: {integrity: sha512-0dHDIXC7y7LDmCh/lp1oYkmv73K25AMugQI07r8eFopkW6f7Ufn1q+ETMsJjnV9Am14SlElkqy3O92r6xEaxPw==} + fast-copy@4.0.2: + resolution: {integrity: sha512-ybA6PDXIXOXivLJK/z9e+Otk7ve13I4ckBvGO5I2RRmBU1gMHLVDJYEuJYhGwez7YNlYji2M2DvVU+a9mSFDlw==} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -2893,6 +2921,9 @@ packages: resolution: {integrity: sha512-jOiHyAZsmnr8LqoPGmCjYAaiuWwjAPLgY8ZX2XrmHawt99/u1y6RgrZMTeoPfpUbV96HOalYgz1qzkRbw54Pmg==} engines: {node: '>=18.0.0'} + help-me@5.0.0: + resolution: {integrity: sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==} + hookified@1.15.1: resolution: {integrity: sha512-MvG/clsADq1GPM2KGo2nyfaWVyn9naPiXrqIe4jYjXNZQt238kWyOGrsyc/DmRAQ+Re6yeo6yX/yoNCG5KAEVg==} @@ -3253,6 +3284,10 @@ packages: resolution: {integrity: sha512-RuCOQMIt78LWnktPoeBL0GErkNaJPTBGcYuyaBvUOQSpcpcLfWrHPPihYdOGbV5pam9VTWbeoF7TsGiHugcjGA==} engines: {node: '>= 20'} + joycon@3.1.1: + resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} + engines: {node: '>=10'} + js-beautify@1.15.4: resolution: {integrity: sha512-9/KXeZUKKJwqCXUdBxFJ3vPh467OCckSBmYDwSK/EtV090K+iMJ7zx2S3HLVDIWFQdqMIsZWbnaGiba18aWhaA==} engines: {node: '>=14'} @@ -3686,6 +3721,15 @@ packages: neo-async@2.6.2: resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} + nestjs-pino@4.6.1: + resolution: {integrity: sha512-nuARXa0xpdJ1lY2+fgycIQr6H3g0VgqAWNK3xMYjOFcj2DoPETNXj0lV3Y86nRuI7BUfQp5PGiVoZvT4dTWbpQ==} + engines: {node: '>= 14'} + peerDependencies: + '@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 + pino: ^7.5.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 + pino-http: ^6.4.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 + rxjs: ^7.1.0 + nice-try@1.0.5: resolution: {integrity: sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==} @@ -3761,6 +3805,10 @@ packages: resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} engines: {node: '>= 0.4'} + on-exit-leak-free@2.1.2: + resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==} + engines: {node: '>=14.0.0'} + on-finished@2.4.1: resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} engines: {node: '>= 0.8'} @@ -3954,6 +4002,23 @@ packages: resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} engines: {node: '>=12'} + pino-abstract-transport@3.0.0: + resolution: {integrity: sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==} + + pino-http@11.0.0: + resolution: {integrity: sha512-wqg5XIAGRRIWtTk8qPGxkbrfiwEWz1lgedVLvhLALudKXvg1/L2lTFgTGPJ4Z2e3qcRmxoFxDuSdMdMGNM6I1g==} + + pino-pretty@13.1.3: + resolution: {integrity: sha512-ttXRkkOz6WWC95KeY9+xxWL6AtImwbyMHrL1mSwqwW9u+vLp/WIElvHvCSDg0xO/Dzrggz1zv3rN5ovTRVowKg==} + hasBin: true + + pino-std-serializers@7.1.0: + resolution: {integrity: sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==} + + pino@10.3.1: + resolution: {integrity: sha512-r34yH/GlQpKZbU1BvFFqOjhISRo1MNx1tWYsYvmj6KIRHSPMT2+yHOEb1SG6NMvRoHRF0a07kCOox/9yakl1vg==} + hasBin: true + pirates@4.0.7: resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} engines: {node: '>= 6'} @@ -4010,6 +4075,9 @@ packages: resolution: {integrity: sha512-nrdhnt+E9ClJ4khk9rNzqgsxubH7xSJSKoqXx/7aed2eghegNGNWkSGOelNgFgUtMz3LmKGks0waH2NuXWWmPg==} engines: {node: '>=14'} + process-warning@5.0.0: + resolution: {integrity: sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==} + promise@7.3.1: resolution: {integrity: sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==} @@ -4063,6 +4131,9 @@ packages: pug@3.0.3: resolution: {integrity: sha512-uBi6kmc9f3SZ3PXxqcHiUZLmIXgfgWooKWXcwSGwQd2Zi5Rb0bT14+8CJjJgI8AB+nndLaNgHGrcc6bPIB665g==} + pump@3.0.4: + resolution: {integrity: sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==} + punycode.js@2.3.1: resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==} engines: {node: '>=6'} @@ -4081,6 +4152,9 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + quick-format-unescaped@4.0.4: + resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} + quick-lru@5.1.1: resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} engines: {node: '>=10'} @@ -4115,6 +4189,10 @@ packages: resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} engines: {node: '>= 14.18.0'} + real-require@0.2.0: + resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} + engines: {node: '>= 12.13.0'} + redis@4.7.1: resolution: {integrity: sha512-S1bJDnqLftzHXHP8JsT5II/CtHWQrASX5K96REjWjlmWKrviSOLWmM7QnRLstAWsu1VBBV1ffV6DzCvxNP0UJQ==} @@ -4189,6 +4267,10 @@ packages: safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + safe-stable-stringify@2.5.0: + resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} + engines: {node: '>=10'} + safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} @@ -4200,6 +4282,9 @@ packages: resolution: {integrity: sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==} engines: {node: '>= 10.13.0'} + secure-json-parse@4.1.0: + resolution: {integrity: sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==} + seek-bzip@2.0.0: resolution: {integrity: sha512-SMguiTnYrhpLdk3PwfzHeotrcwi8bNV4iemL9tx9poR/yeaMYwB9VzR1w7b57DuWpuqR8n6oZboi0hj3AxZxQg==} hasBin: true @@ -4300,6 +4385,9 @@ packages: slick@1.12.2: resolution: {integrity: sha512-4qdtOGcBjral6YIBCWJ0ljFSKNLz9KkhbWtuGvUyRowl1kxfuE1x/Z/aJcaiilpb3do9bl5K7/1h9XC5wWpY/A==} + sonic-boom@4.2.1: + resolution: {integrity: sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==} + sort-keys-length@1.0.1: resolution: {integrity: sha512-GRbEOUqCxemTAk/b32F2xa8wDTs+Z1QHOkbhJDQTvv/6G3ZkbJ+frYWsTcc7cBB3Fu4wy4XlLCuNtJuMn7Gsvw==} engines: {node: '>=0.10.0'} @@ -4402,6 +4490,10 @@ packages: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} + strip-json-comments@5.0.3: + resolution: {integrity: sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==} + engines: {node: '>=14.16'} + strtok3@10.3.4: resolution: {integrity: sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg==} engines: {node: '>=18'} @@ -4481,6 +4573,10 @@ packages: text-decoder@1.2.7: resolution: {integrity: sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==} + thread-stream@4.0.0: + resolution: {integrity: sha512-4iMVL6HAINXWf1ZKZjIPcz5wYaOdPhtO8ATvZ+Xqp3BTdaqtAwQkNmKORqcIo5YkQqGXq5cwfswDwMqqQNrpJA==} + engines: {node: '>=20'} + through@2.3.8: resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} @@ -5952,6 +6048,8 @@ snapshots: dependencies: '@noble/hashes': 1.8.0 + '@pinojs/redact@0.4.0': {} + '@pkgjs/parseargs@0.11.0': optional: true @@ -6715,6 +6813,8 @@ snapshots: asynckit@0.4.0: {} + atomic-sleep@1.0.0: {} + available-typed-arrays@1.0.7: dependencies: possible-typed-array-names: 1.1.0 @@ -7075,6 +7175,8 @@ snapshots: color-name@1.1.4: {} + colorette@2.0.20: {} + combined-stream@1.0.8: dependencies: delayed-stream: 1.0.0 @@ -7204,6 +7306,8 @@ snapshots: css-what@6.2.2: optional: true + dateformat@4.6.3: {} + dayjs@1.11.19: {} debug@4.4.3: @@ -7363,6 +7467,10 @@ snapshots: encoding-japanese@2.2.0: optional: true + end-of-stream@1.4.5: + dependencies: + once: 1.4.0 + enhanced-resolve@5.19.0: dependencies: graceful-fs: 4.2.11 @@ -7603,6 +7711,8 @@ snapshots: extend-object@1.0.0: optional: true + fast-copy@4.0.2: {} + fast-deep-equal@3.1.3: {} fast-diff@1.3.0: {} @@ -7918,6 +8028,8 @@ snapshots: helmet@8.1.0: {} + help-me@5.0.0: {} + hookified@1.15.1: {} html-escaper@2.0.2: {} @@ -8489,6 +8601,8 @@ snapshots: '@hapi/topo': 6.0.2 '@standard-schema/spec': 1.1.0 + joycon@3.1.1: {} + js-beautify@1.15.4: dependencies: config-chain: 1.1.13 @@ -9134,6 +9248,13 @@ snapshots: neo-async@2.6.2: {} + nestjs-pino@4.6.1(@nestjs/common@11.1.14(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(pino-http@11.0.0)(pino@10.3.1)(rxjs@7.8.2): + dependencies: + '@nestjs/common': 11.1.14(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2) + pino: 10.3.1 + pino-http: 11.0.0 + rxjs: 7.8.2 + nice-try@1.0.5: optional: true @@ -9193,6 +9314,8 @@ snapshots: object-inspect@1.13.4: {} + on-exit-leak-free@2.1.2: {} + on-finished@2.4.1: dependencies: ee-first: 1.1.1 @@ -9395,6 +9518,49 @@ snapshots: picomatch@4.0.3: {} + pino-abstract-transport@3.0.0: + dependencies: + split2: 4.2.0 + + pino-http@11.0.0: + dependencies: + get-caller-file: 2.0.5 + pino: 10.3.1 + pino-std-serializers: 7.1.0 + process-warning: 5.0.0 + + pino-pretty@13.1.3: + dependencies: + colorette: 2.0.20 + dateformat: 4.6.3 + fast-copy: 4.0.2 + fast-safe-stringify: 2.1.1 + help-me: 5.0.0 + joycon: 3.1.1 + minimist: 1.2.8 + on-exit-leak-free: 2.1.2 + pino-abstract-transport: 3.0.0 + pump: 3.0.4 + secure-json-parse: 4.1.0 + sonic-boom: 4.2.1 + strip-json-comments: 5.0.3 + + pino-std-serializers@7.1.0: {} + + pino@10.3.1: + dependencies: + '@pinojs/redact': 0.4.0 + atomic-sleep: 1.0.0 + on-exit-leak-free: 2.1.2 + pino-abstract-transport: 3.0.0 + pino-std-serializers: 7.1.0 + process-warning: 5.0.0 + quick-format-unescaped: 4.0.4 + real-require: 0.2.0 + safe-stable-stringify: 2.5.0 + sonic-boom: 4.2.1 + thread-stream: 4.0.0 + pirates@4.0.7: {} piscina@4.9.2: @@ -9448,6 +9614,8 @@ snapshots: uuid: 9.0.1 optional: true + process-warning@5.0.0: {} + promise@7.3.1: dependencies: asap: 2.0.6 @@ -9547,6 +9715,11 @@ snapshots: pug-strip-comments: 2.0.0 optional: true + pump@3.0.4: + dependencies: + end-of-stream: 1.4.5 + once: 1.4.0 + punycode.js@2.3.1: optional: true @@ -9560,6 +9733,8 @@ snapshots: queue-microtask@1.2.3: {} + quick-format-unescaped@4.0.4: {} + quick-lru@5.1.1: {} randombytes@2.1.0: @@ -9598,6 +9773,8 @@ snapshots: readdirp@4.1.2: {} + real-require@0.2.0: {} + redis@4.7.1: dependencies: '@redis/bloom': 1.2.0(@redis/client@1.6.1) @@ -9675,6 +9852,8 @@ snapshots: safe-buffer@5.2.1: {} + safe-stable-stringify@2.5.0: {} + safer-buffer@2.1.2: {} schema-utils@3.3.0: @@ -9690,6 +9869,8 @@ snapshots: ajv-formats: 2.1.1(ajv@8.18.0) ajv-keywords: 5.1.0(ajv@8.18.0) + secure-json-parse@4.1.0: {} + seek-bzip@2.0.0: dependencies: commander: 6.2.1 @@ -9811,6 +9992,10 @@ snapshots: slick@1.12.2: optional: true + sonic-boom@4.2.1: + dependencies: + atomic-sleep: 1.0.0 + sort-keys-length@1.0.1: dependencies: sort-keys: 1.1.2 @@ -9906,6 +10091,8 @@ snapshots: strip-json-comments@3.1.1: {} + strip-json-comments@5.0.3: {} + strtok3@10.3.4: dependencies: '@tokenizer/token': 0.3.0 @@ -10002,6 +10189,10 @@ snapshots: transitivePeerDependencies: - react-native-b4a + thread-stream@4.0.0: + dependencies: + real-require: 0.2.0 + through@2.3.8: {} tinyglobby@0.2.15: