feat: implement comprehensive notification engine with policies, preferences, and automated dispatching services
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
import { Document, Model, Schema, model } from "mongoose";
|
||||
import {
|
||||
CreateJobParams,
|
||||
INotificationJob,
|
||||
INotificationJobAdapter,
|
||||
NotificationJobStatus,
|
||||
} from "./NotificationJobs.Interface";
|
||||
|
||||
export interface INotificationJobDocument
|
||||
extends Omit<INotificationJob, "id">,
|
||||
Document {}
|
||||
|
||||
export class NotificationJobsAdapterMongoose implements INotificationJobAdapter {
|
||||
schema: Schema;
|
||||
notificationJobList: Model<INotificationJobDocument>;
|
||||
|
||||
constructor() {
|
||||
this.schema = new Schema(
|
||||
{
|
||||
companyId: {
|
||||
type: Schema.Types.ObjectId,
|
||||
required: true,
|
||||
ref: "Company",
|
||||
},
|
||||
appointmentId: {
|
||||
type: Schema.Types.ObjectId,
|
||||
required: true,
|
||||
ref: "Appointment",
|
||||
},
|
||||
clientId: {
|
||||
type: Schema.Types.ObjectId,
|
||||
required: true,
|
||||
ref: "Client",
|
||||
},
|
||||
channel: {
|
||||
type: String,
|
||||
required: true,
|
||||
enum: ["whatsapp", "email", "system"],
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
required: true,
|
||||
enum: ["creation", "reminder", "update", "cancellation"],
|
||||
},
|
||||
scheduledAt: { type: Date, required: true },
|
||||
status: {
|
||||
type: String,
|
||||
required: true,
|
||||
enum: Object.values(NotificationJobStatus),
|
||||
default: NotificationJobStatus.PENDING,
|
||||
},
|
||||
attempts: { type: Number, required: true, default: 0 },
|
||||
maxAttempts: { type: Number, required: true, default: 3 },
|
||||
lastError: { type: String, required: false },
|
||||
nextRetryAt: { type: Date, required: false },
|
||||
providerResponse: { type: String, required: false },
|
||||
payload: { type: Schema.Types.Mixed, required: false },
|
||||
sentAt: { type: Date, required: false },
|
||||
},
|
||||
{
|
||||
timestamps: true,
|
||||
}
|
||||
);
|
||||
|
||||
this.schema.index({ status: 1, scheduledAt: 1 });
|
||||
this.schema.index({ companyId: 1, status: 1 });
|
||||
this.schema.index({ appointmentId: 1 });
|
||||
|
||||
this.notificationJobList = model<INotificationJobDocument>(
|
||||
"NotificationJob",
|
||||
this.schema
|
||||
);
|
||||
}
|
||||
|
||||
public async create(data: CreateJobParams): Promise<INotificationJob> {
|
||||
const doc = await this.notificationJobList.create({
|
||||
...data,
|
||||
status: NotificationJobStatus.PENDING,
|
||||
attempts: 0,
|
||||
maxAttempts: 3,
|
||||
});
|
||||
return doc.toObject() as INotificationJob;
|
||||
}
|
||||
|
||||
public async findPendingDue(now: Date): Promise<INotificationJob[]> {
|
||||
const docs = await this.notificationJobList
|
||||
.find({
|
||||
status: NotificationJobStatus.PENDING,
|
||||
scheduledAt: { $lte: now },
|
||||
})
|
||||
.sort({ scheduledAt: 1 })
|
||||
.exec();
|
||||
return docs.map((d) => d.toObject() as INotificationJob);
|
||||
}
|
||||
|
||||
public async updateStatus(
|
||||
jobId: string,
|
||||
status: NotificationJobStatus,
|
||||
data?: { lastError?: string; nextRetryAt?: Date; providerResponse?: string }
|
||||
): Promise<void> {
|
||||
const update: Record<string, unknown> = { status };
|
||||
if (status === NotificationJobStatus.SENT) {
|
||||
update.sentAt = new Date();
|
||||
}
|
||||
if (data?.lastError !== undefined) update.lastError = data.lastError;
|
||||
if (data?.nextRetryAt !== undefined) update.nextRetryAt = data.nextRetryAt;
|
||||
if (data?.providerResponse !== undefined)
|
||||
update.providerResponse = data.providerResponse;
|
||||
|
||||
await this.notificationJobList.updateOne({ _id: jobId }, { $set: update }).exec();
|
||||
}
|
||||
|
||||
public async cancelByAppointment(appointmentId: string): Promise<void> {
|
||||
await this.notificationJobList
|
||||
.updateMany(
|
||||
{
|
||||
appointmentId,
|
||||
status: NotificationJobStatus.PENDING,
|
||||
},
|
||||
{ $set: { status: NotificationJobStatus.CANCELLED } }
|
||||
)
|
||||
.exec();
|
||||
}
|
||||
|
||||
public async findByAppointment(appointmentId: string): Promise<INotificationJob[]> {
|
||||
const docs = await this.notificationJobList
|
||||
.find({ appointmentId })
|
||||
.exec();
|
||||
return docs.map((d) => d.toObject() as INotificationJob);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user