// handlers/event_handler.go package handlers import ( "api_bb/internal/models" "api_bb/internal/service" "api_bb/pkg/logger" "api_bb/pkg/middleware" "api_bb/pkg/utils" "encoding/json" "net/http" "strconv" "time" "go.uber.org/zap" ) type EventHandler struct { logger logger.LoggerInterface eventService service.EventService } func NewEventHandler(eventService service.EventService) *EventHandler { return &EventHandler{ logger: logger.NewWrapper(logger.Get().With(zap.String("handler", "event"))), eventService: eventService, } } // CreateEventRequest - DTO для создания события type CreateEventRequest struct { Title string `json:"title" validate:"required,min=5,max=255"` Description string `json:"description" validate:"required,min=10"` Date time.Time `json:"date" validate:"required"` Location string `json:"location" validate:"required,max=255"` Type models.EventType `json:"type" validate:"required,oneof=race training social workshop"` Distance string `json:"distance" validate:"max=50"` MaxParticipants int `json:"max_participants" validate:"min=0"` RegistrationOpen bool `json:"registration_open"` Image string `json:"image" validate:"max=500"` } // UpdateEventRequest - DTO для обновления события type UpdateEventRequest struct { Title string `json:"title" validate:"required,min=5,max=255"` Description string `json:"description" validate:"required,min=10"` Date time.Time `json:"date" validate:"required"` Location string `json:"location" validate:"required,max=255"` Type models.EventType `json:"type" validate:"required,oneof=race training social workshop"` Distance string `json:"distance" validate:"max=50"` MaxParticipants int `json:"max_participants" validate:"min=0"` RegistrationOpen bool `json:"registration_open"` Image string `json:"image" validate:"max=500"` } // EventResponse - DTO для ответа с событием type EventResponse struct { ID uint `json:"id"` Title string `json:"title"` Description string `json:"description"` Date time.Time `json:"date"` Location string `json:"location"` Type models.EventType `json:"type"` Distance string `json:"distance"` ParticipantsCount int `json:"participants_count"` MaxParticipants int `json:"max_participants"` RegistrationOpen bool `json:"registration_open"` Image string `json:"image"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } // CreateEvent создает новое событие func (h *EventHandler) CreateEvent(w http.ResponseWriter, r *http.Request) { h.logger.Info("handling create event request", zap.String("method", r.Method), zap.String("path", r.URL.Path), zap.String("remote_addr", r.RemoteAddr), ) // Проверяем аутентификацию user, ok := middleware.GetUserFromContext(r.Context()) if !ok { h.logger.Warn("create event failed - authentication required") utils.RespondWithError(w, http.StatusUnauthorized, "Authentication required") return } // Проверяем права доступа (только админы могут создавать события) if user.Role != "admin" { h.logger.Warn("create event failed - insufficient permissions", zap.Uint("user_id", user.ID), zap.String("user_role", user.Role), ) utils.RespondWithError(w, http.StatusForbidden, "Insufficient permissions") return } var req CreateEventRequest if err := utils.DecodeJSONBody(w, r, &req); err != nil { h.logger.Error("failed to decode request body", zap.Error(err)) utils.RespondWithError(w, http.StatusBadRequest, "Invalid request payload: "+err.Error()) return } // Валидация if err := utils.ValidateStruct(req); err != nil { h.logger.Warn("validation failed for create event", zap.Error(err)) utils.RespondWithValidationError(w, err) return } // Создаем модель события event := &models.Event{ Title: req.Title, Description: req.Description, Date: req.Date, Location: req.Location, Type: req.Type, Distance: req.Distance, MaxParticipants: req.MaxParticipants, RegistrationOpen: req.RegistrationOpen, Image: req.Image, } if err := h.eventService.CreateEvent(event); err != nil { h.logger.Error("failed to create event", zap.Error(err)) utils.RespondWithError(w, http.StatusInternalServerError, "Failed to create event: "+err.Error()) return } h.logger.Info("event created successfully", zap.Uint("event_id", event.ID), zap.String("title", event.Title), ) utils.RespondWithJSON(w, http.StatusCreated, map[string]interface{}{ "message": "Event created successfully", "event": toEventResponse(event), }) } // GetEvent возвращает событие по ID func (h *EventHandler) GetEvent(w http.ResponseWriter, r *http.Request) { h.logger.Info("handling get event request", zap.String("method", r.Method), zap.String("path", r.URL.Path), zap.String("remote_addr", r.RemoteAddr), ) // Извлекаем ID события из URL параметров eventID, err := strconv.ParseUint(r.PathValue("id"), 10, 32) if err != nil { h.logger.Warn("invalid event ID", zap.Error(err)) utils.RespondWithError(w, http.StatusBadRequest, "Invalid event ID") return } event, err := h.eventService.GetEventByID(uint(eventID)) if err != nil { h.logger.Warn("event not found", zap.Uint("event_id", uint(eventID)), zap.Error(err), ) utils.RespondWithError(w, http.StatusNotFound, "Event not found") return } h.logger.Info("event retrieved successfully", zap.Uint("event_id", uint(eventID)), zap.String("title", event.Title), ) utils.RespondWithJSON(w, http.StatusOK, toEventResponse(event)) } // GetAllEvents возвращает все события func (h *EventHandler) GetAllEvents(w http.ResponseWriter, r *http.Request) { h.logger.Info("handling get all events request", zap.String("method", r.Method), zap.String("path", r.URL.Path), zap.String("remote_addr", r.RemoteAddr), ) events, err := h.eventService.GetAllEvents() if err != nil { h.logger.Error("failed to get events", zap.Error(err)) utils.RespondWithError(w, http.StatusInternalServerError, "Failed to get events: "+err.Error()) return } // Преобразуем в response формат var eventResponses []EventResponse for _, event := range events { eventResponses = append(eventResponses, toEventResponse(&event)) } h.logger.Info("events list retrieved successfully", zap.Int("events_count", len(eventResponses)), ) utils.RespondWithJSON(w, http.StatusOK, eventResponses) } // UpdateEvent обновляет событие func (h *EventHandler) UpdateEvent(w http.ResponseWriter, r *http.Request) { h.logger.Info("handling update event request", zap.String("method", r.Method), zap.String("path", r.URL.Path), zap.String("remote_addr", r.RemoteAddr), ) // Проверяем аутентификацию и права user, ok := middleware.GetUserFromContext(r.Context()) if !ok { h.logger.Warn("update event failed - authentication required") utils.RespondWithError(w, http.StatusUnauthorized, "Authentication required") return } if user.Role != "admin" { h.logger.Warn("update event failed - insufficient permissions", zap.Uint("user_id", user.ID), zap.String("user_role", user.Role), ) utils.RespondWithError(w, http.StatusForbidden, "Insufficient permissions") return } // Извлекаем ID события eventID, err := strconv.ParseUint(r.PathValue("id"), 10, 32) if err != nil { h.logger.Warn("invalid event ID", zap.Error(err)) utils.RespondWithError(w, http.StatusBadRequest, "Invalid event ID") return } var req UpdateEventRequest if err := utils.DecodeJSONBody(w, r, &req); err != nil { h.logger.Error("failed to decode request body", zap.Error(err)) utils.RespondWithError(w, http.StatusBadRequest, "Invalid request payload: "+err.Error()) return } // Валидация if err := utils.ValidateStruct(req); err != nil { h.logger.Warn("validation failed for update event", zap.Error(err)) utils.RespondWithValidationError(w, err) return } // Создаем модель события для обновления event := &models.Event{ ID: uint(eventID), Title: req.Title, Description: req.Description, Date: req.Date, Location: req.Location, Type: req.Type, Distance: req.Distance, MaxParticipants: req.MaxParticipants, RegistrationOpen: req.RegistrationOpen, Image: req.Image, } if err := h.eventService.UpdateEvent(event); err != nil { h.logger.Error("failed to update event", zap.Uint("event_id", uint(eventID)), zap.Error(err), ) utils.RespondWithError(w, http.StatusInternalServerError, "Failed to update event: "+err.Error()) return } h.logger.Info("event updated successfully", zap.Uint("event_id", uint(eventID)), zap.String("title", event.Title), ) utils.RespondWithJSON(w, http.StatusOK, map[string]interface{}{ "message": "Event updated successfully", "event": toEventResponse(event), }) } // DeleteEvent удаляет событие func (h *EventHandler) DeleteEvent(w http.ResponseWriter, r *http.Request) { h.logger.Info("handling delete event request", zap.String("method", r.Method), zap.String("path", r.URL.Path), zap.String("remote_addr", r.RemoteAddr), ) // Проверяем аутентификацию и права user, ok := middleware.GetUserFromContext(r.Context()) if !ok { h.logger.Warn("delete event failed - authentication required") utils.RespondWithError(w, http.StatusUnauthorized, "Authentication required") return } if user.Role != "admin" { h.logger.Warn("delete event failed - insufficient permissions", zap.Uint("user_id", user.ID), zap.String("user_role", user.Role), ) utils.RespondWithError(w, http.StatusForbidden, "Insufficient permissions") return } // Извлекаем ID события eventID, err := strconv.ParseUint(r.PathValue("id"), 10, 32) if err != nil { h.logger.Warn("invalid event ID", zap.Error(err)) utils.RespondWithError(w, http.StatusBadRequest, "Invalid event ID") return } if err := h.eventService.DeleteEvent(uint(eventID)); err != nil { h.logger.Error("failed to delete event", zap.Uint("event_id", uint(eventID)), zap.Error(err), ) utils.RespondWithError(w, http.StatusInternalServerError, "Failed to delete event: "+err.Error()) return } h.logger.Info("event deleted successfully", zap.Uint("event_id", uint(eventID)), ) utils.RespondWithJSON(w, http.StatusOK, map[string]string{ "message": "Event deleted successfully", }) } // GetEventsByType возвращает события по типу func (h *EventHandler) GetEventsByType(w http.ResponseWriter, r *http.Request) { h.logger.Info("handling get events by type request", zap.String("method", r.Method), zap.String("path", r.URL.Path), zap.String("remote_addr", r.RemoteAddr), ) eventType := models.EventType(r.PathValue("type")) // Валидация типа события validTypes := []models.EventType{"race", "training", "social", "workshop"} if !isValidEventType(eventType, validTypes) { h.logger.Warn("invalid event type", zap.String("event_type", string(eventType))) utils.RespondWithError(w, http.StatusBadRequest, "Invalid event type") return } events, err := h.eventService.GetEventsByType(eventType) if err != nil { h.logger.Error("failed to get events by type", zap.String("event_type", string(eventType)), zap.Error(err), ) utils.RespondWithError(w, http.StatusInternalServerError, "Failed to get events: "+err.Error()) return } var eventResponses []EventResponse for _, event := range events { eventResponses = append(eventResponses, toEventResponse(&event)) } h.logger.Info("events by type retrieved successfully", zap.String("event_type", string(eventType)), zap.Int("events_count", len(eventResponses)), ) utils.RespondWithJSON(w, http.StatusOK, eventResponses) } // GetUpcomingEvents возвращает предстоящие события func (h *EventHandler) GetUpcomingEvents(w http.ResponseWriter, r *http.Request) { h.logger.Info("handling get upcoming events request", zap.String("method", r.Method), zap.String("path", r.URL.Path), zap.String("remote_addr", r.RemoteAddr), ) events, err := h.eventService.GetUpcomingEvents() if err != nil { h.logger.Error("failed to get upcoming events", zap.Error(err)) utils.RespondWithError(w, http.StatusInternalServerError, "Failed to get upcoming events: "+err.Error()) return } var eventResponses []EventResponse for _, event := range events { eventResponses = append(eventResponses, toEventResponse(&event)) } h.logger.Info("upcoming events retrieved successfully", zap.Int("events_count", len(eventResponses)), ) utils.RespondWithJSON(w, http.StatusOK, eventResponses) } // ToggleRegistrationStatus переключает статус регистрации func (h *EventHandler) ToggleRegistrationStatus(w http.ResponseWriter, r *http.Request) { h.logger.Info("handling toggle registration status request", zap.String("method", r.Method), zap.String("path", r.URL.Path), zap.String("remote_addr", r.RemoteAddr), ) // Проверяем аутентификацию и права user, ok := middleware.GetUserFromContext(r.Context()) if !ok { h.logger.Warn("toggle registration status failed - authentication required") utils.RespondWithError(w, http.StatusUnauthorized, "Authentication required") return } if user.Role != "admin" { h.logger.Warn("toggle registration status failed - insufficient permissions", zap.Uint("user_id", user.ID), zap.String("user_role", user.Role), ) utils.RespondWithError(w, http.StatusForbidden, "Insufficient permissions") return } // Извлекаем ID события eventID, err := strconv.ParseUint(r.PathValue("id"), 10, 32) if err != nil { h.logger.Warn("invalid event ID", zap.Error(err)) utils.RespondWithError(w, http.StatusBadRequest, "Invalid event ID") return } var req struct { RegistrationOpen bool `json:"registration_open" validate:"required"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { h.logger.Error("failed to decode request body", zap.Error(err)) utils.RespondWithError(w, http.StatusBadRequest, "Invalid request payload") return } if err := h.eventService.ToggleRegistrationStatus(uint(eventID), req.RegistrationOpen); err != nil { h.logger.Error("failed to toggle registration status", zap.Uint("event_id", uint(eventID)), zap.Bool("registration_open", req.RegistrationOpen), zap.Error(err), ) utils.RespondWithError(w, http.StatusInternalServerError, "Failed to toggle registration status: "+err.Error()) return } h.logger.Info("registration status toggled successfully", zap.Uint("event_id", uint(eventID)), zap.Bool("registration_open", req.RegistrationOpen), ) utils.RespondWithJSON(w, http.StatusOK, map[string]string{ "message": "Registration status updated successfully", }) } // toEventResponse преобразует модель события в response DTO func toEventResponse(event *models.Event) EventResponse { return EventResponse{ ID: event.ID, Title: event.Title, Description: event.Description, Date: event.Date, Location: event.Location, Type: event.Type, Distance: event.Distance, ParticipantsCount: event.ParticipantsCount, MaxParticipants: event.MaxParticipants, RegistrationOpen: event.RegistrationOpen, Image: event.Image, CreatedAt: event.CreatedAt, UpdatedAt: event.UpdatedAt, } } // isValidEventType проверяет валидность типа события func isValidEventType(eventType models.EventType, validTypes []models.EventType) bool { for _, validType := range validTypes { if eventType == validType { return true } } return false }