new file: "serv_nginx/api_bb/internal/handlers/training_plan_handler\321\216\320\277\321\211"
modified: serv_nginx/api_bb/internal/models/training_plan.go new file: serv_nginx/api_bb/internal/service/training_plan_service.go create CRUD for traing_plan
This commit is contained in:
@@ -0,0 +1,557 @@
|
|||||||
|
// handlers/training_plan_handler.go
|
||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"api_bb/internal/models"
|
||||||
|
"api_bb/internal/service"
|
||||||
|
"api_bb/pkg/logger"
|
||||||
|
"api_bb/pkg/middleware"
|
||||||
|
"api_bb/pkg/utils"
|
||||||
|
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TrainingPlanHandler struct {
|
||||||
|
logger logger.LoggerInterface
|
||||||
|
trainingPlanService service.TrainingPlanService
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTrainingPlanHandler(trainingPlanService service.TrainingPlanService) *TrainingPlanHandler {
|
||||||
|
return &TrainingPlanHandler{
|
||||||
|
logger: logger.NewWrapper(logger.Get().With(zap.String("handler", "training_plan"))),
|
||||||
|
trainingPlanService: trainingPlanService,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TrainingPlanResponse - DTO для ответа с планом тренировок
|
||||||
|
type TrainingPlanResponse struct {
|
||||||
|
ID uint `json:"id"`
|
||||||
|
UserID uint `json:"user_id"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
Weeks int `json:"weeks"`
|
||||||
|
WorkoutsPerWeek int `json:"workouts_per_week"`
|
||||||
|
TargetDistance string `json:"target_distance"`
|
||||||
|
TargetDate time.Time `json:"target_date"`
|
||||||
|
CurrentWeek int `json:"current_week"`
|
||||||
|
Completed bool `json:"completed"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
Workouts []TrainingWorkoutResponse `json:"workouts,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TrainingWorkoutResponse - DTO для ответа с тренировкой плана
|
||||||
|
type TrainingWorkoutResponse struct {
|
||||||
|
ID uint `json:"id"`
|
||||||
|
PlanID uint `json:"plan_id"`
|
||||||
|
Week int `json:"week"`
|
||||||
|
Day int `json:"day"`
|
||||||
|
Type models.WorkoutType `json:"type"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
Distance float64 `json:"distance_km"`
|
||||||
|
Duration int `json:"duration_min"`
|
||||||
|
Completed bool `json:"completed"`
|
||||||
|
CompletedAt *time.Time `json:"completed_at"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateTrainingPlan создает новый план тренировок
|
||||||
|
func (h *TrainingPlanHandler) CreateTrainingPlan(w http.ResponseWriter, r *http.Request) {
|
||||||
|
h.logger.Info("handling create training plan 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 training plan failed - authentication required")
|
||||||
|
utils.RespondWithError(w, http.StatusUnauthorized, "Authentication required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var req models.TrainingPlanCreateRequest
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
h.logger.Debug("creating training plan",
|
||||||
|
zap.Uint("user_id", user.ID),
|
||||||
|
zap.String("title", req.Title),
|
||||||
|
zap.Int("weeks", req.Weeks),
|
||||||
|
zap.Int("workouts_per_week", req.WorkoutsPerWeek),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Создаем план тренировок через сервис
|
||||||
|
plan, err := h.trainingPlanService.CreateTrainingPlan(user.ID, &req)
|
||||||
|
if err != nil {
|
||||||
|
h.logger.Error("failed to create training plan in service",
|
||||||
|
zap.Uint("user_id", user.ID),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
utils.RespondWithError(w, http.StatusInternalServerError, "Failed to create training plan: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h.logger.Info("training plan created successfully",
|
||||||
|
zap.Uint("user_id", user.ID),
|
||||||
|
zap.Uint("plan_id", plan.ID),
|
||||||
|
)
|
||||||
|
|
||||||
|
utils.RespondWithJSON(w, http.StatusCreated, map[string]interface{}{
|
||||||
|
"message": "Training plan created successfully",
|
||||||
|
"plan": toTrainingPlanResponse(plan),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTrainingPlans возвращает все планы тренировок пользователя
|
||||||
|
func (h *TrainingPlanHandler) GetTrainingPlans(w http.ResponseWriter, r *http.Request) {
|
||||||
|
h.logger.Debug("handling get training plans 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 training plans failed - authentication required")
|
||||||
|
utils.RespondWithError(w, http.StatusUnauthorized, "Authentication required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h.logger.Debug("getting training plans for user", zap.Uint("user_id", user.ID))
|
||||||
|
|
||||||
|
// Получаем планы тренировок через сервис
|
||||||
|
plans, err := h.trainingPlanService.GetTrainingPlansByUserID(user.ID)
|
||||||
|
if err != nil {
|
||||||
|
h.logger.Error("failed to get training plans from service",
|
||||||
|
zap.Uint("user_id", user.ID),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
utils.RespondWithError(w, http.StatusInternalServerError, "Failed to get training plans: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Преобразуем в response формат
|
||||||
|
var planResponses []TrainingPlanResponse
|
||||||
|
for _, plan := range plans {
|
||||||
|
planResponses = append(planResponses, toTrainingPlanResponse(&plan))
|
||||||
|
}
|
||||||
|
|
||||||
|
h.logger.Debug("training plans retrieved successfully",
|
||||||
|
zap.Uint("user_id", user.ID),
|
||||||
|
zap.Int("plans_count", len(planResponses)),
|
||||||
|
)
|
||||||
|
|
||||||
|
utils.RespondWithJSON(w, http.StatusOK, planResponses)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTrainingPlanByID возвращает план тренировок по ID
|
||||||
|
func (h *TrainingPlanHandler) GetTrainingPlanByID(w http.ResponseWriter, r *http.Request) {
|
||||||
|
h.logger.Debug("handling get training plan 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 training plan failed - authentication required")
|
||||||
|
utils.RespondWithError(w, http.StatusUnauthorized, "Authentication required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Извлекаем ID плана из URL параметров
|
||||||
|
planIDStr := r.URL.Query().Get("id")
|
||||||
|
if planIDStr == "" {
|
||||||
|
h.logger.Warn("get training plan failed - plan ID required")
|
||||||
|
utils.RespondWithError(w, http.StatusBadRequest, "Plan ID is required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
planID, err := strconv.ParseUint(planIDStr, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
h.logger.Warn("get training plan failed - invalid plan ID",
|
||||||
|
zap.String("plan_id", planIDStr),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
utils.RespondWithError(w, http.StatusBadRequest, "Invalid plan ID")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h.logger.Debug("getting training plan by ID",
|
||||||
|
zap.Uint("user_id", user.ID),
|
||||||
|
zap.Uint("plan_id", uint(planID)),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Получаем план тренировок через сервис
|
||||||
|
plan, err := h.trainingPlanService.GetTrainingPlanByID(user.ID, uint(planID))
|
||||||
|
if err != nil {
|
||||||
|
h.logger.Error("failed to get training plan from service",
|
||||||
|
zap.Uint("user_id", user.ID),
|
||||||
|
zap.Uint("plan_id", uint(planID)),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
utils.RespondWithError(w, http.StatusInternalServerError, "Failed to get training plan: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h.logger.Debug("training plan retrieved successfully",
|
||||||
|
zap.Uint("user_id", user.ID),
|
||||||
|
zap.Uint("plan_id", uint(planID)),
|
||||||
|
)
|
||||||
|
|
||||||
|
utils.RespondWithJSON(w, http.StatusOK, toTrainingPlanResponse(plan))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateTrainingPlan обновляет план тренировок
|
||||||
|
func (h *TrainingPlanHandler) UpdateTrainingPlan(w http.ResponseWriter, r *http.Request) {
|
||||||
|
h.logger.Info("handling update training plan 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 training plan failed - authentication required")
|
||||||
|
utils.RespondWithError(w, http.StatusUnauthorized, "Authentication required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Извлекаем ID плана из URL параметров
|
||||||
|
planIDStr := r.URL.Query().Get("id")
|
||||||
|
if planIDStr == "" {
|
||||||
|
h.logger.Warn("update training plan failed - plan ID required")
|
||||||
|
utils.RespondWithError(w, http.StatusBadRequest, "Plan ID is required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
planID, err := strconv.ParseUint(planIDStr, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
h.logger.Warn("update training plan failed - invalid plan ID",
|
||||||
|
zap.String("plan_id", planIDStr),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
utils.RespondWithError(w, http.StatusBadRequest, "Invalid plan ID")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var req models.TrainingPlanUpdateRequest
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
h.logger.Info("updating training plan",
|
||||||
|
zap.Uint("user_id", user.ID),
|
||||||
|
zap.Uint("plan_id", uint(planID)),
|
||||||
|
zap.String("title", req.Title),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Обновляем план тренировок через сервис
|
||||||
|
plan, err := h.trainingPlanService.UpdateTrainingPlan(user.ID, uint(planID), &req)
|
||||||
|
if err != nil {
|
||||||
|
h.logger.Error("failed to update training plan in service",
|
||||||
|
zap.Uint("user_id", user.ID),
|
||||||
|
zap.Uint("plan_id", uint(planID)),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
utils.RespondWithError(w, http.StatusInternalServerError, "Failed to update training plan: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h.logger.Info("training plan updated successfully",
|
||||||
|
zap.Uint("user_id", user.ID),
|
||||||
|
zap.Uint("plan_id", uint(planID)),
|
||||||
|
)
|
||||||
|
|
||||||
|
utils.RespondWithJSON(w, http.StatusOK, map[string]interface{}{
|
||||||
|
"message": "Training plan updated successfully",
|
||||||
|
"plan": toTrainingPlanResponse(plan),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteTrainingPlan удаляет план тренировок
|
||||||
|
func (h *TrainingPlanHandler) DeleteTrainingPlan(w http.ResponseWriter, r *http.Request) {
|
||||||
|
h.logger.Info("handling delete training plan 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 training plan failed - authentication required")
|
||||||
|
utils.RespondWithError(w, http.StatusUnauthorized, "Authentication required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Извлекаем ID плана из URL параметров
|
||||||
|
planIDStr := r.URL.Query().Get("id")
|
||||||
|
if planIDStr == "" {
|
||||||
|
h.logger.Warn("delete training plan failed - plan ID required")
|
||||||
|
utils.RespondWithError(w, http.StatusBadRequest, "Plan ID is required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
planID, err := strconv.ParseUint(planIDStr, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
h.logger.Warn("delete training plan failed - invalid plan ID",
|
||||||
|
zap.String("plan_id", planIDStr),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
utils.RespondWithError(w, http.StatusBadRequest, "Invalid plan ID")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h.logger.Info("deleting training plan",
|
||||||
|
zap.Uint("user_id", user.ID),
|
||||||
|
zap.Uint("plan_id", uint(planID)),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Удаляем план тренировок через сервис
|
||||||
|
if err := h.trainingPlanService.DeleteTrainingPlan(user.ID, uint(planID)); err != nil {
|
||||||
|
h.logger.Error("failed to delete training plan in service",
|
||||||
|
zap.Uint("user_id", user.ID),
|
||||||
|
zap.Uint("plan_id", uint(planID)),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
utils.RespondWithError(w, http.StatusInternalServerError, "Failed to delete training plan: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h.logger.Info("training plan deleted successfully",
|
||||||
|
zap.Uint("user_id", user.ID),
|
||||||
|
zap.Uint("plan_id", uint(planID)),
|
||||||
|
)
|
||||||
|
|
||||||
|
utils.RespondWithJSON(w, http.StatusOK, map[string]interface{}{
|
||||||
|
"message": "Training plan deleted successfully",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetActiveTrainingPlan возвращает активный план тренировок пользователя
|
||||||
|
func (h *TrainingPlanHandler) GetActiveTrainingPlan(w http.ResponseWriter, r *http.Request) {
|
||||||
|
h.logger.Debug("handling get active training plan 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 active training plan failed - authentication required")
|
||||||
|
utils.RespondWithError(w, http.StatusUnauthorized, "Authentication required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h.logger.Debug("getting active training plan for user", zap.Uint("user_id", user.ID))
|
||||||
|
|
||||||
|
// Получаем активный план тренировок через сервис
|
||||||
|
plan, err := h.trainingPlanService.GetActiveTrainingPlan(user.ID)
|
||||||
|
if err != nil {
|
||||||
|
h.logger.Error("failed to get active training plan from service",
|
||||||
|
zap.Uint("user_id", user.ID),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
utils.RespondWithError(w, http.StatusInternalServerError, "Failed to get active training plan: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h.logger.Debug("active training plan retrieved successfully",
|
||||||
|
zap.Uint("user_id", user.ID),
|
||||||
|
zap.Uint("plan_id", plan.ID),
|
||||||
|
)
|
||||||
|
|
||||||
|
utils.RespondWithJSON(w, http.StatusOK, toTrainingPlanResponse(plan))
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarkTrainingPlanAsCompleted помечает план тренировок как завершенный
|
||||||
|
func (h *TrainingPlanHandler) MarkTrainingPlanAsCompleted(w http.ResponseWriter, r *http.Request) {
|
||||||
|
h.logger.Info("handling mark training plan as completed 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("mark training plan as completed failed - authentication required")
|
||||||
|
utils.RespondWithError(w, http.StatusUnauthorized, "Authentication required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Извлекаем ID плана из URL параметров
|
||||||
|
planIDStr := r.URL.Query().Get("id")
|
||||||
|
if planIDStr == "" {
|
||||||
|
h.logger.Warn("mark training plan as completed failed - plan ID required")
|
||||||
|
utils.RespondWithError(w, http.StatusBadRequest, "Plan ID is required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
planID, err := strconv.ParseUint(planIDStr, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
h.logger.Warn("mark training plan as completed failed - invalid plan ID",
|
||||||
|
zap.String("plan_id", planIDStr),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
utils.RespondWithError(w, http.StatusBadRequest, "Invalid plan ID")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h.logger.Info("marking training plan as completed",
|
||||||
|
zap.Uint("user_id", user.ID),
|
||||||
|
zap.Uint("plan_id", uint(planID)),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Помечаем план как завершенный через сервис
|
||||||
|
if err := h.trainingPlanService.MarkTrainingPlanAsCompleted(user.ID, uint(planID)); err != nil {
|
||||||
|
h.logger.Error("failed to mark training plan as completed in service",
|
||||||
|
zap.Uint("user_id", user.ID),
|
||||||
|
zap.Uint("plan_id", uint(planID)),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
utils.RespondWithError(w, http.StatusInternalServerError, "Failed to mark training plan as completed: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h.logger.Info("training plan marked as completed successfully",
|
||||||
|
zap.Uint("user_id", user.ID),
|
||||||
|
zap.Uint("plan_id", uint(planID)),
|
||||||
|
)
|
||||||
|
|
||||||
|
utils.RespondWithJSON(w, http.StatusOK, map[string]interface{}{
|
||||||
|
"message": "Training plan marked as completed successfully",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateCurrentWeek обновляет текущую неделю плана тренировок
|
||||||
|
func (h *TrainingPlanHandler) UpdateCurrentWeek(w http.ResponseWriter, r *http.Request) {
|
||||||
|
h.logger.Info("handling update current week 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 current week failed - authentication required")
|
||||||
|
utils.RespondWithError(w, http.StatusUnauthorized, "Authentication required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Извлекаем ID плана из URL параметров
|
||||||
|
planIDStr := r.URL.Query().Get("id")
|
||||||
|
if planIDStr == "" {
|
||||||
|
h.logger.Warn("update current week failed - plan ID required")
|
||||||
|
utils.RespondWithError(w, http.StatusBadRequest, "Plan ID is required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
planID, err := strconv.ParseUint(planIDStr, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
h.logger.Warn("update current week failed - invalid plan ID",
|
||||||
|
zap.String("plan_id", planIDStr),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
utils.RespondWithError(w, http.StatusBadRequest, "Invalid plan ID")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var req struct {
|
||||||
|
CurrentWeek int `json:"current_week" validate:"required,min=1,max=52"`
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
h.logger.Info("updating current week for training plan",
|
||||||
|
zap.Uint("user_id", user.ID),
|
||||||
|
zap.Uint("plan_id", uint(planID)),
|
||||||
|
zap.Int("current_week", req.CurrentWeek),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Обновляем текущую неделю через сервис
|
||||||
|
if err := h.trainingPlanService.UpdateCurrentWeek(user.ID, uint(planID), req.CurrentWeek); err != nil {
|
||||||
|
h.logger.Error("failed to update current week in service",
|
||||||
|
zap.Uint("user_id", user.ID),
|
||||||
|
zap.Uint("plan_id", uint(planID)),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
utils.RespondWithError(w, http.StatusInternalServerError, "Failed to update current week: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h.logger.Info("current week updated successfully",
|
||||||
|
zap.Uint("user_id", user.ID),
|
||||||
|
zap.Uint("plan_id", uint(planID)),
|
||||||
|
zap.Int("current_week", req.CurrentWeek),
|
||||||
|
)
|
||||||
|
|
||||||
|
utils.RespondWithJSON(w, http.StatusOK, map[string]interface{}{
|
||||||
|
"message": "Current week updated successfully",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Вспомогательные функции для преобразования моделей в DTO
|
||||||
|
|
||||||
|
func toTrainingPlanResponse(plan *models.TrainingPlan) TrainingPlanResponse {
|
||||||
|
response := TrainingPlanResponse{
|
||||||
|
ID: plan.ID,
|
||||||
|
UserID: plan.UserID,
|
||||||
|
Title: plan.Title,
|
||||||
|
Description: plan.Description,
|
||||||
|
Weeks: plan.Weeks,
|
||||||
|
WorkoutsPerWeek: plan.WorkoutsPerWeek,
|
||||||
|
TargetDistance: plan.TargetDistance,
|
||||||
|
TargetDate: plan.TargetDate,
|
||||||
|
CurrentWeek: plan.CurrentWeek,
|
||||||
|
Completed: plan.Completed,
|
||||||
|
CreatedAt: plan.CreatedAt,
|
||||||
|
UpdatedAt: plan.UpdatedAt,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Преобразуем тренировки, если они загружены
|
||||||
|
if plan.Workouts != nil {
|
||||||
|
for _, workout := range plan.Workouts {
|
||||||
|
response.Workouts = append(response.Workouts, toTrainingWorkoutResponse(&workout))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
|
||||||
|
func toTrainingWorkoutResponse(workout *models.TrainingWorkout) TrainingWorkoutResponse {
|
||||||
|
return TrainingWorkoutResponse{
|
||||||
|
ID: workout.ID,
|
||||||
|
PlanID: workout.PlanID,
|
||||||
|
Week: workout.Week,
|
||||||
|
Day: workout.Day,
|
||||||
|
Type: workout.Type,
|
||||||
|
Description: workout.Description,
|
||||||
|
Distance: workout.Distance,
|
||||||
|
Duration: workout.Duration,
|
||||||
|
Completed: workout.Completed,
|
||||||
|
CompletedAt: workout.CompletedAt,
|
||||||
|
CreatedAt: workout.CreatedAt,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -73,3 +73,13 @@ type TrainingPlanCreateRequest struct {
|
|||||||
TargetDistance string `json:"target_distance" validate:"max=50"`
|
TargetDistance string `json:"target_distance" validate:"max=50"`
|
||||||
TargetDate time.Time `json:"target_date"`
|
TargetDate time.Time `json:"target_date"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DTO для обновления плана тренировок
|
||||||
|
type TrainingPlanUpdateRequest struct {
|
||||||
|
Title string `json:"title" validate:"min=5,max=255"`
|
||||||
|
Description string `json:"description" validate:"max=1000"`
|
||||||
|
Weeks int `json:"weeks" validate:"min=1,max=52"`
|
||||||
|
WorkoutsPerWeek int `json:"workouts_per_week" validate:"min=1,max=7"`
|
||||||
|
TargetDistance string `json:"target_distance" validate:"max=50"`
|
||||||
|
TargetDate time.Time `json:"target_date"`
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,291 @@
|
|||||||
|
// service/training_plan_service.go
|
||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"api_bb/internal/models"
|
||||||
|
"api_bb/internal/repository"
|
||||||
|
"api_bb/pkg/logger"
|
||||||
|
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TrainingPlanService interface {
|
||||||
|
CreateTrainingPlan(userID uint, req *models.TrainingPlanCreateRequest) (*models.TrainingPlan, error)
|
||||||
|
GetTrainingPlansByUserID(userID uint) ([]models.TrainingPlan, error)
|
||||||
|
GetTrainingPlanByID(userID uint, planID uint) (*models.TrainingPlan, error)
|
||||||
|
UpdateTrainingPlan(userID uint, planID uint, req *models.TrainingPlanUpdateRequest) (*models.TrainingPlan, error)
|
||||||
|
DeleteTrainingPlan(userID uint, planID uint) error
|
||||||
|
GetActiveTrainingPlan(userID uint) (*models.TrainingPlan, error)
|
||||||
|
MarkTrainingPlanAsCompleted(userID uint, planID uint) error
|
||||||
|
UpdateCurrentWeek(userID uint, planID uint, currentWeek int) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type trainingPlanService struct {
|
||||||
|
trainingPlanRepo repository.TrainingPlanRepository
|
||||||
|
logger logger.LoggerInterface
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTrainingPlanService(trainingPlanRepo repository.TrainingPlanRepository) TrainingPlanService {
|
||||||
|
return &trainingPlanService{
|
||||||
|
trainingPlanRepo: trainingPlanRepo,
|
||||||
|
logger: logger.NewWrapper(logger.Get().With(zap.String("service", "training_plan"))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateTrainingPlan создает новый план тренировок
|
||||||
|
func (s *trainingPlanService) CreateTrainingPlan(userID uint, req *models.TrainingPlanCreateRequest) (*models.TrainingPlan, error) {
|
||||||
|
s.logger.Debug("creating training plan",
|
||||||
|
zap.Uint("user_id", userID),
|
||||||
|
zap.String("title", req.Title),
|
||||||
|
)
|
||||||
|
|
||||||
|
plan := &models.TrainingPlan{
|
||||||
|
UserID: userID,
|
||||||
|
Title: req.Title,
|
||||||
|
Description: req.Description,
|
||||||
|
Weeks: req.Weeks,
|
||||||
|
WorkoutsPerWeek: req.WorkoutsPerWeek,
|
||||||
|
TargetDistance: req.TargetDistance,
|
||||||
|
TargetDate: req.TargetDate,
|
||||||
|
CurrentWeek: 1,
|
||||||
|
Completed: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.trainingPlanRepo.Create(plan); err != nil {
|
||||||
|
s.logger.Error("failed to create training plan in repository",
|
||||||
|
zap.Uint("user_id", userID),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
s.logger.Debug("training plan created successfully",
|
||||||
|
zap.Uint("user_id", userID),
|
||||||
|
zap.Uint("plan_id", plan.ID),
|
||||||
|
)
|
||||||
|
|
||||||
|
return plan, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTrainingPlansByUserID возвращает все планы тренировок пользователя
|
||||||
|
func (s *trainingPlanService) GetTrainingPlansByUserID(userID uint) ([]models.TrainingPlan, error) {
|
||||||
|
s.logger.Debug("getting training plans for user", zap.Uint("user_id", userID))
|
||||||
|
|
||||||
|
plans, err := s.trainingPlanRepo.GetByUserID(userID)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("failed to get training plans from repository",
|
||||||
|
zap.Uint("user_id", userID),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
s.logger.Debug("training plans retrieved successfully",
|
||||||
|
zap.Uint("user_id", userID),
|
||||||
|
zap.Int("count", len(plans)),
|
||||||
|
)
|
||||||
|
|
||||||
|
return plans, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTrainingPlanByID возвращает план тренировок по ID
|
||||||
|
func (s *trainingPlanService) GetTrainingPlanByID(userID uint, planID uint) (*models.TrainingPlan, error) {
|
||||||
|
s.logger.Debug("getting training plan by ID",
|
||||||
|
zap.Uint("user_id", userID),
|
||||||
|
zap.Uint("plan_id", planID),
|
||||||
|
)
|
||||||
|
|
||||||
|
plan, err := s.trainingPlanRepo.GetByID(planID)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("failed to get training plan from repository",
|
||||||
|
zap.Uint("user_id", userID),
|
||||||
|
zap.Uint("plan_id", planID),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверяем, что план принадлежит пользователю
|
||||||
|
if plan.UserID != userID {
|
||||||
|
s.logger.Warn("training plan access denied - user mismatch",
|
||||||
|
zap.Uint("user_id", userID),
|
||||||
|
zap.Uint("plan_user_id", plan.UserID),
|
||||||
|
zap.Uint("plan_id", planID),
|
||||||
|
)
|
||||||
|
return nil, repository.ErrNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
s.logger.Debug("training plan retrieved successfully",
|
||||||
|
zap.Uint("user_id", userID),
|
||||||
|
zap.Uint("plan_id", planID),
|
||||||
|
)
|
||||||
|
|
||||||
|
return plan, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateTrainingPlan обновляет план тренировок
|
||||||
|
func (s *trainingPlanService) UpdateTrainingPlan(userID uint, planID uint, req *models.TrainingPlanUpdateRequest) (*models.TrainingPlan, error) {
|
||||||
|
s.logger.Debug("updating training plan",
|
||||||
|
zap.Uint("user_id", userID),
|
||||||
|
zap.Uint("plan_id", planID),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Сначала получаем существующий план
|
||||||
|
plan, err := s.GetTrainingPlanByID(userID, planID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Обновляем только переданные поля
|
||||||
|
if req.Title != "" {
|
||||||
|
plan.Title = req.Title
|
||||||
|
}
|
||||||
|
if req.Description != "" {
|
||||||
|
plan.Description = req.Description
|
||||||
|
}
|
||||||
|
if req.Weeks > 0 {
|
||||||
|
plan.Weeks = req.Weeks
|
||||||
|
}
|
||||||
|
if req.WorkoutsPerWeek > 0 {
|
||||||
|
plan.WorkoutsPerWeek = req.WorkoutsPerWeek
|
||||||
|
}
|
||||||
|
if req.TargetDistance != "" {
|
||||||
|
plan.TargetDistance = req.TargetDistance
|
||||||
|
}
|
||||||
|
if !req.TargetDate.IsZero() {
|
||||||
|
plan.TargetDate = req.TargetDate
|
||||||
|
}
|
||||||
|
|
||||||
|
// Сохраняем обновления
|
||||||
|
if err := s.trainingPlanRepo.Update(plan); err != nil {
|
||||||
|
s.logger.Error("failed to update training plan in repository",
|
||||||
|
zap.Uint("user_id", userID),
|
||||||
|
zap.Uint("plan_id", planID),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
s.logger.Debug("training plan updated successfully",
|
||||||
|
zap.Uint("user_id", userID),
|
||||||
|
zap.Uint("plan_id", planID),
|
||||||
|
)
|
||||||
|
|
||||||
|
return plan, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteTrainingPlan удаляет план тренировок
|
||||||
|
func (s *trainingPlanService) DeleteTrainingPlan(userID uint, planID uint) error {
|
||||||
|
s.logger.Debug("deleting training plan",
|
||||||
|
zap.Uint("user_id", userID),
|
||||||
|
zap.Uint("plan_id", planID),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Проверяем, что план существует и принадлежит пользователю
|
||||||
|
_, err := s.GetTrainingPlanByID(userID, planID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Удаляем план
|
||||||
|
if err := s.trainingPlanRepo.Delete(planID); err != nil {
|
||||||
|
s.logger.Error("failed to delete training plan from repository",
|
||||||
|
zap.Uint("user_id", userID),
|
||||||
|
zap.Uint("plan_id", planID),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
s.logger.Debug("training plan deleted successfully",
|
||||||
|
zap.Uint("user_id", userID),
|
||||||
|
zap.Uint("plan_id", planID),
|
||||||
|
)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetActiveTrainingPlan возвращает активный план тренировок пользователя
|
||||||
|
func (s *trainingPlanService) GetActiveTrainingPlan(userID uint) (*models.TrainingPlan, error) {
|
||||||
|
s.logger.Debug("getting active training plan for user", zap.Uint("user_id", userID))
|
||||||
|
|
||||||
|
plan, err := s.trainingPlanRepo.GetActivePlan(userID)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("failed to get active training plan from repository",
|
||||||
|
zap.Uint("user_id", userID),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
s.logger.Debug("active training plan retrieved successfully",
|
||||||
|
zap.Uint("user_id", userID),
|
||||||
|
zap.Uint("plan_id", plan.ID),
|
||||||
|
)
|
||||||
|
|
||||||
|
return plan, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarkTrainingPlanAsCompleted помечает план тренировок как завершенный
|
||||||
|
func (s *trainingPlanService) MarkTrainingPlanAsCompleted(userID uint, planID uint) error {
|
||||||
|
s.logger.Debug("marking training plan as completed",
|
||||||
|
zap.Uint("user_id", userID),
|
||||||
|
zap.Uint("plan_id", planID),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Проверяем, что план существует и принадлежит пользователю
|
||||||
|
_, err := s.GetTrainingPlanByID(userID, planID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Помечаем как завершенный
|
||||||
|
if err := s.trainingPlanRepo.MarkAsCompleted(planID); err != nil {
|
||||||
|
s.logger.Error("failed to mark training plan as completed in repository",
|
||||||
|
zap.Uint("user_id", userID),
|
||||||
|
zap.Uint("plan_id", planID),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
s.logger.Debug("training plan marked as completed successfully",
|
||||||
|
zap.Uint("user_id", userID),
|
||||||
|
zap.Uint("plan_id", planID),
|
||||||
|
)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateCurrentWeek обновляет текущую неделю плана тренировок
|
||||||
|
func (s *trainingPlanService) UpdateCurrentWeek(userID uint, planID uint, currentWeek int) error {
|
||||||
|
s.logger.Debug("updating current week for training plan",
|
||||||
|
zap.Uint("user_id", userID),
|
||||||
|
zap.Uint("plan_id", planID),
|
||||||
|
zap.Int("current_week", currentWeek),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Проверяем, что план существует и принадлежит пользователю
|
||||||
|
_, err := s.GetTrainingPlanByID(userID, planID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Обновляем текущую неделю
|
||||||
|
if err := s.trainingPlanRepo.UpdateCurrentWeek(planID, currentWeek); err != nil {
|
||||||
|
s.logger.Error("failed to update current week in repository",
|
||||||
|
zap.Uint("user_id", userID),
|
||||||
|
zap.Uint("plan_id", planID),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
s.logger.Debug("current week updated successfully",
|
||||||
|
zap.Uint("user_id", userID),
|
||||||
|
zap.Uint("plan_id", planID),
|
||||||
|
zap.Int("current_week", currentWeek),
|
||||||
|
)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user