From dbc71c255570d5aee71d70b5e58ccc60e48d60f3 Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Sat, 18 Oct 2025 13:27:43 +0200 Subject: [PATCH] feat(server): socket module --- packages/server/package.json | 3 ++ packages/server/src/main.ts | 6 +++ packages/server/src/modules/App/App.module.ts | 2 + .../BankingPlaid/BankingPlaid.module.ts | 4 +- .../jobs/PlaidFetchTransactionsJob.ts | 4 +- .../src/modules/Import/Import.controller.ts | 2 +- .../src/modules/Socket/Socket.gateway.ts | 51 +++++++++++++++++++ .../src/modules/Socket/Socket.module.ts | 10 ++++ .../Subscription/Subscription.module.ts | 4 +- ...ggerInvalidateCacheOnSubscriptionChange.ts | 9 ++-- 10 files changed, 87 insertions(+), 8 deletions(-) create mode 100644 packages/server/src/modules/Socket/Socket.gateway.ts create mode 100644 packages/server/src/modules/Socket/Socket.module.ts diff --git a/packages/server/package.json b/packages/server/package.json index dbd2486c6..a13eba482 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -38,10 +38,12 @@ "@nestjs/jwt": "^10.2.0", "@nestjs/passport": "^11.0.5", "@nestjs/platform-express": "^10.0.0", + "@nestjs/platform-socket.io": "^10.0.0", "@nestjs/schedule": "^4.1.2", "@nestjs/serve-static": "^5.0.3", "@nestjs/swagger": "^7.4.2", "@nestjs/throttler": "^6.2.1", + "@nestjs/websockets": "^10.0.0", "@supercharge/promise-pool": "^3.2.0", "@types/multer": "^1.4.11", "@types/nodemailer": "^6.4.17", @@ -100,6 +102,7 @@ "remeda": "^2.19.2", "rxjs": "^7.8.1", "serialize-interceptor": "^1.1.7", + "socket.io": "^4.8.1", "strategy": "^1.1.1", "stripe": "^16.10.0", "uniqid": "^5.2.0", diff --git a/packages/server/src/main.ts b/packages/server/src/main.ts index 8990a2020..5be6bfe23 100644 --- a/packages/server/src/main.ts +++ b/packages/server/src/main.ts @@ -18,6 +18,12 @@ async function bootstrap() { const app = await NestFactory.create(AppModule); app.setGlobalPrefix('/api'); + // Enable CORS for socket.io + app.enableCors({ + origin: true, + credentials: true, + }); + // create and mount the middleware manually here app.use(new ClsMiddleware({}).use); diff --git a/packages/server/src/modules/App/App.module.ts b/packages/server/src/modules/App/App.module.ts index 0123189f7..375fb25ec 100644 --- a/packages/server/src/modules/App/App.module.ts +++ b/packages/server/src/modules/App/App.module.ts @@ -94,6 +94,7 @@ import { BankingPlaidModule } from '../BankingPlaid/BankingPlaid.module'; import { BankingCategorizeModule } from '../BankingCategorize/BankingCategorize.module'; import { TenantModelsInitializeModule } from '../Tenancy/TenantModelsInitialize.module'; import { BillLandedCostsModule } from '../BillLandedCosts/BillLandedCosts.module'; +import { SocketModule } from '../Socket/Socket.module'; @Module({ imports: [ @@ -226,6 +227,7 @@ import { BillLandedCostsModule } from '../BillLandedCosts/BillLandedCosts.module MiscellaneousModule, UsersModule, ContactsModule, + SocketModule, ], controllers: [AppController], providers: [ diff --git a/packages/server/src/modules/BankingPlaid/BankingPlaid.module.ts b/packages/server/src/modules/BankingPlaid/BankingPlaid.module.ts index bde51bf57..c208c1f29 100644 --- a/packages/server/src/modules/BankingPlaid/BankingPlaid.module.ts +++ b/packages/server/src/modules/BankingPlaid/BankingPlaid.module.ts @@ -1,5 +1,6 @@ import { BullModule } from '@nestjs/bullmq'; import { Module } from '@nestjs/common'; +import { SocketModule } from '../Socket/Socket.module'; import { PlaidUpdateTransactionsOnItemCreatedSubscriber } from './subscribers/PlaidUpdateTransactionsOnItemCreatedSubscriber'; import { PlaidUpdateTransactions } from './command/PlaidUpdateTransactions'; import { PlaidSyncDb } from './command/PlaidSyncDB'; @@ -26,6 +27,7 @@ const models = [RegisterTenancyModel(PlaidItem)]; @Module({ imports: [ + SocketModule, PlaidModule, AccountsModule, BankingCategorizeModule, @@ -49,4 +51,4 @@ const models = [RegisterTenancyModel(PlaidItem)]; exports: [...models], controllers: [BankingPlaidController, BankingPlaidWebhooksController], }) -export class BankingPlaidModule {} +export class BankingPlaidModule { } diff --git a/packages/server/src/modules/BankingPlaid/jobs/PlaidFetchTransactionsJob.ts b/packages/server/src/modules/BankingPlaid/jobs/PlaidFetchTransactionsJob.ts index b59ba1935..03c2a53ac 100644 --- a/packages/server/src/modules/BankingPlaid/jobs/PlaidFetchTransactionsJob.ts +++ b/packages/server/src/modules/BankingPlaid/jobs/PlaidFetchTransactionsJob.ts @@ -10,6 +10,7 @@ import { } from '../types/BankingPlaid.types'; import { PlaidUpdateTransactions } from '../command/PlaidUpdateTransactions'; import { SetupPlaidItemTenantService } from '../command/SetupPlaidItemTenant.service'; +import { SocketGateway } from '../../Socket/Socket.gateway'; @Processor({ name: UpdateBankingPlaidTransitionsQueueJob, @@ -19,6 +20,7 @@ export class PlaidFetchTransactionsProcessor extends WorkerHost { constructor( private readonly plaidFetchTransactionsService: PlaidUpdateTransactions, private readonly setupPlaidItemService: SetupPlaidItemTenantService, + private readonly socketGateway: SocketGateway, ) { super(); } @@ -38,7 +40,7 @@ export class PlaidFetchTransactionsProcessor extends WorkerHost { ); }); // Notify the frontend to reflect the new transactions changes. - // io.emit('NEW_TRANSACTIONS_DATA', { plaidItemId }); + this.socketGateway.emitNewTransactionsData(); } catch (error) { console.log(error); } diff --git a/packages/server/src/modules/Import/Import.controller.ts b/packages/server/src/modules/Import/Import.controller.ts index 466bd26f2..61567932a 100644 --- a/packages/server/src/modules/Import/Import.controller.ts +++ b/packages/server/src/modules/Import/Import.controller.ts @@ -22,7 +22,7 @@ import { ApiCommonHeaders } from '@/common/decorators/ApiCommonHeaders'; @ApiTags('Import') @ApiCommonHeaders() export class ImportController { - constructor(private readonly importResourceApp: ImportResourceApplication) {} + constructor(private readonly importResourceApp: ImportResourceApplication) { } /** * Imports xlsx/csv to the given resource type. diff --git a/packages/server/src/modules/Socket/Socket.gateway.ts b/packages/server/src/modules/Socket/Socket.gateway.ts new file mode 100644 index 000000000..be20afea7 --- /dev/null +++ b/packages/server/src/modules/Socket/Socket.gateway.ts @@ -0,0 +1,51 @@ +import { + WebSocketGateway, + WebSocketServer, + OnGatewayConnection, + OnGatewayDisconnect, + OnGatewayInit, +} from '@nestjs/websockets'; +import { Server, Socket } from 'socket.io'; +import { Logger } from '@nestjs/common'; + +@WebSocketGateway({ + namespace: '/', + path: '/socket', + cors: { + origin: '*', + methods: ['GET', 'POST'], + }, +}) +export class SocketGateway + implements OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect { + @WebSocketServer() + server: Server; + + private logger: Logger = new Logger('SocketGateway'); + + afterInit(server: Server) { + this.logger.log('Socket.IO Gateway initialized'); + } + + handleConnection(client: Socket, ...args: any[]) { + this.logger.log(`Client connected: ${client.id}`); + } + + handleDisconnect(client: Socket) { + this.logger.log(`Client disconnected: ${client.id}`); + } + + // Method to emit NEW_TRANSACTIONS_DATA event + emitNewTransactionsData() { + this.server.emit('NEW_TRANSACTIONS_DATA'); + this.logger.log('Emitted NEW_TRANSACTIONS_DATA event'); + } + + // Method to emit SUBSCRIPTION_CHANGED event + emitSubscriptionChanged() { + this.server.emit('SUBSCRIPTION_CHANGED'); + this.logger.log('Emitted SUBSCRIPTION_CHANGED event'); + } +} + + diff --git a/packages/server/src/modules/Socket/Socket.module.ts b/packages/server/src/modules/Socket/Socket.module.ts new file mode 100644 index 000000000..8d7219be1 --- /dev/null +++ b/packages/server/src/modules/Socket/Socket.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common'; +import { SocketGateway } from './Socket.gateway'; + +@Module({ + providers: [SocketGateway], + exports: [SocketGateway], +}) +export class SocketModule { } + + diff --git a/packages/server/src/modules/Subscription/Subscription.module.ts b/packages/server/src/modules/Subscription/Subscription.module.ts index f26aa5119..a00e11daf 100644 --- a/packages/server/src/modules/Subscription/Subscription.module.ts +++ b/packages/server/src/modules/Subscription/Subscription.module.ts @@ -1,4 +1,5 @@ import { Module } from '@nestjs/common'; +import { SocketModule } from '../Socket/Socket.module'; import { CancelLemonSubscription } from './commands/CancelLemonSubscription.service'; import { ChangeLemonSubscription } from './commands/ChangeLemonSubscription.service'; import { ResumeLemonSubscription } from './commands/ResumeLemonSubscription.service'; @@ -25,6 +26,7 @@ import { PlanSubscriptionRepository } from './repositories/PlanSubscription.repo const models = [InjectSystemModel(Plan), InjectSystemModel(PlanSubscription)]; @Module({ + imports: [SocketModule], providers: [ ...models, TenancyContext, @@ -48,4 +50,4 @@ const models = [InjectSystemModel(Plan), InjectSystemModel(PlanSubscription)]; controllers: [SubscriptionsController, SubscriptionsLemonWebhook], exports: [...models], }) -export class SubscriptionModule {} +export class SubscriptionModule { } diff --git a/packages/server/src/modules/Subscription/subscribers/TriggerInvalidateCacheOnSubscriptionChange.ts b/packages/server/src/modules/Subscription/subscribers/TriggerInvalidateCacheOnSubscriptionChange.ts index abe88c4d0..592e80c0e 100644 --- a/packages/server/src/modules/Subscription/subscribers/TriggerInvalidateCacheOnSubscriptionChange.ts +++ b/packages/server/src/modules/Subscription/subscribers/TriggerInvalidateCacheOnSubscriptionChange.ts @@ -1,16 +1,17 @@ import { events } from '@/common/events/events'; import { Injectable } from '@nestjs/common'; import { OnEvent } from '@nestjs/event-emitter'; +import { SocketGateway } from '../../Socket/Socket.gateway'; @Injectable() export class TriggerInvalidateCacheOnSubscriptionChange { + constructor(private readonly socketGateway: SocketGateway) { } + @OnEvent(events.subscription.onSubscriptionCancelled) @OnEvent(events.subscription.onSubscriptionResumed) @OnEvent(events.subscription.onSubscriptionPlanChanged) triggerInvalidateCache() { - // const io = Container.get('socket'); - - // // Notify the frontend to reflect the new transactions changes. - // io.emit('SUBSCRIPTION_CHANGED', { subscriptionSlug: 'main' }); + // Notify the frontend to reflect the subscription changes. + this.socketGateway.emitSubscriptionChanged(); } }