modified: serv_nginx/api_bb/internal/repository/user_stats_repository.go
new file: serv_nginx/api_bb/internal/scripts/migrate_existing_users.go modified: serv_nginx/api_bb/internal/service/auth_service.go modified: serv_nginx/api_bb/internal/service/user_stats_service.go modified: serv_nginx/bbvue/src/views/Achievements.vue modified: serv_nginx/bbvue/src/views/Reviews.vue modified: serv_nginx/bbvue/src/views/Training.vue fix bag with no stats into table
This commit is contained in:
@@ -4,9 +4,9 @@ package repository
|
|||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gorm.io/gorm"
|
|
||||||
"api_bb/internal/models"
|
"api_bb/internal/models"
|
||||||
"api_bb/pkg/utils"
|
"api_bb/pkg/utils"
|
||||||
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
type UserStatsRepository interface {
|
type UserStatsRepository interface {
|
||||||
@@ -21,6 +21,7 @@ type UserStatsRepository interface {
|
|||||||
IncrementWorkouts(userID uint, distance float64, duration int) error
|
IncrementWorkouts(userID uint, distance float64, duration int) error
|
||||||
UpdatePersonalBest(userID uint, distanceType string, time string) error
|
UpdatePersonalBest(userID uint, distanceType string, time string) error
|
||||||
GetUserStatsResponse(userID uint) (*models.UserStatsResponse, error)
|
GetUserStatsResponse(userID uint) (*models.UserStatsResponse, error)
|
||||||
|
GetByUserIDOrCreate(userID uint) (*models.UserStats, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type userStatsRepository struct {
|
type userStatsRepository struct {
|
||||||
@@ -31,6 +32,39 @@ func NewUserStatsRepository(db *gorm.DB) UserStatsRepository {
|
|||||||
return &userStatsRepository{db: db}
|
return &userStatsRepository{db: db}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetByUserIDOrCreate возвращает статистику по ID пользователя или создает новую
|
||||||
|
func (r *userStatsRepository) GetByUserIDOrCreate(userID uint) (*models.UserStats, error) {
|
||||||
|
userStats, err := r.GetByUserID(userID)
|
||||||
|
if err != nil {
|
||||||
|
if err == gorm.ErrRecordNotFound {
|
||||||
|
// Создаем новую статистику
|
||||||
|
newStats := &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 := r.Create(newStats); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return newStats, nil
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return userStats, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Create создает новую статистику пользователя
|
// Create создает новую статистику пользователя
|
||||||
func (r *userStatsRepository) Create(userStats *models.UserStats) error {
|
func (r *userStatsRepository) Create(userStats *models.UserStats) error {
|
||||||
return r.db.Create(userStats).Error
|
return r.db.Create(userStats).Error
|
||||||
@@ -156,7 +190,7 @@ func (r *userStatsRepository) UpdatePersonalBest(userID uint, distanceType strin
|
|||||||
|
|
||||||
// GetUserStatsResponse возвращает статистику в формате DTO
|
// GetUserStatsResponse возвращает статистику в формате DTO
|
||||||
func (r *userStatsRepository) GetUserStatsResponse(userID uint) (*models.UserStatsResponse, error) {
|
func (r *userStatsRepository) GetUserStatsResponse(userID uint) (*models.UserStatsResponse, error) {
|
||||||
userStats, err := r.GetByUserID(userID)
|
userStats, err := r.GetByUserIDOrCreate(userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -178,4 +212,3 @@ func (r *userStatsRepository) GetUserStatsResponse(userID uint) (*models.UserSta
|
|||||||
},
|
},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
// scripts/migrate_existing_users.go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"api_bb/internal/models"
|
||||||
|
"api_bb/internal/repository"
|
||||||
|
"api_bb/pkg/logger"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
func MigrateExistingUsers(db *gorm.DB) error {
|
||||||
|
log := logger.NewWrapper(logger.Get().With(zap.String("script", "migrate_existing_users")))
|
||||||
|
|
||||||
|
userRepo := repository.NewUserRepository(db)
|
||||||
|
userStatsRepo := repository.NewUserStatsRepository(db)
|
||||||
|
|
||||||
|
// Получаем всех пользователей
|
||||||
|
users, err := userRepo.FindAll()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("starting migration for existing users",
|
||||||
|
zap.Int("total_users", len(users)))
|
||||||
|
|
||||||
|
successCount := 0
|
||||||
|
for _, user := range users {
|
||||||
|
// Проверяем, есть ли уже статистика
|
||||||
|
_, err := userStatsRepo.GetByUserID(user.ID)
|
||||||
|
if err == gorm.ErrRecordNotFound {
|
||||||
|
// Создаем статистику
|
||||||
|
userStats := &models.UserStats{
|
||||||
|
UserID: user.ID,
|
||||||
|
TotalDistance: 0,
|
||||||
|
TotalTime: 0,
|
||||||
|
AvgPace: "0:00",
|
||||||
|
WorkoutsCount: 0,
|
||||||
|
CurrentStreak: 0,
|
||||||
|
LongestStreak: 0,
|
||||||
|
WeeklyDistance: 0,
|
||||||
|
MonthlyDistance: 0,
|
||||||
|
Best5K: "",
|
||||||
|
Best10K: "",
|
||||||
|
BestHalf: "",
|
||||||
|
BestMarathon: "",
|
||||||
|
LastWorkout: user.CreatedAt, // Используем дату создания как последнюю тренировку
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := userStatsRepo.Create(userStats); err != nil {
|
||||||
|
log.Error("failed to create stats for user",
|
||||||
|
zap.Uint("user_id", user.ID),
|
||||||
|
zap.Error(err))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
successCount++
|
||||||
|
log.Info("created stats for user",
|
||||||
|
zap.Uint("user_id", user.ID),
|
||||||
|
zap.String("email", user.Email))
|
||||||
|
} else if err != nil {
|
||||||
|
log.Error("error checking stats for user",
|
||||||
|
zap.Uint("user_id", user.ID),
|
||||||
|
zap.Error(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("migration completed",
|
||||||
|
zap.Int("successful_creations", successCount),
|
||||||
|
zap.Int("total_users", len(users)))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -55,12 +55,14 @@ func (s *authService) Register(user *models.User) error {
|
|||||||
)
|
)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
s.logger.Info("User registered successfully",
|
s.logger.Info("User registered successfully",
|
||||||
zap.Uint("user_id", user.ID),
|
zap.Uint("user_id", user.ID),
|
||||||
zap.String("email", user.Email),
|
zap.String("email", user.Email),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ type UserStatsService interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type userStatsService struct {
|
type userStatsService struct {
|
||||||
logger logger.LoggerInterface
|
logger logger.LoggerInterface
|
||||||
userStatsRepo repository.UserStatsRepository
|
userStatsRepo repository.UserStatsRepository
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewUserStatsService(userStatsRepo repository.UserStatsRepository) UserStatsService {
|
func NewUserStatsService(userStatsRepo repository.UserStatsRepository) UserStatsService {
|
||||||
@@ -64,15 +64,14 @@ func (s *userStatsService) UpdatePersonalBest(userID uint, distanceType string,
|
|||||||
zap.String("time", time),
|
zap.String("time", time),
|
||||||
)
|
)
|
||||||
|
|
||||||
// Проверяем существование статистики пользователя
|
// Используем GetByUserIDOrCreate вместо проверки существования
|
||||||
_, err := s.userStatsRepo.GetByUserID(userID)
|
_, err := s.userStatsRepo.GetByUserIDOrCreate(userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Warn("user stats not found, creating new stats",
|
s.logger.Error("failed to get or create user stats",
|
||||||
zap.Uint("user_id", userID),
|
zap.Uint("user_id", userID),
|
||||||
|
zap.Error(err),
|
||||||
)
|
)
|
||||||
if err := s.CreateUserStats(userID); err != nil {
|
return err
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.userStatsRepo.UpdatePersonalBest(userID, distanceType, time); err != nil {
|
if err := s.userStatsRepo.UpdatePersonalBest(userID, distanceType, time); err != nil {
|
||||||
@@ -101,15 +100,14 @@ func (s *userStatsService) IncrementWorkout(userID uint, distance float64, durat
|
|||||||
zap.Int("duration", duration),
|
zap.Int("duration", duration),
|
||||||
)
|
)
|
||||||
|
|
||||||
// Проверяем существование статистики пользователя
|
// Используем GetByUserIDOrCreate для гарантии существования статистики
|
||||||
_, err := s.userStatsRepo.GetByUserID(userID)
|
_, err := s.userStatsRepo.GetByUserIDOrCreate(userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Warn("user stats not found, creating new stats",
|
s.logger.Error("failed to get or create user stats",
|
||||||
zap.Uint("user_id", userID),
|
zap.Uint("user_id", userID),
|
||||||
|
zap.Error(err),
|
||||||
)
|
)
|
||||||
if err := s.CreateUserStats(userID); err != nil {
|
return err
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Обновляем серии тренировок
|
// Обновляем серии тренировок
|
||||||
@@ -226,20 +224,20 @@ func (s *userStatsService) CreateUserStats(userID uint) error {
|
|||||||
)
|
)
|
||||||
|
|
||||||
userStats := &models.UserStats{
|
userStats := &models.UserStats{
|
||||||
UserID: userID,
|
UserID: userID,
|
||||||
TotalDistance: 0,
|
TotalDistance: 0,
|
||||||
TotalTime: 0,
|
TotalTime: 0,
|
||||||
AvgPace: "0:00",
|
AvgPace: "0:00",
|
||||||
WorkoutsCount: 0,
|
WorkoutsCount: 0,
|
||||||
CurrentStreak: 0,
|
CurrentStreak: 0,
|
||||||
LongestStreak: 0,
|
LongestStreak: 0,
|
||||||
WeeklyDistance: 0,
|
WeeklyDistance: 0,
|
||||||
MonthlyDistance: 0,
|
MonthlyDistance: 0,
|
||||||
Best5K: "",
|
Best5K: "",
|
||||||
Best10K: "",
|
Best10K: "",
|
||||||
BestHalf: "",
|
BestHalf: "",
|
||||||
BestMarathon: "",
|
BestMarathon: "",
|
||||||
LastWorkout: time.Time{},
|
LastWorkout: time.Time{},
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.userStatsRepo.Create(userStats); err != nil {
|
if err := s.userStatsRepo.Create(userStats); err != nil {
|
||||||
@@ -255,4 +253,4 @@ func (s *userStatsService) CreateUserStats(userID uint) error {
|
|||||||
)
|
)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -713,7 +713,9 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.achievement-details {
|
.achievement-details {
|
||||||
space-y: 0.5rem;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1.5rem; /* Современный способ для Flexbox */
|
||||||
}
|
}
|
||||||
|
|
||||||
.pace {
|
.pace {
|
||||||
|
|||||||
@@ -1163,7 +1163,9 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.review-content {
|
.review-content {
|
||||||
space-y: 1rem;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1.5rem; /* Современный способ для Flexbox */
|
||||||
}
|
}
|
||||||
|
|
||||||
.review-text {
|
.review-text {
|
||||||
|
|||||||
@@ -945,7 +945,9 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.signup-form {
|
.signup-form {
|
||||||
space-y: 1.5rem;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1.5rem; /* Современный способ для Flexbox */
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-group {
|
.form-group {
|
||||||
|
|||||||
Reference in New Issue
Block a user