package handler import ( "encoding/json" "net/http" "strconv" "time" "effective_mobile_test_go_api/internal/model" "effective_mobile_test_go_api/internal/service" "github.com/google/uuid" "github.com/gorilla/mux" "go.uber.org/zap" ) type Handler struct { log *zap.Logger service *service.SubscriptionService } func NewHandler(log *zap.Logger, service *service.SubscriptionService) *Handler { return &Handler{log: log, service: service} } type CreateSubscriptionRequest struct { ServiceName string `json:"service_name" example:"Yandex Plus"` Price int `json:"price" example:"400"` UserID string `json:"user_id" example:"60601fee-2bf1-4721-ae6f-7636e79a0cba"` StartDate string `json:"start_date" example:"07-2025"` EndDate *string `json:"end_date,omitempty" example:"12-2025"` } // CreateSubscription godoc // @Summary Создать подписку // @Tags subscriptions // @Accept json // @Produce json // @Param subscription body CreateSubscriptionRequest true "Создание подписки" // @Success 201 {object} model.Subscription // @Failure 400 {object} map[string]string // @Failure 500 {object} map[string]string // @Router /subscriptions [post] func (h *Handler) CreateSubscription(w http.ResponseWriter, r *http.Request) { var req CreateSubscriptionRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { h.log.Error("Ошибка декодирования", zap.Error(err)) http.Error(w, "Ошибка запроса", http.StatusBadRequest) return } userUUID, err := uuid.Parse(req.UserID) if err != nil { http.Error(w, "Неверный uuid пользователя", http.StatusBadRequest) return } startDate, err := time.Parse("01-2006", req.StartDate) if err != nil { http.Error(w, "Неверный формат start_date", http.StatusBadRequest) return } var endDate *time.Time if req.EndDate != nil { ed, err := time.Parse("01-2006", *req.EndDate) if err != nil { http.Error(w, "Неверный формат даты окончания", http.StatusBadRequest) return } endDate = &ed } sub := &model.Subscription{ ServiceName: req.ServiceName, Price: req.Price, UserID: userUUID, StartDate: startDate, EndDate: endDate, } if err := h.service.Create(r.Context(), sub); err != nil { h.log.Error("Подписчик не создан", zap.Error(err)) http.Error(w, "Ошибка создания", http.StatusInternalServerError) return } w.WriteHeader(http.StatusCreated) json.NewEncoder(w).Encode(sub) } // GetSubscription godoc // @Summary Получить подписку по ID // @Tags subscriptions // @Produce json // @Param id path int true "ID подписки" // @Success 200 {object} model.Subscription // @Failure 400 {object} map[string]string // @Failure 404 {object} map[string]string // @Router /subscriptions/{id} [get] func (h *Handler) GetSubscription(w http.ResponseWriter, r *http.Request) { idStr := mux.Vars(r)["id"] id, err := strconv.Atoi(idStr) if err != nil { http.Error(w, "Неверный идентификатор", http.StatusBadRequest) return } sub, err := h.service.GetByID(r.Context(), id) if err != nil { http.Error(w, "Подписчик не найден", http.StatusNotFound) return } json.NewEncoder(w).Encode(sub) } // UpdateSubscription godoc // @Summary Обновить подписку по ID // @Tags subscriptions // @Accept json // @Produce json // @Param id path int true "ID подписки" // @Param subscription body CreateSubscriptionRequest true "Обновление подписки" // @Success 200 {object} model.Subscription // @Failure 400 {object} map[string]string // @Failure 500 {object} map[string]string // @Router /subscriptions/{id} [put] func (h *Handler) UpdateSubscription(w http.ResponseWriter, r *http.Request) { idStr := mux.Vars(r)["id"] id, err := strconv.Atoi(idStr) if err != nil { http.Error(w, "Неверный идентификатор", http.StatusBadRequest) return } var req CreateSubscriptionRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "Неверный запрос", http.StatusBadRequest) return } userUUID, err := uuid.Parse(req.UserID) if err != nil { http.Error(w, "Неверный uuid пользователя", http.StatusBadRequest) return } startDate, err := time.Parse("01-2006", req.StartDate) if err != nil { http.Error(w, "Неверный формат даты начала", http.StatusBadRequest) return } var endDate *time.Time if req.EndDate != nil { ed, err := time.Parse("01-2006", *req.EndDate) if err != nil { http.Error(w, "Неверный формат даты окончания", http.StatusBadRequest) return } endDate = &ed } sub := &model.Subscription{ ID: id, ServiceName: req.ServiceName, Price: req.Price, UserID: userUUID, StartDate: startDate, EndDate: endDate, } if err := h.service.Update(r.Context(), sub); err != nil { http.Error(w, "Не удалось обновить", http.StatusInternalServerError) return } json.NewEncoder(w).Encode(sub) } // DeleteSubscription godoc // @Summary Удалить подписку по ID // @Tags subscriptions // @Param id path int true "ID подписки" // @Success 204 "No Content" // @Failure 400 {object} map[string]string // @Failure 500 {object} map[string]string // @Router /subscriptions/{id} [delete] func (h *Handler) DeleteSubscription(w http.ResponseWriter, r *http.Request) { idStr := mux.Vars(r)["id"] id, err := strconv.Atoi(idStr) if err != nil { http.Error(w, "Неверный идентификатор", http.StatusBadRequest) return } if err := h.service.Delete(r.Context(), id); err != nil { http.Error(w, "Не удалось удалить", http.StatusInternalServerError) return } w.WriteHeader(http.StatusNoContent) } // ListSubscriptions godoc // @Summary Список подписок // @Tags subscriptions // @Produce json // @Success 200 {array} model.Subscription // @Failure 500 {object} map[string]string // @Router /subscriptions [get] func (h *Handler) ListSubscriptions(w http.ResponseWriter, r *http.Request) { subs, err := h.service.List(r.Context()) if err != nil { http.Error(w, "Не удалось включить в список", http.StatusInternalServerError) return } json.NewEncoder(w).Encode(subs) } // SumSubscriptions godoc // @Summary Суммарная стоимость подписок за период с фильтрацией // @Tags subscriptions // @Produce json // @Param user_id query string false "UUID пользователя" // @Param service_name query string false "Название сервиса" // @Param start query string true "Начало периода, формат MM-YYYY" example("07-2025") // @Param end query string true "Конец периода, формат MM-YYYY" example("08-2025") // @Success 200 {object} map[string]int // @Failure 400 {object} map[string]string // @Failure 500 {object} map[string]string // @Router /subscriptions/sum [get] func (h *Handler) SumSubscriptions(w http.ResponseWriter, r *http.Request) { q := r.URL.Query() var userID *uuid.UUID if u := q.Get("user_id"); u != "" { id, err := uuid.Parse(u) if err != nil { http.Error(w, "Неверный идентификатор пользователя", http.StatusBadRequest) return } userID = &id } serviceName := q.Get("service_name") startStr := q.Get("start") endStr := q.Get("end") if startStr == "" || endStr == "" { http.Error(w, "требуются начальные и конечные параметры запроса", http.StatusBadRequest) return } startDate, err := time.Parse("01-2006", startStr) if err != nil { http.Error(w, "Неверная дата начала", http.StatusBadRequest) return } endDate, err := time.Parse("01-2006", endStr) if err != nil { http.Error(w, "Неверная дата окончания", http.StatusBadRequest) return } sum, err := h.service.SumPrice(r.Context(), userID, serviceName, startDate, endDate) if err != nil { http.Error(w, "Не удалось рассчитать сумму", http.StatusInternalServerError) return } resp := map[string]int{"total_price": sum} json.NewEncoder(w).Encode(resp) }