From ad5087c9d874c2a931bbccdb38f0f2dbeef6d0d9 Mon Sep 17 00:00:00 2001 From: Edwin Edjokpa Date: Fri, 28 Feb 2025 19:00:47 +0100 Subject: [PATCH 1/5] feat: added pusher service for real-time notifications, added spec file to test service action --- .gitignore | 1 + docker-compose.yaml | 43 ++++++++++ package.json | 1 + src/app.module.ts | 2 + src/modules/pusher/pusher.module.ts | 8 ++ src/modules/pusher/pusher.service.ts | 28 +++++++ .../pusher/tests/pusher.service.spec.ts | 81 +++++++++++++++++++ 7 files changed, 164 insertions(+) create mode 100644 docker-compose.yaml create mode 100644 src/modules/pusher/pusher.module.ts create mode 100644 src/modules/pusher/pusher.service.ts create mode 100644 src/modules/pusher/tests/pusher.service.spec.ts diff --git a/.gitignore b/.gitignore index 52ef48ea5..e1f72278d 100644 --- a/.gitignore +++ b/.gitignore @@ -415,3 +415,4 @@ data/ .dev.env + diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 000000000..fd9e06766 --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,43 @@ +services: + server: + container_name: server + build: . + ports: + - '3008:3008' + env_file: + - .env + postgres: + container_name: postgres-boiler + image: postgres:latest + ports: + - '5432:5432' + environment: + - POSTGRES_USER=${DB_USERNAME} + - POSTGRES_PASSWORD=${DB_PASSWORD} + - POSTGRES_DB=${DB_NAME} + volumes: + - db_data:/var/lib/postgresql/data + restart: always + + adminer: + image: adminer + container_name: adminer-boiler + ports: + - '8080:8080' + restart: always + depends_on: + - postgres + + redis: + image: redis:latest + container_name: redis-boiler + ports: + - '6379:6379' + command: ['redis-server', '--appendonly', 'yes'] + volumes: + - redis_data:/data + restart: always + +volumes: + db_data: + redis_data: diff --git a/package.json b/package.json index 847f7b500..b6e6ccc4e 100644 --- a/package.json +++ b/package.json @@ -69,6 +69,7 @@ "passport-google-oauth20": "^2.0.0", "passport-jwt": "^4.0.1", "pg": "^8.13.3", + "pusher": "^5.2.0", "reflect-metadata": "^0.2.2", "rxjs": "^7.8.2", "sharp": "^0.33.5", diff --git a/src/app.module.ts b/src/app.module.ts index 276af8f52..65a80fe69 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -48,6 +48,7 @@ import { ServeStaticModule } from '@nestjs/serve-static'; import { join } from 'path'; import { LanguageGuard } from '@guards/language.guard'; import { ApiStatusModule } from '@modules/api-status/api-status.module'; +import { PusherModule } from '@modules/pusher/pusher.module'; @Module({ providers: [ @@ -174,6 +175,7 @@ import { ApiStatusModule } from '@modules/api-status/api-status.module'; }, }), ApiStatusModule, + PusherModule, ], controllers: [HealthController, ProbeController], }) diff --git a/src/modules/pusher/pusher.module.ts b/src/modules/pusher/pusher.module.ts new file mode 100644 index 000000000..bd88bcbe4 --- /dev/null +++ b/src/modules/pusher/pusher.module.ts @@ -0,0 +1,8 @@ +import { Module } from '@nestjs/common'; +import { PusherService } from './pusher.service'; + +@Module({ + providers: [PusherService], + exports: [PusherService], +}) +export class PusherModule {} diff --git a/src/modules/pusher/pusher.service.ts b/src/modules/pusher/pusher.service.ts new file mode 100644 index 000000000..5e8ead5f1 --- /dev/null +++ b/src/modules/pusher/pusher.service.ts @@ -0,0 +1,28 @@ +// src/pusher/pusher.service.ts +import { Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import * as Pusher from 'pusher'; + +@Injectable() +export class PusherService { + public pusher: Pusher; + + constructor(private readonly configService: ConfigService) { + this.pusher = new Pusher({ + appId: this.configService.get('PUSHER_APP_ID'), + key: this.configService.get('PUSHER_APP_KEY'), + secret: this.configService.get('PUSHER_APP_SECRET'), + cluster: this.configService.get('PUSHER_APP_CLUSTER'), + useTLS: true, + }); + } + + async triggerEvent(channel: string, event: string, data: any) { + try { + await this.pusher.trigger(channel, event, data); + console.log('Notifications sent to pusher successfully'); + } catch (error) { + console.log(error); + } + } +} diff --git a/src/modules/pusher/tests/pusher.service.spec.ts b/src/modules/pusher/tests/pusher.service.spec.ts new file mode 100644 index 000000000..d34a4bd74 --- /dev/null +++ b/src/modules/pusher/tests/pusher.service.spec.ts @@ -0,0 +1,81 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { PusherService } from '../pusher.service'; +import { ConfigService } from '@nestjs/config'; +import * as Pusher from 'pusher'; + +const mockConfigService = { + get: jest.fn((key: string) => { + const config = { + PUSHER_APP_ID: 'testAppId', + PUSHER_APP_KEY: 'testAppKey', + PUSHER_APP_SECRET: 'testAppSecret', + PUSHER_APP_CLUSTER: 'testCluster', + }; + return config[key]; + }), +}; + +jest.mock('pusher', () => { + return jest.fn().mockImplementation(() => ({ + trigger: jest.fn().mockResolvedValue(true), + })); +}); + +describe('PusherService', () => { + let pusherService: PusherService; + let pusherInstance: Pusher; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [PusherService, { provide: ConfigService, useValue: mockConfigService }], + }).compile(); + + pusherService = module.get(PusherService); + pusherInstance = module.get(PusherService).pusher; + }); + + it('should be defined', () => { + expect(pusherService).toBeDefined(); + }); + + it('should call pusher trigger method with correct parameters', async () => { + const channel = 'test-channel'; + const event = 'test-event'; + const data = { message: 'test message' }; + + await pusherService.triggerEvent(channel, event, data); + + expect(pusherInstance.trigger).toHaveBeenCalledWith(channel, event, data); + expect(pusherInstance.trigger).toHaveBeenCalledTimes(1); + }); + + it('should log success message when event is triggered successfully', async () => { + const channel = 'test-channel'; + const event = 'test-event'; + const data = { message: 'test message' }; + + const consoleSpy = jest.spyOn(console, 'log').mockImplementation(); + + await pusherService.triggerEvent(channel, event, data); + + expect(consoleSpy).toHaveBeenCalledWith('Notifications sent to pusher successfully'); + + consoleSpy.mockRestore(); + }); + + it('should log error when pusher trigger fails', async () => { + (pusherInstance.trigger as jest.Mock).mockRejectedValueOnce(new Error('Pusher Error')); + + const consoleSpy = jest.spyOn(console, 'log').mockImplementation(); + + const channel = 'test-channel'; + const event = 'test-event'; + const data = { message: 'test message' }; + + await pusherService.triggerEvent(channel, event, data); + + expect(consoleSpy).toHaveBeenCalledWith(new Error('Pusher Error')); + + consoleSpy.mockRestore(); + }); +}); From a66139eb00a6a9b22f708b0587801f6c7428de4d Mon Sep 17 00:00:00 2001 From: Edwin Edjokpa Date: Fri, 28 Feb 2025 20:36:13 +0100 Subject: [PATCH 2/5] fix: pusher tests --- src/health.controller.ts | 1 + src/modules/pusher/pusher.service.ts | 5 +-- .../pusher/tests/pusher.service.spec.ts | 35 ++++++------------- 3 files changed, 14 insertions(+), 27 deletions(-) diff --git a/src/health.controller.ts b/src/health.controller.ts index 64792ed1f..78c48bc28 100644 --- a/src/health.controller.ts +++ b/src/health.controller.ts @@ -1,6 +1,7 @@ import { skipAuth } from '@shared/helpers/skipAuth'; import { Controller, Get } from '@nestjs/common'; import * as os from 'os'; +import { PusherService } from '@modules/pusher/pusher.service'; @Controller() export default class HealthController { diff --git a/src/modules/pusher/pusher.service.ts b/src/modules/pusher/pusher.service.ts index 5e8ead5f1..9173295ab 100644 --- a/src/modules/pusher/pusher.service.ts +++ b/src/modules/pusher/pusher.service.ts @@ -20,9 +20,10 @@ export class PusherService { async triggerEvent(channel: string, event: string, data: any) { try { await this.pusher.trigger(channel, event, data); - console.log('Notifications sent to pusher successfully'); + console.log('Notifications sent to Pusher successfully'); } catch (error) { - console.log(error); + console.log('Error triggering Pusher event:', error); + throw new Error('Pusher Error'); } } } diff --git a/src/modules/pusher/tests/pusher.service.spec.ts b/src/modules/pusher/tests/pusher.service.spec.ts index d34a4bd74..96e70590f 100644 --- a/src/modules/pusher/tests/pusher.service.spec.ts +++ b/src/modules/pusher/tests/pusher.service.spec.ts @@ -49,33 +49,18 @@ describe('PusherService', () => { expect(pusherInstance.trigger).toHaveBeenCalledTimes(1); }); - it('should log success message when event is triggered successfully', async () => { - const channel = 'test-channel'; - const event = 'test-event'; - const data = { message: 'test message' }; - - const consoleSpy = jest.spyOn(console, 'log').mockImplementation(); - - await pusherService.triggerEvent(channel, event, data); - - expect(consoleSpy).toHaveBeenCalledWith('Notifications sent to pusher successfully'); - - consoleSpy.mockRestore(); + it('should trigger event successfully', async () => { + const result = await pusherService.triggerEvent('test-channel', 'test-event', { message: 'new notification' }); + expect(result).toBeUndefined(); + expect(pusherInstance.trigger).toHaveBeenCalledWith('test-channel', 'test-event', { message: 'new notification' }); }); - it('should log error when pusher trigger fails', async () => { - (pusherInstance.trigger as jest.Mock).mockRejectedValueOnce(new Error('Pusher Error')); - - const consoleSpy = jest.spyOn(console, 'log').mockImplementation(); - - const channel = 'test-channel'; - const event = 'test-event'; - const data = { message: 'test message' }; - - await pusherService.triggerEvent(channel, event, data); - - expect(consoleSpy).toHaveBeenCalledWith(new Error('Pusher Error')); + it('should throw an error if Pusher fails', async () => { + (pusherInstance.trigger as jest.Mock).mockRejectedValue(new Error('Pusher Error')); + await expect(async () => { + await pusherService.triggerEvent('test-channel', 'test-event', { message: 'new notification' }); + }).rejects.toThrow('Pusher Error'); - consoleSpy.mockRestore(); + expect(pusherInstance.trigger).toHaveBeenCalledWith('test-channel', 'test-event', { message: 'new notification' }); }); }); From ce17f2e9455e7f465e8fb81922cb6daeb545c270 Mon Sep 17 00:00:00 2001 From: Edwin Edjokpa Date: Fri, 28 Feb 2025 20:45:58 +0100 Subject: [PATCH 3/5] chore: improve pusher.service.spec --- src/modules/pusher/pusher.service.ts | 2 -- .../pusher/tests/pusher.service.spec.ts | 18 +++++++----------- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/src/modules/pusher/pusher.service.ts b/src/modules/pusher/pusher.service.ts index 9173295ab..3de8fa965 100644 --- a/src/modules/pusher/pusher.service.ts +++ b/src/modules/pusher/pusher.service.ts @@ -20,9 +20,7 @@ export class PusherService { async triggerEvent(channel: string, event: string, data: any) { try { await this.pusher.trigger(channel, event, data); - console.log('Notifications sent to Pusher successfully'); } catch (error) { - console.log('Error triggering Pusher event:', error); throw new Error('Pusher Error'); } } diff --git a/src/modules/pusher/tests/pusher.service.spec.ts b/src/modules/pusher/tests/pusher.service.spec.ts index 96e70590f..0b27bb562 100644 --- a/src/modules/pusher/tests/pusher.service.spec.ts +++ b/src/modules/pusher/tests/pusher.service.spec.ts @@ -38,7 +38,7 @@ describe('PusherService', () => { expect(pusherService).toBeDefined(); }); - it('should call pusher trigger method with correct parameters', async () => { + it('should trigger event successfully', async () => { const channel = 'test-channel'; const event = 'test-event'; const data = { message: 'test message' }; @@ -49,18 +49,14 @@ describe('PusherService', () => { expect(pusherInstance.trigger).toHaveBeenCalledTimes(1); }); - it('should trigger event successfully', async () => { - const result = await pusherService.triggerEvent('test-channel', 'test-event', { message: 'new notification' }); - expect(result).toBeUndefined(); - expect(pusherInstance.trigger).toHaveBeenCalledWith('test-channel', 'test-event', { message: 'new notification' }); - }); - it('should throw an error if Pusher fails', async () => { + const channel = 'test-channel'; + const event = 'test-event'; + const data = { message: 'new notification' }; + (pusherInstance.trigger as jest.Mock).mockRejectedValue(new Error('Pusher Error')); - await expect(async () => { - await pusherService.triggerEvent('test-channel', 'test-event', { message: 'new notification' }); - }).rejects.toThrow('Pusher Error'); + await expect(pusherService.triggerEvent(channel, event, data)).rejects.toThrow('Pusher Error'); - expect(pusherInstance.trigger).toHaveBeenCalledWith('test-channel', 'test-event', { message: 'new notification' }); + expect(pusherInstance.trigger).toHaveBeenCalledWith(channel, event, data); }); }); From 6b5b64c9784f58f702cfc8fa1b71d9b3bcb1edb7 Mon Sep 17 00:00:00 2001 From: Edwin Edjokpa Date: Fri, 28 Feb 2025 20:59:02 +0100 Subject: [PATCH 4/5] fix: removed docker-compose.yaml from git tracking --- .gitignore | 1 + docker-compose.yaml | 43 ------------------------------------------- 2 files changed, 1 insertion(+), 43 deletions(-) delete mode 100644 docker-compose.yaml diff --git a/.gitignore b/.gitignore index e1f72278d..4a43592ff 100644 --- a/.gitignore +++ b/.gitignore @@ -416,3 +416,4 @@ data/ +docker-compose.yaml diff --git a/docker-compose.yaml b/docker-compose.yaml deleted file mode 100644 index fd9e06766..000000000 --- a/docker-compose.yaml +++ /dev/null @@ -1,43 +0,0 @@ -services: - server: - container_name: server - build: . - ports: - - '3008:3008' - env_file: - - .env - postgres: - container_name: postgres-boiler - image: postgres:latest - ports: - - '5432:5432' - environment: - - POSTGRES_USER=${DB_USERNAME} - - POSTGRES_PASSWORD=${DB_PASSWORD} - - POSTGRES_DB=${DB_NAME} - volumes: - - db_data:/var/lib/postgresql/data - restart: always - - adminer: - image: adminer - container_name: adminer-boiler - ports: - - '8080:8080' - restart: always - depends_on: - - postgres - - redis: - image: redis:latest - container_name: redis-boiler - ports: - - '6379:6379' - command: ['redis-server', '--appendonly', 'yes'] - volumes: - - redis_data:/data - restart: always - -volumes: - db_data: - redis_data: From 74a62b0dfb62a9efa1c5106d52f266d9e4b0d3db Mon Sep 17 00:00:00 2001 From: Edwin Edjokpa Date: Fri, 28 Feb 2025 21:24:19 +0100 Subject: [PATCH 5/5] fix: removed docker-compose.yaml from git tracking --- .gitignore | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.gitignore b/.gitignore index 4a43592ff..4bb95ab41 100644 --- a/.gitignore +++ b/.gitignore @@ -413,7 +413,3 @@ package-lock.json docker-compose.yml data/ .dev.env - - - -docker-compose.yaml