package handler import ( "context" "errors" "net/http" "strings" "time" jsoniter "github.com/json-iterator/go" "github.com/gofrs/uuid/v5" "github.com/saveinmed/backend-go/internal/domain" "github.com/saveinmed/backend-go/internal/usecase" ) var json = jsoniter.ConfigCompatibleWithStandardLibrary type Handler struct { svc *usecase.Service } func New(svc *usecase.Service) *Handler { return &Handler{svc: svc} } // CreateCompany godoc // @Summary Registro de empresas // @Description Cadastra farmácia, distribuidora ou administrador com CNPJ e licença sanitária. // @Tags Empresas // @Accept json // @Produce json // @Param company body registerCompanyRequest true "Dados da empresa" // @Success 201 {object} domain.Company // @Router /api/companies [post] func (h *Handler) CreateCompany(w http.ResponseWriter, r *http.Request) { var req registerCompanyRequest if err := decodeJSON(r.Context(), r, &req); err != nil { writeError(w, http.StatusBadRequest, err) return } company := &domain.Company{ Role: req.Role, CNPJ: req.CNPJ, CorporateName: req.CorporateName, SanitaryLicense: req.SanitaryLicense, } if err := h.svc.RegisterCompany(r.Context(), company); err != nil { writeError(w, http.StatusInternalServerError, err) return } writeJSON(w, http.StatusCreated, company) } // ListCompanies godoc // @Summary Lista empresas // @Tags Empresas // @Produce json // @Success 200 {array} domain.Company // @Router /api/companies [get] func (h *Handler) ListCompanies(w http.ResponseWriter, r *http.Request) { companies, err := h.svc.ListCompanies(r.Context()) if err != nil { writeError(w, http.StatusInternalServerError, err) return } writeJSON(w, http.StatusOK, companies) } // CreateProduct godoc // @Summary Cadastro de produto com rastreabilidade de lote // @Tags Produtos // @Accept json // @Produce json // @Param product body registerProductRequest true "Produto" // @Success 201 {object} domain.Product // @Router /api/products [post] func (h *Handler) CreateProduct(w http.ResponseWriter, r *http.Request) { var req registerProductRequest if err := decodeJSON(r.Context(), r, &req); err != nil { writeError(w, http.StatusBadRequest, err) return } product := &domain.Product{ SellerID: req.SellerID, Name: req.Name, Description: req.Description, Batch: req.Batch, ExpiresAt: req.ExpiresAt, PriceCents: req.PriceCents, Stock: req.Stock, } if err := h.svc.RegisterProduct(r.Context(), product); err != nil { writeError(w, http.StatusInternalServerError, err) return } writeJSON(w, http.StatusCreated, product) } // ListProducts godoc // @Summary Lista catálogo com lote e validade // @Tags Produtos // @Produce json // @Success 200 {array} domain.Product // @Router /api/products [get] func (h *Handler) ListProducts(w http.ResponseWriter, r *http.Request) { products, err := h.svc.ListProducts(r.Context()) if err != nil { writeError(w, http.StatusInternalServerError, err) return } writeJSON(w, http.StatusOK, products) } // CreateOrder godoc // @Summary Criação de pedido com split // @Tags Pedidos // @Accept json // @Produce json // @Param order body createOrderRequest true "Pedido" // @Success 201 {object} domain.Order // @Router /api/orders [post] func (h *Handler) CreateOrder(w http.ResponseWriter, r *http.Request) { var req createOrderRequest if err := decodeJSON(r.Context(), r, &req); err != nil { writeError(w, http.StatusBadRequest, err) return } order := &domain.Order{ BuyerID: req.BuyerID, SellerID: req.SellerID, Items: req.Items, } var total int64 for _, item := range req.Items { total += item.UnitCents * item.Quantity } order.TotalCents = total if err := h.svc.CreateOrder(r.Context(), order); err != nil { writeError(w, http.StatusInternalServerError, err) return } writeJSON(w, http.StatusCreated, order) } // GetOrder godoc // @Summary Consulta pedido // @Tags Pedidos // @Produce json // @Param id path string true "Order ID" // @Success 200 {object} domain.Order // @Router /api/orders/{id} [get] func (h *Handler) GetOrder(w http.ResponseWriter, r *http.Request) { id, err := parseUUIDFromPath(r.URL.Path) if err != nil { writeError(w, http.StatusBadRequest, err) return } order, err := h.svc.GetOrder(r.Context(), id) if err != nil { writeError(w, http.StatusNotFound, err) return } writeJSON(w, http.StatusOK, order) } // UpdateOrderStatus godoc // @Summary Atualiza status do pedido // @Tags Pedidos // @Accept json // @Produce json // @Param id path string true "Order ID" // @Param status body updateStatusRequest true "Novo status" // @Success 204 "" // @Router /api/orders/{id}/status [patch] func (h *Handler) UpdateOrderStatus(w http.ResponseWriter, r *http.Request) { id, err := parseUUIDFromPath(r.URL.Path) if err != nil { writeError(w, http.StatusBadRequest, err) return } var req updateStatusRequest if err := decodeJSON(r.Context(), r, &req); err != nil { writeError(w, http.StatusBadRequest, err) return } if !isValidStatus(req.Status) { writeError(w, http.StatusBadRequest, errors.New("invalid status")) return } if err := h.svc.UpdateOrderStatus(r.Context(), id, domain.OrderStatus(req.Status)); err != nil { writeError(w, http.StatusInternalServerError, err) return } w.WriteHeader(http.StatusNoContent) } // CreatePaymentPreference godoc // @Summary Cria preferência de pagamento Mercado Pago com split nativo // @Tags Pagamentos // @Produce json // @Param id path string true "Order ID" // @Success 201 {object} domain.PaymentPreference // @Router /api/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) } type registerCompanyRequest struct { Role string `json:"role"` CNPJ string `json:"cnpj"` CorporateName string `json:"corporate_name"` SanitaryLicense string `json:"sanitary_license"` } type registerProductRequest struct { SellerID uuid.UUID `json:"seller_id"` Name string `json:"name"` Description string `json:"description"` Batch string `json:"batch"` ExpiresAt time.Time `json:"expires_at"` PriceCents int64 `json:"price_cents"` Stock int64 `json:"stock"` } type createOrderRequest struct { BuyerID uuid.UUID `json:"buyer_id"` SellerID uuid.UUID `json:"seller_id"` Items []domain.OrderItem `json:"items"` } type updateStatusRequest struct { Status string `json:"status"` } func writeJSON(w http.ResponseWriter, status int, v any) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(status) _ = json.NewEncoder(w).Encode(v) } func writeError(w http.ResponseWriter, status int, err error) { writeJSON(w, status, map[string]string{"error": err.Error()}) } func decodeJSON(ctx context.Context, r *http.Request, v any) error { ctx, cancel := context.WithTimeout(ctx, 10*time.Second) defer cancel() dec := json.NewDecoder(r.Body) dec.DisallowUnknownFields() if err := dec.Decode(v); err != nil { return err } return ctx.Err() } func parseUUIDFromPath(path string) (uuid.UUID, error) { parts := splitPath(path) for i := len(parts) - 1; i >= 0; i-- { if id, err := uuid.FromString(parts[i]); err == nil { return id, nil } } return uuid.UUID{}, errors.New("missing resource id") } func splitPath(p string) []string { var parts []string start := 0 for i := 0; i < len(p); i++ { if p[i] == '/' { if i > start { parts = append(parts, p[start:i]) } start = i + 1 } } if start < len(p) { parts = append(parts, p[start:]) } return parts } func isValidStatus(status string) bool { switch domain.OrderStatus(status) { case domain.OrderStatusPending, domain.OrderStatusPaid, domain.OrderStatusInvoiced, domain.OrderStatusDelivered: return true default: return false } }