new file: serv_nginx/api_bb/internal/repository/achievement_repository.go
modified: serv_nginx/api_bb/internal/repository/personal_best_repository.go new file: serv_nginx/api_bb/internal/service/achievement_service.go new file: serv_nginx/api_bb/internal/service/personal_best_service.go new file: serv_nginx/api_bb/internal/service/user_stats_service.go add some services
This commit is contained in:
@@ -0,0 +1,244 @@
|
|||||||
|
// repositories/achievement_repository.go
|
||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"api_bb/internal/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AchievementRepository interface {
|
||||||
|
Create(achievement *models.Achievement) error
|
||||||
|
GetByID(id uint) (*models.Achievement, error)
|
||||||
|
GetByUserID(userID uint) ([]models.Achievement, error)
|
||||||
|
GetByUserAndType(userID uint, achievementType models.AchievementType) ([]models.Achievement, error)
|
||||||
|
GetVerifiedByUserID(userID uint) ([]models.Achievement, error)
|
||||||
|
GetByDateRange(userID uint, startDate, endDate time.Time) ([]models.Achievement, error)
|
||||||
|
Update(achievement *models.Achievement) error
|
||||||
|
Delete(id uint) error
|
||||||
|
VerifyAchievement(id uint) error
|
||||||
|
GetUserAchievementsSummary(userID uint) (*models.UserAchievementsResponse, error)
|
||||||
|
GetRecentAchievements(userID uint, limit int) ([]models.Achievement, error)
|
||||||
|
CountByType(userID uint) (map[models.AchievementType]int64, error)
|
||||||
|
ExistsByTitleAndUser(userID uint, title string) (bool, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type achievementRepository struct {
|
||||||
|
db *gorm.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAchievementRepository(db *gorm.DB) AchievementRepository {
|
||||||
|
return &achievementRepository{db: db}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create создает новое достижение
|
||||||
|
func (r *achievementRepository) Create(achievement *models.Achievement) error {
|
||||||
|
return r.db.Create(achievement).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetByID возвращает достижение по ID
|
||||||
|
func (r *achievementRepository) GetByID(id uint) (*models.Achievement, error) {
|
||||||
|
var achievement models.Achievement
|
||||||
|
err := r.db.Preload("User").First(&achievement, id).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &achievement, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetByUserID возвращает все достижения пользователя
|
||||||
|
func (r *achievementRepository) GetByUserID(userID uint) ([]models.Achievement, error) {
|
||||||
|
var achievements []models.Achievement
|
||||||
|
err := r.db.Where("user_id = ?", userID).
|
||||||
|
Order("date DESC, created_at DESC").
|
||||||
|
Find(&achievements).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return achievements, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetByUserAndType возвращает достижения пользователя по типу
|
||||||
|
func (r *achievementRepository) GetByUserAndType(userID uint, achievementType models.AchievementType) ([]models.Achievement, error) {
|
||||||
|
var achievements []models.Achievement
|
||||||
|
err := r.db.Where("user_id = ? AND type = ?", userID, achievementType).
|
||||||
|
Order("date DESC").
|
||||||
|
Find(&achievements).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return achievements, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetVerifiedByUserID возвращает подтвержденные достижения пользователя
|
||||||
|
func (r *achievementRepository) GetVerifiedByUserID(userID uint) ([]models.Achievement, error) {
|
||||||
|
var achievements []models.Achievement
|
||||||
|
err := r.db.Where("user_id = ? AND verified = ?", userID, true).
|
||||||
|
Order("date DESC").
|
||||||
|
Find(&achievements).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return achievements, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetByDateRange возвращает достижения за период времени
|
||||||
|
func (r *achievementRepository) GetByDateRange(userID uint, startDate, endDate time.Time) ([]models.Achievement, error) {
|
||||||
|
var achievements []models.Achievement
|
||||||
|
err := r.db.Where("user_id = ? AND date BETWEEN ? AND ?", userID, startDate, endDate).
|
||||||
|
Order("date DESC").
|
||||||
|
Find(&achievements).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return achievements, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update обновляет достижение
|
||||||
|
func (r *achievementRepository) Update(achievement *models.Achievement) error {
|
||||||
|
return r.db.Save(achievement).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete удаляет достижение
|
||||||
|
func (r *achievementRepository) Delete(id uint) error {
|
||||||
|
return r.db.Delete(&models.Achievement{}, id).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
// VerifyAchievement подтверждает достижение
|
||||||
|
func (r *achievementRepository) VerifyAchievement(id uint) error {
|
||||||
|
return r.db.Model(&models.Achievement{}).
|
||||||
|
Where("id = ?", id).
|
||||||
|
Update("verified", true).
|
||||||
|
Error
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserAchievementsSummary возвращает сводку по достижениям пользователя
|
||||||
|
func (r *achievementRepository) GetUserAchievementsSummary(userID uint) (*models.UserAchievementsResponse, error) {
|
||||||
|
var totalCount int64
|
||||||
|
var verifiedCount int64
|
||||||
|
|
||||||
|
// Считаем общее количество достижений
|
||||||
|
err := r.db.Model(&models.Achievement{}).
|
||||||
|
Where("user_id = ?", userID).
|
||||||
|
Count(&totalCount).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Считаем количество подтвержденных достижений
|
||||||
|
err = r.db.Model(&models.Achievement{}).
|
||||||
|
Where("user_id = ? AND verified = ?", userID, true).
|
||||||
|
Count(&verifiedCount).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Получаем все достижения пользователя
|
||||||
|
achievements, err := r.GetByUserID(userID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Вычисляем процент прогресса
|
||||||
|
progressPercent := 0.0
|
||||||
|
if totalCount > 0 {
|
||||||
|
progressPercent = (float64(verifiedCount) / float64(totalCount)) * 100
|
||||||
|
}
|
||||||
|
|
||||||
|
return &models.UserAchievementsResponse{
|
||||||
|
TotalAchievements: int(totalCount),
|
||||||
|
Completed: int(verifiedCount),
|
||||||
|
ProgressPercent: progressPercent,
|
||||||
|
Achievements: achievements,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRecentAchievements возвращает последние достижения пользователя
|
||||||
|
func (r *achievementRepository) GetRecentAchievements(userID uint, limit int) ([]models.Achievement, error) {
|
||||||
|
var achievements []models.Achievement
|
||||||
|
err := r.db.Where("user_id = ?", userID).
|
||||||
|
Order("created_at DESC").
|
||||||
|
Limit(limit).
|
||||||
|
Find(&achievements).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return achievements, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CountByType возвращает количество достижений по типам
|
||||||
|
func (r *achievementRepository) CountByType(userID uint) (map[models.AchievementType]int64, error) {
|
||||||
|
type CountResult struct {
|
||||||
|
Type models.AchievementType
|
||||||
|
Count int64
|
||||||
|
}
|
||||||
|
|
||||||
|
var results []CountResult
|
||||||
|
err := r.db.Model(&models.Achievement{}).
|
||||||
|
Select("type, COUNT(*) as count").
|
||||||
|
Where("user_id = ?", userID).
|
||||||
|
Group("type").
|
||||||
|
Scan(&results).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
counts := make(map[models.AchievementType]int64)
|
||||||
|
for _, result := range results {
|
||||||
|
counts[result.Type] = result.Count
|
||||||
|
}
|
||||||
|
|
||||||
|
return counts, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExistsByTitleAndUser проверяет, существует ли достижение с таким названием у пользователя
|
||||||
|
func (r *achievementRepository) ExistsByTitleAndUser(userID uint, title string) (bool, error) {
|
||||||
|
var count int64
|
||||||
|
err := r.db.Model(&models.Achievement{}).
|
||||||
|
Where("user_id = ? AND title = ?", userID, title).
|
||||||
|
Count(&count).Error
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return count > 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUnverifiedAchievements возвращает неподтвержденные достижения
|
||||||
|
func (r *achievementRepository) GetUnverifiedAchievements(userID uint) ([]models.Achievement, error) {
|
||||||
|
var achievements []models.Achievement
|
||||||
|
err := r.db.Where("user_id = ? AND verified = ?", userID, false).
|
||||||
|
Order("created_at DESC").
|
||||||
|
Find(&achievements).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return achievements, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAchievementsWithPagination возвращает достижения с пагинацией
|
||||||
|
func (r *achievementRepository) GetAchievementsWithPagination(userID uint, page, pageSize int) ([]models.Achievement, int64, error) {
|
||||||
|
var achievements []models.Achievement
|
||||||
|
var totalCount int64
|
||||||
|
|
||||||
|
// Считаем общее количество
|
||||||
|
err := r.db.Model(&models.Achievement{}).
|
||||||
|
Where("user_id = ?", userID).
|
||||||
|
Count(&totalCount).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Получаем данные с пагинацией
|
||||||
|
offset := (page - 1) * pageSize
|
||||||
|
err = r.db.Where("user_id = ?", userID).
|
||||||
|
Order("date DESC, created_at DESC").
|
||||||
|
Offset(offset).
|
||||||
|
Limit(pageSize).
|
||||||
|
Find(&achievements).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return achievements, totalCount, nil
|
||||||
|
}
|
||||||
@@ -21,6 +21,7 @@ type PersonalBestRepository interface {
|
|||||||
GetByDateRange(userID uint, startDate, endDate time.Time) ([]models.PersonalBest, error)
|
GetByDateRange(userID uint, startDate, endDate time.Time) ([]models.PersonalBest, error)
|
||||||
GetPersonalBestsSummary(userID uint) (*models.PersonalBestsSummary, error)
|
GetPersonalBestsSummary(userID uint) (*models.PersonalBestsSummary, error)
|
||||||
ExistsBetterTime(userID uint, distanceType models.DistanceType, time string) (bool, error)
|
ExistsBetterTime(userID uint, distanceType models.DistanceType, time string) (bool, error)
|
||||||
|
CalculatePace(timeStr string, distanceType models.DistanceType) (string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type personalBestRepository struct {
|
type personalBestRepository struct {
|
||||||
|
|||||||
@@ -0,0 +1,104 @@
|
|||||||
|
// service/achievement_service.go
|
||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"api_bb/internal/models"
|
||||||
|
"api_bb/internal/repository"
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AchievementService struct {
|
||||||
|
achievementRepo repository.AchievementRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAchievementService(achievementRepo repository.AchievementRepository) *AchievementService {
|
||||||
|
return &AchievementService{
|
||||||
|
achievementRepo: achievementRepo,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateAchievement создает новое достижение
|
||||||
|
func (s *AchievementService) CreateAchievement(userID uint, req models.AchievementCreateRequest) (*models.Achievement, error) {
|
||||||
|
// Проверяем, нет ли уже достижения с таким названием у пользователя
|
||||||
|
exists, err := s.achievementRepo.ExistsByTitleAndUser(userID, req.Title)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if exists {
|
||||||
|
return nil, ErrAchievementAlreadyExists
|
||||||
|
}
|
||||||
|
|
||||||
|
achievement := &models.Achievement{
|
||||||
|
UserID: userID,
|
||||||
|
Type: req.Type,
|
||||||
|
Title: req.Title,
|
||||||
|
Description: req.Description,
|
||||||
|
Result: req.Result,
|
||||||
|
Distance: req.Distance,
|
||||||
|
Date: req.Date,
|
||||||
|
BadgeImage: req.BadgeImage,
|
||||||
|
Verified: false, // По умолчанию не подтверждено
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.achievementRepo.Create(achievement); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return achievement, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserAchievements возвращает все достижения пользователя
|
||||||
|
func (s *AchievementService) GetUserAchievements(userID uint) ([]models.Achievement, error) {
|
||||||
|
return s.achievementRepo.GetByUserID(userID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserAchievementsSummary возвращает сводку по достижениям пользователя
|
||||||
|
func (s *AchievementService) GetUserAchievementsSummary(userID uint) (*models.UserAchievementsResponse, error) {
|
||||||
|
return s.achievementRepo.GetUserAchievementsSummary(userID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// VerifyAchievement подтверждает достижение
|
||||||
|
func (s *AchievementService) VerifyAchievement(achievementID uint, userID uint) error {
|
||||||
|
// Проверяем, что достижение принадлежит пользователю
|
||||||
|
achievement, err := s.achievementRepo.GetByID(achievementID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if achievement.UserID != userID {
|
||||||
|
return ErrAchievementNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.achievementRepo.VerifyAchievement(achievementID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRecentAchievements возвращает последние достижения
|
||||||
|
func (s *AchievementService) GetRecentAchievements(userID uint, limit int) ([]models.Achievement, error) {
|
||||||
|
return s.achievementRepo.GetRecentAchievements(userID, limit)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAchievementsByType возвращает достижения по типу
|
||||||
|
func (s *AchievementService) GetAchievementsByType(userID uint, achievementType models.AchievementType) ([]models.Achievement, error) {
|
||||||
|
return s.achievementRepo.GetByUserAndType(userID, achievementType)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteAchievement удаляет достижение
|
||||||
|
func (s *AchievementService) DeleteAchievement(achievementID uint, userID uint) error {
|
||||||
|
// Проверяем, что достижение принадлежит пользователю
|
||||||
|
achievement, err := s.achievementRepo.GetByID(achievementID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if achievement.UserID != userID {
|
||||||
|
return ErrAchievementNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.achievementRepo.Delete(achievementID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ошибки
|
||||||
|
var (
|
||||||
|
ErrAchievementAlreadyExists = errors.New("achievement with this title already exists")
|
||||||
|
ErrAchievementNotFound = errors.New("achievement not found")
|
||||||
|
)
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
// services/personal_best_service.go
|
||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"api_bb/internal/models"
|
||||||
|
"api_bb/internal/repository"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PersonalBestService struct {
|
||||||
|
pbRepo repository.PersonalBestRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPersonalBestService(pbRepo repository.PersonalBestRepository) *PersonalBestService {
|
||||||
|
return &PersonalBestService{
|
||||||
|
pbRepo: pbRepo,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreatePersonalBest создает новый личный рекорд
|
||||||
|
func (s *PersonalBestService) CreatePersonalBest(userID uint, req models.PersonalBestCreateRequest) (*models.PersonalBest, error) {
|
||||||
|
// Вычисляем темп, если не предоставлен
|
||||||
|
pace := req.Pace
|
||||||
|
if pace == "" {
|
||||||
|
calculatedPace, err := s.pbRepo.CalculatePace(req.Time, req.DistanceType)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
pace = calculatedPace
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверяем, является ли это личным рекордом
|
||||||
|
isBest, err := s.pbRepo.ExistsBetterTime(userID, req.DistanceType, req.Time)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
personalBest := &models.PersonalBest{
|
||||||
|
UserID: userID,
|
||||||
|
DistanceType: req.DistanceType,
|
||||||
|
Time: req.Time,
|
||||||
|
Pace: pace,
|
||||||
|
Date: req.Date,
|
||||||
|
EventName: req.EventName,
|
||||||
|
Location: req.Location,
|
||||||
|
Verified: false, // По умолчанию не подтвержден
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.pbRepo.Create(personalBest); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Если это лучший результат, можно обновить статистику пользователя
|
||||||
|
if !isBest {
|
||||||
|
// Здесь можно вызвать метод для обновления UserStats
|
||||||
|
}
|
||||||
|
|
||||||
|
return personalBest, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserPersonalBests возвращает все личные рекорды пользователя
|
||||||
|
func (s *PersonalBestService) GetUserPersonalBests(userID uint) ([]models.PersonalBest, error) {
|
||||||
|
return s.pbRepo.GetByUserID(userID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPersonalBestsSummary возвращает сводку лучших результатов
|
||||||
|
func (s *PersonalBestService) GetPersonalBestsSummary(userID uint) (*models.PersonalBestsSummary, error) {
|
||||||
|
return s.pbRepo.GetPersonalBestsSummary(userID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// VerifyPersonalBest подтверждает личный рекорд
|
||||||
|
func (s *PersonalBestService) VerifyPersonalBest(id uint, userID uint) error {
|
||||||
|
pb, err := s.pbRepo.GetByID(id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверяем, что рекорд принадлежит пользователю
|
||||||
|
if pb.UserID != userID {
|
||||||
|
return gorm.ErrRecordNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
pb.Verified = true
|
||||||
|
return s.pbRepo.Update(pb)
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
// services/user_stats_service.go
|
||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
"api_bb/internal/models"
|
||||||
|
"api_bb/internal/repository"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UserStatsService struct {
|
||||||
|
userStatsRepo repository.UserStatsRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUserStatsService(userStatsRepo repository.UserStatsRepository) *UserStatsService {
|
||||||
|
return &UserStatsService{
|
||||||
|
userStatsRepo: userStatsRepo,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *UserStatsService) AddWorkout(userID uint, distance float64, duration int, workoutTime time.Time) error {
|
||||||
|
// Обновляем общую статистику
|
||||||
|
if err := s.userStatsRepo.IncrementWorkouts(userID, distance, duration); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Обновляем серии
|
||||||
|
if err := s.userStatsRepo.UpdateStreaks(userID, workoutTime); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Обновляем недельный и месячный пробег
|
||||||
|
if err := s.userStatsRepo.UpdateWeeklyDistance(userID, distance); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.userStatsRepo.UpdateMonthlyDistance(userID, distance)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *UserStatsService) GetUserStats(userID uint) (*models.UserStatsResponse, error) {
|
||||||
|
return s.userStatsRepo.GetUserStatsResponse(userID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *UserStatsService) UpdatePersonalBest(userID uint, distanceType string, time string) error {
|
||||||
|
return s.userStatsRepo.UpdatePersonalBest(userID, distanceType, time)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user