File

apps/recallassess/recallassess-api/src/api/shared/dynamic-mail-fields/dynamic-mail-fields.service.ts

Description

Dynamic Mail Fields Service

Provides template variables for email templates from Admin Settings (SystemSetting). Values can be configured in Admin PWA under System Settings.

Supported variables:

  • mail.current_year: Auto-updated from system date
  • mail.company_name: From admin settings (fallback: company from context or default)
  • mail.domain_name: From admin settings (fallback: hostname from required FRONTEND_URL, else default)
  • mail.footer_text: From admin settings

Index

Properties
Methods

Constructor

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

Methods

Private Async getCachedSettings
getCachedSettings()
Returns : Promise<literal type>
Private getDomainFromEnvironment
getDomainFromEnvironment()
Returns : string | null
Async getDynamicMailVariables
getDynamicMailVariables(options?: literal type)

Get dynamic mail variables for template replacement. Use these with {{variable.name}} placeholders in email templates.

Parameters :
Name Type Optional
options literal type Yes
Returns : Promise<Record<string, string>>
invalidateCache
invalidateCache()

Invalidate cache (call after admin updates settings)

Returns : void

Properties

Private cache
Type : literal type | null
Default value : null
Private Readonly CACHE_TTL_MS
Type : number
Default value : 60_000
Private Readonly logger
Type : unknown
Default value : new Logger(DynamicMailFieldsService.name)
import { requireEnv } from "@bish-nest/core";
import { BNestPrismaService } from "@bish-nest/core/services";
import { Injectable, Logger } from "@nestjs/common";

/**
 * SystemSetting keys for dynamic mail fields (configurable via Admin Settings)
 */
export const DYNAMIC_MAIL_SETTING_KEYS = {
  COMPANY_NAME: "mail.company_name",
  DOMAIN_NAME: "mail.domain_name",
  FOOTER_TEXT: "mail.footer_text",
  CONTACT_EMAIL: "mail.contact_email",
} as const;

/**
 * Default values when SystemSetting is not configured
 */
const DEFAULTS = {
  COMPANY_NAME: "Recall",
  DOMAIN_NAME: "recallsolutions.ai",
  FOOTER_TEXT: "Development Through Action and Reflection",
  CONTACT_EMAIL: "enquiries@recallsolutions.ai",
} as const;

/**
 * Dynamic Mail Fields Service
 *
 * Provides template variables for email templates from Admin Settings (SystemSetting).
 * Values can be configured in Admin PWA under System Settings.
 *
 * Supported variables:
 * - mail.current_year: Auto-updated from system date
 * - mail.company_name: From admin settings (fallback: company from context or default)
 * - mail.domain_name: From admin settings (fallback: hostname from required `FRONTEND_URL`, else default)
 * - mail.footer_text: From admin settings
 */
@Injectable()
export class DynamicMailFieldsService {
  private readonly logger = new Logger(DynamicMailFieldsService.name);
  private cache: {
    companyName: string | null;
    domainName: string | null;
    footerText: string | null;
    contactEmail: string | null;
    fetchedAt: Date;
  } | null = null;
  private readonly CACHE_TTL_MS = 60_000; // 1 minute

  constructor(private readonly prisma: BNestPrismaService) {}

  /**
   * Get dynamic mail variables for template replacement.
   * Use these with {{variable.name}} placeholders in email templates.
   *
   * @param options.companyNameFallback - Fallback when setting not configured (e.g. Company.name from account)
   */
  async getDynamicMailVariables(options?: {
    companyNameFallback?: string;
  }): Promise<Record<string, string>> {
    const settings = await this.getCachedSettings();

    const domainFromEnv = this.getDomainFromEnvironment();
    const companyName =
      settings.companyName ||
      options?.companyNameFallback ||
      DEFAULTS.COMPANY_NAME;
    const domainName = settings.domainName || domainFromEnv || DEFAULTS.DOMAIN_NAME;
    const footerText = settings.footerText || DEFAULTS.FOOTER_TEXT;
    const currentYear = new Date().getFullYear().toString();

    const domainUrl = domainName.startsWith("http")
      ? domainName
      : `https://${domainName}`;
    const domainHost = domainName.replace(/^https?:\/\//, "").split("/")[0] || domainName;
    const contactEmail =
      settings.contactEmail || `enquiries@${domainHost}` || DEFAULTS.CONTACT_EMAIL;

    return {
      "mail.current_year": currentYear,
      "mail.company_name": companyName,
      "mail.domain_name": domainName,
      "mail.footer_text": footerText,
      "mail.domain_url": domainUrl,
      "mail.contact_email": contactEmail,
    };
  }

  /**
   * Invalidate cache (call after admin updates settings)
   */
  invalidateCache(): void {
    this.cache = null;
  }

  private async getCachedSettings(): Promise<{
    companyName: string | null;
    domainName: string | null;
    footerText: string | null;
    contactEmail: string | null;
  }> {
    const now = new Date();
    if (
      this.cache &&
      now.getTime() - this.cache.fetchedAt.getTime() < this.CACHE_TTL_MS
    ) {
      return this.cache;
    }

    const settings = await this.prisma.client.systemSetting.findMany({
      where: {
        key: {
          in: [
            DYNAMIC_MAIL_SETTING_KEYS.COMPANY_NAME,
            DYNAMIC_MAIL_SETTING_KEYS.DOMAIN_NAME,
            DYNAMIC_MAIL_SETTING_KEYS.FOOTER_TEXT,
            DYNAMIC_MAIL_SETTING_KEYS.CONTACT_EMAIL,
          ],
        },
      },
    });

    const byKey = Object.fromEntries(
      settings.map((s) => [s.key, s.value?.trim() || ""])
    );

    const companyName =
      byKey[DYNAMIC_MAIL_SETTING_KEYS.COMPANY_NAME] || null;
    const domainName = byKey[DYNAMIC_MAIL_SETTING_KEYS.DOMAIN_NAME] || null;
    const footerText = byKey[DYNAMIC_MAIL_SETTING_KEYS.FOOTER_TEXT] || null;
    const contactEmail = byKey[DYNAMIC_MAIL_SETTING_KEYS.CONTACT_EMAIL] || null;

    this.cache = {
      companyName: companyName || null,
      domainName: domainName || null,
      footerText: footerText || null,
      contactEmail: contactEmail || null,
      fetchedAt: now,
    };

    return this.cache;
  }

  private getDomainFromEnvironment(): string | null {
    const url = requireEnv("FRONTEND_URL");
    try {
      const parsed = new URL(url);
      return parsed.hostname || null;
    } catch {
      return null;
    }
  }
}

results matching ""

    No results matching ""