import EventDay from '../model/eventDay.model'; import { IEvent } from '../model/event.model'; import cloudinary from './cloudinary.util'; /** * Generate EventDay records for each day between event's startDate and endDate */ export const generateEventDays = async (event: IEvent): Promise => { const startDate = new Date(event.startDate); const endDate = new Date(event.endDate); // Set time to midnight for consistent date comparison startDate.setHours(0, 0, 0, 0); endDate.setHours(0, 0, 0, 0); const days: any[] = []; let currentDate = new Date(startDate); let dayNumber = 1; // Parse default time slots const [startHour, startMinute] = event.defaultStartTime.split(':').map(Number); const [endHour, endMinute] = event.defaultEndTime.split(':').map(Number); while (currentDate <= endDate) { // Create start and end times for this specific day const dayStartTime = new Date(currentDate); dayStartTime.setHours(startHour, startMinute, 0, 0); const dayEndTime = new Date(currentDate); dayEndTime.setHours(endHour, endMinute, 0, 0); days.push({ event: event._id, date: new Date(currentDate), dayNumber: dayNumber, startTime: dayStartTime, endTime: dayEndTime, photos: [], attendance: [], hasStarted: false, isCompleted: false }); currentDate.setDate(currentDate.getDate() + 1); dayNumber++; } // Bulk insert all days if (days.length > 0) { await EventDay.insertMany(days); } }; /** * Upload event photo to Cloudinary with proper folder structure * Folder: events/{eventId}/day-{dayNumber}/{timestamp}-{random} */ export const uploadEventPhoto = async ( file: any, eventId: string, dayNumber: number ): Promise<{ url: string; publicId: string }> => { return new Promise((resolve, reject) => { const timestamp = Date.now(); const random = Math.random().toString(36).substring(7); const folder = `events/${eventId}/day-${dayNumber}`; const publicId = `${folder}/${timestamp}-${random}`; const uploadStream = cloudinary.uploader.upload_stream( { folder: folder, public_id: `${timestamp}-${random}`, resource_type: 'auto' }, (error, result) => { if (error || !result) { reject(error || new Error('Upload failed')); } else { resolve({ url: result.secure_url, publicId: result.public_id }); } } ); uploadStream.end(file.buffer); }); }; /** * Delete event photo from Cloudinary */ export const deleteEventPhoto = async (publicId: string): Promise => { await cloudinary.uploader.destroy(publicId); }; /** * Calculate attendance statistics for an event */ export const calculateAttendanceStats = async (eventId: string): Promise<{ totalDays: number; completedDays: number; averageAttendance: number; dailyStats: Array<{ date: Date; dayNumber: number; attendanceCount: number; isCompleted: boolean; }>; }> => { const eventDays = await EventDay.find({ event: eventId }).sort({ dayNumber: 1 }); const totalDays = eventDays.length; const completedDays = eventDays.filter(day => day.isCompleted).length; const dailyStats = eventDays.map(day => ({ date: day.date, dayNumber: day.dayNumber, attendanceCount: day.attendance.length, isCompleted: day.isCompleted })); const totalAttendance = dailyStats.reduce((sum, day) => sum + day.attendanceCount, 0); const averageAttendance = totalDays > 0 ? totalAttendance / totalDays : 0; return { totalDays, completedDays, averageAttendance: Math.round(averageAttendance * 100) / 100, dailyStats }; }; /** * Delete all EventDay records for an event (used when event is deleted) */ export const deleteEventDays = async (eventId: string): Promise => { // Get all event days to delete their photos from Cloudinary const eventDays = await EventDay.find({ event: eventId }); // Delete all photos from Cloudinary for (const day of eventDays) { for (const photo of day.photos) { try { await deleteEventPhoto(photo.publicId); } catch (err) { console.error(`Failed to delete photo ${photo.publicId}:`, err); } } } // Delete all EventDay records await EventDay.deleteMany({ event: eventId }); }; /** * Regenerate EventDay records if event dates are updated */ export const regenerateEventDays = async (event: IEvent): Promise => { // Delete existing days (but preserve photos and attendance if possible) const existingDays = await EventDay.find({ event: event._id }).sort({ date: 1 }); // Delete all existing days await EventDay.deleteMany({ event: event._id }); // Generate new days await generateEventDays(event); // Try to restore data to matching dates const newDays = await EventDay.find({ event: event._id }).sort({ date: 1 }); for (const existingDay of existingDays) { const matchingNewDay = newDays.find( newDay => newDay.date.toISOString().split('T')[0] === existingDay.date.toISOString().split('T')[0] ); if (matchingNewDay) { matchingNewDay.photos = existingDay.photos; matchingNewDay.attendance = existingDay.attendance; matchingNewDay.notes = existingDay.notes; matchingNewDay.weatherCondition = existingDay.weatherCondition; matchingNewDay.activitiesConducted = existingDay.activitiesConducted; matchingNewDay.incidentsReported = existingDay.incidentsReported; matchingNewDay.equipmentUsed = existingDay.equipmentUsed; matchingNewDay.isCompleted = existingDay.isCompleted; matchingNewDay.completedAt = existingDay.completedAt; await matchingNewDay.save(); } } };