mirror of
https://github.com/vernu/textbee.git
synced 2026-02-20 15:44:31 -05:00
Merge pull request #97 from vernu/improve-status-and-error-tracking
Improve sms status and error tracking logic
This commit is contained in:
@@ -13,6 +13,7 @@ import { BullModule } from '@nestjs/bull'
|
||||
import { ConfigModule } from '@nestjs/config'
|
||||
import { SmsQueueService } from './queue/sms-queue.service'
|
||||
import { SmsQueueProcessor } from './queue/sms-queue.processor'
|
||||
import { SmsStatusUpdateTask } from './tasks/sms-status-update.task'
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@@ -49,7 +50,7 @@ import { SmsQueueProcessor } from './queue/sms-queue.processor'
|
||||
ConfigModule,
|
||||
],
|
||||
controllers: [GatewayController],
|
||||
providers: [GatewayService, SmsQueueService, SmsQueueProcessor],
|
||||
providers: [GatewayService, SmsQueueService, SmsQueueProcessor, SmsStatusUpdateTask],
|
||||
exports: [MongooseModule, GatewayService, SmsQueueService],
|
||||
})
|
||||
export class GatewayModule {}
|
||||
|
||||
@@ -547,6 +547,7 @@ export class GatewayService {
|
||||
device: device._id,
|
||||
message: dto.message,
|
||||
type: SMSType.RECEIVED,
|
||||
status: 'received',
|
||||
sender: dto.sender,
|
||||
receivedAt,
|
||||
})
|
||||
@@ -763,8 +764,9 @@ export class GatewayService {
|
||||
const allHaveSameStatus = allSmsInBatch.every(sms => sms.status.toLowerCase() === normalizedStatus);
|
||||
|
||||
if (allHaveSameStatus) {
|
||||
const smsBatchStatus = normalizedStatus === 'failed' ? 'failed' : 'completed';
|
||||
await this.smsBatchModel.findByIdAndUpdate(dto.smsBatchId, {
|
||||
$set: { status: normalizedStatus }
|
||||
$set: { status: smsBatchStatus }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,8 +50,8 @@ export class SMS {
|
||||
@Prop({ type: Date })
|
||||
failedAt: Date
|
||||
|
||||
@Prop({ type: Number, required: false })
|
||||
errorCode: number
|
||||
@Prop({ type: String, required: false })
|
||||
errorCode: string
|
||||
|
||||
@Prop({ type: String, required: false })
|
||||
errorMessage: string
|
||||
@@ -60,7 +60,7 @@ export class SMS {
|
||||
// failureReason: string
|
||||
|
||||
@Prop({ type: String, default: 'pending' })
|
||||
status: 'pending' | 'sent' | 'delivered' | 'failed'
|
||||
status: 'pending' | 'sent' | 'delivered' | 'failed' | 'unknown' | 'received'
|
||||
|
||||
// misc metadata for debugging
|
||||
@Prop({ type: Object })
|
||||
|
||||
77
api/src/gateway/tasks/sms-status-update.task.spec.ts
Normal file
77
api/src/gateway/tasks/sms-status-update.task.spec.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { getModelToken } from '@nestjs/mongoose';
|
||||
import { SmsStatusUpdateTask } from './sms-status-update.task';
|
||||
import { SMS } from '../schemas/sms.schema';
|
||||
import { SMSBatch } from '../schemas/sms-batch.schema';
|
||||
import { Model } from 'mongoose';
|
||||
|
||||
describe('SmsStatusUpdateTask', () => {
|
||||
let task: SmsStatusUpdateTask;
|
||||
let smsModel: Model<SMS>;
|
||||
let smsBatchModel: Model<SMSBatch>;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
SmsStatusUpdateTask,
|
||||
{
|
||||
provide: getModelToken(SMS.name),
|
||||
useValue: {
|
||||
updateMany: jest.fn().mockResolvedValue({ modifiedCount: 5 }),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: getModelToken(SMSBatch.name),
|
||||
useValue: {
|
||||
updateMany: jest.fn().mockResolvedValue({ modifiedCount: 2 }),
|
||||
},
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
task = module.get<SmsStatusUpdateTask>(SmsStatusUpdateTask);
|
||||
smsModel = module.get<Model<SMS>>(getModelToken(SMS.name));
|
||||
smsBatchModel = module.get<Model<SMSBatch>>(getModelToken(SMSBatch.name));
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(task).toBeDefined();
|
||||
});
|
||||
|
||||
describe('handlePendingSmsTimeout', () => {
|
||||
it('should update stale pending SMS messages to unknown status', async () => {
|
||||
jest.spyOn(smsModel, 'updateMany');
|
||||
jest.spyOn(smsBatchModel, 'updateMany');
|
||||
|
||||
await task.handlePendingSmsTimeout();
|
||||
|
||||
// Check that SMS model was updated with correct query
|
||||
expect(smsModel.updateMany).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
status: 'pending',
|
||||
requestedAt: expect.any(Object),
|
||||
}),
|
||||
{
|
||||
$set: {
|
||||
status: 'unknown',
|
||||
errorMessage: 'Status update timeout - no response received after 20 minutes',
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
// Check that SMSBatch model was updated with correct query
|
||||
expect(smsBatchModel.updateMany).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
status: 'pending',
|
||||
createdAt: expect.any(Object),
|
||||
}),
|
||||
{
|
||||
$set: {
|
||||
status: 'unknown',
|
||||
error: 'Status update timeout - no response received after 20 minutes',
|
||||
},
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
68
api/src/gateway/tasks/sms-status-update.task.ts
Normal file
68
api/src/gateway/tasks/sms-status-update.task.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { Cron, CronExpression } from '@nestjs/schedule';
|
||||
import { InjectModel } from '@nestjs/mongoose';
|
||||
import { Model } from 'mongoose';
|
||||
import { SMS } from '../schemas/sms.schema';
|
||||
import { SMSBatch } from '../schemas/sms-batch.schema';
|
||||
|
||||
|
||||
@Injectable()
|
||||
export class SmsStatusUpdateTask {
|
||||
private readonly logger = new Logger(SmsStatusUpdateTask.name);
|
||||
|
||||
constructor(
|
||||
@InjectModel(SMS.name) private smsModel: Model<SMS>,
|
||||
@InjectModel(SMSBatch.name) private smsBatchModel: Model<SMSBatch>,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Cron job that runs every 5 minutes to update the status of SMS messages
|
||||
* that have been pending for more than 20 minutes without any status updates.
|
||||
*/
|
||||
@Cron(CronExpression.EVERY_5_MINUTES)
|
||||
async handlePendingSmsTimeout() {
|
||||
this.logger.log('Running cron job to update stale pending SMS messages');
|
||||
|
||||
const twentyMinutesAgo = new Date();
|
||||
twentyMinutesAgo.setMinutes(twentyMinutesAgo.getMinutes() - 20);
|
||||
|
||||
try {
|
||||
|
||||
const result = await this.smsModel.updateMany(
|
||||
{
|
||||
status: 'pending',
|
||||
requestedAt: { $lt: twentyMinutesAgo },
|
||||
},
|
||||
{
|
||||
$set: {
|
||||
status: 'unknown',
|
||||
errorMessage: 'Status update timeout - no response received after 20 minutes'
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
|
||||
this.logger.log(`Updated ${result.modifiedCount} SMS messages from 'pending' to 'unknown' status`);
|
||||
|
||||
const batchResult = await this.smsBatchModel.updateMany(
|
||||
{
|
||||
status: 'pending',
|
||||
createdAt: { $lt: twentyMinutesAgo }
|
||||
},
|
||||
{
|
||||
$set: {
|
||||
status: 'unknown',
|
||||
error: 'Status update timeout - no response received after 20 minutes'
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
|
||||
this.logger.log(`Updated ${batchResult.modifiedCount} SMS batches from 'pending' to 'unknown' status`);
|
||||
} catch (error) {
|
||||
this.logger.error('Error updating stale pending SMS messages', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user