119 lines
3.9 KiB
TypeScript
119 lines
3.9 KiB
TypeScript
import mongoose, { Document, Model, Schema, model } from "mongoose";
|
|
|
|
export type NotificationChannel = "whatsapp" | "email" | "system";
|
|
export type NotificationJobStatus = "pending" | "processing" | "sent" | "failed" | "cancelled";
|
|
export type NotificationJobType = "creation" | "reminder" | "update" | "cancellation";
|
|
|
|
export const DELETABLE_NOTIFICATION_JOB_STATUSES: NotificationJobStatus[] = ["sent", "failed", "cancelled"];
|
|
|
|
export interface NotificationJobPayload {
|
|
email?: string;
|
|
phoneNumber?: string;
|
|
userId?: string;
|
|
companyOwnerId?: string;
|
|
subject?: string;
|
|
message?: string;
|
|
emailSubject?: string;
|
|
emailMessage?: string;
|
|
emailTemplateId?: string;
|
|
emailContext?: Record<string, unknown>;
|
|
wapMessage?: string;
|
|
systemSubject?: string;
|
|
systemMessage?: string;
|
|
}
|
|
|
|
export interface INotificationJob {
|
|
_id: string;
|
|
companyId: string;
|
|
appointmentId: string;
|
|
clientId: string;
|
|
channel: NotificationChannel;
|
|
type: NotificationJobType;
|
|
scheduledAt: Date;
|
|
status: NotificationJobStatus;
|
|
attempts: number;
|
|
maxAttempts: number;
|
|
lastError?: string;
|
|
nextRetryAt?: Date;
|
|
providerResponse?: string;
|
|
payload?: NotificationJobPayload;
|
|
createdAt: Date;
|
|
updatedAt: Date;
|
|
sentAt?: Date;
|
|
}
|
|
|
|
export interface INotificationJobDocument extends Omit<INotificationJob, "_id">, Document {}
|
|
|
|
export class NotificationJobModel {
|
|
schema: Schema;
|
|
notificationJobList: Model<INotificationJobDocument>;
|
|
|
|
constructor() {
|
|
this.schema = new Schema(
|
|
{
|
|
companyId: {
|
|
type: Schema.Types.ObjectId,
|
|
required: true,
|
|
ref: "Companie",
|
|
},
|
|
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: ["pending", "processing", "sent", "failed", "cancelled"],
|
|
default: "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({ status: 1, scheduledAt: 1, nextRetryAt: 1 });
|
|
this.schema.index({ companyId: 1, status: 1 });
|
|
this.schema.index({ appointmentId: 1 });
|
|
|
|
this.notificationJobList = model<INotificationJobDocument>(
|
|
"NotificationJob",
|
|
this.schema
|
|
);
|
|
}
|
|
|
|
async deleteBeforeScheduledAt(cutoff: Date): Promise<number> {
|
|
const result = await this.notificationJobList.deleteMany({
|
|
scheduledAt: { $lt: cutoff },
|
|
status: { $in: DELETABLE_NOTIFICATION_JOB_STATUSES },
|
|
}).exec();
|
|
|
|
return result.deletedCount || 0;
|
|
}
|
|
}
|