| import { Schema, model, Document, Types } from 'mongoose'; |
|
|
| interface IGeoPoint { |
| type: "Point"; |
| coordinates: [number, number]; |
| } |
|
|
| interface IEventDayPhoto { |
| url: string; |
| publicId: string; |
| caption?: string; |
| uploadedBy: Types.ObjectId; |
| uploadedAt: Date; |
| } |
|
|
| interface IEventDayAttendance { |
| user: Types.ObjectId; |
| checkInTime: Date; |
| checkInMethod: 'gps' | 'qr' | 'manual' | 'hotspot'; |
| checkInLocation?: IGeoPoint; |
| verifiedBy?: Types.ObjectId; |
| } |
|
|
| export interface IEventDay extends Document { |
| |
| event: Types.ObjectId; |
|
|
| |
| date: Date; |
| dayNumber: number; |
|
|
| |
| startTime: Date; |
| endTime: Date; |
|
|
| |
| photos: IEventDayPhoto[]; |
|
|
| |
| attendance: IEventDayAttendance[]; |
|
|
| |
| notes?: string; |
| weatherCondition?: string; |
| activitiesConducted?: string[]; |
| incidentsReported?: string; |
| equipmentUsed?: string[]; |
| cvDetectedCount?: number; |
|
|
| |
| hasStarted: boolean; |
| isCompleted: boolean; |
| completedAt?: Date; |
|
|
| |
| createdAt: Date; |
| updatedAt: Date; |
| } |
|
|
| const EventDaySchema = new Schema<IEventDay>({ |
| event: { |
| type: Schema.Types.ObjectId, |
| ref: 'Event', |
| required: true, |
| index: true |
| }, |
|
|
| date: { |
| type: Date, |
| required: true, |
| index: true |
| }, |
|
|
| dayNumber: { |
| type: Number, |
| required: true, |
| min: 1 |
| }, |
|
|
| startTime: { |
| type: Date, |
| required: true |
| }, |
|
|
| endTime: { |
| type: Date, |
| required: true |
| }, |
|
|
| photos: [{ |
| url: { |
| type: String, |
| required: true |
| }, |
| publicId: { |
| type: String, |
| required: true |
| }, |
| caption: { |
| type: String, |
| trim: true |
| }, |
| uploadedBy: { |
| type: Schema.Types.ObjectId, |
| ref: 'User', |
| required: true |
| }, |
| uploadedAt: { |
| type: Date, |
| default: Date.now |
| } |
| }], |
|
|
| attendance: [{ |
| user: { |
| type: Schema.Types.ObjectId, |
| ref: 'User', |
| required: true |
| }, |
| checkInTime: { |
| type: Date, |
| default: Date.now |
| }, |
| checkInMethod: { |
| type: String, |
| enum: ['gps', 'qr', 'manual', 'hotspot'], |
| required: true |
| }, |
| checkInLocation: { |
| type: { |
| type: String, |
| enum: ['Point'] |
| }, |
| coordinates: { |
| type: [Number] |
| } |
| }, |
| verifiedBy: { |
| type: Schema.Types.ObjectId, |
| ref: 'User' |
| } |
| }], |
|
|
| notes: { |
| type: String, |
| trim: true |
| }, |
|
|
| weatherCondition: { |
| type: String, |
| trim: true |
| }, |
|
|
| activitiesConducted: [{ |
| type: String, |
| trim: true |
| }], |
|
|
| incidentsReported: { |
| type: String, |
| trim: true |
| }, |
|
|
| equipmentUsed: [{ |
| type: String, |
| trim: true |
| }], |
|
|
| cvDetectedCount: { |
| type: Number, |
| min: 0 |
| }, |
|
|
| hasStarted: { |
| type: Boolean, |
| default: false |
| }, |
|
|
| isCompleted: { |
| type: Boolean, |
| default: false |
| }, |
|
|
| completedAt: { |
| type: Date |
| } |
| }, { |
| timestamps: true |
| }); |
|
|
| |
| EventDaySchema.index({ event: 1, date: 1 }, { unique: true }); |
| EventDaySchema.index({ event: 1, dayNumber: 1 }); |
| EventDaySchema.index({ 'attendance.user': 1 }); |
| EventDaySchema.index({ date: 1 }); |
|
|
| |
| EventDaySchema.pre('save', function (this: IEventDay) { |
| |
| if (this.endTime <= this.startTime) { |
| throw new Error('End time must be after start time'); |
| } |
|
|
| |
| if (this.isModified('isCompleted') && this.isCompleted) { |
| |
| if (this.photos.length === 0) { |
| throw new Error('Cannot mark as completed: No photos uploaded'); |
| } |
| if (this.attendance.length === 0) { |
| throw new Error('Cannot mark as completed: No attendance recorded'); |
| } |
|
|
| |
| if (!this.completedAt) { |
| this.completedAt = new Date(); |
| } |
| } |
| }); |
|
|
| export default model<IEventDay>('EventDay', EventDaySchema); |
|
|