package handler import ( "context" "database/sql" "errors" "net/http" jsoniter "github.com/json-iterator/go" "github.com/gofrs/uuid/v5" "github.com/saveinmed/backend-go/internal/domain" "github.com/saveinmed/backend-go/internal/http/middleware" "github.com/saveinmed/backend-go/internal/usecase" ) var json = jsoniter.ConfigCompatibleWithStandardLibrary type Handler struct { svc *usecase.Service buyerFeeRate float64 // Rate to inflate prices for buyers (e.g., 0.12 = 12%) } func New(svc *usecase.Service, buyerFeeRate float64) *Handler { return &Handler{svc: svc, buyerFeeRate: buyerFeeRate} } // Register godoc // @Summary Cadastro de usuário // @Description Cria um usuário e opcionalmente uma empresa, retornando token JWT. // @Tags Autenticação // @Accept json // @Produce json // @Param payload body registerAuthRequest true "Dados do usuário e empresa" // @Success 201 {object} authResponse // @Failure 400 {object} map[string]string // @Failure 500 {object} map[string]string // @Router /api/v1/auth/register [post] func (h *Handler) Register(w http.ResponseWriter, r *http.Request) { var req registerAuthRequest if err := decodeJSON(r.Context(), r, &req); err != nil { writeError(w, http.StatusBadRequest, err) return } var company *domain.Company if req.Company != nil { company = &domain.Company{ ID: req.Company.ID, Category: req.Company.Category, CNPJ: req.Company.CNPJ, CorporateName: req.Company.CorporateName, LicenseNumber: req.Company.LicenseNumber, Latitude: req.Company.Latitude, Longitude: req.Company.Longitude, City: req.Company.City, State: req.Company.State, } } var companyID uuid.UUID if req.CompanyID != nil { companyID = *req.CompanyID } user := &domain.User{ CompanyID: companyID, Role: req.Role, Name: req.Name, Username: req.Username, Email: req.Email, } if user.CompanyID == uuid.Nil && company == nil { writeError(w, http.StatusBadRequest, errors.New("company_id or company payload is required")) return } if err := h.svc.RegisterAccount(r.Context(), company, user, req.Password); err != nil { writeError(w, http.StatusInternalServerError, err) return } token, exp, err := h.svc.Authenticate(r.Context(), user.Username, req.Password) if err != nil { writeError(w, http.StatusInternalServerError, err) return } writeJSON(w, http.StatusCreated, authResponse{Token: token, ExpiresAt: exp}) } // Login godoc // @Summary Login // @Description Autentica usuário e retorna token JWT. // @Tags Autenticação // @Accept json // @Produce json // @Param payload body loginRequest true "Credenciais" // @Success 200 {object} authResponse // @Failure 400 {object} map[string]string // @Failure 401 {object} map[string]string // @Router /api/v1/auth/login [post] func (h *Handler) Login(w http.ResponseWriter, r *http.Request) { var req loginRequest if err := decodeJSON(r.Context(), r, &req); err != nil { writeError(w, http.StatusBadRequest, err) return } identifier := req.Email if identifier == "" { identifier = req.Username } token, exp, err := h.svc.Authenticate(r.Context(), identifier, req.Password) if err != nil { writeError(w, http.StatusUnauthorized, err) return } writeJSON(w, http.StatusOK, authResponse{Token: token, ExpiresAt: exp}) } // RegisterCustomer godoc // @Summary Cadastro de cliente // @Description Cria um usuário do tipo cliente e opcionalmente uma empresa, retornando token JWT. // @Tags Autenticação // @Accept json // @Produce json // @Param payload body registerAuthRequest true "Dados do usuário e empresa" // @Success 201 {object} authResponse // @Failure 400 {object} map[string]string // @Failure 500 {object} map[string]string // @Router /api/v1/auth/register/customer [post] func (h *Handler) RegisterCustomer(w http.ResponseWriter, r *http.Request) { var req registerAuthRequest if err := decodeJSON(r.Context(), r, &req); err != nil { writeError(w, http.StatusBadRequest, err) return } req.Role = "Customer" h.registerWithPayload(w, r, req) } // RegisterTenant godoc // @Summary Cadastro de tenant // @Description Cria um usuário do tipo tenant e opcionalmente uma empresa, retornando token JWT. // @Tags Autenticação // @Accept json // @Produce json // @Param payload body registerAuthRequest true "Dados do usuário e empresa" // @Success 201 {object} authResponse // @Failure 400 {object} map[string]string // @Failure 500 {object} map[string]string // @Router /api/v1/auth/register/tenant [post] func (h *Handler) RegisterTenant(w http.ResponseWriter, r *http.Request) { var req registerAuthRequest if err := decodeJSON(r.Context(), r, &req); err != nil { writeError(w, http.StatusBadRequest, err) return } req.Role = "Seller" h.registerWithPayload(w, r, req) } // RefreshToken godoc // @Summary Atualizar token // @Description Gera um novo JWT a partir de um token válido. // @Tags Autenticação // @Accept json // @Produce json // @Param Authorization header string true "Bearer token" // @Success 200 {object} authResponse // @Failure 401 {object} map[string]string // @Router /api/v1/auth/refresh-token [post] func (h *Handler) RefreshToken(w http.ResponseWriter, r *http.Request) { tokenStr, err := parseBearerToken(r) if err != nil { writeError(w, http.StatusUnauthorized, err) return } token, exp, err := h.svc.RefreshToken(r.Context(), tokenStr) if err != nil { writeError(w, http.StatusUnauthorized, err) return } writeJSON(w, http.StatusOK, authResponse{Token: token, ExpiresAt: exp}) } // Logout godoc // @Summary Logout // @Description Endpoint para logout (invalidação client-side). // @Tags Autenticação // @Success 204 {string} string "No Content" // @Router /api/v1/auth/logout [post] func (h *Handler) Logout(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusNoContent) } // ForgotPassword godoc // @Summary Solicitar redefinição de senha // @Description Gera um token de redefinição de senha. // @Tags Autenticação // @Accept json // @Produce json // @Param payload body forgotPasswordRequest true "Email do usuário" // @Success 202 {object} resetTokenResponse // @Failure 400 {object} map[string]string // @Router /api/v1/auth/password/forgot [post] func (h *Handler) ForgotPassword(w http.ResponseWriter, r *http.Request) { var req forgotPasswordRequest if err := decodeJSON(r.Context(), r, &req); err != nil { writeError(w, http.StatusBadRequest, err) return } if req.Email == "" { writeError(w, http.StatusBadRequest, errors.New("email is required")) return } token, exp, err := h.svc.CreatePasswordResetToken(r.Context(), req.Email) if err != nil { if errors.Is(err, sql.ErrNoRows) { writeJSON(w, http.StatusAccepted, resetTokenResponse{ Message: "Se existir uma conta, enviaremos instruções de redefinição.", }) return } writeError(w, http.StatusInternalServerError, err) return } writeJSON(w, http.StatusAccepted, resetTokenResponse{ Message: "Token de redefinição gerado.", ResetToken: token, ExpiresAt: &exp, }) } // ResetPassword godoc // @Summary Redefinir senha // @Description Atualiza a senha usando o token de redefinição. // @Tags Autenticação // @Accept json // @Produce json // @Param payload body resetPasswordRequest true "Token e nova senha" // @Success 200 {object} messageResponse // @Failure 400 {object} map[string]string // @Failure 401 {object} map[string]string // @Router /api/v1/auth/password/reset [post] func (h *Handler) ResetPassword(w http.ResponseWriter, r *http.Request) { var req resetPasswordRequest if err := decodeJSON(r.Context(), r, &req); err != nil { writeError(w, http.StatusBadRequest, err) return } if req.Token == "" || req.Password == "" { writeError(w, http.StatusBadRequest, errors.New("token and password are required")) return } if err := h.svc.ResetPassword(r.Context(), req.Token, req.Password); err != nil { writeError(w, http.StatusUnauthorized, err) return } writeJSON(w, http.StatusOK, messageResponse{Message: "Senha atualizada com sucesso."}) } // VerifyEmail godoc // @Summary Verificar email // @Description Marca o email como verificado usando um token JWT. // @Tags Autenticação // @Accept json // @Produce json // @Param payload body verifyEmailRequest true "Token de verificação" // @Success 200 {object} messageResponse // @Failure 400 {object} map[string]string // @Failure 401 {object} map[string]string // @Router /api/v1/auth/verify-email [post] func (h *Handler) VerifyEmail(w http.ResponseWriter, r *http.Request) { var req verifyEmailRequest if err := decodeJSON(r.Context(), r, &req); err != nil { writeError(w, http.StatusBadRequest, err) return } if req.Token == "" { writeError(w, http.StatusBadRequest, errors.New("token is required")) return } if _, err := h.svc.VerifyEmail(r.Context(), req.Token); err != nil { writeError(w, http.StatusUnauthorized, err) return } writeJSON(w, http.StatusOK, messageResponse{Message: "E-mail verificado com sucesso."}) } func (h *Handler) registerWithPayload(w http.ResponseWriter, r *http.Request, req registerAuthRequest) { var company *domain.Company if req.Company != nil { company = &domain.Company{ ID: req.Company.ID, Category: req.Company.Category, CNPJ: req.Company.CNPJ, CorporateName: req.Company.CorporateName, LicenseNumber: req.Company.LicenseNumber, Latitude: req.Company.Latitude, Longitude: req.Company.Longitude, City: req.Company.City, State: req.Company.State, } } var companyID uuid.UUID if req.CompanyID != nil { companyID = *req.CompanyID } user := &domain.User{ CompanyID: companyID, Role: req.Role, Name: req.Name, Email: req.Email, } if user.CompanyID == uuid.Nil && company == nil { writeError(w, http.StatusBadRequest, errors.New("company_id or company payload is required")) return } if err := h.svc.RegisterAccount(r.Context(), company, user, req.Password); err != nil { writeError(w, http.StatusInternalServerError, err) return } token, exp, err := h.svc.Authenticate(r.Context(), user.Email, req.Password) if err != nil { writeError(w, http.StatusInternalServerError, err) return } writeJSON(w, http.StatusCreated, authResponse{Token: token, ExpiresAt: exp}) } func (h *Handler) getUserFromContext(ctx context.Context) (*domain.User, error) { claims, ok := middleware.GetClaims(ctx) if !ok { return nil, errors.New("unauthorized") } var cid uuid.UUID if claims.CompanyID != nil { cid = *claims.CompanyID } return &domain.User{ ID: claims.UserID, Role: claims.Role, CompanyID: cid, }, nil }