Files
tp/main_dc/api_bb/internal/handlers/user_stats_handler.go
T

348 lines
11 KiB
Go

// handlers/user_stats_handler.go
package handlers
import (
"net/http"
"strconv"
"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 UserStatsHandler struct {
logger logger.LoggerInterface
userStatsService service.UserStatsService
}
func NewUserStatsHandler(userStatsService service.UserStatsService) *UserStatsHandler {
return &UserStatsHandler{
logger: logger.NewWrapper(logger.Get().With(zap.String("handler", "user_stats"))),
userStatsService: userStatsService,
}
}
// GetUserStats возвращает статистику текущего пользователя
func (h *UserStatsHandler) GetUserStats(w http.ResponseWriter, r *http.Request) {
h.logger.Info("handling get user stats 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 user stats failed - authentication required")
utils.RespondWithError(w, http.StatusUnauthorized, "Authentication required")
return
}
// Получаем статистику через сервис
stats, err := h.userStatsService.GetUserStats(user.ID)
if err != nil {
h.logger.Error("failed to get user stats from service",
zap.Uint("user_id", user.ID),
zap.Error(err),
)
utils.RespondWithError(w, http.StatusInternalServerError, "Failed to get user stats: "+err.Error())
return
}
h.logger.Info("user stats retrieved successfully",
zap.Uint("user_id", user.ID),
zap.Float64("total_distance", stats.TotalDistance),
zap.Int("workouts_count", stats.WorkoutsCount),
)
utils.RespondWithJSON(w, http.StatusOK, stats)
}
// GetUserStatsByID возвращает статистику пользователя по ID (для администраторов)
func (h *UserStatsHandler) GetUserStatsByID(w http.ResponseWriter, r *http.Request) {
h.logger.Info("handling get user stats by ID request",
zap.String("method", r.Method),
zap.String("path", r.URL.Path),
zap.String("remote_addr", r.RemoteAddr),
)
// Получаем текущего пользователя для проверки прав
currentUser, ok := middleware.GetUserFromContext(r.Context())
if !ok {
h.logger.Warn("get user stats by ID failed - authentication required")
utils.RespondWithError(w, http.StatusUnauthorized, "Authentication required")
return
}
// Проверяем права администратора
if currentUser.Role != "admin" {
h.logger.Warn("get user stats by ID failed - insufficient permissions",
zap.Uint("user_id", currentUser.ID),
zap.String("role", currentUser.Role),
)
utils.RespondWithError(w, http.StatusForbidden, "Insufficient permissions")
return
}
// Получаем ID пользователя из параметров URL
userIDStr := chi.URLParam(r, "userID")
userID, err := strconv.ParseUint(userIDStr, 10, 32)
if err != nil {
h.logger.Warn("invalid user ID parameter",
zap.String("user_id_param", userIDStr),
zap.Error(err),
)
utils.RespondWithError(w, http.StatusBadRequest, "Invalid user ID")
return
}
// Получаем статистику через сервис
stats, err := h.userStatsService.GetUserStats(uint(userID))
if err != nil {
h.logger.Error("failed to get user stats by ID from service",
zap.Uint("target_user_id", uint(userID)),
zap.Error(err),
)
utils.RespondWithError(w, http.StatusInternalServerError, "Failed to get user stats: "+err.Error())
return
}
h.logger.Info("user stats by ID retrieved successfully",
zap.Uint("admin_user_id", currentUser.ID),
zap.Uint("target_user_id", uint(userID)),
)
utils.RespondWithJSON(w, http.StatusOK, stats)
}
// UpdatePersonalBest обновляет личный рекорд пользователя
func (h *UserStatsHandler) 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
}
var req struct {
DistanceType string `json:"distance_type"`
Time string `json:"time"`
}
if err := utils.DecodeJSONBody(w, r, &req); err != nil {
h.logger.Error("failed to decode update personal best request",
zap.Error(err),
)
utils.RespondWithError(w, http.StatusBadRequest, "Invalid request payload: "+err.Error())
return
}
// Валидация обязательных полей
if req.DistanceType == "" || req.Time == "" {
h.logger.Warn("update personal best failed - missing required fields")
utils.RespondWithError(w, http.StatusBadRequest, "Distance type and time are required")
return
}
// Валидация типа дистанции
validDistanceTypes := map[string]bool{
"5k": true, "10k": true, "half": true, "marathon": true,
}
if !validDistanceTypes[req.DistanceType] {
h.logger.Warn("update personal best failed - invalid distance type",
zap.String("distance_type", req.DistanceType),
)
utils.RespondWithError(w, http.StatusBadRequest, "Invalid distance type. Must be: 5k, 10k, half, marathon")
return
}
h.logger.Info("updating personal best",
zap.Uint("user_id", user.ID),
zap.String("distance_type", req.DistanceType),
zap.String("time", req.Time),
)
// Обновляем личный рекорд через сервис
if err := h.userStatsService.UpdatePersonalBest(user.ID, req.DistanceType, req.Time); err != nil {
h.logger.Error("failed to update personal best in service",
zap.Uint("user_id", user.ID),
zap.Error(err),
)
utils.RespondWithError(w, http.StatusInternalServerError, "Failed to update personal best: "+err.Error())
return
}
h.logger.Info("personal best updated successfully",
zap.Uint("user_id", user.ID),
zap.String("distance_type", req.DistanceType),
)
utils.RespondWithJSON(w, http.StatusOK, map[string]interface{}{
"message": "Personal best updated successfully",
"distance_type": req.DistanceType,
"time": req.Time,
})
}
// IncrementWorkout увеличивает счетчик тренировок и обновляет статистику
func (h *UserStatsHandler) IncrementWorkout(w http.ResponseWriter, r *http.Request) {
h.logger.Info("handling increment workout 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("increment workout failed - authentication required")
utils.RespondWithError(w, http.StatusUnauthorized, "Authentication required")
return
}
var req struct {
Distance float64 `json:"distance"`
Duration int `json:"duration"`
}
if err := utils.DecodeJSONBody(w, r, &req); err != nil {
h.logger.Error("failed to decode increment workout request",
zap.Error(err),
)
utils.RespondWithError(w, http.StatusBadRequest, "Invalid request payload: "+err.Error())
return
}
// Валидация данных тренировки
if req.Distance <= 0 {
h.logger.Warn("increment workout failed - invalid distance",
zap.Float64("distance", req.Distance),
)
utils.RespondWithError(w, http.StatusBadRequest, "Distance must be greater than 0")
return
}
if req.Duration <= 0 {
h.logger.Warn("increment workout failed - invalid duration",
zap.Int("duration", req.Duration),
)
utils.RespondWithError(w, http.StatusBadRequest, "Duration must be greater than 0")
return
}
h.logger.Info("incrementing workout stats",
zap.Uint("user_id", user.ID),
zap.Float64("distance", req.Distance),
zap.Int("duration", req.Duration),
)
// Обновляем статистику через сервис
if err := h.userStatsService.IncrementWorkout(user.ID, req.Distance, req.Duration); err != nil {
h.logger.Error("failed to increment workout in service",
zap.Uint("user_id", user.ID),
zap.Error(err),
)
utils.RespondWithError(w, http.StatusInternalServerError, "Failed to update workout stats: "+err.Error())
return
}
h.logger.Info("workout stats incremented successfully",
zap.Uint("user_id", user.ID),
zap.Float64("distance", req.Distance),
zap.Int("duration", req.Duration),
)
utils.RespondWithJSON(w, http.StatusOK, map[string]interface{}{
"message": "Workout stats updated successfully",
"distance": req.Distance,
"duration": req.Duration,
})
}
// ResetWeeklyDistance сбрасывает недельный пробег
func (h *UserStatsHandler) ResetWeeklyDistance(w http.ResponseWriter, r *http.Request) {
h.logger.Info("handling reset weekly 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("reset weekly distance failed - authentication required")
utils.RespondWithError(w, http.StatusUnauthorized, "Authentication required")
return
}
h.logger.Info("resetting weekly distance",
zap.Uint("user_id", user.ID),
)
// Сбрасываем недельный пробег через сервис
if err := h.userStatsService.ResetWeeklyDistance(user.ID); err != nil {
h.logger.Error("failed to reset weekly distance in service",
zap.Uint("user_id", user.ID),
zap.Error(err),
)
utils.RespondWithError(w, http.StatusInternalServerError, "Failed to reset weekly distance: "+err.Error())
return
}
h.logger.Info("weekly distance reset successfully",
zap.Uint("user_id", user.ID),
)
utils.RespondWithJSON(w, http.StatusOK, map[string]interface{}{
"message": "Weekly distance reset successfully",
})
}
// ResetMonthlyDistance сбрасывает месячный пробег
func (h *UserStatsHandler) ResetMonthlyDistance(w http.ResponseWriter, r *http.Request) {
h.logger.Info("handling reset monthly 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("reset monthly distance failed - authentication required")
utils.RespondWithError(w, http.StatusUnauthorized, "Authentication required")
return
}
h.logger.Info("resetting monthly distance",
zap.Uint("user_id", user.ID),
)
// Сбрасываем месячный пробег через сервис
if err := h.userStatsService.ResetMonthlyDistance(user.ID); err != nil {
h.logger.Error("failed to reset monthly distance in service",
zap.Uint("user_id", user.ID),
zap.Error(err),
)
utils.RespondWithError(w, http.StatusInternalServerError, "Failed to reset monthly distance: "+err.Error())
return
}
h.logger.Info("monthly distance reset successfully",
zap.Uint("user_id", user.ID),
)
utils.RespondWithJSON(w, http.StatusOK, map[string]interface{}{
"message": "Monthly distance reset successfully",
})
}