diff --git a/apps/edr-passenger-api/.env.example b/apps/edr-passenger-api/.env.example index 5ea89f4e5..1a9c71990 100644 --- a/apps/edr-passenger-api/.env.example +++ b/apps/edr-passenger-api/.env.example @@ -116,3 +116,12 @@ FAYDA_CLAIMS_LOCALES=en am FAYDA_SESSION_TTL_MINUTES=10 GITHUB_PACKAGE_TOKEN= + +# --- Payment event consumer (RabbitMQ) ------------------------------------------------------- +# Consumes payment.succeeded / payment.failed events from the payment microservice. Separate +# from any RABBITMQ_URL used by the IAM/notification modules so the two connections are +# independent. Points at the dedicated `payment` vhost on the (shared) broker. +# Local dev broker (docker): amqp://edr:edr_secret@localhost:5672/payment +PAYMENT_RABBITMQ_URL=amqp://edr:edr_secret@localhost:5672/payment +# Max unacknowledged payment events this consumer holds at once. +PAYMENT_EVENTS_PREFETCH=10 diff --git a/apps/edr-passenger-api/package.json b/apps/edr-passenger-api/package.json index 3ff2f8b62..4ff9ba2c6 100644 --- a/apps/edr-passenger-api/package.json +++ b/apps/edr-passenger-api/package.json @@ -22,6 +22,7 @@ "dependencies": { "@edr/types": "workspace:*", + "@golevelup/nestjs-rabbitmq": "^5.5.0", "@nestjs/axios": "^4.0.1", "@nestjs/common": "^11.0.0", "@nestjs/config": "^4.0.4", diff --git a/apps/edr-passenger-api/src/app.module.ts b/apps/edr-passenger-api/src/app.module.ts index 16178a1b4..b747b77c1 100644 --- a/apps/edr-passenger-api/src/app.module.ts +++ b/apps/edr-passenger-api/src/app.module.ts @@ -14,6 +14,7 @@ import ebirrConfig from './config/ebirr.config'; import cardConfig from './config/card.config'; import waafiConfig from './config/waafi.config'; import faydaConfig from './config/fayda.config'; +import rabbitmqConfig from './config/rabbitmq.config'; import { AuthModule } from './modules/auth/auth.module'; import { StationsModule } from './modules/stations/stations.module'; import { FleetModule } from './modules/fleet/fleet.module'; @@ -52,6 +53,7 @@ import { VerifaydaModule } from './modules/verifayda/verifayda.module'; cardConfig, waafiConfig, faydaConfig, + rabbitmqConfig, ], }), ScheduleModule.forRoot(), diff --git a/apps/edr-passenger-api/src/common/filters/http-exception.filter.ts b/apps/edr-passenger-api/src/common/filters/http-exception.filter.ts index f02ce4abf..e50172db9 100644 --- a/apps/edr-passenger-api/src/common/filters/http-exception.filter.ts +++ b/apps/edr-passenger-api/src/common/filters/http-exception.filter.ts @@ -12,6 +12,12 @@ export class HttpExceptionFilter implements ExceptionFilter { private readonly logger = new Logger(HttpExceptionFilter.name); catch(exception: unknown, host: ArgumentsHost): void { + // Global filter — also reached by non-HTTP (e.g. RabbitMQ) handlers. switchToHttp() would + // yield no response object there, so re-throw and let the transport (golevelup) handle it + // (nack/dead-letter) instead of crashing on response.status(). + if (host.getType() !== 'http') { + throw exception; + } const ctx = host.switchToHttp(); const response = ctx.getResponse(); const request = ctx.getRequest(); diff --git a/apps/edr-passenger-api/src/common/interceptors/response-transform.interceptor.ts b/apps/edr-passenger-api/src/common/interceptors/response-transform.interceptor.ts index d1cd41c7b..83f810966 100644 --- a/apps/edr-passenger-api/src/common/interceptors/response-transform.interceptor.ts +++ b/apps/edr-passenger-api/src/common/interceptors/response-transform.interceptor.ts @@ -4,7 +4,14 @@ import { map } from 'rxjs/operators'; @Injectable() export class ResponseTransformInterceptor implements NestInterceptor { - intercept(_ctx: ExecutionContext, next: CallHandler): Observable { + intercept(ctx: ExecutionContext, next: CallHandler): Observable { + // Only wrap HTTP responses. This interceptor is global, so it also runs for RabbitMQ + // message handlers (golevelup uses Nest's context system) — there, wrapping the return + // value would corrupt the handler's contract (e.g. a returned Nack would be swallowed, + // dropping a message instead of dead-lettering it). Let non-HTTP returns pass through. + if (ctx.getType() !== 'http') { + return next.handle(); + } return next.handle().pipe( map((data) => ({ success: true, data, timestamp: new Date().toISOString() })), ); diff --git a/apps/edr-passenger-api/src/config/rabbitmq.config.ts b/apps/edr-passenger-api/src/config/rabbitmq.config.ts new file mode 100644 index 000000000..d6d5ee335 --- /dev/null +++ b/apps/edr-passenger-api/src/config/rabbitmq.config.ts @@ -0,0 +1,12 @@ +import { registerAs } from '@nestjs/config'; + +/** + * RabbitMQ connection for the payment-event consumer (payment microservice -> passenger). + * Independent of the IAM/notification module's RABBITMQ_URL so the two broker connections + * never interfere. Points at the dedicated `payment` vhost on the shared broker. + */ +export default registerAs('rabbitmq', () => ({ + url: process.env.PAYMENT_RABBITMQ_URL ?? 'amqp://localhost:5672/payment', + /** Max unacked payment events held by this consumer at once. */ + prefetch: parseInt(process.env.PAYMENT_EVENTS_PREFETCH ?? '10', 10), +})); diff --git a/apps/edr-passenger-api/src/modules/payments/payment-events.consumer.ts b/apps/edr-passenger-api/src/modules/payments/payment-events.consumer.ts new file mode 100644 index 000000000..291402240 --- /dev/null +++ b/apps/edr-passenger-api/src/modules/payments/payment-events.consumer.ts @@ -0,0 +1,47 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { Nack, RabbitSubscribe } from '@golevelup/nestjs-rabbitmq'; +import { + PAYMENT_EVENTS_DLX, + PAYMENT_EVENTS_EXCHANGE, + PAYMENT_QUEUES, + PaymentEvent, + PaymentService, + paymentServiceBindingPattern, +} from '@edr/types'; +import { PaymentEventDto } from './internal-payments.dto'; +import { PaymentsService } from './payments.service'; + +const PASSENGER_QUEUE = PAYMENT_QUEUES[PaymentService.PASSENGER]; + +@Injectable() +export class PaymentEventsConsumer { + private readonly logger = new Logger(PaymentEventsConsumer.name); + + constructor(private readonly paymentsService: PaymentsService) {} + + @RabbitSubscribe({ + exchange: PAYMENT_EVENTS_EXCHANGE, + routingKey: paymentServiceBindingPattern(PaymentService.PASSENGER), // payment.passenger.* + queue: PASSENGER_QUEUE.main, + queueOptions: { + durable: true, + deadLetterExchange: PAYMENT_EVENTS_DLX, + }, + }) + async handle(event: PaymentEvent): Promise { + try { + const result = await this.paymentsService.handlePaymentEvent( + event as unknown as PaymentEventDto, + ); + this.logger.log( + `processed ${event.eventType} (${event.eventId}) ref=${event.referenceId}: ${JSON.stringify(result)}`, + ); + } catch (err) { + const message = err instanceof Error ? err.message : String(err); + this.logger.error( + `DEAD-LETTERING ${event.eventType} (${event.eventId}) ref=${event.referenceId}: ${message}`, + ); + return new Nack(false); + } + } +} diff --git a/apps/edr-passenger-api/src/modules/payments/payments.module.ts b/apps/edr-passenger-api/src/modules/payments/payments.module.ts index dfab3e9f1..056986613 100644 --- a/apps/edr-passenger-api/src/modules/payments/payments.module.ts +++ b/apps/edr-passenger-api/src/modules/payments/payments.module.ts @@ -1,25 +1,61 @@ import { Module } from "@nestjs/common"; import { HttpModule } from "@nestjs/axios"; +import { ConfigService } from "@nestjs/config"; +import { RabbitMQModule } from "@golevelup/nestjs-rabbitmq"; +import { + PAYMENT_EVENTS_DLX, + PAYMENT_EVENTS_EXCHANGE, + PAYMENT_QUEUES, + PaymentService, + paymentServiceBindingPattern, +} from "@edr/types"; import { PaymentsController } from "./payments.controller"; import { PaymentsService } from "./payments.service"; import { InternalPaymentsController } from "./internal-payments.controller"; import { PaymentClientService } from "./payment-client.service"; +import { PaymentEventsConsumer } from "./payment-events.consumer"; import { ServiceAuthGuard } from "../../common/guards/service-auth.guard"; import { SeatsModule } from "../seats/seats.module"; import { TicketsModule } from "../tickets/tickets.module"; -/** - * Post-cutover (docs/payment-service phase 6): provider gateways and webhook handlers live in - * apps/edr-payment-api. This module keeps domain validation, the WALLET flow, the payment - * client, and the idempotent mark-paid consumer. - */ +const PASSENGER_QUEUE = PAYMENT_QUEUES[PaymentService.PASSENGER]; + @Module({ imports: [ SeatsModule, TicketsModule, HttpModule.register({ timeout: 10_000 }), + RabbitMQModule.forRootAsync({ + inject: [ConfigService], + useFactory: (config: ConfigService) => ({ + uri: config.get("rabbitmq.url") as string, + exchanges: [ + { + name: PAYMENT_EVENTS_EXCHANGE, + type: "topic", + options: { durable: true }, + }, + { name: PAYMENT_EVENTS_DLX, type: "topic", options: { durable: true } }, + ], + queues: [ + { + name: PASSENGER_QUEUE.dlq, + exchange: PAYMENT_EVENTS_DLX, + routingKey: paymentServiceBindingPattern(PaymentService.PASSENGER), + options: { durable: true }, + }, + ], + prefetchCount: config.get("rabbitmq.prefetch") ?? 10, + connectionInitOptions: { wait: false }, + }), + }), ], controllers: [PaymentsController, InternalPaymentsController], - providers: [PaymentsService, PaymentClientService, ServiceAuthGuard], + providers: [ + PaymentsService, + PaymentClientService, + PaymentEventsConsumer, + ServiceAuthGuard, + ], }) export class PaymentsModule {} diff --git a/apps/edr-payment-api/package.json b/apps/edr-payment-api/package.json index d85dc83dc..bdcda721b 100644 --- a/apps/edr-payment-api/package.json +++ b/apps/edr-payment-api/package.json @@ -20,6 +20,7 @@ "@edr/api-common": "workspace:*", "@edr/payment-providers": "workspace:*", "@edr/types": "workspace:*", + "@golevelup/nestjs-rabbitmq": "^5.5.0", "@nestjs/axios": "^4.0.1", "@nestjs/common": "^11.0.0", "@nestjs/config": "^4.0.0", diff --git a/apps/edr-payment-api/src/app.module.ts b/apps/edr-payment-api/src/app.module.ts index 4a99472d0..de92f3909 100644 --- a/apps/edr-payment-api/src/app.module.ts +++ b/apps/edr-payment-api/src/app.module.ts @@ -5,6 +5,7 @@ import { TypeOrmModule, TypeOrmModuleOptions } from "@nestjs/typeorm"; import appConfig from "./config/app.config"; import databaseConfig from "./config/database.config"; import notifierConfig from "./config/notifier.config"; +import rabbitmqConfig from "./config/rabbitmq.config"; import telebirrConfig from "./config/telebirr.config"; import waafiConfig from "./config/waafi.config"; import cbeConfig from "./config/cbe.config"; @@ -26,6 +27,7 @@ import { WebhooksModule } from "./modules/webhooks/webhooks.module"; appConfig, databaseConfig, notifierConfig, + rabbitmqConfig, telebirrConfig, waafiConfig, cbeConfig, diff --git a/apps/edr-payment-api/src/config/rabbitmq.config.ts b/apps/edr-payment-api/src/config/rabbitmq.config.ts new file mode 100644 index 000000000..631a5cb68 --- /dev/null +++ b/apps/edr-payment-api/src/config/rabbitmq.config.ts @@ -0,0 +1,23 @@ +import { registerAs } from "@nestjs/config"; + +/** + * RabbitMQ connection for the outbox event publisher (payment -> apps). + * `publisherTransport` selects which PaymentEventPublisher the outbox relay uses: + * "rabbitmq" -> RabbitMqPaymentEventPublisher (publish to the payment.events exchange) [DEFAULT] + * "http" -> HttpPaymentEventPublisher (POST to each app's mark-paid endpoint) [rollback] + * RabbitMQ is the default; set PUBLISHER_TRANSPORT=http explicitly to fall back to HTTP. + */ +export default registerAs("rabbitmq", () => ({ + url: process.env.PAYMENT_RABBITMQ_URL ?? "amqp://localhost:5672/payment", + publisherTransport: (process.env.PUBLISHER_TRANSPORT ?? "rabbitmq").toLowerCase(), + /** Publish is failed (so the relay retries) if the broker does not confirm within this. */ + publishTimeoutMs: parseInt(process.env.PAYMENT_PUBLISH_TIMEOUT_MS ?? "10000", 10), +})); + +/** + * True when the outbox relay should publish to RabbitMQ rather than POST over HTTP. + * RabbitMQ is the default; only an explicit PUBLISHER_TRANSPORT=http opts out. + */ +export function isRabbitPublisher(): boolean { + return (process.env.PUBLISHER_TRANSPORT ?? "rabbitmq").toLowerCase() !== "http"; +} diff --git a/apps/edr-payment-api/src/modules/outbox/outbox.module.ts b/apps/edr-payment-api/src/modules/outbox/outbox.module.ts index 85f464afe..eae5515e8 100644 --- a/apps/edr-payment-api/src/modules/outbox/outbox.module.ts +++ b/apps/edr-payment-api/src/modules/outbox/outbox.module.ts @@ -1,19 +1,56 @@ import { Module } from "@nestjs/common"; import { HttpModule } from "@nestjs/axios"; +import { ConfigService } from "@nestjs/config"; import { TypeOrmModule } from "@nestjs/typeorm"; +import { RabbitMQModule } from "@golevelup/nestjs-rabbitmq"; +import { PAYMENT_EVENTS_DLX, PAYMENT_EVENTS_EXCHANGE } from "@edr/types"; +import { isRabbitPublisher } from "../../config/rabbitmq.config"; import { NotificationOutbox } from "./entities/notification-outbox.entity"; import { OutboxRelayService } from "./outbox-relay.service"; import { OutboxRepository } from "./outbox.repository"; import { HttpPaymentEventPublisher } from "./publisher/http-payment-event-publisher"; import { PAYMENT_EVENT_PUBLISHER } from "./publisher/payment-event-publisher"; +import { RabbitMqPaymentEventPublisher } from "./publisher/rabbitmq-payment-event-publisher"; + +const rabbitImports = isRabbitPublisher() + ? [ + RabbitMQModule.forRootAsync({ + inject: [ConfigService], + useFactory: (config: ConfigService) => ({ + uri: config.get("rabbitmq.url") as string, + exchanges: [ + { + name: PAYMENT_EVENTS_EXCHANGE, + type: "topic", + options: { durable: true }, + }, + { + name: PAYMENT_EVENTS_DLX, + type: "topic", + options: { durable: true }, + }, + ], + connectionInitOptions: { wait: false }, + }), + }), + ] + : []; @Module({ - imports: [TypeOrmModule.forFeature([NotificationOutbox]), HttpModule], + imports: [ + TypeOrmModule.forFeature([NotificationOutbox]), + HttpModule, + ...rabbitImports, + ], providers: [ OutboxRepository, OutboxRelayService, - // Swap to RabbitPaymentEventPublisher here when the broker lands — nothing else changes. - { provide: PAYMENT_EVENT_PUBLISHER, useClass: HttpPaymentEventPublisher }, + { + provide: PAYMENT_EVENT_PUBLISHER, + useClass: isRabbitPublisher() + ? RabbitMqPaymentEventPublisher + : HttpPaymentEventPublisher, + }, ], exports: [OutboxRepository], }) diff --git a/apps/edr-payment-api/src/modules/outbox/publisher/rabbitmq-payment-event-publisher.ts b/apps/edr-payment-api/src/modules/outbox/publisher/rabbitmq-payment-event-publisher.ts new file mode 100644 index 000000000..0453b22ca --- /dev/null +++ b/apps/edr-payment-api/src/modules/outbox/publisher/rabbitmq-payment-event-publisher.ts @@ -0,0 +1,61 @@ +import { Injectable, Logger } from "@nestjs/common"; +import { ConfigService } from "@nestjs/config"; +import { AmqpConnection } from "@golevelup/nestjs-rabbitmq"; +import { + PAYMENT_EVENTS_EXCHANGE, + PaymentEvent, + paymentRoutingKey, +} from "@edr/types"; +import { PaymentEventPublisher } from "./payment-event-publisher"; + +@Injectable() +export class RabbitMqPaymentEventPublisher implements PaymentEventPublisher { + private readonly logger = new Logger(RabbitMqPaymentEventPublisher.name); + private readonly publishTimeoutMs: number; + + constructor( + private readonly amqp: AmqpConnection, + config: ConfigService, + ) { + this.publishTimeoutMs = + config.get("rabbitmq.publishTimeoutMs") ?? 10_000; + } + + async publish(event: PaymentEvent): Promise { + const routingKey = paymentRoutingKey(event.service, event.eventType); + + const confirmed = await this.withTimeout( + this.amqp.publish(PAYMENT_EVENTS_EXCHANGE, routingKey, event, { + persistent: true, + messageId: event.eventId, + contentType: "application/json", + type: event.eventType, + headers: { "x-event-version": event.version }, + }), + ); + + if (!confirmed) { + throw new Error( + `broker did not confirm publish of ${event.eventType} (${event.eventId})`, + ); + } + + this.logger.log( + `published ${event.eventType} (${event.eventId}) -> ${routingKey}`, + ); + } + + private withTimeout(p: Promise): Promise { + let timer: NodeJS.Timeout; + const timeout = new Promise((_, reject) => { + timer = setTimeout( + () => + reject( + new Error(`publish confirm timed out after ${this.publishTimeoutMs}ms`), + ), + this.publishTimeoutMs, + ); + }); + return Promise.race([p, timeout]).finally(() => clearTimeout(timer)); + } +} diff --git a/docker-compose.yaml b/docker-compose.yaml index 1bbcd9515..81cc286d7 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -6,6 +6,24 @@ # Run: docker compose up -d services: + # Message broker for payment event delivery (payment-api -> passenger/freight). + # Management UI: http://localhost:15672 (login edr / edr_secret). vhost: payment. + # In deployed envs this is a shared/managed RabbitMQ; only PAYMENT_RABBITMQ_URL changes. + rabbitmq: + image: rabbitmq:3.13-management + ports: + - "5672:5672" # AMQP — apps connect here + - "15672:15672" # management UI + environment: + RABBITMQ_DEFAULT_USER: edr + RABBITMQ_DEFAULT_PASS: edr_secret + RABBITMQ_DEFAULT_VHOST: payment + healthcheck: + test: ["CMD", "rabbitmq-diagnostics", "-q", "ping"] + interval: 10s + timeout: 5s + retries: 5 + freight-api: build: context: . @@ -25,6 +43,9 @@ services: - "${PASSENGER_API_PORT:-4000}:${PASSENGER_API_PORT:-4000}" env_file: - apps/edr-passenger-api/.env + depends_on: + rabbitmq: + condition: service_healthy freight-portal: build: @@ -96,6 +117,9 @@ services: - "${PAYMENT_API_PORT:-3008}:${PAYMENT_API_PORT:-3008}" env_file: - apps/edr-payment-api/.env + depends_on: + rabbitmq: + condition: service_healthy secrets: npmrc: diff --git a/packages/types/src/common/index.ts b/packages/types/src/common/index.ts index 4ccd3dca3..317898d80 100644 --- a/packages/types/src/common/index.ts +++ b/packages/types/src/common/index.ts @@ -1,4 +1,5 @@ export * from "./payments"; +export * from "./payment-messaging"; export interface BaseEntity { id: string; diff --git a/packages/types/src/common/payment-messaging.ts b/packages/types/src/common/payment-messaging.ts new file mode 100644 index 000000000..ea9794e2b --- /dev/null +++ b/packages/types/src/common/payment-messaging.ts @@ -0,0 +1,55 @@ +/* ------------------------------------------------------------------------------------------------ + * Payment event messaging contract (RabbitMQ) + * + * The single source of truth for the broker topology shared between the payment microservice + * (publisher) and the domain apps (consumers). Both sides import these constants/helpers so the + * exchange name, routing keys, and queue names can never drift apart. + * + * Topology (see docs/payment-service/rabbitmq/): + * exchange payment.events (topic, durable) ← every payment event is published here + * exchange payment.events.dlx (topic, durable) ← dead-letter for events a consumer rejects + * routing payment.. e.g. payment.passenger.succeeded + * queue .payment-events bound to payment..* + * queue .payment-events.dlq dead-letter queue (bound on the dlx) + * ---------------------------------------------------------------------------------------------- */ + +import { PaymentEventType, PaymentService } from "./payments"; + +/** Topic exchange every payment event is published to. */ +export const PAYMENT_EVENTS_EXCHANGE = "payment.events"; + +/** Dead-letter exchange for payment events a consumer could not process (poison messages). */ +export const PAYMENT_EVENTS_DLX = "payment.events.dlx"; + +/** + * Routing key for a payment event: `payment..`. + * e.g. `payment.passenger.succeeded`, `payment.freight.failed`. + */ +export function paymentRoutingKey( + service: PaymentService, + eventType: PaymentEventType, +): string { + // "payment.succeeded" -> "succeeded", "payment.failed" -> "failed" + const outcome = eventType.split(".")[1]; + return `payment.${service.toLowerCase()}.${outcome}`; +} + +/** Binding pattern a service's queue uses so it receives only its own events. */ +export function paymentServiceBindingPattern(service: PaymentService): string { + return `payment.${service.toLowerCase()}.*`; +} + +/** Durable queue names per owning service: the main work queue and its dead-letter queue. */ +export const PAYMENT_QUEUES: Record< + PaymentService, + { main: string; dlq: string } +> = { + [PaymentService.PASSENGER]: { + main: "passenger.payment-events", + dlq: "passenger.payment-events.dlq", + }, + [PaymentService.FREIGHT]: { + main: "freight.payment-events", + dlq: "freight.payment-events.dlq", + }, +}; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b6771011e..89e4b2c58 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -395,6 +395,9 @@ importers: '@edr/types': specifier: workspace:* version: link:../../packages/types + '@golevelup/nestjs-rabbitmq': + specifier: ^5.5.0 + version: 5.7.0(@nestjs/common@11.1.24(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.24)(reflect-metadata@0.2.2)(rxjs@7.8.2) '@nestjs/axios': specifier: ^4.0.1 version: 4.0.1(@nestjs/common@11.1.24(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(axios@1.17.0)(rxjs@7.8.2) @@ -680,6 +683,9 @@ importers: '@edr/types': specifier: workspace:* version: link:../../packages/types + '@golevelup/nestjs-rabbitmq': + specifier: ^5.5.0 + version: 5.7.0(@nestjs/common@11.1.24(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.24)(reflect-metadata@0.2.2)(rxjs@7.8.2) '@nestjs/axios': specifier: ^4.0.1 version: 4.0.1(@nestjs/common@11.1.24(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(axios@1.17.0)(rxjs@7.8.2) @@ -1594,6 +1600,20 @@ packages: '@gilbarbara/deep-equal@0.3.1': resolution: {integrity: sha512-I7xWjLs2YSVMc5gGx1Z3ZG1lgFpITPndpi8Ku55GeEIKpACCPQNS/OTqQbxgTCfq0Ncvcc+CrFov96itVh6Qvw==} + '@golevelup/nestjs-discovery@4.0.3': + resolution: {integrity: sha512-8w3CsXHN7+7Sn2i419Eal1Iw/kOjAd6Kb55M/ZqKBBwACCMn4WiEuzssC71LpBMI1090CiDxuelfPRwwIrQK+A==} + peerDependencies: + '@nestjs/common': ^10.x || ^11.0.0 + '@nestjs/core': ^10.x || ^11.0.0 + + '@golevelup/nestjs-rabbitmq@5.7.0': + resolution: {integrity: sha512-0j+r+Fg515UCJUds8irkTpQwps0LVGnEP1aLGr4VDflGdr1jmvVkFukfUtpa/cMuKC9vFe2v1LBe/D584Vpobg==} + peerDependencies: + '@nestjs/common': ^10.x || ^11.0.0 + '@nestjs/core': ^10.x || ^11.0.0 + reflect-metadata: ^0.2.2 + rxjs: ^7.x + '@hello-pangea/dnd@18.0.1': resolution: {integrity: sha512-xojVWG8s/TGrKT1fC8K2tIWeejJYTAeJuj36zM//yEm/ZrnZUSFGS15BpO+jGZT1ybWvyXmeDJwPYb4dhWlbZQ==} peerDependencies: @@ -5003,12 +5023,22 @@ packages: resolution: {integrity: sha512-GrTZLRpmp6wIC2ztrWW9MjjTgSKccffgFagbNDOX95/dcjEcYZibYTeaOntySQLcdw1ztBoFkviiUvTMbb9MYg==} engines: {node: '>=0.10.0'} + amqp-connection-manager@4.1.15: + resolution: {integrity: sha512-YGi8MuO2K4u0qC4b1qAKVIK8CdBJF+PpEZpHKxmrL3x61pU1sAb2SHzQl7ypoqr6oECIfVqtG9DGdOZxaUq1Wg==} + engines: {node: '>=10.0.0', npm: '>5.0.0'} + peerDependencies: + amqplib: '*' + amqp-connection-manager@5.0.0: resolution: {integrity: sha512-88yQzqa5RSBgnLl504XjvCQJ7d+osskdwvg35Lwm1LRbfLjNU9p7SQUMSP82BB7mseiq9tIUPJ3HE3eXQbpjEw==} engines: {node: '>=10.0.0', npm: '>5.0.0'} peerDependencies: amqplib: '*' + amqplib@0.10.9: + resolution: {integrity: sha512-jwSftI4QjS3mizvnSnOrPGYiUnm1vI2OP1iXeOUz5pb74Ua0nbf6nPyyTzuiCLEE3fMpaJORXh2K/TQ08H5xGA==} + engines: {node: '>=10'} + amqplib@2.0.1: resolution: {integrity: sha512-a3P2MgfCf9nzVis12VxWEn0dS6hcqve7dlEAhXDtIWR27BlhtMkILOc+H9aeHjDi6i6r94dYKc2Kx2OFe3avvg==} engines: {node: '>=18'} @@ -5659,6 +5689,9 @@ packages: buffer-indexof@1.1.1: resolution: {integrity: sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g==} + buffer-more-ints@1.0.0: + resolution: {integrity: sha512-EMetuGFz5SLsT0QTnXzINh4Ksr+oo4i+UGTXEshiGCQWnsgSs7ZhJ8fzlwQ+OzEMs0MpDAMr1hxnblp5a4vcHg==} + buffer-xor@1.0.3: resolution: {integrity: sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==} @@ -13493,6 +13526,23 @@ snapshots: '@gilbarbara/deep-equal@0.3.1': {} + '@golevelup/nestjs-discovery@4.0.3(@nestjs/common@11.1.24(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.24)': + dependencies: + '@nestjs/common': 11.1.24(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.24(@nestjs/common@11.1.24(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/microservices@11.1.24)(@nestjs/platform-express@11.1.24)(reflect-metadata@0.2.2)(rxjs@7.8.2) + lodash: 4.18.1 + + '@golevelup/nestjs-rabbitmq@5.7.0(@nestjs/common@11.1.24(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.24)(reflect-metadata@0.2.2)(rxjs@7.8.2)': + dependencies: + '@golevelup/nestjs-discovery': 4.0.3(@nestjs/common@11.1.24(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.24) + '@nestjs/common': 11.1.24(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.24(@nestjs/common@11.1.24(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/microservices@11.1.24)(@nestjs/platform-express@11.1.24)(reflect-metadata@0.2.2)(rxjs@7.8.2) + amqp-connection-manager: 4.1.15(amqplib@0.10.9) + amqplib: 0.10.9 + lodash: 4.18.1 + reflect-metadata: 0.2.2 + rxjs: 7.8.2 + '@hello-pangea/dnd@18.0.1(@types/react@18.3.31)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: '@babel/runtime': 7.29.7 @@ -18491,11 +18541,21 @@ snapshots: longest: 1.0.1 repeat-string: 1.6.1 + amqp-connection-manager@4.1.15(amqplib@0.10.9): + dependencies: + amqplib: 0.10.9 + promise-breaker: 6.0.0 + amqp-connection-manager@5.0.0(amqplib@2.0.1): dependencies: amqplib: 2.0.1 promise-breaker: 6.0.0 + amqplib@0.10.9: + dependencies: + buffer-more-ints: 1.0.0 + url-parse: 1.5.10 + amqplib@2.0.1: {} ansi-bgblack@0.1.1: @@ -19290,6 +19350,8 @@ snapshots: buffer-indexof@1.1.1: {} + buffer-more-ints@1.0.0: {} + buffer-xor@1.0.3: {} buffer@4.9.2: