496 lines
16 KiB
Go
496 lines
16 KiB
Go
// 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
|
|
}
|