chore(api): better handle batch sms

This commit is contained in:
isra el
2024-04-28 18:02:36 +03:00
parent f7fa02cddb
commit 8a8ae33900
4 changed files with 116 additions and 19 deletions

View File

@@ -6,6 +6,7 @@ import { GatewayService } from './gateway.service'
import { AuthModule } from '../auth/auth.module'
import { UsersModule } from '../users/users.module'
import { SMS, SMSSchema } from './schemas/sms.schema'
import { SMSBatch, SMSBatchSchema } from './schemas/sms-batch.schema'
@Module({
imports: [
@@ -18,6 +19,10 @@ import { SMS, SMSSchema } from './schemas/sms.schema'
name: SMS.name,
schema: SMSSchema,
},
{
name: SMSBatch.name,
schema: SMSBatchSchema,
},
]),
AuthModule,
UsersModule,

View File

@@ -13,11 +13,14 @@ import { User } from '../users/schemas/user.schema'
import { AuthService } from 'src/auth/auth.service'
import { SMS } from './schemas/sms.schema'
import { SMSType } from './sms-type.enum'
import { SMSBatch } from './schemas/sms-batch.schema'
import { Message } from 'firebase-admin/lib/messaging/messaging-api'
@Injectable()
export class GatewayService {
constructor(
@InjectModel(Device.name) private deviceModel: Model<DeviceDocument>,
@InjectModel(SMS.name) private smsModel: Model<SMS>,
@InjectModel(SMSBatch.name) private smsBatchModel: Model<SMSBatch>,
private authService: AuthService,
) {}
@@ -85,14 +88,6 @@ export class GatewayService {
}
async sendSMS(deviceId: string, smsData: SendSMSInputDTO): Promise<any> {
const updatedSMSData = {
message: smsData.message || smsData.smsBody,
recipients: smsData.recipients || smsData.receivers,
// Legacy fields to be removed in the future
smsBody: smsData.message || smsData.smsBody,
receivers: smsData.recipients || smsData.receivers,
}
const device = await this.deviceModel.findById(deviceId)
if (!device?.enabled) {
@@ -105,23 +100,76 @@ export class GatewayService {
)
}
const stringifiedSMSData = JSON.stringify(updatedSMSData)
const payload: any = {
data: {
smsData: stringifiedSMSData,
},
}
const message = smsData.message || smsData.smsBody
const recipients = smsData.recipients || smsData.receivers
// TODO: Save SMS and Implement a queue to send the SMS if recipients are too many
// TODO: Implement a queue to send the SMS if recipients are too many
let smsBatch: SMSBatch
try {
const response = await firebaseAdmin
.messaging()
.sendToDevice(device.fcmToken, payload, { priority: 'high' })
smsBatch = await this.smsBatchModel.create({
device: device._id,
message,
metadata: {
recipientCount: recipients.length,
recipientPreview: this.getRecipientsPreview(recipients),
},
})
} catch (e) {
throw new HttpException(
{
success: false,
error: 'Failed to create SMS batch',
additionalInfo: e,
},
HttpStatus.BAD_REQUEST,
)
}
const fcmMessages: Message[] = []
for (const recipient of recipients) {
const sms = await this.smsModel.create({
device: device._id,
smsBatch: smsBatch._id,
message: message,
type: SMSType.SENT,
recipient,
requestedAt: new Date(),
})
const updatedSMSData = {
smsId: sms._id,
smsBatchId: smsBatch._id,
message,
recipients: [recipient],
// Legacy fields to be removed in the future
smsBody: message,
receivers: [recipient],
}
const stringifiedSMSData = JSON.stringify(updatedSMSData)
const fcmMessage: Message = {
data: {
smsData: stringifiedSMSData,
},
token: device.fcmToken,
android: {
priority: 'high',
},
}
fcmMessages.push(fcmMessage)
}
try {
const response = await firebaseAdmin.messaging().sendAll(fcmMessages)
console.log(response)
this.deviceModel
.findByIdAndUpdate(deviceId, {
$inc: { sentSMSCount: updatedSMSData.recipients.length },
$inc: { sentSMSCount: recipients.length },
})
.exec()
.catch((e) => {
@@ -132,7 +180,9 @@ export class GatewayService {
} catch (e) {
throw new HttpException(
{
success: false,
error: 'Failed to send SMS',
additionalInfo: e,
},
HttpStatus.BAD_REQUEST,
)
@@ -235,4 +285,20 @@ export class GatewayService {
totalApiKeyCount,
}
}
private getRecipientsPreview(recipients: string[]): string {
if (recipients.length === 0) {
return null
} else if (recipients.length === 1) {
return recipients[0]
} else if (recipients.length === 2) {
return `${recipients[0]} and ${recipients[1]}`
} else if (recipients.length === 3) {
return `${recipients[0]}, ${recipients[1]}, and ${recipients[2]}`
} else {
return `${recipients[0]}, ${recipients[1]}, and ${
recipients.length - 2
} others`
}
}
}

View File

@@ -0,0 +1,22 @@
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'
import { Document, Types } from 'mongoose'
import { Device } from './device.schema'
export type SMSBatchDocument = SMSBatch & Document
@Schema({ timestamps: true })
export class SMSBatch {
_id?: Types.ObjectId
@Prop({ type: Types.ObjectId, ref: Device.name })
device: Device
@Prop({ type: String })
message: string
// misc metadata for debugging
@Prop({ type: Object })
metadata: Record<string, any>
}
export const SMSBatchSchema = SchemaFactory.createForClass(SMSBatch)

View File

@@ -1,6 +1,7 @@
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'
import { Document, Types } from 'mongoose'
import { Device } from './device.schema'
import { SMSBatch } from './sms-batch.schema'
export type SMSDocument = SMS & Document
@@ -11,6 +12,9 @@ export class SMS {
@Prop({ type: Types.ObjectId, ref: Device.name, required: true })
device: Device
@Prop({ type: Types.ObjectId, ref: SMSBatch.name, required: true })
smsBatch: SMSBatch
@Prop({ type: String })
message: string