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/user_achievement_handler.go modified: serv_nginx/api_bb/internal/routes/routes.go modified: serv_nginx/api_bb/internal/service/achievement_service.go modified: serv_nginx/api_bb/pkg/utils/validation.go modified: serv_nginx/bbvue/src/views/Home.vue add achievement's handler, routing, service, migrator gorm and update repository
This commit is contained in:
@@ -21,6 +21,7 @@ func (d *Database) Migrate() error {
|
|||||||
&models.Review{},
|
&models.Review{},
|
||||||
&models.UserStats{},
|
&models.UserStats{},
|
||||||
&models.Workout{},
|
&models.Workout{},
|
||||||
|
&models.Achievement{},
|
||||||
// Добавьте другие модели здесь
|
// Добавьте другие модели здесь
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,6 +82,8 @@ func getModelName(model interface{}) string {
|
|||||||
return "Статистика Пользователя"
|
return "Статистика Пользователя"
|
||||||
case *models.Workout:
|
case *models.Workout:
|
||||||
return "Тренировки пользователя"
|
return "Тренировки пользователя"
|
||||||
|
case *models.Achievement:
|
||||||
|
return "Достижения пользователя"
|
||||||
default:
|
default:
|
||||||
return "Unknown"
|
return "Unknown"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ type Handler struct {
|
|||||||
reviewHandler *ReviewHandler
|
reviewHandler *ReviewHandler
|
||||||
userStatsHandler *UserStatsHandler
|
userStatsHandler *UserStatsHandler
|
||||||
userWorkoutHandler *UserWorkoutHandler
|
userWorkoutHandler *UserWorkoutHandler
|
||||||
|
userAchievementHandler * UserAchievementHandler
|
||||||
// Здесь будут добавлены другие обработчики
|
// Здесь будут добавлены другие обработчики
|
||||||
// userHandler *UserHandler
|
// userHandler *UserHandler
|
||||||
// eventHandler *EventHandler
|
// eventHandler *EventHandler
|
||||||
@@ -33,6 +34,7 @@ func NewHandler(db *gorm.DB, cfg *config.Config) *Handler {
|
|||||||
reviewRepo := repository.NewReviewRepository(db)
|
reviewRepo := repository.NewReviewRepository(db)
|
||||||
userStatsRepo := repository.NewUserStatsRepository(db)
|
userStatsRepo := repository.NewUserStatsRepository(db)
|
||||||
userWorkoutRepo := repository.NewWorkoutRepository(db)
|
userWorkoutRepo := repository.NewWorkoutRepository(db)
|
||||||
|
userAchievemenRepo := repository.NewAchievementRepository(db)
|
||||||
|
|
||||||
// Initialize logger
|
// Initialize logger
|
||||||
baseLogger := logger.NewWrapper(logger.Get()) // Создаем базовый логгер
|
baseLogger := logger.NewWrapper(logger.Get()) // Создаем базовый логгер
|
||||||
@@ -46,6 +48,7 @@ func NewHandler(db *gorm.DB, cfg *config.Config) *Handler {
|
|||||||
reviewService := service.NewReviewService(reviewRepo, baseLogger)
|
reviewService := service.NewReviewService(reviewRepo, baseLogger)
|
||||||
userStatsService := service.NewUserStatsService(userStatsRepo)
|
userStatsService := service.NewUserStatsService(userStatsRepo)
|
||||||
userWorkoutService := service.NewWorkoutService(userWorkoutRepo)
|
userWorkoutService := service.NewWorkoutService(userWorkoutRepo)
|
||||||
|
achievementService := service.NewAchievementService(userAchievemenRepo)
|
||||||
|
|
||||||
// Инициализация обработчиков
|
// Инициализация обработчиков
|
||||||
healthHandler := NewHealthHandler()
|
healthHandler := NewHealthHandler()
|
||||||
@@ -56,6 +59,7 @@ func NewHandler(db *gorm.DB, cfg *config.Config) *Handler {
|
|||||||
reviewHandler := NewReviewHandler(reviewService, baseLogger)
|
reviewHandler := NewReviewHandler(reviewService, baseLogger)
|
||||||
userStatsHandler := NewUserStatsHandler(userStatsService)
|
userStatsHandler := NewUserStatsHandler(userStatsService)
|
||||||
userWorkoutHandler := NewUserWorkoutHandler(userWorkoutService)
|
userWorkoutHandler := NewUserWorkoutHandler(userWorkoutService)
|
||||||
|
userAchievementHandler := NewUserAchievementHandler(*achievementService)
|
||||||
|
|
||||||
return &Handler{
|
return &Handler{
|
||||||
healthHandler: healthHandler,
|
healthHandler: healthHandler,
|
||||||
@@ -66,6 +70,7 @@ func NewHandler(db *gorm.DB, cfg *config.Config) *Handler {
|
|||||||
reviewHandler: reviewHandler,
|
reviewHandler: reviewHandler,
|
||||||
userStatsHandler: userStatsHandler,
|
userStatsHandler: userStatsHandler,
|
||||||
userWorkoutHandler: userWorkoutHandler,
|
userWorkoutHandler: userWorkoutHandler,
|
||||||
|
userAchievementHandler: userAchievementHandler,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,3 +106,7 @@ func (h *Handler) UserStatsHandler() *UserStatsHandler {
|
|||||||
func (h *Handler) UserWorkoutHandler() *UserWorkoutHandler {
|
func (h *Handler) UserWorkoutHandler() *UserWorkoutHandler {
|
||||||
return h.userWorkoutHandler
|
return h.userWorkoutHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h * Handler) UserAchievementHandler() *UserAchievementHandler {
|
||||||
|
return h.userAchievementHandler
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,617 @@
|
|||||||
|
// handlers/user_achievement_handler.go
|
||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"api_bb/internal/models"
|
||||||
|
"api_bb/internal/service"
|
||||||
|
"api_bb/pkg/logger"
|
||||||
|
"api_bb/pkg/middleware"
|
||||||
|
"api_bb/pkg/utils"
|
||||||
|
|
||||||
|
"github.com/go-chi/chi/v5"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UserAchievementHandler struct {
|
||||||
|
logger logger.LoggerInterface
|
||||||
|
achievementService service.AchievementService
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUserAchievementHandler(achievementService service.AchievementService) *UserAchievementHandler {
|
||||||
|
return &UserAchievementHandler{
|
||||||
|
logger: logger.NewWrapper(logger.Get().With(zap.String("handler", "user_achievement"))),
|
||||||
|
achievementService: achievementService,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAchievementsByType возвращает достижения по типу
|
||||||
|
func (h *UserAchievementHandler) GetAchievementsByType(w http.ResponseWriter, r *http.Request) {
|
||||||
|
h.logger.Info("handling get achievements by type 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 achievements by type failed - authentication required")
|
||||||
|
utils.RespondWithError(w, http.StatusUnauthorized, "Authentication required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Получаем тип из URL параметров
|
||||||
|
achievementType := chi.URLParam(r, "type")
|
||||||
|
if achievementType == "" {
|
||||||
|
utils.RespondWithError(w, http.StatusBadRequest, "Achievement type is required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Валидируем тип достижения
|
||||||
|
validType := models.AchievementType(achievementType)
|
||||||
|
switch validType {
|
||||||
|
case models.AchievementTypeDistance, models.AchievementTypeSpeed,
|
||||||
|
models.AchievementTypeConsistency, models.AchievementTypeEvent,
|
||||||
|
models.AchievementTypeSpecial:
|
||||||
|
// valid type
|
||||||
|
default:
|
||||||
|
utils.RespondWithError(w, http.StatusBadRequest, "Invalid achievement type")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
achievements, err := h.achievementService.GetAchievementsByType(user.ID, validType)
|
||||||
|
if err != nil {
|
||||||
|
h.logger.Error("failed to get achievements by type",
|
||||||
|
zap.Uint("user_id", user.ID),
|
||||||
|
zap.String("type", achievementType),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
utils.RespondWithError(w, http.StatusInternalServerError, "Failed to get achievements: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h.logger.Info("achievements by type retrieved successfully",
|
||||||
|
zap.Uint("user_id", user.ID),
|
||||||
|
zap.String("type", achievementType),
|
||||||
|
zap.Int("achievements_count", len(achievements)),
|
||||||
|
)
|
||||||
|
|
||||||
|
utils.RespondWithJSON(w, http.StatusOK, achievements)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAchievementByID возвращает достижение по ID
|
||||||
|
func (h *UserAchievementHandler) GetAchievementByID(w http.ResponseWriter, r *http.Request) {
|
||||||
|
h.logger.Info("handling get achievement by ID 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 achievement by ID failed - authentication required")
|
||||||
|
utils.RespondWithError(w, http.StatusUnauthorized, "Authentication required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Получаем ID достижения из URL параметров
|
||||||
|
achievementIDStr := chi.URLParam(r, "id")
|
||||||
|
if achievementIDStr == "" {
|
||||||
|
utils.RespondWithError(w, http.StatusBadRequest, "Achievement ID is required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
achievementID, err := strconv.ParseUint(achievementIDStr, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
utils.RespondWithError(w, http.StatusBadRequest, "Invalid achievement ID")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
achievement, err := h.achievementService.GetAchievementByID(uint(achievementID), user.ID)
|
||||||
|
if err != nil {
|
||||||
|
h.logger.Error("failed to get achievement by ID",
|
||||||
|
zap.Uint("user_id", user.ID),
|
||||||
|
zap.Uint("achievement_id", uint(achievementID)),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
if err == service.ErrAchievementNotFound {
|
||||||
|
utils.RespondWithError(w, http.StatusNotFound, "Achievement not found")
|
||||||
|
} else {
|
||||||
|
utils.RespondWithError(w, http.StatusInternalServerError, "Failed to get achievement: "+err.Error())
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h.logger.Info("achievement retrieved successfully",
|
||||||
|
zap.Uint("user_id", user.ID),
|
||||||
|
zap.Uint("achievement_id", uint(achievementID)),
|
||||||
|
)
|
||||||
|
|
||||||
|
utils.RespondWithJSON(w, http.StatusOK, achievement)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateAchievement обновляет достижение
|
||||||
|
func (h *UserAchievementHandler) UpdateAchievement(w http.ResponseWriter, r *http.Request) {
|
||||||
|
h.logger.Info("handling update achievement 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 achievement failed - authentication required")
|
||||||
|
utils.RespondWithError(w, http.StatusUnauthorized, "Authentication required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Получаем ID достижения из URL параметров
|
||||||
|
achievementIDStr := chi.URLParam(r, "id")
|
||||||
|
if achievementIDStr == "" {
|
||||||
|
utils.RespondWithError(w, http.StatusBadRequest, "Achievement ID is required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
achievementID, err := strconv.ParseUint(achievementIDStr, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
utils.RespondWithError(w, http.StatusBadRequest, "Invalid achievement ID")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var req models.AchievementCreateRequest
|
||||||
|
if err := utils.DecodeJSONBody(w, r, &req); err != nil {
|
||||||
|
h.logger.Error("failed to decode achievement update request", zap.Error(err))
|
||||||
|
utils.RespondWithError(w, http.StatusBadRequest, "Invalid request payload: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Валидация запроса
|
||||||
|
if err := utils.ValidateStruct(req); err != nil {
|
||||||
|
h.logger.Warn("achievement update validation failed", zap.Error(err))
|
||||||
|
utils.RespondWithValidationError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Обновляем достижение через сервис
|
||||||
|
achievement, err := h.achievementService.UpdateAchievement(uint(achievementID), user.ID, req)
|
||||||
|
if err != nil {
|
||||||
|
h.logger.Error("failed to update achievement",
|
||||||
|
zap.Uint("user_id", user.ID),
|
||||||
|
zap.Uint("achievement_id", uint(achievementID)),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
if err == service.ErrAchievementNotFound {
|
||||||
|
utils.RespondWithError(w, http.StatusNotFound, "Achievement not found")
|
||||||
|
} else if err == service.ErrAchievementAlreadyExists {
|
||||||
|
utils.RespondWithError(w, http.StatusConflict, "Achievement with this title already exists")
|
||||||
|
} else {
|
||||||
|
utils.RespondWithError(w, http.StatusInternalServerError, "Failed to update achievement: "+err.Error())
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h.logger.Info("achievement updated successfully",
|
||||||
|
zap.Uint("user_id", user.ID),
|
||||||
|
zap.Uint("achievement_id", uint(achievementID)),
|
||||||
|
)
|
||||||
|
|
||||||
|
utils.RespondWithJSON(w, http.StatusOK, map[string]interface{}{
|
||||||
|
"message": "Achievement updated successfully",
|
||||||
|
"achievement": achievement,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPublicUserAchievements возвращает достижения пользователя для публичного просмотра
|
||||||
|
func (h *UserAchievementHandler) GetPublicUserAchievements(w http.ResponseWriter, r *http.Request) {
|
||||||
|
h.logger.Info("handling get public user achievements request",
|
||||||
|
zap.String("method", r.Method),
|
||||||
|
zap.String("path", r.URL.Path),
|
||||||
|
zap.String("remote_addr", r.RemoteAddr),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Получаем userID из URL параметров
|
||||||
|
userIDStr := r.URL.Query().Get("userID")
|
||||||
|
if userIDStr == "" {
|
||||||
|
utils.RespondWithError(w, http.StatusBadRequest, "User ID is required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
userID, err := strconv.ParseUint(userIDStr, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
utils.RespondWithError(w, http.StatusBadRequest, "Invalid user ID")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Получаем только подтвержденные достижения для публичного просмотра
|
||||||
|
achievements, err := h.achievementService.GetVerifiedAchievements(uint(userID))
|
||||||
|
if err != nil {
|
||||||
|
h.logger.Error("failed to get public user achievements",
|
||||||
|
zap.Uint("user_id", uint(userID)),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
utils.RespondWithError(w, http.StatusInternalServerError, "Failed to get achievements: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h.logger.Info("public user achievements retrieved successfully",
|
||||||
|
zap.Uint("user_id", uint(userID)),
|
||||||
|
zap.Int("achievements_count", len(achievements)),
|
||||||
|
)
|
||||||
|
|
||||||
|
utils.RespondWithJSON(w, http.StatusOK, achievements)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPublicUserAchievementsSummary возвращает сводку по достижениям пользователя для публичного просмотра
|
||||||
|
func (h *UserAchievementHandler) GetPublicUserAchievementsSummary(w http.ResponseWriter, r *http.Request) {
|
||||||
|
h.logger.Info("handling get public user achievements summary request",
|
||||||
|
zap.String("method", r.Method),
|
||||||
|
zap.String("path", r.URL.Path),
|
||||||
|
zap.String("remote_addr", r.RemoteAddr),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Получаем userID из URL параметров
|
||||||
|
userIDStr := r.URL.Query().Get("userID")
|
||||||
|
if userIDStr == "" {
|
||||||
|
utils.RespondWithError(w, http.StatusBadRequest, "User ID is required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
userID, err := strconv.ParseUint(userIDStr, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
utils.RespondWithError(w, http.StatusBadRequest, "Invalid user ID")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
summary, err := h.achievementService.GetUserAchievementsSummary(uint(userID))
|
||||||
|
if err != nil {
|
||||||
|
h.logger.Error("failed to get public user achievements summary",
|
||||||
|
zap.Uint("user_id", uint(userID)),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
utils.RespondWithError(w, http.StatusInternalServerError, "Failed to get achievements summary: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h.logger.Info("public user achievements summary retrieved successfully",
|
||||||
|
zap.Uint("user_id", uint(userID)),
|
||||||
|
zap.Int("total_achievements", summary.TotalAchievements),
|
||||||
|
zap.Int("completed", summary.Completed),
|
||||||
|
)
|
||||||
|
|
||||||
|
utils.RespondWithJSON(w, http.StatusOK, summary)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPublicRecentAchievements возвращает последние достижения пользователя для публичного просмотра
|
||||||
|
func (h *UserAchievementHandler) GetPublicRecentAchievements(w http.ResponseWriter, r *http.Request) {
|
||||||
|
h.logger.Info("handling get public recent achievements request",
|
||||||
|
zap.String("method", r.Method),
|
||||||
|
zap.String("path", r.URL.Path),
|
||||||
|
zap.String("remote_addr", r.RemoteAddr),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Получаем userID из URL параметров
|
||||||
|
userIDStr := r.URL.Query().Get("userID")
|
||||||
|
if userIDStr == "" {
|
||||||
|
utils.RespondWithError(w, http.StatusBadRequest, "User ID is required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
userID, err := strconv.ParseUint(userIDStr, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
utils.RespondWithError(w, http.StatusBadRequest, "Invalid user ID")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Получаем параметр limit из query string (по умолчанию 10)
|
||||||
|
limit := 10
|
||||||
|
limitStr := r.URL.Query().Get("limit")
|
||||||
|
if limitStr != "" {
|
||||||
|
if parsedLimit, err := strconv.Atoi(limitStr); err == nil && parsedLimit > 0 {
|
||||||
|
limit = parsedLimit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Получаем только подтвержденные достижения
|
||||||
|
achievements, err := h.achievementService.GetVerifiedRecentAchievements(uint(userID), limit)
|
||||||
|
if err != nil {
|
||||||
|
h.logger.Error("failed to get public recent achievements",
|
||||||
|
zap.Uint("user_id", uint(userID)),
|
||||||
|
zap.Int("limit", limit),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
utils.RespondWithError(w, http.StatusInternalServerError, "Failed to get recent achievements: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h.logger.Info("public recent achievements retrieved successfully",
|
||||||
|
zap.Uint("user_id", uint(userID)),
|
||||||
|
zap.Int("achievements_count", len(achievements)),
|
||||||
|
)
|
||||||
|
|
||||||
|
utils.RespondWithJSON(w, http.StatusOK, achievements)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateAchievement создает новое достижение для пользователя
|
||||||
|
func (h *UserAchievementHandler) CreateAchievement(w http.ResponseWriter, r *http.Request) {
|
||||||
|
h.logger.Info("handling create achievement 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 achievement failed - authentication required")
|
||||||
|
utils.RespondWithError(w, http.StatusUnauthorized, "Authentication required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var req models.AchievementCreateRequest
|
||||||
|
if err := utils.DecodeJSONBody(w, r, &req); err != nil {
|
||||||
|
h.logger.Error("failed to decode achievement request", zap.Error(err))
|
||||||
|
utils.RespondWithError(w, http.StatusBadRequest, "Invalid request payload: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Валидация запроса
|
||||||
|
if err := utils.ValidateStruct(req); err != nil {
|
||||||
|
h.logger.Warn("achievement validation failed", zap.Error(err))
|
||||||
|
utils.RespondWithValidationError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Создаем достижение через сервис
|
||||||
|
achievement, err := h.achievementService.CreateAchievement(user.ID, req)
|
||||||
|
if err != nil {
|
||||||
|
h.logger.Error("failed to create achievement",
|
||||||
|
zap.Uint("user_id", user.ID),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
if err == service.ErrAchievementAlreadyExists {
|
||||||
|
utils.RespondWithError(w, http.StatusConflict, "Achievement with this title already exists")
|
||||||
|
} else {
|
||||||
|
utils.RespondWithError(w, http.StatusInternalServerError, "Failed to create achievement: "+err.Error())
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h.logger.Info("achievement created successfully",
|
||||||
|
zap.Uint("user_id", user.ID),
|
||||||
|
zap.Uint("achievement_id", achievement.ID),
|
||||||
|
zap.String("title", achievement.Title),
|
||||||
|
)
|
||||||
|
|
||||||
|
utils.RespondWithJSON(w, http.StatusCreated, map[string]interface{}{
|
||||||
|
"message": "Achievement created successfully",
|
||||||
|
"achievement": achievement,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserAchievements возвращает все достижения пользователя
|
||||||
|
func (h *UserAchievementHandler) GetUserAchievements(w http.ResponseWriter, r *http.Request) {
|
||||||
|
h.logger.Info("handling get user achievements 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 achievements failed - authentication required")
|
||||||
|
utils.RespondWithError(w, http.StatusUnauthorized, "Authentication required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
achievements, err := h.achievementService.GetUserAchievements(user.ID)
|
||||||
|
if err != nil {
|
||||||
|
h.logger.Error("failed to get user achievements",
|
||||||
|
zap.Uint("user_id", user.ID),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
utils.RespondWithError(w, http.StatusInternalServerError, "Failed to get achievements: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h.logger.Info("user achievements retrieved successfully",
|
||||||
|
zap.Uint("user_id", user.ID),
|
||||||
|
zap.Int("achievements_count", len(achievements)),
|
||||||
|
)
|
||||||
|
|
||||||
|
utils.RespondWithJSON(w, http.StatusOK, achievements)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserAchievementsSummary возвращает сводку по достижениям пользователя
|
||||||
|
func (h *UserAchievementHandler) GetUserAchievementsSummary(w http.ResponseWriter, r *http.Request) {
|
||||||
|
h.logger.Info("handling get user achievements 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 achievements summary failed - authentication required")
|
||||||
|
utils.RespondWithError(w, http.StatusUnauthorized, "Authentication required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
summary, err := h.achievementService.GetUserAchievementsSummary(user.ID)
|
||||||
|
if err != nil {
|
||||||
|
h.logger.Error("failed to get user achievements summary",
|
||||||
|
zap.Uint("user_id", user.ID),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
utils.RespondWithError(w, http.StatusInternalServerError, "Failed to get achievements summary: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h.logger.Info("user achievements summary retrieved successfully",
|
||||||
|
zap.Uint("user_id", user.ID),
|
||||||
|
zap.Int("total_achievements", summary.TotalAchievements),
|
||||||
|
zap.Int("completed", summary.Completed),
|
||||||
|
zap.Float64("progress_percent", summary.ProgressPercent),
|
||||||
|
)
|
||||||
|
|
||||||
|
utils.RespondWithJSON(w, http.StatusOK, summary)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRecentAchievements возвращает последние достижения пользователя
|
||||||
|
func (h *UserAchievementHandler) GetRecentAchievements(w http.ResponseWriter, r *http.Request) {
|
||||||
|
h.logger.Info("handling get recent achievements 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 achievements failed - authentication required")
|
||||||
|
utils.RespondWithError(w, http.StatusUnauthorized, "Authentication required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Получаем параметр limit из query string (по умолчанию 10)
|
||||||
|
limit := 10
|
||||||
|
limitStr := r.URL.Query().Get("limit")
|
||||||
|
if limitStr != "" {
|
||||||
|
if parsedLimit, err := strconv.Atoi(limitStr); err == nil && parsedLimit > 0 {
|
||||||
|
limit = parsedLimit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
achievements, err := h.achievementService.GetRecentAchievements(user.ID, limit)
|
||||||
|
if err != nil {
|
||||||
|
h.logger.Error("failed to get recent achievements",
|
||||||
|
zap.Uint("user_id", user.ID),
|
||||||
|
zap.Int("limit", limit),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
utils.RespondWithError(w, http.StatusInternalServerError, "Failed to get recent achievements: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h.logger.Info("recent achievements retrieved successfully",
|
||||||
|
zap.Uint("user_id", user.ID),
|
||||||
|
zap.Int("achievements_count", len(achievements)),
|
||||||
|
zap.Int("limit", limit),
|
||||||
|
)
|
||||||
|
|
||||||
|
utils.RespondWithJSON(w, http.StatusOK, achievements)
|
||||||
|
}
|
||||||
|
|
||||||
|
// VerifyAchievement подтверждает достижение пользователя
|
||||||
|
func (h *UserAchievementHandler) VerifyAchievement(w http.ResponseWriter, r *http.Request) {
|
||||||
|
h.logger.Info("handling verify achievement 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 achievement failed - authentication required")
|
||||||
|
utils.RespondWithError(w, http.StatusUnauthorized, "Authentication required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Получаем ID достижения из URL параметров
|
||||||
|
achievementIDStr := r.URL.Query().Get("id")
|
||||||
|
if achievementIDStr == "" {
|
||||||
|
utils.RespondWithError(w, http.StatusBadRequest, "Achievement ID is required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
achievementID, err := strconv.ParseUint(achievementIDStr, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
utils.RespondWithError(w, http.StatusBadRequest, "Invalid achievement ID")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = h.achievementService.VerifyAchievement(uint(achievementID), user.ID)
|
||||||
|
if err != nil {
|
||||||
|
h.logger.Error("failed to verify achievement",
|
||||||
|
zap.Uint("user_id", user.ID),
|
||||||
|
zap.Uint("achievement_id", uint(achievementID)),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
if err == service.ErrAchievementNotFound {
|
||||||
|
utils.RespondWithError(w, http.StatusNotFound, "Achievement not found")
|
||||||
|
} else {
|
||||||
|
utils.RespondWithError(w, http.StatusInternalServerError, "Failed to verify achievement: "+err.Error())
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h.logger.Info("achievement verified successfully",
|
||||||
|
zap.Uint("user_id", user.ID),
|
||||||
|
zap.Uint("achievement_id", uint(achievementID)),
|
||||||
|
)
|
||||||
|
|
||||||
|
utils.RespondWithJSON(w, http.StatusOK, map[string]string{
|
||||||
|
"message": "Achievement verified successfully",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteAchievement удаляет достижение пользователя
|
||||||
|
func (h *UserAchievementHandler) DeleteAchievement(w http.ResponseWriter, r *http.Request) {
|
||||||
|
h.logger.Info("handling delete achievement 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 achievement failed - authentication required")
|
||||||
|
utils.RespondWithError(w, http.StatusUnauthorized, "Authentication required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Получаем ID достижения из URL параметров
|
||||||
|
achievementIDStr := r.URL.Query().Get("id")
|
||||||
|
if achievementIDStr == "" {
|
||||||
|
utils.RespondWithError(w, http.StatusBadRequest, "Achievement ID is required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
achievementID, err := strconv.ParseUint(achievementIDStr, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
utils.RespondWithError(w, http.StatusBadRequest, "Invalid achievement ID")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = h.achievementService.DeleteAchievement(uint(achievementID), user.ID)
|
||||||
|
if err != nil {
|
||||||
|
h.logger.Error("failed to delete achievement",
|
||||||
|
zap.Uint("user_id", user.ID),
|
||||||
|
zap.Uint("achievement_id", uint(achievementID)),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
if err == service.ErrAchievementNotFound {
|
||||||
|
utils.RespondWithError(w, http.StatusNotFound, "Achievement not found")
|
||||||
|
} else {
|
||||||
|
utils.RespondWithError(w, http.StatusInternalServerError, "Failed to delete achievement: "+err.Error())
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h.logger.Info("achievement deleted successfully",
|
||||||
|
zap.Uint("user_id", user.ID),
|
||||||
|
zap.Uint("achievement_id", uint(achievementID)),
|
||||||
|
)
|
||||||
|
|
||||||
|
utils.RespondWithJSON(w, http.StatusOK, map[string]string{
|
||||||
|
"message": "Achievement deleted successfully",
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -26,7 +26,7 @@ func SetupRouter(db *gorm.DB, config *config.Config) http.Handler {
|
|||||||
// handler
|
// handler
|
||||||
allHandler := handlers.NewHandler(db, config)
|
allHandler := handlers.NewHandler(db, config)
|
||||||
|
|
||||||
// Serve static files (avatars) - ДОБАВЬТЕ ЭТО
|
// Serve static files (avatars)
|
||||||
r.Handle("/uploads/*", http.StripPrefix("/uploads/",
|
r.Handle("/uploads/*", http.StripPrefix("/uploads/",
|
||||||
http.FileServer(http.Dir("./uploads"))))
|
http.FileServer(http.Dir("./uploads"))))
|
||||||
|
|
||||||
@@ -69,7 +69,6 @@ func SetupRouter(db *gorm.DB, config *config.Config) http.Handler {
|
|||||||
|
|
||||||
// Все операции с аватарами теперь через AvatarHandler
|
// Все операции с аватарами теперь через AvatarHandler
|
||||||
r.Route("/avatars", func(r chi.Router) {
|
r.Route("/avatars", func(r chi.Router) {
|
||||||
|
|
||||||
r.Post("/upload", allHandler.AvatarHandler().UploadAvatar)
|
r.Post("/upload", allHandler.AvatarHandler().UploadAvatar)
|
||||||
r.Delete("/delete", allHandler.AvatarHandler().DeleteAvatar)
|
r.Delete("/delete", allHandler.AvatarHandler().DeleteAvatar)
|
||||||
r.Get("/{filename}", allHandler.AvatarHandler().GetAvatar)
|
r.Get("/{filename}", allHandler.AvatarHandler().GetAvatar)
|
||||||
@@ -84,7 +83,7 @@ func SetupRouter(db *gorm.DB, config *config.Config) http.Handler {
|
|||||||
r.Post("/monthly/reset", allHandler.UserStatsHandler().ResetMonthlyDistance)
|
r.Post("/monthly/reset", allHandler.UserStatsHandler().ResetMonthlyDistance)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Маршруты для тренировок
|
// Маршруты для тренировок
|
||||||
r.Route("/workouts", func(r chi.Router) {
|
r.Route("/workouts", func(r chi.Router) {
|
||||||
r.Post("/", allHandler.UserWorkoutHandler().CreateWorkout)
|
r.Post("/", allHandler.UserWorkoutHandler().CreateWorkout)
|
||||||
r.Get("/", allHandler.UserWorkoutHandler().GetWorkouts)
|
r.Get("/", allHandler.UserWorkoutHandler().GetWorkouts)
|
||||||
@@ -97,7 +96,39 @@ func SetupRouter(db *gorm.DB, config *config.Config) http.Handler {
|
|||||||
r.Delete("/", allHandler.UserWorkoutHandler().DeleteWorkout)
|
r.Delete("/", allHandler.UserWorkoutHandler().DeleteWorkout)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
// Здесь будут другие защищенные маршруты пользователя
|
|
||||||
|
// Маршруты для достижений (achievements)
|
||||||
|
r.Route("/achievements", func(r chi.Router) {
|
||||||
|
// Создание нового достижения
|
||||||
|
r.Post("/", allHandler.UserAchievementHandler().CreateAchievement)
|
||||||
|
|
||||||
|
// Получение всех достижений пользователя
|
||||||
|
r.Get("/", allHandler.UserAchievementHandler().GetUserAchievements)
|
||||||
|
|
||||||
|
// Получение сводки по достижениям
|
||||||
|
r.Get("/summary", allHandler.UserAchievementHandler().GetUserAchievementsSummary)
|
||||||
|
|
||||||
|
// Получение последних достижений (с опциональным лимитом)
|
||||||
|
r.Get("/recent", allHandler.UserAchievementHandler().GetRecentAchievements)
|
||||||
|
|
||||||
|
// Получение достижений по типу
|
||||||
|
r.Get("/type/{type}", allHandler.UserAchievementHandler().GetAchievementsByType)
|
||||||
|
|
||||||
|
// Операции с конкретным достижением
|
||||||
|
r.Route("/{id}", func(r chi.Router) {
|
||||||
|
// Получение достижения по ID
|
||||||
|
r.Get("/", allHandler.UserAchievementHandler().GetAchievementByID)
|
||||||
|
|
||||||
|
// Обновление достижения
|
||||||
|
r.Put("/", allHandler.UserAchievementHandler().UpdateAchievement)
|
||||||
|
|
||||||
|
// Удаление достижения
|
||||||
|
r.Delete("/", allHandler.UserAchievementHandler().DeleteAchievement)
|
||||||
|
|
||||||
|
// Подтверждение достижения
|
||||||
|
r.Patch("/verify", allHandler.UserAchievementHandler().VerifyAchievement)
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
r.Route("/news", func(r chi.Router) {
|
r.Route("/news", func(r chi.Router) {
|
||||||
@@ -144,9 +175,14 @@ func SetupRouter(db *gorm.DB, config *config.Config) http.Handler {
|
|||||||
r.Delete("/{id}", allHandler.ReviewHandler().DeleteReview)
|
r.Delete("/{id}", allHandler.ReviewHandler().DeleteReview)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
// Здесь будут добавлены другие маршруты:
|
|
||||||
// r.Mount("/events", eventHandler.Routes())
|
// Публичные маршруты для достижений (если нужны)
|
||||||
// r.Mount("/reviews", reviewHandler.Routes())
|
r.Route("/achievements", func(r chi.Router) {
|
||||||
|
// Публичные маршруты для просмотра достижений других пользователей
|
||||||
|
r.Get("/user/{userID}", allHandler.UserAchievementHandler().GetPublicUserAchievements)
|
||||||
|
r.Get("/user/{userID}/summary", allHandler.UserAchievementHandler().GetPublicUserAchievementsSummary)
|
||||||
|
r.Get("/user/{userID}/recent", allHandler.UserAchievementHandler().GetPublicRecentAchievements)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// Логируем все зарегистрированные маршруты
|
// Логируем все зарегистрированные маршруты
|
||||||
@@ -154,4 +190,4 @@ func SetupRouter(db *gorm.DB, config *config.Config) http.Handler {
|
|||||||
routeLogger.LogRoutes(r)
|
routeLogger.LogRoutes(r)
|
||||||
|
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
// service/achievement_service.go
|
// service/achievement_service.go (дополнение)
|
||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -47,6 +47,29 @@ func (s *AchievementService) CreateAchievement(userID uint, req models.Achieveme
|
|||||||
return achievement, nil
|
return achievement, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetVerifiedAchievements возвращает только подтвержденные достижения пользователя
|
||||||
|
func (s *AchievementService) GetVerifiedAchievements(userID uint) ([]models.Achievement, error) {
|
||||||
|
return s.achievementRepo.GetVerifiedByUserID(userID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetVerifiedRecentAchievements возвращает последние подтвержденные достижения
|
||||||
|
func (s *AchievementService) GetVerifiedRecentAchievements(userID uint, limit int) ([]models.Achievement, error) {
|
||||||
|
achievements, err := s.achievementRepo.GetRecentAchievements(userID, limit)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Фильтруем только подтвержденные
|
||||||
|
var verified []models.Achievement
|
||||||
|
for _, achievement := range achievements {
|
||||||
|
if achievement.Verified {
|
||||||
|
verified = append(verified, achievement)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return verified, nil
|
||||||
|
}
|
||||||
|
|
||||||
// GetUserAchievements возвращает все достижения пользователя
|
// GetUserAchievements возвращает все достижения пользователя
|
||||||
func (s *AchievementService) GetUserAchievements(userID uint) ([]models.Achievement, error) {
|
func (s *AchievementService) GetUserAchievements(userID uint) ([]models.Achievement, error) {
|
||||||
return s.achievementRepo.GetByUserID(userID)
|
return s.achievementRepo.GetByUserID(userID)
|
||||||
@@ -97,8 +120,62 @@ func (s *AchievementService) DeleteAchievement(achievementID uint, userID uint)
|
|||||||
return s.achievementRepo.Delete(achievementID)
|
return s.achievementRepo.Delete(achievementID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetAchievementByID возвращает достижение по ID
|
||||||
|
func (s *AchievementService) GetAchievementByID(achievementID uint, userID uint) (*models.Achievement, error) {
|
||||||
|
achievement, err := s.achievementRepo.GetByID(achievementID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверяем, что достижение принадлежит пользователю
|
||||||
|
if achievement.UserID != userID {
|
||||||
|
return nil, ErrAchievementNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
return achievement, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateAchievement обновляет достижение
|
||||||
|
func (s *AchievementService) UpdateAchievement(achievementID uint, userID uint, req models.AchievementCreateRequest) (*models.Achievement, error) {
|
||||||
|
// Проверяем, что достижение принадлежит пользователю
|
||||||
|
existingAchievement, err := s.achievementRepo.GetByID(achievementID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if existingAchievement.UserID != userID {
|
||||||
|
return nil, ErrAchievementNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверяем, нет ли другого достижения с таким названием
|
||||||
|
if existingAchievement.Title != req.Title {
|
||||||
|
exists, err := s.achievementRepo.ExistsByTitleAndUser(userID, req.Title)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if exists {
|
||||||
|
return nil, ErrAchievementAlreadyExists
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Обновляем данные
|
||||||
|
existingAchievement.Type = req.Type
|
||||||
|
existingAchievement.Title = req.Title
|
||||||
|
existingAchievement.Description = req.Description
|
||||||
|
existingAchievement.Result = req.Result
|
||||||
|
existingAchievement.Distance = req.Distance
|
||||||
|
existingAchievement.Date = req.Date
|
||||||
|
existingAchievement.BadgeImage = req.BadgeImage
|
||||||
|
|
||||||
|
if err := s.achievementRepo.Update(existingAchievement); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return existingAchievement, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Ошибки
|
// Ошибки
|
||||||
var (
|
var (
|
||||||
ErrAchievementAlreadyExists = errors.New("achievement with this title already exists")
|
ErrAchievementAlreadyExists = errors.New("achievement with this title already exists")
|
||||||
ErrAchievementNotFound = errors.New("achievement not found")
|
ErrAchievementNotFound = errors.New("achievement not found")
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -359,3 +359,40 @@ func LogValidationErrors(logger *zap.Logger, err error, context string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ParseUintFromQuery парсит uint из query параметра
|
||||||
|
func ParseUintFromQuery(queryParam string, defaultValue uint) (uint, error) {
|
||||||
|
if queryParam == "" {
|
||||||
|
return defaultValue, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
value, err := strconv.ParseUint(queryParam, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
return defaultValue, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return uint(value), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseIntFromQuery парсит int из query параметра
|
||||||
|
func ParseIntFromQuery(queryParam string, defaultValue int) (int, error) {
|
||||||
|
if queryParam == "" {
|
||||||
|
return defaultValue, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
value, err := strconv.Atoi(queryParam)
|
||||||
|
if err != nil {
|
||||||
|
return defaultValue, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return value, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseBoolFromQuery парсит bool из query параметра
|
||||||
|
func ParseBoolFromQuery(queryParam string, defaultValue bool) bool {
|
||||||
|
if queryParam == "" {
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.ToLower(queryParam) == "true" || queryParam == "1"
|
||||||
|
}
|
||||||
|
|||||||
@@ -383,6 +383,7 @@ export default {
|
|||||||
|
|
||||||
.btn-primary {
|
.btn-primary {
|
||||||
border: 2px solid;
|
border: 2px solid;
|
||||||
|
background-color: #ffea00;
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
box-shadow: 0 4px 15px rgba(255, 215, 0, 0.3);
|
box-shadow: 0 4px 15px rgba(255, 215, 0, 0.3);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user