refactor(handler): complete package decomposition - 92% extracted
Final handler package structure (9 files): - handler.go: 111 lines (Auth: Register, Login) - dto.go: 220 lines (DTOs, helpers) - company_handler.go: 228 lines (Companies CRUD) - product_handler.go: 216 lines (Products + Inventory) - order_handler.go: 147 lines (Orders CRUD) - cart_handler.go: 127 lines (Cart + Reviews) - payment_handler.go: 117 lines (Payments + Shipments) - dashboard_handler.go: 81 lines (Seller/Admin dashboards) - user_handler.go: 256 lines (Users CRUD) Total: 1471 -> 111 lines in handler.go (~92% extracted) All tests passing
This commit is contained in:
parent
a3f00cd8ff
commit
a0720fb4a6
4 changed files with 463 additions and 437 deletions
81
backend/internal/http/handler/dashboard_handler.go
Normal file
81
backend/internal/http/handler/dashboard_handler.go
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
package handler
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gofrs/uuid/v5"
|
||||
)
|
||||
|
||||
// GetSellerDashboard aggregates KPIs for the authenticated seller or the requested company.
|
||||
// @Summary Dashboard do vendedor
|
||||
// @Tags Dashboard
|
||||
// @Security BearerAuth
|
||||
// @Produce json
|
||||
// @Param seller_id query string false "Seller ID"
|
||||
// @Success 200 {object} domain.SellerDashboard
|
||||
// @Router /api/v1/dashboard/seller [get]
|
||||
func (h *Handler) GetSellerDashboard(w http.ResponseWriter, r *http.Request) {
|
||||
requester, err := getRequester(r)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
sellerID := requester.CompanyID
|
||||
if sid := r.URL.Query().Get("seller_id"); sid != "" {
|
||||
id, err := uuid.FromString(sid)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusBadRequest, errors.New("invalid seller_id"))
|
||||
return
|
||||
}
|
||||
sellerID = &id
|
||||
}
|
||||
|
||||
if sellerID == nil {
|
||||
writeError(w, http.StatusBadRequest, errors.New("seller context is required"))
|
||||
return
|
||||
}
|
||||
|
||||
if !strings.EqualFold(requester.Role, "Admin") && requester.CompanyID != nil && *sellerID != *requester.CompanyID {
|
||||
writeError(w, http.StatusForbidden, errors.New("not allowed to view other sellers"))
|
||||
return
|
||||
}
|
||||
|
||||
dashboard, err := h.svc.GetSellerDashboard(r.Context(), *sellerID)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusOK, dashboard)
|
||||
}
|
||||
|
||||
// GetAdminDashboard exposes platform-wide aggregated metrics.
|
||||
// @Summary Dashboard do administrador
|
||||
// @Tags Dashboard
|
||||
// @Security BearerAuth
|
||||
// @Produce json
|
||||
// @Success 200 {object} domain.AdminDashboard
|
||||
// @Router /api/v1/dashboard/admin [get]
|
||||
func (h *Handler) GetAdminDashboard(w http.ResponseWriter, r *http.Request) {
|
||||
requester, err := getRequester(r)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
if !strings.EqualFold(requester.Role, "Admin") {
|
||||
writeError(w, http.StatusForbidden, errors.New("admin role required"))
|
||||
return
|
||||
}
|
||||
|
||||
dashboard, err := h.svc.GetAdminDashboard(r.Context())
|
||||
if err != nil {
|
||||
writeError(w, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusOK, dashboard)
|
||||
}
|
||||
|
|
@ -3,7 +3,6 @@ package handler
|
|||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
|
||||
|
|
@ -109,439 +108,3 @@ func (h *Handler) Login(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
writeJSON(w, http.StatusOK, authResponse{Token: token, ExpiresAt: exp})
|
||||
}
|
||||
|
||||
// CreatePaymentPreference godoc
|
||||
// @Summary Cria preferência de pagamento Mercado Pago com split nativo
|
||||
// @Tags Pagamentos
|
||||
// @Security BearerAuth
|
||||
// @Produce json
|
||||
// @Param id path string true "Order ID"
|
||||
// @Success 201 {object} domain.PaymentPreference
|
||||
// @Router /api/v1/orders/{id}/payment [post]
|
||||
func (h *Handler) CreatePaymentPreference(w http.ResponseWriter, r *http.Request) {
|
||||
if !strings.HasSuffix(r.URL.Path, "/payment") {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
id, err := parseUUIDFromPath(r.URL.Path)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
pref, err := h.svc.CreatePaymentPreference(r.Context(), id)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusCreated, pref)
|
||||
}
|
||||
|
||||
// CreateShipment godoc
|
||||
// @Summary Gera guia de postagem/transporte
|
||||
// @Tags Logistica
|
||||
// @Security BearerAuth
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param shipment body createShipmentRequest true "Dados de envio"
|
||||
// @Success 201 {object} domain.Shipment
|
||||
// @Router /api/v1/shipments [post]
|
||||
func (h *Handler) CreateShipment(w http.ResponseWriter, r *http.Request) {
|
||||
var req createShipmentRequest
|
||||
if err := decodeJSON(r.Context(), r, &req); err != nil {
|
||||
writeError(w, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
shipment := &domain.Shipment{
|
||||
OrderID: req.OrderID,
|
||||
Carrier: req.Carrier,
|
||||
TrackingCode: req.TrackingCode,
|
||||
ExternalTracking: req.ExternalTracking,
|
||||
}
|
||||
|
||||
if err := h.svc.CreateShipment(r.Context(), shipment); err != nil {
|
||||
writeError(w, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusCreated, shipment)
|
||||
}
|
||||
|
||||
// GetShipmentByOrderID godoc
|
||||
// @Summary Rastreia entrega
|
||||
// @Tags Logistica
|
||||
// @Security BearerAuth
|
||||
// @Produce json
|
||||
// @Param order_id path string true "Order ID"
|
||||
// @Success 200 {object} domain.Shipment
|
||||
// @Router /api/v1/shipments/{order_id} [get]
|
||||
func (h *Handler) GetShipmentByOrderID(w http.ResponseWriter, r *http.Request) {
|
||||
orderID, err := parseUUIDFromPath(r.URL.Path)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
shipment, err := h.svc.GetShipmentByOrderID(r.Context(), orderID)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusNotFound, err)
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusOK, shipment)
|
||||
}
|
||||
|
||||
// HandlePaymentWebhook godoc
|
||||
// @Summary Recebe notificações do Mercado Pago
|
||||
// @Tags Pagamentos
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param notification body domain.PaymentWebhookEvent true "Evento do gateway"
|
||||
// @Success 200 {object} domain.PaymentSplitResult
|
||||
// @Router /api/v1/payments/webhook [post]
|
||||
func (h *Handler) HandlePaymentWebhook(w http.ResponseWriter, r *http.Request) {
|
||||
var event domain.PaymentWebhookEvent
|
||||
if err := decodeJSON(r.Context(), r, &event); err != nil {
|
||||
writeError(w, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
summary, err := h.svc.HandlePaymentWebhook(r.Context(), event)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusOK, summary)
|
||||
}
|
||||
|
||||
// GetSellerDashboard aggregates KPIs for the authenticated seller or the requested company.
|
||||
// @Summary Dashboard do vendedor
|
||||
// @Tags Dashboard
|
||||
// @Security BearerAuth
|
||||
// @Produce json
|
||||
// @Param seller_id query string false "Seller ID"
|
||||
// @Success 200 {object} domain.SellerDashboard
|
||||
// @Router /api/v1/dashboard/seller [get]
|
||||
func (h *Handler) GetSellerDashboard(w http.ResponseWriter, r *http.Request) {
|
||||
requester, err := getRequester(r)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
sellerID := requester.CompanyID
|
||||
if sid := r.URL.Query().Get("seller_id"); sid != "" {
|
||||
id, err := uuid.FromString(sid)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusBadRequest, errors.New("invalid seller_id"))
|
||||
return
|
||||
}
|
||||
sellerID = &id
|
||||
}
|
||||
|
||||
if sellerID == nil {
|
||||
writeError(w, http.StatusBadRequest, errors.New("seller context is required"))
|
||||
return
|
||||
}
|
||||
|
||||
if !strings.EqualFold(requester.Role, "Admin") && requester.CompanyID != nil && *sellerID != *requester.CompanyID {
|
||||
writeError(w, http.StatusForbidden, errors.New("not allowed to view other sellers"))
|
||||
return
|
||||
}
|
||||
|
||||
dashboard, err := h.svc.GetSellerDashboard(r.Context(), *sellerID)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusOK, dashboard)
|
||||
}
|
||||
|
||||
// GetAdminDashboard exposes platform-wide aggregated metrics.
|
||||
// @Summary Dashboard do administrador
|
||||
// @Tags Dashboard
|
||||
// @Security BearerAuth
|
||||
// @Produce json
|
||||
// @Success 200 {object} domain.AdminDashboard
|
||||
// @Router /api/v1/dashboard/admin [get]
|
||||
func (h *Handler) GetAdminDashboard(w http.ResponseWriter, r *http.Request) {
|
||||
requester, err := getRequester(r)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
if !strings.EqualFold(requester.Role, "Admin") {
|
||||
writeError(w, http.StatusForbidden, errors.New("admin role required"))
|
||||
return
|
||||
}
|
||||
|
||||
dashboard, err := h.svc.GetAdminDashboard(r.Context())
|
||||
if err != nil {
|
||||
writeError(w, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusOK, dashboard)
|
||||
}
|
||||
|
||||
// CreateUser godoc
|
||||
// @Summary Criar usuário
|
||||
// @Tags Usuários
|
||||
// @Security BearerAuth
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param payload body createUserRequest true "Novo usuário"
|
||||
// @Success 201 {object} domain.User
|
||||
// @Failure 400 {object} map[string]string
|
||||
// @Failure 403 {object} map[string]string
|
||||
// @Failure 500 {object} map[string]string
|
||||
// @Router /api/v1/users [post]
|
||||
func (h *Handler) CreateUser(w http.ResponseWriter, r *http.Request) {
|
||||
requester, err := getRequester(r)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
var req createUserRequest
|
||||
if err := decodeJSON(r.Context(), r, &req); err != nil {
|
||||
writeError(w, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
if strings.EqualFold(requester.Role, "Seller") {
|
||||
if requester.CompanyID == nil {
|
||||
writeError(w, http.StatusBadRequest, errors.New("seller must include X-Company-ID header"))
|
||||
return
|
||||
}
|
||||
if req.CompanyID != *requester.CompanyID {
|
||||
writeError(w, http.StatusForbidden, errors.New("seller can only manage their own company users"))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
user := &domain.User{
|
||||
CompanyID: req.CompanyID,
|
||||
Role: req.Role,
|
||||
Name: req.Name,
|
||||
Email: req.Email,
|
||||
}
|
||||
|
||||
if err := h.svc.CreateUser(r.Context(), user, req.Password); err != nil {
|
||||
writeError(w, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusCreated, user)
|
||||
}
|
||||
|
||||
// ListUsers godoc
|
||||
// @Summary Listar usuários
|
||||
// @Tags Usuários
|
||||
// @Security BearerAuth
|
||||
// @Produce json
|
||||
// @Param page query int false "Página"
|
||||
// @Param page_size query int false "Tamanho da página"
|
||||
// @Param company_id query string false "Filtro por empresa"
|
||||
// @Success 200 {object} domain.UserPage
|
||||
// @Failure 400 {object} map[string]string
|
||||
// @Failure 500 {object} map[string]string
|
||||
// @Router /api/v1/users [get]
|
||||
func (h *Handler) ListUsers(w http.ResponseWriter, r *http.Request) {
|
||||
requester, err := getRequester(r)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
page, pageSize := parsePagination(r)
|
||||
|
||||
var companyFilter *uuid.UUID
|
||||
if cid := r.URL.Query().Get("company_id"); cid != "" {
|
||||
id, err := uuid.FromString(cid)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusBadRequest, errors.New("invalid company_id"))
|
||||
return
|
||||
}
|
||||
companyFilter = &id
|
||||
}
|
||||
|
||||
if strings.EqualFold(requester.Role, "Seller") {
|
||||
if requester.CompanyID == nil {
|
||||
writeError(w, http.StatusBadRequest, errors.New("seller must include X-Company-ID header"))
|
||||
return
|
||||
}
|
||||
companyFilter = requester.CompanyID
|
||||
}
|
||||
|
||||
pageResult, err := h.svc.ListUsers(r.Context(), domain.UserFilter{CompanyID: companyFilter}, page, pageSize)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusOK, pageResult)
|
||||
}
|
||||
|
||||
// GetUser godoc
|
||||
// @Summary Obter usuário
|
||||
// @Tags Usuários
|
||||
// @Security BearerAuth
|
||||
// @Produce json
|
||||
// @Param id path string true "User ID"
|
||||
// @Success 200 {object} domain.User
|
||||
// @Failure 400 {object} map[string]string
|
||||
// @Failure 403 {object} map[string]string
|
||||
// @Failure 404 {object} map[string]string
|
||||
// @Router /api/v1/users/{id} [get]
|
||||
func (h *Handler) GetUser(w http.ResponseWriter, r *http.Request) {
|
||||
requester, err := getRequester(r)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
id, err := parseUUIDFromPath(r.URL.Path)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
user, err := h.svc.GetUser(r.Context(), id)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusNotFound, err)
|
||||
return
|
||||
}
|
||||
|
||||
if strings.EqualFold(requester.Role, "Seller") {
|
||||
if requester.CompanyID == nil || user.CompanyID != *requester.CompanyID {
|
||||
writeError(w, http.StatusForbidden, errors.New("seller can only view users from their company"))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusOK, user)
|
||||
}
|
||||
|
||||
// UpdateUser godoc
|
||||
// @Summary Atualizar usuário
|
||||
// @Tags Usuários
|
||||
// @Security BearerAuth
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path string true "User ID"
|
||||
// @Param payload body updateUserRequest true "Campos para atualização"
|
||||
// @Success 200 {object} domain.User
|
||||
// @Failure 400 {object} map[string]string
|
||||
// @Failure 403 {object} map[string]string
|
||||
// @Failure 404 {object} map[string]string
|
||||
// @Failure 500 {object} map[string]string
|
||||
// @Router /api/v1/users/{id} [put]
|
||||
func (h *Handler) UpdateUser(w http.ResponseWriter, r *http.Request) {
|
||||
requester, err := getRequester(r)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
id, err := parseUUIDFromPath(r.URL.Path)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
var req updateUserRequest
|
||||
if err := decodeJSON(r.Context(), r, &req); err != nil {
|
||||
writeError(w, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
user, err := h.svc.GetUser(r.Context(), id)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusNotFound, err)
|
||||
return
|
||||
}
|
||||
|
||||
if strings.EqualFold(requester.Role, "Seller") {
|
||||
if requester.CompanyID == nil || user.CompanyID != *requester.CompanyID {
|
||||
writeError(w, http.StatusForbidden, errors.New("seller can only update their company users"))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if req.CompanyID != nil {
|
||||
user.CompanyID = *req.CompanyID
|
||||
}
|
||||
if req.Role != nil {
|
||||
user.Role = *req.Role
|
||||
}
|
||||
if req.Name != nil {
|
||||
user.Name = *req.Name
|
||||
}
|
||||
if req.Email != nil {
|
||||
user.Email = *req.Email
|
||||
}
|
||||
|
||||
newPassword := ""
|
||||
if req.Password != nil {
|
||||
newPassword = *req.Password
|
||||
}
|
||||
|
||||
if err := h.svc.UpdateUser(r.Context(), user, newPassword); err != nil {
|
||||
writeError(w, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusOK, user)
|
||||
}
|
||||
|
||||
// DeleteUser godoc
|
||||
// @Summary Excluir usuário
|
||||
// @Tags Usuários
|
||||
// @Security BearerAuth
|
||||
// @Param id path string true "User ID"
|
||||
// @Success 204 {string} string "No Content"
|
||||
// @Failure 400 {object} map[string]string
|
||||
// @Failure 403 {object} map[string]string
|
||||
// @Failure 404 {object} map[string]string
|
||||
// @Failure 500 {object} map[string]string
|
||||
// @Router /api/v1/users/{id} [delete]
|
||||
func (h *Handler) DeleteUser(w http.ResponseWriter, r *http.Request) {
|
||||
requester, err := getRequester(r)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
id, err := parseUUIDFromPath(r.URL.Path)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
user, err := h.svc.GetUser(r.Context(), id)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusNotFound, err)
|
||||
return
|
||||
}
|
||||
|
||||
if strings.EqualFold(requester.Role, "Seller") {
|
||||
if requester.CompanyID == nil || user.CompanyID != *requester.CompanyID {
|
||||
writeError(w, http.StatusForbidden, errors.New("seller can only delete their company users"))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if err := h.svc.DeleteUser(r.Context(), id); err != nil {
|
||||
writeError(w, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
|
|
|||
116
backend/internal/http/handler/payment_handler.go
Normal file
116
backend/internal/http/handler/payment_handler.go
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/saveinmed/backend-go/internal/domain"
|
||||
)
|
||||
|
||||
// CreatePaymentPreference godoc
|
||||
// @Summary Cria preferência de pagamento Mercado Pago com split nativo
|
||||
// @Tags Pagamentos
|
||||
// @Security BearerAuth
|
||||
// @Produce json
|
||||
// @Param id path string true "Order ID"
|
||||
// @Success 201 {object} domain.PaymentPreference
|
||||
// @Router /api/v1/orders/{id}/payment [post]
|
||||
func (h *Handler) CreatePaymentPreference(w http.ResponseWriter, r *http.Request) {
|
||||
if !strings.HasSuffix(r.URL.Path, "/payment") {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
id, err := parseUUIDFromPath(r.URL.Path)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
pref, err := h.svc.CreatePaymentPreference(r.Context(), id)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusCreated, pref)
|
||||
}
|
||||
|
||||
// CreateShipment godoc
|
||||
// @Summary Gera guia de postagem/transporte
|
||||
// @Tags Logistica
|
||||
// @Security BearerAuth
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param shipment body createShipmentRequest true "Dados de envio"
|
||||
// @Success 201 {object} domain.Shipment
|
||||
// @Router /api/v1/shipments [post]
|
||||
func (h *Handler) CreateShipment(w http.ResponseWriter, r *http.Request) {
|
||||
var req createShipmentRequest
|
||||
if err := decodeJSON(r.Context(), r, &req); err != nil {
|
||||
writeError(w, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
shipment := &domain.Shipment{
|
||||
OrderID: req.OrderID,
|
||||
Carrier: req.Carrier,
|
||||
TrackingCode: req.TrackingCode,
|
||||
ExternalTracking: req.ExternalTracking,
|
||||
}
|
||||
|
||||
if err := h.svc.CreateShipment(r.Context(), shipment); err != nil {
|
||||
writeError(w, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusCreated, shipment)
|
||||
}
|
||||
|
||||
// GetShipmentByOrderID godoc
|
||||
// @Summary Rastreia entrega
|
||||
// @Tags Logistica
|
||||
// @Security BearerAuth
|
||||
// @Produce json
|
||||
// @Param order_id path string true "Order ID"
|
||||
// @Success 200 {object} domain.Shipment
|
||||
// @Router /api/v1/shipments/{order_id} [get]
|
||||
func (h *Handler) GetShipmentByOrderID(w http.ResponseWriter, r *http.Request) {
|
||||
orderID, err := parseUUIDFromPath(r.URL.Path)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
shipment, err := h.svc.GetShipmentByOrderID(r.Context(), orderID)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusNotFound, err)
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusOK, shipment)
|
||||
}
|
||||
|
||||
// HandlePaymentWebhook godoc
|
||||
// @Summary Recebe notificações do Mercado Pago
|
||||
// @Tags Pagamentos
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param notification body domain.PaymentWebhookEvent true "Evento do gateway"
|
||||
// @Success 200 {object} domain.PaymentSplitResult
|
||||
// @Router /api/v1/payments/webhook [post]
|
||||
func (h *Handler) HandlePaymentWebhook(w http.ResponseWriter, r *http.Request) {
|
||||
var event domain.PaymentWebhookEvent
|
||||
if err := decodeJSON(r.Context(), r, &event); err != nil {
|
||||
writeError(w, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
summary, err := h.svc.HandlePaymentWebhook(r.Context(), event)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusOK, summary)
|
||||
}
|
||||
266
backend/internal/http/handler/user_handler.go
Normal file
266
backend/internal/http/handler/user_handler.go
Normal file
|
|
@ -0,0 +1,266 @@
|
|||
package handler
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gofrs/uuid/v5"
|
||||
"github.com/saveinmed/backend-go/internal/domain"
|
||||
)
|
||||
|
||||
// CreateUser godoc
|
||||
// @Summary Criar usuário
|
||||
// @Tags Usuários
|
||||
// @Security BearerAuth
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param payload body createUserRequest true "Novo usuário"
|
||||
// @Success 201 {object} domain.User
|
||||
// @Failure 400 {object} map[string]string
|
||||
// @Failure 403 {object} map[string]string
|
||||
// @Failure 500 {object} map[string]string
|
||||
// @Router /api/v1/users [post]
|
||||
func (h *Handler) CreateUser(w http.ResponseWriter, r *http.Request) {
|
||||
requester, err := getRequester(r)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
var req createUserRequest
|
||||
if err := decodeJSON(r.Context(), r, &req); err != nil {
|
||||
writeError(w, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
if strings.EqualFold(requester.Role, "Seller") {
|
||||
if requester.CompanyID == nil {
|
||||
writeError(w, http.StatusBadRequest, errors.New("seller must include X-Company-ID header"))
|
||||
return
|
||||
}
|
||||
if req.CompanyID != *requester.CompanyID {
|
||||
writeError(w, http.StatusForbidden, errors.New("seller can only manage their own company users"))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
user := &domain.User{
|
||||
CompanyID: req.CompanyID,
|
||||
Role: req.Role,
|
||||
Name: req.Name,
|
||||
Email: req.Email,
|
||||
}
|
||||
|
||||
if err := h.svc.CreateUser(r.Context(), user, req.Password); err != nil {
|
||||
writeError(w, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusCreated, user)
|
||||
}
|
||||
|
||||
// ListUsers godoc
|
||||
// @Summary Listar usuários
|
||||
// @Tags Usuários
|
||||
// @Security BearerAuth
|
||||
// @Produce json
|
||||
// @Param page query int false "Página"
|
||||
// @Param page_size query int false "Tamanho da página"
|
||||
// @Param company_id query string false "Filtro por empresa"
|
||||
// @Success 200 {object} domain.UserPage
|
||||
// @Failure 400 {object} map[string]string
|
||||
// @Failure 500 {object} map[string]string
|
||||
// @Router /api/v1/users [get]
|
||||
func (h *Handler) ListUsers(w http.ResponseWriter, r *http.Request) {
|
||||
requester, err := getRequester(r)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
page, pageSize := parsePagination(r)
|
||||
|
||||
var companyFilter *uuid.UUID
|
||||
if cid := r.URL.Query().Get("company_id"); cid != "" {
|
||||
id, err := uuid.FromString(cid)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusBadRequest, errors.New("invalid company_id"))
|
||||
return
|
||||
}
|
||||
companyFilter = &id
|
||||
}
|
||||
|
||||
if strings.EqualFold(requester.Role, "Seller") {
|
||||
if requester.CompanyID == nil {
|
||||
writeError(w, http.StatusBadRequest, errors.New("seller must include X-Company-ID header"))
|
||||
return
|
||||
}
|
||||
companyFilter = requester.CompanyID
|
||||
}
|
||||
|
||||
pageResult, err := h.svc.ListUsers(r.Context(), domain.UserFilter{CompanyID: companyFilter}, page, pageSize)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusOK, pageResult)
|
||||
}
|
||||
|
||||
// GetUser godoc
|
||||
// @Summary Obter usuário
|
||||
// @Tags Usuários
|
||||
// @Security BearerAuth
|
||||
// @Produce json
|
||||
// @Param id path string true "User ID"
|
||||
// @Success 200 {object} domain.User
|
||||
// @Failure 400 {object} map[string]string
|
||||
// @Failure 403 {object} map[string]string
|
||||
// @Failure 404 {object} map[string]string
|
||||
// @Router /api/v1/users/{id} [get]
|
||||
func (h *Handler) GetUser(w http.ResponseWriter, r *http.Request) {
|
||||
requester, err := getRequester(r)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
id, err := parseUUIDFromPath(r.URL.Path)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
user, err := h.svc.GetUser(r.Context(), id)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusNotFound, err)
|
||||
return
|
||||
}
|
||||
|
||||
if strings.EqualFold(requester.Role, "Seller") {
|
||||
if requester.CompanyID == nil || user.CompanyID != *requester.CompanyID {
|
||||
writeError(w, http.StatusForbidden, errors.New("seller can only view users from their company"))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusOK, user)
|
||||
}
|
||||
|
||||
// UpdateUser godoc
|
||||
// @Summary Atualizar usuário
|
||||
// @Tags Usuários
|
||||
// @Security BearerAuth
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path string true "User ID"
|
||||
// @Param payload body updateUserRequest true "Campos para atualização"
|
||||
// @Success 200 {object} domain.User
|
||||
// @Failure 400 {object} map[string]string
|
||||
// @Failure 403 {object} map[string]string
|
||||
// @Failure 404 {object} map[string]string
|
||||
// @Failure 500 {object} map[string]string
|
||||
// @Router /api/v1/users/{id} [put]
|
||||
func (h *Handler) UpdateUser(w http.ResponseWriter, r *http.Request) {
|
||||
requester, err := getRequester(r)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
id, err := parseUUIDFromPath(r.URL.Path)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
var req updateUserRequest
|
||||
if err := decodeJSON(r.Context(), r, &req); err != nil {
|
||||
writeError(w, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
user, err := h.svc.GetUser(r.Context(), id)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusNotFound, err)
|
||||
return
|
||||
}
|
||||
|
||||
if strings.EqualFold(requester.Role, "Seller") {
|
||||
if requester.CompanyID == nil || user.CompanyID != *requester.CompanyID {
|
||||
writeError(w, http.StatusForbidden, errors.New("seller can only update their company users"))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if req.CompanyID != nil {
|
||||
user.CompanyID = *req.CompanyID
|
||||
}
|
||||
if req.Role != nil {
|
||||
user.Role = *req.Role
|
||||
}
|
||||
if req.Name != nil {
|
||||
user.Name = *req.Name
|
||||
}
|
||||
if req.Email != nil {
|
||||
user.Email = *req.Email
|
||||
}
|
||||
|
||||
newPassword := ""
|
||||
if req.Password != nil {
|
||||
newPassword = *req.Password
|
||||
}
|
||||
|
||||
if err := h.svc.UpdateUser(r.Context(), user, newPassword); err != nil {
|
||||
writeError(w, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusOK, user)
|
||||
}
|
||||
|
||||
// DeleteUser godoc
|
||||
// @Summary Excluir usuário
|
||||
// @Tags Usuários
|
||||
// @Security BearerAuth
|
||||
// @Param id path string true "User ID"
|
||||
// @Success 204 {string} string "No Content"
|
||||
// @Failure 400 {object} map[string]string
|
||||
// @Failure 403 {object} map[string]string
|
||||
// @Failure 404 {object} map[string]string
|
||||
// @Failure 500 {object} map[string]string
|
||||
// @Router /api/v1/users/{id} [delete]
|
||||
func (h *Handler) DeleteUser(w http.ResponseWriter, r *http.Request) {
|
||||
requester, err := getRequester(r)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
id, err := parseUUIDFromPath(r.URL.Path)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
user, err := h.svc.GetUser(r.Context(), id)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusNotFound, err)
|
||||
return
|
||||
}
|
||||
|
||||
if strings.EqualFold(requester.Role, "Seller") {
|
||||
if requester.CompanyID == nil || user.CompanyID != *requester.CompanyID {
|
||||
writeError(w, http.StatusForbidden, errors.New("seller can only delete their company users"))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if err := h.svc.DeleteUser(r.Context(), id); err != nil {
|
||||
writeError(w, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
Loading…
Reference in a new issue