File

apps/recallassess/recallassess-api/src/api/shared/email-category/email-category.util.ts

Index

Properties

Properties

category
category: EmailCategory
Type : EmailCategory
description
description: string
Type : string
Optional
icon
icon: string
Type : string
label
label: string
Type : string
templateKeys
templateKeys: string[]
Type : string[]
templateTypes
templateTypes: string[]
Type : string[]
Optional
import { Prisma } from "@prisma/client";

/**
 * Email Category Utility
 * Maps email templates to categories for better UX organization
 */

export enum EmailCategory {
  ALL = "all",
  DIGEST = "digest",
  COURSE = "course",
  REMINDERS = "reminders",
  SYSTEM = "system",
  OTHER = "other",
}

export interface EmailCategoryConfig {
  category: EmailCategory;
  label: string;
  icon: string;
  templateKeys: string[];
  templateTypes?: string[];
  description?: string;
}

export const EMAIL_CATEGORY_CONFIG: EmailCategoryConfig[] = [
  {
    category: EmailCategory.DIGEST,
    label: "Digest Emails",
    icon: "pi pi-envelope",
    description: "Daily, weekly, and monthly digest emails",
    templateKeys: [
      "digest.daily",
      "digest.weekly",
      "admin.digest.weekly",
    ],
  },
  {
    category: EmailCategory.COURSE,
    label: "Course Emails",
    icon: "pi pi-book",
    description: "Course-related emails including BAT, eLearning, and assignments",
    templateKeys: [
      "course.learning.group.invitation",
      "course.pre.bat.completion",
      "course.elearning.completion",
      "course.knowledge.review",
      "course.knowledge.review.completion",
      "course.completion",
      "course.hundred.day.journey.email1",
      "course.hundred.day.journey.email2",
      "course.hundred.day.journey.email3",
      "course.hundred.day.journey.email4",
      "admin.flash.course.completed.user",
      "admin.flash.course.completed",
      "admin.flash.pre.bat.completed",
      "admin.flash.elearning.completed",
      "admin.flash.post.bat.completed",
    ],
    templateTypes: ["COURSE_ASSIGNMENT", "KNOWLEDGE_REVIEW"],
  },
  {
    category: EmailCategory.REMINDERS,
    label: "Activity Reminders",
    icon: "pi pi-bell",
    description: "Reminders for inactivity, progress, deadlines, and assessments",
    templateKeys: [
      "reminder.inactivity.day3",
      "reminder.inactivity.day7",
      "reminder.inactivity.day14",
      "reminder.inactivity.day30",
      "reminder.course.stuck",
      "reminder.pre.bat.due",
      "reminder.pre.bat.overdue",
      "reminder.post.bat.available",
      "reminder.post.bat",
      "reminder.course.deadline.7days",
      "reminder.course.deadline.3days",
      "reminder.course.deadline.1day",
      "admin.inactivity.weekly",
      "admin.inactivity.alert.day7",
      "admin.inactivity.alert.day14",
      "admin.inactivity.alert.day30",
    ],
    templateTypes: ["INACTIVITY_ALERT"],
  },
  {
    category: EmailCategory.SYSTEM,
    label: "System Emails",
    icon: "pi pi-cog",
    description: "System notifications, account management, and security emails",
    templateKeys: [
      "account.welcome.email",
      "account.password.reset",
      "account.email.verification",
      "subscription.unsubscribe.confirmation",
      "subscription.resubscribe.welcome",
    ],
    templateTypes: [
      "SYSTEM_NOTIFICATION",
      "WELCOME_EMAIL",
      "PASSWORD_RESET",
      "EMAIL_VERIFICATION",
      "ACCOUNT_ACTIVATED",
    ],
  },
  {
    category: EmailCategory.OTHER,
    label: "Other Emails",
    icon: "pi pi-ellipsis-h",
    description: "Reports, replacements, and custom emails",
    templateKeys: [
      "admin.license.fully.allocated",
      "admin.license.warning",
      "admin.license.critical",
    ],
    templateTypes: ["REPORT_DELIVERY", "PARTICIPANT_REPLACEMENT"],
  },
];

/**
 * Get category for an email based on template key or type
 */
export function getEmailCategory(
  templateKey?: string | null,
  templateType?: string | null,
): EmailCategory {
  if (!templateKey && !templateType) {
    return EmailCategory.OTHER;
  }

  // Check by template key first (partial match for flexibility)
  if (templateKey) {
    const upperKey = templateKey.toUpperCase();
    for (const config of EMAIL_CATEGORY_CONFIG) {
      if (
        config.templateKeys.some((key) => upperKey.includes(key.toUpperCase()))
      ) {
        return config.category;
      }
    }
  }

  // Check by template type
  if (templateType) {
    const upperType = templateType.toUpperCase();
    for (const config of EMAIL_CATEGORY_CONFIG) {
      if (
        config.templateTypes?.some(
          (type) => type.toUpperCase() === upperType,
        )
      ) {
        return config.category;
      }
    }
  }

  return EmailCategory.OTHER;
}

/**
 * Get all template keys for a category
 */
export function getTemplateKeysForCategory(
  category: EmailCategory,
): string[] {
  if (category === EmailCategory.ALL) {
    return []; // Empty means all
  }

  const config = EMAIL_CATEGORY_CONFIG.find(
    (c) => c.category === category,
  );

  return config?.templateKeys || [];
}

/**
 * Get category config
 */
export function getCategoryConfig(
  category: EmailCategory,
): EmailCategoryConfig | undefined {
  return EMAIL_CATEGORY_CONFIG.find((c) => c.category === category);
}

/**
 * Build WHERE clause for Prisma query based on category
 */
export function buildCategoryWhereClause(
  category: EmailCategory,
): Prisma.EmailLogWhereInput | undefined {
  if (category === EmailCategory.ALL) {
    return undefined; // No filter for "all"
  }

  const config = getCategoryConfig(category);
  if (!config) {
    return undefined;
  }

  const templateKeys = config.templateKeys || [];
  const templateTypes = config.templateTypes || [];

  const orConditions: Prisma.EmailLogWhereInput[] = [];

  // Filter by template keys
  if (templateKeys.length > 0) {
    // Use case-insensitive contains for all template keys
    // This matches the logic in getEmailCategory which does case-insensitive partial matching
    // Also normalize the key (replace underscores with dots) to match database format
    templateKeys.forEach((key) => {
      const normalizedKey = key.replace(/_/g, '.').toLowerCase();
      const originalKey = key.toLowerCase();
      
      // Try both normalized (dots) and original (underscores) formats
      // Prisma automatically excludes null relations when filtering by nested fields
      orConditions.push({
        emailTemplate: {
          template_key: {
            contains: normalizedKey,
            mode: "insensitive",
          },
        },
      });
      
      // Also try with original format in case some use underscores
      if (normalizedKey !== originalKey) {
        orConditions.push({
          emailTemplate: {
            template_key: {
              contains: originalKey,
              mode: "insensitive",
            },
          },
        });
      }
    });
  }

  // Filter by template types
  if (templateTypes.length > 0) {
    orConditions.push({
      emailTemplate: {
        template_type: {
          in: templateTypes as any,
        },
      },
    });
  }

  return orConditions.length > 0 ? { OR: orConditions } : undefined;
}

results matching ""

    No results matching ""