Merge pull request #289 from Tria-plc/freight/fix/type-errors

chore: setup sms rabbit mq
This commit is contained in:
Nathnael Wondisha
2026-06-25 10:43:12 +03:00
committed by GitHub
6 changed files with 151 additions and 33 deletions

View File

@@ -52,3 +52,10 @@ MINIO_SECRET_KEY=
# Redis
REDIS_HOST=localhost
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

View File

@@ -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[];
}

View File

@@ -1,14 +1,29 @@
import { Module } from "@nestjs/common";
import { ConfigModule } from "@nestjs/config";
import { ClientsModule, Transport } from "@nestjs/microservices";
import { NotificationsService } from "./notifications.service";
import { SmsClientService } from "./sms-client.service";
import { EmailNotificationStrategy } from "./strategies/notification.email.strategy";
import { SmsNotificationStrategy } from "./strategies/notification.sms.strategy";
@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: [],
providers: [EmailNotificationStrategy, SmsNotificationStrategy, NotificationsService],
exports: [NotificationsService],
providers: [EmailNotificationStrategy, SmsNotificationStrategy, NotificationsService, SmsClientService],
exports: [NotificationsService, SmsClientService],
})
export class NotificationsModule {}

View File

@@ -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 };
}
}

View File

@@ -12,11 +12,14 @@ import { OtpService } from "./otp.service";
import { OtpRepository } from "./otp.repository";
import { NotificationsModule } from "../notifications/notifications.module";
@Module({
imports: [
TypeOrmModule.forFeature([
OtpVerification,
]),
NotificationsModule,
],
controllers: [OtpController],

View File

@@ -5,14 +5,15 @@ import {
Injectable,
} from "@nestjs/common";
import axios from "axios";
import { OtpRepository } from "./otp.repository";
import { SmsClientService } from "../notifications/sms-client.service";
@Injectable()
export class OtpService {
constructor(
private readonly otpRepository: OtpRepository
private readonly otpRepository: OtpRepository,
private readonly smsClient: SmsClientService
) {}
// ---------------------------------------------------------------------------
@@ -56,33 +57,11 @@ export class OtpService {
);
}
// send sms
await axios.post(
"https://notification-dev.license.aafda.gov.et/api/sms-services/ozeking/sms",
{
to: phone,
sourceId: "EDR",
sourceName:
"EDR Freight",
appKey:
"YOUR_APP_KEY",
text: `Your verification code is ${otp}`,
callbackUrl: "",
},
{
headers: {
accept: "*/*",
"Content-Type":
"application/json",
},
}
);
// send sms (queued to RabbitMQ via the shared SMS service)
await this.smsClient.sendSms({
to: phone,
message: `Your verification code is ${otp}`,
});
return {
success: true,