// 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), ) switch err { case service.ErrAchievementNotFound: utils.RespondWithError(w, http.StatusNotFound, "Achievement not found") case service.ErrAchievementAlreadyExists: utils.RespondWithError(w, http.StatusConflict, "Achievement with this title already exists") default: 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", }) }