saveinmed/backend-old/internal/http/handler/user_handler.go
NANDO9322 35f86c8e26 feat: Melhorias na Gestão de Usuários, Correções de Frete e UI
Frontend:
- Refatoração completa do [GestaoUsuarioModal](cci:1://file:///c:/Projetos/saveinmed/saveinmed-frontend/src/components/GestaoUsuarioModal.tsx:17:0-711:2) para melhor visibilidade e UX.
- Correção de erro (crash) ao carregar endereços vazios.
- Nova interface de Configuração de Frete com abas para Entrega e Retirada.
- Correção na busca de dados completos da empresa (CEP, etc).
- Ajuste na chave de autenticação (`access_token`) no serviço de endereços.

Backend:
- Correção do erro 500 em [GetShippingSettings](cci:1://file:///c:/Projetos/saveinmed/backend-old/internal/repository/postgres/postgres.go:1398:0-1405:1) (tratamento de `no rows`).
- Ajustes nos handlers de endereço para suportar Admin/EntityID corretamente.
- Migrações de banco de dados para configurações de envio e coordenadas.
- Atualização da documentação Swagger e testes.
2026-01-27 21:27:03 -03:00

293 lines
7.7 KiB
Go

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
}
}
// Only Superadmin can create another Superadmin
if req.Superadmin {
if !strings.EqualFold(requester.Role, "super_admin") && !strings.EqualFold(requester.Role, "superadmin") { // Allow both variations just in case
writeError(w, http.StatusForbidden, errors.New("only superadmins can create superadmins"))
return
}
}
user := &domain.User{
CompanyID: req.CompanyID,
Role: req.Role,
Name: req.Name,
Username: req.Username,
Email: req.Email,
Superadmin: req.Superadmin,
NomeSocial: req.NomeSocial,
CPF: req.CPF,
}
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
}
// Non-admin/Superadmin users can only see users from their own company
isSuperAdmin := strings.EqualFold(requester.Role, "superadmin") || strings.EqualFold(requester.Role, "super_admin")
isAdmin := strings.EqualFold(requester.Role, "Admin")
if !isSuperAdmin && !isAdmin {
if requester.CompanyID == nil {
writeError(w, http.StatusBadRequest, errors.New("user must have a company associated"))
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
}
// Map frontend's array of company IDs to the single CompanyID
if len(req.EmpresasDados) > 0 {
// Use the first company ID from the list
if id, err := uuid.FromString(req.EmpresasDados[0]); err == nil {
user.CompanyID = id
}
}
if req.Role != nil {
user.Role = *req.Role
}
if req.Name != nil {
user.Name = *req.Name
}
if req.Username != nil {
user.Username = *req.Username
}
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)
}