From a4297a672d48f2af5cebfb83aacd48d6a8dfb318 Mon Sep 17 00:00:00 2001 From: Bram Borggreve Date: Thu, 8 Feb 2024 07:00:22 +0000 Subject: [PATCH] feat: add Redis and BullMQ --- .env.example | 4 + docker-compose.yml | 6 + .../src/lib/api-core-data-access.module.ts | 37 +-- .../data-access/src/lib/api-core.service.ts | 4 +- .../src/lib/config/api-core-config.module.ts | 19 ++ .../{ => config}/api-core-config.service.ts | 33 ++- .../src/lib/config/configuration.ts | 2 + .../src/lib/config/validation-schema.ts | 1 + .../lib/graphql/api-core-graphql.module.ts | 26 ++ .../src/lib/helpers/serve-static-factory.ts | 2 +- .../src/lib/queues/api-core-queues.module.ts | 37 +++ .../lib/queues/bull-dashboard-middleware.ts | 22 ++ .../src/lib/api-network.service.ts | 13 +- package.json | 6 + pnpm-lock.yaml | 260 ++++++++++++++++++ 15 files changed, 430 insertions(+), 42 deletions(-) create mode 100644 libs/api/core/data-access/src/lib/config/api-core-config.module.ts rename libs/api/core/data-access/src/lib/{ => config}/api-core-config.service.ts (89%) create mode 100644 libs/api/core/data-access/src/lib/graphql/api-core-graphql.module.ts create mode 100644 libs/api/core/data-access/src/lib/queues/api-core-queues.module.ts create mode 100644 libs/api/core/data-access/src/lib/queues/bull-dashboard-middleware.ts diff --git a/.env.example b/.env.example index 97abaf05..a43c7f7e 100644 --- a/.env.example +++ b/.env.example @@ -36,6 +36,8 @@ AUTH_REGISTER_ENABLED=true AUTH_SOLANA_ADMIN_IDS= # Enable login with Solana AUTH_SOLANA_ENABLED=true +# Enable Bull UI +BULL_ADMIN=admin:6c9107073a49d1e6129bfba07d494050c182d7630d3c86aca94ffa43cf1533f2 # Domains to allow cookies for (comma-separated) COOKIE_DOMAINS=localhost,127.0.0.1 # URL of the database to connect to @@ -54,6 +56,8 @@ JWT_SECRET= HOST=127.0.0.1 # Port to listen on PORT=3000 +# Redis configuration +REDIS_URL=redis://localhost:6379 # Session Secret (generate a random string with `openssl rand -hex 32`) SESSION_SECRET= # The URL of the Web UI, used to redirect to the Web UI after login. diff --git a/docker-compose.yml b/docker-compose.yml index 9a99e05c..3dec7f61 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -11,3 +11,9 @@ services: POSTGRES_PASSWORD: pubkey-link volumes: - ./tmp/postgres:/var/lib/postgresql/data + redis: + image: redis:7-alpine + ports: + - '6379:6379' + volumes: + - ./tmp/redis:/data diff --git a/libs/api/core/data-access/src/lib/api-core-data-access.module.ts b/libs/api/core/data-access/src/lib/api-core-data-access.module.ts index 554c8266..3a8db60e 100644 --- a/libs/api/core/data-access/src/lib/api-core-data-access.module.ts +++ b/libs/api/core/data-access/src/lib/api-core-data-access.module.ts @@ -1,45 +1,22 @@ -import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo' import { Module } from '@nestjs/common' -import { ConfigModule } from '@nestjs/config' -import { GraphQLModule } from '@nestjs/graphql' import { ScheduleModule } from '@nestjs/schedule' import { ServeStaticModule } from '@nestjs/serve-static' -import { join } from 'path' - -import { ApiCoreConfigService } from './api-core-config.service' import { ApiCoreProvisionService } from './api-core-provision.service' import { ApiCoreService } from './api-core.service' -import { configuration } from './config/configuration' -import { validationSchema } from './config/validation-schema' -import { AppContext } from './entity/app-context' +import { ApiCoreConfigModule } from './config/api-core-config.module' +import { ApiCoreGraphQLModule } from './graphql/api-core-graphql.module' import { serveStaticFactory } from './helpers/serve-static-factory' +import { ApiCoreQueuesModule } from './queues/api-core-queues.module' @Module({ imports: [ - ConfigModule.forRoot({ - cache: true, - load: [configuration], - validationSchema, - }), - GraphQLModule.forRoot({ - autoSchemaFile: join(process.cwd(), 'api-schema.graphql'), - sortSchema: true, - driver: ApolloDriver, - introspection: process.env['GRAPHQL_PLAYGROUND']?.toLowerCase() === 'true', - playground: { - settings: { - 'request.credentials': 'include', - }, - }, - resolvers: { - // JSON: GraphQLJSON, - }, - context: ({ req, res }: AppContext) => ({ req, res }), - }), + ApiCoreConfigModule, + ApiCoreGraphQLModule, + ApiCoreQueuesModule, ScheduleModule.forRoot(), ServeStaticModule.forRootAsync({ useFactory: serveStaticFactory() }), ], - providers: [ApiCoreService, ApiCoreConfigService, ApiCoreProvisionService], + providers: [ApiCoreService, ApiCoreProvisionService], exports: [ApiCoreService], }) export class ApiCoreDataAccessModule {} diff --git a/libs/api/core/data-access/src/lib/api-core.service.ts b/libs/api/core/data-access/src/lib/api-core.service.ts index 6aee5ad9..c3b8a1c7 100644 --- a/libs/api/core/data-access/src/lib/api-core.service.ts +++ b/libs/api/core/data-access/src/lib/api-core.service.ts @@ -1,8 +1,8 @@ import { Injectable } from '@nestjs/common' import { CommunityRole, IdentityProvider, LogLevel, Prisma } from '@prisma/client' import { LogRelatedType } from '@pubkey-link/sdk' -import { ApiCoreConfigService } from './api-core-config.service' import { ApiCorePrismaClient, prismaClient } from './api-core-prisma-client' +import { ApiCoreConfigService } from './config/api-core-config.service' import { slugifyId } from './helpers/slugify-id' @Injectable() @@ -78,8 +78,10 @@ export class ApiCoreService { } export interface CoreLogInput { + botId?: string | null data?: Prisma.InputJsonValue level: LogLevel relatedId?: string | null relatedType?: LogRelatedType | null + userId?: string | null } diff --git a/libs/api/core/data-access/src/lib/config/api-core-config.module.ts b/libs/api/core/data-access/src/lib/config/api-core-config.module.ts new file mode 100644 index 00000000..0a575ca2 --- /dev/null +++ b/libs/api/core/data-access/src/lib/config/api-core-config.module.ts @@ -0,0 +1,19 @@ +import { Module } from '@nestjs/common' +import { ConfigModule } from '@nestjs/config' + +import { ApiCoreConfigService } from './api-core-config.service' +import { configuration } from './configuration' +import { validationSchema } from './validation-schema' + +@Module({ + imports: [ + ConfigModule.forRoot({ + cache: true, + load: [configuration], + validationSchema, + }), + ], + providers: [ApiCoreConfigService], + exports: [ApiCoreConfigService], +}) +export class ApiCoreConfigModule {} diff --git a/libs/api/core/data-access/src/lib/api-core-config.service.ts b/libs/api/core/data-access/src/lib/config/api-core-config.service.ts similarity index 89% rename from libs/api/core/data-access/src/lib/api-core-config.service.ts rename to libs/api/core/data-access/src/lib/config/api-core-config.service.ts index 52a5b036..ea9cece7 100644 --- a/libs/api/core/data-access/src/lib/api-core-config.service.ts +++ b/libs/api/core/data-access/src/lib/config/api-core-config.service.ts @@ -1,9 +1,10 @@ import { Injectable, Logger } from '@nestjs/common' import { ConfigService } from '@nestjs/config' import { IdentityProvider } from '@prisma/client' +import { RedisOptions } from 'bullmq' import { CookieOptions } from 'express-serve-static-core' -import { ApiCoreConfig } from './config/configuration' -import { AppConfig } from './entity/app-config.entity' +import { AppConfig } from '../entity/app-config.entity' +import { ApiCoreConfig } from './configuration' @Injectable() export class ApiCoreConfigService { @@ -282,7 +283,33 @@ export class ApiCoreConfigService { } get prefix() { - return 'api' + return '/api' + } + + get redisOptions(): RedisOptions { + // Parse the Redis URL to get the host, port, and password, etc. + const parsed = new URL(this.redisUrl) + + // The URL class encodes the password if it contains special characters, so we need to decode it. + // https://nodejs.org/dist/latest-v18.x/docs/api/url.html#urlpassword + // This caused an issue because Azure Cache for Redis generates passwords that end with an equals sign. + const password = parsed.password ? decodeURIComponent(parsed.password) : undefined + + return { + host: parsed.hostname, + port: Number(parsed.port), + password: password, + username: parsed.username, + tls: parsed.protocol?.startsWith('rediss') + ? { + rejectUnauthorized: false, + } + : undefined, + } + } + + get redisUrl() { + return this.service.get('redisUrl') } get sessionSecret() { diff --git a/libs/api/core/data-access/src/lib/config/configuration.ts b/libs/api/core/data-access/src/lib/config/configuration.ts index 820ddac9..a6bf334d 100644 --- a/libs/api/core/data-access/src/lib/config/configuration.ts +++ b/libs/api/core/data-access/src/lib/config/configuration.ts @@ -66,6 +66,7 @@ export interface ApiCoreConfig { host: string jwtSecret: string port: number + redisUrl: string sessionSecret: string webUrl: string } @@ -109,6 +110,7 @@ export function configuration(): ApiCoreConfig { host: process.env['HOST'] as string, jwtSecret: process.env['JWT_SECRET'] as string, port: parseInt(process.env['PORT'] as string, 10) || 3000, + redisUrl: process.env['REDIS_URL'] as string, sessionSecret: process.env['SESSION_SECRET'] as string, webUrl: WEB_URL, } diff --git a/libs/api/core/data-access/src/lib/config/validation-schema.ts b/libs/api/core/data-access/src/lib/config/validation-schema.ts index 76a508aa..e0424ad6 100644 --- a/libs/api/core/data-access/src/lib/config/validation-schema.ts +++ b/libs/api/core/data-access/src/lib/config/validation-schema.ts @@ -45,6 +45,7 @@ export const validationSchema = Joi.object({ HOST: Joi.string().default('0.0.0.0'), NODE_ENV: Joi.string().valid('development', 'production', 'test', 'provision').default('development'), PORT: Joi.number().default(3000), + REDIS_URL: Joi.string().required().error(new Error(`REDIS_URL is required.`)), SESSION_SECRET: Joi.string().required(), SYNC_DRY_RUN: Joi.boolean().default(false), }) diff --git a/libs/api/core/data-access/src/lib/graphql/api-core-graphql.module.ts b/libs/api/core/data-access/src/lib/graphql/api-core-graphql.module.ts new file mode 100644 index 00000000..7e215704 --- /dev/null +++ b/libs/api/core/data-access/src/lib/graphql/api-core-graphql.module.ts @@ -0,0 +1,26 @@ +import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo' +import { Module } from '@nestjs/common' +import { GraphQLModule } from '@nestjs/graphql' +import { AppContext } from '../entity/app-context' +import { join } from 'path' + +@Module({ + imports: [ + GraphQLModule.forRoot({ + autoSchemaFile: join(process.cwd(), 'api-schema.graphql'), + sortSchema: true, + driver: ApolloDriver, + introspection: process.env['GRAPHQL_PLAYGROUND']?.toLowerCase() === 'true', + playground: { + settings: { + 'request.credentials': 'include', + }, + }, + resolvers: { + // JSON: GraphQLJSON, + }, + context: ({ req, res }: AppContext) => ({ req, res }), + }), + ], +}) +export class ApiCoreGraphQLModule {} diff --git a/libs/api/core/data-access/src/lib/helpers/serve-static-factory.ts b/libs/api/core/data-access/src/lib/helpers/serve-static-factory.ts index 82f9c581..100e2fc0 100644 --- a/libs/api/core/data-access/src/lib/helpers/serve-static-factory.ts +++ b/libs/api/core/data-access/src/lib/helpers/serve-static-factory.ts @@ -17,7 +17,7 @@ export function serveStaticFactory() { return [ { rootPath, - exclude: ['/api/*', '/graphql'], + exclude: ['/api/*', '/graphql', '/queues/'], }, ] } diff --git a/libs/api/core/data-access/src/lib/queues/api-core-queues.module.ts b/libs/api/core/data-access/src/lib/queues/api-core-queues.module.ts new file mode 100644 index 00000000..d6772239 --- /dev/null +++ b/libs/api/core/data-access/src/lib/queues/api-core-queues.module.ts @@ -0,0 +1,37 @@ +import { ExpressAdapter } from '@bull-board/express' +import { BullBoardModule } from '@bull-board/nestjs' +import { BullModule } from '@nestjs/bullmq' +import { Module } from '@nestjs/common' +import { ApiCoreConfigModule } from '../config/api-core-config.module' +import { ApiCoreConfigService } from '../config/api-core-config.service' +import { BullDashboardMiddleware } from './bull-dashboard-middleware' + +const logoUrl = 'https://avatars.githubusercontent.com/u/125477168?v=4' +@Module({ + imports: [ + BullModule.forRootAsync({ + imports: [ApiCoreConfigModule], + useFactory: async (config: ApiCoreConfigService) => ({ + prefix: 'pubkey:api', + connection: config.redisOptions, + defaultJobOptions: { + removeOnFail: { age: 24 * 3600 }, + }, + }), + inject: [ApiCoreConfigService], + }), + BullBoardModule.forRoot({ + route: '/queues', + boardOptions: { + uiConfig: { + boardTitle: 'PubKey API', + boardLogo: { path: logoUrl }, + favIcon: { default: logoUrl, alternative: logoUrl }, + }, + }, + adapter: ExpressAdapter, + middleware: BullDashboardMiddleware, + }), + ], +}) +export class ApiCoreQueuesModule {} diff --git a/libs/api/core/data-access/src/lib/queues/bull-dashboard-middleware.ts b/libs/api/core/data-access/src/lib/queues/bull-dashboard-middleware.ts new file mode 100644 index 00000000..a78ccd0c --- /dev/null +++ b/libs/api/core/data-access/src/lib/queues/bull-dashboard-middleware.ts @@ -0,0 +1,22 @@ +import { HttpStatus, Logger, NestMiddleware } from '@nestjs/common' +import { NextFunction, Request, Response } from 'express-serve-static-core' +import * as process from 'process' + +export class BullDashboardMiddleware implements NestMiddleware { + private readonly envCreds = process.env['BULL_ADMIN'] ?? `admin:${Math.random().toString(36).substring(7)}` + private readonly encodedCreds = Buffer.from(this.envCreds).toString('base64') + + constructor() { + Logger.verbose(`BullDashboardMiddleware: ${this.envCreds}`) + } + use(req: Request, res: Response, next: NextFunction) { + const reqCreds = req.get('authorization')?.split('Basic ')?.[1] ?? null + + if (this.encodedCreds && reqCreds !== this.encodedCreds) { + res.setHeader('WWW-Authenticate', 'Basic realm="realm", charset="UTF-8"') + res.sendStatus(HttpStatus.UNAUTHORIZED) + } else { + next() + } + } +} diff --git a/libs/api/network/data-access/src/lib/api-network.service.ts b/libs/api/network/data-access/src/lib/api-network.service.ts index 30ca03e0..04dc674c 100644 --- a/libs/api/network/data-access/src/lib/api-network.service.ts +++ b/libs/api/network/data-access/src/lib/api-network.service.ts @@ -18,6 +18,10 @@ export class ApiNetworkService { private readonly cacheAnybodiesVaults = new LRUCache({ max: 1000, ttl: 1000 * 60 * 60, // 1 hour + fetchMethod: async (vaultId) => { + this.logger.verbose(`cacheAnybodiesVaults: Cache miss for ${vaultId}`) + return getAnybodiesVaultSnapshot({ vaultId }) + }, }) private readonly solanaNetworkAssetsCache = new LRUCache({ @@ -255,7 +259,6 @@ export class ApiNetworkService { return res }) - // .then((accounts) => ({ owner, accounts, amount: `${accounts.length}` })) } async resolveSolanaNonFungibleAssetsForOwners({ @@ -382,12 +385,8 @@ export class ApiNetworkService { } private async getCachedAnybodiesVaultSnapshot({ vaultId }: { vaultId: string }): Promise { - if (!this.cacheAnybodiesVaults.has(vaultId)) { - this.logger.verbose(`getCachedAnybodiesVaultSnapshot: Cache miss for vaultId: ${vaultId}`) - const assets = await getAnybodiesVaultSnapshot({ vaultId }) - this.cacheAnybodiesVaults.set(vaultId, assets) - } - return this.cacheAnybodiesVaults.get(vaultId) ?? [] + const result = await this.cacheAnybodiesVaults.fetch(vaultId) + return result ?? [] } } /** diff --git a/package.json b/package.json index 33be8e95..d6cdb866 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,9 @@ "private": true, "dependencies": { "@apollo/server": "^4.10.0", + "@bull-board/api": "^5.14.0", + "@bull-board/express": "^5.14.0", + "@bull-board/nestjs": "^5.14.0", "@clack/prompts": "^0.7.0", "@coral-xyz/anchor": "^0.29.0", "@discordjs/rest": "^2.2.0", @@ -45,6 +48,7 @@ "@metaplex-foundation/umi-rpc-web3js": "^0.9.0", "@mrleebo/prisma-ast": "^0.8.0", "@nestjs/apollo": "^12.0.11", + "@nestjs/bullmq": "^10.1.0", "@nestjs/common": "10.3.0", "@nestjs/config": "^3.1.1", "@nestjs/core": "10.3.0", @@ -75,6 +79,7 @@ "bcrypt": "^5.1.1", "bs58": "^5.0.0", "buffer": "^6.0.3", + "bullmq": "^5.1.9", "clsx": "2.1.0", "cookie-parser": "^1.4.6", "discord.js": "^14.14.1", @@ -143,6 +148,7 @@ "@swc/jest": "0.2.29", "@testing-library/react": "14.1.2", "@types/bcrypt": "^5.0.2", + "@types/express": "^4.17.21", "@types/jest": "^29.5.11", "@types/node": "20.10.8", "@types/passport-discord": "^0.1.11", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 16bdfa63..ed51dcbb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,15 @@ dependencies: '@apollo/server': specifier: ^4.10.0 version: 4.10.0(graphql@16.8.1) + '@bull-board/api': + specifier: ^5.14.0 + version: 5.14.0(@bull-board/ui@5.14.0) + '@bull-board/express': + specifier: ^5.14.0 + version: 5.14.0 + '@bull-board/nestjs': + specifier: ^5.14.0 + version: 5.14.0(@bull-board/api@5.14.0)(@bull-board/express@5.14.0)(@nestjs/common@10.3.0)(@nestjs/core@10.3.0)(reflect-metadata@0.1.14)(rxjs@7.8.1) '@clack/prompts': specifier: ^0.7.0 version: 0.7.0 @@ -53,6 +62,9 @@ dependencies: '@nestjs/apollo': specifier: ^12.0.11 version: 12.0.11(@apollo/server@4.10.0)(@nestjs/common@10.3.0)(@nestjs/core@10.3.0)(@nestjs/graphql@12.0.11)(graphql@16.8.1) + '@nestjs/bullmq': + specifier: ^10.1.0 + version: 10.1.0(@nestjs/common@10.3.0)(@nestjs/core@10.3.0)(bullmq@5.1.9) '@nestjs/common': specifier: 10.3.0 version: 10.3.0(reflect-metadata@0.1.14)(rxjs@7.8.1) @@ -143,6 +155,9 @@ dependencies: buffer: specifier: ^6.0.3 version: 6.0.3 + bullmq: + specifier: ^5.1.9 + version: 5.1.9 clsx: specifier: 2.1.0 version: 2.1.0 @@ -343,6 +358,9 @@ devDependencies: '@types/bcrypt': specifier: ^5.0.2 version: 5.0.2 + '@types/express': + specifier: ^4.17.21 + version: 4.17.21 '@types/jest': specifier: ^29.5.11 version: 29.5.11 @@ -2868,6 +2886,51 @@ packages: - verdaccio dev: false + /@bull-board/api@5.14.0(@bull-board/ui@5.14.0): + resolution: {integrity: sha512-ppN9GeCH8QmCzs47CpDFwVb4Q5W2nK2QvcnbxKpjktCTonZ+5PnoWyXQvLStbcKU9SbMKAM0/OXhj4xOcSRllQ==} + peerDependencies: + '@bull-board/ui': 5.14.0 + dependencies: + '@bull-board/ui': 5.14.0 + redis-info: 3.1.0 + dev: false + + /@bull-board/express@5.14.0: + resolution: {integrity: sha512-lrCPvRmhNtAYDWGAW9v5P/r9mhkX+RVUkyAC5wjrhekOMGXtWk9Hyq7itE2DhAM8XwYM4mNQoSIprpFRGM9JRw==} + dependencies: + '@bull-board/api': 5.14.0(@bull-board/ui@5.14.0) + '@bull-board/ui': 5.14.0 + ejs: 3.1.9 + express: 4.18.2 + transitivePeerDependencies: + - supports-color + dev: false + + /@bull-board/nestjs@5.14.0(@bull-board/api@5.14.0)(@bull-board/express@5.14.0)(@nestjs/common@10.3.0)(@nestjs/core@10.3.0)(reflect-metadata@0.1.14)(rxjs@7.8.1): + resolution: {integrity: sha512-7Nb8EqaUQyUsxXweeTM/hgvWCvlJDsnpSr/5pB6KIzY2r2GAHzViXRS9YXRkcKt27cMcj3BFB00AkgBBOPEDiA==} + peerDependencies: + '@bull-board/api': ^5.14.0 + '@bull-board/express': ^5.14.0 + '@nestjs/common': ^9.0.0 || ^10.0.0 + '@nestjs/core': ^9.0.0 || ^10.0.0 + reflect-metadata: ^0.1.13 + rxjs: ^7.8.1 + dependencies: + '@bull-board/api': 5.14.0(@bull-board/ui@5.14.0) + '@bull-board/express': 5.14.0 + '@nestjs/bull-shared': 10.1.0(@nestjs/common@10.3.0)(@nestjs/core@10.3.0) + '@nestjs/common': 10.3.0(reflect-metadata@0.1.14)(rxjs@7.8.1) + '@nestjs/core': 10.3.0(@nestjs/common@10.3.0)(@nestjs/platform-express@10.3.0)(reflect-metadata@0.1.14)(rxjs@7.8.1) + reflect-metadata: 0.1.14 + rxjs: 7.8.1 + dev: false + + /@bull-board/ui@5.14.0: + resolution: {integrity: sha512-quustWmLsLbqdbCQd4Mud9Eo/2BQzfJSNSiyJt9OrtYT4AXHMgGtbFUy2Ycyda7iQjC4ScKl8f+WdFs4y+KUJA==} + dependencies: + '@bull-board/api': 5.14.0(@bull-board/ui@5.14.0) + dev: false + /@bundlr-network/client@0.7.17(debug@4.3.4): resolution: {integrity: sha512-1qTDrwgmgeh0pO24JbeGt2W8GlpWYkVnQ8AhEZ02Lm00J7RALSyma3C5pNlKuvAQBczL1r9KhLr9KHK1og3J0g==} deprecated: Bundlr is now Irys - please switch to @irys/sdk - this package will remain compatible with Irys for the foreseeable future. @@ -4225,6 +4288,10 @@ packages: /@humanwhocodes/object-schema@2.0.1: resolution: {integrity: sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==} + /@ioredis/commands@1.2.0: + resolution: {integrity: sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==} + dev: false + /@istanbuljs/load-nyc-config@1.1.0: resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==} engines: {node: '>=8'} @@ -4997,6 +5064,54 @@ packages: lilconfig: 2.1.0 dev: false + /@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.2: + resolution: {integrity: sha512-9bfjwDxIDWmmOKusUcqdS4Rw+SETlp9Dy39Xui9BEGEk19dDwH0jhipwFzEff/pFg95NKymc6TOTbRKcWeRqyQ==} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: false + optional: true + + /@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.2: + resolution: {integrity: sha512-lwriRAHm1Yg4iDf23Oxm9n/t5Zpw1lVnxYU3HnJPTi2lJRkKTrps1KVgvL6m7WvmhYVt/FIsssWay+k45QHeuw==} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: false + optional: true + + /@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.2: + resolution: {integrity: sha512-FU20Bo66/f7He9Fp9sP2zaJ1Q8L9uLPZQDub/WlUip78JlPeMbVL8546HbZfcW9LNciEXc8d+tThSJjSC+tmsg==} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@msgpackr-extract/msgpackr-extract-linux-arm@3.0.2: + resolution: {integrity: sha512-MOI9Dlfrpi2Cuc7i5dXdxPbFIgbDBGgKR5F2yWEa6FVEtSWncfVNKW5AKjImAQ6CZlBK9tympdsZJ2xThBiWWA==} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@msgpackr-extract/msgpackr-extract-linux-x64@3.0.2: + resolution: {integrity: sha512-gsWNDCklNy7Ajk0vBBf9jEx04RUxuDQfBse918Ww+Qb9HCPoGzS+XJTLe96iN3BVK7grnLiYghP/M4L8VsaHeA==} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@msgpackr-extract/msgpackr-extract-win32-x64@3.0.2: + resolution: {integrity: sha512-O+6Gs8UeDbyFpbSh2CPEz/UOrrdWPTBYNblZK5CxxLisYt4kGX3Sc+czffFonyjiGSq3jWLwJS/CCJc7tBr4sQ==} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: false + optional: true + /@nestjs/apollo@12.0.11(@apollo/server@4.10.0)(@nestjs/common@10.3.0)(@nestjs/core@10.3.0)(@nestjs/graphql@12.0.11)(graphql@16.8.1): resolution: {integrity: sha512-E8kBOyGBZ8Zx4qMLnK3+ECZgmLKqNHyYbtkOi0fXWr8ackosLMkRqGgtDVffXRlVA3eo6G3RgnL0Qyu3VvfD5A==} peerDependencies: @@ -5027,6 +5142,31 @@ packages: tslib: 2.6.2 dev: false + /@nestjs/bull-shared@10.1.0(@nestjs/common@10.3.0)(@nestjs/core@10.3.0): + resolution: {integrity: sha512-E1lAvVTCwbtBXySElkVrleXzr1bNuTCOLaQ1GmLSQGGlzXIvrXFXEIS1Dh1JCULICC25b7rGOfD3yL7uKRaMzw==} + peerDependencies: + '@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0 + '@nestjs/core': ^8.0.0 || ^9.0.0 || ^10.0.0 + dependencies: + '@nestjs/common': 10.3.0(reflect-metadata@0.1.14)(rxjs@7.8.1) + '@nestjs/core': 10.3.0(@nestjs/common@10.3.0)(@nestjs/platform-express@10.3.0)(reflect-metadata@0.1.14)(rxjs@7.8.1) + tslib: 2.6.2 + dev: false + + /@nestjs/bullmq@10.1.0(@nestjs/common@10.3.0)(@nestjs/core@10.3.0)(bullmq@5.1.9): + resolution: {integrity: sha512-e4QD3JilyOZAddyQ4ABj0rX7T2Rr0OVx4KwJKWTpaEqNQhBP4yVLZbSdEY+GFu1HEE8NkV92Q8BJJdCxlVphSw==} + peerDependencies: + '@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0 + '@nestjs/core': ^8.0.0 || ^9.0.0 || ^10.0.0 + bullmq: ^3.0.0 || ^4.0.0 || ^5.0.0 + dependencies: + '@nestjs/bull-shared': 10.1.0(@nestjs/common@10.3.0)(@nestjs/core@10.3.0) + '@nestjs/common': 10.3.0(reflect-metadata@0.1.14)(rxjs@7.8.1) + '@nestjs/core': 10.3.0(@nestjs/common@10.3.0)(@nestjs/platform-express@10.3.0)(reflect-metadata@0.1.14)(rxjs@7.8.1) + bullmq: 5.1.9 + tslib: 2.6.2 + dev: false + /@nestjs/common@10.3.0(reflect-metadata@0.1.14)(rxjs@7.8.1): resolution: {integrity: sha512-DGv34UHsZBxCM3H5QGE2XE/+oLJzz5+714JQjBhjD9VccFlQs3LRxo/epso4l7nJIiNlZkPyIUC8WzfU/5RTsQ==} peerDependencies: @@ -10738,6 +10878,22 @@ packages: dependencies: semver: 7.5.4 + /bullmq@5.1.9: + resolution: {integrity: sha512-9MfcQxYyfkG8kxpIxRsRXWYlTRQ1o8xWqgdoFR5pLClVTjtMI8qeDO5basRQLZPfp/uiPtv+gpzJ3OTNrm2ZNg==} + dependencies: + cron-parser: 4.9.0 + glob: 8.1.0 + ioredis: 5.3.2 + lodash: 4.17.21 + msgpackr: 1.10.1 + node-abort-controller: 3.1.1 + semver: 7.5.4 + tslib: 2.6.2 + uuid: 9.0.1 + transitivePeerDependencies: + - supports-color + dev: false + /busboy@1.6.0: resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} engines: {node: '>=10.16.0'} @@ -11078,6 +11234,11 @@ packages: engines: {node: '>=6'} dev: false + /cluster-key-slot@1.1.2: + resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==} + engines: {node: '>=0.10.0'} + dev: false + /co@4.6.0: resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} @@ -11444,6 +11605,13 @@ packages: /create-require@1.1.1: resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} + /cron-parser@4.9.0: + resolution: {integrity: sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==} + engines: {node: '>=12.0.0'} + dependencies: + luxon: 3.4.4 + dev: false + /cron@3.1.3: resolution: {integrity: sha512-KVxeKTKYj2eNzN4ElnT6nRSbjbfhyxR92O/Jdp6SH3pc05CDJws59jBrZWEMQlxevCiE6QUTrXy+Im3vC3oD3A==} dependencies: @@ -11993,6 +12161,11 @@ packages: resolution: {integrity: sha512-KNTihKNmQENUZeKu5fzfpzRqR5S2VMp4gl9RFHiWzj9DfvYQPMJ6XHKNaQxaGCXwPk6y9yme3aUoaiAe+KX+vg==} dev: false + /denque@2.1.0: + resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==} + engines: {node: '>=0.10'} + dev: false + /depd@1.1.2: resolution: {integrity: sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==} engines: {node: '>= 0.6'} @@ -13612,6 +13785,17 @@ packages: once: 1.4.0 path-is-absolute: 1.0.1 + /glob@8.1.0: + resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==} + engines: {node: '>=12'} + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 5.1.6 + once: 1.4.0 + dev: false + /global-dirs@3.0.1: resolution: {integrity: sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==} engines: {node: '>=10'} @@ -14275,6 +14459,23 @@ packages: dependencies: loose-envify: 1.4.0 + /ioredis@5.3.2: + resolution: {integrity: sha512-1DKMMzlIHM02eBBVOFQ1+AolGjs6+xEcM4PDL7NqOS6szq7H9jSaEkIUH6/a5Hl241LzW6JLSiAbNvTQjUupUA==} + engines: {node: '>=12.22.0'} + dependencies: + '@ioredis/commands': 1.2.0 + cluster-key-slot: 1.1.2 + debug: 4.3.4(supports-color@8.1.1) + denque: 2.1.0 + lodash.defaults: 4.2.0 + lodash.isarguments: 3.1.0 + redis-errors: 1.2.0 + redis-parser: 3.0.0 + standard-as-callback: 2.1.0 + transitivePeerDependencies: + - supports-color + dev: false + /ip@1.1.8: resolution: {integrity: sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg==} dev: false @@ -15774,10 +15975,18 @@ packages: /lodash.debounce@4.0.8: resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} + /lodash.defaults@4.2.0: + resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==} + dev: false + /lodash.includes@4.3.0: resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==} dev: false + /lodash.isarguments@3.1.0: + resolution: {integrity: sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==} + dev: false + /lodash.isboolean@3.0.3: resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==} dev: false @@ -16534,6 +16743,28 @@ packages: /ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + /msgpackr-extract@3.0.2: + resolution: {integrity: sha512-SdzXp4kD/Qf8agZ9+iTu6eql0m3kWm1A2y1hkpTeVNENutaB0BwHlSvAIaMxwntmRUAUjon2V4L8Z/njd0Ct8A==} + hasBin: true + requiresBuild: true + dependencies: + node-gyp-build-optional-packages: 5.0.7 + optionalDependencies: + '@msgpackr-extract/msgpackr-extract-darwin-arm64': 3.0.2 + '@msgpackr-extract/msgpackr-extract-darwin-x64': 3.0.2 + '@msgpackr-extract/msgpackr-extract-linux-arm': 3.0.2 + '@msgpackr-extract/msgpackr-extract-linux-arm64': 3.0.2 + '@msgpackr-extract/msgpackr-extract-linux-x64': 3.0.2 + '@msgpackr-extract/msgpackr-extract-win32-x64': 3.0.2 + dev: false + optional: true + + /msgpackr@1.10.1: + resolution: {integrity: sha512-r5VRLv9qouXuLiIBrLpl2d5ZvPt8svdQTl5/vMvE4nzDMyEX4sgW5yWhuBBj5UmgwOTWj8CIdSXn5sAfsHAWIQ==} + optionalDependencies: + msgpackr-extract: 3.0.2 + dev: false + /multer@1.4.4-lts.1: resolution: {integrity: sha512-WeSGziVj6+Z2/MwQo3GvqzgR+9Uc+qt8SwHKh3gvNPiISKfsMfG4SvCOFYlxxgkXt7yIV2i1yczehm0EOKIxIg==} engines: {node: '>= 6.0.0'} @@ -16683,6 +16914,13 @@ packages: engines: {node: '>= 6.13.0'} dev: true + /node-gyp-build-optional-packages@5.0.7: + resolution: {integrity: sha512-YlCCc6Wffkx0kHkmam79GKvDQ6x+QZkMjFGrIMxgFNILFvGSbCp2fCBC55pGTT9gVaz8Na5CLmxt/urtzRv36w==} + hasBin: true + requiresBuild: true + dev: false + optional: true + /node-gyp-build@4.7.1: resolution: {integrity: sha512-wTSrZ+8lsRRa3I3H8Xr65dLWSgCvY2l4AOnaeKdPA9TB/WYMPaTcrzf3rXvFoVvjKNVnu0CcWSx54qq9GKRUYg==} hasBin: true @@ -18902,6 +19140,24 @@ packages: tslib: 2.6.2 dev: false + /redis-errors@1.2.0: + resolution: {integrity: sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==} + engines: {node: '>=4'} + dev: false + + /redis-info@3.1.0: + resolution: {integrity: sha512-ER4L9Sh/vm63DkIE0bkSjxluQlioBiBgf5w1UuldaW/3vPcecdljVDisZhmnCMvsxHNiARTTDDHGg9cGwTfrKg==} + dependencies: + lodash: 4.17.21 + dev: false + + /redis-parser@3.0.0: + resolution: {integrity: sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==} + engines: {node: '>=4'} + dependencies: + redis-errors: 1.2.0 + dev: false + /reflect-metadata@0.1.14: resolution: {integrity: sha512-ZhYeb6nRaXCfhnndflDK8qI6ZQ/YcWZCISRAWICW9XYqMUwjZM9Z0DveWX/ABN01oxSHwVxKQmxeYZSsm0jh5A==} @@ -19769,6 +20025,10 @@ packages: type-fest: 0.7.1 dev: false + /standard-as-callback@2.1.0: + resolution: {integrity: sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==} + dev: false + /start-server-and-test@1.15.4: resolution: {integrity: sha512-ucQtp5+UCr0m4aHlY+aEV2JSYNTiMZKdSKK/bsIr6AlmwAWDYDnV7uGlWWEtWa7T4XvRI5cPYcPcQgeLqpz+Tg==} engines: {node: '>=6'}