mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +00:00
Update email and sms services
This commit is contained in:
@@ -19,7 +19,6 @@
|
|||||||
"prisma:backfill": "ts-node prisma/backfill-fields.ts",
|
"prisma:backfill": "ts-node prisma/backfill-fields.ts",
|
||||||
"prisma:verify": "ts-node prisma/verify-backfill.ts"
|
"prisma:verify": "ts-node prisma/verify-backfill.ts"
|
||||||
},
|
},
|
||||||
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@edr/types": "workspace:*",
|
"@edr/types": "workspace:*",
|
||||||
"@golevelup/nestjs-rabbitmq": "^5.5.0",
|
"@golevelup/nestjs-rabbitmq": "^5.5.0",
|
||||||
@@ -29,6 +28,7 @@
|
|||||||
"@nestjs/core": "^11.1.19",
|
"@nestjs/core": "^11.1.19",
|
||||||
"@nestjs/event-emitter": "^2.0.4",
|
"@nestjs/event-emitter": "^2.0.4",
|
||||||
"@nestjs/jwt": "^10.2.0",
|
"@nestjs/jwt": "^10.2.0",
|
||||||
|
"@nestjs/microservices": "^11.1.24",
|
||||||
"@nestjs/passport": "^10.0.3",
|
"@nestjs/passport": "^10.0.3",
|
||||||
"@nestjs/platform-express": "^11.1.19",
|
"@nestjs/platform-express": "^11.1.19",
|
||||||
"@nestjs/schedule": "^6.1.3",
|
"@nestjs/schedule": "^6.1.3",
|
||||||
@@ -63,13 +63,13 @@
|
|||||||
"@types/passport-jwt": "^4.0.1",
|
"@types/passport-jwt": "^4.0.1",
|
||||||
"@types/qrcode": "^1.5.5",
|
"@types/qrcode": "^1.5.5",
|
||||||
"@types/supertest": "^6.0.2",
|
"@types/supertest": "^6.0.2",
|
||||||
|
"@types/uuid": "^9.0.0",
|
||||||
"jest": "^29.7.0",
|
"jest": "^29.7.0",
|
||||||
"prisma": "^6.19.3",
|
"prisma": "^6.19.3",
|
||||||
"supertest": "^7.0.0",
|
"supertest": "^7.0.0",
|
||||||
"ts-jest": "^29.1.1",
|
"ts-jest": "^29.1.1",
|
||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
"typescript": "^5.3.3",
|
"typescript": "^5.3.3"
|
||||||
"@types/uuid": "^9.0.0"
|
|
||||||
},
|
},
|
||||||
"prisma": {
|
"prisma": {
|
||||||
"schema": "prisma/schema.prisma"
|
"schema": "prisma/schema.prisma"
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
export class SendEmail {
|
||||||
|
to: string;
|
||||||
|
subject: string;
|
||||||
|
body: string;
|
||||||
|
html?: string;
|
||||||
|
templateKey?: string;
|
||||||
|
context?: Record<string, unknown>;
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
export class SendMessage {
|
||||||
|
to: string;
|
||||||
|
message: string;
|
||||||
|
from?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class BulkMessagesDto {
|
||||||
|
messages: SendMessage[];
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import { Inject, Injectable, Logger, OnApplicationBootstrap } from '@nestjs/common';
|
||||||
|
import { ClientProxy } from '@nestjs/microservices';
|
||||||
|
import { SendEmail } from './dtos/email.dto';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class EmailClientService implements OnApplicationBootstrap {
|
||||||
|
private readonly logger = new Logger(EmailClientService.name);
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@Inject('EMAIL_SERVICE')
|
||||||
|
private readonly emailServiceClient: ClientProxy,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async onApplicationBootstrap() {
|
||||||
|
this.emailServiceClient
|
||||||
|
.connect()
|
||||||
|
.then(() => this.logger.log('Connected to Email service'))
|
||||||
|
.catch((err) => this.logger.error('Error connecting to Email service', err));
|
||||||
|
}
|
||||||
|
|
||||||
|
async sendEmail(dto: SendEmail) {
|
||||||
|
this.emailServiceClient.emit('send-email', {
|
||||||
|
...dto,
|
||||||
|
appKey: 'EDR-PASSENGER-API',
|
||||||
|
});
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,16 +1,24 @@
|
|||||||
import { Controller, Get, Param, Patch, Post, Body, UseGuards } from '@nestjs/common';
|
import { Controller, Get, Param, Patch, Post, Body, UseGuards } from '@nestjs/common';
|
||||||
import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger';
|
import { ApiTags, ApiOperation, ApiBearerAuth, ApiBody } from '@nestjs/swagger';
|
||||||
import { NotificationsService } from './notifications.service';
|
import { NotificationsService } from './notifications.service';
|
||||||
import { JwtGuard } from '../../common/jwt.guard';
|
import { JwtGuard } from '../../common/jwt.guard';
|
||||||
import { IamGuard, IamRoles } from '../../common/iam-adapter';
|
import { IamGuard, IamRoles } from '../../common/iam-adapter';
|
||||||
import { TestNotificationDto } from './notifications.dto';
|
import { TestNotificationDto } from './notifications.dto';
|
||||||
|
import { EmailClientService } from './email-client.service';
|
||||||
|
import { SmsClientService } from './sms-client.service';
|
||||||
|
import { SendEmail } from './dtos/email.dto';
|
||||||
|
import { SendMessage } from './dtos/sms.dto';
|
||||||
|
|
||||||
@ApiTags('Notifications')
|
@ApiTags('Notifications')
|
||||||
@Controller('notifications')
|
@Controller('notifications')
|
||||||
@UseGuards(JwtGuard)
|
@UseGuards(JwtGuard)
|
||||||
@ApiBearerAuth('JWT-auth')
|
@ApiBearerAuth('JWT-auth')
|
||||||
export class NotificationsController {
|
export class NotificationsController {
|
||||||
constructor(private service: NotificationsService) {}
|
constructor(
|
||||||
|
private service: NotificationsService,
|
||||||
|
private emailClient: EmailClientService,
|
||||||
|
private smsClient: SmsClientService,
|
||||||
|
) {}
|
||||||
|
|
||||||
@Get(':passengerId')
|
@Get(':passengerId')
|
||||||
@ApiOperation({ summary: 'Get notifications for passenger' })
|
@ApiOperation({ summary: 'Get notifications for passenger' })
|
||||||
@@ -30,6 +38,24 @@ export class NotificationsController {
|
|||||||
return this.service.markAllRead(id);
|
return this.service.markAllRead(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Post('send/email')
|
||||||
|
@UseGuards(IamGuard)
|
||||||
|
@IamRoles('ADMIN', 'STAFF')
|
||||||
|
@ApiOperation({ summary: 'Send a direct email via the email microservice' })
|
||||||
|
@ApiBody({ type: SendEmail })
|
||||||
|
sendEmail(@Body() dto: SendEmail) {
|
||||||
|
return this.emailClient.sendEmail(dto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('send/sms')
|
||||||
|
@UseGuards(IamGuard)
|
||||||
|
@IamRoles('ADMIN', 'STAFF')
|
||||||
|
@ApiOperation({ summary: 'Send a direct SMS via the SMS microservice' })
|
||||||
|
@ApiBody({ type: SendMessage })
|
||||||
|
sendSms(@Body() dto: SendMessage) {
|
||||||
|
return this.smsClient.sendSms(dto);
|
||||||
|
}
|
||||||
|
|
||||||
@Post('test')
|
@Post('test')
|
||||||
@UseGuards(IamGuard)
|
@UseGuards(IamGuard)
|
||||||
@IamRoles('ADMIN', 'STAFF')
|
@IamRoles('ADMIN', 'STAFF')
|
||||||
|
|||||||
@@ -1,13 +1,56 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { HttpModule } from '@nestjs/axios';
|
import { HttpModule } from '@nestjs/axios';
|
||||||
|
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||||
|
import { ClientsModule, Transport } from '@nestjs/microservices';
|
||||||
import { NotificationsController } from './notifications.controller';
|
import { NotificationsController } from './notifications.controller';
|
||||||
import { NotificationsService } from './notifications.service';
|
import { NotificationsService } from './notifications.service';
|
||||||
import { EmailAdapter, SmsAdapter, PushAdapter } from './notification.adapters';
|
import { EmailAdapter, SmsAdapter, PushAdapter } from './notification.adapters';
|
||||||
|
import { EmailClientService } from './email-client.service';
|
||||||
|
import { SmsClientService } from './sms-client.service';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [HttpModule.register({ timeout: 10_000 })],
|
imports: [
|
||||||
|
HttpModule.register({ timeout: 10_000 }),
|
||||||
|
ClientsModule.registerAsync([
|
||||||
|
{
|
||||||
|
name: 'EMAIL_SERVICE',
|
||||||
|
imports: [ConfigModule],
|
||||||
|
inject: [ConfigService],
|
||||||
|
useFactory: (config: ConfigService) => ({
|
||||||
|
transport: Transport.RMQ,
|
||||||
|
options: {
|
||||||
|
urls: [config.get<string>('RABBITMQ_URL') ?? 'amqp://localhost:5672'],
|
||||||
|
queue: config.get<string>('EMAIL_QUEUE') ?? 'email_queue',
|
||||||
|
queueOptions: { durable: true },
|
||||||
|
noAck: true,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'SMS_SERVICE',
|
||||||
|
imports: [ConfigModule],
|
||||||
|
inject: [ConfigService],
|
||||||
|
useFactory: (config: ConfigService) => ({
|
||||||
|
transport: Transport.RMQ,
|
||||||
|
options: {
|
||||||
|
urls: [config.get<string>('RABBITMQ_URL') ?? 'amqp://localhost:5672'],
|
||||||
|
queue: config.get<string>('SMS_QUEUE') ?? 'sms_queue',
|
||||||
|
queueOptions: { durable: true },
|
||||||
|
noAck: true,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
],
|
||||||
controllers: [NotificationsController],
|
controllers: [NotificationsController],
|
||||||
providers: [NotificationsService, EmailAdapter, SmsAdapter, PushAdapter],
|
providers: [
|
||||||
exports: [NotificationsService],
|
NotificationsService,
|
||||||
|
EmailAdapter,
|
||||||
|
SmsAdapter,
|
||||||
|
PushAdapter,
|
||||||
|
EmailClientService,
|
||||||
|
SmsClientService,
|
||||||
|
],
|
||||||
|
exports: [NotificationsService, EmailClientService, SmsClientService],
|
||||||
})
|
})
|
||||||
export class NotificationsModule {}
|
export class NotificationsModule {}
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import { Injectable, Logger, NotFoundException } from '@nestjs/common';
|
import { Injectable, Logger } from '@nestjs/common';
|
||||||
import { OnEvent } from '@nestjs/event-emitter';
|
import { OnEvent } from '@nestjs/event-emitter';
|
||||||
import { PrismaService } from '../../common/prisma.service';
|
import { PrismaService } from '../../common/prisma.service';
|
||||||
import { SendNotificationDto, NotificationCategoryEnum } from './notifications.dto';
|
import { SendNotificationDto, NotificationCategoryEnum } from './notifications.dto';
|
||||||
import { EmailAdapter, SmsAdapter, PushAdapter, NotificationChannel } from './notification.adapters';
|
import { PushAdapter, NotificationChannel } from './notification.adapters';
|
||||||
|
import { EmailClientService } from './email-client.service';
|
||||||
|
import { SmsClientService } from './sms-client.service';
|
||||||
|
|
||||||
export type NotificationChannelType = 'EMAIL' | 'SMS' | 'PUSH' | 'IN_APP';
|
export type NotificationChannelType = 'EMAIL' | 'SMS' | 'PUSH' | 'IN_APP';
|
||||||
|
|
||||||
@@ -13,13 +15,13 @@ export class NotificationsService {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private prisma: PrismaService,
|
private prisma: PrismaService,
|
||||||
private emailAdapter: EmailAdapter,
|
private emailClient: EmailClientService,
|
||||||
private smsAdapter: SmsAdapter,
|
private smsClient: SmsClientService,
|
||||||
private pushAdapter: PushAdapter,
|
private pushAdapter: PushAdapter,
|
||||||
) {
|
) {
|
||||||
this.channels = new Map<NotificationChannelType, NotificationChannel>([
|
this.channels = new Map<NotificationChannelType, NotificationChannel>([
|
||||||
['EMAIL', this.emailAdapter as NotificationChannel],
|
['EMAIL', { send: (to, subject, body) => this.emailClient.sendEmail({ to, subject, body }).then(() => true) }],
|
||||||
['SMS', this.smsAdapter as NotificationChannel],
|
['SMS', { send: (to, _subject, body) => this.smsClient.sendSms({ to, message: body }).then(() => true) }],
|
||||||
['PUSH', this.pushAdapter as NotificationChannel],
|
['PUSH', this.pushAdapter as NotificationChannel],
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@@ -102,11 +104,11 @@ export class NotificationsService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (passenger?.user) {
|
if (passenger?.user) {
|
||||||
await this.emailAdapter.send(
|
await this.emailClient.sendEmail({
|
||||||
passenger.user.email,
|
to: passenger.user.email,
|
||||||
this.sanitize(dto.title),
|
subject: this.sanitize(dto.title),
|
||||||
this.sanitize(dto.body),
|
body: this.sanitize(dto.body),
|
||||||
);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return notification;
|
return notification;
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import { Inject, Injectable, Logger, OnApplicationBootstrap } from '@nestjs/common';
|
||||||
|
import { ClientProxy } from '@nestjs/microservices';
|
||||||
|
import { BulkMessagesDto, SendMessage } from './dtos/sms.dto';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class SmsClientService implements OnApplicationBootstrap {
|
||||||
|
private readonly logger = new Logger(SmsClientService.name);
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@Inject('SMS_SERVICE')
|
||||||
|
private readonly smsClient: ClientProxy,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async onApplicationBootstrap() {
|
||||||
|
this.smsClient
|
||||||
|
.connect()
|
||||||
|
.then(() => this.logger.log('Connected to SMS service'))
|
||||||
|
.catch((err) => this.logger.error('Error connecting to SMS service', err));
|
||||||
|
}
|
||||||
|
|
||||||
|
async sendSms(dto: SendMessage) {
|
||||||
|
this.smsClient.emit('send-sms', {
|
||||||
|
...dto,
|
||||||
|
appKey: 'EDR-PASSENGER-API',
|
||||||
|
});
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
async sendBulkMessages(dto: BulkMessagesDto) {
|
||||||
|
this.smsClient.emit('ozeking-bulk-sms', {
|
||||||
|
...dto,
|
||||||
|
appKey: 'EDR-PASSENGER-API',
|
||||||
|
});
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user