feat: sms using twillio

This commit is contained in:
Abdalhamid Alhamad
2024-12-29 09:44:12 +03:00
parent 0750509a85
commit a7028fa64c
8 changed files with 122 additions and 7 deletions

74
package-lock.json generated
View File

@ -40,6 +40,7 @@
"moment": "^2.30.1", "moment": "^2.30.1",
"nestjs-i18n": "^10.4.9", "nestjs-i18n": "^10.4.9",
"nestjs-pino": "^4.1.0", "nestjs-pino": "^4.1.0",
"nestjs-twilio": "^4.4.0",
"nodemailer": "^6.9.16", "nodemailer": "^6.9.16",
"oci-common": "^2.99.0", "oci-common": "^2.99.0",
"oci-sdk": "^2.99.0", "oci-sdk": "^2.99.0",
@ -3808,7 +3809,6 @@
"node_modules/axios": { "node_modules/axios": {
"version": "1.7.7", "version": "1.7.7",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"follow-redirects": "^1.15.6", "follow-redirects": "^1.15.6",
"form-data": "^4.0.0", "form-data": "^4.0.0",
@ -6350,7 +6350,6 @@
} }
], ],
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": ">=4.0" "node": ">=4.0"
}, },
@ -10036,6 +10035,19 @@
"pino-http": "^6.4.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0" "pino-http": "^6.4.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0"
} }
}, },
"node_modules/nestjs-twilio": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/nestjs-twilio/-/nestjs-twilio-4.4.0.tgz",
"integrity": "sha512-TtT+mgVaIsiGNX1J8zkjVhIBxJPsChfU8gfu6dbPyEtde9ewgb5sxhAreOE6STT5U95OiSAlFcqKoqlARCIFxA==",
"license": "MIT",
"dependencies": {
"twilio": "^5.0.2"
},
"peerDependencies": {
"@nestjs/common": ">=9.0.0",
"@nestjs/core": ">=9.0.0"
}
},
"node_modules/nice-try": { "node_modules/nice-try": {
"version": "1.0.5", "version": "1.0.5",
"license": "MIT", "license": "MIT",
@ -15120,8 +15132,7 @@
}, },
"node_modules/proxy-from-env": { "node_modules/proxy-from-env": {
"version": "1.1.0", "version": "1.1.0",
"license": "MIT", "license": "MIT"
"peer": true
}, },
"node_modules/pug": { "node_modules/pug": {
"version": "3.0.3", "version": "3.0.3",
@ -16020,6 +16031,12 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/scmp": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/scmp/-/scmp-2.1.0.tgz",
"integrity": "sha512-o/mRQGk9Rcer/jEEw/yw4mwo3EU/NvYvp577/Btqrym9Qy5/MdWGBqipbALgd2lrdWTJ5/gqDusxfnQBxOxT2Q==",
"license": "BSD-3-Clause"
},
"node_modules/secure-json-parse": { "node_modules/secure-json-parse": {
"version": "2.7.0", "version": "2.7.0",
"license": "BSD-3-Clause" "license": "BSD-3-Clause"
@ -17032,6 +17049,46 @@
"version": "0.14.5", "version": "0.14.5",
"license": "Unlicense" "license": "Unlicense"
}, },
"node_modules/twilio": {
"version": "5.4.0",
"resolved": "https://registry.npmjs.org/twilio/-/twilio-5.4.0.tgz",
"integrity": "sha512-kEmxzdOLTzXzUEXIkBVwT1Itxlbp+rtGrQogNfPtSE3EjoEsxrxB/9tdMIEbrsioL8CzTk/+fiKNJekAyHxjuQ==",
"license": "MIT",
"dependencies": {
"axios": "^1.7.4",
"dayjs": "^1.11.9",
"https-proxy-agent": "^5.0.0",
"jsonwebtoken": "^9.0.2",
"qs": "^6.9.4",
"scmp": "^2.1.0",
"xmlbuilder": "^13.0.2"
},
"engines": {
"node": ">=14.0"
}
},
"node_modules/twilio/node_modules/jsonwebtoken": {
"version": "9.0.2",
"resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",
"integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==",
"license": "MIT",
"dependencies": {
"jws": "^3.2.2",
"lodash.includes": "^4.3.0",
"lodash.isboolean": "^3.0.3",
"lodash.isinteger": "^4.0.4",
"lodash.isnumber": "^3.0.3",
"lodash.isplainobject": "^4.0.6",
"lodash.isstring": "^4.0.1",
"lodash.once": "^4.0.0",
"ms": "^2.1.1",
"semver": "^7.5.4"
},
"engines": {
"node": ">=12",
"npm": ">=6"
}
},
"node_modules/type-check": { "node_modules/type-check": {
"version": "0.4.0", "version": "0.4.0",
"dev": true, "dev": true,
@ -17800,6 +17857,15 @@
"dev": true, "dev": true,
"license": "ISC" "license": "ISC"
}, },
"node_modules/xmlbuilder": {
"version": "13.0.2",
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-13.0.2.tgz",
"integrity": "sha512-Eux0i2QdDYKbdbA6AM6xE4m6ZTZr4G4xF9kahI2ukSEMCzwce2eX9WlTI5J3s+NU7hpasFsr8hWIONae7LluAQ==",
"license": "MIT",
"engines": {
"node": ">=6.0"
}
},
"node_modules/xtend": { "node_modules/xtend": {
"version": "4.0.2", "version": "4.0.2",
"license": "MIT", "license": "MIT",

View File

@ -57,6 +57,7 @@
"moment": "^2.30.1", "moment": "^2.30.1",
"nestjs-i18n": "^10.4.9", "nestjs-i18n": "^10.4.9",
"nestjs-pino": "^4.1.0", "nestjs-pino": "^4.1.0",
"nestjs-twilio": "^4.4.0",
"nodemailer": "^6.9.16", "nodemailer": "^6.9.16",
"oci-common": "^2.99.0", "oci-common": "^2.99.0",
"oci-sdk": "^2.99.0", "oci-sdk": "^2.99.0",

View File

@ -26,6 +26,7 @@ import { JuniorModule } from './junior/junior.module';
import { MoneyRequestModule } from './money-request/money-request.module'; import { MoneyRequestModule } from './money-request/money-request.module';
import { SavingGoalsModule } from './saving-goals/saving-goals.module'; import { SavingGoalsModule } from './saving-goals/saving-goals.module';
import { TaskModule } from './task/task.module'; import { TaskModule } from './task/task.module';
@Module({ @Module({
controllers: [], controllers: [],
imports: [ imports: [

View File

@ -1,15 +1,25 @@
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { TypeOrmModule } from '@nestjs/typeorm'; import { TypeOrmModule } from '@nestjs/typeorm';
import { TwilioModule } from 'nestjs-twilio';
import { AuthModule } from '~/auth/auth.module'; import { AuthModule } from '~/auth/auth.module';
import { buildTwilioOptions } from '~/core/module-options';
import { NotificationsController } from './controllers'; import { NotificationsController } from './controllers';
import { Notification } from './entities'; import { Notification } from './entities';
import { NotificationsRepository } from './repositories'; import { NotificationsRepository } from './repositories';
import { FirebaseService } from './services/firebase.service'; import { FirebaseService } from './services/firebase.service';
import { NotificationsService } from './services/notifications.service'; import { NotificationsService } from './services/notifications.service';
import { TwilioService } from './services/twilio.service';
@Module({ @Module({
imports: [TypeOrmModule.forFeature([Notification]), AuthModule], imports: [
providers: [NotificationsService, FirebaseService, NotificationsRepository], TypeOrmModule.forFeature([Notification]),
AuthModule,
TwilioModule.forRootAsync({
useFactory: buildTwilioOptions,
inject: [ConfigService],
}),
],
providers: [NotificationsService, FirebaseService, NotificationsRepository, TwilioService],
exports: [NotificationsService], exports: [NotificationsService],
controllers: [NotificationsController], controllers: [NotificationsController],
}) })

View File

@ -4,6 +4,7 @@ import { PageOptionsRequestDto } from '~/core/dtos';
import { Notification } from '../entities'; import { Notification } from '../entities';
import { NotificationsRepository } from '../repositories'; import { NotificationsRepository } from '../repositories';
import { FirebaseService } from './firebase.service'; import { FirebaseService } from './firebase.service';
import { TwilioService } from './twilio.service';
@Injectable() @Injectable()
export class NotificationsService { export class NotificationsService {
@ -11,6 +12,7 @@ export class NotificationsService {
private readonly deviceService: DeviceService, private readonly deviceService: DeviceService,
private readonly firebaseService: FirebaseService, private readonly firebaseService: FirebaseService,
private readonly notificationRepository: NotificationsRepository, private readonly notificationRepository: NotificationsRepository,
private readonly twilioService: TwilioService,
) {} ) {}
async sendPushNotification(userId: string, title: string, body: string) { async sendPushNotification(userId: string, title: string, body: string) {
@ -25,6 +27,10 @@ export class NotificationsService {
return this.firebaseService.sendNotification(tokens, title, body); return this.firebaseService.sendNotification(tokens, title, body);
} }
async sendSMS(to: string, body: string) {
await this.twilioService.sendSMS(to, body);
}
async getNotifications(userId: string, pageOptionsDto: PageOptionsRequestDto) { async getNotifications(userId: string, pageOptionsDto: PageOptionsRequestDto) {
const [[notifications, count], unreadCount] = await Promise.all([ const [[notifications, count], unreadCount] = await Promise.all([
this.notificationRepository.getNotifications(userId, pageOptionsDto), this.notificationRepository.getNotifications(userId, pageOptionsDto),

View File

@ -0,0 +1,21 @@
import { Injectable, Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { TwilioService as TwilioApiService } from 'nestjs-twilio';
import { Environment } from '~/core/enums';
@Injectable()
export class TwilioService {
private logger = new Logger(TwilioService.name);
constructor(private readonly twilioService: TwilioApiService, private readonly configService: ConfigService) {}
private from = this.configService.getOrThrow('TWILIO_PHONE_NUMBER');
sendSMS(to: string, body: string) {
if (this.configService.get('NODE_ENV') === Environment.DEV) {
this.logger.log(`Skipping SMS sending in DEV environment. Message: ${body} to: ${to}`);
return;
}
return this.twilioService.client.messages.create({
body,
to,
from: this.from,
});
}
}

View File

@ -1,4 +1,5 @@
export * from '././keyv-options'; export * from '././keyv-options';
export * from './config-options'; export * from './config-options';
export * from './logger-options'; export * from './logger-options';
export * from './twilio-options';
export * from './typeorm-options'; export * from './typeorm-options';

View File

@ -0,0 +1,9 @@
import { ConfigService } from '@nestjs/config';
import { TwilioModuleOptions } from 'nestjs-twilio';
export function buildTwilioOptions(config: ConfigService): TwilioModuleOptions {
return {
accountSid: config.get('TWILIO_ACCOUNT_SID'),
authToken: config.get('TWILIO_AUTH_TOKEN'),
};
}