From b19ce8fdfe5b197abfc693dd20871cdaf0c71e43 Mon Sep 17 00:00:00 2001 From: valitovgaziz Date: Fri, 17 Oct 2025 05:09:53 +0500 Subject: [PATCH] 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 --- README.md | 2 +- serv_nginx/api_bb/go.mod | 5 + serv_nginx/api_bb/go.sum | 12 +++ .../api_bb/internal/models/achievement.go | 72 +++++++++++++ serv_nginx/api_bb/internal/models/common.go | 38 +++++++ serv_nginx/api_bb/internal/models/event.go | 101 ++++++++++++++++++ serv_nginx/api_bb/internal/models/gallery.go | 74 +++++++++++++ serv_nginx/api_bb/internal/models/news.go | 7 -- .../api_bb/internal/models/personal_best.go | 73 +++++++++++++ .../api_bb/internal/models/training_plan.go | 75 +++++++++++++ serv_nginx/api_bb/internal/models/user.go | 88 +++++++++++---- .../api_bb/internal/models/user_stats.go | 68 ++++++++++++ serv_nginx/api_bb/internal/models/workout.go | 86 ++++++++++++++- .../bbvue/src/components/NavigationMenu.vue | 33 +++--- serv_nginx/bbvue/src/components/writeLogo.vue | 57 ++++++++++ 15 files changed, 741 insertions(+), 50 deletions(-) create mode 100644 serv_nginx/api_bb/internal/models/achievement.go create mode 100644 serv_nginx/api_bb/internal/models/common.go create mode 100644 serv_nginx/api_bb/internal/models/event.go create mode 100644 serv_nginx/api_bb/internal/models/gallery.go create mode 100644 serv_nginx/api_bb/internal/models/personal_best.go create mode 100644 serv_nginx/api_bb/internal/models/training_plan.go create mode 100644 serv_nginx/api_bb/internal/models/user_stats.go create mode 100644 serv_nginx/bbvue/src/components/writeLogo.vue diff --git a/README.md b/README.md index 029c680..7c8c69e 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ ## for yalarba.ru && begushiybashkir.ru -### tasks: profile page need full upgreade +### tasks: profile page need full upgreade, main sender (почтовая рассылка) ### BackEnd REST API on Golang 1.25.1 ### FrontEnd vue3.js ### Zagir Загир тренер diff --git a/serv_nginx/api_bb/go.mod b/serv_nginx/api_bb/go.mod index 3b72df5..a6368a6 100644 --- a/serv_nginx/api_bb/go.mod +++ b/serv_nginx/api_bb/go.mod @@ -12,13 +12,17 @@ require ( ) require ( + filippo.io/edwards25519 v1.1.0 // indirect github.com/gabriel-vasile/mimetype v1.4.10 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-sql-driver/mysql v1.8.1 // indirect + github.com/google/uuid v1.6.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/stretchr/testify v1.11.1 // indirect go.uber.org/multierr v1.10.0 // indirect golang.org/x/sys v0.37.0 // indirect + gorm.io/driver/mysql v1.5.6 // indirect ) require ( @@ -33,4 +37,5 @@ require ( go.uber.org/zap v1.27.0 golang.org/x/sync v0.17.0 // indirect golang.org/x/text v0.30.0 // indirect + gorm.io/datatypes v1.2.7 ) diff --git a/serv_nginx/api_bb/go.sum b/serv_nginx/api_bb/go.sum index 04c9bb8..af937b3 100644 --- a/serv_nginx/api_bb/go.sum +++ b/serv_nginx/api_bb/go.sum @@ -1,3 +1,5 @@ +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -15,8 +17,13 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.28.0 h1:Q7ibns33JjyW48gHkuFT91qX48KG0ktULL6FgHdG688= github.com/go-playground/validator/v10 v10.28.0/go.mod h1:GoI6I1SjPBh9p7ykNE/yj3fFYbyDOpwMn5KXd+m2hUU= +github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= +github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= @@ -58,7 +65,12 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/datatypes v1.2.7 h1:ww9GAhF1aGXZY3EB3cJPJ7//JiuQo7DlQA7NNlVaTdk= +gorm.io/datatypes v1.2.7/go.mod h1:M2iO+6S3hhi4nAyYe444Pcb0dcIiOMJ7QHaUXxyiNZY= +gorm.io/driver/mysql v1.5.6 h1:Ld4mkIickM+EliaQZQx3uOJDJHtrd70MxAUqWqlx3Y8= +gorm.io/driver/mysql v1.5.6/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM= gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4= gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo= +gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= gorm.io/gorm v1.31.0 h1:0VlycGreVhK7RF/Bwt51Fk8v0xLiiiFdbGDPIZQ7mJY= gorm.io/gorm v1.31.0/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs= diff --git a/serv_nginx/api_bb/internal/models/achievement.go b/serv_nginx/api_bb/internal/models/achievement.go new file mode 100644 index 0000000..5a71971 --- /dev/null +++ b/serv_nginx/api_bb/internal/models/achievement.go @@ -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"` +} \ No newline at end of file diff --git a/serv_nginx/api_bb/internal/models/common.go b/serv_nginx/api_bb/internal/models/common.go new file mode 100644 index 0000000..f82d2ee --- /dev/null +++ b/serv_nginx/api_bb/internal/models/common.go @@ -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"` +} \ No newline at end of file diff --git a/serv_nginx/api_bb/internal/models/event.go b/serv_nginx/api_bb/internal/models/event.go new file mode 100644 index 0000000..1288bef --- /dev/null +++ b/serv_nginx/api_bb/internal/models/event.go @@ -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"` +} \ No newline at end of file diff --git a/serv_nginx/api_bb/internal/models/gallery.go b/serv_nginx/api_bb/internal/models/gallery.go new file mode 100644 index 0000000..6c013fb --- /dev/null +++ b/serv_nginx/api_bb/internal/models/gallery.go @@ -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"` +} \ No newline at end of file diff --git a/serv_nginx/api_bb/internal/models/news.go b/serv_nginx/api_bb/internal/models/news.go index 14f0113..f00be65 100644 --- a/serv_nginx/api_bb/internal/models/news.go +++ b/serv_nginx/api_bb/internal/models/news.go @@ -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"` diff --git a/serv_nginx/api_bb/internal/models/personal_best.go b/serv_nginx/api_bb/internal/models/personal_best.go new file mode 100644 index 0000000..0952c84 --- /dev/null +++ b/serv_nginx/api_bb/internal/models/personal_best.go @@ -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"` +} \ No newline at end of file diff --git a/serv_nginx/api_bb/internal/models/training_plan.go b/serv_nginx/api_bb/internal/models/training_plan.go new file mode 100644 index 0000000..551a9b8 --- /dev/null +++ b/serv_nginx/api_bb/internal/models/training_plan.go @@ -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"` +} \ No newline at end of file diff --git a/serv_nginx/api_bb/internal/models/user.go b/serv_nginx/api_bb/internal/models/user.go index efcd3ff..75fc88b 100644 --- a/serv_nginx/api_bb/internal/models/user.go +++ b/serv_nginx/api_bb/internal/models/user.go @@ -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"` +} diff --git a/serv_nginx/api_bb/internal/models/user_stats.go b/serv_nginx/api_bb/internal/models/user_stats.go new file mode 100644 index 0000000..b17da52 --- /dev/null +++ b/serv_nginx/api_bb/internal/models/user_stats.go @@ -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"` +} \ No newline at end of file diff --git a/serv_nginx/api_bb/internal/models/workout.go b/serv_nginx/api_bb/internal/models/workout.go index 023cfac..3efecbf 100644 --- a/serv_nginx/api_bb/internal/models/workout.go +++ b/serv_nginx/api_bb/internal/models/workout.go @@ -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"` } \ No newline at end of file diff --git a/serv_nginx/bbvue/src/components/NavigationMenu.vue b/serv_nginx/bbvue/src/components/NavigationMenu.vue index 477120c..9d387ff 100644 --- a/serv_nginx/bbvue/src/components/NavigationMenu.vue +++ b/serv_nginx/bbvue/src/components/NavigationMenu.vue @@ -2,13 +2,8 @@
-
- +