diff --git a/backend/internal/http/handler/dto.go b/backend/internal/http/handler/dto.go new file mode 100644 index 0000000..16f0a36 --- /dev/null +++ b/backend/internal/http/handler/dto.go @@ -0,0 +1,233 @@ +package handler + +import ( + "context" + "errors" + "net/http" + "strconv" + "time" + + "github.com/gofrs/uuid/v5" + "github.com/saveinmed/backend-go/internal/domain" + "github.com/saveinmed/backend-go/internal/http/middleware" +) + +// --- Request DTOs --- + +type createUserRequest struct { + CompanyID uuid.UUID `json:"company_id"` + Role string `json:"role"` + Name string `json:"name"` + Email string `json:"email"` + Password string `json:"password"` +} + +type registerAuthRequest struct { + CompanyID *uuid.UUID `json:"company_id,omitempty"` + Company *registerCompanyTarget `json:"company,omitempty"` + Role string `json:"role"` + Name string `json:"name"` + Email string `json:"email"` + Password string `json:"password"` +} + +type registerCompanyTarget struct { + ID uuid.UUID `json:"id,omitempty"` + Role string `json:"role"` + CNPJ string `json:"cnpj"` + CorporateName string `json:"corporate_name"` + LicenseNumber string `json:"license_number"` +} + +type loginRequest struct { + Email string `json:"email"` + Password string `json:"password"` +} + +type authResponse struct { + Token string `json:"token"` + ExpiresAt time.Time `json:"expires_at"` +} + +type inventoryAdjustRequest struct { + ProductID uuid.UUID `json:"product_id"` + Delta int64 `json:"delta"` + Reason string `json:"reason"` +} + +type addCartItemRequest struct { + ProductID uuid.UUID `json:"product_id"` + Quantity int64 `json:"quantity"` +} + +type createReviewRequest struct { + OrderID uuid.UUID `json:"order_id"` + Rating int `json:"rating"` + Comment string `json:"comment"` +} + +type updateUserRequest struct { + CompanyID *uuid.UUID `json:"company_id,omitempty"` + Role *string `json:"role,omitempty"` + Name *string `json:"name,omitempty"` + Email *string `json:"email,omitempty"` + Password *string `json:"password,omitempty"` +} + +type requester struct { + Role string + CompanyID *uuid.UUID +} + +type registerCompanyRequest struct { + Role string `json:"role"` + CNPJ string `json:"cnpj"` + CorporateName string `json:"corporate_name"` + LicenseNumber string `json:"license_number"` +} + +type updateCompanyRequest struct { + Role *string `json:"role,omitempty"` + CNPJ *string `json:"cnpj,omitempty"` + CorporateName *string `json:"corporate_name,omitempty"` + LicenseNumber *string `json:"license_number,omitempty"` + IsVerified *bool `json:"is_verified,omitempty"` +} + +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 updateProductRequest struct { + SellerID *uuid.UUID `json:"seller_id,omitempty"` + Name *string `json:"name,omitempty"` + Description *string `json:"description,omitempty"` + Batch *string `json:"batch,omitempty"` + ExpiresAt *time.Time `json:"expires_at,omitempty"` + PriceCents *int64 `json:"price_cents,omitempty"` + Stock *int64 `json:"stock,omitempty"` +} + +type createOrderRequest struct { + BuyerID uuid.UUID `json:"buyer_id"` + SellerID uuid.UUID `json:"seller_id"` + Items []domain.OrderItem `json:"items"` + Shipping domain.ShippingAddress `json:"shipping"` +} + +type createShipmentRequest struct { + OrderID uuid.UUID `json:"order_id"` + Carrier string `json:"carrier"` + TrackingCode string `json:"tracking_code"` + ExternalTracking string `json:"external_tracking"` +} + +type updateStatusRequest struct { + Status string `json:"status"` +} + +// --- Utility Functions --- + +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 + } +} + +func parsePagination(r *http.Request) (int, int) { + page := 1 + pageSize := 20 + + if v := r.URL.Query().Get("page"); v != "" { + if p, err := strconv.Atoi(v); err == nil && p > 0 { + page = p + } + } + + if v := r.URL.Query().Get("page_size"); v != "" { + if ps, err := strconv.Atoi(v); err == nil && ps > 0 { + pageSize = ps + } + } + + return page, pageSize +} + +func getRequester(r *http.Request) (requester, error) { + if claims, ok := middleware.GetClaims(r.Context()); ok { + return requester{Role: claims.Role, CompanyID: claims.CompanyID}, nil + } + role := r.Header.Get("X-User-Role") + if role == "" { + role = "Admin" + } + + var companyID *uuid.UUID + if cid := r.Header.Get("X-Company-ID"); cid != "" { + id, err := uuid.FromString(cid) + if err != nil { + return requester{}, errors.New("invalid X-Company-ID header") + } + companyID = &id + } + + return requester{Role: role, CompanyID: companyID}, nil +} diff --git a/backend/internal/http/handler/handler.go b/backend/internal/http/handler/handler.go index 079c6ba..0415036 100644 --- a/backend/internal/http/handler/handler.go +++ b/backend/internal/http/handler/handler.go @@ -1,7 +1,6 @@ package handler import ( - "context" "errors" "net/http" "strconv" @@ -1252,219 +1251,3 @@ func (h *Handler) DeleteUser(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNoContent) } - -type createUserRequest struct { - CompanyID uuid.UUID `json:"company_id"` - Role string `json:"role"` - Name string `json:"name"` - Email string `json:"email"` - Password string `json:"password"` -} - -type registerAuthRequest struct { - CompanyID *uuid.UUID `json:"company_id,omitempty"` - Company *registerCompanyTarget `json:"company,omitempty"` - Role string `json:"role"` - Name string `json:"name"` - Email string `json:"email"` - Password string `json:"password"` -} - -type registerCompanyTarget struct { - ID uuid.UUID `json:"id,omitempty"` - Role string `json:"role"` - CNPJ string `json:"cnpj"` - CorporateName string `json:"corporate_name"` - LicenseNumber string `json:"license_number"` -} - -type loginRequest struct { - Email string `json:"email"` - Password string `json:"password"` -} - -type authResponse struct { - Token string `json:"token"` - ExpiresAt time.Time `json:"expires_at"` -} - -type inventoryAdjustRequest struct { - ProductID uuid.UUID `json:"product_id"` - Delta int64 `json:"delta"` - Reason string `json:"reason"` -} - -type addCartItemRequest struct { - ProductID uuid.UUID `json:"product_id"` - Quantity int64 `json:"quantity"` -} - -type createReviewRequest struct { - OrderID uuid.UUID `json:"order_id"` - Rating int `json:"rating"` - Comment string `json:"comment"` -} - -type updateUserRequest struct { - CompanyID *uuid.UUID `json:"company_id,omitempty"` - Role *string `json:"role,omitempty"` - Name *string `json:"name,omitempty"` - Email *string `json:"email,omitempty"` - Password *string `json:"password,omitempty"` -} - -type requester struct { - Role string - CompanyID *uuid.UUID -} - -func parsePagination(r *http.Request) (int, int) { - page := 1 - pageSize := 20 - - if v := r.URL.Query().Get("page"); v != "" { - if p, err := strconv.Atoi(v); err == nil && p > 0 { - page = p - } - } - - if v := r.URL.Query().Get("page_size"); v != "" { - if ps, err := strconv.Atoi(v); err == nil && ps > 0 { - pageSize = ps - } - } - - return page, pageSize -} - -func getRequester(r *http.Request) (requester, error) { - if claims, ok := middleware.GetClaims(r.Context()); ok { - return requester{Role: claims.Role, CompanyID: claims.CompanyID}, nil - } - role := r.Header.Get("X-User-Role") - if role == "" { - role = "Admin" - } - - var companyID *uuid.UUID - if cid := r.Header.Get("X-Company-ID"); cid != "" { - id, err := uuid.FromString(cid) - if err != nil { - return requester{}, errors.New("invalid X-Company-ID header") - } - companyID = &id - } - - return requester{Role: role, CompanyID: companyID}, nil -} - -type registerCompanyRequest struct { - Role string `json:"role"` - CNPJ string `json:"cnpj"` - CorporateName string `json:"corporate_name"` - LicenseNumber string `json:"license_number"` -} - -type updateCompanyRequest struct { - Role *string `json:"role,omitempty"` - CNPJ *string `json:"cnpj,omitempty"` - CorporateName *string `json:"corporate_name,omitempty"` - LicenseNumber *string `json:"license_number,omitempty"` - IsVerified *bool `json:"is_verified,omitempty"` -} - -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 updateProductRequest struct { - SellerID *uuid.UUID `json:"seller_id,omitempty"` - Name *string `json:"name,omitempty"` - Description *string `json:"description,omitempty"` - Batch *string `json:"batch,omitempty"` - ExpiresAt *time.Time `json:"expires_at,omitempty"` - PriceCents *int64 `json:"price_cents,omitempty"` - Stock *int64 `json:"stock,omitempty"` -} - -type createOrderRequest struct { - BuyerID uuid.UUID `json:"buyer_id"` - SellerID uuid.UUID `json:"seller_id"` - Items []domain.OrderItem `json:"items"` - Shipping domain.ShippingAddress `json:"shipping"` -} - -type createShipmentRequest struct { - OrderID uuid.UUID `json:"order_id"` - Carrier string `json:"carrier"` - TrackingCode string `json:"tracking_code"` - ExternalTracking string `json:"external_tracking"` -} - -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 - } -}