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;
}