modified: serv_nginx/api_bb/internal/handlers/handlers.go

new file:   serv_nginx/api_bb/internal/handlers/user_workout_handler.go
	modified:   serv_nginx/api_bb/internal/models/workout.go
	modified:   serv_nginx/api_bb/internal/repository/workout_repository.go
	modified:   serv_nginx/api_bb/internal/routes/routes.go
	new file:   serv_nginx/api_bb/internal/service/user_workout_service.go
	new file:   serv_nginx/api_bb/pkg/utils/response.go
	new file:   serv_nginx/api_bb/pkg/utils/validation.go
add workout EndPoints for workout struct, CRUD operations and logics
This commit is contained in:
2025-10-19 08:20:21 +05:00
parent 15c59b2f55
commit 6c02f11570
8 changed files with 1077 additions and 9 deletions
@@ -18,6 +18,7 @@ type Handler struct {
newsHandler *NewsHandler
reviewHandler *ReviewHandler
userStatsHandler *UserStatsHandler
userWorkoutHandler *UserWorkoutHandler
// Здесь будут добавлены другие обработчики
// userHandler *UserHandler
// eventHandler *EventHandler
@@ -31,6 +32,7 @@ func NewHandler(db *gorm.DB, cfg *config.Config) *Handler {
commentRepo := repository.NewCommentRepository(db)
reviewRepo := repository.NewReviewRepository(db)
userStatsRepo := repository.NewUserStatsRepository(db)
userWorkoutRepo := repository.NewWorkoutRepository(db)
// Initialize logger
baseLogger := logger.NewWrapper(logger.Get()) // Создаем базовый логгер
@@ -43,6 +45,7 @@ func NewHandler(db *gorm.DB, cfg *config.Config) *Handler {
newsService := service.NewNewsService(newsRepo, commentRepo, baseLogger)
reviewService := service.NewReviewService(reviewRepo, baseLogger)
userStatsService := service.NewUserStatsService(userStatsRepo)
userWorkoutService := service.NewWorkoutService(userWorkoutRepo)
// Инициализация обработчиков
healthHandler := NewHealthHandler()
@@ -52,6 +55,7 @@ func NewHandler(db *gorm.DB, cfg *config.Config) *Handler {
avatarHandler := NewAvatarHandler(avatarService)
reviewHandler := NewReviewHandler(reviewService, baseLogger)
userStatsHandler := NewUserStatsHandler(userStatsService)
userWorkoutHandler := NewUserWorkoutHandler(userWorkoutService)
return &Handler{
healthHandler: healthHandler,
@@ -61,6 +65,7 @@ func NewHandler(db *gorm.DB, cfg *config.Config) *Handler {
avatarHandler: avatarHandler,
reviewHandler: reviewHandler,
userStatsHandler: userStatsHandler,
userWorkoutHandler: userWorkoutHandler,
}
}
@@ -92,3 +97,7 @@ func (h *Handler) ReviewHandler() *ReviewHandler {
func (h *Handler) UserStatsHandler() *UserStatsHandler {
return h.userStatsHandler
}
func (h *Handler) UserWorkoutHandler() *UserWorkoutHandler {
return h.userWorkoutHandler
}
@@ -0,0 +1,374 @@
// handlers/user_workout_handler.go
package handlers
import (
"encoding/json"
"net/http"
"strconv"
"api_bb/internal/models"
"api_bb/internal/service"
"api_bb/pkg/logger"
"api_bb/pkg/middleware"
"api_bb/pkg/utils"
"go.uber.org/zap"
"github.com/go-chi/chi/v5"
)
type UserWorkoutHandler struct {
logger logger.LoggerInterface
workoutService service.WorkoutService
}
func NewUserWorkoutHandler(workoutService service.WorkoutService) *UserWorkoutHandler {
return &UserWorkoutHandler{
logger: logger.NewWrapper(logger.Get().With(zap.String("handler", "user_workout"))),
workoutService: workoutService,
}
}
// CreateWorkout создает новую тренировку
func (h *UserWorkoutHandler) CreateWorkout(w http.ResponseWriter, r *http.Request) {
h.logger.Info("handling create workout 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 workout failed - authentication required")
utils.RespondWithError(w, http.StatusUnauthorized, "Authentication required")
return
}
var req models.WorkoutCreateRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
h.logger.Error("failed to decode JSON payload", zap.Error(err))
utils.RespondWithError(w, http.StatusBadRequest, "Invalid request payload: "+err.Error())
return
}
// Валидация
if err := utils.ValidateStruct(req); err != nil {
h.logger.Warn("create workout failed - validation error", zap.Error(err))
utils.RespondWithError(w, http.StatusBadRequest, "Validation error: "+err.Error())
return
}
h.logger.Info("creating new workout",
zap.Uint("user_id", user.ID),
zap.String("type", string(req.Type)),
zap.Float64("distance", req.Distance),
zap.Int("duration", req.Duration),
)
// Создаем тренировку
workout, err := h.workoutService.CreateWorkout(user.ID, &req)
if err != nil {
h.logger.Error("failed to create workout in service",
zap.Uint("user_id", user.ID),
zap.Error(err),
)
utils.RespondWithError(w, http.StatusInternalServerError, "Failed to create workout: "+err.Error())
return
}
h.logger.Info("workout created successfully",
zap.Uint("workout_id", workout.ID),
zap.Uint("user_id", user.ID),
)
utils.RespondWithJSON(w, http.StatusCreated, map[string]interface{}{
"message": "Workout created successfully",
"workout": workout,
})
}
// GetWorkouts возвращает список тренировок пользователя
func (h *UserWorkoutHandler) GetWorkouts(w http.ResponseWriter, r *http.Request) {
h.logger.Info("handling get workouts 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 workouts failed - authentication required")
utils.RespondWithError(w, http.StatusUnauthorized, "Authentication required")
return
}
workouts, err := h.workoutService.GetUserWorkouts(user.ID)
if err != nil {
h.logger.Error("failed to get user workouts from service",
zap.Uint("user_id", user.ID),
zap.Error(err),
)
utils.RespondWithError(w, http.StatusInternalServerError, "Failed to get workouts: "+err.Error())
return
}
h.logger.Info("user workouts retrieved successfully",
zap.Uint("user_id", user.ID),
zap.Int("workouts_count", len(workouts)),
)
utils.RespondWithJSON(w, http.StatusOK, workouts)
}
// GetWorkoutByID возвращает тренировку по ID
func (h *UserWorkoutHandler) GetWorkoutByID(w http.ResponseWriter, r *http.Request) {
h.logger.Info("handling get workout by ID 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 workout failed - authentication required")
utils.RespondWithError(w, http.StatusUnauthorized, "Authentication required")
return
}
// Получаем ID тренировки из URL параметров
workoutIDStr := chi.URLParam(r, "id")
workoutID, err := strconv.ParseUint(workoutIDStr, 10, 32)
if err != nil {
h.logger.Warn("invalid workout ID", zap.String("workout_id", workoutIDStr))
utils.RespondWithError(w, http.StatusBadRequest, "Invalid workout ID")
return
}
workout, err := h.workoutService.GetWorkoutByID(user.ID, uint(workoutID))
if err != nil {
h.logger.Error("failed to get workout from service",
zap.Uint("user_id", user.ID),
zap.Uint("workout_id", uint(workoutID)),
zap.Error(err),
)
utils.RespondWithError(w, http.StatusNotFound, "Workout not found: "+err.Error())
return
}
h.logger.Info("workout retrieved successfully",
zap.Uint("user_id", user.ID),
zap.Uint("workout_id", uint(workoutID)),
)
utils.RespondWithJSON(w, http.StatusOK, workout)
}
// UpdateWorkout обновляет тренировку
func (h *UserWorkoutHandler) UpdateWorkout(w http.ResponseWriter, r *http.Request) {
h.logger.Info("handling update workout 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 workout failed - authentication required")
utils.RespondWithError(w, http.StatusUnauthorized, "Authentication required")
return
}
// Получаем ID тренировки из URL параметров
workoutIDStr := chi.URLParam(r, "id")
workoutID, err := strconv.ParseUint(workoutIDStr, 10, 32)
if err != nil {
h.logger.Warn("invalid workout ID", zap.String("workout_id", workoutIDStr))
utils.RespondWithError(w, http.StatusBadRequest, "Invalid workout ID")
return
}
var req models.WorkoutUpdateRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
h.logger.Error("failed to decode JSON payload", zap.Error(err))
utils.RespondWithError(w, http.StatusBadRequest, "Invalid request payload: "+err.Error())
return
}
// Валидация
if err := utils.ValidateStruct(req); err != nil {
h.logger.Warn("update workout failed - validation error", zap.Error(err))
utils.RespondWithError(w, http.StatusBadRequest, "Validation error: "+err.Error())
return
}
h.logger.Info("updating workout",
zap.Uint("user_id", user.ID),
zap.Uint("workout_id", uint(workoutID)),
zap.String("type", string(req.Type)),
)
// Обновляем тренировку
workout, err := h.workoutService.UpdateWorkout(user.ID, uint(workoutID), &req)
if err != nil {
h.logger.Error("failed to update workout in service",
zap.Uint("user_id", user.ID),
zap.Uint("workout_id", uint(workoutID)),
zap.Error(err),
)
utils.RespondWithError(w, http.StatusInternalServerError, "Failed to update workout: "+err.Error())
return
}
h.logger.Info("workout updated successfully",
zap.Uint("user_id", user.ID),
zap.Uint("workout_id", uint(workoutID)),
)
utils.RespondWithJSON(w, http.StatusOK, map[string]interface{}{
"message": "Workout updated successfully",
"workout": workout,
})
}
// DeleteWorkout удаляет тренировку
func (h *UserWorkoutHandler) DeleteWorkout(w http.ResponseWriter, r *http.Request) {
h.logger.Info("handling delete workout 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 workout failed - authentication required")
utils.RespondWithError(w, http.StatusUnauthorized, "Authentication required")
return
}
// Получаем ID тренировки из URL параметров
workoutIDStr := chi.URLParam(r, "id")
workoutID, err := strconv.ParseUint(workoutIDStr, 10, 32)
if err != nil {
h.logger.Warn("invalid workout ID", zap.String("workout_id", workoutIDStr))
utils.RespondWithError(w, http.StatusBadRequest, "Invalid workout ID")
return
}
h.logger.Info("deleting workout",
zap.Uint("user_id", user.ID),
zap.Uint("workout_id", uint(workoutID)),
)
// Удаляем тренировку
if err := h.workoutService.DeleteWorkout(user.ID, uint(workoutID)); err != nil {
h.logger.Error("failed to delete workout in service",
zap.Uint("user_id", user.ID),
zap.Uint("workout_id", uint(workoutID)),
zap.Error(err),
)
utils.RespondWithError(w, http.StatusInternalServerError, "Failed to delete workout: "+err.Error())
return
}
h.logger.Info("workout deleted successfully",
zap.Uint("user_id", user.ID),
zap.Uint("workout_id", uint(workoutID)),
)
utils.RespondWithJSON(w, http.StatusOK, map[string]string{
"message": "Workout deleted successfully",
})
}
// GetWorkoutStats возвращает статистику тренировок
func (h *UserWorkoutHandler) GetWorkoutStats(w http.ResponseWriter, r *http.Request) {
h.logger.Info("handling get workout stats 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 workout stats failed - authentication required")
utils.RespondWithError(w, http.StatusUnauthorized, "Authentication required")
return
}
stats, err := h.workoutService.GetWorkoutStats(user.ID)
if err != nil {
h.logger.Error("failed to get workout stats from service",
zap.Uint("user_id", user.ID),
zap.Error(err),
)
utils.RespondWithError(w, http.StatusInternalServerError, "Failed to get workout stats: "+err.Error())
return
}
h.logger.Info("workout stats retrieved successfully",
zap.Uint("user_id", user.ID),
zap.Int("total_workouts", stats.TotalWorkouts),
zap.Float64("total_distance", stats.TotalDistance),
)
utils.RespondWithJSON(w, http.StatusOK, stats)
}
// GetWorkoutsByType возвращает тренировки по типу
func (h *UserWorkoutHandler) GetWorkoutsByType(w http.ResponseWriter, r *http.Request) {
h.logger.Info("handling get workouts by type 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 workouts by type failed - authentication required")
utils.RespondWithError(w, http.StatusUnauthorized, "Authentication required")
return
}
// Получаем тип тренировки из URL параметров
workoutType := models.WorkoutType(chi.URLParam(r, "type"))
// Валидация типа тренировки
validTypes := map[models.WorkoutType]bool{
models.WorkoutTypeEasy: true,
models.WorkoutTypeTempo: true,
models.WorkoutTypeInterval: true,
models.WorkoutTypeLong: true,
models.WorkoutTypeRecovery: true,
}
if !validTypes[workoutType] {
h.logger.Warn("invalid workout type", zap.String("type", string(workoutType)))
utils.RespondWithError(w, http.StatusBadRequest, "Invalid workout type")
return
}
workouts, err := h.workoutService.GetWorkoutsByType(user.ID, workoutType)
if err != nil {
h.logger.Error("failed to get workouts by type from service",
zap.Uint("user_id", user.ID),
zap.String("type", string(workoutType)),
zap.Error(err),
)
utils.RespondWithError(w, http.StatusInternalServerError, "Failed to get workouts: "+err.Error())
return
}
h.logger.Info("workouts by type retrieved successfully",
zap.Uint("user_id", user.ID),
zap.String("type", string(workoutType)),
zap.Int("workouts_count", len(workouts)),
)
utils.RespondWithJSON(w, http.StatusOK, workouts)
}
+9 -9
View File
@@ -56,20 +56,20 @@ type WorkoutCreateRequest struct {
Type WorkoutType `json:"type" validate:"required,oneof=easy tempo interval long recovery"`
Distance float64 `json:"distance_km" validate:"required,min=0.1,max=1000"`
Duration int `json:"duration_min" validate:"required,min=1,max=1440"`
Pace string `json:"pace" validate:"max=20"`
Calories int `json:"calories" validate:"min=0,max=5000"`
Notes string `json:"notes" validate:"max=1000"`
Pace string `json:"pace" validate:"maxlen=20"`
Calories int `json:"calories" validate:"minint=0,maxint=5000"`
Notes string `json:"notes" validate:"maxlen=1000"`
Date time.Time `json:"date" validate:"required"`
}
// DTO для обновления тренировки
type WorkoutUpdateRequest struct {
Type WorkoutType `json:"type" validate:"omitempty,oneof=easy tempo interval long recovery"`
Distance float64 `json:"distance_km" validate:"omitempty,min=0.1,max=1000"`
Duration int `json:"duration_min" validate:"omitempty,min=1,max=1440"`
Pace string `json:"pace" validate:"omitempty,max=20"`
Calories int `json:"calories" validate:"omitempty,min=0,max=5000"`
Notes string `json:"notes" validate:"omitempty,max=1000"`
Type WorkoutType `json:"type" validate:"oneof=easy tempo interval long recovery"`
Distance float64 `json:"distance_km" validate:"min=0.1,max=1000"`
Duration int `json:"duration_min" validate:"min=1,max=1440"`
Pace string `json:"pace" validate:"maxlen=20"`
Calories int `json:"calories" validate:"minint=0,maxint=5000"`
Notes string `json:"notes" validate:"maxlen=1000"`
Date time.Time `json:"date"`
}
@@ -3,6 +3,7 @@ package repository
import (
"api_bb/internal/models"
"errors"
"fmt"
"time"
@@ -26,6 +27,10 @@ type workoutRepository struct {
db *gorm.DB
}
var (
ErrNotFound = errors.New("record not found")
)
func NewWorkoutRepository(db *gorm.DB) WorkoutRepository {
return &workoutRepository{db: db}
}
@@ -83,6 +83,20 @@ func SetupRouter(db *gorm.DB, config *config.Config) http.Handler {
r.Post("/weekly/reset", allHandler.UserStatsHandler().ResetWeeklyDistance)
r.Post("/monthly/reset", allHandler.UserStatsHandler().ResetMonthlyDistance)
})
// Маршруты для тренировок
r.Route("/workouts", func(r chi.Router) {
r.Post("/", allHandler.UserWorkoutHandler().CreateWorkout)
r.Get("/", allHandler.UserWorkoutHandler().GetWorkouts)
r.Get("/stats", allHandler.UserWorkoutHandler().GetWorkoutStats)
r.Get("/type/{type}", allHandler.UserWorkoutHandler().GetWorkoutsByType)
r.Route("/{id}", func(r chi.Router) {
r.Get("/", allHandler.UserWorkoutHandler().GetWorkoutByID)
r.Put("/", allHandler.UserWorkoutHandler().UpdateWorkout)
r.Delete("/", allHandler.UserWorkoutHandler().DeleteWorkout)
})
})
// Здесь будут другие защищенные маршруты пользователя
})
@@ -0,0 +1,285 @@
// service/user_workout_service.go
package service
import (
"api_bb/internal/models"
"api_bb/internal/repository"
"api_bb/pkg/logger"
"go.uber.org/zap"
)
type WorkoutService interface {
CreateWorkout(userID uint, req *models.WorkoutCreateRequest) (*models.Workout, error)
GetUserWorkouts(userID uint) ([]models.Workout, error)
GetWorkoutByID(userID uint, workoutID uint) (*models.Workout, error)
UpdateWorkout(userID uint, workoutID uint, req *models.WorkoutUpdateRequest) (*models.Workout, error)
DeleteWorkout(userID uint, workoutID uint) error
GetWorkoutStats(userID uint) (*models.WorkoutStatsResponse, error)
GetWorkoutsByType(userID uint, workoutType models.WorkoutType) ([]models.Workout, error)
GetLatestWorkouts(userID uint, limit int) ([]models.Workout, error)
}
type workoutService struct {
workoutRepo repository.WorkoutRepository
logger logger.LoggerInterface
}
func NewWorkoutService(workoutRepo repository.WorkoutRepository) WorkoutService {
return &workoutService{
workoutRepo: workoutRepo,
logger: logger.NewWrapper(logger.Get().With(zap.String("service", "workout"))),
}
}
// CreateWorkout создает новую тренировку
func (s *workoutService) CreateWorkout(userID uint, req *models.WorkoutCreateRequest) (*models.Workout, error) {
s.logger.Info("creating new workout",
zap.Uint("user_id", userID),
zap.String("type", string(req.Type)),
zap.Float64("distance", req.Distance),
)
// Создаем модель тренировки
workout := &models.Workout{
UserID: userID,
Type: req.Type,
Distance: req.Distance,
Duration: req.Duration,
Pace: req.Pace,
Calories: req.Calories,
Notes: req.Notes,
Date: req.Date,
}
// Сохраняем в репозитории
if err := s.workoutRepo.Create(workout); err != nil {
s.logger.Error("failed to create workout in repository",
zap.Uint("user_id", userID),
zap.Error(err),
)
return nil, err
}
s.logger.Info("workout created successfully",
zap.Uint("workout_id", workout.ID),
zap.Uint("user_id", userID),
)
return workout, nil
}
// GetUserWorkouts возвращает все тренировки пользователя
func (s *workoutService) GetUserWorkouts(userID uint) ([]models.Workout, error) {
s.logger.Debug("getting user workouts", zap.Uint("user_id", userID))
workouts, err := s.workoutRepo.FindByUserID(userID)
if err != nil {
s.logger.Error("failed to get user workouts from repository",
zap.Uint("user_id", userID),
zap.Error(err),
)
return nil, err
}
s.logger.Debug("retrieved user workouts",
zap.Uint("user_id", userID),
zap.Int("count", len(workouts)),
)
return workouts, nil
}
// GetWorkoutByID возвращает тренировку по ID
func (s *workoutService) GetWorkoutByID(userID uint, workoutID uint) (*models.Workout, error) {
s.logger.Debug("getting workout by ID",
zap.Uint("user_id", userID),
zap.Uint("workout_id", workoutID),
)
workout, err := s.workoutRepo.FindByID(workoutID)
if err != nil {
s.logger.Error("failed to get workout from repository",
zap.Uint("user_id", userID),
zap.Uint("workout_id", workoutID),
zap.Error(err),
)
return nil, err
}
// Проверяем, что тренировка принадлежит пользователю
if workout.UserID != userID {
s.logger.Warn("workout access denied - user mismatch",
zap.Uint("user_id", userID),
zap.Uint("workout_user_id", workout.UserID),
zap.Uint("workout_id", workoutID),
)
return nil, repository.ErrNotFound
}
s.logger.Debug("workout retrieved successfully",
zap.Uint("user_id", userID),
zap.Uint("workout_id", workoutID),
)
return workout, nil
}
// UpdateWorkout обновляет тренировку
func (s *workoutService) UpdateWorkout(userID uint, workoutID uint, req *models.WorkoutUpdateRequest) (*models.Workout, error) {
s.logger.Info("updating workout",
zap.Uint("user_id", userID),
zap.Uint("workout_id", workoutID),
)
// Сначала получаем существующую тренировку
workout, err := s.GetWorkoutByID(userID, workoutID)
if err != nil {
return nil, err
}
// Обновляем только переданные поля
if req.Type != "" {
workout.Type = req.Type
}
if req.Distance > 0 {
workout.Distance = req.Distance
}
if req.Duration > 0 {
workout.Duration = req.Duration
}
if req.Pace != "" {
workout.Pace = req.Pace
}
if req.Calories > 0 {
workout.Calories = req.Calories
}
if req.Notes != "" {
workout.Notes = req.Notes
}
if !req.Date.IsZero() {
workout.Date = req.Date
}
// Сохраняем обновления
if err := s.workoutRepo.Update(workout); err != nil {
s.logger.Error("failed to update workout in repository",
zap.Uint("user_id", userID),
zap.Uint("workout_id", workoutID),
zap.Error(err),
)
return nil, err
}
s.logger.Info("workout updated successfully",
zap.Uint("user_id", userID),
zap.Uint("workout_id", workoutID),
)
return workout, nil
}
// DeleteWorkout удаляет тренировку
func (s *workoutService) DeleteWorkout(userID uint, workoutID uint) error {
s.logger.Info("deleting workout",
zap.Uint("user_id", userID),
zap.Uint("workout_id", workoutID),
)
// Проверяем, что тренировка существует и принадлежит пользователю
workout, err := s.GetWorkoutByID(userID, workoutID)
if err != nil {
return err
}
// Удаляем тренировку
if err := s.workoutRepo.Delete(workout.ID); err != nil {
s.logger.Error("failed to delete workout from repository",
zap.Uint("user_id", userID),
zap.Uint("workout_id", workoutID),
zap.Error(err),
)
return err
}
s.logger.Info("workout deleted successfully",
zap.Uint("user_id", userID),
zap.Uint("workout_id", workoutID),
)
return nil
}
// GetWorkoutStats возвращает статистику тренировок
func (s *workoutService) GetWorkoutStats(userID uint) (*models.WorkoutStatsResponse, error) {
s.logger.Debug("getting workout stats", zap.Uint("user_id", userID))
stats, err := s.workoutRepo.GetWorkoutStats(userID)
if err != nil {
s.logger.Error("failed to get workout stats from repository",
zap.Uint("user_id", userID),
zap.Error(err),
)
return nil, err
}
s.logger.Debug("workout stats retrieved successfully",
zap.Uint("user_id", userID),
zap.Int("total_workouts", stats.TotalWorkouts),
zap.Float64("total_distance", stats.TotalDistance),
)
return stats, nil
}
// GetWorkoutsByType возвращает тренировки по типу
func (s *workoutService) GetWorkoutsByType(userID uint, workoutType models.WorkoutType) ([]models.Workout, error) {
s.logger.Debug("getting workouts by type",
zap.Uint("user_id", userID),
zap.String("type", string(workoutType)),
)
workouts, err := s.workoutRepo.GetByType(userID, workoutType)
if err != nil {
s.logger.Error("failed to get workouts by type from repository",
zap.Uint("user_id", userID),
zap.String("type", string(workoutType)),
zap.Error(err),
)
return nil, err
}
s.logger.Debug("workouts by type retrieved successfully",
zap.Uint("user_id", userID),
zap.String("type", string(workoutType)),
zap.Int("count", len(workouts)),
)
return workouts, nil
}
// GetLatestWorkouts возвращает последние тренировки
func (s *workoutService) GetLatestWorkouts(userID uint, limit int) ([]models.Workout, error) {
s.logger.Debug("getting latest workouts",
zap.Uint("user_id", userID),
zap.Int("limit", limit),
)
workouts, err := s.workoutRepo.GetLatestWorkouts(userID, limit)
if err != nil {
s.logger.Error("failed to get latest workouts from repository",
zap.Uint("user_id", userID),
zap.Int("limit", limit),
zap.Error(err),
)
return nil, err
}
s.logger.Debug("latest workouts retrieved successfully",
zap.Uint("user_id", userID),
zap.Int("limit", limit),
zap.Int("count", len(workouts)),
)
return workouts, nil
}