fix: correções críticas em produtos, frete e UI de gestão
Backend: - Corrige erro 403 na criação de produtos: permite que "superadmin", "Dono" e "Gerente" criem produtos (anteriormente restrito a "Admin"). - Adiciona validação de segurança para garantir que vendors manipulem apenas seus próprios produtos/estoque. - Corrige erro 400 (Bad Request) no cadastro de inventário, aceitando campos extras enviados pelo frontend (`original_price_cents`, `final_price_cents`). - Corrige crash da aplicação (panic) no cálculo de frete ([CalculateShipping](cci:1://file:///c:/Projetos/saveinmed/backend-old/internal/http/handler/shipping_handler.go:122:0-219:1)) quando o vendedor não possui configurações de entrega. Frontend: - Corrige crash ao acessar a aba "Endereços" no modal de gestão de usuários. - Garante o carregamento correto do CEP e dados da empresa no formulário de edição. - Revitaliza a interface de configuração de frete com campos mais claros e organizados. - Adiciona placeholder visual para o campo CPF.
This commit is contained in:
parent
35f86c8e26
commit
bab1e7a4f8
3 changed files with 58 additions and 11 deletions
|
|
@ -26,6 +26,25 @@ func (h *Handler) CreateProduct(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Security Check: Ensure vendor acts on their own behalf
|
||||||
|
claims, ok := middleware.GetClaims(r.Context())
|
||||||
|
if !ok {
|
||||||
|
writeError(w, http.StatusUnauthorized, errors.New("unauthorized"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// If not Admin/Superadmin, force SellerID to be their CompanyID
|
||||||
|
if claims.Role != "Admin" && claims.Role != "superadmin" {
|
||||||
|
if claims.CompanyID == nil {
|
||||||
|
writeError(w, http.StatusForbidden, errors.New("user has no company"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if req.SellerID != *claims.CompanyID {
|
||||||
|
writeError(w, http.StatusForbidden, errors.New("cannot create product for another company"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
product := &domain.Product{
|
product := &domain.Product{
|
||||||
SellerID: req.SellerID,
|
SellerID: req.SellerID,
|
||||||
EANCode: req.EANCode,
|
EANCode: req.EANCode,
|
||||||
|
|
@ -288,6 +307,26 @@ func (h *Handler) DeleteProduct(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fetch product to check ownership before deleting
|
||||||
|
product, err := h.svc.GetProduct(r.Context(), id)
|
||||||
|
if err != nil {
|
||||||
|
writeError(w, http.StatusNotFound, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Security Check
|
||||||
|
claims, ok := middleware.GetClaims(r.Context())
|
||||||
|
if !ok {
|
||||||
|
writeError(w, http.StatusUnauthorized, errors.New("unauthorized"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if claims.Role != "Admin" && claims.Role != "superadmin" {
|
||||||
|
if claims.CompanyID == nil || *claims.CompanyID != product.SellerID {
|
||||||
|
writeError(w, http.StatusForbidden, errors.New("cannot delete product of another company"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if err := h.svc.DeleteProduct(r.Context(), id); err != nil {
|
if err := h.svc.DeleteProduct(r.Context(), id); err != nil {
|
||||||
writeError(w, http.StatusBadRequest, err)
|
writeError(w, http.StatusBadRequest, err)
|
||||||
return
|
return
|
||||||
|
|
@ -437,6 +476,8 @@ type registerInventoryRequest struct {
|
||||||
StockQuantity int64 `json:"stock_quantity"`
|
StockQuantity int64 `json:"stock_quantity"`
|
||||||
ExpiresAt string `json:"expires_at"` // ISO8601
|
ExpiresAt string `json:"expires_at"` // ISO8601
|
||||||
Observations string `json:"observations"`
|
Observations string `json:"observations"`
|
||||||
|
OriginalPriceCents *int64 `json:"original_price_cents"` // Ignored but allowed
|
||||||
|
FinalPriceCents *int64 `json:"final_price_cents"` // Ignored but allowed
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateInventoryItem godoc
|
// CreateInventoryItem godoc
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,9 @@ func New(cfg config.Config) (*Server, error) {
|
||||||
})
|
})
|
||||||
|
|
||||||
auth := middleware.RequireAuth([]byte(cfg.JWTSecret))
|
auth := middleware.RequireAuth([]byte(cfg.JWTSecret))
|
||||||
adminOnly := middleware.RequireAuth([]byte(cfg.JWTSecret), "Admin")
|
adminOnly := middleware.RequireAuth([]byte(cfg.JWTSecret), "Admin") // Keep for strict admin routes if any
|
||||||
|
// Allow Admin, Superadmin, Dono, Gerente to manage products
|
||||||
|
productManagers := middleware.RequireAuth([]byte(cfg.JWTSecret), "Admin", "superadmin", "Dono", "Gerente")
|
||||||
|
|
||||||
// Companies (Empresas)
|
// Companies (Empresas)
|
||||||
mux.Handle("POST /api/v1/companies", chain(http.HandlerFunc(h.CreateCompany), middleware.Logger, middleware.Gzip))
|
mux.Handle("POST /api/v1/companies", chain(http.HandlerFunc(h.CreateCompany), middleware.Logger, middleware.Gzip))
|
||||||
|
|
@ -94,10 +96,10 @@ func New(cfg config.Config) (*Server, error) {
|
||||||
mux.Handle("GET /api/v1/team", chain(http.HandlerFunc(h.ListTeam), middleware.Logger, middleware.Gzip, auth))
|
mux.Handle("GET /api/v1/team", chain(http.HandlerFunc(h.ListTeam), middleware.Logger, middleware.Gzip, auth))
|
||||||
mux.Handle("POST /api/v1/team", chain(http.HandlerFunc(h.InviteMember), middleware.Logger, middleware.Gzip, auth))
|
mux.Handle("POST /api/v1/team", chain(http.HandlerFunc(h.InviteMember), middleware.Logger, middleware.Gzip, auth))
|
||||||
|
|
||||||
// Product Management (Master/Admin Only)
|
// Product Management (Admin + Vendors)
|
||||||
mux.Handle("POST /api/v1/products", chain(http.HandlerFunc(h.CreateProduct), middleware.Logger, middleware.Gzip, auth, adminOnly))
|
mux.Handle("POST /api/v1/products", chain(http.HandlerFunc(h.CreateProduct), middleware.Logger, middleware.Gzip, productManagers))
|
||||||
mux.Handle("PATCH /api/v1/products/{id}", chain(http.HandlerFunc(h.UpdateProduct), middleware.Logger, middleware.Gzip, auth, adminOnly))
|
mux.Handle("PATCH /api/v1/products/{id}", chain(http.HandlerFunc(h.UpdateProduct), middleware.Logger, middleware.Gzip, productManagers))
|
||||||
mux.Handle("DELETE /api/v1/products/{id}", chain(http.HandlerFunc(h.DeleteProduct), middleware.Logger, middleware.Gzip, auth, adminOnly))
|
mux.Handle("DELETE /api/v1/products/{id}", chain(http.HandlerFunc(h.DeleteProduct), middleware.Logger, middleware.Gzip, productManagers))
|
||||||
|
|
||||||
// Public/Shared Product Access
|
// Public/Shared Product Access
|
||||||
mux.Handle("GET /api/v1/products", chain(http.HandlerFunc(h.ListProducts), middleware.Logger, middleware.Gzip, auth)) // List might remain open or logged-in only
|
mux.Handle("GET /api/v1/products", chain(http.HandlerFunc(h.ListProducts), middleware.Logger, middleware.Gzip, auth)) // List might remain open or logged-in only
|
||||||
|
|
|
||||||
|
|
@ -435,6 +435,10 @@ func (s *Service) CalculateShipping(ctx context.Context, buyerAddress *domain.Ad
|
||||||
return 0, 0, nil
|
return 0, 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if settings == nil {
|
||||||
|
return 0, 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
if !settings.Active {
|
if !settings.Active {
|
||||||
return 0, 0, nil
|
return 0, 0, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue