package controllers import ( "fmt" "time" "github.com/gin-gonic/gin" "gorm.io/gorm" "fotbal-club/internal/models" "fotbal-club/pkg/database" ) // EquipmentController handles equipment management operations type EquipmentController struct { db *gorm.DB } // NewEquipmentController creates a new equipment controller func NewEquipmentController() *EquipmentController { return &EquipmentController{ db: database.GetDB(), } } // EquipmentListRequest represents query parameters for equipment listing type EquipmentListRequest struct { FacilityID uint `form:"facility_id"` Category string `form:"category"` Status string `form:"status"` Page int `form:"page,default=1"` Limit int `form:"limit,default=20"` Search string `form:"search"` } // GetEquipment handles GET /api/v1/admin/equipment func (ec *EquipmentController) GetEquipment(c *gin.Context) { var req EquipmentListRequest if err := c.ShouldBindQuery(&req); err != nil { c.JSON(400, gin.H{"error": err.Error()}) return } var equipment []models.FacilityEquipment query := ec.db.Model(&models.FacilityEquipment{}) // Apply filters if req.FacilityID > 0 { query = query.Where("facility_id = ?", req.FacilityID) } if req.Category != "" { query = query.Where("category = ?", req.Category) } if req.Status != "" { query = query.Where("status = ?", req.Status) } if req.Search != "" { query = query.Where("name ILIKE ? OR description ILIKE ?", "%"+req.Search+"%", "%"+req.Search+"%") } // Count total var total int64 query.Count(&total) // Apply pagination offset := (req.Page - 1) * req.Limit query = query.Offset(offset).Limit(req.Limit) if err := query.Preload("Facility").Find(&equipment).Error; err != nil { c.JSON(500, gin.H{"error": "Failed to fetch equipment"}) return } c.JSON(200, gin.H{ "equipment": equipment, "total": total, "page": req.Page, "limit": req.Limit, }) } // CreateEquipment handles POST /api/v1/admin/equipment func (ec *EquipmentController) CreateEquipment(c *gin.Context) { var equipment models.FacilityEquipment if err := c.ShouldBindJSON(&equipment); err != nil { c.JSON(400, gin.H{"error": err.Error()}) return } // Set default values if equipment.Status == "" { equipment.Status = models.EquipmentStatusAvailable } if equipment.Available == 0 { equipment.Available = equipment.Quantity } if err := ec.db.Create(&equipment).Error; err != nil { c.JSON(500, gin.H{"error": "Failed to create equipment"}) return } c.JSON(201, gin.H{"equipment": equipment}) } // UpdateEquipment handles PUT /api/v1/admin/equipment/:id func (ec *EquipmentController) UpdateEquipment(c *gin.Context) { id := c.Param("id") var equipment models.FacilityEquipment if err := ec.db.First(&equipment, id).Error; err != nil { if err == gorm.ErrRecordNotFound { c.JSON(404, gin.H{"error": "Equipment not found"}) } else { c.JSON(500, gin.H{"error": "Failed to fetch equipment"}) } return } var updates models.FacilityEquipment if err := c.ShouldBindJSON(&updates); err != nil { c.JSON(400, gin.H{"error": err.Error()}) return } if err := ec.db.Model(&equipment).Updates(updates).Error; err != nil { c.JSON(500, gin.H{"error": "Failed to update equipment"}) return } // Fetch updated equipment if err := ec.db.Preload("Facility").First(&equipment, id).Error; err != nil { c.JSON(500, gin.H{"error": "Failed to fetch updated equipment"}) return } c.JSON(200, gin.H{"equipment": equipment}) } // DeleteEquipment handles DELETE /api/v1/admin/equipment/:id func (ec *EquipmentController) DeleteEquipment(c *gin.Context) { id := c.Param("id") if err := ec.db.Delete(&models.FacilityEquipment{}, id).Error; err != nil { c.JSON(500, gin.H{"error": "Failed to delete equipment"}) return } c.JSON(200, gin.H{"message": "Equipment deleted successfully"}) } // MaintenanceController handles maintenance scheduling operations type MaintenanceController struct { db *gorm.DB } // NewMaintenanceController creates a new maintenance controller func NewMaintenanceController() *MaintenanceController { return &MaintenanceController{ db: database.GetDB(), } } // MaintenanceListRequest represents query parameters for maintenance listing type MaintenanceListRequest struct { FacilityID uint `form:"facility_id"` Type string `form:"type"` Status string `form:"status"` StartDate string `form:"start_date"` EndDate string `form:"end_date"` Page int `form:"page,default=1"` Limit int `form:"limit,default=20"` } // GetMaintenance handles GET /api/v1/admin/maintenance func (mc *MaintenanceController) GetMaintenance(c *gin.Context) { var req MaintenanceListRequest if err := c.ShouldBindQuery(&req); err != nil { c.JSON(400, gin.H{"error": err.Error()}) return } var maintenance []models.FacilityMaintenance query := mc.db.Model(&models.FacilityMaintenance{}) // Apply filters if req.FacilityID > 0 { query = query.Where("facility_id = ?", req.FacilityID) } if req.Type != "" { query = query.Where("type = ?", req.Type) } if req.Status != "" { query = query.Where("status = ?", req.Status) } if req.StartDate != "" { query = query.Where("scheduled_date >= ?", req.StartDate) } if req.EndDate != "" { query = query.Where("scheduled_date <= ?", req.EndDate) } // Count total var total int64 query.Count(&total) // Apply pagination offset := (req.Page - 1) * req.Limit query = query.Offset(offset).Limit(req.Limit) if err := query.Preload("Facility").Order("scheduled_date ASC").Find(&maintenance).Error; err != nil { c.JSON(500, gin.H{"error": "Failed to fetch maintenance records"}) return } c.JSON(200, gin.H{ "maintenance": maintenance, "total": total, "page": req.Page, "limit": req.Limit, }) } // CreateMaintenance handles POST /api/v1/admin/maintenance func (mc *MaintenanceController) CreateMaintenance(c *gin.Context) { var maintenance models.FacilityMaintenance if err := c.ShouldBindJSON(&maintenance); err != nil { c.JSON(400, gin.H{"error": err.Error()}) return } // Set default status if maintenance.Status == "" { maintenance.Status = "scheduled" } if err := mc.db.Create(&maintenance).Error; err != nil { c.JSON(500, gin.H{"error": "Failed to create maintenance record"}) return } // If facility is unavailable during maintenance, update facility status if maintenance.IsFacilityUnavailable && maintenance.ScheduledDate != nil { mc.db.Model(&models.Facility{}).Where("id = ?", maintenance.FacilityID). Update("status", models.FacilityStatusMaintenance) } c.JSON(201, gin.H{"maintenance": maintenance}) } // UpdateMaintenance handles PUT /api/v1/admin/maintenance/:id func (mc *MaintenanceController) UpdateMaintenance(c *gin.Context) { id := c.Param("id") var maintenance models.FacilityMaintenance if err := mc.db.First(&maintenance, id).Error; err != nil { if err == gorm.ErrRecordNotFound { c.JSON(404, gin.H{"error": "Maintenance record not found"}) } else { c.JSON(500, gin.H{"error": "Failed to fetch maintenance record"}) } return } var updates models.FacilityMaintenance if err := c.ShouldBindJSON(&updates); err != nil { c.JSON(400, gin.H{"error": err.Error()}) return } // Store old values for comparison oldStatus := maintenance.Status oldIsUnavailable := maintenance.IsFacilityUnavailable if err := mc.db.Model(&maintenance).Updates(updates).Error; err != nil { c.JSON(500, gin.H{"error": "Failed to update maintenance record"}) return } // Fetch updated maintenance if err := mc.db.Preload("Facility").First(&maintenance, id).Error; err != nil { c.JSON(500, gin.H{"error": "Failed to fetch updated maintenance record"}) return } // Update facility status if maintenance is completed if oldStatus != "completed" && maintenance.Status == "completed" && oldIsUnavailable { mc.db.Model(&models.Facility{}).Where("id = ?", maintenance.FacilityID). Update("status", models.FacilityStatusActive) } c.JSON(200, gin.H{"maintenance": maintenance}) } // WeatherController handles weather integration for outdoor facilities type WeatherController struct { db *gorm.DB apiKey string // OpenWeatherMap API key } // NewWeatherController creates a new weather controller func NewWeatherController(apiKey string) *WeatherController { return &WeatherController{ db: database.GetDB(), apiKey: apiKey, } } // WeatherResponse represents weather data type WeatherResponse struct { DateTime time.Time `json:"date_time"` Temperature float64 `json:"temperature"` Humidity float64 `json:"humidity"` Precipitation float64 `json:"precipitation"` WindSpeed float64 `json:"wind_speed"` WindDirection int `json:"wind_direction"` WeatherCode string `json:"weather_code"` Description string `json:"description"` IsSuitable bool `json:"is_suitable"` Recommendations string `json:"recommendations"` } // OpenWeatherMapResponse represents API response from OpenWeatherMap type OpenWeatherMapResponse struct { List []struct { Dt int64 `json:"dt"` Main struct { Temp float64 `json:"temp"` Humidity int `json:"humidity"` } `json:"main"` Weather []struct { ID int `json:"id"` Main string `json:"main"` Description string `json:"description"` } `json:"weather"` Wind struct { Speed float64 `json:"speed"` Deg int `json:"deg"` } `json:"wind"` Rain struct { OneHour float64 `json:"1h"` } `json:"rain"` Snow struct { OneHour float64 `json:"1h"` } `json:"snow"` } `json:"list"` } // GetWeatherForecast handles GET /api/v1/facilities/:id/weather func (wc *WeatherController) GetWeatherForecast(c *gin.Context) { facilityID := c.Param("id") // Get facility var facility models.Facility if err := wc.db.First(&facility, facilityID).Error; err != nil { c.JSON(404, gin.H{"error": "Facility not found"}) return } // Only provide weather for outdoor facilities if !facility.IsOutdoor { c.JSON(400, gin.H{"error": "Weather data only available for outdoor facilities"}) return } // Try to get cached weather data first var cachedWeather []models.WeatherCondition twoHoursAgo := time.Now().Add(-2 * time.Hour) if err := wc.db.Where("facility_id = ? AND created_at > ?", facilityID, twoHoursAgo). Order("date_time ASC").Find(&cachedWeather).Error; err == nil && len(cachedWeather) > 0 { var responses []WeatherResponse for _, weather := range cachedWeather { responses = append(responses, WeatherResponse{ DateTime: weather.DateTime, Temperature: weather.Temperature, Humidity: weather.Humidity, Precipitation: weather.Precipitation, WindSpeed: weather.WindSpeed, WindDirection: weather.WindDirection, WeatherCode: weather.WeatherCode, Description: weather.Description, IsSuitable: weather.IsSuitable, Recommendations: weather.Recommendations, }) } c.JSON(200, gin.H{"weather": responses}) return } // Fetch fresh weather data if no recent cache if wc.apiKey == "" { c.JSON(503, gin.H{"error": "Weather service not configured"}) return } // For demo purposes, return mock data if API key is not set // In production, you would call OpenWeatherMap API here mockWeather := wc.generateMockWeather(facility) // Cache the weather data for _, weather := range mockWeather { wc.db.Create(&models.WeatherCondition{ FacilityID: facility.ID, DateTime: weather.DateTime, Temperature: weather.Temperature, Humidity: weather.Humidity, Precipitation: weather.Precipitation, WindSpeed: weather.WindSpeed, WindDirection: weather.WindDirection, WeatherCode: weather.WeatherCode, Description: weather.Description, IsSuitable: weather.IsSuitable, Recommendations: weather.Recommendations, }) } c.JSON(200, gin.H{"weather": mockWeather}) } // generateMockWeather creates sample weather data for demonstration func (wc *WeatherController) generateMockWeather(facility models.Facility) []WeatherResponse { var weather []WeatherResponse now := time.Now() // Generate 5-day forecast for i := 0; i < 5; i++ { date := now.AddDate(0, 0, i) // Generate 3 time slots per day (morning, afternoon, evening) for _, hour := range []int{9, 15, 18} { dateTime := time.Date(date.Year(), date.Month(), date.Day(), hour, 0, 0, 0, date.Location()) // Mock weather conditions temp := 15.0 + float64(i) + float64(hour/10) humidity := 60.0 + float64(i*5) windSpeed := 10.0 + float64(i) precipitation := 0.0 isSuitable := true recommendations := "Dobré podmínky pro trénink" // Add some rain on random days if i == 2 && hour == 15 { precipitation = 5.0 isSuitable = false recommendations = "Déšť - doporučeno přerušit trénink nebo přesunout dovnitř" } weather = append(weather, WeatherResponse{ DateTime: dateTime, Temperature: temp, Humidity: humidity, Precipitation: precipitation, WindSpeed: windSpeed, WindDirection: 180 + i*10, WeatherCode: "800", Description: "Jasno", IsSuitable: isSuitable, Recommendations: recommendations, }) } } return weather } // BookingCalendarController handles calendar view for bookings type BookingCalendarController struct { db *gorm.DB } // NewBookingCalendarController creates a new booking calendar controller func NewBookingCalendarController() *BookingCalendarController { return &BookingCalendarController{ db: database.GetDB(), } } // CalendarEvent represents a calendar event type CalendarEvent struct { ID uint `json:"id"` Title string `json:"title"` Start time.Time `json:"start"` End time.Time `json:"end"` Type string `json:"type"` // "booking", "maintenance", "unavailable" Status string `json:"status"` FacilityName string `json:"facility_name"` UserEmail string `json:"user_email,omitempty"` Color string `json:"color"` // For calendar display } // GetCalendarEvents handles GET /api/v1/facilities/calendar func (bcc *BookingCalendarController) GetCalendarEvents(c *gin.Context) { // Parse date range startDate := c.Query("start") endDate := c.Query("end") facilityID := c.Query("facility_id") if startDate == "" || endDate == "" { c.JSON(400, gin.H{"error": "start and end parameters are required"}) return } start, err := time.Parse(time.RFC3339, startDate) if err != nil { c.JSON(400, gin.H{"error": "Invalid start date format"}) return } end, err := time.Parse(time.RFC3339, endDate) if err != nil { c.JSON(400, gin.H{"error": "Invalid end date format"}) return } var events []CalendarEvent // Get bookings var bookings []models.FacilityBooking bookingQuery := bcc.db.Preload("Facility").Preload("User"). Where("start_time >= ? AND end_time <= ? AND status NOT IN (?, ?)", start, end, string(models.BookingStatusCancelled), string(models.BookingStatusNoShow)) if facilityID != "" { bookingQuery = bookingQuery.Where("facility_id = ?", facilityID) } bookingQuery.Find(&bookings) for _, booking := range bookings { color := "#3B82F6" // Blue for confirmed bookings if booking.Status == models.BookingStatusPending { color = "#F59E0B" // Orange for pending } events = append(events, CalendarEvent{ ID: booking.ID, Title: booking.Title, Start: booking.StartTime, End: booking.EndTime, Type: "booking", Status: string(booking.Status), FacilityName: booking.Facility.Name, UserEmail: booking.User.Email, Color: color, }) } // Get maintenance var maintenance []models.FacilityMaintenance maintenanceQuery := bcc.db.Preload("Facility"). Where("scheduled_date >= ? AND scheduled_date <= ? AND is_facility_unavailable = ?", start, end, true) if facilityID != "" { maintenanceQuery = maintenanceQuery.Where("facility_id = ?", facilityID) } maintenanceQuery.Find(&maintenance) for _, m := range maintenance { endTime := m.ScheduledDate.Add(time.Duration(m.EstimatedDuration) * time.Minute) events = append(events, CalendarEvent{ ID: m.ID, Title: fmt.Sprintf("Údržba: %s", m.Title), Start: *m.ScheduledDate, End: endTime, Type: "maintenance", Status: m.Status, FacilityName: m.Facility.Name, Color: "#EF4444", // Red for maintenance }) } c.JSON(200, gin.H{"events": events}) }