modified: serv_nginx/api_bb/internal/database/migrate.go
new file: serv_nginx/api_bb/internal/handlers/event_handler.go new file: serv_nginx/api_bb/internal/handlers/event_registration_handler.go modified: serv_nginx/api_bb/internal/handlers/handlers.go modified: serv_nginx/api_bb/internal/models/event.go modified: serv_nginx/api_bb/internal/routes/routes.go new file: serv_nginx/api_bb/internal/service/event_registration_service.go new file: serv_nginx/api_bb/internal/service/event_service.go new file: serv_nginx/api_bb/pkg/middleware/admin_middleware.go add admin middleware, add event and eventRegistration handlers, routes, services, EndPoints
This commit is contained in:
@@ -22,6 +22,8 @@ func (d *Database) Migrate() error {
|
|||||||
&models.UserStats{},
|
&models.UserStats{},
|
||||||
&models.Workout{},
|
&models.Workout{},
|
||||||
&models.Achievement{},
|
&models.Achievement{},
|
||||||
|
&models.Event{},
|
||||||
|
&models.EventRegistration{},
|
||||||
// Добавьте другие модели здесь
|
// Добавьте другие модели здесь
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,6 +86,10 @@ func getModelName(model interface{}) string {
|
|||||||
return "Тренировки пользователя"
|
return "Тренировки пользователя"
|
||||||
case *models.Achievement:
|
case *models.Achievement:
|
||||||
return "Достижения пользователя"
|
return "Достижения пользователя"
|
||||||
|
case *models.Event:
|
||||||
|
return "Событие"
|
||||||
|
case *models.EventRegistration:
|
||||||
|
return "Администрирование события"
|
||||||
default:
|
default:
|
||||||
return "Unknown"
|
return "Unknown"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -11,15 +11,17 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Handler struct {
|
type Handler struct {
|
||||||
healthHandler *HealthHandler
|
healthHandler *HealthHandler
|
||||||
authHandler *AuthHandler
|
authHandler *AuthHandler
|
||||||
userHandler *UserHandler
|
userHandler *UserHandler
|
||||||
avatarHandler *AvatarHandler
|
avatarHandler *AvatarHandler
|
||||||
newsHandler *NewsHandler
|
newsHandler *NewsHandler
|
||||||
reviewHandler *ReviewHandler
|
reviewHandler *ReviewHandler
|
||||||
userStatsHandler *UserStatsHandler
|
userStatsHandler *UserStatsHandler
|
||||||
userWorkoutHandler *UserWorkoutHandler
|
userWorkoutHandler *UserWorkoutHandler
|
||||||
userAchievementHandler * UserAchievementHandler
|
userAchievementHandler *UserAchievementHandler
|
||||||
|
eventHandler *EventHandler
|
||||||
|
eventRegistrationHandler *EventRegistrationHandler
|
||||||
// Здесь будут добавлены другие обработчики
|
// Здесь будут добавлены другие обработчики
|
||||||
// userHandler *UserHandler
|
// userHandler *UserHandler
|
||||||
// eventHandler *EventHandler
|
// eventHandler *EventHandler
|
||||||
@@ -35,6 +37,8 @@ func NewHandler(db *gorm.DB, cfg *config.Config) *Handler {
|
|||||||
userStatsRepo := repository.NewUserStatsRepository(db)
|
userStatsRepo := repository.NewUserStatsRepository(db)
|
||||||
userWorkoutRepo := repository.NewWorkoutRepository(db)
|
userWorkoutRepo := repository.NewWorkoutRepository(db)
|
||||||
userAchievemenRepo := repository.NewAchievementRepository(db)
|
userAchievemenRepo := repository.NewAchievementRepository(db)
|
||||||
|
eventRepo := repository.NewEventRepository(db)
|
||||||
|
eventRegistrationRepo := repository.NewEventRegistrationRepository(db)
|
||||||
|
|
||||||
// Initialize logger
|
// Initialize logger
|
||||||
baseLogger := logger.NewWrapper(logger.Get()) // Создаем базовый логгер
|
baseLogger := logger.NewWrapper(logger.Get()) // Создаем базовый логгер
|
||||||
@@ -49,6 +53,8 @@ func NewHandler(db *gorm.DB, cfg *config.Config) *Handler {
|
|||||||
userStatsService := service.NewUserStatsService(userStatsRepo)
|
userStatsService := service.NewUserStatsService(userStatsRepo)
|
||||||
userWorkoutService := service.NewWorkoutService(userWorkoutRepo)
|
userWorkoutService := service.NewWorkoutService(userWorkoutRepo)
|
||||||
achievementService := service.NewAchievementService(userAchievemenRepo)
|
achievementService := service.NewAchievementService(userAchievemenRepo)
|
||||||
|
eventRegistrationService := service.NewEventRegistrationService(eventRegistrationRepo, eventRepo, baseLogger)
|
||||||
|
eventService := service.NewEventService(eventRepo, eventRegistrationRepo, baseLogger)
|
||||||
|
|
||||||
// Инициализация обработчиков
|
// Инициализация обработчиков
|
||||||
healthHandler := NewHealthHandler()
|
healthHandler := NewHealthHandler()
|
||||||
@@ -60,21 +66,33 @@ func NewHandler(db *gorm.DB, cfg *config.Config) *Handler {
|
|||||||
userStatsHandler := NewUserStatsHandler(userStatsService)
|
userStatsHandler := NewUserStatsHandler(userStatsService)
|
||||||
userWorkoutHandler := NewUserWorkoutHandler(userWorkoutService)
|
userWorkoutHandler := NewUserWorkoutHandler(userWorkoutService)
|
||||||
userAchievementHandler := NewUserAchievementHandler(*achievementService)
|
userAchievementHandler := NewUserAchievementHandler(*achievementService)
|
||||||
|
eventHandler := NewEventHandler(eventService)
|
||||||
|
eventRegistrationHandler := NewEventRegistrationHandler(eventRegistrationService)
|
||||||
|
|
||||||
return &Handler{
|
return &Handler{
|
||||||
healthHandler: healthHandler,
|
healthHandler: healthHandler,
|
||||||
authHandler: authHandler,
|
authHandler: authHandler,
|
||||||
userHandler: userHandler,
|
userHandler: userHandler,
|
||||||
newsHandler: newsHandler,
|
newsHandler: newsHandler,
|
||||||
avatarHandler: avatarHandler,
|
avatarHandler: avatarHandler,
|
||||||
reviewHandler: reviewHandler,
|
reviewHandler: reviewHandler,
|
||||||
userStatsHandler: userStatsHandler,
|
userStatsHandler: userStatsHandler,
|
||||||
userWorkoutHandler: userWorkoutHandler,
|
userWorkoutHandler: userWorkoutHandler,
|
||||||
userAchievementHandler: userAchievementHandler,
|
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 {
|
func (h *Handler) HealthHandler() *HealthHandler {
|
||||||
return h.healthHandler
|
return h.healthHandler
|
||||||
}
|
}
|
||||||
@@ -107,6 +125,6 @@ func (h *Handler) UserWorkoutHandler() *UserWorkoutHandler {
|
|||||||
return h.userWorkoutHandler
|
return h.userWorkoutHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h * Handler) UserAchievementHandler() *UserAchievementHandler {
|
func (h *Handler) UserAchievementHandler() *UserAchievementHandler {
|
||||||
return h.userAchievementHandler
|
return h.userAchievementHandler
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,6 @@ package models
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type EventType string
|
type EventType string
|
||||||
@@ -17,85 +15,35 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Event struct {
|
type Event struct {
|
||||||
ID uint `json:"id" gorm:"primaryKey"`
|
ID uint `gorm:"primaryKey" json:"id"`
|
||||||
Title string `json:"title" gorm:"size:255;not null"`
|
Title string `gorm:"size:255;not null" json:"title" validate:"required,min=5,max=255"`
|
||||||
Description string `json:"description" gorm:"type:text;not null"`
|
Description string `gorm:"type:text;not null" json:"description" validate:"required,min=10"`
|
||||||
Date time.Time `json:"date" gorm:"not null;index"`
|
Date time.Time `gorm:"not null" json:"date" validate:"required"`
|
||||||
Location string `json:"location" gorm:"size:255;not null"`
|
Location string `gorm:"size:255;not null" json:"location" validate:"required,max=255"`
|
||||||
Type EventType `json:"type" gorm:"type:varchar(20);not null"`
|
Type EventType `gorm:"size:50;not null" json:"type" validate:"required,oneof=race training social workshop"`
|
||||||
Distance string `json:"distance" gorm:"size:50"` // Дистанция забега
|
Distance string `gorm:"size:50" json:"distance" validate:"max=50"`
|
||||||
ParticipantsCount int `json:"participants_count" gorm:"default:0"` // Количество участников
|
ParticipantsCount int `gorm:"default:0" json:"participants_count"`
|
||||||
MaxParticipants int `json:"max_participants" gorm:"default:0"` // Максимальное количество участников
|
MaxParticipants int `gorm:"default:0" json:"max_participants" validate:"min=0"`
|
||||||
RegistrationOpen bool `json:"registration_open" gorm:"default:true"` // Открыта ли регистрация
|
RegistrationOpen bool `gorm:"default:true" json:"registration_open"`
|
||||||
Image string `json:"image" gorm:"size:500"` // Изображение события
|
Image string `gorm:"size:500" json:"image" validate:"max=500"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
|
||||||
UpdatedAt time.Time `json:"updated_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 {
|
type EventRegistration struct {
|
||||||
ID uint `json:"id" gorm:"primaryKey"`
|
ID uint `gorm:"primaryKey" json:"id"`
|
||||||
EventID uint `json:"event_id" gorm:"not null;index"`
|
UserID uint `gorm:"not null" json:"user_id"`
|
||||||
UserID uint `json:"user_id" gorm:"not null;index"`
|
EventID uint `gorm:"not null" json:"event_id"`
|
||||||
Status string `json:"status" gorm:"size:20;default:pending"` // pending, confirmed, cancelled, completed
|
Status string `gorm:"size:50;default:pending" json:"status" validate:"oneof=pending confirmed cancelled completed"`
|
||||||
ResultTime *string `json:"result_time" gorm:"size:20"` // Результат забега
|
Notes string `gorm:"type:text" json:"notes" validate:"max=500"`
|
||||||
BibNumber *string `json:"bib_number" gorm:"size:10"` // Стартовый номер
|
ResultTime string `gorm:"size:20" json:"result_time" validate:"max=20"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
|
||||||
|
|
||||||
// Связи
|
// Связи
|
||||||
Event Event `json:"event,omitempty" gorm:"foreignKey:EventID"`
|
User User `gorm:"foreignKey:UserID" json:"user,omitempty"`
|
||||||
User User `json:"user,omitempty" gorm:"foreignKey:UserID"`
|
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"`
|
|
||||||
}
|
|
||||||
@@ -24,7 +24,7 @@ func SetupRouter(db *gorm.DB, config *config.Config) http.Handler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// handler
|
// handler
|
||||||
allHandler := handlers.NewHandler(db, config)
|
h := handlers.NewHandler(db, config)
|
||||||
|
|
||||||
// Serve static files (avatars)
|
// Serve static files (avatars)
|
||||||
r.Handle("/uploads/*", http.StripPrefix("/uploads/",
|
r.Handle("/uploads/*", http.StripPrefix("/uploads/",
|
||||||
@@ -43,8 +43,8 @@ func SetupRouter(db *gorm.DB, config *config.Config) http.Handler {
|
|||||||
|
|
||||||
// Health routes
|
// Health routes
|
||||||
r.Route("/api", func(r chi.Router) {
|
r.Route("/api", func(r chi.Router) {
|
||||||
r.Get("/health", allHandler.HealthHandler().HealthCheck)
|
r.Get("/health", h.HealthHandler().HealthCheck)
|
||||||
r.Get("/check", allHandler.HealthHandler().Check)
|
r.Get("/check", h.HealthHandler().Check)
|
||||||
})
|
})
|
||||||
|
|
||||||
// API v1 routes
|
// API v1 routes
|
||||||
@@ -52,9 +52,17 @@ func SetupRouter(db *gorm.DB, config *config.Config) http.Handler {
|
|||||||
|
|
||||||
// Public auth routes
|
// Public auth routes
|
||||||
r.Route("/auth", func(r chi.Router) {
|
r.Route("/auth", func(r chi.Router) {
|
||||||
r.Post("/register", allHandler.AuthHandler().Register)
|
r.Post("/register", h.AuthHandler().Register)
|
||||||
r.Post("/login", allHandler.AuthHandler().Login)
|
r.Post("/login", h.AuthHandler().Login)
|
||||||
r.Post("/logout", allHandler.AuthHandler().Logout)
|
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
|
// Protected routes
|
||||||
@@ -63,70 +71,70 @@ func SetupRouter(db *gorm.DB, config *config.Config) http.Handler {
|
|||||||
r.Use(middleware.RequireAuth)
|
r.Use(middleware.RequireAuth)
|
||||||
|
|
||||||
// user profile routes
|
// user profile routes
|
||||||
r.Get("/profile", allHandler.UserHandler().GetProfile)
|
r.Get("/profile", h.UserHandler().GetProfile)
|
||||||
r.Post("/editProfile", allHandler.UserHandler().UpdateProfile)
|
r.Post("/editProfile", h.UserHandler().UpdateProfile)
|
||||||
r.Get("/", allHandler.UserHandler().GetUsers)
|
r.Get("/", h.UserHandler().GetUsers)
|
||||||
|
|
||||||
// Все операции с аватарами теперь через AvatarHandler
|
// Все операции с аватарами теперь через AvatarHandler
|
||||||
r.Route("/avatars", func(r chi.Router) {
|
r.Route("/avatars", func(r chi.Router) {
|
||||||
r.Post("/upload", allHandler.AvatarHandler().UploadAvatar)
|
r.Post("/upload", h.AvatarHandler().UploadAvatar)
|
||||||
r.Delete("/delete", allHandler.AvatarHandler().DeleteAvatar)
|
r.Delete("/delete", h.AvatarHandler().DeleteAvatar)
|
||||||
r.Get("/{filename}", allHandler.AvatarHandler().GetAvatar)
|
r.Get("/{filename}", h.AvatarHandler().GetAvatar)
|
||||||
})
|
})
|
||||||
|
|
||||||
r.Route("/stats", func(r chi.Router) {
|
r.Route("/stats", func(r chi.Router) {
|
||||||
r.Get("/", allHandler.UserStatsHandler().GetUserStats)
|
r.Get("/", h.UserStatsHandler().GetUserStats)
|
||||||
r.Get("/{userID}", allHandler.UserStatsHandler().GetUserStatsByID)
|
r.Get("/{userID}", h.UserStatsHandler().GetUserStatsByID)
|
||||||
r.Post("/workout", allHandler.UserStatsHandler().IncrementWorkout)
|
r.Post("/workout", h.UserStatsHandler().IncrementWorkout)
|
||||||
r.Put("/personal-best", allHandler.UserStatsHandler().UpdatePersonalBest)
|
r.Put("/personal-best", h.UserStatsHandler().UpdatePersonalBest)
|
||||||
r.Post("/weekly/reset", allHandler.UserStatsHandler().ResetWeeklyDistance)
|
r.Post("/weekly/reset", h.UserStatsHandler().ResetWeeklyDistance)
|
||||||
r.Post("/monthly/reset", allHandler.UserStatsHandler().ResetMonthlyDistance)
|
r.Post("/monthly/reset", h.UserStatsHandler().ResetMonthlyDistance)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Маршруты для тренировок
|
// Маршруты для тренировок
|
||||||
r.Route("/workouts", func(r chi.Router) {
|
r.Route("/workouts", func(r chi.Router) {
|
||||||
r.Post("/", allHandler.UserWorkoutHandler().CreateWorkout)
|
r.Post("/", h.UserWorkoutHandler().CreateWorkout)
|
||||||
r.Get("/", allHandler.UserWorkoutHandler().GetWorkouts)
|
r.Get("/", h.UserWorkoutHandler().GetWorkouts)
|
||||||
r.Get("/stats", allHandler.UserWorkoutHandler().GetWorkoutStats)
|
r.Get("/stats", h.UserWorkoutHandler().GetWorkoutStats)
|
||||||
r.Get("/type/{type}", allHandler.UserWorkoutHandler().GetWorkoutsByType)
|
r.Get("/type/{type}", h.UserWorkoutHandler().GetWorkoutsByType)
|
||||||
|
|
||||||
r.Route("/{id}", func(r chi.Router) {
|
r.Route("/{id}", func(r chi.Router) {
|
||||||
r.Get("/", allHandler.UserWorkoutHandler().GetWorkoutByID)
|
r.Get("/", h.UserWorkoutHandler().GetWorkoutByID)
|
||||||
r.Put("/", allHandler.UserWorkoutHandler().UpdateWorkout)
|
r.Put("/", h.UserWorkoutHandler().UpdateWorkout)
|
||||||
r.Delete("/", allHandler.UserWorkoutHandler().DeleteWorkout)
|
r.Delete("/", h.UserWorkoutHandler().DeleteWorkout)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// Маршруты для достижений (achievements)
|
// Маршруты для достижений (achievements)
|
||||||
r.Route("/achievements", func(r chi.Router) {
|
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) {
|
r.Route("/{id}", func(r chi.Router) {
|
||||||
// Получение достижения по ID
|
// Получение достижения по 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.Route("/news", func(r chi.Router) {
|
||||||
|
|
||||||
// Публичные маршруты
|
// Публичные маршруты
|
||||||
r.Get("/", allHandler.NewsHandler().GetNews)
|
r.Get("/", h.NewsHandler().GetNews)
|
||||||
r.Get("/{id}", allHandler.NewsHandler().GetNewsByID)
|
r.Get("/{id}", h.NewsHandler().GetNewsByID)
|
||||||
r.Get("/{id}/comments", allHandler.NewsHandler().GetComments)
|
r.Get("/{id}/comments", h.NewsHandler().GetComments)
|
||||||
r.Get("/check", allHandler.HealthHandler().Check)
|
r.Get("/check", h.HealthHandler().Check)
|
||||||
|
|
||||||
// Защищенные маршруты
|
// Защищенные маршруты
|
||||||
r.Group(func(r chi.Router) {
|
r.Group(func(r chi.Router) {
|
||||||
@@ -145,44 +153,77 @@ func SetupRouter(db *gorm.DB, config *config.Config) http.Handler {
|
|||||||
r.Use(middleware.RequireAuth)
|
r.Use(middleware.RequireAuth)
|
||||||
|
|
||||||
// News EndPoints
|
// News EndPoints
|
||||||
r.Post("/", allHandler.NewsHandler().CreateNews)
|
r.Post("/", h.NewsHandler().CreateNews)
|
||||||
r.Put("/{id}", allHandler.NewsHandler().UpdateNews)
|
r.Put("/{id}", h.NewsHandler().UpdateNews)
|
||||||
r.Delete("/{id}", allHandler.NewsHandler().DeleteNews)
|
r.Delete("/{id}", h.NewsHandler().DeleteNews)
|
||||||
r.Get("/my/news", allHandler.NewsHandler().GetUserNews)
|
r.Get("/my/news", h.NewsHandler().GetUserNews)
|
||||||
|
|
||||||
r.Post("/{id}/comments", allHandler.NewsHandler().CreateComment)
|
r.Post("/{id}/comments", h.NewsHandler().CreateComment)
|
||||||
r.Delete("/comments/{commentId}", allHandler.NewsHandler().DeleteComment)
|
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.Route("/reviews", func(r chi.Router) {
|
||||||
// Публичные маршруты
|
// Публичные маршруты
|
||||||
r.Get("/", allHandler.ReviewHandler().GetReviews)
|
r.Get("/", h.ReviewHandler().GetReviews)
|
||||||
r.Get("/stats", allHandler.ReviewHandler().GetReviewsStats)
|
r.Get("/stats", h.ReviewHandler().GetReviewsStats)
|
||||||
r.Get("/{id}", allHandler.ReviewHandler().GetReviewByID)
|
r.Get("/{id}", h.ReviewHandler().GetReviewByID)
|
||||||
|
|
||||||
// Защищенные маршруты
|
// Защищенные маршруты
|
||||||
r.Group(func(r chi.Router) {
|
r.Group(func(r chi.Router) {
|
||||||
r.Use(middleware.AuthMiddleware(jwtService, userRepo))
|
r.Use(middleware.AuthMiddleware(jwtService, userRepo))
|
||||||
r.Use(middleware.RequireAuth)
|
r.Use(middleware.RequireAuth)
|
||||||
|
|
||||||
r.Post("/", allHandler.ReviewHandler().CreateReview)
|
r.Post("/", h.ReviewHandler().CreateReview)
|
||||||
r.Get("/my", allHandler.ReviewHandler().GetMyReviews)
|
r.Get("/my", h.ReviewHandler().GetMyReviews)
|
||||||
r.Put("/{id}", allHandler.ReviewHandler().UpdateReview)
|
r.Put("/{id}", h.ReviewHandler().UpdateReview)
|
||||||
r.Delete("/{id}", allHandler.ReviewHandler().DeleteReview)
|
r.Delete("/{id}", h.ReviewHandler().DeleteReview)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// Публичные маршруты для достижений (если нужны)
|
r.Route("/events", func(r chi.Router) {
|
||||||
r.Route("/achievements", func(r chi.Router) {
|
|
||||||
// Публичные маршруты для просмотра достижений других пользователей
|
// Публичные маршруты
|
||||||
r.Get("/user/{userID}", allHandler.UserAchievementHandler().GetPublicUserAchievements)
|
r.Get("/", h.EventHandler().GetAllEvents)
|
||||||
r.Get("/user/{userID}/summary", allHandler.UserAchievementHandler().GetPublicUserAchievementsSummary)
|
r.Get("/upcoming", h.EventHandler().GetUpcomingEvents)
|
||||||
r.Get("/user/{userID}/recent", allHandler.UserAchievementHandler().GetPublicRecentAchievements)
|
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)
|
routeLogger.LogRoutes(r)
|
||||||
|
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
})
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user