feat: added smsService and sendOtp
This commit is contained in:
@@ -20,12 +20,14 @@
|
||||
"test:e2e": "jest --config ./test/jest-e2e.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nestjs/axios": "^4.0.1",
|
||||
"@nestjs/common": "^11.1.28",
|
||||
"@nestjs/config": "^4.0.4",
|
||||
"@nestjs/core": "^11.1.28",
|
||||
"@nestjs/platform-express": "^11.1.28",
|
||||
"@nestjs/swagger": "^11.4.6",
|
||||
"@nestjs/typeorm": "^11.0.3",
|
||||
"axios": "^1.18.1",
|
||||
"bcrypt": "^6.0.0",
|
||||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.15.1",
|
||||
|
||||
Generated
+70
@@ -8,6 +8,9 @@ importers:
|
||||
|
||||
.:
|
||||
dependencies:
|
||||
'@nestjs/axios':
|
||||
specifier: ^4.0.1
|
||||
version: 4.0.1(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(axios@1.18.1)(rxjs@7.8.2)
|
||||
'@nestjs/common':
|
||||
specifier: ^11.1.28
|
||||
version: 11.1.28(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2)
|
||||
@@ -26,6 +29,9 @@ importers:
|
||||
'@nestjs/typeorm':
|
||||
specifier: ^11.0.3
|
||||
version: 11.0.3(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.28)(reflect-metadata@0.2.2)(rxjs@7.8.2)(typeorm@1.1.0(pg@8.22.0)(ts-node@10.9.2(@types/node@22.20.1)(typescript@5.9.3)))
|
||||
axios:
|
||||
specifier: ^1.18.1
|
||||
version: 1.18.1
|
||||
bcrypt:
|
||||
specifier: ^6.0.0
|
||||
version: 6.0.0
|
||||
@@ -692,6 +698,13 @@ packages:
|
||||
'@emnapi/core': ^1.7.1
|
||||
'@emnapi/runtime': ^1.7.1
|
||||
|
||||
'@nestjs/axios@4.0.1':
|
||||
resolution: {integrity: sha512-68pFJgu+/AZbWkGu65Z3r55bTsCPlgyKaV4BSG8yUAD72q1PPuyVRgUwFv6BxdnibTUHlyxm06FmYWNC+bjN7A==}
|
||||
peerDependencies:
|
||||
'@nestjs/common': ^10.0.0 || ^11.0.0
|
||||
axios: ^1.3.1
|
||||
rxjs: ^7.0.0
|
||||
|
||||
'@nestjs/cli@11.0.24':
|
||||
resolution: {integrity: sha512-aIHxQLSYtXShifA3zwWIeznEsZnNa3Iz2QRykFj+sl9IcbERBHr5nH87FRgywM+He3NxoF5WazHfR8FsmVeWxw==}
|
||||
engines: {node: '>= 20.11'}
|
||||
@@ -1214,6 +1227,10 @@ packages:
|
||||
engines: {node: '>=0.4.0'}
|
||||
hasBin: true
|
||||
|
||||
agent-base@6.0.2:
|
||||
resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==}
|
||||
engines: {node: '>= 6.0.0'}
|
||||
|
||||
ajv-formats@2.1.1:
|
||||
resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==}
|
||||
peerDependencies:
|
||||
@@ -1310,6 +1327,9 @@ packages:
|
||||
asynckit@0.4.0:
|
||||
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
|
||||
|
||||
axios@1.18.1:
|
||||
resolution: {integrity: sha512-3nTvFlvpn9Zu/RkHUqtc7/+al4UpRW5az71ap5zccp6e8RAYEzhMTecX8Dz1wWDYrPpUoB1HAQEGEAEvUr7S9g==}
|
||||
|
||||
babel-jest@30.4.1:
|
||||
resolution: {integrity: sha512-fATAbM8piYxkiXQp3RBXmZHxZVNJZAVXXfyeyCN2Tida3+qJ8ea9UxhiJ2y4fLO90ZImKt6k9FlcH2+rLkJGhw==}
|
||||
engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
|
||||
@@ -1857,6 +1877,15 @@ packages:
|
||||
flatted@3.4.2:
|
||||
resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==}
|
||||
|
||||
follow-redirects@1.16.0:
|
||||
resolution: {integrity: sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==}
|
||||
engines: {node: '>=4.0'}
|
||||
peerDependencies:
|
||||
debug: '*'
|
||||
peerDependenciesMeta:
|
||||
debug:
|
||||
optional: true
|
||||
|
||||
foreground-child@3.3.1:
|
||||
resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==}
|
||||
engines: {node: '>=14'}
|
||||
@@ -1993,6 +2022,10 @@ packages:
|
||||
resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
https-proxy-agent@5.0.1:
|
||||
resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==}
|
||||
engines: {node: '>= 6'}
|
||||
|
||||
human-signals@2.1.0:
|
||||
resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==}
|
||||
engines: {node: '>=10.17.0'}
|
||||
@@ -2663,6 +2696,10 @@ packages:
|
||||
resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==}
|
||||
engines: {node: '>= 0.10'}
|
||||
|
||||
proxy-from-env@2.1.0:
|
||||
resolution: {integrity: sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
punycode@2.3.1:
|
||||
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
|
||||
engines: {node: '>=6'}
|
||||
@@ -4029,6 +4066,12 @@ snapshots:
|
||||
'@tybys/wasm-util': 0.10.3
|
||||
optional: true
|
||||
|
||||
'@nestjs/axios@4.0.1(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(axios@1.18.1)(rxjs@7.8.2)':
|
||||
dependencies:
|
||||
'@nestjs/common': 11.1.28(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2)
|
||||
axios: 1.18.1
|
||||
rxjs: 7.8.2
|
||||
|
||||
'@nestjs/cli@11.0.24(@types/node@22.20.1)(prettier@3.9.5)':
|
||||
dependencies:
|
||||
'@angular-devkit/core': 19.2.27(chokidar@4.0.3)
|
||||
@@ -4597,6 +4640,12 @@ snapshots:
|
||||
|
||||
acorn@8.17.0: {}
|
||||
|
||||
agent-base@6.0.2:
|
||||
dependencies:
|
||||
debug: 4.4.3
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
ajv-formats@2.1.1(ajv@8.20.0):
|
||||
optionalDependencies:
|
||||
ajv: 8.20.0
|
||||
@@ -4678,6 +4727,16 @@ snapshots:
|
||||
|
||||
asynckit@0.4.0: {}
|
||||
|
||||
axios@1.18.1:
|
||||
dependencies:
|
||||
follow-redirects: 1.16.0
|
||||
form-data: 4.0.6
|
||||
https-proxy-agent: 5.0.1
|
||||
proxy-from-env: 2.1.0
|
||||
transitivePeerDependencies:
|
||||
- debug
|
||||
- supports-color
|
||||
|
||||
babel-jest@30.4.1(@babel/core@7.29.7):
|
||||
dependencies:
|
||||
'@babel/core': 7.29.7
|
||||
@@ -5253,6 +5312,8 @@ snapshots:
|
||||
|
||||
flatted@3.4.2: {}
|
||||
|
||||
follow-redirects@1.16.0: {}
|
||||
|
||||
foreground-child@3.3.1:
|
||||
dependencies:
|
||||
cross-spawn: 7.0.6
|
||||
@@ -5405,6 +5466,13 @@ snapshots:
|
||||
statuses: 2.0.2
|
||||
toidentifier: 1.0.1
|
||||
|
||||
https-proxy-agent@5.0.1:
|
||||
dependencies:
|
||||
agent-base: 6.0.2
|
||||
debug: 4.4.3
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
human-signals@2.1.0: {}
|
||||
|
||||
iconv-lite@0.7.3:
|
||||
@@ -6182,6 +6250,8 @@ snapshots:
|
||||
forwarded: 0.2.0
|
||||
ipaddr.js: 1.9.1
|
||||
|
||||
proxy-from-env@2.1.0: {}
|
||||
|
||||
punycode@2.3.1: {}
|
||||
|
||||
pure-rand@7.0.1: {}
|
||||
|
||||
+5
-1
@@ -5,6 +5,8 @@ import Joi from 'joi';
|
||||
import { validationSchema } from './config/config.validation';
|
||||
import { UsersModule } from './users/users.module';
|
||||
import { databaseConfig } from './config/typeorm.config';
|
||||
import { HttpModule } from '@nestjs/axios';
|
||||
import { AuthModule } from './auth/auth.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@@ -13,7 +15,9 @@ import { databaseConfig } from './config/typeorm.config';
|
||||
validationSchema
|
||||
}),
|
||||
TypeOrmModule.forRootAsync(databaseConfig()),
|
||||
UsersModule
|
||||
HttpModule.register({global: true, timeout: 10000, headers: {"Content-Type": "application/json"}}),
|
||||
UsersModule,
|
||||
AuthModule
|
||||
],
|
||||
})
|
||||
export class AppModule {}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { Matches } from 'class-validator';
|
||||
|
||||
export class SendOtpDto {
|
||||
@ApiProperty({
|
||||
example: '09123456789',
|
||||
})
|
||||
@Matches(/^09\d{9}$/, {
|
||||
message: 'Phone number must be a valid Iranian mobile number.',
|
||||
})
|
||||
phoneNumber!: string;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import { Body, Controller, Post } from "@nestjs/common";
|
||||
import { ApiOperation, ApiTags } from "@nestjs/swagger";
|
||||
import { AuthService } from "./auth.service";
|
||||
import { SendOtpDto } from "./DTO/send-otp.dto";
|
||||
|
||||
@ApiTags('Authentication')
|
||||
@Controller('auth')
|
||||
export class AuthController {
|
||||
constructor(
|
||||
private readonly authService: AuthService,
|
||||
) {}
|
||||
|
||||
@Post('send-otp')
|
||||
@ApiOperation({
|
||||
summary: 'Send OTP to a phone number',
|
||||
})
|
||||
async sendOtp(
|
||||
@Body() dto: SendOtpDto,
|
||||
) {
|
||||
return this.authService.sendOTP(dto.phoneNumber);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { AuthController } from './auth.controller';
|
||||
import { AuthService } from './auth.service';
|
||||
import { UtilsModule } from 'src/utils/utils.module';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { User } from 'src/users/entities/user.entity';
|
||||
import { OTP } from './entities/otp.entity';
|
||||
import { OtpRepository } from './repositories/otp.repository';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([User, OTP]), UtilsModule],
|
||||
controllers: [AuthController],
|
||||
providers: [AuthService, OtpRepository],
|
||||
})
|
||||
export class AuthModule {}
|
||||
@@ -0,0 +1,62 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import type { ISmsConfigs } from 'src/config/sms.config';
|
||||
import { SMS_CONFIG } from 'src/utils/constants';
|
||||
import { SmsService } from 'src/utils/providers/sms.service';
|
||||
import { OtpRepository } from './repositories/otp.repository';
|
||||
import { OtpStatus } from './enums/otp-status.enum';
|
||||
|
||||
@Injectable()
|
||||
export class AuthService {
|
||||
constructor(
|
||||
@Inject(SMS_CONFIG) private smsConfig: ISmsConfigs,
|
||||
private readonly smsService: SmsService,
|
||||
private readonly configService: ConfigService,
|
||||
private readonly OtpRepository: OtpRepository,
|
||||
) {}
|
||||
|
||||
async sendOTP(phone: string) {
|
||||
const otp = this.generateOtp();
|
||||
|
||||
try {
|
||||
await this.smsService.sendSms({
|
||||
Mobile: phone,
|
||||
TemplateId: this.smsConfig.SMS_PATTERN_OTP,
|
||||
Parameters: [
|
||||
{
|
||||
name: 'code',
|
||||
value: otp,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
await this.OtpRepository.update(
|
||||
{ phoneNumber: phone, status: OtpStatus.PENDING },
|
||||
{ status: OtpStatus.EXPIRED },
|
||||
);
|
||||
|
||||
await this.OtpRepository.save(
|
||||
this.OtpRepository.create({
|
||||
phoneNumber: phone,
|
||||
code: otp,
|
||||
expirationDate: Date.now() + 2 * 60 * 1000,
|
||||
status: OtpStatus.PENDING,
|
||||
numberOfTries: 0
|
||||
})
|
||||
);
|
||||
|
||||
return {otp};
|
||||
|
||||
} catch (error) {
|
||||
if (this.configService.get('NODE_ENV') !== 'production') {
|
||||
return { otp };
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
private generateOtp(): string {
|
||||
return Math.floor(100000 + Math.random() * 900000).toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { Column, Entity } from 'typeorm';
|
||||
import { BaseEntity } from 'src/common/entities/base.entity';
|
||||
import { OtpStatus } from '../enums/otp-status.enum';
|
||||
|
||||
@Entity('otps')
|
||||
export class OTP extends BaseEntity {
|
||||
@Column({ type: 'varchar', length: 6 })
|
||||
code: string;
|
||||
|
||||
@Column({ type: 'timestamptz' })
|
||||
expirationDate: Date;
|
||||
|
||||
@Column({type: 'enum', enum: OtpStatus, default: OtpStatus.PENDING})
|
||||
status: OtpStatus;
|
||||
|
||||
@Column({ type: 'smallint', default: 0 })
|
||||
numberOfTries: number;
|
||||
|
||||
@Column({
|
||||
length: 11,
|
||||
})
|
||||
phoneNumber: string;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export enum OtpStatus {
|
||||
PENDING = 'PENDING',
|
||||
VERIFIED = 'VERIFIED',
|
||||
EXPIRED = 'EXPIRED'
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { Repository } from 'typeorm';
|
||||
import { OTP } from '../entities/otp.entity';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
export class OtpRepository extends Repository<OTP> {
|
||||
constructor(@InjectRepository(OTP) otpRepository: Repository<OTP>) {
|
||||
super(
|
||||
otpRepository.target,
|
||||
otpRepository.manager,
|
||||
otpRepository.queryRunner,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -3,9 +3,17 @@ import * as Joi from 'joi';
|
||||
export const validationSchema = Joi.object({
|
||||
APP_PORT: Joi.number().required(),
|
||||
|
||||
// DB Config
|
||||
DB_HOST: Joi.string().required(),
|
||||
DB_PORT: Joi.number().required(),
|
||||
DB_USER: Joi.string().required(),
|
||||
DB_PASSWORD: Joi.string().required(),
|
||||
DB_NAME: Joi.string().required(),
|
||||
|
||||
// SMS Config
|
||||
SMS_API_URL: Joi.string().required(),
|
||||
SMS_API_KEY: Joi.string().required(),
|
||||
SMS_PATTERN_OTP: Joi.string().required(),
|
||||
|
||||
NODE_ENV: Joi.string().required()
|
||||
});
|
||||
@@ -0,0 +1,22 @@
|
||||
import { FactoryProvider } from "@nestjs/common";
|
||||
import { ConfigService } from "@nestjs/config";
|
||||
|
||||
import { SMS_CONFIG } from "src/utils/constants";
|
||||
|
||||
export const smsConfigsProvider: FactoryProvider<ISmsConfigs> = {
|
||||
provide: SMS_CONFIG,
|
||||
inject: [ConfigService],
|
||||
useFactory(configService: ConfigService) {
|
||||
return {
|
||||
API_URL: configService.getOrThrow<string>("SMS_API_URL"),
|
||||
API_KEY: configService.getOrThrow<string>("SMS_API_KEY"),
|
||||
SMS_PATTERN_OTP: configService.getOrThrow<string>("SMS_PATTERN_OTP")
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
export interface ISmsConfigs {
|
||||
API_URL: string;
|
||||
API_KEY: string;
|
||||
SMS_PATTERN_OTP: string;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Repository } from "typeorm";
|
||||
import { User } from "../entities/user.entity";
|
||||
import { User } from "./entities/user.entity";
|
||||
import { InjectRepository } from "@nestjs/typeorm";
|
||||
import { Injectable } from "@nestjs/common";
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Body, Controller, Get, Post } from '@nestjs/common';
|
||||
import { ApiOperation, ApiResponse } from '@nestjs/swagger';
|
||||
import { CreateUserDto } from '../DTO/create-user.dto';
|
||||
import { User } from '../entities/user.entity';
|
||||
import { UsersService } from '../providers/users.service';
|
||||
import { CreateUserDto } from './DTO/create-user.dto';
|
||||
import { User } from './entities/user.entity';
|
||||
import { UsersService } from './users.service';
|
||||
|
||||
@Controller('users')
|
||||
export class UsersController {
|
||||
@@ -16,6 +16,6 @@ export class UsersController {
|
||||
@ApiResponse({status: 201, description: 'User created successfully!'})
|
||||
@ApiResponse({status: 400, description: 'Bad Request from User!'})
|
||||
signupUser(@Body() body: CreateUserDto): Promise<User> {
|
||||
return this.userService.signupUser(body);
|
||||
return this.userService.createUser(body);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { UsersController } from './controllers/users.controller';
|
||||
import { UsersService } from './providers/users.service';
|
||||
import { UserRepository } from './repositories/user.repository';
|
||||
import { UsersController } from './users.controller';
|
||||
import { UsersService } from './users.service';
|
||||
import { UserRepository } from './user.repository';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { User } from './entities/user.entity';
|
||||
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import { BadRequestException, Injectable } from '@nestjs/common';
|
||||
import { UserRepository } from '../repositories/user.repository';
|
||||
import { User } from '../entities/user.entity';
|
||||
import { UserRepository } from './user.repository';
|
||||
import { User } from './entities/user.entity';
|
||||
import * as bcrypt from 'bcrypt';
|
||||
import { CreateUserDto } from '../DTO/create-user.dto';
|
||||
import { CreateUserDto } from './DTO/create-user.dto';
|
||||
import { UserMessages } from 'src/common/enums/messages.enum';
|
||||
|
||||
@Injectable()
|
||||
export class UsersService {
|
||||
constructor(private readonly userRepository: UserRepository) {}
|
||||
|
||||
async signupUser(userDto: CreateUserDto): Promise<User> {
|
||||
async createUser(userDto: CreateUserDto): Promise<User> {
|
||||
const existingUser = await this.userRepository.findOne({
|
||||
where: [
|
||||
{
|
||||
@@ -0,0 +1 @@
|
||||
export const SMS_CONFIG = "SMS_CONFIG";
|
||||
@@ -0,0 +1,24 @@
|
||||
export interface SmsParameter {
|
||||
name: SmsTemplateParameter;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface SmsRequest {
|
||||
Mobile: string;
|
||||
TemplateId: string;
|
||||
Parameters: SmsParameter[];
|
||||
}
|
||||
|
||||
export interface SmsResponse {
|
||||
status: number;
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface SmsSendResponse extends SmsResponse {
|
||||
data: {
|
||||
MessageId: number;
|
||||
Cost: number;
|
||||
};
|
||||
}
|
||||
|
||||
export type SmsTemplateParameter = string;
|
||||
@@ -0,0 +1,62 @@
|
||||
import {
|
||||
BadGatewayException,
|
||||
Inject,
|
||||
Injectable,
|
||||
InternalServerErrorException,
|
||||
Logger,
|
||||
} from '@nestjs/common';
|
||||
import { SMS_CONFIG } from '../constants';
|
||||
import type { ISmsConfigs } from 'src/config/sms.config';
|
||||
import { catchError, firstValueFrom } from 'rxjs';
|
||||
import { HttpService } from '@nestjs/axios';
|
||||
import { AxiosError } from 'axios';
|
||||
import { SmsRequest, SmsSendResponse } from '../interfaces/ISms';
|
||||
|
||||
@Injectable()
|
||||
export class SmsService {
|
||||
private readonly logger = new Logger(SmsService.name);
|
||||
|
||||
constructor(
|
||||
@Inject(SMS_CONFIG) private smsConfigs: ISmsConfigs,
|
||||
private readonly httpService: HttpService,
|
||||
) {}
|
||||
|
||||
async sendSms(body: SmsRequest): Promise<SmsSendResponse> {
|
||||
try {
|
||||
const { data } = await firstValueFrom(
|
||||
this.httpService
|
||||
.post<SmsSendResponse>(
|
||||
`${this.smsConfigs.API_URL}/send/verify`,
|
||||
body,
|
||||
{
|
||||
headers: {
|
||||
'X-API-KEY': this.smsConfigs.API_KEY,
|
||||
},
|
||||
timeout: 5000,
|
||||
},
|
||||
)
|
||||
.pipe(
|
||||
catchError((error: AxiosError) => {
|
||||
this.logger.error(
|
||||
'Error while calling SMS provider.',
|
||||
error.stack,
|
||||
);
|
||||
|
||||
throw new InternalServerErrorException(
|
||||
'Failed to communicate with SMS provider.',
|
||||
);
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
return data;
|
||||
} catch (error) {
|
||||
this.logger.error(
|
||||
'Failed to send SMS.',
|
||||
error instanceof Error ? error.stack : undefined,
|
||||
);
|
||||
|
||||
throw new InternalServerErrorException('Failed to send SMS.');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { SmsService } from './providers/sms.service';
|
||||
import { smsConfigsProvider } from 'src/config/sms.config';
|
||||
import { SMS_CONFIG } from './constants';
|
||||
|
||||
@Module({
|
||||
providers: [SmsService, smsConfigsProvider],
|
||||
exports: [SMS_CONFIG, SmsService],
|
||||
})
|
||||
export class UtilsModule {}
|
||||
Reference in New Issue
Block a user