c55310e2e0
new file: serv_nginx/api_bb/internal/handlers/user_stats_handler.go modified: serv_nginx/api_bb/internal/routes/routes.go modified: serv_nginx/api_bb/internal/service/user_stats_service.go add EndPoints for user stats
258 lines
7.0 KiB
Go
258 lines
7.0 KiB
Go
// 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
|
|
} |