// service/user_stats_service.go package service import ( "time" "api_bb/internal/models" "api_bb/internal/repository" "api_bb/pkg/logger" "go.uber.org/zap" ) type UserStatsService interface { GetUserStats(userID uint) (*models.UserStatsResponse, error) UpdatePersonalBest(userID uint, distanceType string, time string) error IncrementWorkout(userID uint, distance float64, duration int) error ResetWeeklyDistance(userID uint) error ResetMonthlyDistance(userID uint) error CreateUserStats(userID uint) error } type userStatsService struct { logger logger.LoggerInterface userStatsRepo repository.UserStatsRepository } func NewUserStatsService(userStatsRepo repository.UserStatsRepository) UserStatsService { return &userStatsService{ logger: logger.NewWrapper(logger.Get().With(zap.String("service", "user_stats"))), userStatsRepo: userStatsRepo, } } // GetUserStats возвращает статистику пользователя в формате DTO func (s *userStatsService) GetUserStats(userID uint) (*models.UserStatsResponse, error) { s.logger.Info("getting user stats", zap.Uint("user_id", userID), ) stats, err := s.userStatsRepo.GetUserStatsResponse(userID) if err != nil { s.logger.Error("failed to get user stats from repository", zap.Uint("user_id", userID), zap.Error(err), ) return nil, err } s.logger.Debug("user stats retrieved successfully", zap.Uint("user_id", userID), zap.Float64("total_distance", stats.TotalDistance), zap.Int("workouts_count", stats.WorkoutsCount), ) return stats, nil } // UpdatePersonalBest обновляет личный рекорд пользователя func (s *userStatsService) UpdatePersonalBest(userID uint, distanceType string, time string) error { s.logger.Info("updating personal best", zap.Uint("user_id", userID), zap.String("distance_type", distanceType), zap.String("time", time), ) // Проверяем существование статистики пользователя _, err := s.userStatsRepo.GetByUserID(userID) if err != nil { s.logger.Warn("user stats not found, creating new stats", zap.Uint("user_id", userID), ) if err := s.CreateUserStats(userID); err != nil { return err } } if err := s.userStatsRepo.UpdatePersonalBest(userID, distanceType, time); err != nil { s.logger.Error("failed to update personal best in repository", zap.Uint("user_id", userID), zap.String("distance_type", distanceType), zap.Error(err), ) return err } s.logger.Info("personal best updated successfully", zap.Uint("user_id", userID), zap.String("distance_type", distanceType), zap.String("time", time), ) return nil } // IncrementWorkout увеличивает счетчик тренировок и обновляет статистику func (s *userStatsService) IncrementWorkout(userID uint, distance float64, duration int) error { s.logger.Info("incrementing workout stats", zap.Uint("user_id", userID), zap.Float64("distance", distance), zap.Int("duration", duration), ) // Проверяем существование статистики пользователя _, err := s.userStatsRepo.GetByUserID(userID) if err != nil { s.logger.Warn("user stats not found, creating new stats", zap.Uint("user_id", userID), ) if err := s.CreateUserStats(userID); err != nil { return err } } // Обновляем серии тренировок currentTime := time.Now() if err := s.userStatsRepo.UpdateStreaks(userID, currentTime); err != nil { s.logger.Error("failed to update streaks in repository", zap.Uint("user_id", userID), zap.Error(err), ) return err } // Обновляем недельный и месячный пробег if err := s.userStatsRepo.UpdateWeeklyDistance(userID, distance); err != nil { s.logger.Error("failed to update weekly distance in repository", zap.Uint("user_id", userID), zap.Error(err), ) return err } if err := s.userStatsRepo.UpdateMonthlyDistance(userID, distance); err != nil { s.logger.Error("failed to update monthly distance in repository", zap.Uint("user_id", userID), zap.Error(err), ) return err } // Увеличиваем счетчик тренировок и обновляем общие показатели if err := s.userStatsRepo.IncrementWorkouts(userID, distance, duration); err != nil { s.logger.Error("failed to increment workouts in repository", zap.Uint("user_id", userID), zap.Error(err), ) return err } s.logger.Info("workout stats incremented successfully", zap.Uint("user_id", userID), zap.Float64("distance", distance), zap.Int("duration", duration), ) return nil } // ResetWeeklyDistance сбрасывает недельный пробег func (s *userStatsService) ResetWeeklyDistance(userID uint) error { s.logger.Info("resetting weekly distance", zap.Uint("user_id", userID), ) userStats, err := s.userStatsRepo.GetByUserID(userID) if err != nil { s.logger.Error("failed to get user stats for weekly reset", zap.Uint("user_id", userID), zap.Error(err), ) return err } userStats.WeeklyDistance = 0 if err := s.userStatsRepo.Update(userStats); err != nil { s.logger.Error("failed to reset weekly distance in repository", zap.Uint("user_id", userID), zap.Error(err), ) return err } s.logger.Info("weekly distance reset successfully", zap.Uint("user_id", userID), ) return nil } // ResetMonthlyDistance сбрасывает месячный пробег func (s *userStatsService) ResetMonthlyDistance(userID uint) error { s.logger.Info("resetting monthly distance", zap.Uint("user_id", userID), ) userStats, err := s.userStatsRepo.GetByUserID(userID) if err != nil { s.logger.Error("failed to get user stats for monthly reset", zap.Uint("user_id", userID), zap.Error(err), ) return err } userStats.MonthlyDistance = 0 if err := s.userStatsRepo.Update(userStats); err != nil { s.logger.Error("failed to reset monthly distance in repository", zap.Uint("user_id", userID), zap.Error(err), ) return err } s.logger.Info("monthly distance reset successfully", zap.Uint("user_id", userID), ) return nil } // CreateUserStats создает начальную статистику для пользователя func (s *userStatsService) CreateUserStats(userID uint) error { s.logger.Info("creating user stats", zap.Uint("user_id", userID), ) userStats := &models.UserStats{ UserID: userID, TotalDistance: 0, TotalTime: 0, AvgPace: "0:00", WorkoutsCount: 0, CurrentStreak: 0, LongestStreak: 0, WeeklyDistance: 0, MonthlyDistance: 0, Best5K: "", Best10K: "", BestHalf: "", BestMarathon: "", LastWorkout: time.Time{}, } if err := s.userStatsRepo.Create(userStats); err != nil { s.logger.Error("failed to create user stats in repository", zap.Uint("user_id", userID), zap.Error(err), ) return err } s.logger.Info("user stats created successfully", zap.Uint("user_id", userID), ) return nil }