mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-08 00:45:41 +00:00
Merge pull request #289 from Tria-plc/freight/fix/type-errors
chore: setup sms rabbit mq
This commit is contained in:
@@ -52,3 +52,10 @@ MINIO_SECRET_KEY=
|
|||||||
# Redis
|
# Redis
|
||||||
REDIS_HOST=localhost
|
REDIS_HOST=localhost
|
||||||
REDIS_PORT=6379
|
REDIS_PORT=6379
|
||||||
|
|
||||||
|
# --- Notification broker (RabbitMQ) ---------------------------------------------
|
||||||
|
# SMS OTP / notifications are queued to RabbitMQ (consumed by the shared SMS service).
|
||||||
|
# Set RABBITMQ_ENABLED=false to skip the broker entirely (dev without a local broker).
|
||||||
|
RABBITMQ_ENABLED=false
|
||||||
|
RABBITMQ_URL=amqp://localhost:5672
|
||||||
|
SMS_QUEUE=sms_queue
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||||
|
import { IsArray, IsNotEmpty, IsOptional, IsString, ValidateNested } from 'class-validator';
|
||||||
|
import { Type } from 'class-transformer';
|
||||||
|
|
||||||
|
export class SendMessage {
|
||||||
|
@ApiProperty()
|
||||||
|
@IsNotEmpty()
|
||||||
|
@IsString()
|
||||||
|
to!: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@IsNotEmpty()
|
||||||
|
@IsString()
|
||||||
|
message!: string;
|
||||||
|
|
||||||
|
@ApiPropertyOptional()
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
from?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SingleMessageDto {
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'Recipient phone number',
|
||||||
|
example: '+1234567890',
|
||||||
|
})
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
to!: string;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'Message content',
|
||||||
|
example: 'Test Single SMS from',
|
||||||
|
})
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
message!: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class BulkMessagesDto {
|
||||||
|
@ApiProperty({ type: [SendMessage] })
|
||||||
|
@IsArray()
|
||||||
|
@ValidateNested({ each: true })
|
||||||
|
@Type(() => SendMessage)
|
||||||
|
messages!: SendMessage[];
|
||||||
|
}
|
||||||
@@ -1,14 +1,29 @@
|
|||||||
import { Module } from "@nestjs/common";
|
import { Module } from "@nestjs/common";
|
||||||
import { ConfigModule } from "@nestjs/config";
|
import { ConfigModule } from "@nestjs/config";
|
||||||
|
import { ClientsModule, Transport } from "@nestjs/microservices";
|
||||||
|
|
||||||
import { NotificationsService } from "./notifications.service";
|
import { NotificationsService } from "./notifications.service";
|
||||||
|
import { SmsClientService } from "./sms-client.service";
|
||||||
import { EmailNotificationStrategy } from "./strategies/notification.email.strategy";
|
import { EmailNotificationStrategy } from "./strategies/notification.email.strategy";
|
||||||
import { SmsNotificationStrategy } from "./strategies/notification.sms.strategy";
|
import { SmsNotificationStrategy } from "./strategies/notification.sms.strategy";
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [ConfigModule],
|
imports: [
|
||||||
|
ConfigModule,
|
||||||
|
ClientsModule.register([
|
||||||
|
{
|
||||||
|
name: "SMS_SERVICE",
|
||||||
|
transport: Transport.RMQ,
|
||||||
|
options: {
|
||||||
|
urls: [process.env.RABBITMQ_URL as string],
|
||||||
|
queue: process.env.SMS_QUEUE ?? "sms_queue",
|
||||||
|
queueOptions: { durable: true },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
],
|
||||||
controllers: [],
|
controllers: [],
|
||||||
providers: [EmailNotificationStrategy, SmsNotificationStrategy, NotificationsService],
|
providers: [EmailNotificationStrategy, SmsNotificationStrategy, NotificationsService, SmsClientService],
|
||||||
exports: [NotificationsService],
|
exports: [NotificationsService, SmsClientService],
|
||||||
})
|
})
|
||||||
export class NotificationsModule {}
|
export class NotificationsModule {}
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
import {
|
||||||
|
Inject,
|
||||||
|
Injectable,
|
||||||
|
Logger,
|
||||||
|
OnApplicationBootstrap,
|
||||||
|
} from "@nestjs/common";
|
||||||
|
import { ClientProxy } from "@nestjs/microservices";
|
||||||
|
import { BulkMessagesDto, SingleMessageDto } from "./dtos/sms.dto";
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class SmsClientService implements OnApplicationBootstrap {
|
||||||
|
private readonly logger = new Logger(SmsClientService.name);
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@Inject("SMS_SERVICE")
|
||||||
|
private smsClient: ClientProxy,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
private readonly enabled = process.env.RABBITMQ_ENABLED !== "false";
|
||||||
|
|
||||||
|
async onApplicationBootstrap() {
|
||||||
|
if (!this.enabled) return;
|
||||||
|
this.smsClient
|
||||||
|
.connect()
|
||||||
|
.then(() => {
|
||||||
|
this.logger.log("connected to SMS service");
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error("Error happened at SMS service", err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async sendSms(dto: SingleMessageDto): Promise<{ queued: boolean }> {
|
||||||
|
if (!this.enabled) {
|
||||||
|
this.logger.warn(`RABBITMQ disabled — skipped SMS`);
|
||||||
|
return { queued: false };
|
||||||
|
}
|
||||||
|
this.smsClient.emit("send-sms", {
|
||||||
|
to: dto.to,
|
||||||
|
text: dto.message,
|
||||||
|
appKey: "IFHCRS-LICENSE-MANAGEMENT",
|
||||||
|
});
|
||||||
|
// Fire-and-forget enqueue: confirms hand-off to RabbitMQ, NOT delivery.
|
||||||
|
this.logger.log(
|
||||||
|
`SMS queued to RabbitMQ [${process.env.SMS_QUEUE ?? "sms_queue"}] pattern='send-sms'`,
|
||||||
|
);
|
||||||
|
// Recipient + content are PII — debug only.
|
||||||
|
this.logger.debug(`SMS payload to=${dto.to} text="${dto.message}"`);
|
||||||
|
return { queued: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
async sendBulkMessages(dto: BulkMessagesDto): Promise<{ queued: boolean }> {
|
||||||
|
if (!this.enabled) {
|
||||||
|
this.logger.warn(`RABBITMQ disabled — skipped BULK SMS (${dto.messages?.length ?? 0} messages)`);
|
||||||
|
return { queued: false };
|
||||||
|
}
|
||||||
|
const messages = (dto.messages ?? []).map((m) => ({ to: m.to, text: m.message, from: m.from }));
|
||||||
|
this.smsClient.emit("ozeking-bulk-sms", {
|
||||||
|
messages,
|
||||||
|
appKey: "IFHCRS-LICENSE-MANAGEMENT",
|
||||||
|
});
|
||||||
|
this.logger.log(
|
||||||
|
`BULK SMS queued to RabbitMQ [${process.env.SMS_QUEUE ?? "sms_queue"}] pattern='ozeking-bulk-sms' count=${messages.length}`,
|
||||||
|
);
|
||||||
|
this.logger.debug(`BULK SMS payload messages=${JSON.stringify(messages)}`);
|
||||||
|
return { queued: true };
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,11 +12,14 @@ import { OtpService } from "./otp.service";
|
|||||||
|
|
||||||
import { OtpRepository } from "./otp.repository";
|
import { OtpRepository } from "./otp.repository";
|
||||||
|
|
||||||
|
import { NotificationsModule } from "../notifications/notifications.module";
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
TypeOrmModule.forFeature([
|
TypeOrmModule.forFeature([
|
||||||
OtpVerification,
|
OtpVerification,
|
||||||
]),
|
]),
|
||||||
|
NotificationsModule,
|
||||||
],
|
],
|
||||||
|
|
||||||
controllers: [OtpController],
|
controllers: [OtpController],
|
||||||
|
|||||||
@@ -5,14 +5,15 @@ import {
|
|||||||
Injectable,
|
Injectable,
|
||||||
} from "@nestjs/common";
|
} from "@nestjs/common";
|
||||||
|
|
||||||
import axios from "axios";
|
|
||||||
|
|
||||||
import { OtpRepository } from "./otp.repository";
|
import { OtpRepository } from "./otp.repository";
|
||||||
|
|
||||||
|
import { SmsClientService } from "../notifications/sms-client.service";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class OtpService {
|
export class OtpService {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly otpRepository: OtpRepository
|
private readonly otpRepository: OtpRepository,
|
||||||
|
private readonly smsClient: SmsClientService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -56,33 +57,11 @@ export class OtpService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// send sms
|
// send sms (queued to RabbitMQ via the shared SMS service)
|
||||||
await axios.post(
|
await this.smsClient.sendSms({
|
||||||
"https://notification-dev.license.aafda.gov.et/api/sms-services/ozeking/sms",
|
to: phone,
|
||||||
{
|
message: `Your verification code is ${otp}`,
|
||||||
to: phone,
|
});
|
||||||
|
|
||||||
sourceId: "EDR",
|
|
||||||
|
|
||||||
sourceName:
|
|
||||||
"EDR Freight",
|
|
||||||
|
|
||||||
appKey:
|
|
||||||
"YOUR_APP_KEY",
|
|
||||||
|
|
||||||
text: `Your verification code is ${otp}`,
|
|
||||||
|
|
||||||
callbackUrl: "",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
accept: "*/*",
|
|
||||||
|
|
||||||
"Content-Type":
|
|
||||||
"application/json",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
|
|||||||
Reference in New Issue
Block a user