diff --git a/backend/src/blockchain/blockchain.module.ts b/backend/src/blockchain/blockchain.module.ts index 7111f942..7b5f333b 100644 --- a/backend/src/blockchain/blockchain.module.ts +++ b/backend/src/blockchain/blockchain.module.ts @@ -1,14 +1,10 @@ import { Module } from '@nestjs/common'; -import { ConfigModule } from '@nestjs/config'; import { BlockchainController } from './controller/blockchain.controller'; import { BlockchainService } from './provider/blockchain.service'; -import { SubmitPuzzleProvider } from './providers/submit-puzzle.provider'; import { GetPlayerProvider } from './providers/get-player.provider'; @Module({ - imports: [ConfigModule], controllers: [BlockchainController], - providers: [BlockchainService, SubmitPuzzleProvider], providers: [BlockchainService, GetPlayerProvider], exports: [BlockchainService], }) diff --git a/backend/src/blockchain/provider/blockchain.service.ts b/backend/src/blockchain/provider/blockchain.service.ts index cfc39485..ac889bb3 100644 --- a/backend/src/blockchain/provider/blockchain.service.ts +++ b/backend/src/blockchain/provider/blockchain.service.ts @@ -1,23 +1,9 @@ import { Injectable } from '@nestjs/common'; -import { SubmitPuzzleProvider } from '../providers/submit-puzzle.provider'; +import { GetPlayerProvider } from '../providers/get-player.provider'; @Injectable() export class BlockchainService { - constructor(private readonly submitPuzzleProvider: SubmitPuzzleProvider) {} - - async submitPuzzleOnChain( - stellarWallet: string, - puzzleId: string, - category: string, - score: number, - ): Promise { - return this.submitPuzzleProvider.submitPuzzleOnChain( - stellarWallet, - puzzleId, - category, - score, - ); - } + constructor(private readonly getPlayerProvider: GetPlayerProvider) {} getHello(): string { return 'Hello from Blockchain Service'; diff --git a/backend/src/blockchain/providers/submit-puzzle.provider.spec.ts b/backend/src/blockchain/providers/submit-puzzle.provider.spec.ts deleted file mode 100644 index 42521ccd..00000000 --- a/backend/src/blockchain/providers/submit-puzzle.provider.spec.ts +++ /dev/null @@ -1,183 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { ConfigService } from '@nestjs/config'; -import { Logger } from '@nestjs/common'; -import { SubmitPuzzleProvider } from './submit-puzzle.provider'; -import { REDIS_CLIENT } from '../../redis/redis.constants'; -import * as StellarSdk from 'stellar-sdk'; - -// --------------------------------------------------------------------------- -// Helpers -// --------------------------------------------------------------------------- - -const FAKE_SECRET = 'SDBX2ONEFXWE3FOPJM7OIWQVLA6436CJTQURXLFHCRLBJAS4SZ3SBA5Z'; -const FAKE_PUBLIC = 'GCIF7RP3SYHJW5IRCXAM66AKH3XL7ZFL6VI3TQTXVQXVAL6QLT5FBWAY'; - -const mockAccount = new StellarSdk.Account(FAKE_PUBLIC, '100'); - -const mockSendResult = { status: 'PENDING', hash: 'deadbeef' }; -const mockGetTransactionSuccess = { - status: StellarSdk.rpc.Api.GetTransactionStatus.SUCCESS, -}; - -const mockSimResult = { - transactionData: new StellarSdk.SorobanDataBuilder().build(), - minResourceFee: '100', - cost: { cpuInsns: '0', memBytes: '0' }, - footprint: '', - results: [], -}; - -// --------------------------------------------------------------------------- -// Mocks -// --------------------------------------------------------------------------- - -const mockRpcServer = { - getAccount: jest.fn().mockResolvedValue(mockAccount), - simulateTransaction: jest.fn().mockResolvedValue(mockSimResult), - sendTransaction: jest.fn().mockResolvedValue(mockSendResult), - getTransaction: jest.fn().mockResolvedValue(mockGetTransactionSuccess), -}; - -jest.mock('stellar-sdk', () => { - const actual = jest.requireActual('stellar-sdk'); - return { - ...actual, - rpc: { - ...actual.rpc, - Server: jest.fn().mockImplementation(() => mockRpcServer), - assembleTransaction: jest - .fn() - .mockImplementation((tx: StellarSdk.Transaction) => ({ - build: () => tx, - })), - Api: { - ...actual.rpc.Api, - isSimulationError: jest.fn().mockReturnValue(false), - GetTransactionStatus: actual.rpc.Api.GetTransactionStatus, - }, - }, - }; -}); - -// --------------------------------------------------------------------------- -// Tests -// --------------------------------------------------------------------------- - -describe('SubmitPuzzleProvider', () => { - let provider: SubmitPuzzleProvider; - const mockRedis = { rpush: jest.fn().mockResolvedValue(1) }; - - const configValues: Record = { - SOROBAN_RPC_URL: 'https://soroban-testnet.stellar.org', - SOROBAN_CONTRACT_ID: 'CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM', - ORACLE_WALLET_SECRET: FAKE_SECRET, - }; - - beforeEach(async () => { - jest.clearAllMocks(); - - // Reset polling mock to succeed immediately on first poll - mockRpcServer.getTransaction.mockResolvedValue(mockGetTransactionSuccess); - - const module: TestingModule = await Test.createTestingModule({ - providers: [ - SubmitPuzzleProvider, - { - provide: ConfigService, - useValue: { - getOrThrow: jest.fn((key: string) => { - if (key in configValues) return configValues[key]; - throw new Error(`Missing config: ${key}`); - }), - }, - }, - { provide: REDIS_CLIENT, useValue: mockRedis }, - ], - }).compile(); - - // Silence logger noise in test output - jest.spyOn(Logger.prototype, 'log').mockImplementation(() => undefined); - jest.spyOn(Logger.prototype, 'warn').mockImplementation(() => undefined); - jest.spyOn(Logger.prototype, 'error').mockImplementation(() => undefined); - - provider = module.get(SubmitPuzzleProvider); - }); - - it('should be defined', () => { - expect(provider).toBeDefined(); - }); - - describe('submitPuzzleOnChain — success path', () => { - it('builds and submits a signed transaction for a correct puzzle completion', async () => { - await provider.submitPuzzleOnChain( - FAKE_PUBLIC, - 'puzzle-uuid-001', - 'category-uuid-001', - 150, - ); - - // RPC server was instantiated with the configured URL - expect(StellarSdk.rpc.Server).toHaveBeenCalledWith( - 'https://soroban-testnet.stellar.org', - ); - - // Oracle account was fetched - expect(mockRpcServer.getAccount).toHaveBeenCalledWith(FAKE_PUBLIC); - - // Transaction was simulated - expect(mockRpcServer.simulateTransaction).toHaveBeenCalledTimes(1); - - // Transaction was sent - expect(mockRpcServer.sendTransaction).toHaveBeenCalledTimes(1); - - // Final status was polled - expect(mockRpcServer.getTransaction).toHaveBeenCalledWith('deadbeef'); - - // No retry was enqueued for a successful submission - expect(mockRedis.rpush).not.toHaveBeenCalled(); - }); - }); - - describe('submitPuzzleOnChain — failure path', () => { - it('enqueues a retry and does not throw when the RPC call errors', async () => { - mockRpcServer.sendTransaction.mockRejectedValueOnce( - new Error('Network timeout'), - ); - - // Must not throw — failure is non-blocking - await expect( - provider.submitPuzzleOnChain( - FAKE_PUBLIC, - 'puzzle-uuid-002', - 'category-uuid-002', - 80, - ), - ).resolves.toBeUndefined(); - - // Failure logged and pushed to Redis retry queue - expect(mockRedis.rpush).toHaveBeenCalledWith( - 'blockchain:submit_puzzle:retry', - expect.stringContaining('puzzle-uuid-002'), - ); - }); - - it('enqueues a retry when simulation returns an error', async () => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - (StellarSdk.rpc.Api.isSimulationError as unknown as jest.Mock).mockReturnValueOnce(true); - (mockRpcServer.simulateTransaction as jest.Mock).mockResolvedValueOnce({ - error: 'HostError: ...', - }); - - await expect( - provider.submitPuzzleOnChain( - FAKE_PUBLIC, - 'puzzle-uuid-003', - 'category-uuid-003', - 60, - ), - ).resolves.toBeUndefined(); - - expect(mockRedis.rpush).toHaveBeenCalledTimes(1); - }); - }); -}); diff --git a/backend/src/blockchain/providers/submit-puzzle.provider.ts b/backend/src/blockchain/providers/submit-puzzle.provider.ts deleted file mode 100644 index f4fc9df9..00000000 --- a/backend/src/blockchain/providers/submit-puzzle.provider.ts +++ /dev/null @@ -1,142 +0,0 @@ -import { Inject, Injectable, Logger } from '@nestjs/common'; -import { ConfigService } from '@nestjs/config'; -import { - BASE_FEE, - Contract, - Keypair, - Networks, - TransactionBuilder, - nativeToScVal, - rpc as SorobanRpc, - xdr, -} from 'stellar-sdk'; -import Redis from 'ioredis'; -import { REDIS_CLIENT } from '../../redis/redis.constants'; - -const RETRY_QUEUE_KEY = 'blockchain:submit_puzzle:retry'; - -@Injectable() -export class SubmitPuzzleProvider { - private readonly logger = new Logger(SubmitPuzzleProvider.name); - private readonly rpcUrl: string; - private readonly contractId: string; - private readonly oracleSecret: string; - - constructor( - private readonly configService: ConfigService, - @Inject(REDIS_CLIENT) private readonly redis: Redis, - ) { - this.rpcUrl = this.configService.getOrThrow('SOROBAN_RPC_URL'); - this.contractId = this.configService.getOrThrow( - 'SOROBAN_CONTRACT_ID', - ); - this.oracleSecret = this.configService.getOrThrow( - 'ORACLE_WALLET_SECRET', - ); - } - - async submitPuzzleOnChain( - stellarWallet: string, - puzzleId: string, - category: string, - score: number, - ): Promise { - try { - await this.invokeSubmitPuzzle(stellarWallet, puzzleId, category, score); - } catch (err) { - const errorMessage = err instanceof Error ? err.message : String(err); - this.logger.error( - `submit_puzzle on-chain failed — wallet: ${stellarWallet}, puzzleId: ${puzzleId}, score: ${score}. Error: ${errorMessage}`, - ); - await this.enqueueRetry(stellarWallet, puzzleId, category, score); - } - } - - private async invokeSubmitPuzzle( - stellarWallet: string, - puzzleId: string, - category: string, - score: number, - ): Promise { - const server = new SorobanRpc.Server(this.rpcUrl); - const oracleKeypair = Keypair.fromSecret(this.oracleSecret); - const oracleAccount = await server.getAccount(oracleKeypair.publicKey()); - - const contract = new Contract(this.contractId); - - const args: xdr.ScVal[] = [ - nativeToScVal(stellarWallet, { type: 'address' }), - nativeToScVal(puzzleId, { type: 'string' }), - nativeToScVal(category, { type: 'string' }), - nativeToScVal(score, { type: 'i64' }), - ]; - - const tx = new TransactionBuilder(oracleAccount, { - fee: BASE_FEE, - networkPassphrase: Networks.TESTNET, - }) - .addOperation(contract.call('submit_puzzle', ...args)) - .setTimeout(30) - .build(); - - const simResult = await server.simulateTransaction(tx); - - if (SorobanRpc.Api.isSimulationError(simResult)) { - throw new Error(`Simulation failed: ${simResult.error}`); - } - - const preparedTx = SorobanRpc.assembleTransaction(tx, simResult).build(); - preparedTx.sign(oracleKeypair); - - const sendResult = await server.sendTransaction(preparedTx); - - if (sendResult.status === 'ERROR') { - throw new Error( - `Transaction send failed: ${JSON.stringify(sendResult.errorResult)}`, - ); - } - - // Poll for final status - const txHash = sendResult.hash; - let attempts = 0; - const maxAttempts = 10; - - while (attempts < maxAttempts) { - await new Promise((resolve) => setTimeout(resolve, 3000)); - const statusResult = await server.getTransaction(txHash); - - if (statusResult.status === SorobanRpc.Api.GetTransactionStatus.SUCCESS) { - this.logger.log( - `submit_puzzle on-chain succeeded — wallet: ${stellarWallet}, puzzleId: ${puzzleId}, txHash: ${txHash}`, - ); - return; - } - - if (statusResult.status === SorobanRpc.Api.GetTransactionStatus.FAILED) { - throw new Error(`Transaction failed on-chain: ${txHash}`); - } - - attempts++; - } - - throw new Error(`Transaction timed out waiting for confirmation: ${txHash}`); - } - - private async enqueueRetry( - stellarWallet: string, - puzzleId: string, - category: string, - score: number, - ): Promise { - try { - const payload = JSON.stringify({ stellarWallet, puzzleId, category, score }); - await this.redis.rpush(RETRY_QUEUE_KEY, payload); - this.logger.warn( - `submit_puzzle queued for retry — wallet: ${stellarWallet}, puzzleId: ${puzzleId}`, - ); - } catch (redisErr) { - const msg = redisErr instanceof Error ? redisErr.message : String(redisErr); - this.logger.error(`Failed to enqueue retry: ${msg}`); - } - } -} diff --git a/backend/src/progress/progress.module.ts b/backend/src/progress/progress.module.ts index 77b2ec0d..d8da0feb 100644 --- a/backend/src/progress/progress.module.ts +++ b/backend/src/progress/progress.module.ts @@ -10,14 +10,12 @@ import { GetProgressHistoryProvider } from './providers/get-progress-history.pro import { GetCategoryStatsProvider } from './providers/get-category-stats.provider'; import { GetOverallStatsProvider } from './providers/get-overall-stats.provider'; import { ProgressCalculationProvider } from './providers/progress-calculation.provider'; -import { BlockchainModule } from '../blockchain/blockchain.module'; import { Puzzle } from '../puzzles/entities/puzzle.entity'; import { XpLevelService } from '../users/providers/xp-level.service'; @Module({ imports: [ TypeOrmModule.forFeature([UserProgress, User, Puzzle, Streak, DailyQuest]), - BlockchainModule, ], controllers: [ProgressController], providers: [ diff --git a/backend/src/progress/providers/progress-calculation.provider.ts b/backend/src/progress/providers/progress-calculation.provider.ts index 8d270910..6e8d5141 100644 --- a/backend/src/progress/providers/progress-calculation.provider.ts +++ b/backend/src/progress/providers/progress-calculation.provider.ts @@ -1,4 +1,4 @@ -import { Injectable, Logger, NotFoundException } from '@nestjs/common'; +import { Injectable, NotFoundException } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { FindOptionsWhere, MoreThan, Repository } from 'typeorm'; import { Puzzle } from '../../puzzles/entities/puzzle.entity'; @@ -8,7 +8,6 @@ import { XpLevelService } from '../../users/providers/xp-level.service'; import { User } from '../../users/user.entity'; import { DailyQuest } from '../../quests/entities/daily-quest.entity'; import { getPointsByDifficulty } from '../../puzzles/enums/puzzle-difficulty.enum'; -import { BlockchainService } from '../../blockchain/provider/blockchain.service'; export interface AnswerValidationResult { isCorrect: boolean; @@ -23,8 +22,6 @@ export interface ProgressCalculationResult { @Injectable() export class ProgressCalculationProvider { - private readonly logger = new Logger(ProgressCalculationProvider.name); - constructor( @InjectRepository(Puzzle) private readonly puzzleRepository: Repository, @@ -35,7 +32,6 @@ export class ProgressCalculationProvider { private readonly userRepository: Repository, @InjectRepository(DailyQuest) private readonly dailyQuestRepository: Repository, - private readonly blockchainService: BlockchainService, ) {} /** @@ -225,21 +221,8 @@ export class ProgressCalculationProvider { // Save to database await this.userProgressRepository.save(userProgress); - // Non-blocking on-chain record for correct answers with a linked Stellar wallet - if (validation.isCorrect && user?.stellarWallet) { - void this.blockchainService - .submitPuzzleOnChain( - user.stellarWallet, - submitAnswerDto.puzzleId, - submitAnswerDto.categoryId, - pointsEarned, - ) - .catch((err: unknown) => { - const msg = err instanceof Error ? err.message : String(err); - this.logger.error( - `Unexpected error in submitPuzzleOnChain — wallet: ${user.stellarWallet}, puzzleId: ${submitAnswerDto.puzzleId}. Error: ${msg}`, - ); - }); + if (validation.isCorrect && pointsEarned > 0) { + await this.xpLevelService.addXp(submitAnswerDto.userId, pointsEarned); } return { diff --git a/package-lock.json b/package-lock.json index ae0dc953..36196d54 100644 --- a/package-lock.json +++ b/package-lock.json @@ -208,6 +208,7 @@ "version": "0.6.0", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@swc/counter": "^0.1.3", "@xhmikosr/bin-wrapper": "^13.0.5", @@ -250,6 +251,7 @@ "dev": true, "hasInstallScript": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "@swc/counter": "^0.1.3", "@swc/types": "^0.1.24" @@ -286,6 +288,7 @@ "version": "22.18.0", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~6.21.0" } @@ -485,6 +488,7 @@ "version": "10.9.2", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -526,6 +530,7 @@ "backend/node_modules/typeorm": { "version": "0.3.26", "license": "MIT", + "peer": true, "dependencies": { "@sqltools/formatter": "^1.2.5", "ansis": "^3.17.0", @@ -652,6 +657,7 @@ "version": "5.8.3", "devOptional": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -1130,6 +1136,7 @@ "integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/generator": "^7.28.6", @@ -3533,6 +3540,7 @@ "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-11.1.14.tgz", "integrity": "sha512-IN/tlqd7Nl9gl6f0jsWEuOrQDaCI9vHzxv0fisHysfBQzfQIkqlv5A7w4Qge02BUQyczXT9HHPgHtWHCxhjRng==", "license": "MIT", + "peer": true, "dependencies": { "file-type": "21.3.0", "iterare": "1.2.1", @@ -3580,6 +3588,7 @@ "integrity": "sha512-7OXPPMoDr6z+5NkoQKu4hOhfjz/YYqM3bNilPqv1WVFWrzSmuNXxvhbX69YMmNmRYascPXiwESqf5jJdjKXEww==", "hasInstallScript": true, "license": "MIT", + "peer": true, "dependencies": { "@nuxt/opencollective": "0.4.1", "fast-safe-stringify": "2.1.1", @@ -3676,6 +3685,7 @@ "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-11.1.12.tgz", "integrity": "sha512-GYK/vHI0SGz5m8mxr7v3Urx8b9t78Cf/dj5aJMZlGd9/1D9OI1hAl00BaphjEXINUJ/BQLxIlF2zUjrYsd6enQ==", "license": "MIT", + "peer": true, "dependencies": { "cors": "2.8.5", "express": "5.2.1", @@ -5606,6 +5616,7 @@ "integrity": "sha512-WPigyYuGhgZ/cTPRXB2EwUw+XvsRA3GqHlsP4qteqrnnjDrApbS7MxcGr/hke5iUoeB7E/gQtrs9I37zAJ0Vjw==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -5745,6 +5756,7 @@ "integrity": "sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.54.0", "@typescript-eslint/types": "8.54.0", @@ -6464,6 +6476,7 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "devOptional": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -6536,6 +6549,7 @@ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -6965,6 +6979,7 @@ "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.4.tgz", "integrity": "sha512-1wVkUaAO6WyaYtCkcYCOx12ZgpGf9Zif+qXa4n+oYzK558YryKqiL6UWwd5DqiH3VRW0GYhTZQ/vlgJrCoNQlg==", "license": "MIT", + "peer": true, "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.4", @@ -7364,6 +7379,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -7760,13 +7776,15 @@ "version": "0.5.1", "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz", "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/class-validator": { "version": "0.14.3", "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.14.3.tgz", "integrity": "sha512-rXXekcjofVN1LTOSw+u4u9WXVEUvNBVjORW154q/IdmYWy1nMbOU9aNtZB0t8m+FJQ9q91jlr2f9CwwUFdFMRA==", "license": "MIT", + "peer": true, "dependencies": { "@types/validator": "^13.15.3", "libphonenumber-js": "^1.11.1", @@ -8966,6 +8984,7 @@ "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -9155,6 +9174,7 @@ "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -9629,6 +9649,7 @@ "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", "license": "MIT", + "peer": true, "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", @@ -11069,6 +11090,7 @@ "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.9.2.tgz", "integrity": "sha512-tAAg/72/VxOUW7RQSX1pIxJVucYKcjFjfvj60L57jrZpYCHC3XN0WCQ3sNYL4Gmvv+7GPvTAjc+KSdeNuE8oWQ==", "license": "MIT", + "peer": true, "dependencies": { "@ioredis/commands": "1.5.0", "cluster-key-slot": "^1.1.0", @@ -11734,6 +11756,7 @@ "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@jest/core": "^29.7.0", "@jest/types": "^29.6.3", @@ -14526,6 +14549,7 @@ "resolved": "https://registry.npmjs.org/passport/-/passport-0.7.0.tgz", "integrity": "sha512-cPLl+qZpSc+ireUvt+IzqbED1cHHkDoVYMo30jbJIdOOjQ1MQYZBPiNvmi8UM6lJuOpTPXJGZQk0DtC4y61MYQ==", "license": "MIT", + "peer": true, "dependencies": { "passport-strategy": "1.x.x", "pause": "0.0.1", @@ -14700,6 +14724,7 @@ "resolved": "https://registry.npmjs.org/pg/-/pg-8.18.0.tgz", "integrity": "sha512-xqrUDL1b9MbkydY/s+VZ6v+xiMUmOUk7SS9d/1kpyQxoJ6U9AO1oIJyUWVZojbfe5Cc/oluutcgFG4L9RDP1iQ==", "license": "MIT", + "peer": true, "dependencies": { "pg-connection-string": "^2.11.0", "pg-pool": "^3.11.0", @@ -15015,6 +15040,7 @@ "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -15274,6 +15300,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -15283,6 +15310,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz", "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.26.0" }, @@ -15302,6 +15330,7 @@ "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz", "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==", "license": "MIT", + "peer": true, "dependencies": { "@types/use-sync-external-store": "^0.0.6", "use-sync-external-store": "^1.4.0" @@ -15373,7 +15402,8 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/redux-thunk": { "version": "3.1.0", @@ -15388,7 +15418,8 @@ "version": "0.2.2", "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", - "license": "Apache-2.0" + "license": "Apache-2.0", + "peer": true }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", @@ -15691,6 +15722,7 @@ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", "license": "Apache-2.0", + "peer": true, "dependencies": { "tslib": "^2.1.0" } @@ -15783,133 +15815,9 @@ "license": "MIT" }, "node_modules/schema-utils": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz", - "integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/json-schema": "^7.0.9", - "ajv": "^8.9.0", - "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.1.0" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/schema-utils/node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/schema-utils/node_modules/ajv-formats": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", - "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } - } - }, - "node_modules/schema-utils/node_modules/ajv-keywords": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", - "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3" - }, - "peerDependencies": { - "ajv": "^8.8.2" - } - }, - "node_modules/schema-utils/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, - "license": "MIT" - }, - "node_modules/seek-bzip": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/seek-bzip/-/seek-bzip-2.0.0.tgz", - "integrity": "sha512-SMguiTnYrhpLdk3PwfzHeotrcwi8bNV4iemL9tx9poR/yeaMYwB9VzR1w7b57DuWpuqR8n6oZboi0hj3AxZxQg==", - "dev": true, - "license": "MIT", - "dependencies": { - "commander": "^6.0.0" - }, - "bin": { - "seek-bunzip": "bin/seek-bunzip", - "seek-table": "bin/seek-bzip-table" - } - }, - "node_modules/seek-bzip/node_modules/commander": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", - "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, - "node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/semver-regex": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-4.0.5.tgz", - "integrity": "sha512-hunMQrEy1T6Jr2uEVjrAIqjwWcQTgOAcIM52C8MY1EZSD3DDNft04XzvYKPqjED65bNVVko0YI38nYeEHCX3yw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/semver-truncate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/semver-truncate/-/semver-truncate-3.0.0.tgz", - "integrity": "sha512-LJWA9kSvMolR51oDE6PN3kALBNaUdkxzAGcexw8gjMA8xr5zUqK0JiR3CgARSqanYF3Z1YHvsErb1KDgh+v7Rg==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", "dev": true, "license": "MIT", "dependencies": { @@ -17446,6 +17354,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -17866,6 +17775,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -18351,7 +18261,6 @@ "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", "dev": true, "license": "BSD-2-Clause", - "peer": true, "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" @@ -18366,7 +18275,6 @@ "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", "dev": true, "license": "BSD-2-Clause", - "peer": true, "engines": { "node": ">=4.0" }