379 lines
12 KiB
Go
379 lines
12 KiB
Go
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")
|
|
}
|