create and moove into new directories for BegushiyBashkir and
yalarbacreate and moove into new directories for BegushiyBashkir and yalarbacreate and moove into new directories for BegushiyBashkir and yalarbacreate and moove into new directories for BegushiyBashkir and yalarbacreate and moove into new directories for BegushiyBashkir and yalarbacreate and moove into new directories for BegushiyBashkir and yalarbacreate and moove into new directories for BegushiyBashkir and yalarbacreate and moove into new directories for BegushiyBashkir and yalarbacreate and moove into new directories for BegushiyBashkir and yalarba
This commit is contained in:
@@ -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"`
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
// models/email.go
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type EmailVerification struct {
|
||||
ID uint `json:"id" gorm:"primaryKey"`
|
||||
UserID uint `json:"user_id" gorm:"not null;index"`
|
||||
Token string `json:"token" gorm:"size:100;not null;uniqueIndex"`
|
||||
Email string `json:"email" gorm:"not null"`
|
||||
Type string `json:"type" gorm:"size:20;not null"` // verification, password_reset
|
||||
ExpiresAt time.Time `json:"expires_at" gorm:"not null"`
|
||||
Used bool `json:"used" gorm:"default:false"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
DeletedAt gorm.DeletedAt `json:"-" gorm:"index"`
|
||||
|
||||
// Связи
|
||||
User User `gorm:"foreignKey:UserID" json:"user,omitempty"`
|
||||
}
|
||||
|
||||
type PasswordResetRequest struct {
|
||||
Email string `json:"email" validate:"required,email"`
|
||||
}
|
||||
|
||||
type PasswordResetConfirm struct {
|
||||
Token string `json:"token" validate:"required"`
|
||||
Password string `json:"password" validate:"required,min=6"`
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
// models/event.go
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type EventType string
|
||||
|
||||
const (
|
||||
EventTypeRace EventType = "race"
|
||||
EventTypeTraining EventType = "training"
|
||||
EventTypeSocial EventType = "social"
|
||||
EventTypeWorkshop EventType = "workshop"
|
||||
)
|
||||
|
||||
type Event struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
Title string `gorm:"size:255;not null" json:"title" validate:"required,min=5,max=255"`
|
||||
Description string `gorm:"type:text;not null" json:"description" validate:"required,min=10"`
|
||||
Date time.Time `gorm:"not null" json:"date" validate:"required"`
|
||||
Location string `gorm:"size:255;not null" json:"location" validate:"required,max=255"`
|
||||
Type EventType `gorm:"size:50;not null" json:"type" validate:"required,oneof=race training social workshop"`
|
||||
Distance string `gorm:"size:50" json:"distance" validate:"max=50"`
|
||||
ParticipantsCount int `gorm:"default:0" json:"participants_count"`
|
||||
MaxParticipants int `gorm:"default:0" json:"max_participants" validate:"min=0"`
|
||||
RegistrationOpen bool `gorm:"default:true" json:"registration_open"`
|
||||
Image string `gorm:"size:500" json:"image" validate:"max=500"`
|
||||
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
|
||||
|
||||
// Связи
|
||||
Registrations []EventRegistration `gorm:"foreignKey:EventID" json:"registrations,omitempty"`
|
||||
}
|
||||
|
||||
type EventRegistration struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
UserID uint `gorm:"not null" json:"user_id"`
|
||||
EventID uint `gorm:"not null" json:"event_id"`
|
||||
Status string `gorm:"size:50;default:pending" json:"status" validate:"oneof=pending confirmed cancelled completed"`
|
||||
Notes string `gorm:"type:text" json:"notes" validate:"max=500"`
|
||||
ResultTime string `gorm:"size:20" json:"result_time" validate:"max=20"`
|
||||
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
|
||||
|
||||
// Связи
|
||||
User User `gorm:"foreignKey:UserID" json:"user,omitempty"`
|
||||
Event *Event `gorm:"foreignKey:EventID" json:"event,omitempty"`
|
||||
}
|
||||
@@ -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"`
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type NewsCategory string
|
||||
|
||||
const (
|
||||
NewsCategoryEvents NewsCategory = "events"
|
||||
NewsCategoryTraining NewsCategory = "training"
|
||||
NewsCategoryAchievements NewsCategory = "achievements"
|
||||
NewsCategoryCommunity NewsCategory = "community"
|
||||
)
|
||||
|
||||
type News struct {
|
||||
ID uint `json:"id" gorm:"primarykey"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
DeletedAt gorm.DeletedAt `json:"deleted_at,omitempty" gorm:"index"`
|
||||
|
||||
Title string `json:"title" gorm:"size:255;not null"`
|
||||
Excerpt string `json:"excerpt" gorm:"size:500;not null"`
|
||||
Content string `json:"content" gorm:"type:text;not null"`
|
||||
Image string `json:"image" gorm:"size:255"`
|
||||
Category NewsCategory `json:"category" gorm:"type:varchar(20);not null"`
|
||||
Views int `json:"views" gorm:"default:0"`
|
||||
|
||||
// Связи
|
||||
AuthorID uint `json:"author_id" gorm:"not null"`
|
||||
Author User `json:"author" gorm:"foreignKey:AuthorID"`
|
||||
|
||||
Comments []Comment `json:"comments,omitempty" gorm:"foreignKey:NewsID"`
|
||||
}
|
||||
|
||||
type Comment struct {
|
||||
ID uint `json:"id" gorm:"primarykey"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
|
||||
Content string `json:"content" gorm:"type:text;not null"`
|
||||
|
||||
// Связи
|
||||
NewsID uint `json:"news_id" gorm:"not null"`
|
||||
AuthorID uint `json:"author_id" gorm:"not null"`
|
||||
Author User `json:"author" gorm:"foreignKey:AuthorID"`
|
||||
}
|
||||
|
||||
// DTO для создания новости
|
||||
type CreateNewsRequest struct {
|
||||
Title string `json:"title" validate:"required,min=5,max=255"`
|
||||
Excerpt string `json:"excerpt" validate:"required,min=10,max=500"`
|
||||
Content string `json:"content" validate:"required,min=50"`
|
||||
Image string `json:"image"`
|
||||
Category NewsCategory `json:"category" validate:"required,oneof=events training achievements community"`
|
||||
}
|
||||
|
||||
// DTO для обновления новости
|
||||
type UpdateNewsRequest struct {
|
||||
Title string `json:"title" validate:"omitempty,min=5,max=255"`
|
||||
Excerpt string `json:"excerpt" validate:"omitempty,min=10,max=500"`
|
||||
Content string `json:"content" validate:"omitempty,min=50"`
|
||||
Image string `json:"image"`
|
||||
Category NewsCategory `json:"category" validate:"omitempty,oneof=events training achievements community"`
|
||||
}
|
||||
|
||||
// DTO для ответа с новостью
|
||||
type NewsResponse struct {
|
||||
ID uint `json:"id"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
Title string `json:"title"`
|
||||
Excerpt string `json:"excerpt"`
|
||||
Content string `json:"content"`
|
||||
Image string `json:"image"`
|
||||
Category NewsCategory `json:"category"`
|
||||
Views int `json:"views"`
|
||||
Author AuthorInfo `json:"author"`
|
||||
Comments int `json:"comments_count"`
|
||||
}
|
||||
|
||||
// DTO для комментария
|
||||
type CreateCommentRequest struct {
|
||||
Content string `json:"content" validate:"required,min=1,max=1000"`
|
||||
}
|
||||
|
||||
type CommentResponse struct {
|
||||
ID uint `json:"id"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
Content string `json:"content"`
|
||||
Author AuthorInfo `json:"author"`
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
// 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"`
|
||||
}
|
||||
|
||||
// PersonalBestsSummary представляет сводку лучших результатов по дистанциям
|
||||
type PersonalBestsSummary struct {
|
||||
Best5K string `json:"best_5k,omitempty"`
|
||||
Best5KPace string `json:"best_5k_pace,omitempty"`
|
||||
Best10K string `json:"best_10k,omitempty"`
|
||||
Best10KPace string `json:"best_10k_pace,omitempty"`
|
||||
BestHalf string `json:"best_half_marathon,omitempty"`
|
||||
BestHalfPace string `json:"best_half_marathon_pace,omitempty"`
|
||||
BestMarathon string `json:"best_marathon,omitempty"`
|
||||
BestMarathonPace string `json:"best_marathon_pace,omitempty"`
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
// models/review.go
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type Review struct {
|
||||
ID uint `json:"id" gorm:"primarykey"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
DeletedAt gorm.DeletedAt `json:"deleted_at,omitempty" gorm:"index"`
|
||||
|
||||
Rating int `json:"rating" gorm:"not null;check:rating >= 1 AND rating <= 5"`
|
||||
Text string `json:"text" gorm:"type:text;not null"`
|
||||
Achievement string `json:"achievement" gorm:"size:255"`
|
||||
Distance string `json:"distance" gorm:"size:50"`
|
||||
Improvement string `json:"improvement" gorm:"size:100"`
|
||||
Trainings int `json:"trainings" gorm:"default:0"`
|
||||
Verified bool `json:"verified" gorm:"default:false"`
|
||||
|
||||
// Связи
|
||||
AuthorID uint `json:"author_id" gorm:"not null"`
|
||||
Author User `json:"author" gorm:"foreignKey:AuthorID"`
|
||||
}
|
||||
|
||||
// DTO для создания отзыва
|
||||
type CreateReviewRequest struct {
|
||||
Rating int `json:"rating" validate:"required,min=1,max=5"`
|
||||
Text string `json:"text" validate:"required,min=10,max=500"`
|
||||
Achievement string `json:"achievement" validate:"max=255"`
|
||||
Distance string `json:"distance" validate:"max=50"`
|
||||
Improvement string `json:"improvement" validate:"max=100"`
|
||||
Trainings int `json:"trainings" validate:"min=0"`
|
||||
}
|
||||
|
||||
// DTO для обновления отзыва
|
||||
type UpdateReviewRequest struct {
|
||||
Rating int `json:"rating" validate:"omitempty,min=1,max=5"`
|
||||
Text string `json:"text" validate:"omitempty,min=10,max=500"`
|
||||
Achievement string `json:"achievement" validate:"omitempty,max=255"`
|
||||
Distance string `json:"distance" validate:"omitempty,max=50"`
|
||||
Improvement string `json:"improvement" validate:"omitempty,max=100"`
|
||||
Trainings int `json:"trainings" validate:"omitempty,min=0"`
|
||||
}
|
||||
|
||||
// DTO для ответа с отзывом
|
||||
type ReviewResponse struct {
|
||||
ID uint `json:"id"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
Rating int `json:"rating"`
|
||||
Text string `json:"text"`
|
||||
Achievement string `json:"achievement,omitempty"`
|
||||
Distance string `json:"distance,omitempty"`
|
||||
Improvement string `json:"improvement,omitempty"`
|
||||
Trainings int `json:"trainings"`
|
||||
Verified bool `json:"verified"`
|
||||
Author AuthorInfo `json:"author"`
|
||||
}
|
||||
|
||||
// DTO для статистики отзывов
|
||||
type ReviewsStatsResponse struct {
|
||||
TotalReviews int `json:"total_reviews"`
|
||||
AverageRating float64 `json:"average_rating"`
|
||||
SuccessStories int `json:"success_stories"`
|
||||
RatingDistribution map[int]int `json:"rating_distribution"`
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
// 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"`
|
||||
}
|
||||
|
||||
// DTO для обновления плана тренировок
|
||||
type TrainingPlanUpdateRequest struct {
|
||||
Title string `json:"title" validate:"min=5,max=255"`
|
||||
Description string `json:"description" validate:"max=1000"`
|
||||
Weeks int `json:"weeks" validate:"min=1,max=52"`
|
||||
WorkoutsPerWeek int `json:"workouts_per_week" validate:"min=1,max=7"`
|
||||
TargetDistance string `json:"target_distance" validate:"max=50"`
|
||||
TargetDate time.Time `json:"target_date"`
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
// models/user.go
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// 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"`
|
||||
EmailVerified bool `json:"email_verified" gorm:"default:false"`
|
||||
VerifiedAt time.Time `json:"verified_at"`
|
||||
|
||||
// Связи
|
||||
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"`
|
||||
}
|
||||
|
||||
// HashPassword хеширует пароль перед сохранением
|
||||
func (u *User) HashPassword() error {
|
||||
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(u.Password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
u.Password = string(hashedPassword)
|
||||
return nil
|
||||
}
|
||||
|
||||
// CheckPassword проверяет пароль
|
||||
func (u *User) CheckPassword(password string) bool {
|
||||
err := bcrypt.CompareHashAndPassword([]byte(u.Password), []byte(password))
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// BeforeCreate hook для GORM
|
||||
func (u *User) BeforeCreate(tx *gorm.DB) error {
|
||||
if u.CreatedAt.IsZero() {
|
||||
u.CreatedAt = time.Now()
|
||||
}
|
||||
if u.UpdatedAt.IsZero() {
|
||||
u.UpdatedAt = time.Now()
|
||||
}
|
||||
return u.HashPassword()
|
||||
}
|
||||
|
||||
// BeforeUpdate hook для GORM
|
||||
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,61 @@
|
||||
// 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"`
|
||||
}
|
||||
@@ -0,0 +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 `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:"maxlen=20"`
|
||||
Calories int `json:"calories" validate:"minint=0,maxint=5000"`
|
||||
Notes string `json:"notes" validate:"maxlen=1000"`
|
||||
Date time.Time `json:"date" validate:"required"`
|
||||
}
|
||||
|
||||
// DTO для обновления тренировки
|
||||
type WorkoutUpdateRequest struct {
|
||||
Type WorkoutType `json:"type" validate:"oneof=easy tempo interval long recovery"`
|
||||
Distance float64 `json:"distance_km" validate:"min=0.1,max=1000"`
|
||||
Duration int `json:"duration_min" validate:"min=1,max=1440"`
|
||||
Pace string `json:"pace" validate:"maxlen=20"`
|
||||
Calories int `json:"calories" validate:"minint=0,maxint=5000"`
|
||||
Notes string `json:"notes" validate:"maxlen=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"`
|
||||
}
|
||||
Reference in New Issue
Block a user