Re_Backend/src/services/userEnrichment.service.ts

208 lines
6.1 KiB
TypeScript

/**
* User Enrichment Service
*
* Handles automatic user lookup/creation and data enrichment for workflow creation
*/
import { User } from '@models/User';
import logger from '@utils/logger';
import { UserService } from './user.service';
const userService = new UserService();
interface SimplifiedApprovalLevel {
email: string;
tatHours: number;
isFinalApprover?: boolean;
levelNumber?: number;
levelName?: string;
approverId?: string;
approverEmail?: string;
approverName?: string;
}
interface EnrichedApprovalLevel {
levelNumber: number;
levelName: string;
approverId: string;
approverEmail: string;
approverName: string;
tatHours: number;
isFinalApprover: boolean;
}
interface SimplifiedSpectator {
email: string;
userId?: string;
userEmail?: string;
userName?: string;
}
interface EnrichedSpectator {
userId: string;
userEmail: string;
userName: string;
participantType: 'SPECTATOR';
canComment: boolean;
canViewDocuments: boolean;
canDownloadDocuments: boolean;
notificationEnabled: boolean;
}
/**
* Enrich approval levels with user data from database/AD
* @param approvalLevels - Simplified approval levels (only email + tatHours required)
* @returns Enriched approval levels with full user data
*/
export async function enrichApprovalLevels(
approvalLevels: SimplifiedApprovalLevel[]
): Promise<EnrichedApprovalLevel[]> {
const enriched: EnrichedApprovalLevel[] = [];
const processedEmails = new Set<string>();
for (let i = 0; i < approvalLevels.length; i++) {
const level = approvalLevels[i];
const email = level.email.toLowerCase();
// Check for duplicate emails
if (processedEmails.has(email)) {
throw new Error(`Duplicate approver email found: ${email}. Each approver must have a unique email.`);
}
processedEmails.add(email);
try {
// Find or create user from AD
let user = await User.findOne({ where: { email } });
if (!user) {
logger.info(`[UserEnrichment] User not found in DB, attempting to sync from AD: ${email}`);
// Try to fetch and create user from AD
try {
user = await userService.ensureUserExists({ email }) as any;
} catch (adError: any) {
logger.error(`[UserEnrichment] Failed to sync user from AD: ${email}`, adError);
throw new Error(`Approver email '${email}' not found in organization directory. Please verify the email address.`);
}
}
const userId = (user as any).userId;
const displayName = (user as any).displayName || (user as any).email;
const designation = (user as any).designation || (user as any).jobTitle;
const department = (user as any).department;
// Auto-generate level name
let levelName = level.levelName;
if (!levelName) {
if (designation) {
levelName = `${designation} Approval`;
} else if (department) {
levelName = `${department} Approval`;
} else {
levelName = `Level ${i + 1} Approval`;
}
}
// Auto-detect final approver (last level)
const isFinalApprover = level.isFinalApprover !== undefined
? level.isFinalApprover
: (i === approvalLevels.length - 1);
enriched.push({
levelNumber: level.levelNumber || (i + 1),
levelName,
approverId: userId,
approverEmail: email,
approverName: displayName,
tatHours: level.tatHours,
isFinalApprover,
});
logger.info(`[UserEnrichment] Enriched approval level ${i + 1}: ${email} -> ${displayName} (${levelName})`);
} catch (error: any) {
logger.error(`[UserEnrichment] Failed to enrich approval level for ${email}:`, error);
throw error;
}
}
return enriched;
}
/**
* Enrich spectators with user data from database/AD
* @param spectators - Simplified spectators (only email required)
* @returns Enriched spectators with full user data
*/
export async function enrichSpectators(
spectators: SimplifiedSpectator[]
): Promise<EnrichedSpectator[]> {
if (!spectators || spectators.length === 0) {
return [];
}
const enriched: EnrichedSpectator[] = [];
const processedEmails = new Set<string>();
for (const spectator of spectators) {
const email = spectator.email.toLowerCase();
// Check for duplicate emails
if (processedEmails.has(email)) {
throw new Error(`Duplicate spectator email found: ${email}. Each spectator must have a unique email.`);
}
processedEmails.add(email);
try {
// Find or create user from AD
let user = await User.findOne({ where: { email } });
if (!user) {
logger.info(`[UserEnrichment] User not found in DB, attempting to sync from AD: ${email}`);
try {
user = await userService.ensureUserExists({ email }) as any;
} catch (adError: any) {
logger.error(`[UserEnrichment] Failed to sync user from AD: ${email}`, adError);
throw new Error(`Spectator email '${email}' not found in organization directory. Please verify the email address.`);
}
}
const userId = (user as any).userId;
const displayName = (user as any).displayName || (user as any).email;
enriched.push({
userId,
userEmail: email,
userName: displayName,
participantType: 'SPECTATOR',
canComment: true,
canViewDocuments: true,
canDownloadDocuments: false,
notificationEnabled: true,
});
logger.info(`[UserEnrichment] Enriched spectator: ${email} -> ${displayName}`);
} catch (error: any) {
logger.error(`[UserEnrichment] Failed to enrich spectator ${email}:`, error);
throw error;
}
}
return enriched;
}
/**
* Validate and ensure initiator exists in database
* @param initiatorId - User ID of the initiator
* @returns User object if valid
* @throws Error if initiator not found or invalid
*/
export async function validateInitiator(initiatorId: string): Promise<any> {
const user = await User.findByPk(initiatorId);
if (!user) {
throw new Error(`Invalid initiator: User with ID '${initiatorId}' not found. Please ensure you are logged in with a valid account.`);
}
return user;
}