package handler import ( "github.com/go-playground/validator/v10" "github.com/labstack/echo/v4" "tm/internal/domain" "tm/pkg/logger" "tm/pkg/response" ) // CustomerAuthHandler handles authentication related HTTP requests for mobile customers type CustomerAuthHandler struct { customerAuthService domain.CustomerAuthService validator *validator.Validate logger logger.Logger } // NewCustomerAuthHandler creates a new customer authentication handler func NewCustomerAuthHandler( customerAuthService domain.CustomerAuthService, validator *validator.Validate, logger logger.Logger, ) *CustomerAuthHandler { return &CustomerAuthHandler{ customerAuthService: customerAuthService, validator: validator, logger: logger, } } // Register handles customer registration // @Summary Register new customer // @Description Create a new customer account for mobile app // @Tags customer-auth // @Accept json // @Produce json // @Param request body domain.CustomerRegisterRequest true "Customer registration request" // @Success 201 {object} response.APIResponse{data=domain.CustomerAuthResponse} // @Failure 400 {object} response.APIResponse // @Failure 409 {object} response.APIResponse // @Failure 500 {object} response.APIResponse // @Router /api/mobile/auth/register [post] func (h *CustomerAuthHandler) Register(c echo.Context) error { var req domain.CustomerRegisterRequest // Bind request body if err := c.Bind(&req); err != nil { h.logger.Warn("Failed to bind customer register request", map[string]interface{}{ "error": err.Error(), }) return response.BadRequest(c, "Invalid request format", err.Error()) } // Validate request if err := h.validator.Struct(&req); err != nil { h.logger.Warn("Customer register request validation failed", map[string]interface{}{ "error": err.Error(), "email": req.Email, }) return response.ValidationError(c, "Validation failed", err.Error()) } // Call service authResponse, err := h.customerAuthService.Register(c.Request().Context(), req) if err != nil { h.logger.Error("Customer registration failed", map[string]interface{}{ "error": err.Error(), "email": req.Email, }) if err.Error() == "customer already exists" { return response.Conflict(c, "Customer already exists") } return response.InternalServerError(c, "Registration failed") } h.logger.Info("Customer registered successfully", map[string]interface{}{ "customer_id": authResponse.Customer.ID.String(), "email": authResponse.Customer.Email, }) return response.Created(c, authResponse, "Customer registered successfully") } // Login handles customer authentication // @Summary Customer login // @Description Authenticate customer and return tokens // @Tags customer-auth // @Accept json // @Produce json // @Param request body domain.LoginRequest true "Login request" // @Success 200 {object} response.APIResponse{data=domain.CustomerAuthResponse} // @Failure 400 {object} response.APIResponse // @Failure 401 {object} response.APIResponse // @Failure 500 {object} response.APIResponse // @Router /api/mobile/auth/login [post] func (h *CustomerAuthHandler) Login(c echo.Context) error { var req domain.LoginRequest // Bind request body if err := c.Bind(&req); err != nil { h.logger.Warn("Failed to bind customer login request", map[string]interface{}{ "error": err.Error(), }) return response.BadRequest(c, "Invalid request format", err.Error()) } // Validate request if err := h.validator.Struct(&req); err != nil { h.logger.Warn("Customer login request validation failed", map[string]interface{}{ "error": err.Error(), "email": req.Email, }) return response.ValidationError(c, "Validation failed", err.Error()) } // Call service authResponse, err := h.customerAuthService.Login(c.Request().Context(), req) if err != nil { h.logger.Warn("Customer login failed", map[string]interface{}{ "error": err.Error(), "email": req.Email, }) if err.Error() == "invalid credentials" { return response.Unauthorized(c, "Invalid email or password") } return response.InternalServerError(c, "Login failed") } h.logger.Info("Customer logged in successfully", map[string]interface{}{ "customer_id": authResponse.Customer.ID.String(), "email": authResponse.Customer.Email, }) return response.Success(c, authResponse, "Login successful") } // RefreshToken handles token refresh for customers // @Summary Refresh customer access token // @Description Generate new access token using refresh token // @Tags customer-auth // @Accept json // @Produce json // @Param request body RefreshTokenRequest true "Refresh token request" // @Success 200 {object} response.APIResponse{data=domain.CustomerAuthResponse} // @Failure 400 {object} response.APIResponse // @Failure 401 {object} response.APIResponse // @Failure 500 {object} response.APIResponse // @Router /api/mobile/auth/refresh [post] func (h *CustomerAuthHandler) RefreshToken(c echo.Context) error { var req struct { RefreshToken string `json:"refresh_token" validate:"required"` } // Bind request body if err := c.Bind(&req); err != nil { return response.BadRequest(c, "Invalid request format", err.Error()) } // Validate request if err := h.validator.Struct(&req); err != nil { return response.ValidationError(c, "Validation failed", err.Error()) } // Call service authResponse, err := h.customerAuthService.RefreshToken(c.Request().Context(), req.RefreshToken) if err != nil { h.logger.Warn("Customer token refresh failed", map[string]interface{}{ "error": err.Error(), }) if err.Error() == "invalid refresh token" { return response.Unauthorized(c, "Invalid refresh token") } return response.InternalServerError(c, "Token refresh failed") } return response.Success(c, authResponse, "Token refreshed successfully") } // ChangePassword handles password change for customers // @Summary Change customer password // @Description Change authenticated customer's password // @Tags customer-auth // @Accept json // @Produce json // @Security ApiKeyAuth // @Param request body ChangePasswordRequest true "Change password request" // @Success 200 {object} response.APIResponse // @Failure 400 {object} response.APIResponse // @Failure 401 {object} response.APIResponse // @Failure 500 {object} response.APIResponse // @Router /api/mobile/auth/change-password [post] func (h *CustomerAuthHandler) ChangePassword(c echo.Context) error { var req struct { OldPassword string `json:"old_password" validate:"required"` NewPassword string `json:"new_password" validate:"required,min=8"` } // Bind request body if err := c.Bind(&req); err != nil { return response.BadRequest(c, "Invalid request format", err.Error()) } // Validate request if err := h.validator.Struct(&req); err != nil { return response.ValidationError(c, "Validation failed", err.Error()) } // Get customer from context (set by auth middleware) customer, ok := c.Get("customer").(*domain.Customer) if !ok { return response.Unauthorized(c, "Authentication required") } // Call service err := h.customerAuthService.ChangePassword(c.Request().Context(), customer.ID, req.OldPassword, req.NewPassword) if err != nil { h.logger.Error("Customer password change failed", map[string]interface{}{ "error": err.Error(), "customer_id": customer.ID.String(), }) if err.Error() == "invalid old password" { return response.BadRequest(c, "Invalid old password", "") } return response.InternalServerError(c, "Password change failed") } h.logger.Info("Customer password changed successfully", map[string]interface{}{ "customer_id": customer.ID.String(), }) return response.Success(c, nil, "Password changed successfully") } // GetProfile returns current customer profile // @Summary Get customer profile // @Description Get authenticated customer's profile information // @Tags customer-auth // @Produce json // @Security ApiKeyAuth // @Success 200 {object} response.APIResponse{data=domain.Customer} // @Failure 401 {object} response.APIResponse // @Router /api/mobile/auth/profile [get] func (h *CustomerAuthHandler) GetProfile(c echo.Context) error { // Get customer from context (set by auth middleware) customer, ok := c.Get("customer").(*domain.Customer) if !ok { return response.Unauthorized(c, "Authentication required") } return response.Success(c, customer, "Profile retrieved successfully") } // VerifyEmail handles email verification for customers // @Summary Verify customer email // @Description Verify customer's email address using token // @Tags customer-auth // @Accept json // @Produce json // @Param request body VerifyEmailRequest true "Email verification request" // @Success 200 {object} response.APIResponse // @Failure 400 {object} response.APIResponse // @Failure 500 {object} response.APIResponse // @Router /api/mobile/auth/verify-email [post] func (h *CustomerAuthHandler) VerifyEmail(c echo.Context) error { var req struct { Token string `json:"token" validate:"required"` } // Bind request body if err := c.Bind(&req); err != nil { return response.BadRequest(c, "Invalid request format", err.Error()) } // Validate request if err := h.validator.Struct(&req); err != nil { return response.ValidationError(c, "Validation failed", err.Error()) } // Get customer from context (set by auth middleware) customer, ok := c.Get("customer").(*domain.Customer) if !ok { return response.Unauthorized(c, "Authentication required") } // Call service err := h.customerAuthService.VerifyEmail(c.Request().Context(), customer.ID, req.Token) if err != nil { h.logger.Error("Customer email verification failed", map[string]interface{}{ "error": err.Error(), "customer_id": customer.ID.String(), }) if err.Error() == "invalid verification token" { return response.BadRequest(c, "Invalid verification token", "") } return response.InternalServerError(c, "Email verification failed") } h.logger.Info("Customer email verified successfully", map[string]interface{}{ "customer_id": customer.ID.String(), }) return response.Success(c, nil, "Email verified successfully") } // ResendVerification handles resending verification email // @Summary Resend verification email // @Description Resend verification email to customer // @Tags customer-auth // @Accept json // @Produce json // @Param request body ResendVerificationRequest true "Resend verification request" // @Success 200 {object} response.APIResponse // @Failure 400 {object} response.APIResponse // @Failure 500 {object} response.APIResponse // @Router /api/mobile/auth/resend-verification [post] func (h *CustomerAuthHandler) ResendVerification(c echo.Context) error { var req struct { Email string `json:"email" validate:"required,email"` } // Bind request body if err := c.Bind(&req); err != nil { return response.BadRequest(c, "Invalid request format", err.Error()) } // Validate request if err := h.validator.Struct(&req); err != nil { return response.ValidationError(c, "Validation failed", err.Error()) } // Call service err := h.customerAuthService.ResendVerification(c.Request().Context(), req.Email) if err != nil { h.logger.Error("Resend verification failed", map[string]interface{}{ "error": err.Error(), "email": req.Email, }) return response.InternalServerError(c, "Failed to resend verification email") } return response.Success(c, nil, "Verification email sent successfully") } // Logout handles customer logout // @Summary Customer logout // @Description Logout customer (client should remove tokens) // @Tags customer-auth // @Produce json // @Security ApiKeyAuth // @Success 200 {object} response.APIResponse // @Router /api/mobile/auth/logout [post] func (h *CustomerAuthHandler) Logout(c echo.Context) error { // In a stateless JWT implementation, logout is typically handled client-side // by removing the tokens from storage. However, you could implement token // blacklisting here if needed. customer, ok := c.Get("customer").(*domain.Customer) if ok { h.logger.Info("Customer logged out", map[string]interface{}{ "customer_id": customer.ID.String(), "email": customer.Email, }) } return response.Success(c, nil, "Logged out successfully") }