diff --git a/backend/internal/http/handler/dashboard_handler.go b/backend/internal/http/handler/dashboard_handler.go new file mode 100644 index 0000000..1e410c1 --- /dev/null +++ b/backend/internal/http/handler/dashboard_handler.go @@ -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) +} diff --git a/backend/internal/http/handler/handler.go b/backend/internal/http/handler/handler.go index 66ee401..7df94c2 100644 --- a/backend/internal/http/handler/handler.go +++ b/backend/internal/http/handler/handler.go @@ -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) -} diff --git a/backend/internal/http/handler/payment_handler.go b/backend/internal/http/handler/payment_handler.go new file mode 100644 index 0000000..22eba4c --- /dev/null +++ b/backend/internal/http/handler/payment_handler.go @@ -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) +} diff --git a/backend/internal/http/handler/user_handler.go b/backend/internal/http/handler/user_handler.go new file mode 100644 index 0000000..c9d4dd1 --- /dev/null +++ b/backend/internal/http/handler/user_handler.go @@ -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) +}