15357fd3c0
yalarbacreate and moove into new directories for BegushiyBashkir and yalarbacreate and moove into new directories for BegushiyBashkir and yalarbacreate and moove into new directories for BegushiyBashkir and yalarbacreate and moove into new directories for BegushiyBashkir and yalarbacreate and moove into new directories for BegushiyBashkir and yalarbacreate and moove into new directories for BegushiyBashkir and yalarbacreate and moove into new directories for BegushiyBashkir and yalarbacreate and moove into new directories for BegushiyBashkir and yalarba
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
|
|
}
|