modified: serv_nginx/api_bb/internal/database/migrate.go
modified: serv_nginx/api_bb/internal/handlers/handlers.go new file: serv_nginx/api_bb/internal/handlers/personal_best_handler.go modified: serv_nginx/api_bb/internal/models/personal_best.go modified: serv_nginx/api_bb/internal/models/user_stats.go modified: serv_nginx/api_bb/internal/repository/personal_best_repository.go modified: serv_nginx/api_bb/internal/routes/routes.go modified: serv_nginx/api_bb/internal/service/personal_best_service.go modified: serv_nginx/bbvue/src/stores/user.js personal bests add handler, rout, service, repository, logic and migrations for
This commit is contained in:
@@ -24,6 +24,7 @@ func (d *Database) Migrate() error {
|
|||||||
&models.Achievement{},
|
&models.Achievement{},
|
||||||
&models.Event{},
|
&models.Event{},
|
||||||
&models.EventRegistration{},
|
&models.EventRegistration{},
|
||||||
|
&models.PersonalBest{},
|
||||||
// Добавьте другие модели здесь
|
// Добавьте другие модели здесь
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,6 +91,8 @@ func getModelName(model interface{}) string {
|
|||||||
return "Событие"
|
return "Событие"
|
||||||
case *models.EventRegistration:
|
case *models.EventRegistration:
|
||||||
return "Администрирование события"
|
return "Администрирование события"
|
||||||
|
case *models.PersonalBest:
|
||||||
|
return "Персональные достижения"
|
||||||
default:
|
default:
|
||||||
return "Unknown"
|
return "Unknown"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ type Handler struct {
|
|||||||
userAchievementHandler *UserAchievementHandler
|
userAchievementHandler *UserAchievementHandler
|
||||||
eventHandler *EventHandler
|
eventHandler *EventHandler
|
||||||
eventRegistrationHandler *EventRegistrationHandler
|
eventRegistrationHandler *EventRegistrationHandler
|
||||||
|
personalBestHandler *PersonalBestHandler
|
||||||
// Здесь будут добавлены другие обработчики
|
// Здесь будут добавлены другие обработчики
|
||||||
// userHandler *UserHandler
|
// userHandler *UserHandler
|
||||||
// eventHandler *EventHandler
|
// eventHandler *EventHandler
|
||||||
@@ -39,6 +40,7 @@ func NewHandler(db *gorm.DB, cfg *config.Config) *Handler {
|
|||||||
userAchievemenRepo := repository.NewAchievementRepository(db)
|
userAchievemenRepo := repository.NewAchievementRepository(db)
|
||||||
eventRepo := repository.NewEventRepository(db)
|
eventRepo := repository.NewEventRepository(db)
|
||||||
eventRegistrationRepo := repository.NewEventRegistrationRepository(db)
|
eventRegistrationRepo := repository.NewEventRegistrationRepository(db)
|
||||||
|
personalBestRepo := repository.NewPersonalBestRepository(db)
|
||||||
|
|
||||||
// Initialize logger
|
// Initialize logger
|
||||||
baseLogger := logger.NewWrapper(logger.Get()) // Создаем базовый логгер
|
baseLogger := logger.NewWrapper(logger.Get()) // Создаем базовый логгер
|
||||||
@@ -55,6 +57,7 @@ func NewHandler(db *gorm.DB, cfg *config.Config) *Handler {
|
|||||||
achievementService := service.NewAchievementService(userAchievemenRepo)
|
achievementService := service.NewAchievementService(userAchievemenRepo)
|
||||||
eventRegistrationService := service.NewEventRegistrationService(eventRegistrationRepo, eventRepo, baseLogger)
|
eventRegistrationService := service.NewEventRegistrationService(eventRegistrationRepo, eventRepo, baseLogger)
|
||||||
eventService := service.NewEventService(eventRepo, eventRegistrationRepo, baseLogger)
|
eventService := service.NewEventService(eventRepo, eventRegistrationRepo, baseLogger)
|
||||||
|
personalBestService := service.NewPersonalBestService(personalBestRepo, userStatsService)
|
||||||
|
|
||||||
// Инициализация обработчиков
|
// Инициализация обработчиков
|
||||||
healthHandler := NewHealthHandler()
|
healthHandler := NewHealthHandler()
|
||||||
@@ -68,6 +71,7 @@ func NewHandler(db *gorm.DB, cfg *config.Config) *Handler {
|
|||||||
userAchievementHandler := NewUserAchievementHandler(*achievementService)
|
userAchievementHandler := NewUserAchievementHandler(*achievementService)
|
||||||
eventHandler := NewEventHandler(eventService)
|
eventHandler := NewEventHandler(eventService)
|
||||||
eventRegistrationHandler := NewEventRegistrationHandler(eventRegistrationService)
|
eventRegistrationHandler := NewEventRegistrationHandler(eventRegistrationService)
|
||||||
|
personalBestHandler := NewPersonalBestHandler(*personalBestService)
|
||||||
|
|
||||||
return &Handler{
|
return &Handler{
|
||||||
healthHandler: healthHandler,
|
healthHandler: healthHandler,
|
||||||
@@ -81,10 +85,15 @@ func NewHandler(db *gorm.DB, cfg *config.Config) *Handler {
|
|||||||
userAchievementHandler: userAchievementHandler,
|
userAchievementHandler: userAchievementHandler,
|
||||||
eventHandler: eventHandler,
|
eventHandler: eventHandler,
|
||||||
eventRegistrationHandler: eventRegistrationHandler,
|
eventRegistrationHandler: eventRegistrationHandler,
|
||||||
|
personalBestHandler: personalBestHandler,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Геттеры для обработчиков (опционально, для удобства)
|
// Геттеры для обработчиков (опционально, для удобства)
|
||||||
|
func (h *Handler) PersonalBestHandler() *PersonalBestHandler {
|
||||||
|
return h.personalBestHandler
|
||||||
|
}
|
||||||
|
|
||||||
func (h *Handler) EventHandler() *EventHandler {
|
func (h *Handler) EventHandler() *EventHandler {
|
||||||
return h.eventHandler
|
return h.eventHandler
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,506 @@
|
|||||||
|
// handlers/personal_best_handler.go
|
||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"api_bb/internal/models"
|
||||||
|
"api_bb/internal/service"
|
||||||
|
"api_bb/pkg/logger"
|
||||||
|
"api_bb/pkg/middleware"
|
||||||
|
"api_bb/pkg/utils"
|
||||||
|
|
||||||
|
"go.uber.org/zap"
|
||||||
|
|
||||||
|
"github.com/go-chi/chi/v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PersonalBestHandler struct {
|
||||||
|
logger logger.LoggerInterface
|
||||||
|
personalBestService service.PersonalBestService
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPersonalBestHandler(personalBestService service.PersonalBestService) *PersonalBestHandler {
|
||||||
|
return &PersonalBestHandler{
|
||||||
|
logger: logger.NewWrapper(logger.Get().With(zap.String("handler", "personal_best"))),
|
||||||
|
personalBestService: personalBestService,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreatePersonalBest создает новый личный рекорд
|
||||||
|
func (h *PersonalBestHandler) CreatePersonalBest(w http.ResponseWriter, r *http.Request) {
|
||||||
|
h.logger.Info("handling create personal best request",
|
||||||
|
zap.String("method", r.Method),
|
||||||
|
zap.String("path", r.URL.Path),
|
||||||
|
zap.String("remote_addr", r.RemoteAddr),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Получаем пользователя из контекста
|
||||||
|
user, ok := middleware.GetUserFromContext(r.Context())
|
||||||
|
if !ok {
|
||||||
|
h.logger.Warn("create personal best failed - authentication required")
|
||||||
|
utils.RespondWithError(w, http.StatusUnauthorized, "Authentication required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var req models.PersonalBestCreateRequest
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||||
|
h.logger.Error("failed to decode JSON payload", zap.Error(err))
|
||||||
|
utils.RespondWithError(w, http.StatusBadRequest, "Invalid request payload: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Валидация
|
||||||
|
if req.DistanceType == "" {
|
||||||
|
h.logger.Warn("create personal best failed - distance type required")
|
||||||
|
utils.RespondWithError(w, http.StatusBadRequest, "Distance type is required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if req.Time == "" {
|
||||||
|
h.logger.Warn("create personal best failed - time required")
|
||||||
|
utils.RespondWithError(w, http.StatusBadRequest, "Time is required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if req.Date.IsZero() {
|
||||||
|
h.logger.Warn("create personal best failed - date required")
|
||||||
|
utils.RespondWithError(w, http.StatusBadRequest, "Date is required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
personalBest, err := h.personalBestService.CreatePersonalBest(user.ID, req)
|
||||||
|
if err != nil {
|
||||||
|
h.logger.Error("failed to create personal best", zap.Error(err))
|
||||||
|
utils.RespondWithError(w, http.StatusInternalServerError, "Failed to create personal best: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h.logger.Info("personal best created successfully",
|
||||||
|
zap.Uint("user_id", user.ID),
|
||||||
|
zap.Uint("personal_best_id", personalBest.ID),
|
||||||
|
zap.String("distance_type", string(personalBest.DistanceType)),
|
||||||
|
)
|
||||||
|
|
||||||
|
utils.RespondWithJSON(w, http.StatusCreated, personalBest)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPersonalBest возвращает личный рекорд по ID
|
||||||
|
func (h *PersonalBestHandler) GetPersonalBest(w http.ResponseWriter, r *http.Request) {
|
||||||
|
h.logger.Info("handling get personal best request",
|
||||||
|
zap.String("method", r.Method),
|
||||||
|
zap.String("path", r.URL.Path),
|
||||||
|
zap.String("remote_addr", r.RemoteAddr),
|
||||||
|
)
|
||||||
|
|
||||||
|
idStr := chi.URLParam(r, "id")
|
||||||
|
id, err := strconv.ParseUint(idStr, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
h.logger.Warn("invalid personal best ID", zap.String("id", idStr))
|
||||||
|
utils.RespondWithError(w, http.StatusBadRequest, "Invalid personal best ID")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
personalBest, err := h.personalBestService.GetPersonalBestByID(uint(id))
|
||||||
|
if err != nil {
|
||||||
|
h.logger.Error("failed to get personal best", zap.Error(err))
|
||||||
|
if err.Error() == "record not found" {
|
||||||
|
utils.RespondWithError(w, http.StatusNotFound, "Personal best not found")
|
||||||
|
} else {
|
||||||
|
utils.RespondWithError(w, http.StatusInternalServerError, "Failed to get personal best: "+err.Error())
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h.logger.Info("personal best retrieved successfully",
|
||||||
|
zap.Uint("personal_best_id", personalBest.ID),
|
||||||
|
)
|
||||||
|
|
||||||
|
utils.RespondWithJSON(w, http.StatusOK, personalBest)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserPersonalBests возвращает все личные рекорды пользователя
|
||||||
|
func (h *PersonalBestHandler) GetUserPersonalBests(w http.ResponseWriter, r *http.Request) {
|
||||||
|
h.logger.Info("handling get user personal bests request",
|
||||||
|
zap.String("method", r.Method),
|
||||||
|
zap.String("path", r.URL.Path),
|
||||||
|
zap.String("remote_addr", r.RemoteAddr),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Получаем пользователя из контекста
|
||||||
|
user, ok := middleware.GetUserFromContext(r.Context())
|
||||||
|
if !ok {
|
||||||
|
h.logger.Warn("get personal bests failed - authentication required")
|
||||||
|
utils.RespondWithError(w, http.StatusUnauthorized, "Authentication required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
personalBests, err := h.personalBestService.GetUserPersonalBests(user.ID)
|
||||||
|
if err != nil {
|
||||||
|
h.logger.Error("failed to get personal bests", zap.Error(err))
|
||||||
|
utils.RespondWithError(w, http.StatusInternalServerError, "Failed to get personal bests: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h.logger.Info("user personal bests retrieved successfully",
|
||||||
|
zap.Uint("user_id", user.ID),
|
||||||
|
zap.Int("count", len(personalBests)),
|
||||||
|
)
|
||||||
|
|
||||||
|
utils.RespondWithJSON(w, http.StatusOK, personalBests)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdatePersonalBest обновляет личный рекорд
|
||||||
|
func (h *PersonalBestHandler) UpdatePersonalBest(w http.ResponseWriter, r *http.Request) {
|
||||||
|
h.logger.Info("handling update personal best request",
|
||||||
|
zap.String("method", r.Method),
|
||||||
|
zap.String("path", r.URL.Path),
|
||||||
|
zap.String("remote_addr", r.RemoteAddr),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Получаем пользователя из контекста
|
||||||
|
user, ok := middleware.GetUserFromContext(r.Context())
|
||||||
|
if !ok {
|
||||||
|
h.logger.Warn("update personal best failed - authentication required")
|
||||||
|
utils.RespondWithError(w, http.StatusUnauthorized, "Authentication required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
idStr := chi.URLParam(r, "id")
|
||||||
|
id, err := strconv.ParseUint(idStr, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
h.logger.Warn("invalid personal best ID", zap.String("id", idStr))
|
||||||
|
utils.RespondWithError(w, http.StatusBadRequest, "Invalid personal best ID")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var req models.PersonalBestUpdateRequest
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||||
|
h.logger.Error("failed to decode JSON payload", zap.Error(err))
|
||||||
|
utils.RespondWithError(w, http.StatusBadRequest, "Invalid request payload: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
personalBest, err := h.personalBestService.UpdatePersonalBest(uint(id), user.ID, req)
|
||||||
|
if err != nil {
|
||||||
|
h.logger.Error("failed to update personal best", zap.Error(err))
|
||||||
|
if err.Error() == "record not found" {
|
||||||
|
utils.RespondWithError(w, http.StatusNotFound, "Personal best not found or access denied")
|
||||||
|
} else {
|
||||||
|
utils.RespondWithError(w, http.StatusInternalServerError, "Failed to update personal best: "+err.Error())
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h.logger.Info("personal best updated successfully",
|
||||||
|
zap.Uint("personal_best_id", personalBest.ID),
|
||||||
|
zap.Uint("user_id", user.ID),
|
||||||
|
)
|
||||||
|
|
||||||
|
utils.RespondWithJSON(w, http.StatusOK, personalBest)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeletePersonalBest удаляет личный рекорд
|
||||||
|
func (h *PersonalBestHandler) DeletePersonalBest(w http.ResponseWriter, r *http.Request) {
|
||||||
|
h.logger.Info("handling delete personal best request",
|
||||||
|
zap.String("method", r.Method),
|
||||||
|
zap.String("path", r.URL.Path),
|
||||||
|
zap.String("remote_addr", r.RemoteAddr),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Получаем пользователя из контекста
|
||||||
|
user, ok := middleware.GetUserFromContext(r.Context())
|
||||||
|
if !ok {
|
||||||
|
h.logger.Warn("delete personal best failed - authentication required")
|
||||||
|
utils.RespondWithError(w, http.StatusUnauthorized, "Authentication required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
idStr := chi.URLParam(r, "id")
|
||||||
|
id, err := strconv.ParseUint(idStr, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
h.logger.Warn("invalid personal best ID", zap.String("id", idStr))
|
||||||
|
utils.RespondWithError(w, http.StatusBadRequest, "Invalid personal best ID")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = h.personalBestService.DeletePersonalBest(uint(id), user.ID)
|
||||||
|
if err != nil {
|
||||||
|
h.logger.Error("failed to delete personal best", zap.Error(err))
|
||||||
|
if err.Error() == "record not found" {
|
||||||
|
utils.RespondWithError(w, http.StatusNotFound, "Personal best not found or access denied")
|
||||||
|
} else {
|
||||||
|
utils.RespondWithError(w, http.StatusInternalServerError, "Failed to delete personal best: "+err.Error())
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h.logger.Info("personal best deleted successfully",
|
||||||
|
zap.Uint("personal_best_id", uint(id)),
|
||||||
|
zap.Uint("user_id", user.ID),
|
||||||
|
)
|
||||||
|
|
||||||
|
utils.RespondWithJSON(w, http.StatusOK, map[string]interface{}{
|
||||||
|
"message": "Personal best deleted successfully",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPersonalBestsByDistance возвращает личные рекорды по дистанции
|
||||||
|
func (h *PersonalBestHandler) GetPersonalBestsByDistance(w http.ResponseWriter, r *http.Request) {
|
||||||
|
h.logger.Info("handling get personal bests by distance request",
|
||||||
|
zap.String("method", r.Method),
|
||||||
|
zap.String("path", r.URL.Path),
|
||||||
|
zap.String("remote_addr", r.RemoteAddr),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Получаем пользователя из контекста
|
||||||
|
user, ok := middleware.GetUserFromContext(r.Context())
|
||||||
|
if !ok {
|
||||||
|
h.logger.Warn("get personal bests by distance failed - authentication required")
|
||||||
|
utils.RespondWithError(w, http.StatusUnauthorized, "Authentication required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
distanceType := models.DistanceType(chi.URLParam(r, "distanceType"))
|
||||||
|
if distanceType == "" {
|
||||||
|
h.logger.Warn("distance type parameter is required")
|
||||||
|
utils.RespondWithError(w, http.StatusBadRequest, "Distance type parameter is required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Валидация типа дистанции
|
||||||
|
validDistances := map[models.DistanceType]bool{
|
||||||
|
models.Distance5K: true,
|
||||||
|
models.Distance10K: true,
|
||||||
|
models.DistanceHalf: true,
|
||||||
|
models.DistanceFull: true,
|
||||||
|
models.DistanceOther: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
if !validDistances[distanceType] {
|
||||||
|
h.logger.Warn("invalid distance type", zap.String("distance_type", string(distanceType)))
|
||||||
|
utils.RespondWithError(w, http.StatusBadRequest, "Invalid distance type")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
personalBests, err := h.personalBestService.GetPersonalBestsByDistance(user.ID, distanceType)
|
||||||
|
if err != nil {
|
||||||
|
h.logger.Error("failed to get personal bests by distance", zap.Error(err))
|
||||||
|
utils.RespondWithError(w, http.StatusInternalServerError, "Failed to get personal bests: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h.logger.Info("personal bests by distance retrieved successfully",
|
||||||
|
zap.Uint("user_id", user.ID),
|
||||||
|
zap.String("distance_type", string(distanceType)),
|
||||||
|
zap.Int("count", len(personalBests)),
|
||||||
|
)
|
||||||
|
|
||||||
|
utils.RespondWithJSON(w, http.StatusOK, personalBests)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBestByDistance возвращает лучший результат на дистанции
|
||||||
|
func (h *PersonalBestHandler) GetBestByDistance(w http.ResponseWriter, r *http.Request) {
|
||||||
|
h.logger.Info("handling get best by distance request",
|
||||||
|
zap.String("method", r.Method),
|
||||||
|
zap.String("path", r.URL.Path),
|
||||||
|
zap.String("remote_addr", r.RemoteAddr),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Получаем пользователя из контекста
|
||||||
|
user, ok := middleware.GetUserFromContext(r.Context())
|
||||||
|
if !ok {
|
||||||
|
h.logger.Warn("get best by distance failed - authentication required")
|
||||||
|
utils.RespondWithError(w, http.StatusUnauthorized, "Authentication required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
distanceType := models.DistanceType(chi.URLParam(r, "distanceType"))
|
||||||
|
if distanceType == "" {
|
||||||
|
h.logger.Warn("distance type parameter is required")
|
||||||
|
utils.RespondWithError(w, http.StatusBadRequest, "Distance type parameter is required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
best, err := h.personalBestService.GetBestByDistance(user.ID, distanceType)
|
||||||
|
if err != nil {
|
||||||
|
if err.Error() == "record not found" {
|
||||||
|
h.logger.Info("no personal best found for distance",
|
||||||
|
zap.Uint("user_id", user.ID),
|
||||||
|
zap.String("distance_type", string(distanceType)),
|
||||||
|
)
|
||||||
|
utils.RespondWithJSON(w, http.StatusOK, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
h.logger.Error("failed to get best by distance", zap.Error(err))
|
||||||
|
utils.RespondWithError(w, http.StatusInternalServerError, "Failed to get best result: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h.logger.Info("best by distance retrieved successfully",
|
||||||
|
zap.Uint("user_id", user.ID),
|
||||||
|
zap.String("distance_type", string(distanceType)),
|
||||||
|
)
|
||||||
|
|
||||||
|
utils.RespondWithJSON(w, http.StatusOK, best)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPersonalBestsSummary возвращает сводку лучших результатов
|
||||||
|
func (h *PersonalBestHandler) GetPersonalBestsSummary(w http.ResponseWriter, r *http.Request) {
|
||||||
|
h.logger.Info("handling get personal bests summary request",
|
||||||
|
zap.String("method", r.Method),
|
||||||
|
zap.String("path", r.URL.Path),
|
||||||
|
zap.String("remote_addr", r.RemoteAddr),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Получаем пользователя из контекста
|
||||||
|
user, ok := middleware.GetUserFromContext(r.Context())
|
||||||
|
if !ok {
|
||||||
|
h.logger.Warn("get personal bests summary failed - authentication required")
|
||||||
|
utils.RespondWithError(w, http.StatusUnauthorized, "Authentication required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
summary, err := h.personalBestService.GetPersonalBestsSummary(user.ID)
|
||||||
|
if err != nil {
|
||||||
|
h.logger.Error("failed to get personal bests summary", zap.Error(err))
|
||||||
|
utils.RespondWithError(w, http.StatusInternalServerError, "Failed to get personal bests summary: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h.logger.Info("personal bests summary retrieved successfully",
|
||||||
|
zap.Uint("user_id", user.ID),
|
||||||
|
)
|
||||||
|
|
||||||
|
utils.RespondWithJSON(w, http.StatusOK, summary)
|
||||||
|
}
|
||||||
|
|
||||||
|
// VerifyPersonalBest подтверждает личный рекорд
|
||||||
|
func (h *PersonalBestHandler) VerifyPersonalBest(w http.ResponseWriter, r *http.Request) {
|
||||||
|
h.logger.Info("handling verify personal best request",
|
||||||
|
zap.String("method", r.Method),
|
||||||
|
zap.String("path", r.URL.Path),
|
||||||
|
zap.String("remote_addr", r.RemoteAddr),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Получаем пользователя из контекста
|
||||||
|
user, ok := middleware.GetUserFromContext(r.Context())
|
||||||
|
if !ok {
|
||||||
|
h.logger.Warn("verify personal best failed - authentication required")
|
||||||
|
utils.RespondWithError(w, http.StatusUnauthorized, "Authentication required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
idStr := chi.URLParam(r, "id")
|
||||||
|
id, err := strconv.ParseUint(idStr, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
h.logger.Warn("invalid personal best ID", zap.String("id", idStr))
|
||||||
|
utils.RespondWithError(w, http.StatusBadRequest, "Invalid personal best ID")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = h.personalBestService.VerifyPersonalBest(uint(id), user.ID)
|
||||||
|
if err != nil {
|
||||||
|
h.logger.Error("failed to verify personal best", zap.Error(err))
|
||||||
|
if err.Error() == "record not found" {
|
||||||
|
utils.RespondWithError(w, http.StatusNotFound, "Personal best not found or access denied")
|
||||||
|
} else {
|
||||||
|
utils.RespondWithError(w, http.StatusInternalServerError, "Failed to verify personal best: "+err.Error())
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h.logger.Info("personal best verified successfully",
|
||||||
|
zap.Uint("personal_best_id", uint(id)),
|
||||||
|
zap.Uint("user_id", user.ID),
|
||||||
|
)
|
||||||
|
|
||||||
|
utils.RespondWithJSON(w, http.StatusOK, map[string]interface{}{
|
||||||
|
"message": "Personal best verified successfully",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRecentPersonalBests возвращает последние личные рекорды
|
||||||
|
func (h *PersonalBestHandler) GetRecentPersonalBests(w http.ResponseWriter, r *http.Request) {
|
||||||
|
h.logger.Info("handling get recent personal bests request",
|
||||||
|
zap.String("method", r.Method),
|
||||||
|
zap.String("path", r.URL.Path),
|
||||||
|
zap.String("remote_addr", r.RemoteAddr),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Получаем пользователя из контекста
|
||||||
|
user, ok := middleware.GetUserFromContext(r.Context())
|
||||||
|
if !ok {
|
||||||
|
h.logger.Warn("get recent personal bests failed - authentication required")
|
||||||
|
utils.RespondWithError(w, http.StatusUnauthorized, "Authentication required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
limit := 10 // default limit
|
||||||
|
limitStr := r.URL.Query().Get("limit")
|
||||||
|
if limitStr != "" {
|
||||||
|
if l, err := strconv.Atoi(limitStr); err == nil && l > 0 {
|
||||||
|
limit = l
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
personalBests, err := h.personalBestService.GetRecentPersonalBests(user.ID, limit)
|
||||||
|
if err != nil {
|
||||||
|
h.logger.Error("failed to get recent personal bests", zap.Error(err))
|
||||||
|
utils.RespondWithError(w, http.StatusInternalServerError, "Failed to get recent personal bests: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h.logger.Info("recent personal bests retrieved successfully",
|
||||||
|
zap.Uint("user_id", user.ID),
|
||||||
|
zap.Int("limit", limit),
|
||||||
|
zap.Int("count", len(personalBests)),
|
||||||
|
)
|
||||||
|
|
||||||
|
utils.RespondWithJSON(w, http.StatusOK, personalBests)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CalculatePace вычисляет темп
|
||||||
|
func (h *PersonalBestHandler) CalculatePace(w http.ResponseWriter, r *http.Request) {
|
||||||
|
h.logger.Info("handling calculate pace request",
|
||||||
|
zap.String("method", r.Method),
|
||||||
|
zap.String("path", r.URL.Path),
|
||||||
|
zap.String("remote_addr", r.RemoteAddr),
|
||||||
|
)
|
||||||
|
|
||||||
|
var req struct {
|
||||||
|
Time string `json:"time"`
|
||||||
|
DistanceType models.DistanceType `json:"distance_type"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||||
|
h.logger.Error("failed to decode JSON payload", zap.Error(err))
|
||||||
|
utils.RespondWithError(w, http.StatusBadRequest, "Invalid request payload: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.Time == "" || req.DistanceType == "" {
|
||||||
|
h.logger.Warn("time and distance type are required")
|
||||||
|
utils.RespondWithError(w, http.StatusBadRequest, "Time and distance type are required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pace, err := h.personalBestService.CalculatePace(req.Time, req.DistanceType)
|
||||||
|
if err != nil {
|
||||||
|
h.logger.Error("failed to calculate pace", zap.Error(err))
|
||||||
|
utils.RespondWithError(w, http.StatusBadRequest, "Failed to calculate pace: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h.logger.Info("pace calculated successfully",
|
||||||
|
zap.String("time", req.Time),
|
||||||
|
zap.String("distance_type", string(req.DistanceType)),
|
||||||
|
zap.String("pace", pace),
|
||||||
|
)
|
||||||
|
|
||||||
|
utils.RespondWithJSON(w, http.StatusOK, map[string]interface{}{
|
||||||
|
"time": req.Time,
|
||||||
|
"distance_type": req.DistanceType,
|
||||||
|
"pace": pace,
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -71,3 +71,15 @@ type PersonalBestUpdateRequest struct {
|
|||||||
Location string `json:"location" validate:"omitempty,max=255"`
|
Location string `json:"location" validate:"omitempty,max=255"`
|
||||||
Verified bool `json:"verified"`
|
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"`
|
||||||
|
}
|
||||||
@@ -59,10 +59,3 @@ type UserStatsResponse struct {
|
|||||||
MonthlyDistance float64 `json:"monthly_distance"`
|
MonthlyDistance float64 `json:"monthly_distance"`
|
||||||
PersonalBests PersonalBestsSummary `json:"personal_bests"`
|
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"`
|
|
||||||
}
|
|
||||||
@@ -4,9 +4,9 @@ package repository
|
|||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gorm.io/gorm"
|
|
||||||
"api_bb/internal/models"
|
"api_bb/internal/models"
|
||||||
"api_bb/pkg/utils"
|
"api_bb/pkg/utils"
|
||||||
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
type PersonalBestRepository interface {
|
type PersonalBestRepository interface {
|
||||||
@@ -22,6 +22,8 @@ type PersonalBestRepository interface {
|
|||||||
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)
|
CalculatePace(timeStr string, distanceType models.DistanceType) (string, error)
|
||||||
|
GetRecentPersonalBests(userID uint, limit int) ([]models.PersonalBest, error)
|
||||||
|
GetByEventName(userID uint, eventName string) ([]models.PersonalBest, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type personalBestRepository struct {
|
type personalBestRepository struct {
|
||||||
@@ -51,6 +53,7 @@ func (r *personalBestRepository) GetByID(id uint) (*models.PersonalBest, error)
|
|||||||
func (r *personalBestRepository) GetByUserID(userID uint) ([]models.PersonalBest, error) {
|
func (r *personalBestRepository) GetByUserID(userID uint) ([]models.PersonalBest, error) {
|
||||||
var personalBests []models.PersonalBest
|
var personalBests []models.PersonalBest
|
||||||
err := r.db.Where("user_id = ?", userID).
|
err := r.db.Where("user_id = ?", userID).
|
||||||
|
Preload("User").
|
||||||
Order("distance_type, time").
|
Order("distance_type, time").
|
||||||
Find(&personalBests).Error
|
Find(&personalBests).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -63,6 +66,7 @@ func (r *personalBestRepository) GetByUserID(userID uint) ([]models.PersonalBest
|
|||||||
func (r *personalBestRepository) GetByUserAndDistance(userID uint, distanceType models.DistanceType) ([]models.PersonalBest, error) {
|
func (r *personalBestRepository) GetByUserAndDistance(userID uint, distanceType models.DistanceType) ([]models.PersonalBest, error) {
|
||||||
var personalBests []models.PersonalBest
|
var personalBests []models.PersonalBest
|
||||||
err := r.db.Where("user_id = ? AND distance_type = ?", userID, distanceType).
|
err := r.db.Where("user_id = ? AND distance_type = ?", userID, distanceType).
|
||||||
|
Preload("User").
|
||||||
Order("time").
|
Order("time").
|
||||||
Find(&personalBests).Error
|
Find(&personalBests).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -75,6 +79,7 @@ func (r *personalBestRepository) GetByUserAndDistance(userID uint, distanceType
|
|||||||
func (r *personalBestRepository) GetBestByDistance(userID uint, distanceType models.DistanceType) (*models.PersonalBest, error) {
|
func (r *personalBestRepository) GetBestByDistance(userID uint, distanceType models.DistanceType) (*models.PersonalBest, error) {
|
||||||
var personalBest models.PersonalBest
|
var personalBest models.PersonalBest
|
||||||
err := r.db.Where("user_id = ? AND distance_type = ?", userID, distanceType).
|
err := r.db.Where("user_id = ? AND distance_type = ?", userID, distanceType).
|
||||||
|
Preload("User").
|
||||||
Order("time").
|
Order("time").
|
||||||
First(&personalBest).Error
|
First(&personalBest).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -97,6 +102,7 @@ func (r *personalBestRepository) Delete(id uint) error {
|
|||||||
func (r *personalBestRepository) GetVerifiedByUserID(userID uint) ([]models.PersonalBest, error) {
|
func (r *personalBestRepository) GetVerifiedByUserID(userID uint) ([]models.PersonalBest, error) {
|
||||||
var personalBests []models.PersonalBest
|
var personalBests []models.PersonalBest
|
||||||
err := r.db.Where("user_id = ? AND verified = ?", userID, true).
|
err := r.db.Where("user_id = ? AND verified = ?", userID, true).
|
||||||
|
Preload("User").
|
||||||
Order("distance_type, time").
|
Order("distance_type, time").
|
||||||
Find(&personalBests).Error
|
Find(&personalBests).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -109,6 +115,7 @@ func (r *personalBestRepository) GetVerifiedByUserID(userID uint) ([]models.Pers
|
|||||||
func (r *personalBestRepository) GetByDateRange(userID uint, startDate, endDate time.Time) ([]models.PersonalBest, error) {
|
func (r *personalBestRepository) GetByDateRange(userID uint, startDate, endDate time.Time) ([]models.PersonalBest, error) {
|
||||||
var personalBests []models.PersonalBest
|
var personalBests []models.PersonalBest
|
||||||
err := r.db.Where("user_id = ? AND date BETWEEN ? AND ?", userID, startDate, endDate).
|
err := r.db.Where("user_id = ? AND date BETWEEN ? AND ?", userID, startDate, endDate).
|
||||||
|
Preload("User").
|
||||||
Order("date DESC, distance_type").
|
Order("date DESC, distance_type").
|
||||||
Find(&personalBests).Error
|
Find(&personalBests).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -117,6 +124,34 @@ func (r *personalBestRepository) GetByDateRange(userID uint, startDate, endDate
|
|||||||
return personalBests, nil
|
return personalBests, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetRecentPersonalBests возвращает последние личные рекорды
|
||||||
|
func (r *personalBestRepository) GetRecentPersonalBests(userID uint, limit int) ([]models.PersonalBest, error) {
|
||||||
|
var personalBests []models.PersonalBest
|
||||||
|
err := r.db.Where("user_id = ?", userID).
|
||||||
|
Preload("User").
|
||||||
|
Order("created_at DESC").
|
||||||
|
Limit(limit).
|
||||||
|
Find(&personalBests).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return personalBests, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetByEventName возвращает личные рекорды по названию события
|
||||||
|
func (r *personalBestRepository) GetByEventName(userID uint, eventName string) ([]models.PersonalBest, error) {
|
||||||
|
var personalBests []models.PersonalBest
|
||||||
|
err := r.db.Where("user_id = ? AND event_name LIKE ?", userID, "%"+eventName+"%").
|
||||||
|
Preload("User").
|
||||||
|
Order("date DESC").
|
||||||
|
Find(&personalBests).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return personalBests, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPersonalBestsSummary возвращает сводку лучших результатов по дистанциям
|
||||||
// GetPersonalBestsSummary возвращает сводку лучших результатов по дистанциям
|
// GetPersonalBestsSummary возвращает сводку лучших результатов по дистанциям
|
||||||
func (r *personalBestRepository) GetPersonalBestsSummary(userID uint) (*models.PersonalBestsSummary, error) {
|
func (r *personalBestRepository) GetPersonalBestsSummary(userID uint) (*models.PersonalBestsSummary, error) {
|
||||||
summary := &models.PersonalBestsSummary{}
|
summary := &models.PersonalBestsSummary{}
|
||||||
@@ -138,12 +173,16 @@ func (r *personalBestRepository) GetPersonalBestsSummary(userID uint) (*models.P
|
|||||||
switch distance {
|
switch distance {
|
||||||
case models.Distance5K:
|
case models.Distance5K:
|
||||||
summary.Best5K = best.Time
|
summary.Best5K = best.Time
|
||||||
|
summary.Best5KPace = best.Pace
|
||||||
case models.Distance10K:
|
case models.Distance10K:
|
||||||
summary.Best10K = best.Time
|
summary.Best10K = best.Time
|
||||||
|
summary.Best10KPace = best.Pace
|
||||||
case models.DistanceHalf:
|
case models.DistanceHalf:
|
||||||
summary.BestHalf = best.Time
|
summary.BestHalf = best.Time
|
||||||
|
summary.BestHalfPace = best.Pace
|
||||||
case models.DistanceFull:
|
case models.DistanceFull:
|
||||||
summary.BestMarathon = best.Time
|
summary.BestMarathon = best.Time
|
||||||
|
summary.BestMarathonPace = best.Pace
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -137,6 +137,29 @@ func SetupRouter(db *gorm.DB, config *config.Config) http.Handler {
|
|||||||
r.Patch("/verify", h.UserAchievementHandler().VerifyAchievement)
|
r.Patch("/verify", h.UserAchievementHandler().VerifyAchievement)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
// Personal Best routes
|
||||||
|
r.Route("/personal-bests", func(r chi.Router) {
|
||||||
|
// CRUD operations
|
||||||
|
r.Post("/", h.PersonalBestHandler().CreatePersonalBest)
|
||||||
|
r.Get("/", h.PersonalBestHandler().GetUserPersonalBests)
|
||||||
|
r.Get("/recent", h.PersonalBestHandler().GetRecentPersonalBests)
|
||||||
|
r.Get("/summary", h.PersonalBestHandler().GetPersonalBestsSummary)
|
||||||
|
r.Post("/calculate-pace", h.PersonalBestHandler().CalculatePace)
|
||||||
|
|
||||||
|
// Distance-specific routes
|
||||||
|
r.Route("/distance/{distanceType}", func(r chi.Router) {
|
||||||
|
r.Get("/", h.PersonalBestHandler().GetPersonalBestsByDistance)
|
||||||
|
r.Get("/best", h.PersonalBestHandler().GetBestByDistance)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Individual personal best routes
|
||||||
|
r.Route("/{id}", func(r chi.Router) {
|
||||||
|
r.Get("/", h.PersonalBestHandler().GetPersonalBest)
|
||||||
|
r.Put("/", h.PersonalBestHandler().UpdatePersonalBest)
|
||||||
|
r.Delete("/", h.PersonalBestHandler().DeletePersonalBest)
|
||||||
|
r.Patch("/verify", h.PersonalBestHandler().VerifyPersonalBest)
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
r.Route("/news", func(r chi.Router) {
|
r.Route("/news", func(r chi.Router) {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"api_bb/internal/models"
|
"api_bb/internal/models"
|
||||||
"api_bb/internal/repository"
|
"api_bb/internal/repository"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
@@ -64,11 +65,110 @@ func (s *PersonalBestService) CreatePersonalBest(userID uint, req models.Persona
|
|||||||
return personalBest, nil
|
return personalBest, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetPersonalBestByID возвращает личный рекорд по ID
|
||||||
|
func (s *PersonalBestService) GetPersonalBestByID(id uint) (*models.PersonalBest, error) {
|
||||||
|
return s.pbRepo.GetByID(id)
|
||||||
|
}
|
||||||
|
|
||||||
// GetUserPersonalBests возвращает все личные рекорды пользователя
|
// GetUserPersonalBests возвращает все личные рекорды пользователя
|
||||||
func (s *PersonalBestService) GetUserPersonalBests(userID uint) ([]models.PersonalBest, error) {
|
func (s *PersonalBestService) GetUserPersonalBests(userID uint) ([]models.PersonalBest, error) {
|
||||||
return s.pbRepo.GetByUserID(userID)
|
return s.pbRepo.GetByUserID(userID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetPersonalBestsByDistance возвращает личные рекорды по дистанции
|
||||||
|
func (s *PersonalBestService) GetPersonalBestsByDistance(userID uint, distanceType models.DistanceType) ([]models.PersonalBest, error) {
|
||||||
|
return s.pbRepo.GetByUserAndDistance(userID, distanceType)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBestByDistance возвращает лучший результат на дистанции
|
||||||
|
func (s *PersonalBestService) GetBestByDistance(userID uint, distanceType models.DistanceType) (*models.PersonalBest, error) {
|
||||||
|
return s.pbRepo.GetBestByDistance(userID, distanceType)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdatePersonalBest обновляет личный рекорд
|
||||||
|
func (s *PersonalBestService) UpdatePersonalBest(id uint, userID uint, req models.PersonalBestUpdateRequest) (*models.PersonalBest, error) {
|
||||||
|
// Получаем существующий рекорд
|
||||||
|
pb, err := s.pbRepo.GetByID(id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверяем, что рекорд принадлежит пользователю
|
||||||
|
if pb.UserID != userID {
|
||||||
|
return nil, gorm.ErrRecordNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
// Обновляем поля
|
||||||
|
if req.DistanceType != "" {
|
||||||
|
pb.DistanceType = req.DistanceType
|
||||||
|
}
|
||||||
|
if req.Time != "" {
|
||||||
|
pb.Time = req.Time
|
||||||
|
// Пересчитываем темп при изменении времени
|
||||||
|
if req.Pace == "" {
|
||||||
|
calculatedPace, err := s.pbRepo.CalculatePace(req.Time, pb.DistanceType)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
pb.Pace = calculatedPace
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if req.Pace != "" {
|
||||||
|
pb.Pace = req.Pace
|
||||||
|
}
|
||||||
|
if !req.Date.IsZero() {
|
||||||
|
pb.Date = req.Date
|
||||||
|
}
|
||||||
|
if req.EventName != "" {
|
||||||
|
pb.EventName = req.EventName
|
||||||
|
}
|
||||||
|
if req.Location != "" {
|
||||||
|
pb.Location = req.Location
|
||||||
|
}
|
||||||
|
pb.Verified = req.Verified
|
||||||
|
|
||||||
|
if err := s.pbRepo.Update(pb); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return pb, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeletePersonalBest удаляет личный рекорд
|
||||||
|
func (s *PersonalBestService) DeletePersonalBest(id uint, userID uint) error {
|
||||||
|
// Проверяем, что рекорд принадлежит пользователю
|
||||||
|
pb, err := s.pbRepo.GetByID(id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if pb.UserID != userID {
|
||||||
|
return gorm.ErrRecordNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.pbRepo.Delete(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetVerifiedPersonalBests возвращает подтвержденные личные рекорды
|
||||||
|
func (s *PersonalBestService) GetVerifiedPersonalBests(userID uint) ([]models.PersonalBest, error) {
|
||||||
|
return s.pbRepo.GetVerifiedByUserID(userID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPersonalBestsByDateRange возвращает личные рекорды за период
|
||||||
|
func (s *PersonalBestService) GetPersonalBestsByDateRange(userID uint, startDate, endDate time.Time) ([]models.PersonalBest, error) {
|
||||||
|
return s.pbRepo.GetByDateRange(userID, startDate, endDate)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRecentPersonalBests возвращает последние личные рекорды
|
||||||
|
func (s *PersonalBestService) GetRecentPersonalBests(userID uint, limit int) ([]models.PersonalBest, error) {
|
||||||
|
return s.pbRepo.GetRecentPersonalBests(userID, limit)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPersonalBestsByEvent возвращает личные рекорды по названию события
|
||||||
|
func (s *PersonalBestService) GetPersonalBestsByEvent(userID uint, eventName string) ([]models.PersonalBest, error) {
|
||||||
|
return s.pbRepo.GetByEventName(userID, eventName)
|
||||||
|
}
|
||||||
|
|
||||||
// GetPersonalBestsSummary возвращает сводку лучших результатов
|
// GetPersonalBestsSummary возвращает сводку лучших результатов
|
||||||
func (s *PersonalBestService) GetPersonalBestsSummary(userID uint) (*models.PersonalBestsSummary, error) {
|
func (s *PersonalBestService) GetPersonalBestsSummary(userID uint) (*models.PersonalBestsSummary, error) {
|
||||||
return s.pbRepo.GetPersonalBestsSummary(userID)
|
return s.pbRepo.GetPersonalBestsSummary(userID)
|
||||||
@@ -89,3 +189,8 @@ func (s *PersonalBestService) VerifyPersonalBest(id uint, userID uint) error {
|
|||||||
pb.Verified = true
|
pb.Verified = true
|
||||||
return s.pbRepo.Update(pb)
|
return s.pbRepo.Update(pb)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CalculatePace вычисляет темп для времени и дистанции
|
||||||
|
func (s *PersonalBestService) CalculatePace(timeStr string, distanceType models.DistanceType) (string, error) {
|
||||||
|
return s.pbRepo.CalculatePace(timeStr, distanceType)
|
||||||
|
}
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ export const useUserStore = defineStore('user', () => {
|
|||||||
return withStoreLoading(async () => {
|
return withStoreLoading(async () => {
|
||||||
try {
|
try {
|
||||||
const response = await apiClient.get('/user/stats')
|
const response = await apiClient.get('/user/stats')
|
||||||
|
console.log("debug /user/stats " + response.data)
|
||||||
userStats.value = response.data
|
userStats.value = response.data
|
||||||
return { success: true, data: userStats.value }
|
return { success: true, data: userStats.value }
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -95,6 +96,7 @@ export const useUserStore = defineStore('user', () => {
|
|||||||
return withStoreLoading(async () => {
|
return withStoreLoading(async () => {
|
||||||
try {
|
try {
|
||||||
const response = await apiClient.get('/user/achievements')
|
const response = await apiClient.get('/user/achievements')
|
||||||
|
console.log("debug /user/achievements " + response.data)
|
||||||
userAchievements.value = response.data
|
userAchievements.value = response.data
|
||||||
return { success: true, data: userAchievements.value }
|
return { success: true, data: userAchievements.value }
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -154,6 +156,7 @@ export const useUserStore = defineStore('user', () => {
|
|||||||
return withStoreLoading(async () => {
|
return withStoreLoading(async () => {
|
||||||
try {
|
try {
|
||||||
const response = await apiClient.get('/personal-bests')
|
const response = await apiClient.get('/personal-bests')
|
||||||
|
console.log("debug /user/personal-bests " + response.data)
|
||||||
personalBests.value = response.data
|
personalBests.value = response.data
|
||||||
return { success: true, data: personalBests.value }
|
return { success: true, data: personalBests.value }
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -189,6 +192,7 @@ export const useUserStore = defineStore('user', () => {
|
|||||||
return withStoreLoading(async () => {
|
return withStoreLoading(async () => {
|
||||||
try {
|
try {
|
||||||
const response = await apiClient.get('/events/upcoming')
|
const response = await apiClient.get('/events/upcoming')
|
||||||
|
console.log("debug /events/upcoming " + response.data)
|
||||||
upcomingEvents.value = response.data
|
upcomingEvents.value = response.data
|
||||||
return { success: true, data: upcomingEvents.value }
|
return { success: true, data: upcomingEvents.value }
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
Reference in New Issue
Block a user