|
@@ -0,0 +1,559 @@
|
|
|
|
|
+// oms/src/services/messageRecordService.ts
|
|
|
|
|
+
|
|
|
|
|
+import { MessageRecord, IMessageRecord } from "../models/messageRecordModel";
|
|
|
|
|
+import { MessageActivity } from "../models/messageActivityModel";
|
|
|
|
|
+import { MessageStrategy } from "../models/messageStrategyModel";
|
|
|
|
|
+import { MessageTemplate } from "../models/messageTemplateModel";
|
|
|
|
|
+
|
|
|
|
|
+export class MessageRecordService {
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Creates a new message push record.
|
|
|
|
|
+ * @param recordData Message record data.
|
|
|
|
|
+ * @returns The newly created message record object.
|
|
|
|
|
+ */
|
|
|
|
|
+ public async createMessageRecord(recordData: IMessageRecord): Promise<IMessageRecord> {
|
|
|
|
|
+ const newRecord = new MessageRecord(recordData);
|
|
|
|
|
+ return await newRecord.save();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Gets paginated message records with filtering and sorting support.
|
|
|
|
|
+ * @param page Page number.
|
|
|
|
|
+ * @param limit Number of records per page.
|
|
|
|
|
+ * @param filters Filter conditions.
|
|
|
|
|
+ * @param sortField Field to sort by.
|
|
|
|
|
+ * @param sortOrder Sort order ('asc' or 'desc').
|
|
|
|
|
+ * @returns An object containing records and the total count.
|
|
|
|
|
+ */
|
|
|
|
|
+ public async getPaginatedRecords(
|
|
|
|
|
+ page: number = 1,
|
|
|
|
|
+ limit: number = 10,
|
|
|
|
|
+ filters: { [key: string]: any } = {},
|
|
|
|
|
+ sortField: string = "createdAt",
|
|
|
|
|
+ sortOrder: "asc" | "desc" = "desc"
|
|
|
|
|
+ ): Promise<{ records: IMessageRecord[]; total: number }> {
|
|
|
|
|
+ // Build the query conditions
|
|
|
|
|
+ const query: any = {};
|
|
|
|
|
+ if (filters.uid) {
|
|
|
|
|
+ query.uid = filters.uid;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (filters.activityName) {
|
|
|
|
|
+ query.activityName = filters.activityName;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (filters.templateName) {
|
|
|
|
|
+ query.templateName = filters.templateName;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (filters.status !== undefined) {
|
|
|
|
|
+ query.status = filters.status;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const sort: any = {};
|
|
|
|
|
+ sort[sortField] = sortOrder === "asc" ? 1 : -1;
|
|
|
|
|
+
|
|
|
|
|
+ const skip = (page - 1) * limit;
|
|
|
|
|
+
|
|
|
|
|
+ const records = await MessageRecord.find(query).sort(sort).skip(skip).limit(limit);
|
|
|
|
|
+
|
|
|
|
|
+ const total = await MessageRecord.countDocuments(query);
|
|
|
|
|
+
|
|
|
|
|
+ return { records, total };
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Gets all message records for a specific user UID.
|
|
|
|
|
+ * @param uid User UID.
|
|
|
|
|
+ * @returns A list of message records.
|
|
|
|
|
+ */
|
|
|
|
|
+ public async getRecordsByUid(uid: string): Promise<IMessageRecord[]> {
|
|
|
|
|
+ return await MessageRecord.find({ uid }).sort({ createdAt: -1 });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Gets all message records related to a specific activity ID.
|
|
|
|
|
+ * @param activityId Message activity ID.
|
|
|
|
|
+ * @returns A list of message records.
|
|
|
|
|
+ */
|
|
|
|
|
+ public async getRecordsByActivityId(activityId: string): Promise<IMessageRecord[]> {
|
|
|
|
|
+ return await MessageRecord.find({ activityId }).sort({ createdAt: -1 });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Gets a single message record by ID.
|
|
|
|
|
+ * @param recordId Message record ID.
|
|
|
|
|
+ * @returns The message record object or null.
|
|
|
|
|
+ */
|
|
|
|
|
+ public async getSingleRecord(recordId: string): Promise<IMessageRecord | null> {
|
|
|
|
|
+ return await MessageRecord.findById(recordId);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Updates the status of a message record.
|
|
|
|
|
+ * @param recordId Message record ID.
|
|
|
|
|
+ * @param updateData Data to update.
|
|
|
|
|
+ * @returns The updated message record object.
|
|
|
|
|
+ */
|
|
|
|
|
+ public async updateMessageRecord(recordId: string, updateData: Partial<IMessageRecord>): Promise<IMessageRecord | null> {
|
|
|
|
|
+ return await MessageRecord.findByIdAndUpdate(recordId, updateData, { new: true });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Gets overall message push statistics.
|
|
|
|
|
+ */
|
|
|
|
|
+ public async getOverallStatistics() {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const result = await MessageRecord.aggregate([
|
|
|
|
|
+ // 1. Group messages by status
|
|
|
|
|
+ {
|
|
|
|
|
+ $group: {
|
|
|
|
|
+ _id: "$status",
|
|
|
|
|
+ count: { $sum: 1 },
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ // 2. Transform the result into a more manageable format
|
|
|
|
|
+ {
|
|
|
|
|
+ $group: {
|
|
|
|
|
+ _id: null,
|
|
|
|
|
+ total_records: { $sum: "$count" },
|
|
|
|
|
+ status_counts: {
|
|
|
|
|
+ $push: {
|
|
|
|
|
+ k: { $toString: "$_id" },
|
|
|
|
|
+ v: "$count",
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ // 3. Reformat the output and calculate all rates
|
|
|
|
|
+ {
|
|
|
|
|
+ $project: {
|
|
|
|
|
+ _id: 0,
|
|
|
|
|
+ totalRecords: "$total_records",
|
|
|
|
|
+ sent: {
|
|
|
|
|
+ $ifNull: [{ $arrayElemAt: ["$status_counts.v", { $indexOfArray: ["$status_counts.k", "1"] }] }, 0],
|
|
|
|
|
+ },
|
|
|
|
|
+ delivered: {
|
|
|
|
|
+ $ifNull: [{ $arrayElemAt: ["$status_counts.v", { $indexOfArray: ["$status_counts.k", "2"] }] }, 0],
|
|
|
|
|
+ },
|
|
|
|
|
+ opened: {
|
|
|
|
|
+ $ifNull: [{ $arrayElemAt: ["$status_counts.v", { $indexOfArray: ["$status_counts.k", "3"] }] }, 0],
|
|
|
|
|
+ },
|
|
|
|
|
+ failed: {
|
|
|
|
|
+ $ifNull: [{ $arrayElemAt: ["$status_counts.v", { $indexOfArray: ["$status_counts.k", "-1"] }] }, 0],
|
|
|
|
|
+ },
|
|
|
|
|
+ // Sent success rate = (sent + delivered + opened) / total
|
|
|
|
|
+ sentSuccessRate: {
|
|
|
|
|
+ $cond: [
|
|
|
|
|
+ { $eq: ["$total_records", 0] },
|
|
|
|
|
+ 0,
|
|
|
|
|
+ {
|
|
|
|
|
+ $divide: [
|
|
|
|
|
+ {
|
|
|
|
|
+ $sum: [
|
|
|
|
|
+ { $ifNull: [{ $arrayElemAt: ["$status_counts.v", { $indexOfArray: ["$status_counts.k", "1"] }] }, 0] },
|
|
|
|
|
+ { $ifNull: [{ $arrayElemAt: ["$status_counts.v", { $indexOfArray: ["$status_counts.k", "2"] }] }, 0] },
|
|
|
|
|
+ { $ifNull: [{ $arrayElemAt: ["$status_counts.v", { $indexOfArray: ["$status_counts.k", "3"] }] }, 0] },
|
|
|
|
|
+ ],
|
|
|
|
|
+ },
|
|
|
|
|
+ "$total_records",
|
|
|
|
|
+ ],
|
|
|
|
|
+ },
|
|
|
|
|
+ ],
|
|
|
|
|
+ },
|
|
|
|
|
+ // Delivered rate = delivered / (sent + delivered)
|
|
|
|
|
+ deliveredRate: {
|
|
|
|
|
+ $cond: [
|
|
|
|
|
+ {
|
|
|
|
|
+ $eq: [
|
|
|
|
|
+ {
|
|
|
|
|
+ $sum: [
|
|
|
|
|
+ { $ifNull: [{ $arrayElemAt: ["$status_counts.v", { $indexOfArray: ["$status_counts.k", "1"] }] }, 0] },
|
|
|
|
|
+ { $ifNull: [{ $arrayElemAt: ["$status_counts.v", { $indexOfArray: ["$status_counts.k", "2"] }] }, 0] },
|
|
|
|
|
+ ],
|
|
|
|
|
+ },
|
|
|
|
|
+ 0,
|
|
|
|
|
+ ],
|
|
|
|
|
+ },
|
|
|
|
|
+ 0,
|
|
|
|
|
+ {
|
|
|
|
|
+ $divide: [
|
|
|
|
|
+ { $ifNull: [{ $arrayElemAt: ["$status_counts.v", { $indexOfArray: ["$status_counts.k", "2"] }] }, 0] },
|
|
|
|
|
+ {
|
|
|
|
|
+ $sum: [
|
|
|
|
|
+ { $ifNull: [{ $arrayElemAt: ["$status_counts.v", { $indexOfArray: ["$status_counts.k", "1"] }] }, 0] },
|
|
|
|
|
+ { $ifNull: [{ $arrayElemAt: ["$status_counts.v", { $indexOfArray: ["$status_counts.k", "2"] }] }, 0] },
|
|
|
|
|
+ ],
|
|
|
|
|
+ },
|
|
|
|
|
+ ],
|
|
|
|
|
+ },
|
|
|
|
|
+ ],
|
|
|
|
|
+ },
|
|
|
|
|
+ // Opened rate = opened / (delivered + opened)
|
|
|
|
|
+ openedRate: {
|
|
|
|
|
+ $cond: [
|
|
|
|
|
+ {
|
|
|
|
|
+ $eq: [
|
|
|
|
|
+ {
|
|
|
|
|
+ $sum: [
|
|
|
|
|
+ { $ifNull: [{ $arrayElemAt: ["$status_counts.v", { $indexOfArray: ["$status_counts.k", "2"] }] }, 0] },
|
|
|
|
|
+ { $ifNull: [{ $arrayElemAt: ["$status_counts.v", { $indexOfArray: ["$status_counts.k", "3"] }] }, 0] },
|
|
|
|
|
+ ],
|
|
|
|
|
+ },
|
|
|
|
|
+ 0,
|
|
|
|
|
+ ],
|
|
|
|
|
+ },
|
|
|
|
|
+ 0,
|
|
|
|
|
+ {
|
|
|
|
|
+ $divide: [
|
|
|
|
|
+ { $ifNull: [{ $arrayElemAt: ["$status_counts.v", { $indexOfArray: ["$status_counts.k", "3"] }] }, 0] },
|
|
|
|
|
+ {
|
|
|
|
|
+ $sum: [
|
|
|
|
|
+ { $ifNull: [{ $arrayElemAt: ["$status_counts.v", { $indexOfArray: ["$status_counts.k", "2"] }] }, 0] },
|
|
|
|
|
+ { $ifNull: [{ $arrayElemAt: ["$status_counts.v", { $indexOfArray: ["$status_counts.k", "3"] }] }, 0] },
|
|
|
|
|
+ ],
|
|
|
|
|
+ },
|
|
|
|
|
+ ],
|
|
|
|
|
+ },
|
|
|
|
|
+ ],
|
|
|
|
|
+ },
|
|
|
|
|
+ // Token invalidation rate
|
|
|
|
|
+ tokenInvalidationRate: {
|
|
|
|
|
+ $cond: [{ $eq: ["$total_records", 0] }, 0, { $divide: [{ $ifNull: [{ $arrayElemAt: ["$status_counts.v", { $indexOfArray: ["$status_counts.k", "-1"] }] }, 0] }, "$total_records"] }],
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ ]);
|
|
|
|
|
+
|
|
|
|
|
+ return result[0];
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error("Error fetching overall statistics:", error);
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Gets message statistics grouped by activity.
|
|
|
|
|
+ */
|
|
|
|
|
+ public async getStatisticsByActivity() {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const results = await MessageRecord.aggregate([
|
|
|
|
|
+ // 1. Group by both activityId and status
|
|
|
|
|
+ {
|
|
|
|
|
+ $group: {
|
|
|
|
|
+ _id: { activityId: "$activityId", activityName: "$activityName", status: "$status" },
|
|
|
|
|
+ count: { $sum: 1 },
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ // 2. Regroup by activityId to summarize counts and get the activityName
|
|
|
|
|
+ {
|
|
|
|
|
+ $group: {
|
|
|
|
|
+ _id: "$_id.activityId", // Group by the ID
|
|
|
|
|
+ activityName: { $first: "$_id.activityName" }, // Keep the name
|
|
|
|
|
+ total_sent: { $sum: "$count" },
|
|
|
|
|
+ sent_count: { $sum: { $cond: [{ $eq: ["$_id.status", 1] }, "$count", 0] } },
|
|
|
|
|
+ delivered_count: {
|
|
|
|
|
+ $sum: { $cond: [{ $eq: ["$_id.status", 2] }, "$count", 0] },
|
|
|
|
|
+ },
|
|
|
|
|
+ opened_count: {
|
|
|
|
|
+ $sum: { $cond: [{ $eq: ["$_id.status", 3] }, "$count", 0] },
|
|
|
|
|
+ },
|
|
|
|
|
+ failed_count: {
|
|
|
|
|
+ $sum: { $cond: [{ $eq: ["$_id.status", -1] }, "$count", 0] },
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ // 3. Calculate rates and format the output
|
|
|
|
|
+ {
|
|
|
|
|
+ $project: {
|
|
|
|
|
+ _id: 0,
|
|
|
|
|
+ activityId: "$_id",
|
|
|
|
|
+ activityName: "$activityName",
|
|
|
|
|
+ totalSent: "$total_sent",
|
|
|
|
|
+ sent: "$sent_count",
|
|
|
|
|
+ delivered: "$delivered_count",
|
|
|
|
|
+ opened: "$opened_count",
|
|
|
|
|
+ failed: "$failed_count",
|
|
|
|
|
+ sentSuccessRate: {
|
|
|
|
|
+ $cond: [{ $eq: ["$total_sent", 0] }, 0, { $divide: [{ $sum: ["$sent_count", "$delivered_count", "$opened_count"] }, "$total_sent"] }],
|
|
|
|
|
+ },
|
|
|
|
|
+ tokenInvalidationRate: {
|
|
|
|
|
+ $cond: [{ $eq: ["$total_sent", 0] }, 0, { $divide: ["$failed_count", "$total_sent"] }],
|
|
|
|
|
+ },
|
|
|
|
|
+ deliveredRate: {
|
|
|
|
|
+ $cond: [{ $eq: [{ $sum: ["$sent_count", "$delivered_count"] }, 0] }, 0, { $divide: ["$delivered_count", { $sum: ["$sent_count", "$delivered_count"] }] }],
|
|
|
|
|
+ },
|
|
|
|
|
+ openedRate: {
|
|
|
|
|
+ $cond: [{ $eq: [{ $sum: ["$delivered_count", "$opened_count"] }, 0] }, 0, { $divide: ["$opened_count", { $sum: ["$delivered_count", "$opened_count"] }] }],
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ // 4. Sort by deliveredRate in descending order
|
|
|
|
|
+ {
|
|
|
|
|
+ $sort: { deliveredRate: -1 },
|
|
|
|
|
+ },
|
|
|
|
|
+ ]);
|
|
|
|
|
+
|
|
|
|
|
+ return results;
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error("Error fetching statistics by activity:", error);
|
|
|
|
|
+ return [];
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Gets message statistics grouped by strategy.
|
|
|
|
|
+ */
|
|
|
|
|
+ public async getStatisticsByStrategy() {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const results = await MessageRecord.aggregate([
|
|
|
|
|
+ // 1. Group by both strategyId and status
|
|
|
|
|
+ {
|
|
|
|
|
+ $group: {
|
|
|
|
|
+ _id: { strategyId: "$strategyId", strategyName: "$strategyName", status: "$status" },
|
|
|
|
|
+ count: { $sum: 1 },
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ // 2. Regroup by strategyId to summarize counts and get the strategyName
|
|
|
|
|
+ {
|
|
|
|
|
+ $group: {
|
|
|
|
|
+ _id: "$_id.strategyId",
|
|
|
|
|
+ strategyName: { $first: "$_id.strategyName" },
|
|
|
|
|
+ total_sent: { $sum: "$count" },
|
|
|
|
|
+ sent_count: { $sum: { $cond: [{ $eq: ["$_id.status", 1] }, "$count", 0] } },
|
|
|
|
|
+ delivered_count: {
|
|
|
|
|
+ $sum: { $cond: [{ $eq: ["$_id.status", 2] }, "$count", 0] },
|
|
|
|
|
+ },
|
|
|
|
|
+ opened_count: {
|
|
|
|
|
+ $sum: { $cond: [{ $eq: ["$_id.status", 3] }, "$count", 0] },
|
|
|
|
|
+ },
|
|
|
|
|
+ failed_count: {
|
|
|
|
|
+ $sum: { $cond: [{ $eq: ["$_id.status", -1] }, "$count", 0] },
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ // 3. Calculate rates and format the output
|
|
|
|
|
+ {
|
|
|
|
|
+ $project: {
|
|
|
|
|
+ _id: 0,
|
|
|
|
|
+ strategyId: "$_id",
|
|
|
|
|
+ strategyName: "$strategyName",
|
|
|
|
|
+ totalSent: "$total_sent",
|
|
|
|
|
+ sent: "$sent_count",
|
|
|
|
|
+ delivered: "$delivered_count",
|
|
|
|
|
+ opened: "$opened_count",
|
|
|
|
|
+ failed: "$failed_count",
|
|
|
|
|
+ sentSuccessRate: {
|
|
|
|
|
+ $cond: [{ $eq: ["$total_sent", 0] }, 0, { $divide: [{ $sum: ["$sent_count", "$delivered_count", "$opened_count"] }, "$total_sent"] }],
|
|
|
|
|
+ },
|
|
|
|
|
+ tokenInvalidationRate: {
|
|
|
|
|
+ $cond: [{ $eq: ["$total_sent", 0] }, 0, { $divide: ["$failed_count", "$total_sent"] }],
|
|
|
|
|
+ },
|
|
|
|
|
+ deliveredRate: {
|
|
|
|
|
+ $cond: [{ $eq: [{ $sum: ["$sent_count", "$delivered_count"] }, 0] }, 0, { $divide: ["$delivered_count", { $sum: ["$sent_count", "$delivered_count"] }] }],
|
|
|
|
|
+ },
|
|
|
|
|
+ openedRate: {
|
|
|
|
|
+ $cond: [{ $eq: [{ $sum: ["$delivered_count", "$opened_count"] }, 0] }, 0, { $divide: ["$opened_count", { $sum: ["$delivered_count", "$opened_count"] }] }],
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ // 4. Sort by deliveredRate in descending order
|
|
|
|
|
+ {
|
|
|
|
|
+ $sort: { deliveredRate: -1 },
|
|
|
|
|
+ },
|
|
|
|
|
+ ]);
|
|
|
|
|
+
|
|
|
|
|
+ return results;
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error("Error fetching statistics by strategy:", error);
|
|
|
|
|
+ return [];
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Gets message statistics grouped by template.
|
|
|
|
|
+ */
|
|
|
|
|
+ public async getStatisticsByTemplate() {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const results = await MessageRecord.aggregate([
|
|
|
|
|
+ // 1. Group by both templateId and status
|
|
|
|
|
+ {
|
|
|
|
|
+ $group: {
|
|
|
|
|
+ _id: { templateId: "$templateId", templateName: "$templateName", status: "$status" },
|
|
|
|
|
+ count: { $sum: 1 },
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ // 2. Regroup by templateId to summarize counts and get the templateName
|
|
|
|
|
+ {
|
|
|
|
|
+ $group: {
|
|
|
|
|
+ _id: "$_id.templateId",
|
|
|
|
|
+ templateName: { $first: "$_id.templateName" },
|
|
|
|
|
+ total_sent: { $sum: "$count" },
|
|
|
|
|
+ sent_count: { $sum: { $cond: [{ $eq: ["$_id.status", 1] }, "$count", 0] } },
|
|
|
|
|
+ delivered_count: {
|
|
|
|
|
+ $sum: { $cond: [{ $eq: ["$_id.status", 2] }, "$count", 0] },
|
|
|
|
|
+ },
|
|
|
|
|
+ opened_count: {
|
|
|
|
|
+ $sum: { $cond: [{ $eq: ["$_id.status", 3] }, "$count", 0] },
|
|
|
|
|
+ },
|
|
|
|
|
+ failed_count: {
|
|
|
|
|
+ $sum: { $cond: [{ $eq: ["$_id.status", -1] }, "$count", 0] },
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ // 3. Calculate rates and format the output
|
|
|
|
|
+ {
|
|
|
|
|
+ $project: {
|
|
|
|
|
+ _id: 0,
|
|
|
|
|
+ templateId: "$_id",
|
|
|
|
|
+ templateName: "$templateName",
|
|
|
|
|
+ totalSent: "$total_sent",
|
|
|
|
|
+ sent: "$sent_count",
|
|
|
|
|
+ delivered: "$delivered_count",
|
|
|
|
|
+ opened: "$opened_count",
|
|
|
|
|
+ failed: "$failed_count",
|
|
|
|
|
+ sentSuccessRate: {
|
|
|
|
|
+ $cond: [{ $eq: ["$total_sent", 0] }, 0, { $divide: [{ $sum: ["$sent_count", "$delivered_count", "$opened_count"] }, "$total_sent"] }],
|
|
|
|
|
+ },
|
|
|
|
|
+ tokenInvalidationRate: {
|
|
|
|
|
+ $cond: [{ $eq: ["$total_sent", 0] }, 0, { $divide: ["$failed_count", "$total_sent"] }],
|
|
|
|
|
+ },
|
|
|
|
|
+ deliveredRate: {
|
|
|
|
|
+ $cond: [{ $eq: [{ $sum: ["$sent_count", "$delivered_count"] }, 0] }, 0, { $divide: ["$delivered_count", { $sum: ["$sent_count", "$delivered_count"] }] }],
|
|
|
|
|
+ },
|
|
|
|
|
+ openedRate: {
|
|
|
|
|
+ $cond: [{ $eq: [{ $sum: ["$delivered_count", "$opened_count"] }, 0] }, 0, { $divide: ["$opened_count", { $sum: ["$delivered_count", "$opened_count"] }] }],
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ // 4. Sort by deliveredRate in descending order
|
|
|
|
|
+ {
|
|
|
|
|
+ $sort: { deliveredRate: -1 },
|
|
|
|
|
+ },
|
|
|
|
|
+ ]);
|
|
|
|
|
+
|
|
|
|
|
+ return results;
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error("Error fetching statistics by template:", error);
|
|
|
|
|
+ return [];
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Gets daily message sending trends.
|
|
|
|
|
+ */
|
|
|
|
|
+ public async getDailySentTrends() {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const results = await MessageRecord.aggregate([
|
|
|
|
|
+ // 1. Truncate actualSendAt to the day, ignoring time
|
|
|
|
|
+ {
|
|
|
|
|
+ $project: {
|
|
|
|
|
+ _id: 0,
|
|
|
|
|
+ date: {
|
|
|
|
|
+ $dateTrunc: { date: "$actualSendAt", unit: "day", timezone: "America/Los_Angeles" },
|
|
|
|
|
+ },
|
|
|
|
|
+ status: "$status",
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ // 2. Group by date and status
|
|
|
|
|
+ {
|
|
|
|
|
+ $group: {
|
|
|
|
|
+ _id: { date: "$date", status: "$status" },
|
|
|
|
|
+ count: { $sum: 1 },
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ // 3. Regroup to summarize counts by date
|
|
|
|
|
+ {
|
|
|
|
|
+ $group: {
|
|
|
|
|
+ _id: "$_id.date",
|
|
|
|
|
+ totalSent: { $sum: "$count" },
|
|
|
|
|
+ sent: { $sum: { $cond: [{ $eq: ["$_id.status", 1] }, "$count", 0] } },
|
|
|
|
|
+ delivered: { $sum: { $cond: [{ $eq: ["$_id.status", 2] }, "$count", 0] } },
|
|
|
|
|
+ opened: { $sum: { $cond: [{ $eq: ["$_id.status", 3] }, "$count", 0] } },
|
|
|
|
|
+ failed: { $sum: { $cond: [{ $eq: ["$_id.status", -1] }, "$count", 0] } },
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ // 4. Calculate rates and format the output
|
|
|
|
|
+ {
|
|
|
|
|
+ $project: {
|
|
|
|
|
+ _id: 0,
|
|
|
|
|
+ date: "$_id",
|
|
|
|
|
+ totalSent: "$totalSent",
|
|
|
|
|
+ sent: "$sent",
|
|
|
|
|
+ delivered: "$delivered",
|
|
|
|
|
+ opened: "$opened",
|
|
|
|
|
+ failed: "$failed",
|
|
|
|
|
+ // Sent success rate = (sent + delivered + opened) / total
|
|
|
|
|
+ sentSuccessRate: {
|
|
|
|
|
+ $cond: [{ $eq: ["$totalSent", 0] }, 0, { $divide: [{ $sum: ["$sent", "$delivered", "$opened"] }, "$totalSent"] }],
|
|
|
|
|
+ },
|
|
|
|
|
+ // Delivered rate = delivered / (sent + delivered)
|
|
|
|
|
+ deliveredRate: {
|
|
|
|
|
+ $cond: [{ $eq: [{ $sum: ["$sent", "$delivered"] }, 0] }, 0, { $divide: ["$delivered", { $sum: ["$sent", "$delivered"] }] }],
|
|
|
|
|
+ },
|
|
|
|
|
+ // Opened rate = opened / (delivered + opened)
|
|
|
|
|
+ openedRate: {
|
|
|
|
|
+ $cond: [{ $eq: [{ $sum: ["$delivered", "$opened"] }, 0] }, 0, { $divide: ["$opened", { $sum: ["$delivered", "$opened"] }] }],
|
|
|
|
|
+ },
|
|
|
|
|
+ // Token invalidation rate
|
|
|
|
|
+ tokenInvalidationRate: {
|
|
|
|
|
+ $cond: [{ $eq: ["$totalSent", 0] }, 0, { $divide: ["$failed", "$totalSent"] }],
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ // 5. Sort by date in ascending order
|
|
|
|
|
+ {
|
|
|
|
|
+ $sort: { date: 1 },
|
|
|
|
|
+ },
|
|
|
|
|
+ ]);
|
|
|
|
|
+
|
|
|
|
|
+ return results;
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error("Error fetching daily sent trends:", error);
|
|
|
|
|
+ return [];
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Calculates the average delivery time.
|
|
|
|
|
+ */
|
|
|
|
|
+ public async getAverageDeliveryTime() {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const result = await MessageRecord.aggregate([
|
|
|
|
|
+ // 1. Filter for delivered messages (status 2 or 3) with existing timestamps
|
|
|
|
|
+ {
|
|
|
|
|
+ $match: {
|
|
|
|
|
+ status: { $in: [2, 3] },
|
|
|
|
|
+ actualSendAt: { $exists: true, $ne: null },
|
|
|
|
|
+ deliveredAt: { $exists: true, $ne: null },
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ // 2. Calculate the time difference (in milliseconds) from send to delivery
|
|
|
|
|
+ {
|
|
|
|
|
+ $addFields: {
|
|
|
|
|
+ time_to_deliver: {
|
|
|
|
|
+ $subtract: ["$deliveredAt", "$actualSendAt"],
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ // 3. Calculate the average time
|
|
|
|
|
+ {
|
|
|
|
|
+ $group: {
|
|
|
|
|
+ _id: null,
|
|
|
|
|
+ avg_time_to_deliver_ms: { $avg: "$time_to_deliver" },
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ // 4. Convert to seconds for readability
|
|
|
|
|
+ {
|
|
|
|
|
+ $project: {
|
|
|
|
|
+ _id: 0,
|
|
|
|
|
+ averageTimeToDeliverInSeconds: {
|
|
|
|
|
+ $divide: ["$avg_time_to_deliver_ms", 1000],
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ ]);
|
|
|
|
|
+ return result[0];
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error("Error calculating average delivery time:", error);
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|