Prashikshak / API /src /controller /eventDay.controller.ts
Abhisingh-18's picture
Initial commit: Prashikshak - disaster management training platform
9a92a42
Raw
History Blame Contribute Delete
28.5 kB
import { Request, Response } from 'express';
import EventDay from '../model/eventDay.model';
import Event from '../model/event.model';
import User from '../model/user.model';
import { uploadEventPhoto, deleteEventPhoto, calculateAttendanceStats, generateEventDays } from '../util/eventDay.util';
import { canManageEventDay } from '../util/role.util';
interface IEventDayRequest extends Request {
userId?: string;
}
// ==================== GET ALL DAYS FOR AN EVENT ====================
export const getEventDays = async (req: IEventDayRequest, res: Response) => {
try {
const { eventId } = req.params;
const userId = req.userId;
// Verify event exists
const event = await Event.findById(eventId);
if (!event) {
return res.status(404).json({ message: 'Event not found' });
}
// Check if user is participant or creator
const isCreator = event.createdBy.toString() === userId;
const isParticipant = event.participants.some(p => p.user.toString() === userId);
if (!isCreator && !isParticipant) {
return res.status(403).json({ message: 'Access denied. You must be a participant or creator.' });
}
// Get all event days
let eventDays = await EventDay.find({ event: eventId })
.sort({ dayNumber: 1 })
.populate('attendance.user', 'username profilePhoto')
.populate('photos.uploadedBy', 'username');
// Auto-generate event days if they don't exist (for existing events)
if (eventDays.length === 0) {
try {
await generateEventDays(event);
eventDays = await EventDay.find({ event: eventId })
.sort({ dayNumber: 1 })
.populate('attendance.user', 'username profilePhoto')
.populate('photos.uploadedBy', 'username');
} catch (err) {
console.error('Failed to auto-generate event days:', err);
}
}
// Calculate stats
const stats = await calculateAttendanceStats(eventId);
return res.json({
eventDays: eventDays.map(day => {
const now = new Date();
const eventEndTime = new Date(day.endTime);
// Determine status
let status: 'green' | 'red' | 'blue';
if (day.isCompleted && day.photos.length > 0 && day.attendance.length > 0) {
status = 'green';
} else if (eventEndTime < now && (!day.hasStarted || day.photos.length === 0 || day.attendance.length === 0)) {
status = 'red';
} else {
status = 'blue';
}
return {
_id: day._id,
date: day.date,
dayNumber: day.dayNumber,
startTime: day.startTime,
endTime: day.endTime,
photoCount: day.photos.length,
attendanceCount: day.attendance.length,
hasStarted: day.hasStarted,
isCompleted: day.isCompleted,
status,
notes: day.notes,
weatherCondition: day.weatherCondition,
// Only show full details to creator
...(isCreator && {
photos: day.photos,
attendance: day.attendance,
activitiesConducted: day.activitiesConducted,
incidentsReported: day.incidentsReported,
equipmentUsed: day.equipmentUsed
})
};
}),
stats
});
} catch (err: any) {
console.error(err);
return res.status(500).json({ message: 'Server error', error: err.message });
}
};
// ==================== GET SPECIFIC DAY DETAILS ====================
export const getEventDayDetails = async (req: IEventDayRequest, res: Response) => {
try {
const { eventDayId } = req.params;
const userId = req.userId;
const eventDay = await EventDay.findById(eventDayId)
.populate('event')
.populate('attendance.user', 'username email profilePhoto')
.populate('attendance.verifiedBy', 'username')
.populate('photos.uploadedBy', 'username profilePhoto');
if (!eventDay) {
return res.status(404).json({ message: 'Event day not found' });
}
const event = eventDay.event as any;
const isCreator = event.createdBy.toString() === userId;
const isParticipant = event.participants.some((p: any) => p.user.toString() === userId);
if (!isCreator && !isParticipant) {
return res.status(403).json({ message: 'Access denied' });
}
const now = new Date();
const eventEndTime = new Date(eventDay.endTime);
let status: 'green' | 'red' | 'blue';
if (eventDay.isCompleted && eventDay.photos.length > 0 && eventDay.attendance.length > 0) {
status = 'green';
} else if (eventEndTime < now && (!eventDay.hasStarted || eventDay.photos.length === 0 || eventDay.attendance.length === 0)) {
status = 'red';
} else {
status = 'blue';
}
const response: any = {
_id: eventDay._id,
event: {
_id: event._id,
title: event.title,
code: event.code
},
date: eventDay.date,
dayNumber: eventDay.dayNumber,
startTime: eventDay.startTime,
endTime: eventDay.endTime,
hasStarted: eventDay.hasStarted,
isCompleted: eventDay.isCompleted,
status,
photos: eventDay.photos,
notes: eventDay.notes,
weatherCondition: eventDay.weatherCondition
};
if (isCreator) {
response.attendance = eventDay.attendance;
response.activitiesConducted = eventDay.activitiesConducted;
response.incidentsReported = eventDay.incidentsReported;
response.equipmentUsed = eventDay.equipmentUsed;
response.completedAt = eventDay.completedAt;
} else {
// Show only user's own attendance
response.myAttendance = eventDay.attendance.find(
(a: any) => a.user._id.toString() === userId
);
}
return res.json(response);
} catch (err: any) {
console.error(err);
return res.status(500).json({ message: 'Server error', error: err.message });
}
};
// ==================== UPLOAD DAY PHOTOS ====================
export const uploadDayPhotos = async (req: IEventDayRequest, res: Response) => {
try {
const { eventDayId } = req.params;
const userId = req.userId;
const files = req.files as Express.Multer.File[];
const { captions } = req.body; // Array of captions (optional)
if (!files || files.length === 0) {
return res.status(400).json({ message: 'No files uploaded' });
}
const eventDay = await EventDay.findById(eventDayId).populate('event');
if (!eventDay) {
return res.status(404).json({ message: 'Event day not found' });
}
const event = eventDay.event as any;
const user = await User.findById(userId);
if (!user) {
return res.status(404).json({ message: 'User not found' });
}
// Check if user can manage this event day (trainer+ level from same org)
const canManage = await canManageEventDay(user, event);
if (!canManage) {
return res.status(403).json({
message: 'Only trainers, organizations, and admins from the same organization can upload photos'
});
}
// Upload each file to Cloudinary
const uploadedPhotos = [];
const captionsArray = captions ? (Array.isArray(captions) ? captions : [captions]) : [];
for (let i = 0; i < files.length; i++) {
const file = files[i];
const caption = captionsArray[i] || undefined;
try {
const { url, publicId } = await uploadEventPhoto(
file,
event._id.toString(),
eventDay.dayNumber
);
const photo = {
url,
publicId,
caption,
uploadedBy: userId as any,
uploadedAt: new Date()
};
eventDay.photos.push(photo as any);
uploadedPhotos.push(photo);
} catch (uploadErr) {
console.error('Failed to upload photo:', uploadErr);
}
}
// Auto-start event day when first photo is uploaded
if (!eventDay.hasStarted && uploadedPhotos.length > 0) {
eventDay.hasStarted = true;
}
await eventDay.save();
return res.json({
message: `${uploadedPhotos.length} photo(s) uploaded successfully`,
photos: uploadedPhotos
});
} catch (err: any) {
console.error(err);
return res.status(500).json({ message: 'Server error', error: err.message });
}
};
// ==================== DELETE DAY PHOTO ====================
export const deleteDayPhoto = async (req: IEventDayRequest, res: Response) => {
try {
const { eventDayId, photoId } = req.params;
const userId = req.userId;
const eventDay = await EventDay.findById(eventDayId).populate('event');
if (!eventDay) {
return res.status(404).json({ message: 'Event day not found' });
}
const event = eventDay.event as any;
const user = await User.findById(userId);
if (!user) {
return res.status(404).json({ message: 'User not found' });
}
// Check if user can manage this event day
const canManage = await canManageEventDay(user, event);
if (!canManage) {
return res.status(403).json({
message: 'Only trainers, organizations, and admins from the same organization can delete photos'
});
}
const photo = eventDay.photos.find((p: any) => p._id.toString() === photoId);
if (!photo) {
return res.status(404).json({ message: 'Photo not found' });
}
// Delete from Cloudinary
try {
await deleteEventPhoto(photo.publicId);
} catch (cloudinaryErr) {
console.error('Failed to delete from Cloudinary:', cloudinaryErr);
}
// Remove from database
eventDay.photos = eventDay.photos.filter((p: any) => p._id.toString() !== photoId) as any;
await eventDay.save();
return res.json({ message: 'Photo deleted successfully' });
} catch (err: any) {
console.error(err);
return res.status(500).json({ message: 'Server error', error: err.message });
}
};
// ==================== MARK ATTENDANCE ====================
export const markAttendance = async (req: IEventDayRequest, res: Response) => {
try {
const { eventDayId } = req.params;
const currentUserId = req.userId;
const { userId, checkInMethod, latitude, longitude } = req.body;
if (!userId || !checkInMethod) {
return res.status(400).json({ message: 'userId and checkInMethod required' });
}
const eventDay = await EventDay.findById(eventDayId).populate('event');
if (!eventDay) {
return res.status(404).json({ message: 'Event day not found' });
}
const event = eventDay.event as any;
const user = await User.findById(currentUserId);
if (!user) {
return res.status(404).json({ message: 'User not found' });
}
// Check if user can manage this event day
const canManage = await canManageEventDay(user, event);
if (!canManage) {
return res.status(403).json({
message: 'Only trainers, organizations, and admins from the same organization can mark attendance'
});
}
// Check if user is a participant
const isParticipant = event.participants.some((p: any) => p.user.toString() === userId);
if (!isParticipant) {
return res.status(400).json({ message: 'User is not a participant of this event' });
}
// Check if already marked
const existingAttendance = eventDay.attendance.find((a: any) => a.user.toString() === userId);
if (existingAttendance) {
return res.status(400).json({ message: 'Attendance already marked for this user' });
}
// Create attendance record
const attendance: any = {
user: userId,
checkInTime: new Date(),
checkInMethod,
verifiedBy: checkInMethod === 'manual' ? currentUserId : undefined
};
// Add location if provided
if (latitude && longitude) {
attendance.checkInLocation = {
type: 'Point',
coordinates: [parseFloat(longitude), parseFloat(latitude)]
};
}
eventDay.attendance.push(attendance);
await eventDay.save();
return res.json({
message: 'Attendance marked successfully',
attendance
});
} catch (err: any) {
console.error(err);
return res.status(500).json({ message: 'Server error', error: err.message });
}
};
// ==================== BULK MARK ATTENDANCE (HOTSPOT) ====================
export const bulkMarkAttendance = async (req: IEventDayRequest, res: Response) => {
try {
const { eventDayId } = req.params;
const currentUserId = req.userId;
const { userIds, checkInMethod = 'hotspot', latitude, longitude } = req.body;
if (!userIds || !Array.isArray(userIds) || userIds.length === 0) {
return res.status(400).json({ message: 'userIds array required' });
}
const eventDay = await EventDay.findById(eventDayId).populate('event');
if (!eventDay) {
return res.status(404).json({ message: 'Event day not found' });
}
const event = eventDay.event as any;
const user = await User.findById(currentUserId);
if (!user) {
return res.status(404).json({ message: 'User not found' });
}
// Check if user can manage this event day
const canManage = await canManageEventDay(user, event);
if (!canManage) {
return res.status(403).json({
message: 'Only trainers, organizations, and admins from the same organization can mark attendance'
});
}
const results = {
marked: [] as string[],
alreadyMarked: [] as string[],
notParticipant: [] as string[]
};
for (const userId of userIds) {
// Check if user is a participant
const isParticipant = event.participants.some((p: any) => p.user.toString() === userId);
if (!isParticipant) {
results.notParticipant.push(userId);
continue;
}
// Check if already marked
const existingAttendance = eventDay.attendance.find((a: any) => a.user.toString() === userId);
if (existingAttendance) {
results.alreadyMarked.push(userId);
continue;
}
// Create attendance record
const attendance: any = {
user: userId,
checkInTime: new Date(),
checkInMethod
};
// Add location if provided
if (latitude && longitude) {
attendance.checkInLocation = {
type: 'Point',
coordinates: [parseFloat(longitude), parseFloat(latitude)]
};
}
eventDay.attendance.push(attendance);
results.marked.push(userId);
}
await eventDay.save();
return res.json({
message: `Attendance marked for ${results.marked.length} user(s)`,
results
});
} catch (err: any) {
console.error(err);
return res.status(500).json({ message: 'Server error', error: err.message });
}
};
// ==================== UPDATE DAY METADATA ====================
export const updateDayMetadata = async (req: IEventDayRequest, res: Response) => {
try {
const { eventDayId } = req.params;
const userId = req.userId;
const {
notes,
weatherCondition,
activitiesConducted,
incidentsReported,
equipmentUsed,
isCompleted
} = req.body;
const eventDay = await EventDay.findById(eventDayId).populate('event');
if (!eventDay) {
return res.status(404).json({ message: 'Event day not found' });
}
const event = eventDay.event as any;
const user = await User.findById(userId);
if (!user) {
return res.status(404).json({ message: 'User not found' });
}
// Check if user can manage this event day
const canManage = await canManageEventDay(user, event);
if (!canManage) {
return res.status(403).json({
message: 'Only trainers, organizations, and admins from the same organization can update day metadata'
});
}
// Update fields
if (notes !== undefined) eventDay.notes = notes;
if (weatherCondition !== undefined) eventDay.weatherCondition = weatherCondition;
if (activitiesConducted !== undefined) eventDay.activitiesConducted = activitiesConducted;
if (incidentsReported !== undefined) eventDay.incidentsReported = incidentsReported;
if (equipmentUsed !== undefined) eventDay.equipmentUsed = equipmentUsed;
if (isCompleted !== undefined) eventDay.isCompleted = isCompleted;
await eventDay.save();
return res.json({
message: 'Event day updated successfully',
eventDay
});
} catch (err: any) {
console.error(err);
return res.status(500).json({ message: 'Server error', error: err.message });
}
};
// ==================== GET ATTENDANCE REPORT ====================
export const getAttendanceReport = async (req: IEventDayRequest, res: Response) => {
try {
const { eventId } = req.params;
const userId = req.userId;
const event = await Event.findById(eventId).populate('participants.user', 'username email profilePhoto');
if (!event) {
return res.status(404).json({ message: 'Event not found' });
}
// Only event creator can view full attendance report
if (event.createdBy.toString() !== userId) {
return res.status(403).json({ message: 'Only event creator can view attendance report' });
}
const stats = await calculateAttendanceStats(eventId);
const eventDays = await EventDay.find({ event: eventId })
.sort({ dayNumber: 1 })
.populate('attendance.user', 'username email profilePhoto');
// Build participant-wise report
const participantReport = event.participants.map(participant => {
const user = participant.user as any;
const attendanceDays = eventDays.filter(day =>
day.attendance.some((a: any) => a.user._id.toString() === user._id.toString())
);
return {
user: {
_id: user._id,
username: user.username,
email: user.email,
profilePhoto: user.profilePhoto
},
daysAttended: attendanceDays.length,
totalDays: stats.totalDays,
attendancePercentage: stats.totalDays > 0
? Math.round((attendanceDays.length / stats.totalDays) * 100)
: 0,
attendanceDates: attendanceDays.map(day => ({
date: day.date,
dayNumber: day.dayNumber,
checkInTime: day.attendance.find((a: any) => a.user._id.toString() === user._id.toString())?.checkInTime
}))
};
});
return res.json({
event: {
_id: event._id,
title: event.title,
code: event.code,
startDate: event.startDate,
endDate: event.endDate
},
stats,
participantReport,
dailyBreakdown: stats.dailyStats
});
} catch (err: any) {
console.error(err);
return res.status(500).json({ message: 'Server error', error: err.message });
}
};
// ==================== START EVENT DAY ====================
export const startEventDay = async (req: IEventDayRequest, res: Response) => {
try {
const { eventDayId } = req.params;
const userId = req.userId;
const eventDay = await EventDay.findById(eventDayId).populate('event');
if (!eventDay) {
return res.status(404).json({ message: 'Event day not found' });
}
const event = eventDay.event as any;
const user = await User.findById(userId);
if (!user) {
return res.status(404).json({ message: 'User not found' });
}
// Check if user can manage this event day
const canManage = await canManageEventDay(user, event);
if (!canManage) {
return res.status(403).json({
message: 'Only trainers, organizations, and admins from the same organization can start event day'
});
}
// Check if event day date is today or in the past (can't start future events)
const today = new Date();
today.setHours(0, 0, 0, 0);
const eventDate = new Date(eventDay.date);
eventDate.setHours(0, 0, 0, 0);
if (eventDate > today) {
return res.status(400).json({
message: 'Cannot start event day in the future. Event day can only be started on or after its scheduled date.'
});
}
// Check if already started
if (eventDay.hasStarted) {
return res.status(400).json({ message: 'Event day already started' });
}
// Check if already completed
if (eventDay.isCompleted) {
return res.status(400).json({ message: 'Event day already completed' });
}
// Set as started
eventDay.hasStarted = true;
await eventDay.save();
return res.json({
message: 'Event day started successfully',
eventDay: {
_id: eventDay._id,
date: eventDay.date,
dayNumber: eventDay.dayNumber,
hasStarted: eventDay.hasStarted,
startTime: eventDay.startTime,
endTime: eventDay.endTime
}
});
} catch (err: any) {
console.error(err);
return res.status(500).json({ message: 'Server error', error: err.message });
}
};
// ==================== END EVENT DAY ====================
export const endEventDay = async (req: IEventDayRequest, res: Response) => {
try {
const { eventDayId } = req.params;
const userId = req.userId;
const eventDay = await EventDay.findById(eventDayId).populate('event');
if (!eventDay) {
return res.status(404).json({ message: 'Event day not found' });
}
const event = eventDay.event as any;
const user = await User.findById(userId);
if (!user) {
return res.status(404).json({ message: 'User not found' });
}
// Check if user can manage this event day
const canManage = await canManageEventDay(user, event);
if (!canManage) {
return res.status(403).json({
message: 'Only trainers, organizations, and admins from the same organization can end event day'
});
}
// Check if started
if (!eventDay.hasStarted) {
return res.status(400).json({ message: 'Event day has not been started yet' });
}
// Check if already completed
if (eventDay.isCompleted) {
return res.status(400).json({ message: 'Event day already completed' });
}
// Validate completion requirements
if (eventDay.photos.length === 0) {
return res.status(400).json({
message: 'Cannot end event day: No photos uploaded',
requirement: 'photos'
});
}
// if (eventDay.attendance.length === 0) {
// return res.status(400).json({
// message: 'Cannot end event day: No attendance recorded',
// requirement: 'attendance'
// });
// }
// Mark as completed
eventDay.isCompleted = true;
await eventDay.save();
return res.json({
message: 'Event day ended successfully',
eventDay: {
_id: eventDay._id,
date: eventDay.date,
dayNumber: eventDay.dayNumber,
isCompleted: eventDay.isCompleted,
completedAt: eventDay.completedAt,
photoCount: eventDay.photos.length,
attendanceCount: eventDay.attendance.length
}
});
} catch (err: any) {
console.error(err);
return res.status(500).json({ message: 'Server error', error: err.message });
}
};
// ==================== GET EVENT DAY STATUS ====================
export const getEventDayStatus = async (req: IEventDayRequest, res: Response) => {
try {
const { eventDayId } = req.params;
const eventDay = await EventDay.findById(eventDayId);
if (!eventDay) {
return res.status(404).json({ message: 'Event day not found' });
}
const now = new Date();
const eventEndTime = new Date(eventDay.endTime);
let status: 'green' | 'red' | 'blue';
let message: string;
// Green: Completed with photos and attendance
if (eventDay.isCompleted && eventDay.photos.length > 0 && eventDay.attendance.length > 0) {
status = 'green';
message = 'Event completed successfully';
}
// Red: Event day has passed but not completed properly
else if (eventEndTime < now) {
if (!eventDay.hasStarted) {
status = 'red';
message = 'Event was not conducted';
} else if (eventDay.photos.length === 0) {
status = 'red';
message = 'Event missed: No photos uploaded';
} else if (eventDay.attendance.length === 0) {
status = 'red';
message = 'Event missed: No attendance recorded';
} else {
status = 'red';
message = 'Event not properly completed';
}
}
// Blue: Future dates or planned but not yet conducted
else {
status = 'blue';
message = eventDay.hasStarted ? 'Event in progress' : 'Event planned';
}
return res.json({
status,
message,
eventDay: {
_id: eventDay._id,
date: eventDay.date,
dayNumber: eventDay.dayNumber,
startTime: eventDay.startTime,
endTime: eventDay.endTime,
hasStarted: eventDay.hasStarted,
isCompleted: eventDay.isCompleted,
photoCount: eventDay.photos.length,
attendanceCount: eventDay.attendance.length
}
});
} catch (err: any) {
console.error(err);
return res.status(500).json({ message: 'Server error', error: err.message });
}
};