285 lines
9.2 KiB
TypeScript
285 lines
9.2 KiB
TypeScript
/**
|
|
* Email Preferences Helper
|
|
*
|
|
* Handles admin-level and user-level email notification preferences
|
|
* Logic: Email only sent if BOTH admin AND user have it enabled
|
|
*/
|
|
|
|
import { User } from '@models/User';
|
|
import { SYSTEM_CONFIG } from '../config/system.config';
|
|
import { getConfigValue } from '../services/configReader.service';
|
|
import logger from '../utils/logger';
|
|
|
|
/**
|
|
* Email notification types that can be controlled
|
|
*/
|
|
export enum EmailNotificationType {
|
|
REQUEST_CREATED = 'request_created',
|
|
APPROVAL_REQUEST = 'approval_request',
|
|
REQUEST_APPROVED = 'request_approved',
|
|
REQUEST_REJECTED = 'request_rejected',
|
|
TAT_REMINDER = 'tat_reminder', // Generic TAT reminder (any threshold)
|
|
TAT_BREACHED = 'tat_breached', // 100% breach
|
|
WORKFLOW_RESUMED = 'workflow_resumed',
|
|
REQUEST_CLOSED = 'request_closed',
|
|
WORKFLOW_PAUSED = 'workflow_paused',
|
|
PARTICIPANT_ADDED = 'participant_added',
|
|
SPECTATOR_ADDED = 'spectator_added',
|
|
APPROVER_SKIPPED = 'approver_skipped',
|
|
// Dealer Claim Specific
|
|
DEALER_PROPOSAL_SUBMITTED = 'dealer_proposal_submitted',
|
|
ACTIVITY_CREATED = 'activity_created',
|
|
COMPLETION_DOCUMENTS_SUBMITTED = 'completion_documents_submitted',
|
|
EINVOICE_GENERATED = 'einvoice_generated',
|
|
CREDIT_NOTE_SENT = 'credit_note_sent'
|
|
}
|
|
|
|
/**
|
|
* Check if email should be sent based on admin and user preferences
|
|
*
|
|
* @param userId - User ID to check preferences for
|
|
* @param emailType - Type of email notification
|
|
* @returns true if email should be sent, false otherwise
|
|
*/
|
|
export async function shouldSendEmail(
|
|
userId: string,
|
|
emailType: EmailNotificationType
|
|
): Promise<boolean> {
|
|
try {
|
|
// Step 1: Check admin-level configuration (System Config)
|
|
const adminEmailEnabled = await isAdminEmailEnabled(emailType);
|
|
|
|
if (!adminEmailEnabled) {
|
|
logger.info(`[Email] Admin disabled emails for ${emailType} - skipping`);
|
|
return false;
|
|
}
|
|
|
|
// Step 2: Check user-level preferences
|
|
const userEmailEnabled = await isUserEmailEnabled(userId, emailType);
|
|
|
|
if (!userEmailEnabled) {
|
|
logger.info(`[Email] User ${userId} disabled emails for ${emailType} - skipping`);
|
|
return false;
|
|
}
|
|
|
|
// Both admin AND user have enabled - send email
|
|
logger.info(`[Email] Email enabled for user ${userId}, type: ${emailType}`);
|
|
return true;
|
|
|
|
} catch (error) {
|
|
logger.error(`[Email] Error checking email preferences for ${userId}:`, error);
|
|
// On error, default to NOT sending email (safe default)
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if admin has enabled emails globally
|
|
* Checks database configuration first, then falls back to environment variable
|
|
*/
|
|
async function isAdminEmailEnabled(emailType: EmailNotificationType): Promise<boolean> {
|
|
try {
|
|
// Step 1: Check database configuration (admin panel setting)
|
|
const dbConfigValue = await getConfigValue('ENABLE_EMAIL_NOTIFICATIONS', '');
|
|
|
|
if (dbConfigValue) {
|
|
// Parse database value (it's stored as string 'true' or 'false')
|
|
const dbEnabled = dbConfigValue.toLowerCase() === 'true';
|
|
|
|
if (!dbEnabled) {
|
|
logger.info('[Email] Admin has disabled email notifications globally (from database config)');
|
|
return false;
|
|
}
|
|
|
|
logger.debug('[Email] Email notifications enabled (from database config)');
|
|
return true;
|
|
}
|
|
|
|
// Step 2: Fall back to environment variable if database config not found
|
|
const envEnabled = SYSTEM_CONFIG.NOTIFICATIONS.ENABLE_EMAIL;
|
|
|
|
if (!envEnabled) {
|
|
logger.info('[Email] Admin has disabled email notifications globally (from environment variable)');
|
|
return false;
|
|
}
|
|
|
|
logger.debug('[Email] Email notifications enabled (from environment variable)');
|
|
return true;
|
|
} catch (error) {
|
|
logger.error('[Email] Error checking admin email configuration, defaulting to enabled:', error);
|
|
// On error, default to enabled (safe default to avoid blocking notifications)
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if user has enabled emails
|
|
* Uses existing User.emailNotificationsEnabled field
|
|
*/
|
|
async function isUserEmailEnabled(userId: string, emailType: EmailNotificationType): Promise<boolean> {
|
|
try {
|
|
// Fetch user and check emailNotificationsEnabled field
|
|
const user = await User.findByPk(userId, {
|
|
attributes: ['userId', 'emailNotificationsEnabled']
|
|
});
|
|
|
|
if (!user) {
|
|
logger.warn(`[Email] User ${userId} not found - defaulting to enabled`);
|
|
return true;
|
|
}
|
|
|
|
// Check user's global email notification setting
|
|
const enabled = (user as any).emailNotificationsEnabled !== false;
|
|
|
|
if (!enabled) {
|
|
logger.info(`[Email] User ${userId} has disabled email notifications globally`);
|
|
}
|
|
|
|
return enabled;
|
|
} catch (error) {
|
|
logger.warn('[Email] Error checking user email preference (defaulting to enabled):', error);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if in-app notification should be sent
|
|
* Uses existing User.inAppNotificationsEnabled field
|
|
*/
|
|
export async function shouldSendInAppNotification(
|
|
userId: string,
|
|
notificationType: string
|
|
): Promise<boolean> {
|
|
try {
|
|
// Check admin config first (if SystemConfig model exists)
|
|
const adminEnabled = await isAdminInAppEnabled(notificationType);
|
|
|
|
if (!adminEnabled) {
|
|
return false;
|
|
}
|
|
|
|
// Fetch user and check inAppNotificationsEnabled field
|
|
const user = await User.findByPk(userId, {
|
|
attributes: ['userId', 'inAppNotificationsEnabled']
|
|
});
|
|
|
|
if (!user) {
|
|
logger.warn(`[Notification] User ${userId} not found - defaulting to enabled`);
|
|
return true;
|
|
}
|
|
|
|
// Check user's global in-app notification setting
|
|
const enabled = (user as any).inAppNotificationsEnabled !== false;
|
|
|
|
if (!enabled) {
|
|
logger.info(`[Notification] User ${userId} has disabled in-app notifications globally`);
|
|
}
|
|
|
|
return enabled;
|
|
} catch (error) {
|
|
logger.warn('[Notification] Error checking in-app notification preference (defaulting to enabled):', error);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if admin has enabled in-app notifications globally
|
|
* Checks database configuration first, then falls back to environment variable
|
|
*/
|
|
async function isAdminInAppEnabled(notificationType: string): Promise<boolean> {
|
|
try {
|
|
// Step 1: Check database configuration (admin panel setting)
|
|
const dbConfigValue = await getConfigValue('ENABLE_IN_APP_NOTIFICATIONS', '');
|
|
|
|
if (dbConfigValue) {
|
|
// Parse database value (it's stored as string 'true' or 'false')
|
|
const dbEnabled = dbConfigValue.toLowerCase() === 'true';
|
|
|
|
if (!dbEnabled) {
|
|
logger.info('[Notification] Admin has disabled in-app notifications globally (from database config)');
|
|
return false;
|
|
}
|
|
|
|
logger.debug('[Notification] In-app notifications enabled (from database config)');
|
|
return true;
|
|
}
|
|
|
|
// Step 2: Fall back to environment variable if database config not found
|
|
const envValue = process.env.ENABLE_IN_APP_NOTIFICATIONS;
|
|
if (envValue !== undefined) {
|
|
const envEnabled = envValue.toLowerCase() === 'true';
|
|
if (!envEnabled) {
|
|
logger.info('[Notification] Admin has disabled in-app notifications globally (from environment variable)');
|
|
return false;
|
|
}
|
|
logger.debug('[Notification] In-app notifications enabled (from environment variable)');
|
|
return true;
|
|
}
|
|
|
|
// Step 3: Final fallback to system config (defaults to true)
|
|
const adminInAppEnabled = SYSTEM_CONFIG.NOTIFICATIONS.ENABLE_IN_APP;
|
|
|
|
if (!adminInAppEnabled) {
|
|
logger.info('[Notification] Admin has disabled in-app notifications globally (from system config)');
|
|
return false;
|
|
}
|
|
|
|
logger.debug('[Notification] In-app notifications enabled (from system config default)');
|
|
return true;
|
|
} catch (error) {
|
|
logger.error('[Notification] Error checking admin in-app notification configuration, defaulting to enabled:', error);
|
|
// On error, default to enabled (safe default to avoid blocking notifications)
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Batch check for multiple users
|
|
* Returns array of user IDs who should receive the email
|
|
*/
|
|
export async function filterUsersForEmail(
|
|
userIds: string[],
|
|
emailType: EmailNotificationType
|
|
): Promise<string[]> {
|
|
const enabledUsers: string[] = [];
|
|
|
|
for (const userId of userIds) {
|
|
const shouldSend = await shouldSendEmail(userId, emailType);
|
|
if (shouldSend) {
|
|
enabledUsers.push(userId);
|
|
}
|
|
}
|
|
|
|
return enabledUsers;
|
|
}
|
|
|
|
/**
|
|
* Critical emails that should ALWAYS be sent (override preferences)
|
|
* These are too important to be disabled
|
|
*/
|
|
export const CRITICAL_EMAILS = [
|
|
EmailNotificationType.REQUEST_REJECTED,
|
|
EmailNotificationType.TAT_BREACHED
|
|
];
|
|
|
|
/**
|
|
* Check if email should be sent, with critical email override
|
|
*/
|
|
export async function shouldSendEmailWithOverride(
|
|
userId: string,
|
|
emailType: EmailNotificationType
|
|
): Promise<boolean> {
|
|
// Critical emails always sent (override user preference)
|
|
if (CRITICAL_EMAILS.includes(emailType)) {
|
|
const adminEnabled = await isAdminEmailEnabled(emailType);
|
|
if (adminEnabled) {
|
|
logger.info(`[Email] Critical email ${emailType} - sending despite user preference`);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Non-critical emails - check both admin and user preferences
|
|
return await shouldSendEmail(userId, emailType);
|
|
}
|
|
|