mirror of
https://github.com/Dvorinka/MyClubServer.git
synced 2026-06-03 18:22:57 +00:00
579 lines
16 KiB
Go
579 lines
16 KiB
Go
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})
|
|
}
|