mirror of
https://github.com/Dvorinka/MyClubServer.git
synced 2026-06-04 02:32:57 +00:00
hot fix #1
This commit is contained in:
@@ -0,0 +1,578 @@
|
||||
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})
|
||||
}
|
||||
Reference in New Issue
Block a user