Initialize base
This commit is contained in:
@@ -0,0 +1,358 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/labstack/echo/v4"
|
||||
|
||||
"tm/internal/domain"
|
||||
"tm/pkg/logger"
|
||||
"tm/pkg/response"
|
||||
)
|
||||
|
||||
// AuthHandler handles authentication related HTTP requests (legacy/generic)
|
||||
// This handler provides backward compatibility and can handle both customer and user authentication
|
||||
type AuthHandler struct {
|
||||
customerAuthService domain.CustomerAuthService
|
||||
userAuthService domain.UserAuthService
|
||||
validator *validator.Validate
|
||||
logger logger.Logger
|
||||
}
|
||||
|
||||
// NewAuthHandler creates a new authentication handler
|
||||
func NewAuthHandler(
|
||||
customerAuthService domain.CustomerAuthService,
|
||||
userAuthService domain.UserAuthService,
|
||||
validator *validator.Validate,
|
||||
logger logger.Logger,
|
||||
) *AuthHandler {
|
||||
return &AuthHandler{
|
||||
customerAuthService: customerAuthService,
|
||||
userAuthService: userAuthService,
|
||||
validator: validator,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// Register handles user registration (defaults to customer registration)
|
||||
// @Summary Register new user
|
||||
// @Description Create a new user account (defaults to customer)
|
||||
// @Tags auth
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param request body domain.RegisterRequest true "Registration request"
|
||||
// @Success 201 {object} response.APIResponse{data=domain.AuthResponse}
|
||||
// @Failure 400 {object} response.APIResponse
|
||||
// @Failure 409 {object} response.APIResponse
|
||||
// @Failure 500 {object} response.APIResponse
|
||||
// @Router /auth/register [post]
|
||||
func (h *AuthHandler) Register(c echo.Context) error {
|
||||
var req domain.RegisterRequest
|
||||
|
||||
// Bind request body
|
||||
if err := c.Bind(&req); err != nil {
|
||||
h.logger.Warn("Failed to bind 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("Register request validation failed", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"email": req.Email,
|
||||
})
|
||||
return response.ValidationError(c, "Validation failed", err.Error())
|
||||
}
|
||||
|
||||
// Convert to customer registration request
|
||||
customerReq := domain.CustomerRegisterRequest{
|
||||
Email: req.Email,
|
||||
Password: req.Password,
|
||||
FirstName: req.FirstName,
|
||||
LastName: req.LastName,
|
||||
CompanyID: req.CompanyID,
|
||||
}
|
||||
|
||||
// Call customer service (default registration is for customers)
|
||||
customerAuthResponse, err := h.customerAuthService.Register(c.Request().Context(), customerReq)
|
||||
if err != nil {
|
||||
h.logger.Error("Registration failed", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"email": req.Email,
|
||||
})
|
||||
|
||||
if err.Error() == "customer already exists" {
|
||||
return response.Conflict(c, "User already exists")
|
||||
}
|
||||
|
||||
return response.InternalServerError(c, "Registration failed")
|
||||
}
|
||||
|
||||
// Convert to generic auth response for backward compatibility
|
||||
authResponse := &domain.AuthResponse{
|
||||
Customer: customerAuthResponse.Customer,
|
||||
AccessToken: customerAuthResponse.AccessToken,
|
||||
RefreshToken: customerAuthResponse.RefreshToken,
|
||||
ExpiresIn: customerAuthResponse.ExpiresIn,
|
||||
}
|
||||
|
||||
return response.Created(c, authResponse, "User registered successfully")
|
||||
}
|
||||
|
||||
// Login handles user authentication (tries both customer and panel user)
|
||||
// @Summary User login
|
||||
// @Description Authenticate user and return tokens
|
||||
// @Tags auth
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param request body domain.LoginRequest true "Login request"
|
||||
// @Success 200 {object} response.APIResponse{data=domain.AuthResponse}
|
||||
// @Failure 400 {object} response.APIResponse
|
||||
// @Failure 401 {object} response.APIResponse
|
||||
// @Failure 500 {object} response.APIResponse
|
||||
// @Router /auth/login [post]
|
||||
func (h *AuthHandler) 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 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("Login request validation failed", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"email": req.Email,
|
||||
})
|
||||
return response.ValidationError(c, "Validation failed", err.Error())
|
||||
}
|
||||
|
||||
// Try customer login first
|
||||
if customerAuthResponse, err := h.customerAuthService.Login(c.Request().Context(), req); err == nil {
|
||||
h.logger.Info("Customer logged in successfully", map[string]interface{}{
|
||||
"customer_id": customerAuthResponse.Customer.ID.String(),
|
||||
"email": customerAuthResponse.Customer.Email,
|
||||
})
|
||||
|
||||
// Convert to generic auth response
|
||||
authResponse := &domain.AuthResponse{
|
||||
Customer: customerAuthResponse.Customer,
|
||||
AccessToken: customerAuthResponse.AccessToken,
|
||||
RefreshToken: customerAuthResponse.RefreshToken,
|
||||
ExpiresIn: customerAuthResponse.ExpiresIn,
|
||||
}
|
||||
|
||||
return response.Success(c, authResponse, "Login successful")
|
||||
}
|
||||
|
||||
// Try panel user login
|
||||
if userAuthResponse, err := h.userAuthService.Login(c.Request().Context(), req); err == nil {
|
||||
h.logger.Info("Panel user logged in successfully", map[string]interface{}{
|
||||
"user_id": userAuthResponse.User.ID.String(),
|
||||
"email": userAuthResponse.User.Email,
|
||||
"role": userAuthResponse.User.Role,
|
||||
})
|
||||
|
||||
// Convert to generic auth response
|
||||
authResponse := &domain.AuthResponse{
|
||||
User: userAuthResponse.User,
|
||||
AccessToken: userAuthResponse.AccessToken,
|
||||
RefreshToken: userAuthResponse.RefreshToken,
|
||||
ExpiresIn: userAuthResponse.ExpiresIn,
|
||||
}
|
||||
|
||||
return response.Success(c, authResponse, "Login successful")
|
||||
}
|
||||
|
||||
h.logger.Warn("Login failed for both customer and user", map[string]interface{}{
|
||||
"email": req.Email,
|
||||
})
|
||||
|
||||
return response.Unauthorized(c, "Invalid email or password")
|
||||
}
|
||||
|
||||
// RefreshToken handles token refresh (tries both customer and panel user)
|
||||
// @Summary Refresh access token
|
||||
// @Description Generate new access token using refresh token
|
||||
// @Tags auth
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param request body RefreshTokenRequest true "Refresh token request"
|
||||
// @Success 200 {object} response.APIResponse{data=domain.AuthResponse}
|
||||
// @Failure 400 {object} response.APIResponse
|
||||
// @Failure 401 {object} response.APIResponse
|
||||
// @Failure 500 {object} response.APIResponse
|
||||
// @Router /auth/refresh [post]
|
||||
func (h *AuthHandler) 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())
|
||||
}
|
||||
|
||||
// Try customer token refresh first
|
||||
if customerAuthResponse, err := h.customerAuthService.RefreshToken(c.Request().Context(), req.RefreshToken); err == nil {
|
||||
// Convert to generic auth response
|
||||
authResponse := &domain.AuthResponse{
|
||||
Customer: customerAuthResponse.Customer,
|
||||
AccessToken: customerAuthResponse.AccessToken,
|
||||
RefreshToken: customerAuthResponse.RefreshToken,
|
||||
ExpiresIn: customerAuthResponse.ExpiresIn,
|
||||
}
|
||||
|
||||
return response.Success(c, authResponse, "Token refreshed successfully")
|
||||
}
|
||||
|
||||
// Try panel user token refresh
|
||||
if userAuthResponse, err := h.userAuthService.RefreshToken(c.Request().Context(), req.RefreshToken); err == nil {
|
||||
// Convert to generic auth response
|
||||
authResponse := &domain.AuthResponse{
|
||||
User: userAuthResponse.User,
|
||||
AccessToken: userAuthResponse.AccessToken,
|
||||
RefreshToken: userAuthResponse.RefreshToken,
|
||||
ExpiresIn: userAuthResponse.ExpiresIn,
|
||||
}
|
||||
|
||||
return response.Success(c, authResponse, "Token refreshed successfully")
|
||||
}
|
||||
|
||||
h.logger.Warn("Token refresh failed for both customer and user", map[string]interface{}{})
|
||||
|
||||
return response.Unauthorized(c, "Invalid refresh token")
|
||||
}
|
||||
|
||||
// ChangePassword handles password change (determines user type from context)
|
||||
// @Summary Change user password
|
||||
// @Description Change authenticated user's password
|
||||
// @Tags 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 /auth/change-password [post]
|
||||
func (h *AuthHandler) 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())
|
||||
}
|
||||
|
||||
// Check if it's a customer
|
||||
if customer, ok := c.Get("customer").(*domain.Customer); ok {
|
||||
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")
|
||||
}
|
||||
|
||||
return response.Success(c, nil, "Password changed successfully")
|
||||
}
|
||||
|
||||
// Check if it's a panel user
|
||||
if user, ok := c.Get("user").(*domain.User); ok {
|
||||
err := h.userAuthService.ChangePassword(c.Request().Context(), user.ID, req.OldPassword, req.NewPassword)
|
||||
if err != nil {
|
||||
h.logger.Error("Panel user password change failed", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"user_id": user.ID.String(),
|
||||
})
|
||||
|
||||
if err.Error() == "invalid old password" {
|
||||
return response.BadRequest(c, "Invalid old password", "")
|
||||
}
|
||||
|
||||
return response.InternalServerError(c, "Password change failed")
|
||||
}
|
||||
|
||||
return response.Success(c, nil, "Password changed successfully")
|
||||
}
|
||||
|
||||
return response.Unauthorized(c, "Authentication required")
|
||||
}
|
||||
|
||||
// GetProfile returns current user profile (determines user type from context)
|
||||
// @Summary Get user profile
|
||||
// @Description Get authenticated user's profile information
|
||||
// @Tags auth
|
||||
// @Produce json
|
||||
// @Security ApiKeyAuth
|
||||
// @Success 200 {object} response.APIResponse{data=domain.User}
|
||||
// @Failure 401 {object} response.APIResponse
|
||||
// @Router /auth/profile [get]
|
||||
func (h *AuthHandler) GetProfile(c echo.Context) error {
|
||||
// Check if it's a customer
|
||||
if customer, ok := c.Get("customer").(*domain.Customer); ok {
|
||||
return response.Success(c, customer, "Profile retrieved successfully")
|
||||
}
|
||||
|
||||
// Check if it's a panel user
|
||||
if user, ok := c.Get("user").(*domain.User); ok {
|
||||
return response.Success(c, user, "Profile retrieved successfully")
|
||||
}
|
||||
|
||||
return response.Unauthorized(c, "Authentication required")
|
||||
}
|
||||
|
||||
// Logout handles user logout (determines user type from context)
|
||||
// @Summary User logout
|
||||
// @Description Logout user (client should remove tokens)
|
||||
// @Tags auth
|
||||
// @Produce json
|
||||
// @Security ApiKeyAuth
|
||||
// @Success 200 {object} response.APIResponse
|
||||
// @Router /auth/logout [post]
|
||||
func (h *AuthHandler) Logout(c echo.Context) error {
|
||||
// Check if it's a customer
|
||||
if customer, ok := c.Get("customer").(*domain.Customer); 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")
|
||||
}
|
||||
|
||||
// Check if it's a panel user
|
||||
if user, ok := c.Get("user").(*domain.User); ok {
|
||||
h.logger.Info("Panel user logged out", map[string]interface{}{
|
||||
"user_id": user.ID.String(),
|
||||
"email": user.Email,
|
||||
})
|
||||
return response.Success(c, nil, "Logged out successfully")
|
||||
}
|
||||
|
||||
return response.Success(c, nil, "Logged out successfully")
|
||||
}
|
||||
@@ -0,0 +1,378 @@
|
||||
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")
|
||||
}
|
||||
@@ -0,0 +1,380 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/google/uuid"
|
||||
"github.com/labstack/echo/v4"
|
||||
|
||||
"tm/internal/domain"
|
||||
"tm/pkg/logger"
|
||||
"tm/pkg/response"
|
||||
)
|
||||
|
||||
// UserAuthHandler handles authentication related HTTP requests for panel users
|
||||
type UserAuthHandler struct {
|
||||
userAuthService domain.UserAuthService
|
||||
validator *validator.Validate
|
||||
logger logger.Logger
|
||||
}
|
||||
|
||||
// NewUserAuthHandler creates a new panel user authentication handler
|
||||
func NewUserAuthHandler(
|
||||
userAuthService domain.UserAuthService,
|
||||
validator *validator.Validate,
|
||||
logger logger.Logger,
|
||||
) *UserAuthHandler {
|
||||
return &UserAuthHandler{
|
||||
userAuthService: userAuthService,
|
||||
validator: validator,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// Login handles panel user authentication
|
||||
// @Summary Panel user login
|
||||
// @Description Authenticate panel user and return tokens
|
||||
// @Tags user-auth
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param request body domain.LoginRequest true "Login request"
|
||||
// @Success 200 {object} response.APIResponse{data=domain.UserAuthResponse}
|
||||
// @Failure 400 {object} response.APIResponse
|
||||
// @Failure 401 {object} response.APIResponse
|
||||
// @Failure 500 {object} response.APIResponse
|
||||
// @Router /api/admin/auth/login [post]
|
||||
func (h *UserAuthHandler) 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 panel user 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("Panel user 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.userAuthService.Login(c.Request().Context(), req)
|
||||
if err != nil {
|
||||
h.logger.Warn("Panel user 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("Panel user logged in successfully", map[string]interface{}{
|
||||
"user_id": authResponse.User.ID.String(),
|
||||
"email": authResponse.User.Email,
|
||||
"role": authResponse.User.Role,
|
||||
})
|
||||
|
||||
return response.Success(c, authResponse, "Login successful")
|
||||
}
|
||||
|
||||
// RefreshToken handles token refresh for panel users
|
||||
// @Summary Refresh panel user access token
|
||||
// @Description Generate new access token using refresh token
|
||||
// @Tags user-auth
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param request body RefreshTokenRequest true "Refresh token request"
|
||||
// @Success 200 {object} response.APIResponse{data=domain.UserAuthResponse}
|
||||
// @Failure 400 {object} response.APIResponse
|
||||
// @Failure 401 {object} response.APIResponse
|
||||
// @Failure 500 {object} response.APIResponse
|
||||
// @Router /api/admin/auth/refresh [post]
|
||||
func (h *UserAuthHandler) 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.userAuthService.RefreshToken(c.Request().Context(), req.RefreshToken)
|
||||
if err != nil {
|
||||
h.logger.Warn("Panel user 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 panel users
|
||||
// @Summary Change panel user password
|
||||
// @Description Change authenticated panel user's password
|
||||
// @Tags user-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/admin/auth/change-password [post]
|
||||
func (h *UserAuthHandler) 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 user from context (set by auth middleware)
|
||||
user, ok := c.Get("user").(*domain.User)
|
||||
if !ok {
|
||||
return response.Unauthorized(c, "Authentication required")
|
||||
}
|
||||
|
||||
// Call service
|
||||
err := h.userAuthService.ChangePassword(c.Request().Context(), user.ID, req.OldPassword, req.NewPassword)
|
||||
if err != nil {
|
||||
h.logger.Error("Panel user password change failed", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"user_id": user.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("Panel user password changed successfully", map[string]interface{}{
|
||||
"user_id": user.ID.String(),
|
||||
})
|
||||
|
||||
return response.Success(c, nil, "Password changed successfully")
|
||||
}
|
||||
|
||||
// GetProfile returns current panel user profile
|
||||
// @Summary Get panel user profile
|
||||
// @Description Get authenticated panel user's profile information
|
||||
// @Tags user-auth
|
||||
// @Produce json
|
||||
// @Security ApiKeyAuth
|
||||
// @Success 200 {object} response.APIResponse{data=domain.User}
|
||||
// @Failure 401 {object} response.APIResponse
|
||||
// @Router /api/admin/auth/profile [get]
|
||||
func (h *UserAuthHandler) GetProfile(c echo.Context) error {
|
||||
// Get user from context (set by auth middleware)
|
||||
user, ok := c.Get("user").(*domain.User)
|
||||
if !ok {
|
||||
return response.Unauthorized(c, "Authentication required")
|
||||
}
|
||||
|
||||
return response.Success(c, user, "Profile retrieved successfully")
|
||||
}
|
||||
|
||||
// CreatePanelUser handles creating new panel users (admin only)
|
||||
// @Summary Create new panel user
|
||||
// @Description Create a new panel user (admin only)
|
||||
// @Tags user-auth
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security ApiKeyAuth
|
||||
// @Param request body domain.CreateUserRequest true "Create user request"
|
||||
// @Success 201 {object} response.APIResponse{data=domain.User}
|
||||
// @Failure 400 {object} response.APIResponse
|
||||
// @Failure 401 {object} response.APIResponse
|
||||
// @Failure 403 {object} response.APIResponse
|
||||
// @Failure 500 {object} response.APIResponse
|
||||
// @Router /api/admin/users [post]
|
||||
func (h *UserAuthHandler) CreatePanelUser(c echo.Context) error {
|
||||
var req domain.CreateUserRequest
|
||||
|
||||
// Bind request body
|
||||
if err := c.Bind(&req); err != nil {
|
||||
h.logger.Warn("Failed to bind create user 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("Create user request validation failed", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"email": req.Email,
|
||||
})
|
||||
return response.ValidationError(c, "Validation failed", err.Error())
|
||||
}
|
||||
|
||||
// Get current user from context (set by auth middleware)
|
||||
currentUser, ok := c.Get("user").(*domain.User)
|
||||
if !ok {
|
||||
return response.Unauthorized(c, "Authentication required")
|
||||
}
|
||||
|
||||
// Check if current user can create users
|
||||
if !currentUser.CanManageUsers() {
|
||||
return response.Forbidden(c, "Insufficient permissions to create users")
|
||||
}
|
||||
|
||||
// Call service
|
||||
newUser, err := h.userAuthService.CreatePanelUser(c.Request().Context(), req, currentUser.ID)
|
||||
if err != nil {
|
||||
h.logger.Error("Panel user creation failed", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"email": req.Email,
|
||||
"created_by": currentUser.ID.String(),
|
||||
})
|
||||
|
||||
if err.Error() == "user already exists" {
|
||||
return response.Conflict(c, "User already exists")
|
||||
}
|
||||
|
||||
return response.InternalServerError(c, "User creation failed")
|
||||
}
|
||||
|
||||
h.logger.Info("Panel user created successfully", map[string]interface{}{
|
||||
"user_id": newUser.ID.String(),
|
||||
"email": newUser.Email,
|
||||
"role": newUser.Role,
|
||||
"created_by": currentUser.ID.String(),
|
||||
})
|
||||
|
||||
return response.Created(c, newUser, "User created successfully")
|
||||
}
|
||||
|
||||
// UpdateUserPermissions handles updating user permissions (admin only)
|
||||
// @Summary Update user permissions
|
||||
// @Description Update panel user permissions (admin only)
|
||||
// @Tags user-auth
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security ApiKeyAuth
|
||||
// @Param user_id path string true "User ID"
|
||||
// @Param request body UpdatePermissionsRequest true "Update permissions request"
|
||||
// @Success 200 {object} response.APIResponse
|
||||
// @Failure 400 {object} response.APIResponse
|
||||
// @Failure 401 {object} response.APIResponse
|
||||
// @Failure 403 {object} response.APIResponse
|
||||
// @Failure 404 {object} response.APIResponse
|
||||
// @Failure 500 {object} response.APIResponse
|
||||
// @Router /api/admin/users/{user_id}/permissions [put]
|
||||
func (h *UserAuthHandler) UpdateUserPermissions(c echo.Context) error {
|
||||
var req struct {
|
||||
Permissions []string `json:"permissions" 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 user ID from path
|
||||
userIDStr := c.Param("user_id")
|
||||
if userIDStr == "" {
|
||||
return response.BadRequest(c, "User ID is required", "")
|
||||
}
|
||||
|
||||
userID, err := uuid.Parse(userIDStr)
|
||||
if err != nil {
|
||||
return response.BadRequest(c, "Invalid user ID format", "")
|
||||
}
|
||||
|
||||
// Get current user from context (set by auth middleware)
|
||||
currentUser, ok := c.Get("user").(*domain.User)
|
||||
if !ok {
|
||||
return response.Unauthorized(c, "Authentication required")
|
||||
}
|
||||
|
||||
// Check if current user can manage users
|
||||
if !currentUser.CanManageUsers() {
|
||||
return response.Forbidden(c, "Insufficient permissions to update user permissions")
|
||||
}
|
||||
|
||||
// Call service
|
||||
err = h.userAuthService.UpdateUserPermissions(c.Request().Context(), userID, req.Permissions, currentUser.ID)
|
||||
if err != nil {
|
||||
h.logger.Error("User permissions update failed", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"user_id": userID.String(),
|
||||
"updated_by": currentUser.ID.String(),
|
||||
})
|
||||
|
||||
if err.Error() == "user not found" {
|
||||
return response.NotFound(c, "User not found")
|
||||
}
|
||||
|
||||
return response.InternalServerError(c, "Permissions update failed")
|
||||
}
|
||||
|
||||
h.logger.Info("User permissions updated successfully", map[string]interface{}{
|
||||
"user_id": userID.String(),
|
||||
"permissions": req.Permissions,
|
||||
"updated_by": currentUser.ID.String(),
|
||||
})
|
||||
|
||||
return response.Success(c, nil, "Permissions updated successfully")
|
||||
}
|
||||
|
||||
// Logout handles panel user logout
|
||||
// @Summary Panel user logout
|
||||
// @Description Logout panel user (client should remove tokens)
|
||||
// @Tags user-auth
|
||||
// @Produce json
|
||||
// @Security ApiKeyAuth
|
||||
// @Success 200 {object} response.APIResponse
|
||||
// @Router /api/admin/auth/logout [post]
|
||||
func (h *UserAuthHandler) 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.
|
||||
|
||||
user, ok := c.Get("user").(*domain.User)
|
||||
if ok {
|
||||
h.logger.Info("Panel user logged out", map[string]interface{}{
|
||||
"user_id": user.ID.String(),
|
||||
"email": user.Email,
|
||||
})
|
||||
}
|
||||
|
||||
return response.Success(c, nil, "Logged out successfully")
|
||||
}
|
||||
Reference in New Issue
Block a user