diff --git a/serv_nginx/api_bb/internal/database/migrate.go b/serv_nginx/api_bb/internal/database/migrate.go index 452f029..5c1a3e9 100644 --- a/serv_nginx/api_bb/internal/database/migrate.go +++ b/serv_nginx/api_bb/internal/database/migrate.go @@ -22,6 +22,8 @@ func (d *Database) Migrate() error { &models.UserStats{}, &models.Workout{}, &models.Achievement{}, + &models.Event{}, + &models.EventRegistration{}, // Добавьте другие модели здесь } @@ -84,6 +86,10 @@ func getModelName(model interface{}) string { return "Тренировки пользователя" case *models.Achievement: return "Достижения пользователя" + case *models.Event: + return "Событие" + case *models.EventRegistration: + return "Администрирование события" default: return "Unknown" } diff --git a/serv_nginx/api_bb/internal/handlers/event_handler.go b/serv_nginx/api_bb/internal/handlers/event_handler.go new file mode 100644 index 0000000..75bf9c8 --- /dev/null +++ b/serv_nginx/api_bb/internal/handlers/event_handler.go @@ -0,0 +1,495 @@ +// 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 +} diff --git a/serv_nginx/api_bb/internal/handlers/event_registration_handler.go b/serv_nginx/api_bb/internal/handlers/event_registration_handler.go new file mode 100644 index 0000000..97b29ff --- /dev/null +++ b/serv_nginx/api_bb/internal/handlers/event_registration_handler.go @@ -0,0 +1,527 @@ +// handlers/event_registration_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" + + "go.uber.org/zap" +) + +type EventRegistrationHandler struct { + logger logger.LoggerInterface + registrationService service.EventRegistrationService +} + +func NewEventRegistrationHandler(registrationService service.EventRegistrationService) *EventRegistrationHandler { + return &EventRegistrationHandler{ + logger: logger.NewWrapper(logger.Get().With(zap.String("handler", "event_registration"))), + registrationService: registrationService, + } +} + +// RegisterForEventRequest - DTO для регистрации на событие +type RegisterForEventRequest struct { + EventID uint `json:"event_id" validate:"required"` + Notes string `json:"notes" validate:"max=500"` +} + +// UpdateRegistrationRequest - DTO для обновления регистрации +type UpdateRegistrationRequest struct { + Notes string `json:"notes" validate:"max=500"` +} + +// RegistrationResponse - DTO для ответа с регистрацией +type RegistrationResponse struct { + ID uint `json:"id"` + UserID uint `json:"user_id"` + EventID uint `json:"event_id"` + Status string `json:"status"` + Notes string `json:"notes"` + ResultTime string `json:"result_time"` + CreatedAt string `json:"created_at"` + UpdatedAt string `json:"updated_at"` + Event EventResponse `json:"event,omitempty"` +} + +// RegisterForEvent регистрирует пользователя на событие +func (h *EventRegistrationHandler) RegisterForEvent(w http.ResponseWriter, r *http.Request) { + h.logger.Info("handling register for 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("register for event failed - authentication required") + utils.RespondWithError(w, http.StatusUnauthorized, "Authentication required") + return + } + + var req RegisterForEventRequest + 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 register for event", zap.Error(err)) + utils.RespondWithValidationError(w, err) + return + } + + // Создаем модель регистрации + registration := &models.EventRegistration{ + UserID: user.ID, + EventID: req.EventID, + Status: "pending", + Notes: req.Notes, + } + + if err := h.registrationService.RegisterForEvent(registration); err != nil { + h.logger.Error("failed to register for event", + zap.Uint("user_id", user.ID), + zap.Uint("event_id", req.EventID), + zap.Error(err), + ) + statusCode := http.StatusInternalServerError + if err.Error() == "event not found" { + statusCode = http.StatusNotFound + } else if err.Error() == "user already registered for this event" { + statusCode = http.StatusConflict + } else if err.Error() == "registration is closed for this event" { + statusCode = http.StatusForbidden + } else if err.Error() == "event is full" { + statusCode = http.StatusConflict + } + utils.RespondWithError(w, statusCode, "Failed to register for event: "+err.Error()) + return + } + + h.logger.Info("user registered for event successfully", + zap.Uint("user_id", user.ID), + zap.Uint("event_id", req.EventID), + zap.Uint("registration_id", registration.ID), + ) + + utils.RespondWithJSON(w, http.StatusCreated, map[string]interface{}{ + "message": "Successfully registered for event", + "registration": toRegistrationResponse(registration), + }) +} + +// GetRegistration возвращает регистрацию по ID +func (h *EventRegistrationHandler) GetRegistration(w http.ResponseWriter, r *http.Request) { + h.logger.Info("handling get registration 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("get registration failed - authentication required") + utils.RespondWithError(w, http.StatusUnauthorized, "Authentication required") + return + } + + // Извлекаем ID регистрации + registrationID, err := strconv.ParseUint(r.PathValue("id"), 10, 32) + if err != nil { + h.logger.Warn("invalid registration ID", zap.Error(err)) + utils.RespondWithError(w, http.StatusBadRequest, "Invalid registration ID") + return + } + + registration, err := h.registrationService.GetRegistrationByID(uint(registrationID)) + if err != nil { + h.logger.Warn("registration not found", + zap.Uint("registration_id", uint(registrationID)), + zap.Error(err), + ) + utils.RespondWithError(w, http.StatusNotFound, "Registration not found") + return + } + + // Проверяем права доступа (пользователь может видеть только свои регистрации, админ - все) + if user.Role != "admin" && registration.UserID != user.ID { + h.logger.Warn("access denied to registration", + zap.Uint("user_id", user.ID), + zap.Uint("registration_user_id", registration.UserID), + ) + utils.RespondWithError(w, http.StatusForbidden, "Access denied") + return + } + + h.logger.Info("registration retrieved successfully", + zap.Uint("registration_id", uint(registrationID)), + zap.Uint("user_id", user.ID), + ) + + utils.RespondWithJSON(w, http.StatusOK, toRegistrationResponse(registration)) +} + +// GetUserRegistrations возвращает все регистрации пользователя +func (h *EventRegistrationHandler) GetUserRegistrations(w http.ResponseWriter, r *http.Request) { + h.logger.Info("handling get user registrations 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("get user registrations failed - authentication required") + utils.RespondWithError(w, http.StatusUnauthorized, "Authentication required") + return + } + + registrations, err := h.registrationService.GetRegistrationsByUserID(user.ID) + if err != nil { + h.logger.Error("failed to get user registrations", + zap.Uint("user_id", user.ID), + zap.Error(err), + ) + utils.RespondWithError(w, http.StatusInternalServerError, "Failed to get registrations: "+err.Error()) + return + } + + var registrationResponses []RegistrationResponse + for _, registration := range registrations { + registrationResponses = append(registrationResponses, toRegistrationResponse(®istration)) + } + + h.logger.Info("user registrations retrieved successfully", + zap.Uint("user_id", user.ID), + zap.Int("registrations_count", len(registrationResponses)), + ) + + utils.RespondWithJSON(w, http.StatusOK, registrationResponses) +} + +// GetEventRegistrations возвращает все регистрации на событие +func (h *EventRegistrationHandler) GetEventRegistrations(w http.ResponseWriter, r *http.Request) { + h.logger.Info("handling get event registrations 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("get event registrations failed - authentication required") + utils.RespondWithError(w, http.StatusUnauthorized, "Authentication required") + return + } + + if user.Role != "admin" { + h.logger.Warn("get event registrations 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("eventId"), 10, 32) + if err != nil { + h.logger.Warn("invalid event ID", zap.Error(err)) + utils.RespondWithError(w, http.StatusBadRequest, "Invalid event ID") + return + } + + registrations, err := h.registrationService.GetRegistrationsByEventID(uint(eventID)) + if err != nil { + h.logger.Error("failed to get event registrations", + zap.Uint("event_id", uint(eventID)), + zap.Error(err), + ) + utils.RespondWithError(w, http.StatusInternalServerError, "Failed to get registrations: "+err.Error()) + return + } + + var registrationResponses []RegistrationResponse + for _, registration := range registrations { + registrationResponses = append(registrationResponses, toRegistrationResponse(®istration)) + } + + h.logger.Info("event registrations retrieved successfully", + zap.Uint("event_id", uint(eventID)), + zap.Int("registrations_count", len(registrationResponses)), + ) + + utils.RespondWithJSON(w, http.StatusOK, registrationResponses) +} + +// CancelRegistration отменяет регистрацию +func (h *EventRegistrationHandler) CancelRegistration(w http.ResponseWriter, r *http.Request) { + h.logger.Info("handling cancel registration 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("cancel registration failed - authentication required") + utils.RespondWithError(w, http.StatusUnauthorized, "Authentication required") + return + } + + // Извлекаем ID регистрации + registrationID, err := strconv.ParseUint(r.PathValue("id"), 10, 32) + if err != nil { + h.logger.Warn("invalid registration ID", zap.Error(err)) + utils.RespondWithError(w, http.StatusBadRequest, "Invalid registration ID") + return + } + + // Проверяем права доступа + registration, err := h.registrationService.GetRegistrationByID(uint(registrationID)) + if err != nil { + h.logger.Warn("registration not found for cancellation", + zap.Uint("registration_id", uint(registrationID)), + zap.Error(err), + ) + utils.RespondWithError(w, http.StatusNotFound, "Registration not found") + return + } + + if user.Role != "admin" && registration.UserID != user.ID { + h.logger.Warn("access denied to cancel registration", + zap.Uint("user_id", user.ID), + zap.Uint("registration_user_id", registration.UserID), + ) + utils.RespondWithError(w, http.StatusForbidden, "Access denied") + return + } + + if err := h.registrationService.CancelRegistration(uint(registrationID)); err != nil { + h.logger.Error("failed to cancel registration", + zap.Uint("registration_id", uint(registrationID)), + zap.Error(err), + ) + utils.RespondWithError(w, http.StatusInternalServerError, "Failed to cancel registration: "+err.Error()) + return + } + + h.logger.Info("registration cancelled successfully", + zap.Uint("registration_id", uint(registrationID)), + zap.Uint("user_id", user.ID), + ) + + utils.RespondWithJSON(w, http.StatusOK, map[string]string{ + "message": "Registration cancelled successfully", + }) +} + +// UpdateRegistrationStatus обновляет статус регистрации +func (h *EventRegistrationHandler) UpdateRegistrationStatus(w http.ResponseWriter, r *http.Request) { + h.logger.Info("handling update 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("update registration status failed - authentication required") + utils.RespondWithError(w, http.StatusUnauthorized, "Authentication required") + return + } + + if user.Role != "admin" { + h.logger.Warn("update 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 регистрации + registrationID, err := strconv.ParseUint(r.PathValue("id"), 10, 32) + if err != nil { + h.logger.Warn("invalid registration ID", zap.Error(err)) + utils.RespondWithError(w, http.StatusBadRequest, "Invalid registration ID") + return + } + + var req struct { + Status string `json:"status" validate:"required,oneof=pending confirmed cancelled completed"` + } + + 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 := utils.ValidateStruct(req); err != nil { + h.logger.Warn("validation failed for update registration status", zap.Error(err)) + utils.RespondWithValidationError(w, err) + return + } + + if err := h.registrationService.UpdateRegistrationStatus(uint(registrationID), req.Status); err != nil { + h.logger.Error("failed to update registration status", + zap.Uint("registration_id", uint(registrationID)), + zap.String("status", req.Status), + zap.Error(err), + ) + utils.RespondWithError(w, http.StatusInternalServerError, "Failed to update registration status: "+err.Error()) + return + } + + h.logger.Info("registration status updated successfully", + zap.Uint("registration_id", uint(registrationID)), + zap.String("status", req.Status), + ) + + utils.RespondWithJSON(w, http.StatusOK, map[string]string{ + "message": "Registration status updated successfully", + }) +} + +// UpdateResultTime обновляет результат забега +func (h *EventRegistrationHandler) UpdateResultTime(w http.ResponseWriter, r *http.Request) { + h.logger.Info("handling update result time 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 result time failed - authentication required") + utils.RespondWithError(w, http.StatusUnauthorized, "Authentication required") + return + } + + if user.Role != "admin" { + h.logger.Warn("update result time failed - insufficient permissions", + zap.Uint("user_id", user.ID), + zap.String("user_role", user.Role), + ) + utils.RespondWithError(w, http.StatusForbidden, "Insufficient permissions") + return + } + + // Извлекаем ID регистрации + registrationID, err := strconv.ParseUint(r.PathValue("id"), 10, 32) + if err != nil { + h.logger.Warn("invalid registration ID", zap.Error(err)) + utils.RespondWithError(w, http.StatusBadRequest, "Invalid registration ID") + return + } + + var req struct { + ResultTime string `json:"result_time" validate:"required,max=20"` + } + + 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 := utils.ValidateStruct(req); err != nil { + h.logger.Warn("validation failed for update result time", zap.Error(err)) + utils.RespondWithValidationError(w, err) + return + } + + if err := h.registrationService.UpdateResultTime(uint(registrationID), req.ResultTime); err != nil { + h.logger.Error("failed to update result time", + zap.Uint("registration_id", uint(registrationID)), + zap.String("result_time", req.ResultTime), + zap.Error(err), + ) + utils.RespondWithError(w, http.StatusInternalServerError, "Failed to update result time: "+err.Error()) + return + } + + h.logger.Info("result time updated successfully", + zap.Uint("registration_id", uint(registrationID)), + zap.String("result_time", req.ResultTime), + ) + + utils.RespondWithJSON(w, http.StatusOK, map[string]string{ + "message": "Result time updated successfully", + }) +} + +// CheckEventAvailability проверяет доступность мест на событии +func (h *EventRegistrationHandler) CheckEventAvailability(w http.ResponseWriter, r *http.Request) { + h.logger.Info("handling check event availability request", + zap.String("method", r.Method), + zap.String("path", r.URL.Path), + zap.String("remote_addr", r.RemoteAddr), + ) + + // Извлекаем ID события + eventID, err := strconv.ParseUint(r.PathValue("eventId"), 10, 32) + if err != nil { + h.logger.Warn("invalid event ID", zap.Error(err)) + utils.RespondWithError(w, http.StatusBadRequest, "Invalid event ID") + return + } + + available, err := h.registrationService.CheckEventAvailability(uint(eventID)) + if err != nil { + h.logger.Error("failed to check event availability", + zap.Uint("event_id", uint(eventID)), + zap.Error(err), + ) + utils.RespondWithError(w, http.StatusInternalServerError, "Failed to check availability: "+err.Error()) + return + } + + h.logger.Info("event availability checked successfully", + zap.Uint("event_id", uint(eventID)), + zap.Bool("available", available), + ) + + utils.RespondWithJSON(w, http.StatusOK, map[string]interface{}{ + "event_id": eventID, + "available": available, + }) +} + +// toRegistrationResponse преобразует модель регистрации в response DTO +func toRegistrationResponse(registration *models.EventRegistration) RegistrationResponse { + response := RegistrationResponse{ + ID: registration.ID, + UserID: registration.UserID, + EventID: registration.EventID, + Status: registration.Status, + Notes: registration.Notes, + ResultTime: registration.ResultTime, + CreatedAt: registration.CreatedAt.Format("2006-01-02 15:04:05"), + UpdatedAt: registration.UpdatedAt.Format("2006-01-02 15:04:05"), + } + + // Включаем информацию о событии, если она загружена + if registration.Event != nil { + response.Event = toEventResponse(registration.Event) + } + + return response +} \ No newline at end of file diff --git a/serv_nginx/api_bb/internal/handlers/handlers.go b/serv_nginx/api_bb/internal/handlers/handlers.go index bf943ee..37540e1 100644 --- a/serv_nginx/api_bb/internal/handlers/handlers.go +++ b/serv_nginx/api_bb/internal/handlers/handlers.go @@ -11,15 +11,17 @@ import ( ) type Handler struct { - healthHandler *HealthHandler - authHandler *AuthHandler - userHandler *UserHandler - avatarHandler *AvatarHandler - newsHandler *NewsHandler - reviewHandler *ReviewHandler - userStatsHandler *UserStatsHandler - userWorkoutHandler *UserWorkoutHandler - userAchievementHandler * UserAchievementHandler + healthHandler *HealthHandler + authHandler *AuthHandler + userHandler *UserHandler + avatarHandler *AvatarHandler + newsHandler *NewsHandler + reviewHandler *ReviewHandler + userStatsHandler *UserStatsHandler + userWorkoutHandler *UserWorkoutHandler + userAchievementHandler *UserAchievementHandler + eventHandler *EventHandler + eventRegistrationHandler *EventRegistrationHandler // Здесь будут добавлены другие обработчики // userHandler *UserHandler // eventHandler *EventHandler @@ -35,6 +37,8 @@ func NewHandler(db *gorm.DB, cfg *config.Config) *Handler { userStatsRepo := repository.NewUserStatsRepository(db) userWorkoutRepo := repository.NewWorkoutRepository(db) userAchievemenRepo := repository.NewAchievementRepository(db) + eventRepo := repository.NewEventRepository(db) + eventRegistrationRepo := repository.NewEventRegistrationRepository(db) // Initialize logger baseLogger := logger.NewWrapper(logger.Get()) // Создаем базовый логгер @@ -49,6 +53,8 @@ func NewHandler(db *gorm.DB, cfg *config.Config) *Handler { userStatsService := service.NewUserStatsService(userStatsRepo) userWorkoutService := service.NewWorkoutService(userWorkoutRepo) achievementService := service.NewAchievementService(userAchievemenRepo) + eventRegistrationService := service.NewEventRegistrationService(eventRegistrationRepo, eventRepo, baseLogger) + eventService := service.NewEventService(eventRepo, eventRegistrationRepo, baseLogger) // Инициализация обработчиков healthHandler := NewHealthHandler() @@ -60,21 +66,33 @@ func NewHandler(db *gorm.DB, cfg *config.Config) *Handler { userStatsHandler := NewUserStatsHandler(userStatsService) userWorkoutHandler := NewUserWorkoutHandler(userWorkoutService) userAchievementHandler := NewUserAchievementHandler(*achievementService) + eventHandler := NewEventHandler(eventService) + eventRegistrationHandler := NewEventRegistrationHandler(eventRegistrationService) return &Handler{ - healthHandler: healthHandler, - authHandler: authHandler, - userHandler: userHandler, - newsHandler: newsHandler, - avatarHandler: avatarHandler, - reviewHandler: reviewHandler, - userStatsHandler: userStatsHandler, - userWorkoutHandler: userWorkoutHandler, - userAchievementHandler: userAchievementHandler, + healthHandler: healthHandler, + authHandler: authHandler, + userHandler: userHandler, + newsHandler: newsHandler, + avatarHandler: avatarHandler, + reviewHandler: reviewHandler, + userStatsHandler: userStatsHandler, + userWorkoutHandler: userWorkoutHandler, + userAchievementHandler: userAchievementHandler, + eventHandler: eventHandler, + eventRegistrationHandler: eventRegistrationHandler, } } // Геттеры для обработчиков (опционально, для удобства) +func (h *Handler) EventHandler() *EventHandler { + return h.eventHandler +} + +func (h *Handler) EventRegistrationHandler() *EventRegistrationHandler { + return h.eventRegistrationHandler +} + func (h *Handler) HealthHandler() *HealthHandler { return h.healthHandler } @@ -107,6 +125,6 @@ func (h *Handler) UserWorkoutHandler() *UserWorkoutHandler { return h.userWorkoutHandler } -func (h * Handler) UserAchievementHandler() *UserAchievementHandler { +func (h *Handler) UserAchievementHandler() *UserAchievementHandler { return h.userAchievementHandler } diff --git a/serv_nginx/api_bb/internal/models/event.go b/serv_nginx/api_bb/internal/models/event.go index 1288bef..0177f94 100644 --- a/serv_nginx/api_bb/internal/models/event.go +++ b/serv_nginx/api_bb/internal/models/event.go @@ -3,8 +3,6 @@ package models import ( "time" - - "gorm.io/gorm" ) type EventType string @@ -17,85 +15,35 @@ const ( ) type Event struct { - ID uint `json:"id" gorm:"primaryKey"` - Title string `json:"title" gorm:"size:255;not null"` - Description string `json:"description" gorm:"type:text;not null"` - Date time.Time `json:"date" gorm:"not null;index"` - Location string `json:"location" gorm:"size:255;not null"` - Type EventType `json:"type" gorm:"type:varchar(20);not null"` - Distance string `json:"distance" gorm:"size:50"` // Дистанция забега - ParticipantsCount int `json:"participants_count" gorm:"default:0"` // Количество участников - MaxParticipants int `json:"max_participants" gorm:"default:0"` // Максимальное количество участников - RegistrationOpen bool `json:"registration_open" gorm:"default:true"` // Открыта ли регистрация - Image string `json:"image" gorm:"size:500"` // Изображение события - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` + ID uint `gorm:"primaryKey" json:"id"` + Title string `gorm:"size:255;not null" json:"title" validate:"required,min=5,max=255"` + Description string `gorm:"type:text;not null" json:"description" validate:"required,min=10"` + Date time.Time `gorm:"not null" json:"date" validate:"required"` + Location string `gorm:"size:255;not null" json:"location" validate:"required,max=255"` + Type EventType `gorm:"size:50;not null" json:"type" validate:"required,oneof=race training social workshop"` + Distance string `gorm:"size:50" json:"distance" validate:"max=50"` + ParticipantsCount int `gorm:"default:0" json:"participants_count"` + MaxParticipants int `gorm:"default:0" json:"max_participants" validate:"min=0"` + RegistrationOpen bool `gorm:"default:true" json:"registration_open"` + Image string `gorm:"size:500" json:"image" validate:"max=500"` + CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"` + UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"` // Связи - Registrations []EventRegistration `json:"registrations,omitempty" gorm:"foreignKey:EventID"` + Registrations []EventRegistration `gorm:"foreignKey:EventID" json:"registrations,omitempty"` } type EventRegistration struct { - ID uint `json:"id" gorm:"primaryKey"` - EventID uint `json:"event_id" gorm:"not null;index"` - UserID uint `json:"user_id" gorm:"not null;index"` - Status string `json:"status" gorm:"size:20;default:pending"` // pending, confirmed, cancelled, completed - ResultTime *string `json:"result_time" gorm:"size:20"` // Результат забега - BibNumber *string `json:"bib_number" gorm:"size:10"` // Стартовый номер - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` + ID uint `gorm:"primaryKey" json:"id"` + UserID uint `gorm:"not null" json:"user_id"` + EventID uint `gorm:"not null" json:"event_id"` + Status string `gorm:"size:50;default:pending" json:"status" validate:"oneof=pending confirmed cancelled completed"` + Notes string `gorm:"type:text" json:"notes" validate:"max=500"` + ResultTime string `gorm:"size:20" json:"result_time" validate:"max=20"` + CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"` + UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"` // Связи - Event Event `json:"event,omitempty" gorm:"foreignKey:EventID"` - User User `json:"user,omitempty" gorm:"foreignKey:UserID"` + User User `gorm:"foreignKey:UserID" json:"user,omitempty"` + Event *Event `gorm:"foreignKey:EventID" json:"event,omitempty"` } - -// BeforeCreate hooks -func (e *Event) BeforeCreate(tx *gorm.DB) error { - if e.CreatedAt.IsZero() { - e.CreatedAt = time.Now() - } - if e.UpdatedAt.IsZero() { - e.UpdatedAt = time.Now() - } - return nil -} - -func (er *EventRegistration) BeforeCreate(tx *gorm.DB) error { - if er.CreatedAt.IsZero() { - er.CreatedAt = time.Now() - } - if er.UpdatedAt.IsZero() { - er.UpdatedAt = time.Now() - } - return nil -} - -// BeforeUpdate hooks -func (e *Event) BeforeUpdate(tx *gorm.DB) error { - e.UpdatedAt = time.Now() - return nil -} - -func (er *EventRegistration) BeforeUpdate(tx *gorm.DB) error { - er.UpdatedAt = time.Now() - return nil -} - -// DTO для создания события -type EventCreateRequest 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 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"` -} - -// DTO для регистрации на событие -type EventRegistrationRequest struct { - EventID uint `json:"event_id" validate:"required"` -} \ No newline at end of file diff --git a/serv_nginx/api_bb/internal/routes/routes.go b/serv_nginx/api_bb/internal/routes/routes.go index bc2f672..91e7c8b 100644 --- a/serv_nginx/api_bb/internal/routes/routes.go +++ b/serv_nginx/api_bb/internal/routes/routes.go @@ -24,7 +24,7 @@ func SetupRouter(db *gorm.DB, config *config.Config) http.Handler { } // handler - allHandler := handlers.NewHandler(db, config) + h := handlers.NewHandler(db, config) // Serve static files (avatars) r.Handle("/uploads/*", http.StripPrefix("/uploads/", @@ -43,8 +43,8 @@ func SetupRouter(db *gorm.DB, config *config.Config) http.Handler { // Health routes r.Route("/api", func(r chi.Router) { - r.Get("/health", allHandler.HealthHandler().HealthCheck) - r.Get("/check", allHandler.HealthHandler().Check) + r.Get("/health", h.HealthHandler().HealthCheck) + r.Get("/check", h.HealthHandler().Check) }) // API v1 routes @@ -52,9 +52,17 @@ func SetupRouter(db *gorm.DB, config *config.Config) http.Handler { // Public auth routes r.Route("/auth", func(r chi.Router) { - r.Post("/register", allHandler.AuthHandler().Register) - r.Post("/login", allHandler.AuthHandler().Login) - r.Post("/logout", allHandler.AuthHandler().Logout) + r.Post("/register", h.AuthHandler().Register) + r.Post("/login", h.AuthHandler().Login) + r.Post("/logout", h.AuthHandler().Logout) + }) + + // Публичные маршруты для достижений (если нужны) + r.Route("/achievements", func(r chi.Router) { + // Публичные маршруты для просмотра достижений других пользователей + r.Get("/user/{userID}", h.UserAchievementHandler().GetPublicUserAchievements) + r.Get("/user/{userID}/summary", h.UserAchievementHandler().GetPublicUserAchievementsSummary) + r.Get("/user/{userID}/recent", h.UserAchievementHandler().GetPublicRecentAchievements) }) // Protected routes @@ -63,70 +71,70 @@ func SetupRouter(db *gorm.DB, config *config.Config) http.Handler { r.Use(middleware.RequireAuth) // user profile routes - r.Get("/profile", allHandler.UserHandler().GetProfile) - r.Post("/editProfile", allHandler.UserHandler().UpdateProfile) - r.Get("/", allHandler.UserHandler().GetUsers) + r.Get("/profile", h.UserHandler().GetProfile) + r.Post("/editProfile", h.UserHandler().UpdateProfile) + r.Get("/", h.UserHandler().GetUsers) // Все операции с аватарами теперь через AvatarHandler r.Route("/avatars", func(r chi.Router) { - r.Post("/upload", allHandler.AvatarHandler().UploadAvatar) - r.Delete("/delete", allHandler.AvatarHandler().DeleteAvatar) - r.Get("/{filename}", allHandler.AvatarHandler().GetAvatar) + r.Post("/upload", h.AvatarHandler().UploadAvatar) + r.Delete("/delete", h.AvatarHandler().DeleteAvatar) + r.Get("/{filename}", h.AvatarHandler().GetAvatar) }) r.Route("/stats", func(r chi.Router) { - r.Get("/", allHandler.UserStatsHandler().GetUserStats) - r.Get("/{userID}", allHandler.UserStatsHandler().GetUserStatsByID) - r.Post("/workout", allHandler.UserStatsHandler().IncrementWorkout) - r.Put("/personal-best", allHandler.UserStatsHandler().UpdatePersonalBest) - r.Post("/weekly/reset", allHandler.UserStatsHandler().ResetWeeklyDistance) - r.Post("/monthly/reset", allHandler.UserStatsHandler().ResetMonthlyDistance) + r.Get("/", h.UserStatsHandler().GetUserStats) + r.Get("/{userID}", h.UserStatsHandler().GetUserStatsByID) + r.Post("/workout", h.UserStatsHandler().IncrementWorkout) + r.Put("/personal-best", h.UserStatsHandler().UpdatePersonalBest) + r.Post("/weekly/reset", h.UserStatsHandler().ResetWeeklyDistance) + r.Post("/monthly/reset", h.UserStatsHandler().ResetMonthlyDistance) }) // Маршруты для тренировок r.Route("/workouts", func(r chi.Router) { - r.Post("/", allHandler.UserWorkoutHandler().CreateWorkout) - r.Get("/", allHandler.UserWorkoutHandler().GetWorkouts) - r.Get("/stats", allHandler.UserWorkoutHandler().GetWorkoutStats) - r.Get("/type/{type}", allHandler.UserWorkoutHandler().GetWorkoutsByType) - + r.Post("/", h.UserWorkoutHandler().CreateWorkout) + r.Get("/", h.UserWorkoutHandler().GetWorkouts) + r.Get("/stats", h.UserWorkoutHandler().GetWorkoutStats) + r.Get("/type/{type}", h.UserWorkoutHandler().GetWorkoutsByType) + r.Route("/{id}", func(r chi.Router) { - r.Get("/", allHandler.UserWorkoutHandler().GetWorkoutByID) - r.Put("/", allHandler.UserWorkoutHandler().UpdateWorkout) - r.Delete("/", allHandler.UserWorkoutHandler().DeleteWorkout) + r.Get("/", h.UserWorkoutHandler().GetWorkoutByID) + r.Put("/", h.UserWorkoutHandler().UpdateWorkout) + r.Delete("/", h.UserWorkoutHandler().DeleteWorkout) }) }) // Маршруты для достижений (achievements) r.Route("/achievements", func(r chi.Router) { // Создание нового достижения - r.Post("/", allHandler.UserAchievementHandler().CreateAchievement) - + r.Post("/", h.UserAchievementHandler().CreateAchievement) + // Получение всех достижений пользователя - r.Get("/", allHandler.UserAchievementHandler().GetUserAchievements) - + r.Get("/", h.UserAchievementHandler().GetUserAchievements) + // Получение сводки по достижениям - r.Get("/summary", allHandler.UserAchievementHandler().GetUserAchievementsSummary) - + r.Get("/summary", h.UserAchievementHandler().GetUserAchievementsSummary) + // Получение последних достижений (с опциональным лимитом) - r.Get("/recent", allHandler.UserAchievementHandler().GetRecentAchievements) - + r.Get("/recent", h.UserAchievementHandler().GetRecentAchievements) + // Получение достижений по типу - r.Get("/type/{type}", allHandler.UserAchievementHandler().GetAchievementsByType) - + r.Get("/type/{type}", h.UserAchievementHandler().GetAchievementsByType) + // Операции с конкретным достижением r.Route("/{id}", func(r chi.Router) { // Получение достижения по ID - r.Get("/", allHandler.UserAchievementHandler().GetAchievementByID) - + r.Get("/", h.UserAchievementHandler().GetAchievementByID) + // Обновление достижения - r.Put("/", allHandler.UserAchievementHandler().UpdateAchievement) - + r.Put("/", h.UserAchievementHandler().UpdateAchievement) + // Удаление достижения - r.Delete("/", allHandler.UserAchievementHandler().DeleteAchievement) - + r.Delete("/", h.UserAchievementHandler().DeleteAchievement) + // Подтверждение достижения - r.Patch("/verify", allHandler.UserAchievementHandler().VerifyAchievement) + r.Patch("/verify", h.UserAchievementHandler().VerifyAchievement) }) }) }) @@ -134,10 +142,10 @@ func SetupRouter(db *gorm.DB, config *config.Config) http.Handler { r.Route("/news", func(r chi.Router) { // Публичные маршруты - r.Get("/", allHandler.NewsHandler().GetNews) - r.Get("/{id}", allHandler.NewsHandler().GetNewsByID) - r.Get("/{id}/comments", allHandler.NewsHandler().GetComments) - r.Get("/check", allHandler.HealthHandler().Check) + r.Get("/", h.NewsHandler().GetNews) + r.Get("/{id}", h.NewsHandler().GetNewsByID) + r.Get("/{id}/comments", h.NewsHandler().GetComments) + r.Get("/check", h.HealthHandler().Check) // Защищенные маршруты r.Group(func(r chi.Router) { @@ -145,44 +153,77 @@ func SetupRouter(db *gorm.DB, config *config.Config) http.Handler { r.Use(middleware.RequireAuth) // News EndPoints - r.Post("/", allHandler.NewsHandler().CreateNews) - r.Put("/{id}", allHandler.NewsHandler().UpdateNews) - r.Delete("/{id}", allHandler.NewsHandler().DeleteNews) - r.Get("/my/news", allHandler.NewsHandler().GetUserNews) + r.Post("/", h.NewsHandler().CreateNews) + r.Put("/{id}", h.NewsHandler().UpdateNews) + r.Delete("/{id}", h.NewsHandler().DeleteNews) + r.Get("/my/news", h.NewsHandler().GetUserNews) - r.Post("/{id}/comments", allHandler.NewsHandler().CreateComment) - r.Delete("/comments/{commentId}", allHandler.NewsHandler().DeleteComment) + r.Post("/{id}/comments", h.NewsHandler().CreateComment) + r.Delete("/comments/{commentId}", h.NewsHandler().DeleteComment) - r.Get("/check", allHandler.HealthHandler().Check) + r.Get("/check", h.HealthHandler().Check) }) }) // Маршруты для отзывов r.Route("/reviews", func(r chi.Router) { // Публичные маршруты - r.Get("/", allHandler.ReviewHandler().GetReviews) - r.Get("/stats", allHandler.ReviewHandler().GetReviewsStats) - r.Get("/{id}", allHandler.ReviewHandler().GetReviewByID) + r.Get("/", h.ReviewHandler().GetReviews) + r.Get("/stats", h.ReviewHandler().GetReviewsStats) + r.Get("/{id}", h.ReviewHandler().GetReviewByID) // Защищенные маршруты r.Group(func(r chi.Router) { r.Use(middleware.AuthMiddleware(jwtService, userRepo)) r.Use(middleware.RequireAuth) - r.Post("/", allHandler.ReviewHandler().CreateReview) - r.Get("/my", allHandler.ReviewHandler().GetMyReviews) - r.Put("/{id}", allHandler.ReviewHandler().UpdateReview) - r.Delete("/{id}", allHandler.ReviewHandler().DeleteReview) + r.Post("/", h.ReviewHandler().CreateReview) + r.Get("/my", h.ReviewHandler().GetMyReviews) + r.Put("/{id}", h.ReviewHandler().UpdateReview) + r.Delete("/{id}", h.ReviewHandler().DeleteReview) }) }) - // Публичные маршруты для достижений (если нужны) - r.Route("/achievements", func(r chi.Router) { - // Публичные маршруты для просмотра достижений других пользователей - r.Get("/user/{userID}", allHandler.UserAchievementHandler().GetPublicUserAchievements) - r.Get("/user/{userID}/summary", allHandler.UserAchievementHandler().GetPublicUserAchievementsSummary) - r.Get("/user/{userID}/recent", allHandler.UserAchievementHandler().GetPublicRecentAchievements) + r.Route("/events", func(r chi.Router) { + + // Публичные маршруты + r.Get("/", h.EventHandler().GetAllEvents) + r.Get("/upcoming", h.EventHandler().GetUpcomingEvents) + r.Get("/type/{type}", h.EventHandler().GetEventsByType) + r.Get("/{id}", h.EventHandler().GetEvent) + r.Get("/{eventId}/availability", h.EventRegistrationHandler().CheckEventAvailability) + + // Защищенные маршруты (требуют аутентификации) + r.Group(func(r chi.Router) { + r.Use(middleware.AuthMiddleware(jwtService, userRepo)) + r.Use(middleware.RequireAuth) + + // Регистрации пользователя + r.Post("/register", h.EventRegistrationHandler().RegisterForEvent) + r.Get("/my/registrations", h.EventRegistrationHandler().GetUserRegistrations) + r.Delete("/registrations/{id}", h.EventRegistrationHandler().CancelRegistration) + r.Get("/registrations/{id}", h.EventRegistrationHandler().GetRegistration) + }) + + // Админские маршруты + r.Group(func(r chi.Router) { + r.Use(middleware.AuthMiddleware(jwtService, userRepo)) + r.Use(middleware.RequireAuth) + r.Use(middleware.AdminMiddleware) + + // Управление событиями + r.Post("/", h.EventHandler().CreateEvent) + r.Put("/{id}", h.EventHandler().UpdateEvent) + r.Delete("/{id}", h.EventHandler().DeleteEvent) + r.Patch("/{id}/registration-status", h.EventHandler().ToggleRegistrationStatus) + + // Управление регистрациями + r.Get("/{eventId}/registrations", h.EventRegistrationHandler().GetEventRegistrations) + r.Patch("/registrations/{id}/status", h.EventRegistrationHandler().UpdateRegistrationStatus) + r.Patch("/registrations/{id}/result-time", h.EventRegistrationHandler().UpdateResultTime) + }) }) + }) // Логируем все зарегистрированные маршруты @@ -190,4 +231,4 @@ func SetupRouter(db *gorm.DB, config *config.Config) http.Handler { routeLogger.LogRoutes(r) return r -} \ No newline at end of file +} diff --git a/serv_nginx/api_bb/internal/service/event_registration_service.go b/serv_nginx/api_bb/internal/service/event_registration_service.go new file mode 100644 index 0000000..f57ed39 --- /dev/null +++ b/serv_nginx/api_bb/internal/service/event_registration_service.go @@ -0,0 +1,380 @@ +// service/event_registration_service.go +package service + +import ( + "api_bb/internal/models" + "api_bb/internal/repository" + "api_bb/pkg/logger" + "fmt" + + "go.uber.org/zap" +) + +type EventRegistrationService interface { + RegisterForEvent(registration *models.EventRegistration) error + GetRegistrationByID(id uint) (*models.EventRegistration, error) + GetRegistrationsByEventID(eventID uint) ([]models.EventRegistration, error) + GetRegistrationsByUserID(userID uint) ([]models.EventRegistration, error) + GetRegistrationByEventAndUser(eventID, userID uint) (*models.EventRegistration, error) + UpdateRegistration(registration *models.EventRegistration) error + CancelRegistration(id uint) error + UpdateRegistrationStatus(registrationID uint, status string) error + UpdateResultTime(registrationID uint, resultTime string) error + CheckEventAvailability(eventID uint) (bool, error) +} + +type eventRegistrationService struct { + registrationRepo repository.EventRegistrationRepository + eventRepo repository.EventRepository + logger logger.LoggerInterface +} + +func NewEventRegistrationService( + registrationRepo repository.EventRegistrationRepository, + eventRepo repository.EventRepository, + log logger.LoggerInterface, +) EventRegistrationService { + serviceLogger := log.With(zap.String("service", "event_registration")) + + return &eventRegistrationService{ + registrationRepo: registrationRepo, + eventRepo: eventRepo, + logger: serviceLogger, + } +} + +// RegisterForEvent регистрирует пользователя на событие +func (s *eventRegistrationService) RegisterForEvent(registration *models.EventRegistration) error { + s.logger.Info("Registering user for event", + zap.Uint("user_id", registration.UserID), + zap.Uint("event_id", registration.EventID), + ) + + // Проверяем существование события + event, err := s.eventRepo.FindByID(registration.EventID) + if err != nil { + s.logger.Warn("Event not found for registration", + zap.Uint("event_id", registration.EventID), + zap.Error(err), + ) + return fmt.Errorf("event not found") + } + + // Проверяем, открыта ли регистрация + if !event.RegistrationOpen { + s.logger.Warn("Registration is closed for event", + zap.Uint("event_id", registration.EventID), + zap.String("event_title", event.Title), + ) + return fmt.Errorf("registration is closed for this event") + } + + // Проверяем, не зарегистрирован ли пользователь уже + existingRegistration, err := s.registrationRepo.FindByEventAndUser(registration.EventID, registration.UserID) + if err == nil && existingRegistration != nil { + s.logger.Warn("User already registered for event", + zap.Uint("user_id", registration.UserID), + zap.Uint("event_id", registration.EventID), + ) + return fmt.Errorf("user already registered for this event") + } + + // Проверяем доступность мест + available, err := s.CheckEventAvailability(registration.EventID) + if err != nil { + s.logger.Error("Failed to check event availability", + zap.Uint("event_id", registration.EventID), + zap.Error(err), + ) + return fmt.Errorf("failed to check event availability: %w", err) + } + + if !available { + s.logger.Warn("Event is full", + zap.Uint("event_id", registration.EventID), + zap.String("event_title", event.Title), + ) + return fmt.Errorf("event is full") + } + + // Создаем регистрацию + if err := s.registrationRepo.Create(registration); err != nil { + s.logger.Error("Failed to create registration", + zap.Uint("user_id", registration.UserID), + zap.Uint("event_id", registration.EventID), + zap.Error(err), + ) + return fmt.Errorf("failed to register for event: %w", err) + } + + // Обновляем счетчик участников + if err := s.eventRepo.UpdateParticipantsCount(registration.EventID, event.ParticipantsCount+1); err != nil { + s.logger.Error("Failed to update participants count", + zap.Uint("event_id", registration.EventID), + zap.Error(err), + ) + // Не прерываем выполнение, только логируем ошибку + } + + s.logger.Info("User registered for event successfully", + zap.Uint("user_id", registration.UserID), + zap.Uint("event_id", registration.EventID), + zap.String("status", registration.Status), + ) + return nil +} + +// GetRegistrationByID возвращает регистрацию по ID +func (s *eventRegistrationService) GetRegistrationByID(id uint) (*models.EventRegistration, error) { + s.logger.Debug("Getting registration by ID", zap.Uint("registration_id", id)) + + registration, err := s.registrationRepo.FindByID(id) + if err != nil { + s.logger.Warn("Registration not found", + zap.Uint("registration_id", id), + zap.Error(err), + ) + return nil, fmt.Errorf("registration not found: %w", err) + } + + s.logger.Debug("Registration retrieved successfully", + zap.Uint("registration_id", id), + zap.Uint("user_id", registration.UserID), + zap.Uint("event_id", registration.EventID), + ) + return registration, nil +} + +// GetRegistrationsByEventID возвращает все регистрации на событие +func (s *eventRegistrationService) GetRegistrationsByEventID(eventID uint) ([]models.EventRegistration, error) { + s.logger.Debug("Getting registrations by event ID", zap.Uint("event_id", eventID)) + + registrations, err := s.registrationRepo.FindByEventID(eventID) + if err != nil { + s.logger.Error("Failed to get registrations by event ID", + zap.Uint("event_id", eventID), + zap.Error(err), + ) + return nil, fmt.Errorf("failed to get registrations: %w", err) + } + + s.logger.Debug("Registrations by event retrieved successfully", + zap.Uint("event_id", eventID), + zap.Int("count", len(registrations)), + ) + return registrations, nil +} + +// GetRegistrationsByUserID возвращает все регистрации пользователя +func (s *eventRegistrationService) GetRegistrationsByUserID(userID uint) ([]models.EventRegistration, error) { + s.logger.Debug("Getting registrations by user ID", zap.Uint("user_id", userID)) + + registrations, err := s.registrationRepo.FindByUserID(userID) + if err != nil { + s.logger.Error("Failed to get registrations by user ID", + zap.Uint("user_id", userID), + zap.Error(err), + ) + return nil, fmt.Errorf("failed to get user registrations: %w", err) + } + + s.logger.Debug("User registrations retrieved successfully", + zap.Uint("user_id", userID), + zap.Int("count", len(registrations)), + ) + return registrations, nil +} + +// GetRegistrationByEventAndUser возвращает регистрацию по событию и пользователю +func (s *eventRegistrationService) GetRegistrationByEventAndUser(eventID, userID uint) (*models.EventRegistration, error) { + s.logger.Debug("Getting registration by event and user", + zap.Uint("event_id", eventID), + zap.Uint("user_id", userID), + ) + + registration, err := s.registrationRepo.FindByEventAndUser(eventID, userID) + if err != nil { + s.logger.Debug("Registration not found for event and user", + zap.Uint("event_id", eventID), + zap.Uint("user_id", userID), + zap.Error(err), + ) + return nil, fmt.Errorf("registration not found: %w", err) + } + + s.logger.Debug("Registration by event and user retrieved successfully", + zap.Uint("event_id", eventID), + zap.Uint("user_id", userID), + ) + return registration, nil +} + +// UpdateRegistration обновляет регистрацию +func (s *eventRegistrationService) UpdateRegistration(registration *models.EventRegistration) error { + s.logger.Info("Updating registration", + zap.Uint("registration_id", registration.ID), + zap.Uint("user_id", registration.UserID), + zap.Uint("event_id", registration.EventID), + ) + + // Проверяем существование регистрации + existingRegistration, err := s.registrationRepo.FindByID(registration.ID) + if err != nil { + s.logger.Warn("Registration not found for update", + zap.Uint("registration_id", registration.ID), + zap.Error(err), + ) + return fmt.Errorf("registration not found") + } + + // Сохраняем неизменяемые поля + registration.CreatedAt = existingRegistration.CreatedAt + + if err := s.registrationRepo.Update(registration); err != nil { + s.logger.Error("Failed to update registration", + zap.Uint("registration_id", registration.ID), + zap.Error(err), + ) + return fmt.Errorf("failed to update registration: %w", err) + } + + s.logger.Info("Registration updated successfully", + zap.Uint("registration_id", registration.ID), + ) + return nil +} + +// CancelRegistration отменяет регистрацию +func (s *eventRegistrationService) CancelRegistration(id uint) error { + s.logger.Info("Canceling registration", zap.Uint("registration_id", id)) + + // Получаем регистрацию для получения event_id + registration, err := s.registrationRepo.FindByID(id) + if err != nil { + s.logger.Warn("Registration not found for cancellation", + zap.Uint("registration_id", id), + zap.Error(err), + ) + return fmt.Errorf("registration not found") + } + + if err := s.registrationRepo.Delete(id); err != nil { + s.logger.Error("Failed to cancel registration", + zap.Uint("registration_id", id), + zap.Error(err), + ) + return fmt.Errorf("failed to cancel registration: %w", err) + } + + // Обновляем счетчик участников + if err := s.eventRepo.UpdateParticipantsCount(registration.EventID, registration.Event.ParticipantsCount-1); err != nil { + s.logger.Error("Failed to update participants count after cancellation", + zap.Uint("event_id", registration.EventID), + zap.Error(err), + ) + // Не прерываем выполнение, только логируем ошибку + } + + s.logger.Info("Registration canceled successfully", + zap.Uint("registration_id", id), + zap.Uint("event_id", registration.EventID), + ) + return nil +} + +// UpdateRegistrationStatus обновляет статус регистрации +func (s *eventRegistrationService) UpdateRegistrationStatus(registrationID uint, status string) error { + s.logger.Info("Updating registration status", + zap.Uint("registration_id", registrationID), + zap.String("status", status), + ) + + validStatuses := []string{"pending", "confirmed", "cancelled", "completed"} + if !contains(validStatuses, status) { + s.logger.Warn("Invalid registration status", + zap.String("status", status), + zap.Strings("valid_statuses", validStatuses), + ) + return fmt.Errorf("invalid status: %s", status) + } + + if err := s.registrationRepo.UpdateStatus(registrationID, status); err != nil { + s.logger.Error("Failed to update registration status", + zap.Uint("registration_id", registrationID), + zap.String("status", status), + zap.Error(err), + ) + return fmt.Errorf("failed to update registration status: %w", err) + } + + s.logger.Info("Registration status updated successfully", + zap.Uint("registration_id", registrationID), + zap.String("status", status), + ) + return nil +} + +// UpdateResultTime обновляет результат забега +func (s *eventRegistrationService) UpdateResultTime(registrationID uint, resultTime string) error { + s.logger.Info("Updating result time", + zap.Uint("registration_id", registrationID), + zap.String("result_time", resultTime), + ) + + if err := s.registrationRepo.UpdateResultTime(registrationID, resultTime); err != nil { + s.logger.Error("Failed to update result time", + zap.Uint("registration_id", registrationID), + zap.String("result_time", resultTime), + zap.Error(err), + ) + return fmt.Errorf("failed to update result time: %w", err) + } + + s.logger.Info("Result time updated successfully", + zap.Uint("registration_id", registrationID), + zap.String("result_time", resultTime), + ) + return nil +} + +// CheckEventAvailability проверяет доступность мест на событии +func (s *eventRegistrationService) CheckEventAvailability(eventID uint) (bool, error) { + s.logger.Debug("Checking event availability", zap.Uint("event_id", eventID)) + + event, err := s.eventRepo.FindByID(eventID) + if err != nil { + return false, fmt.Errorf("event not found: %w", err) + } + + // Если максимальное количество участников не установлено, считаем доступным + if event.MaxParticipants == 0 { + return true, nil + } + + // Получаем текущее количество подтвержденных регистраций + currentCount, err := s.registrationRepo.CountByEventID(eventID) + if err != nil { + return false, fmt.Errorf("failed to count registrations: %w", err) + } + + available := int(currentCount) < event.MaxParticipants + + s.logger.Debug("Event availability check completed", + zap.Uint("event_id", eventID), + zap.Int64("current_count", currentCount), + zap.Int("max_participants", event.MaxParticipants), + zap.Bool("available", available), + ) + + return available, nil +} + +// contains проверяет наличие строки в слайсе +func contains(slice []string, item string) bool { + for _, s := range slice { + if s == item { + return true + } + } + return false +} diff --git a/serv_nginx/api_bb/internal/service/event_service.go b/serv_nginx/api_bb/internal/service/event_service.go new file mode 100644 index 0000000..1c7e82d --- /dev/null +++ b/serv_nginx/api_bb/internal/service/event_service.go @@ -0,0 +1,280 @@ +// service/event_service.go +package service + +import ( + "api_bb/internal/models" + "api_bb/internal/repository" + "api_bb/pkg/logger" + "fmt" + "time" + + "go.uber.org/zap" +) + +type EventService interface { + CreateEvent(event *models.Event) error + GetEventByID(id uint) (*models.Event, error) + GetAllEvents() ([]models.Event, error) + UpdateEvent(event *models.Event) error + DeleteEvent(id uint) error + GetEventsByType(eventType models.EventType) ([]models.Event, error) + GetUpcomingEvents() ([]models.Event, error) + GetEventsByDateRange(startDate, endDate time.Time) ([]models.Event, error) + UpdateParticipantsCount(eventID uint) error + ToggleRegistrationStatus(eventID uint, registrationOpen bool) error +} + +type eventService struct { + eventRepo repository.EventRepository + registrationRepo repository.EventRegistrationRepository + logger logger.LoggerInterface +} + +func NewEventService( + eventRepo repository.EventRepository, + registrationRepo repository.EventRegistrationRepository, + log logger.LoggerInterface, +) EventService { + serviceLogger := log.With(zap.String("service", "event")) + + return &eventService{ + eventRepo: eventRepo, + registrationRepo: registrationRepo, + logger: serviceLogger, + } +} + +// CreateEvent создает новое событие +func (s *eventService) CreateEvent(event *models.Event) error { + s.logger.Info("Creating new event", + zap.String("title", event.Title), + zap.String("type", string(event.Type)), + zap.Time("date", event.Date), + ) + + if err := s.eventRepo.Create(event); err != nil { + s.logger.Error("Failed to create event", + zap.String("title", event.Title), + zap.Error(err), + ) + return fmt.Errorf("failed to create event: %w", err) + } + + s.logger.Info("Event created successfully", + zap.Uint("event_id", event.ID), + zap.String("title", event.Title), + ) + return nil +} + +// GetEventByID возвращает событие по ID +func (s *eventService) GetEventByID(id uint) (*models.Event, error) { + s.logger.Debug("Getting event by ID", zap.Uint("event_id", id)) + + event, err := s.eventRepo.FindByID(id) + if err != nil { + s.logger.Warn("Event not found", + zap.Uint("event_id", id), + zap.Error(err), + ) + return nil, fmt.Errorf("event not found: %w", err) + } + + s.logger.Debug("Event retrieved successfully", + zap.Uint("event_id", id), + zap.String("title", event.Title), + ) + return event, nil +} + +// GetAllEvents возвращает все события +func (s *eventService) GetAllEvents() ([]models.Event, error) { + s.logger.Debug("Getting all events") + + events, err := s.eventRepo.FindAll() + if err != nil { + s.logger.Error("Failed to get events", zap.Error(err)) + return nil, fmt.Errorf("failed to get events: %w", err) + } + + s.logger.Debug("Events retrieved successfully", + zap.Int("count", len(events)), + ) + return events, nil +} + +// UpdateEvent обновляет событие +func (s *eventService) UpdateEvent(event *models.Event) error { + s.logger.Info("Updating event", + zap.Uint("event_id", event.ID), + zap.String("title", event.Title), + ) + + // Проверяем существование события + existingEvent, err := s.eventRepo.FindByID(event.ID) + if err != nil { + s.logger.Warn("Event not found for update", + zap.Uint("event_id", event.ID), + zap.Error(err), + ) + return fmt.Errorf("event not found") + } + + // Сохраняем неизменяемые поля + event.CreatedAt = existingEvent.CreatedAt + event.UpdatedAt = time.Now() + + if err := s.eventRepo.Update(event); err != nil { + s.logger.Error("Failed to update event", + zap.Uint("event_id", event.ID), + zap.Error(err), + ) + return fmt.Errorf("failed to update event: %w", err) + } + + s.logger.Info("Event updated successfully", + zap.Uint("event_id", event.ID), + ) + return nil +} + +// DeleteEvent удаляет событие +func (s *eventService) DeleteEvent(id uint) error { + s.logger.Info("Deleting event", zap.Uint("event_id", id)) + + // Проверяем существование события + _, err := s.eventRepo.FindByID(id) + if err != nil { + s.logger.Warn("Event not found for deletion", + zap.Uint("event_id", id), + zap.Error(err), + ) + return fmt.Errorf("event not found") + } + + if err := s.eventRepo.Delete(id); err != nil { + s.logger.Error("Failed to delete event", + zap.Uint("event_id", id), + zap.Error(err), + ) + return fmt.Errorf("failed to delete event: %w", err) + } + + s.logger.Info("Event deleted successfully", + zap.Uint("event_id", id), + ) + return nil +} + +// GetEventsByType возвращает события по типу +func (s *eventService) GetEventsByType(eventType models.EventType) ([]models.Event, error) { + s.logger.Debug("Getting events by type", zap.String("type", string(eventType))) + + events, err := s.eventRepo.FindByType(eventType) + if err != nil { + s.logger.Error("Failed to get events by type", + zap.String("type", string(eventType)), + zap.Error(err), + ) + return nil, fmt.Errorf("failed to get events by type: %w", err) + } + + s.logger.Debug("Events by type retrieved successfully", + zap.String("type", string(eventType)), + zap.Int("count", len(events)), + ) + return events, nil +} + +// GetUpcomingEvents возвращает предстоящие события +func (s *eventService) GetUpcomingEvents() ([]models.Event, error) { + s.logger.Debug("Getting upcoming events") + + events, err := s.eventRepo.FindUpcoming() + if err != nil { + s.logger.Error("Failed to get upcoming events", zap.Error(err)) + return nil, fmt.Errorf("failed to get upcoming events: %w", err) + } + + s.logger.Debug("Upcoming events retrieved successfully", + zap.Int("count", len(events)), + ) + return events, nil +} + +// GetEventsByDateRange возвращает события в диапазоне дат +func (s *eventService) GetEventsByDateRange(startDate, endDate time.Time) ([]models.Event, error) { + s.logger.Debug("Getting events by date range", + zap.Time("start_date", startDate), + zap.Time("end_date", endDate), + ) + + events, err := s.eventRepo.FindByDateRange(startDate, endDate) + if err != nil { + s.logger.Error("Failed to get events by date range", + zap.Time("start_date", startDate), + zap.Time("end_date", endDate), + zap.Error(err), + ) + return nil, fmt.Errorf("failed to get events by date range: %w", err) + } + + s.logger.Debug("Events by date range retrieved successfully", + zap.Time("start_date", startDate), + zap.Time("end_date", endDate), + zap.Int("count", len(events)), + ) + return events, nil +} + +// UpdateParticipantsCount обновляет количество участников события +func (s *eventService) UpdateParticipantsCount(eventID uint) error { + s.logger.Debug("Updating participants count", zap.Uint("event_id", eventID)) + + count, err := s.registrationRepo.CountByEventID(eventID) + if err != nil { + s.logger.Error("Failed to count event registrations", + zap.Uint("event_id", eventID), + zap.Error(err), + ) + return fmt.Errorf("failed to count registrations: %w", err) + } + + if err := s.eventRepo.UpdateParticipantsCount(eventID, int(count)); err != nil { + s.logger.Error("Failed to update participants count", + zap.Uint("event_id", eventID), + zap.Int64("count", count), + zap.Error(err), + ) + return fmt.Errorf("failed to update participants count: %w", err) + } + + s.logger.Debug("Participants count updated successfully", + zap.Uint("event_id", eventID), + zap.Int64("count", count), + ) + return nil +} + +// ToggleRegistrationStatus переключает статус регистрации на событие +func (s *eventService) ToggleRegistrationStatus(eventID uint, registrationOpen bool) error { + s.logger.Info("Toggling registration status", + zap.Uint("event_id", eventID), + zap.Bool("registration_open", registrationOpen), + ) + + if err := s.eventRepo.UpdateRegistrationStatus(eventID, registrationOpen); err != nil { + s.logger.Error("Failed to toggle registration status", + zap.Uint("event_id", eventID), + zap.Bool("registration_open", registrationOpen), + zap.Error(err), + ) + return fmt.Errorf("failed to toggle registration status: %w", err) + } + + s.logger.Info("Registration status updated successfully", + zap.Uint("event_id", eventID), + zap.Bool("registration_open", registrationOpen), + ) + return nil +} diff --git a/serv_nginx/api_bb/pkg/middleware/admin_middleware.go b/serv_nginx/api_bb/pkg/middleware/admin_middleware.go new file mode 100644 index 0000000..99b1acb --- /dev/null +++ b/serv_nginx/api_bb/pkg/middleware/admin_middleware.go @@ -0,0 +1,49 @@ +// pkg/middleware/admin_middleware.go +package middleware + +import ( + "api_bb/pkg/logger" + "api_bb/pkg/utils" + "net/http" + + "go.uber.org/zap" +) + +// AdminMiddleware проверяет, что пользователь имеет роль администратора +func AdminMiddleware(next http.Handler) http.Handler { + logger := logger.NewWrapper(logger.Get().With(zap.String("middleware", "admin"))) + + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + logger.Info("admin middleware check", + zap.String("method", r.Method), + zap.String("path", r.URL.Path), + zap.String("remote_addr", r.RemoteAddr), + ) + + // Получаем пользователя из контекста + user, ok := GetUserFromContext(r.Context()) + if !ok { + logger.Warn("admin middleware failed - user not found in context") + utils.RespondWithError(w, http.StatusUnauthorized, "Authentication required") + return + } + + // Проверяем роль пользователя + if user.Role != "admin" { + logger.Warn("admin middleware failed - insufficient permissions", + zap.Uint("user_id", user.ID), + zap.String("user_role", user.Role), + zap.String("required_role", "admin"), + ) + utils.RespondWithError(w, http.StatusForbidden, "Insufficient permissions: admin role required") + return + } + + logger.Debug("admin middleware passed", + zap.Uint("user_id", user.ID), + zap.String("user_email", user.Email), + ) + + next.ServeHTTP(w, r) + }) +}