Skip to content

Latest commit

 

History

History
334 lines (286 loc) · 9.13 KB

File metadata and controls

334 lines (286 loc) · 9.13 KB

Recurring Events Feature - Backend Implementation

Overview

The recurring events feature allows Events Office users to create gym sessions and professors to create workshops on a weekly recurring schedule. Instead of creating individual events one by one, users can specify a pattern (day of week, start date, end date) and all occurrences are created automatically.

Architecture

Models

RecurringEvent Model

Tracks recurring event series metadata and links to all created occurrences.

Fields:

  • eventType: 'gym_session' | 'workshop' - Type of recurring event
  • startDate: Date - First date of the series (inclusive)
  • endDate: Date - Last date to include (inclusive)
  • dayOfWeek: String - Target day ('Monday', 'Tuesday', etc.)
  • gymSessionData: Object - Template data for gym sessions
    • type: 'yoga' | 'pilates' | 'aerobics' | 'zumba' | 'cross_circuit' | 'kick_boxing'
    • time: HH:MM format string
    • duration: Number (minutes)
    • maxParticipants: Number
  • workshopData: Object - Template data for workshops
    • name, location, startTime, endTime
    • shortDescription, fullAgenda, faculty
    • professors: Array of strings
    • requiredBudget, fundingSource, extraResources
    • capacity, registrationDeadline
  • createdEvents: Array - Links to created event documents
    • eventId: ObjectId of created GymSession or Workshop
    • eventDate: Date of the event
    • status: 'created' | 'deleted'
  • creator: ObjectId - Reference to User who created the series
  • totalOccurrences: Number - Count of occurrences created
  • status: 'active' | 'cancelled' - Series status

Updated Models

GymSession

Added field:

  • recurringEventId: ObjectId (optional) - Reference to RecurringEvent if part of a series

Workshop

Added field:

  • recurringEventId: ObjectId (optional) - Reference to RecurringEvent if part of a series

API Endpoints

Gym Sessions

Create Recurring Gym Session Series

POST /api/facilities/gymsessions/recurring
Authorization: Bearer <token>
Role Required: events_office

Request Body:

{
  "type": "yoga",
  "time": "09:00",
  "duration": 60,
  "maxParticipants": 20,
  "startDate": "2025-01-15T00:00:00Z",
  "endDate": "2025-04-30T00:00:00Z",
  "dayOfWeek": "Monday"
}

Response (201 Created):

{
  "status": "success",
  "data": {
    "recurringEvent": {
      "id": "650abc123def456",
      "eventType": "gym_session",
      "pattern": {
        "dayOfWeek": "Monday",
        "startDate": "2025-01-15T00:00:00Z",
        "endDate": "2025-04-30T00:00:00Z"
      },
      "totalOccurrences": 15,
      "sessions": [
        {
          "_id": "650abc123def457",
          "date": "2025-01-20T00:00:00Z",
          "time": "09:00",
          "duration": 60,
          "type": "yoga",
          "maxParticipants": 20,
          "currentRegistrations": 0,
          "creator": "650abc123def000"
        },
        // ... more sessions
      ]
    }
  }
}

Error Responses:

  • 400: Invalid input (validation errors, date ranges, etc.)
  • 403: User is not Events Office
  • 500: Server error

Example Usage:

// Create 15 Yoga sessions every Monday from Jan 15 to Apr 30, 2025
const response = await fetch('/api/facilities/gymsessions/recurring', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': `Bearer ${token}`
  },
  body: JSON.stringify({
    type: 'yoga',
    time: '09:00',
    duration: 60,
    maxParticipants: 20,
    startDate: '2025-01-15',
    endDate: '2025-04-30',
    dayOfWeek: 'Monday'
  })
});

Workshops

Create Recurring Workshop Series

POST /api/workshops/recurring
Authorization: Bearer <token>
Role Required: professor

Request Body:

{
  "name": "Python Advanced Concepts",
  "location": "GUC Cairo",
  "startTime": "14:00",
  "endTime": "16:00",
  "shortDescription": "Advanced Python programming course",
  "fullAgenda": "Decorators, metaclasses, async/await patterns...",
  "faculty": "Engineering",
  "professors": ["Dr. Ahmed"],
  "requiredBudget": 1000,
  "fundingSource": "GUC",
  "extraResources": "Laptops, projector",
  "capacity": 30,
  "registrationDeadline": "2025-01-13T00:00:00Z",
  "startDate": "2025-01-15T00:00:00Z",
  "endDate": "2025-04-30T00:00:00Z",
  "dayOfWeek": "Friday"
}

Response (201 Created):

{
  "status": "success",
  "data": {
    "recurringEvent": {
      "id": "650abc123def789",
      "eventType": "workshop",
      "pattern": {
        "dayOfWeek": "Friday",
        "startDate": "2025-01-15T00:00:00Z",
        "endDate": "2025-04-30T00:00:00Z"
      },
      "totalOccurrences": 13,
      "workshops": [
        {
          "_id": "650abc123def790",
          "name": "Python Advanced Concepts",
          "location": "GUC Cairo",
          "startAt": "2025-01-17T00:00:00Z",
          "endAt": "2025-01-18T00:00:00Z",
          "startTime": "14:00",
          "endTime": "16:00",
          "shortDescription": "Advanced Python programming course",
          "faculty": "Engineering",
          "professors": ["Dr. Ahmed", "Prof. Name"],
          "capacity": 30,
          "status": "pending",
          "creator": "650abc123def111"
        },
        // ... more workshops
      ]
    }
  }
}

Error Responses:

  • 400: Invalid input or date validation
  • 403: User is not a professor
  • 500: Server error

Example Usage:

// Create 13 workshops every Friday from Jan 15 to Apr 30, 2025
const response = await fetch('/api/workshops/recurring', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': `Bearer ${token}`
  },
  body: JSON.stringify({
    name: 'Python Advanced Concepts',
    location: 'GUC Cairo',
    startTime: '14:00',
    endTime: '16:00',
    shortDescription: 'Advanced Python programming course',
    fullAgenda: 'Detailed agenda here...',
    faculty: 'Engineering',
    professors: ['Dr. Ahmed'],
    requiredBudget: 1000,
    fundingSource: 'GUC',
    capacity: 30,
    registrationDeadline: '2025-01-13',
    startDate: '2025-01-15',
    endDate: '2025-04-30',
    dayOfWeek: 'Friday'
  })
});

Utility Functions

calculateRecurringDates(startDate, endDate, dayOfWeek)

Calculates all dates matching a weekly pattern.

Parameters:

  • startDate: Date - Start date (inclusive)
  • endDate: Date - End date (inclusive)
  • dayOfWeek: String - Day name ('Monday', 'Tuesday', etc.)

Returns:

  • Array of Date objects

Example:

const { calculateRecurringDates } = require('../utils/recurringEventUtils');

const dates = calculateRecurringDates(
  new Date('2025-01-15'),
  new Date('2025-04-30'),
  'Monday'
);
// Returns: [2025-01-20, 2025-01-27, 2025-02-03, ...]

validateRecurringParams(params)

Validates recurring event parameters.

Parameters:

  • params: Object with startDate, endDate, dayOfWeek

Returns:

  • { valid: true } or { valid: false, error: string }

deleteRecurringGymSessions(recurringEventId)

Deletes all gym sessions in a recurring series.

Parameters:

  • recurringEventId: ObjectId

Returns:

  • { success: true, deletedCount: number } or { success: false, error: string }

deleteRecurringWorkshops(recurringEventId)

Deletes all workshops in a recurring series.

Parameters:

  • recurringEventId: ObjectId

Returns:

  • { success: true, deletedCount: number } or { success: false, error: string }

getRecurringEventDetails(recurringEventId)

Gets recurring event details with all related events.

Parameters:

  • recurringEventId: ObjectId

Returns:

  • { success: true, data: { recurringEvent, relatedEvents, count } } or error

Validation Rules

Input Validation

  • startDate & endDate: ISO 8601 format, startDate must be before endDate
  • dayOfWeek: One of ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']
  • type (gym): One of ['yoga', 'pilates', 'aerobics', 'zumba', 'cross_circuit', 'kick_boxing']
  • time (gym): HH:MM format (00:00 - 23:59)
  • duration: Integer >= 30 minutes
  • maxParticipants: Integer >= 1
  • capacity (workshop): Integer >= 1
  • registrationDeadline (workshop): Must be before startAt

Business Rules

  • Events Office only: Can create recurring gym sessions
  • Professors only: Can create recurring workshops
  • Each individual event can be edited/deleted independently
  • Deleting a recurring series cancels all occurrences

Database Queries

Find All Gym Sessions in a Series

const sessions = await GymSession.find({ recurringEventId });

Find All Workshops in a Series

const workshops = await Workshop.find({ recurringEventId });

Get Recurring Event with Details

const recurring = await RecurringEvent.findById(id)
  .populate('creator', 'firstName lastName email');

Notes

  • Each created event (GymSession or Workshop) is a separate document
  • Events can be edited or deleted individually without affecting the series
  • Deleting the RecurringEvent marks all linked events as deleted
  • The system supports weekly patterns only (no daily, monthly, custom patterns)
  • All times are stored in UTC, conversion to local time happens on frontend