File

apps/recallassess/recallassess-api/src/api/admin/email/services/email-engagement.service.ts

Description

Persists per-participant email engagement metrics for analytics.

Index

Properties
Methods

Constructor

constructor(prisma: BNestPrismaService)
Parameters :
Name Type Optional
prisma BNestPrismaService No

Methods

Async incrementEmailsSent
incrementEmailsSent(participantId: number, periodStart: Date, periodEnd: Date)

Increment emails_sent for the given participant and reporting period (creates row if missing).

Parameters :
Name Type Optional
participantId number No
periodStart Date No
periodEnd Date No
Returns : Promise<EmailEngagementMetrics>
Private logEmailsSentSnapshot
logEmailsSentSnapshot(row: EmailEngagementMetrics)
Parameters :
Name Type Optional
row EmailEngagementMetrics No
Returns : EmailEngagementMetrics
Async mergeEngagementMetrics
mergeEngagementMetrics(participantId: number, periodStart: Date, periodEnd: Date, patch: Prisma.EmailEngagementMetricsUpdateInput)

Merge partial engagement counters / rates for a period.

Parameters :
Name Type Optional
participantId number No
periodStart Date No
periodEnd Date No
patch Prisma.EmailEngagementMetricsUpdateInput No
Returns : Promise<EmailEngagementMetrics>
Async updateEngagementById
updateEngagementById(id: number, data: Prisma.EmailEngagementMetricsUpdateInput)

Updates an engagement row by primary key (e.g. after batch reconciliation).

BUG-020: updated may be null if middleware / client extensions alter the result — never read updated.emails_sent without checking.

Parameters :
Name Type Optional
id number No
data Prisma.EmailEngagementMetricsUpdateInput No
Returns : Promise<EmailEngagementMetrics | null>
Private Async upsertEngagementMetrics
upsertEngagementMetrics(participantId: number, periodStart: Date, periodEnd: Date, update: Prisma.EmailEngagementMetricsUpdateInput)
Parameters :
Name Type Optional
participantId number No
periodStart Date No
periodEnd Date No
update Prisma.EmailEngagementMetricsUpdateInput No
Returns : Promise<EmailEngagementMetrics>

Properties

Private Readonly logger
Type : unknown
Default value : new Logger(EmailEngagementService.name)
import { BNestPrismaService } from "@bish-nest/core/services/database/prisma/prisma.service";
import { Injectable, Logger } from "@nestjs/common";
import type { EmailEngagementMetrics, Prisma } from "@prisma/client";

/**
 * Persists per-participant email engagement metrics for analytics.
 */
@Injectable()
export class EmailEngagementService {
  private readonly logger = new Logger(EmailEngagementService.name);

  constructor(private readonly prisma: BNestPrismaService) {}

  /**
   * Increment emails_sent for the given participant and reporting period (creates row if missing).
   */
  async incrementEmailsSent(
    participantId: number,
    periodStart: Date,
    periodEnd: Date,
  ): Promise<EmailEngagementMetrics> {
    return this.upsertEngagementMetrics(participantId, periodStart, periodEnd, {
      emails_sent: { increment: 1 },
    });
  }

  /**
   * Merge partial engagement counters / rates for a period.
   */
  async mergeEngagementMetrics(
    participantId: number,
    periodStart: Date,
    periodEnd: Date,
    patch: Prisma.EmailEngagementMetricsUpdateInput,
  ): Promise<EmailEngagementMetrics> {
    return this.upsertEngagementMetrics(participantId, periodStart, periodEnd, patch);
  }

  private async upsertEngagementMetrics(
    participantId: number,
    periodStart: Date,
    periodEnd: Date,
    update: Prisma.EmailEngagementMetricsUpdateInput,
  ): Promise<EmailEngagementMetrics> {
    const createdDefaults: Prisma.EmailEngagementMetricsCreateInput = {
      participant_id: participantId,
      period_start: periodStart,
      period_end: periodEnd,
      emails_sent: 0,
      emails_opened: 0,
      emails_clicked: 0,
      emails_converted: 0,
    };

    const row = await this.prisma.client.emailEngagementMetrics.upsert({
      where: {
        participant_id_period_start_period_end: {
          participant_id: participantId,
          period_start: periodStart,
          period_end: periodEnd,
        },
      },
      create: createdDefaults,
      update,
    });

    return this.logEmailsSentSnapshot(row);
  }

  /**
   * Updates an engagement row by primary key (e.g. after batch reconciliation).
   *
   * BUG-020: `updated` may be null if middleware / client extensions alter the result — never read
   * `updated.emails_sent` without checking.
   */
  async updateEngagementById(
    id: number,
    data: Prisma.EmailEngagementMetricsUpdateInput,
  ): Promise<EmailEngagementMetrics | null> {
    const updated = await this.prisma.client.emailEngagementMetrics.update({
      where: { id },
      data,
    });

    if (updated == null) {
      this.logger.warn(
        `Email engagement update returned null for id=${id}; skipping analytics that read emails_sent.`,
      );
      return null;
    }

    return this.logEmailsSentSnapshot(updated);
  }

  private logEmailsSentSnapshot(row: EmailEngagementMetrics): EmailEngagementMetrics {
    const sent = row.emails_sent;
    this.logger.debug(`Engagement metrics id=${row.id} participant_id=${row.participant_id} emails_sent=${sent}`);
    return row;
  }
}

results matching ""

    No results matching ""