modified: README.md

modified:   serv_nginx/api_bb/go.mod
	modified:   serv_nginx/api_bb/go.sum
	new file:   serv_nginx/api_bb/internal/models/achievement.go
	new file:   serv_nginx/api_bb/internal/models/common.go
	new file:   serv_nginx/api_bb/internal/models/event.go
	new file:   serv_nginx/api_bb/internal/models/gallery.go
	modified:   serv_nginx/api_bb/internal/models/news.go
	new file:   serv_nginx/api_bb/internal/models/personal_best.go
	new file:   serv_nginx/api_bb/internal/models/training_plan.go
	modified:   serv_nginx/api_bb/internal/models/user.go
	new file:   serv_nginx/api_bb/internal/models/user_stats.go
	modified:   serv_nginx/api_bb/internal/models/workout.go
	modified:   serv_nginx/bbvue/src/components/NavigationMenu.vue
	new file:   serv_nginx/bbvue/src/components/writeLogo.vue
add satructs for begushiybashkir.ru site
This commit is contained in:
2025-10-17 05:09:53 +05:00
parent 280d9a0eb3
commit b19ce8fdfe
15 changed files with 741 additions and 50 deletions
@@ -0,0 +1,72 @@
// models/achievement.go
package models
import (
"time"
"gorm.io/gorm"
)
type AchievementType string
const (
AchievementTypeDistance AchievementType = "distance"
AchievementTypeSpeed AchievementType = "speed"
AchievementTypeConsistency AchievementType = "consistency"
AchievementTypeEvent AchievementType = "event"
AchievementTypeSpecial AchievementType = "special"
)
type Achievement struct {
ID uint `json:"id" gorm:"primaryKey"`
UserID uint `json:"user_id" gorm:"not null;index"`
Type AchievementType `json:"type" gorm:"type:varchar(20);not null"`
Title string `json:"title" gorm:"size:255;not null"`
Description string `json:"description" gorm:"type:text"`
Result string `json:"result" gorm:"size:100"` // Достигнутый результат
Distance string `json:"distance" gorm:"size:50"` // Дистанция достижения
Date time.Time `json:"date" gorm:"not null"`
Verified bool `json:"verified" gorm:"default:false"`
BadgeImage string `json:"badge_image" gorm:"size:500"` // Изображение бейджа
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
// Связи
User User `json:"user,omitempty" gorm:"foreignKey:UserID"`
}
// BeforeCreate hook
func (a *Achievement) BeforeCreate(tx *gorm.DB) error {
if a.CreatedAt.IsZero() {
a.CreatedAt = time.Now()
}
if a.UpdatedAt.IsZero() {
a.UpdatedAt = time.Now()
}
return nil
}
// BeforeUpdate hook
func (a *Achievement) BeforeUpdate(tx *gorm.DB) error {
a.UpdatedAt = time.Now()
return nil
}
// DTO для создания достижения
type AchievementCreateRequest struct {
Type AchievementType `json:"type" validate:"required,oneof=distance speed consistency event special"`
Title string `json:"title" validate:"required,min=5,max=255"`
Description string `json:"description" validate:"max=1000"`
Result string `json:"result" validate:"max=100"`
Distance string `json:"distance" validate:"max=50"`
Date time.Time `json:"date" validate:"required"`
BadgeImage string `json:"badge_image" validate:"max=500"`
}
// DTO для ответа с достижениями пользователя
type UserAchievementsResponse struct {
TotalAchievements int `json:"total_achievements"`
Completed int `json:"completed"`
ProgressPercent float64 `json:"progress_percent"`
Achievements []Achievement `json:"achievements"`
}
@@ -0,0 +1,38 @@
// models/common.go
package models
import "time"
// Общая структура для информации об авторе
type AuthorInfo struct {
ID uint `json:"id"`
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
Avatar string `json:"avatar,omitempty"`
Email string `json:"email,omitempty"` // Добавляем email
}
// DTO для пагинации
type PaginationRequest struct {
Page int `form:"page" validate:"min=1" default:"1"`
PerPage int `form:"per_page" validate:"min=1,max=100" default:"10"`
}
type PaginationResponse struct {
Page int `json:"page"`
PerPage int `json:"per_page"`
Total int `json:"total"`
TotalPages int `json:"total_pages"`
}
// DTO для фильтров
type DateRangeFilter struct {
StartDate *time.Time `form:"start_date"`
EndDate *time.Time `form:"end_date"`
}
type WorkoutFilter struct {
DateRangeFilter
Type string `form:"type"`
UserID uint `form:"user_id"`
}
+101
View File
@@ -0,0 +1,101 @@
// models/event.go
package models
import (
"time"
"gorm.io/gorm"
)
type EventType string
const (
EventTypeRace EventType = "race"
EventTypeTraining EventType = "training"
EventTypeSocial EventType = "social"
EventTypeWorkshop EventType = "workshop"
)
type Event struct {
ID uint `json:"id" gorm:"primaryKey"`
Title string `json:"title" gorm:"size:255;not null"`
Description string `json:"description" gorm:"type:text;not null"`
Date time.Time `json:"date" gorm:"not null;index"`
Location string `json:"location" gorm:"size:255;not null"`
Type EventType `json:"type" gorm:"type:varchar(20);not null"`
Distance string `json:"distance" gorm:"size:50"` // Дистанция забега
ParticipantsCount int `json:"participants_count" gorm:"default:0"` // Количество участников
MaxParticipants int `json:"max_participants" gorm:"default:0"` // Максимальное количество участников
RegistrationOpen bool `json:"registration_open" gorm:"default:true"` // Открыта ли регистрация
Image string `json:"image" gorm:"size:500"` // Изображение события
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
// Связи
Registrations []EventRegistration `json:"registrations,omitempty" gorm:"foreignKey:EventID"`
}
type EventRegistration struct {
ID uint `json:"id" gorm:"primaryKey"`
EventID uint `json:"event_id" gorm:"not null;index"`
UserID uint `json:"user_id" gorm:"not null;index"`
Status string `json:"status" gorm:"size:20;default:pending"` // pending, confirmed, cancelled, completed
ResultTime *string `json:"result_time" gorm:"size:20"` // Результат забега
BibNumber *string `json:"bib_number" gorm:"size:10"` // Стартовый номер
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
// Связи
Event Event `json:"event,omitempty" gorm:"foreignKey:EventID"`
User User `json:"user,omitempty" gorm:"foreignKey:UserID"`
}
// BeforeCreate hooks
func (e *Event) BeforeCreate(tx *gorm.DB) error {
if e.CreatedAt.IsZero() {
e.CreatedAt = time.Now()
}
if e.UpdatedAt.IsZero() {
e.UpdatedAt = time.Now()
}
return nil
}
func (er *EventRegistration) BeforeCreate(tx *gorm.DB) error {
if er.CreatedAt.IsZero() {
er.CreatedAt = time.Now()
}
if er.UpdatedAt.IsZero() {
er.UpdatedAt = time.Now()
}
return nil
}
// BeforeUpdate hooks
func (e *Event) BeforeUpdate(tx *gorm.DB) error {
e.UpdatedAt = time.Now()
return nil
}
func (er *EventRegistration) BeforeUpdate(tx *gorm.DB) error {
er.UpdatedAt = time.Now()
return nil
}
// DTO для создания события
type EventCreateRequest struct {
Title string `json:"title" validate:"required,min=5,max=255"`
Description string `json:"description" validate:"required,min=10"`
Date time.Time `json:"date" validate:"required"`
Location string `json:"location" validate:"required,max=255"`
Type EventType `json:"type" validate:"required,oneof=race training social workshop"`
Distance string `json:"distance" validate:"max=50"`
MaxParticipants int `json:"max_participants" validate:"min=0"`
RegistrationOpen bool `json:"registration_open"`
Image string `json:"image" validate:"max=500"`
}
// DTO для регистрации на событие
type EventRegistrationRequest struct {
EventID uint `json:"event_id" validate:"required"`
}
@@ -0,0 +1,74 @@
// models/gallery.go
package models
import (
"time"
"gorm.io/gorm"
)
type GalleryCategory string
const (
GalleryCategoryTraining GalleryCategory = "training"
GalleryCategoryEvents GalleryCategory = "events"
GalleryCategoryCommunity GalleryCategory = "community"
GalleryCategoryAchievements GalleryCategory = "achievements"
)
type Gallery struct {
ID uint `json:"id" gorm:"primaryKey"`
Title string `json:"title" gorm:"size:255;not null"`
Description string `json:"description" gorm:"type:text"`
ImagePath string `json:"image_path" gorm:"size:500;not null"` // Путь к изображению
Category GalleryCategory `json:"category" gorm:"type:varchar(20);not null"`
AuthorID uint `json:"author_id" gorm:"not null;index"`
EventDate *time.Time `json:"event_date"` // Дата события на фото
Views int `json:"views" gorm:"default:0"`
Likes int `json:"likes" gorm:"default:0"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
// Связи
Author User `json:"author,omitempty" gorm:"foreignKey:AuthorID"`
}
// BeforeCreate hook
func (g *Gallery) BeforeCreate(tx *gorm.DB) error {
if g.CreatedAt.IsZero() {
g.CreatedAt = time.Now()
}
if g.UpdatedAt.IsZero() {
g.UpdatedAt = time.Now()
}
return nil
}
// BeforeUpdate hook
func (g *Gallery) BeforeUpdate(tx *gorm.DB) error {
g.UpdatedAt = time.Now()
return nil
}
// DTO для создания записи в галерее
type GalleryCreateRequest struct {
Title string `json:"title" validate:"required,min=5,max=255"`
Description string `json:"description" validate:"max=1000"`
ImagePath string `json:"image_path" validate:"required,max=500"`
Category GalleryCategory `json:"category" validate:"required,oneof=training events community achievements"`
EventDate *time.Time `json:"event_date"`
}
// DTO для ответа с галереей
type GalleryResponse struct {
ID uint `json:"id"`
Title string `json:"title"`
Description string `json:"description"`
ImagePath string `json:"image_path"`
Category GalleryCategory `json:"category"`
EventDate *time.Time `json:"event_date"`
Views int `json:"views"`
Likes int `json:"likes"`
CreatedAt time.Time `json:"created_at"`
Author AuthorInfo `json:"author"`
}
@@ -80,13 +80,6 @@ type NewsResponse struct {
Comments int `json:"comments_count"`
}
type AuthorInfo struct {
ID uint `json:"id"`
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
Email string `json:"email,omitempty"`
}
// DTO для комментария
type CreateCommentRequest struct {
Content string `json:"content" validate:"required,min=1,max=1000"`
@@ -0,0 +1,73 @@
// models/personal_best.go
package models
import (
"time"
"gorm.io/gorm"
)
type DistanceType string
const (
Distance5K DistanceType = "5k"
Distance10K DistanceType = "10k"
DistanceHalf DistanceType = "half_marathon"
DistanceFull DistanceType = "marathon"
DistanceOther DistanceType = "other"
)
type PersonalBest struct {
ID uint `json:"id" gorm:"primaryKey"`
UserID uint `json:"user_id" gorm:"not null;index"`
DistanceType DistanceType `json:"distance_type" gorm:"type:varchar(20);not null"`
Time string `json:"time" gorm:"size:20;not null"` // Время в формате "HH:MM:SS"
Pace string `json:"pace" gorm:"size:20"` // Темп
Date time.Time `json:"date" gorm:"not null"`
Verified bool `json:"verified" gorm:"default:false"` // Подтвержден ли результат
EventName string `json:"event_name" gorm:"size:255"` // Название забега
Location string `json:"location" gorm:"size:255"` // Место проведения
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
// Связи
User User `json:"user,omitempty" gorm:"foreignKey:UserID"`
}
// BeforeCreate hook
func (pb *PersonalBest) BeforeCreate(tx *gorm.DB) error {
if pb.CreatedAt.IsZero() {
pb.CreatedAt = time.Now()
}
if pb.UpdatedAt.IsZero() {
pb.UpdatedAt = time.Now()
}
return nil
}
// BeforeUpdate hook
func (pb *PersonalBest) BeforeUpdate(tx *gorm.DB) error {
pb.UpdatedAt = time.Now()
return nil
}
// DTO для создания личного рекорда
type PersonalBestCreateRequest struct {
DistanceType DistanceType `json:"distance_type" validate:"required,oneof=5k 10k half_marathon marathon other"`
Time string `json:"time" validate:"required,max=20"`
Pace string `json:"pace" validate:"max=20"`
Date time.Time `json:"date" validate:"required"`
EventName string `json:"event_name" validate:"max=255"`
Location string `json:"location" validate:"max=255"`
}
// DTO для обновления личного рекорда
type PersonalBestUpdateRequest struct {
DistanceType DistanceType `json:"distance_type" validate:"omitempty,oneof=5k 10k half_marathon marathon other"`
Time string `json:"time" validate:"omitempty,max=20"`
Pace string `json:"pace" validate:"omitempty,max=20"`
Date time.Time `json:"date"`
EventName string `json:"event_name" validate:"omitempty,max=255"`
Location string `json:"location" validate:"omitempty,max=255"`
Verified bool `json:"verified"`
}
@@ -0,0 +1,75 @@
// models/training_plan.go
package models
import (
"time"
"gorm.io/gorm"
)
type TrainingPlan struct {
ID uint `json:"id" gorm:"primaryKey"`
UserID uint `json:"user_id" gorm:"not null;index"`
Title string `json:"title" gorm:"size:255;not null"`
Description string `json:"description" gorm:"type:text"`
Weeks int `json:"weeks" gorm:"not null;default:12"` // Длительность плана в неделях
WorkoutsPerWeek int `json:"workouts_per_week" gorm:"not null;default:3"` // Тренировок в неделю
TargetDistance string `json:"target_distance" gorm:"size:50"` // Целевая дистанция
TargetDate time.Time `json:"target_date"` // Дата цели
CurrentWeek int `json:"current_week" gorm:"default:1"` // Текущая неделя
Completed bool `json:"completed" gorm:"default:false"` // Завершен ли план
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
// Связи
User User `json:"user,omitempty" gorm:"foreignKey:UserID"`
Workouts []TrainingWorkout `json:"workouts,omitempty" gorm:"foreignKey:PlanID"`
}
type TrainingWorkout struct {
ID uint `json:"id" gorm:"primaryKey"`
PlanID uint `json:"plan_id" gorm:"not null;index"`
Week int `json:"week" gorm:"not null"` // Неделя плана
Day int `json:"day" gorm:"not null"` // День недели (1-7)
Type WorkoutType `json:"type" gorm:"type:varchar(20);not null"`
Description string `json:"description" gorm:"type:text"`
Distance float64 `json:"distance_km" gorm:"type:decimal(5,2)"`
Duration int `json:"duration_min"`
Completed bool `json:"completed" gorm:"default:false"`
CompletedAt *time.Time `json:"completed_at"`
CreatedAt time.Time `json:"created_at"`
}
// BeforeCreate hooks
func (tp *TrainingPlan) BeforeCreate(tx *gorm.DB) error {
if tp.CreatedAt.IsZero() {
tp.CreatedAt = time.Now()
}
if tp.UpdatedAt.IsZero() {
tp.UpdatedAt = time.Now()
}
return nil
}
func (tw *TrainingWorkout) BeforeCreate(tx *gorm.DB) error {
if tw.CreatedAt.IsZero() {
tw.CreatedAt = time.Now()
}
return nil
}
// BeforeUpdate hook
func (tp *TrainingPlan) BeforeUpdate(tx *gorm.DB) error {
tp.UpdatedAt = time.Now()
return nil
}
// DTO для создания плана тренировок
type TrainingPlanCreateRequest struct {
Title string `json:"title" validate:"required,min=5,max=255"`
Description string `json:"description" validate:"max=1000"`
Weeks int `json:"weeks" validate:"required,min=1,max=52"`
WorkoutsPerWeek int `json:"workouts_per_week" validate:"required,min=1,max=7"`
TargetDistance string `json:"target_distance" validate:"max=50"`
TargetDate time.Time `json:"target_date"`
}
+65 -23
View File
@@ -10,32 +10,43 @@ import (
// models/user.go - добавить поле Avatar
type User struct {
ID uint `json:"id" gorm:"primaryKey"`
Email string `json:"email" gorm:"uniqueIndex;not null"`
Password string `json:"-" gorm:"not null"`
FirstName string `json:"first_name" gorm:"not null"`
LastName string `json:"last_name" gorm:"not null"`
Avatar string `json:"avatar"` // Путь к файлу аватара
Phone string `json:"phone"`
Experience string `json:"experience"`
Goals string `json:"goals"`
Newsletter bool `json:"newsletter"`
Role string `json:"role" gorm:"default:user"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `json:"-" gorm:"index"`
ID uint `json:"id" gorm:"primaryKey"`
Email string `json:"email" gorm:"uniqueIndex;not null"`
Password string `json:"-" gorm:"not null"`
FirstName string `json:"first_name" gorm:"not null"`
LastName string `json:"last_name" gorm:"not null"`
Avatar string `json:"avatar"` // Путь к файлу аватара
Phone string `json:"phone"`
Experience string `json:"experience"`
Goals string `json:"goals"`
Newsletter bool `json:"newsletter"`
Role string `json:"role" gorm:"default:user"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `json:"-" gorm:"index"`
// Связи
Workouts []Workout `json:"workouts,omitempty" gorm:"foreignKey:UserID"`
PersonalBests []PersonalBest `json:"personal_bests,omitempty" gorm:"foreignKey:UserID"`
Achievements []Achievement `json:"achievements,omitempty" gorm:"foreignKey:UserID"`
TrainingPlans []TrainingPlan `json:"training_plans,omitempty" gorm:"foreignKey:UserID"`
News []News `json:"news,omitempty" gorm:"foreignKey:AuthorID"`
Comments []Comment `json:"comments,omitempty" gorm:"foreignKey:AuthorID"`
Reviews []Review `json:"reviews,omitempty" gorm:"foreignKey:AuthorID"`
Gallery []Gallery `json:"gallery,omitempty" gorm:"foreignKey:AuthorID"`
EventRegistrations []EventRegistration `json:"event_registrations,omitempty" gorm:"foreignKey:UserID"`
}
type UserUpdate struct {
ID uint `json:"id"`
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
Avatar string `json:"avatar"`
Phone string `json:"phone"`
Experience string `json:"experience"`
Goals string `json:"goals"`
Newsletter bool `json:"newsletter"`
UpdatedAt time.Time `json:"updated_at"`
ID uint `json:"id"`
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
Avatar string `json:"avatar"`
Phone string `json:"phone"`
Experience string `json:"experience"`
Goals string `json:"goals"`
Newsletter bool `json:"newsletter"`
UpdatedAt time.Time `json:"updated_at"`
}
// HashPassword хеширует пароль перед сохранением
@@ -70,3 +81,34 @@ func (u *User) BeforeUpdate(tx *gorm.DB) error {
u.UpdatedAt = time.Now()
return nil
}
// DTO для обновления профиля
type UserUpdateRequest struct {
FirstName string `json:"first_name" validate:"required,min=2,max=100"`
LastName string `json:"last_name" validate:"required,min=2,max=100"`
Phone string `json:"phone" validate:"max=20"`
Experience string `json:"experience" validate:"max=50"`
Goals string `json:"goals" validate:"max=100"`
Newsletter bool `json:"newsletter"`
}
// DTO для ответа с пользователем (без sensitive данных)
type UserResponse struct {
ID uint `json:"id"`
Email string `json:"email"`
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
Avatar string `json:"avatar"`
Phone string `json:"phone"`
Experience string `json:"experience"`
Goals string `json:"goals"`
Newsletter bool `json:"newsletter"`
Role string `json:"role"`
CreatedAt time.Time `json:"created_at"`
}
// DTO для ответа с пользователем и статистикой
type UserWithStatsResponse struct {
UserResponse
Stats *UserStatsResponse `json:"stats,omitempty"`
}
@@ -0,0 +1,68 @@
// models/user_stats.go
package models
import (
"time"
"gorm.io/gorm"
)
type UserStats struct {
ID uint `json:"id" gorm:"primaryKey"`
UserID uint `json:"user_id" gorm:"uniqueIndex;not null"`
TotalDistance float64 `json:"total_distance" gorm:"type:decimal(10,2);default:0"` // Общий пробег в км
TotalTime int `json:"total_time" gorm:"default:0"` // Общее время в минутах
AvgPace string `json:"avg_pace" gorm:"size:20"` // Средний темп
WorkoutsCount int `json:"workouts_count" gorm:"default:0"` // Количество тренировок
CurrentStreak int `json:"current_streak" gorm:"default:0"` // Текущая серия дней подряд
LongestStreak int `json:"longest_streak" gorm:"default:0"` // Самая длинная серия
WeeklyDistance float64 `json:"weekly_distance" gorm:"type:decimal(8,2);default:0"` // Пробег за неделю
MonthlyDistance float64 `json:"monthly_distance" gorm:"type:decimal(8,2);default:0"` // Пробег за месяц
Best5K string `json:"best_5k" gorm:"size:20"` // Лучший результат на 5к
Best10K string `json:"best_10k" gorm:"size:20"` // Лучший результат на 10к
BestHalf string `json:"best_half" gorm:"size:20"` // Лучший результат на полумарафон
BestMarathon string `json:"best_marathon" gorm:"size:20"` // Лучший результат на марафон
LastWorkout time.Time `json:"last_workout"` // Последняя тренировка
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
// Связи
User User `json:"user,omitempty" gorm:"foreignKey:UserID"`
}
// BeforeCreate hook
func (us *UserStats) BeforeCreate(tx *gorm.DB) error {
if us.CreatedAt.IsZero() {
us.CreatedAt = time.Now()
}
if us.UpdatedAt.IsZero() {
us.UpdatedAt = time.Now()
}
return nil
}
// BeforeUpdate hook
func (us *UserStats) BeforeUpdate(tx *gorm.DB) error {
us.UpdatedAt = time.Now()
return nil
}
// DTO для статистики пользователя
type UserStatsResponse struct {
TotalDistance float64 `json:"total_distance"`
TotalTime int `json:"total_time"`
AvgPace string `json:"avg_pace"`
WorkoutsCount int `json:"workouts_count"`
CurrentStreak int `json:"current_streak"`
LongestStreak int `json:"longest_streak"`
WeeklyDistance float64 `json:"weekly_distance"`
MonthlyDistance float64 `json:"monthly_distance"`
PersonalBests PersonalBestsSummary `json:"personal_bests"`
}
type PersonalBestsSummary struct {
Best5K string `json:"best_5k"`
Best10K string `json:"best_10k"`
BestHalf string `json:"best_half"`
BestMarathon string `json:"best_marathon"`
}
+85 -1
View File
@@ -1,5 +1,89 @@
// models/workout.go
package models
import (
"time"
"gorm.io/gorm"
)
type WorkoutType string
const (
WorkoutTypeEasy WorkoutType = "easy"
WorkoutTypeTempo WorkoutType = "tempo"
WorkoutTypeInterval WorkoutType = "interval"
WorkoutTypeLong WorkoutType = "long"
WorkoutTypeRecovery WorkoutType = "recovery"
)
type Workout struct {
ID uint
ID uint `json:"id" gorm:"primaryKey"`
UserID uint `json:"user_id" gorm:"not null;index"`
Type WorkoutType `json:"type" gorm:"type:varchar(20);not null"`
Distance float64 `json:"distance_km" gorm:"type:decimal(5,2);not null"` // Дистанция в км
Duration int `json:"duration_min" gorm:"not null"` // Продолжительность в минутах
Pace string `json:"pace" gorm:"size:20"` // Темп (например, "5:30")
Calories int `json:"calories" gorm:"default:0"` // Сожженные калории
Notes string `json:"notes" gorm:"type:text"` // Заметки к тренировке
Date time.Time `json:"date" gorm:"not null;index"` // Дата тренировки
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
// Связи
User User `json:"user,omitempty" gorm:"foreignKey:UserID"`
}
// BeforeCreate hook
func (w *Workout) BeforeCreate(tx *gorm.DB) error {
if w.CreatedAt.IsZero() {
w.CreatedAt = time.Now()
}
if w.UpdatedAt.IsZero() {
w.UpdatedAt = time.Now()
}
return nil
}
// BeforeUpdate hook
func (w *Workout) BeforeUpdate(tx *gorm.DB) error {
w.UpdatedAt = time.Now()
return nil
}
// DTO для создания тренировки
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"`
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"`
Date time.Time `json:"date"`
}
// DTO для статистики тренировок
type WorkoutStatsResponse struct {
TotalWorkouts int `json:"total_workouts"`
TotalDistance float64 `json:"total_distance_km"`
TotalTime int `json:"total_time_min"`
AveragePace string `json:"average_pace"`
MonthlyStats []MonthlyStat `json:"monthly_stats"`
}
type MonthlyStat struct {
Month string `json:"month"`
Distance float64 `json:"distance_km"`
Workouts int `json:"workouts"`
}