978 lines
28 KiB
TypeScript
978 lines
28 KiB
TypeScript
import { Request, Response } from 'express';
|
|
import { HolidayModel as Holiday, HolidayType } from '../models/mongoose/Holiday.schema';
|
|
import { holidayMongoService as holidayService } from '../services/holiday.service';
|
|
import { activityTypeService } from '../services/activityType.service';
|
|
import { adminConfigMongoService } from '../services/adminConfig.service';
|
|
import logger from '../utils/logger';
|
|
import dayjs from 'dayjs';
|
|
import { initializeHolidaysCache, clearWorkingHoursCache } from '../utils/tatTimeUtils';
|
|
import { clearConfigCache } from '../services/configReader.service';
|
|
import { UserModel as User, IUser } from '../models/mongoose/User.schema';
|
|
import { UserRole } from '../types/user.types';
|
|
|
|
/**
|
|
* Get all holidays (with optional year filter)
|
|
*/
|
|
export const getAllHolidays = async (req: Request, res: Response): Promise<void> => {
|
|
try {
|
|
const { year } = req.query;
|
|
const yearNum = year ? parseInt(year as string) : undefined;
|
|
|
|
const holidays = await holidayService.getAllActiveHolidays(yearNum);
|
|
|
|
// Format response to match legacy structure
|
|
const formattedHolidays = holidays.map(mapToLegacyHoliday);
|
|
|
|
res.json({
|
|
success: true,
|
|
data: formattedHolidays,
|
|
count: formattedHolidays.length
|
|
});
|
|
} catch (error) {
|
|
logger.error('[Admin] Error fetching holidays:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Failed to fetch holidays'
|
|
});
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Get holiday calendar for a specific year
|
|
*/
|
|
export const getHolidayCalendar = async (req: Request, res: Response): Promise<void> => {
|
|
try {
|
|
const { year } = req.params;
|
|
const yearNum = parseInt(year);
|
|
|
|
if (isNaN(yearNum) || yearNum < 2000 || yearNum > 2100) {
|
|
res.status(400).json({
|
|
success: false,
|
|
error: 'Invalid year'
|
|
});
|
|
return;
|
|
}
|
|
|
|
// Use getAllActiveHolidays to get full docs, then filter by year in memory or update service
|
|
// Service has getHolidayCalendar(year) which returns partial objects.
|
|
// Better to use getAllActiveHolidays(year) and map ourselves.
|
|
const holidays = await holidayService.getAllActiveHolidays(yearNum);
|
|
const formattedHolidays = holidays.map(mapToLegacyHoliday);
|
|
|
|
res.json({
|
|
success: true,
|
|
year: yearNum,
|
|
holidays: formattedHolidays,
|
|
count: formattedHolidays.length
|
|
});
|
|
} catch (error) {
|
|
logger.error('[Admin] Error fetching holiday calendar:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Failed to fetch holiday calendar'
|
|
});
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Create a new holiday
|
|
*/
|
|
export const createHoliday = async (req: Request, res: Response): Promise<void> => {
|
|
try {
|
|
const userId = req.user?.userId;
|
|
if (!userId) {
|
|
res.status(401).json({
|
|
success: false,
|
|
error: 'User not authenticated'
|
|
});
|
|
return;
|
|
}
|
|
|
|
const {
|
|
holidayDate,
|
|
holidayName,
|
|
description,
|
|
holidayType,
|
|
isRecurring,
|
|
recurrenceRule,
|
|
appliesToDepartments,
|
|
appliesToLocations
|
|
} = req.body;
|
|
|
|
// Validate required fields
|
|
if (!holidayDate || !holidayName) {
|
|
res.status(400).json({
|
|
success: false,
|
|
error: 'Holiday date and name are required'
|
|
});
|
|
return;
|
|
}
|
|
|
|
const holiday = await holidayService.createHoliday({
|
|
holidayDate,
|
|
holidayName,
|
|
holidayType: (holidayType as any) || HolidayType.ORGANIZATIONAL,
|
|
year: new Date(holidayDate).getFullYear(),
|
|
appliesToDepartments,
|
|
appliesToLocations,
|
|
description,
|
|
isRecurring,
|
|
recurrenceRule,
|
|
createdBy: userId
|
|
});
|
|
|
|
// Reload holidays cache
|
|
await initializeHolidaysCache();
|
|
|
|
// Format response to match legacy structure
|
|
const legacyResponse = mapToLegacyHoliday(holiday);
|
|
|
|
res.status(201).json({
|
|
success: true,
|
|
message: 'Holiday created successfully',
|
|
data: [legacyResponse] // Returning array as requested
|
|
});
|
|
} catch (error: any) {
|
|
logger.error('[Admin] Error creating holiday:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: error.message || 'Failed to create holiday'
|
|
});
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Helper to map Mongoose document to Legacy JSON format
|
|
*/
|
|
const mapToLegacyHoliday = (holiday: any) => ({
|
|
holidayId: holiday._id,
|
|
holidayDate: dayjs(holiday.holidayDate).format('YYYY-MM-DD'),
|
|
holidayName: holiday.holidayName,
|
|
description: holiday.description || null,
|
|
isRecurring: holiday.isRecurring || false,
|
|
recurrenceRule: holiday.recurrenceRule || null,
|
|
holidayType: holiday.holidayType,
|
|
isActive: holiday.isActive !== undefined ? holiday.isActive : true,
|
|
appliesToDepartments: (holiday.appliesToDepartments && holiday.appliesToDepartments.length > 0) ? holiday.appliesToDepartments : null,
|
|
appliesToLocations: (holiday.appliesToLocations && holiday.appliesToLocations.length > 0) ? holiday.appliesToLocations : null,
|
|
createdBy: holiday.createdBy || null,
|
|
updatedBy: holiday.updatedBy || null,
|
|
createdAt: holiday.createdAt,
|
|
updatedAt: holiday.updatedAt,
|
|
created_at: holiday.createdAt,
|
|
updated_at: holiday.updatedAt
|
|
});
|
|
|
|
/**
|
|
* Update a holiday
|
|
*/
|
|
export const updateHoliday = async (req: Request, res: Response): Promise<void> => {
|
|
try {
|
|
const userId = req.user?.userId;
|
|
if (!userId) {
|
|
res.status(401).json({
|
|
success: false,
|
|
error: 'User not authenticated'
|
|
});
|
|
return;
|
|
}
|
|
|
|
const { holidayId } = req.params;
|
|
const updates = req.body;
|
|
|
|
const holiday = await holidayService.updateHoliday(holidayId, updates);
|
|
|
|
if (!holiday) {
|
|
res.status(404).json({
|
|
success: false,
|
|
error: 'Holiday not found'
|
|
});
|
|
return;
|
|
}
|
|
|
|
// Reload holidays cache
|
|
await initializeHolidaysCache();
|
|
|
|
res.json({
|
|
success: true,
|
|
message: 'Holiday updated successfully',
|
|
data: [mapToLegacyHoliday(holiday)] // Returning array for consistency
|
|
});
|
|
} catch (error: any) {
|
|
logger.error('[Admin] Error updating holiday:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: error.message || 'Failed to update holiday'
|
|
});
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Delete (deactivate) a holiday
|
|
*/
|
|
export const deleteHoliday = async (req: Request, res: Response): Promise<void> => {
|
|
try {
|
|
const { holidayId } = req.params;
|
|
|
|
await holidayService.deleteHoliday(holidayId);
|
|
|
|
// Reload holidays cache
|
|
await initializeHolidaysCache();
|
|
|
|
res.json({
|
|
success: true,
|
|
message: 'Holiday deleted successfully'
|
|
});
|
|
} catch (error: any) {
|
|
logger.error('[Admin] Error deleting holiday:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: error.message || 'Failed to delete holiday'
|
|
});
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Bulk import holidays from CSV/JSON
|
|
*/
|
|
export const bulkImportHolidays = async (req: Request, res: Response): Promise<void> => {
|
|
try {
|
|
const userId = req.user?.userId;
|
|
if (!userId) {
|
|
res.status(401).json({
|
|
success: false,
|
|
error: 'User not authenticated'
|
|
});
|
|
return;
|
|
}
|
|
|
|
const { holidays } = req.body;
|
|
|
|
if (!Array.isArray(holidays) || holidays.length === 0) {
|
|
res.status(400).json({
|
|
success: false,
|
|
error: 'Holidays array is required'
|
|
});
|
|
return;
|
|
}
|
|
|
|
const result = await holidayService.bulkImportHolidays(holidays);
|
|
|
|
// Reload holidays cache
|
|
await initializeHolidaysCache();
|
|
|
|
res.json({
|
|
success: true,
|
|
message: `Imported ${result.success} holidays, ${result.failed} failed`,
|
|
data: result
|
|
});
|
|
} catch (error: any) {
|
|
logger.error('[Admin] Error bulk importing holidays:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: error.message || 'Failed to import holidays'
|
|
});
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Get public configurations (read-only, non-sensitive)
|
|
* Accessible to all authenticated users
|
|
*/
|
|
export const getPublicConfigurations = async (req: Request, res: Response): Promise<void> => {
|
|
try {
|
|
const { category } = req.query;
|
|
|
|
// Only allow certain categories for public access
|
|
const allowedCategories = ['DOCUMENT_POLICY', 'TAT_SETTINGS', 'WORKFLOW_SHARING', 'SYSTEM_SETTINGS'];
|
|
if (category && !allowedCategories.includes(category as string)) {
|
|
res.status(403).json({
|
|
success: false,
|
|
error: 'Access denied to this configuration category'
|
|
});
|
|
return;
|
|
}
|
|
|
|
const configurations = await adminConfigMongoService.getPublicConfigurations(category as string);
|
|
|
|
res.json({
|
|
success: true,
|
|
data: configurations,
|
|
count: configurations.length
|
|
});
|
|
} catch (error) {
|
|
logger.error('[Admin] Error fetching public configurations:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Failed to fetch configurations'
|
|
});
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Get all admin configurations
|
|
*/
|
|
export const getAllConfigurations = async (req: Request, res: Response): Promise<void> => {
|
|
try {
|
|
const { category } = req.query;
|
|
|
|
const configurations = await adminConfigMongoService.getAllConfigurations(category as string);
|
|
|
|
res.json({
|
|
success: true,
|
|
data: configurations,
|
|
count: configurations.length
|
|
});
|
|
} catch (error) {
|
|
logger.error('[Admin] Error fetching configurations:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Failed to fetch configurations'
|
|
});
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Update a configuration
|
|
*/
|
|
export const updateConfiguration = async (req: Request, res: Response): Promise<void> => {
|
|
try {
|
|
const userId = req.user?.userId;
|
|
if (!userId) {
|
|
res.status(401).json({
|
|
success: false,
|
|
error: 'User not authenticated'
|
|
});
|
|
return;
|
|
}
|
|
|
|
const { configKey } = req.params;
|
|
const { configValue } = req.body;
|
|
|
|
if (configValue === undefined) {
|
|
res.status(400).json({
|
|
success: false,
|
|
error: 'Config value is required'
|
|
});
|
|
return;
|
|
}
|
|
|
|
// Update configuration
|
|
const config = await adminConfigMongoService.updateConfig(configKey, configValue, userId);
|
|
|
|
if (!config) {
|
|
res.status(404).json({
|
|
success: false,
|
|
error: 'Configuration not found or not editable'
|
|
});
|
|
return;
|
|
}
|
|
|
|
// Clear config cache so new values are used immediately
|
|
clearConfigCache();
|
|
|
|
// If working hours config was updated, also clear working hours cache
|
|
const workingHoursKeys = ['WORK_START_HOUR', 'WORK_END_HOUR', 'WORK_START_DAY', 'WORK_END_DAY'];
|
|
if (workingHoursKeys.includes(configKey)) {
|
|
await clearWorkingHoursCache();
|
|
logger.info(`[Admin] Working hours configuration '${configKey}' updated - cache cleared and reloaded`);
|
|
}
|
|
|
|
// If AI config was updated, reinitialize AI service
|
|
const aiConfigKeys = ['AI_ENABLED'];
|
|
if (aiConfigKeys.includes(configKey)) {
|
|
try {
|
|
const { aiService } = require('../services/ai.service');
|
|
await aiService.reinitialize();
|
|
logger.info(`[Admin] AI configuration '${configKey}' updated - AI service reinitialized with ${aiService.getProviderName()}`);
|
|
} catch (error) {
|
|
logger.error(`[Admin] Failed to reinitialize AI service:`, error);
|
|
}
|
|
} else {
|
|
logger.info(`[Admin] Configuration '${configKey}' updated and cache cleared`);
|
|
}
|
|
|
|
res.json({
|
|
success: true,
|
|
message: 'Configuration updated successfully'
|
|
});
|
|
} catch (error: any) {
|
|
logger.error('[Admin] Error updating configuration:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: error.message || 'Failed to update configuration'
|
|
});
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Reset configuration to default value
|
|
*/
|
|
export const resetConfiguration = async (req: Request, res: Response): Promise<void> => {
|
|
try {
|
|
const { configKey } = req.params;
|
|
|
|
const config = await adminConfigMongoService.resetConfig(configKey);
|
|
|
|
if (!config) {
|
|
res.status(404).json({
|
|
success: false,
|
|
error: 'Configuration not found'
|
|
});
|
|
return;
|
|
}
|
|
|
|
// Clear config cache so reset values are used immediately
|
|
clearConfigCache();
|
|
|
|
// If working hours config was reset, also clear working hours cache
|
|
const workingHoursKeys = ['WORK_START_HOUR', 'WORK_END_HOUR', 'WORK_START_DAY', 'WORK_END_DAY'];
|
|
if (workingHoursKeys.includes(configKey)) {
|
|
await clearWorkingHoursCache();
|
|
logger.info(`[Admin] Working hours configuration '${configKey}' reset to default - cache cleared and reloaded`);
|
|
} else {
|
|
logger.info(`[Admin] Configuration '${configKey}' reset to default and cache cleared`);
|
|
}
|
|
|
|
res.json({
|
|
success: true,
|
|
message: 'Configuration reset to default'
|
|
});
|
|
} catch (error: any) {
|
|
logger.error('[Admin] Error resetting configuration:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: error.message || 'Failed to reset configuration'
|
|
});
|
|
}
|
|
};
|
|
|
|
/**
|
|
* ============================================
|
|
* USER ROLE MANAGEMENT (RBAC)
|
|
* ============================================
|
|
*/
|
|
|
|
/**
|
|
* Update User Role
|
|
*
|
|
* Purpose: Change user's role (USER, MANAGEMENT, ADMIN)
|
|
*
|
|
* Access: ADMIN only
|
|
*
|
|
* Body: { role: 'USER' | 'MANAGEMENT' | 'ADMIN' }
|
|
*/
|
|
export const updateUserRole = async (req: Request, res: Response): Promise<void> => {
|
|
try {
|
|
const { userId } = req.params;
|
|
const { role } = req.body;
|
|
|
|
// Validate role
|
|
const validRoles: UserRole[] = ['USER', 'MANAGEMENT', 'ADMIN'];
|
|
if (!role || !validRoles.includes(role)) {
|
|
res.status(400).json({
|
|
success: false,
|
|
error: 'Invalid role. Must be USER, MANAGEMENT, or ADMIN'
|
|
});
|
|
return;
|
|
}
|
|
|
|
// Find user
|
|
const user = await User.findOne({ userId });
|
|
if (!user) {
|
|
res.status(404).json({
|
|
success: false,
|
|
error: 'User not found'
|
|
});
|
|
return;
|
|
}
|
|
|
|
// Store old role for logging
|
|
const oldRole = user.role;
|
|
|
|
// Prevent self-demotion from ADMIN (safety check)
|
|
const adminUser = req.user;
|
|
if (adminUser?.userId === userId && role !== 'ADMIN') {
|
|
res.status(400).json({
|
|
success: false,
|
|
error: 'Cannot remove your own admin privileges. Ask another admin to change your role.'
|
|
});
|
|
return;
|
|
}
|
|
|
|
// Update role
|
|
user.role = role;
|
|
await user.save();
|
|
|
|
logger.info(`✅ User role updated by ${adminUser?.email}: ${user.email} - ${oldRole} → ${role}`);
|
|
|
|
res.json({
|
|
success: true,
|
|
message: `User role updated from ${oldRole} to ${role}`,
|
|
data: {
|
|
userId: user.userId,
|
|
email: user.email,
|
|
displayName: user.displayName,
|
|
role: user.role,
|
|
previousRole: oldRole,
|
|
updatedAt: user.updatedAt
|
|
}
|
|
});
|
|
} catch (error) {
|
|
logger.error('[Admin] Error updating user role:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Failed to update user role'
|
|
});
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Get All Users by Role (with pagination and filtering)
|
|
*
|
|
* Purpose: List all users with optional role filtering and pagination
|
|
*
|
|
* Access: ADMIN only
|
|
*
|
|
* Query:
|
|
* - ?role=ADMIN | MANAGEMENT | USER | ALL | ELEVATED (default: ELEVATED for ADMIN+MANAGEMENT only)
|
|
* - ?page=1 (default)
|
|
* - ?limit=10 (default)
|
|
*/
|
|
export const getUsersByRole = async (req: Request, res: Response): Promise<void> => {
|
|
try {
|
|
const { role, page = '1', limit = '10' } = req.query;
|
|
|
|
const pageNum = parseInt(page as string) || 1;
|
|
const limitNum = Math.min(parseInt(limit as string) || 10, 100); // Max 100 per page
|
|
const offset = (pageNum - 1) * limitNum;
|
|
|
|
const whereClause: any = { isActive: true };
|
|
|
|
// Handle role filtering
|
|
if (role && role !== 'ALL' && role !== 'ELEVATED') {
|
|
const validRoles: string[] = ['USER', 'MANAGEMENT', 'ADMIN'];
|
|
if (!validRoles.includes(role as string)) {
|
|
res.status(400).json({
|
|
success: false,
|
|
error: 'Invalid role. Must be USER, MANAGEMENT, ADMIN, ALL, or ELEVATED'
|
|
});
|
|
return;
|
|
}
|
|
whereClause.role = role;
|
|
} else if (role === 'ELEVATED' || !role) {
|
|
// Default: Show only ADMIN and MANAGEMENT (elevated users)
|
|
whereClause.role = { $in: ['ADMIN', 'MANAGEMENT'] };
|
|
}
|
|
// If role === 'ALL', don't filter by role (show all users)
|
|
|
|
// Get total count for pagination
|
|
const totalUsers = await User.countDocuments(whereClause);
|
|
const totalPages = Math.ceil(totalUsers / limitNum);
|
|
|
|
// Get paginated users
|
|
const users = await User.find(whereClause)
|
|
.select('userId email displayName firstName lastName department designation role manager postalAddress lastLogin createdAt')
|
|
.sort({ role: 1, displayName: 1 })
|
|
.skip(offset)
|
|
.limit(limitNum);
|
|
|
|
// Get role summary (across all users, not just current page)
|
|
const roleStatsRaw = await User.aggregate([
|
|
{ $match: { isActive: true } },
|
|
{ $group: { _id: '$role', count: { $sum: 1 } } },
|
|
{ $sort: { _id: 1 } }
|
|
]);
|
|
|
|
const summary = {
|
|
ADMIN: roleStatsRaw.find((s: any) => s._id === 'ADMIN')?.count || 0,
|
|
MANAGEMENT: roleStatsRaw.find((s: any) => s._id === 'MANAGEMENT')?.count || 0,
|
|
USER: roleStatsRaw.find((s: any) => s._id === 'USER')?.count || 0
|
|
};
|
|
|
|
res.json({
|
|
success: true,
|
|
data: {
|
|
users: users,
|
|
pagination: {
|
|
currentPage: pageNum,
|
|
totalPages: totalPages,
|
|
totalUsers: totalUsers,
|
|
limit: limitNum,
|
|
hasNextPage: pageNum < totalPages,
|
|
hasPrevPage: pageNum > 1
|
|
},
|
|
summary,
|
|
filter: role || 'ELEVATED'
|
|
}
|
|
});
|
|
} catch (error) {
|
|
logger.error('[Admin] Error fetching users by role:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Failed to fetch users'
|
|
});
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Get Role Statistics
|
|
*
|
|
* Purpose: Get count of users in each role
|
|
*
|
|
* Access: ADMIN only
|
|
*/
|
|
export const getRoleStatistics = async (req: Request, res: Response): Promise<void> => {
|
|
try {
|
|
const stats = await User.aggregate([
|
|
{
|
|
$group: {
|
|
_id: '$role',
|
|
count: { $sum: 1 },
|
|
activeCount: { $sum: { $cond: ['$isActive', 1, 0] } },
|
|
inactiveCount: { $sum: { $cond: ['$isActive', 0, 1] } }
|
|
}
|
|
},
|
|
{ $sort: { _id: 1 } }
|
|
]);
|
|
|
|
// Format for frontend
|
|
const formattedStats = stats.map((stat: any) => ({
|
|
role: stat._id,
|
|
count: stat.count,
|
|
active_count: stat.activeCount,
|
|
inactive_count: stat.inactiveCount
|
|
}));
|
|
|
|
res.json({
|
|
success: true,
|
|
data: {
|
|
statistics: formattedStats,
|
|
total: formattedStats.reduce((sum: number, stat: any) => sum + stat.count, 0)
|
|
}
|
|
});
|
|
} catch (error) {
|
|
logger.error('[Admin] Error fetching role statistics:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Failed to fetch role statistics'
|
|
});
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Assign role to user by email
|
|
*
|
|
* Purpose: Search user in Okta, create if doesn't exist, then assign role
|
|
*
|
|
* Access: ADMIN only
|
|
*
|
|
* Body: { email: string, role: 'USER' | 'MANAGEMENT' | 'ADMIN' }
|
|
*/
|
|
export const assignRoleByEmail = async (req: Request, res: Response): Promise<void> => {
|
|
try {
|
|
const { email, role } = req.body;
|
|
const currentUserId = req.user?.userId;
|
|
|
|
// Validate inputs
|
|
if (!email || !role) {
|
|
res.status(400).json({
|
|
success: false,
|
|
error: 'Email and role are required'
|
|
});
|
|
return;
|
|
}
|
|
|
|
// Validate role
|
|
if (!['USER', 'MANAGEMENT', 'ADMIN'].includes(role)) {
|
|
res.status(400).json({
|
|
success: false,
|
|
error: 'Invalid role. Must be USER, MANAGEMENT, or ADMIN'
|
|
});
|
|
return;
|
|
}
|
|
|
|
logger.info(`[Admin] Assigning role ${role} to ${email} by user ${currentUserId}`);
|
|
|
|
// First, check if user already exists in our database
|
|
let user: IUser | null = await User.findOne({ email });
|
|
|
|
if (!user) {
|
|
// User doesn't exist, need to fetch from Okta and create
|
|
logger.info(`[Admin] User ${email} not found in database, fetching from Okta...`);
|
|
|
|
// Import UserService to fetch full profile from Okta
|
|
const { UserService } = await import('@services/user.service');
|
|
const userService = new UserService();
|
|
|
|
try {
|
|
// Fetch full user profile from Okta Users API (includes manager, jobTitle, etc.)
|
|
const oktaUserData = await userService.fetchAndExtractOktaUserByEmail(email);
|
|
|
|
if (!oktaUserData) {
|
|
res.status(404).json({
|
|
success: false,
|
|
error: 'User not found in Okta. Please ensure the email is correct.'
|
|
});
|
|
return;
|
|
}
|
|
|
|
// Create user in our database via centralized userService with all fields including manager
|
|
user = (await userService.createOrUpdateUser({
|
|
...oktaUserData,
|
|
role: role as any, // Set the assigned role
|
|
isActive: true, // Ensure user is active
|
|
})) as IUser;
|
|
|
|
logger.info(`[Admin] Created new user ${email} with role ${role} (manager: ${oktaUserData.manager || 'N/A'})`);
|
|
} catch (oktaError: any) {
|
|
logger.error('[Admin] Error fetching from Okta:', oktaError);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Failed to fetch user from Okta: ' + (oktaError.message || 'Unknown error')
|
|
});
|
|
return;
|
|
}
|
|
} else {
|
|
// User exists - fetch latest data from Okta and sync all fields including role
|
|
const previousRole = user.role;
|
|
|
|
// Prevent self-demotion
|
|
if (user.userId === currentUserId && role !== 'ADMIN') {
|
|
res.status(403).json({
|
|
success: false,
|
|
error: 'You cannot demote yourself from ADMIN role'
|
|
});
|
|
return;
|
|
}
|
|
|
|
// Import UserService to fetch latest data from Okta
|
|
const { UserService } = await import('@services/user.service');
|
|
const userService = new UserService();
|
|
|
|
try {
|
|
// Fetch full user profile from Okta Users API to sync manager and other fields
|
|
const oktaUserData = await userService.fetchAndExtractOktaUserByEmail(email);
|
|
|
|
if (oktaUserData) {
|
|
// Sync all fields from Okta including the new role using centralized method
|
|
user = (await userService.createOrUpdateUser({
|
|
...oktaUserData, // Includes all fields: manager, jobTitle, postalAddress, etc.
|
|
role: role as any, // Set the new role
|
|
isActive: true, // Ensure user is active
|
|
})) as IUser;
|
|
|
|
logger.info(`[Admin] Synced user ${email} from Okta (manager: ${oktaUserData.manager || 'N/A'}) and updated role from ${previousRole} to ${role}`);
|
|
} else {
|
|
// Okta user not found, just update role
|
|
user.role = role as any;
|
|
await user.save();
|
|
logger.info(`[Admin] Updated user ${email} role from ${previousRole} to ${role} (Okta data not available)`);
|
|
}
|
|
} catch (oktaError: any) {
|
|
// If Okta fetch fails, just update the role
|
|
logger.warn(`[Admin] Failed to fetch Okta data for ${email}, updating role only:`, oktaError.message);
|
|
user.role = role as any;
|
|
await user.save();
|
|
logger.info(`[Admin] Updated user ${email} role from ${previousRole} to ${role} (Okta sync failed)`);
|
|
}
|
|
}
|
|
|
|
if (!user) {
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Failed to create or update user'
|
|
});
|
|
return;
|
|
}
|
|
|
|
res.json({
|
|
success: true,
|
|
message: `Successfully assigned ${role} role to ${user.displayName || email}`,
|
|
data: {
|
|
userId: user.userId,
|
|
email: user.email,
|
|
displayName: user.displayName,
|
|
role: user.role
|
|
}
|
|
});
|
|
} catch (error) {
|
|
logger.error('[Admin] Error assigning role by email:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Failed to assign role'
|
|
});
|
|
}
|
|
};
|
|
|
|
// ==================== Activity Type Management Routes ====================
|
|
|
|
/**
|
|
* Get all activity types (optionally filtered by active status)
|
|
*/
|
|
export const getAllActivityTypes = async (req: Request, res: Response): Promise<void> => {
|
|
try {
|
|
const { activeOnly } = req.query;
|
|
const activeOnlyBool = activeOnly === 'true';
|
|
|
|
const activityTypes = await activityTypeService.getAllActivityTypes(activeOnlyBool);
|
|
|
|
res.json({
|
|
success: true,
|
|
data: activityTypes,
|
|
count: activityTypes.length
|
|
});
|
|
} catch (error: any) {
|
|
logger.error('[Admin] Error fetching activity types:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: error.message || 'Failed to fetch activity types'
|
|
});
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Get a single activity type by ID
|
|
*/
|
|
export const getActivityTypeById = async (req: Request, res: Response): Promise<void> => {
|
|
try {
|
|
const { activityTypeId } = req.params;
|
|
|
|
const activityType = await activityTypeService.getActivityTypeById(activityTypeId);
|
|
|
|
if (!activityType) {
|
|
res.status(404).json({
|
|
success: false,
|
|
error: 'Activity type not found'
|
|
});
|
|
return;
|
|
}
|
|
|
|
res.json({
|
|
success: true,
|
|
data: activityType
|
|
});
|
|
} catch (error: any) {
|
|
logger.error('[Admin] Error fetching activity type:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: error.message || 'Failed to fetch activity type'
|
|
});
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Create a new activity type
|
|
*/
|
|
export const createActivityType = async (req: Request, res: Response): Promise<void> => {
|
|
try {
|
|
const userId = req.user?.userId;
|
|
if (!userId) {
|
|
res.status(401).json({
|
|
success: false,
|
|
error: 'User not authenticated'
|
|
});
|
|
return;
|
|
}
|
|
|
|
const {
|
|
title,
|
|
itemCode,
|
|
taxationType,
|
|
sapRefNo
|
|
} = req.body;
|
|
|
|
// Validate required fields
|
|
if (!title) {
|
|
res.status(400).json({
|
|
success: false,
|
|
error: 'Activity type title is required'
|
|
});
|
|
return;
|
|
}
|
|
|
|
const activityType = await activityTypeService.createActivityType({
|
|
title,
|
|
itemCode: itemCode || null,
|
|
taxationType: taxationType || null,
|
|
sapRefNo: sapRefNo || null,
|
|
createdBy: userId
|
|
});
|
|
|
|
res.status(201).json({
|
|
success: true,
|
|
message: 'Activity type created successfully',
|
|
data: activityType
|
|
});
|
|
} catch (error: any) {
|
|
logger.error('[Admin] Error creating activity type:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: error.message || 'Failed to create activity type'
|
|
});
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Update an activity type
|
|
*/
|
|
export const updateActivityType = async (req: Request, res: Response): Promise<void> => {
|
|
try {
|
|
const userId = req.user?.userId;
|
|
if (!userId) {
|
|
res.status(401).json({
|
|
success: false,
|
|
error: 'User not authenticated'
|
|
});
|
|
return;
|
|
}
|
|
|
|
const { activityTypeId } = req.params;
|
|
const updates = req.body;
|
|
|
|
const activityType = await activityTypeService.updateActivityType(activityTypeId, updates, userId);
|
|
|
|
if (!activityType) {
|
|
res.status(404).json({
|
|
success: false,
|
|
error: 'Activity type not found'
|
|
});
|
|
return;
|
|
}
|
|
|
|
res.json({
|
|
success: true,
|
|
message: 'Activity type updated successfully',
|
|
data: activityType
|
|
});
|
|
} catch (error: any) {
|
|
logger.error('[Admin] Error updating activity type:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: error.message || 'Failed to update activity type'
|
|
});
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Delete (deactivate) an activity type
|
|
*/
|
|
export const deleteActivityType = async (req: Request, res: Response): Promise<void> => {
|
|
try {
|
|
const { activityTypeId } = req.params;
|
|
|
|
await activityTypeService.deleteActivityType(activityTypeId);
|
|
|
|
res.json({
|
|
success: true,
|
|
message: 'Activity type deleted successfully'
|
|
});
|
|
} catch (error: any) {
|
|
logger.error('[Admin] Error deleting activity type:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: error.message || 'Failed to delete activity type'
|
|
});
|
|
}
|
|
};
|