9119e2383e
- Introduced a new `config.yaml` file for managing server, database, cache, queue, AI, scraping, logging, and rate limiting configurations. - Updated `docker-compose.yml` to reflect the new server port (8081) and adjusted health check endpoints accordingly. - Modified `Dockerfile` to expose the new server port. - Updated `README.md` to reflect changes in server configuration and added documentation for the new configuration structure. - Added test scripts for server and Swagger documentation testing. - Refactored customer domain structure to align with new configuration settings and improve maintainability.
572 lines
19 KiB
Go
572 lines
19 KiB
Go
package customer
|
|
|
|
import (
|
|
"net/http"
|
|
"tm/pkg/response"
|
|
|
|
"github.com/asaskevich/govalidator"
|
|
"github.com/google/uuid"
|
|
"github.com/labstack/echo/v4"
|
|
)
|
|
|
|
// Handler handles HTTP requests for customer operations
|
|
type Handler struct {
|
|
service Service
|
|
}
|
|
|
|
// NewHandler creates a new customer handler
|
|
func NewHandler(service Service) *Handler {
|
|
return &Handler{
|
|
service: service,
|
|
}
|
|
}
|
|
|
|
// RegisterRoutes registers all customer routes
|
|
func (h *Handler) RegisterRoutes(e *echo.Echo) {
|
|
|
|
v1 := e.Group("/api/v1")
|
|
|
|
v1.POST("/customers/register", h.RegisterCustomer)
|
|
// Public routes (no authentication required)
|
|
v1.POST("/customers/login", h.Login)
|
|
v1.POST("/customers/refresh-token", h.RefreshToken)
|
|
|
|
// Protected routes (authentication required)
|
|
customers := v1.Group("/customers")
|
|
customers.Use(h.authMiddleware) // TODO: Implement auth middleware
|
|
customers.GET("/profile", h.GetProfile)
|
|
customers.PUT("/profile", h.UpdateProfile)
|
|
customers.PUT("/change-password", h.ChangePassword)
|
|
customers.POST("/device-token", h.AddDeviceToken)
|
|
customers.DELETE("/device-token", h.RemoveDeviceToken)
|
|
customers.POST("/logout", h.Logout)
|
|
|
|
// Admin routes (admin authentication required)
|
|
admin := v1.Group("/admin/customers")
|
|
admin.Use(h.adminAuthMiddleware) // TODO: Implement admin auth middleware
|
|
admin.POST("/register", h.RegisterCustomer)
|
|
admin.GET("", h.ListCustomers)
|
|
admin.GET("/:id", h.GetCustomerByID)
|
|
admin.PUT("/:id/status", h.UpdateCustomerStatus)
|
|
admin.GET("/device-tokens", h.GetAllDeviceTokens)
|
|
}
|
|
|
|
// RegisterCustomer handles customer registration
|
|
// @Summary Register a new customer
|
|
// @Description Register a new customer with the provided information
|
|
// @Tags customers
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param customer body RegisterCustomerForm true "Customer registration information"
|
|
// @Success 201 {object} response.APIResponse{data=customer.CustomerResponse} "Customer registered successfully"
|
|
// @Failure 400 {object} response.APIResponse "Bad request"
|
|
// @Failure 409 {object} response.APIResponse "Customer already exists"
|
|
// @Failure 500 {object} response.APIResponse "Internal server error"
|
|
// @Router /admin/customers/register [post]
|
|
func (h *Handler) RegisterCustomer(c echo.Context) error {
|
|
var form RegisterCustomerForm
|
|
|
|
if err := c.Bind(&form); err != nil {
|
|
return response.BadRequest(c, "Invalid request body", err.Error())
|
|
}
|
|
|
|
// Validate form
|
|
if _, err := govalidator.ValidateStruct(form); err != nil {
|
|
return response.ValidationError(c, "Validation failed", err.Error())
|
|
}
|
|
|
|
// Register customer
|
|
customer, err := h.service.RegisterCustomer(c.Request().Context(), &form)
|
|
if err != nil {
|
|
return response.Conflict(c, err.Error())
|
|
}
|
|
|
|
return response.Created(c, customer.ToResponse(), "Customer registered successfully")
|
|
}
|
|
|
|
// Login handles customer authentication
|
|
// @Summary Customer login
|
|
// @Description Authenticate customer with email and password
|
|
// @Tags customers
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param credentials body LoginForm true "Login credentials"
|
|
// @Success 200 {object} response.APIResponse{data=customer.AuthResponse} "Login successful"
|
|
// @Failure 400 {object} response.APIResponse "Bad request"
|
|
// @Failure 401 {object} response.APIResponse "Invalid credentials"
|
|
// @Failure 500 {object} response.APIResponse "Internal server error"
|
|
// @Router /customers/login [post]
|
|
func (h *Handler) Login(c echo.Context) error {
|
|
var form LoginForm
|
|
|
|
if err := c.Bind(&form); err != nil {
|
|
return response.BadRequest(c, "Invalid request body", err.Error())
|
|
}
|
|
|
|
// Validate form
|
|
if _, err := govalidator.ValidateStruct(form); err != nil {
|
|
return response.ValidationError(c, "Validation failed", err.Error())
|
|
}
|
|
|
|
// Authenticate customer
|
|
authResponse, err := h.service.Login(c.Request().Context(), &form)
|
|
if err != nil {
|
|
return response.Unauthorized(c, err.Error())
|
|
}
|
|
|
|
return response.Success(c, authResponse, "Login successful")
|
|
}
|
|
|
|
// RefreshToken handles token refresh
|
|
// @Summary Refresh access token
|
|
// @Description Refresh the access token using a refresh token
|
|
// @Tags customers
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param token body RefreshTokenForm true "Refresh token information"
|
|
// @Success 200 {object} response.APIResponse{data=customer.AuthResponse} "Token refreshed successfully"
|
|
// @Failure 400 {object} response.APIResponse "Bad request"
|
|
// @Failure 401 {object} response.APIResponse "Invalid refresh token"
|
|
// @Failure 500 {object} response.APIResponse "Internal server error"
|
|
// @Router /customers/refresh-token [post]
|
|
func (h *Handler) RefreshToken(c echo.Context) error {
|
|
var form RefreshTokenForm
|
|
|
|
if err := c.Bind(&form); err != nil {
|
|
return response.BadRequest(c, "Invalid request body", err.Error())
|
|
}
|
|
|
|
// Validate form
|
|
if _, err := govalidator.ValidateStruct(form); err != nil {
|
|
return response.ValidationError(c, "Validation failed", err.Error())
|
|
}
|
|
|
|
// Refresh token
|
|
authResponse, err := h.service.RefreshToken(c.Request().Context(), form.RefreshToken)
|
|
if err != nil {
|
|
return response.Unauthorized(c, err.Error())
|
|
}
|
|
|
|
return response.Success(c, authResponse, "Token refreshed successfully")
|
|
}
|
|
|
|
// GetProfile retrieves customer profile
|
|
// @Summary Get customer profile
|
|
// @Description Retrieve the authenticated customer's profile information
|
|
// @Tags customers
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Security BearerAuth
|
|
// @Success 200 {object} response.APIResponse{data=customer.CustomerResponse} "Profile retrieved successfully"
|
|
// @Failure 401 {object} response.APIResponse "Unauthorized"
|
|
// @Failure 404 {object} response.APIResponse "Customer not found"
|
|
// @Failure 500 {object} response.APIResponse "Internal server error"
|
|
// @Router /customers/profile [get]
|
|
func (h *Handler) GetProfile(c echo.Context) error {
|
|
// Get customer ID from context (set by auth middleware)
|
|
customerID, err := h.getCustomerIDFromContext(c)
|
|
if err != nil {
|
|
return response.Unauthorized(c, "Invalid authentication")
|
|
}
|
|
|
|
// Get customer
|
|
customer, err := h.service.GetCustomerByID(c.Request().Context(), customerID)
|
|
if err != nil {
|
|
return response.NotFound(c, "Customer not found")
|
|
}
|
|
|
|
return response.Success(c, customer.ToResponse(), "Profile retrieved successfully")
|
|
}
|
|
|
|
// UpdateProfile updates customer profile
|
|
// @Summary Update customer profile
|
|
// @Description Update the authenticated customer's profile information
|
|
// @Tags customers
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Security BearerAuth
|
|
// @Param profile body UpdateCustomerForm true "Profile update information"
|
|
// @Success 200 {object} response.APIResponse{data=customer.CustomerResponse} "Profile updated successfully"
|
|
// @Failure 400 {object} response.APIResponse "Bad request"
|
|
// @Failure 401 {object} response.APIResponse "Unauthorized"
|
|
// @Failure 500 {object} response.APIResponse "Internal server error"
|
|
// @Router /customers/profile [put]
|
|
func (h *Handler) UpdateProfile(c echo.Context) error {
|
|
// Get customer ID from context
|
|
customerID, err := h.getCustomerIDFromContext(c)
|
|
if err != nil {
|
|
return response.Unauthorized(c, "Invalid authentication")
|
|
}
|
|
|
|
var form UpdateCustomerForm
|
|
|
|
if err := c.Bind(&form); err != nil {
|
|
return response.BadRequest(c, "Invalid request body", err.Error())
|
|
}
|
|
|
|
// Validate form
|
|
if _, err := govalidator.ValidateStruct(form); err != nil {
|
|
return response.ValidationError(c, "Validation failed", err.Error())
|
|
}
|
|
|
|
// Update customer
|
|
customer, err := h.service.UpdateCustomer(c.Request().Context(), customerID, &form)
|
|
if err != nil {
|
|
return response.BadRequest(c, err.Error(), "")
|
|
}
|
|
|
|
return response.Success(c, customer.ToResponse(), "Profile updated successfully")
|
|
}
|
|
|
|
// ChangePassword changes customer password
|
|
// @Summary Change customer password
|
|
// @Description Change the authenticated customer's password
|
|
// @Tags customers
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Security BearerAuth
|
|
// @Param password body ChangePasswordForm true "Password change information"
|
|
// @Success 200 {object} response.APIResponse "Password changed successfully"
|
|
// @Failure 400 {object} response.APIResponse "Bad request"
|
|
// @Failure 401 {object} response.APIResponse "Unauthorized"
|
|
// @Failure 500 {object} response.APIResponse "Internal server error"
|
|
// @Router /customers/change-password [put]
|
|
func (h *Handler) ChangePassword(c echo.Context) error {
|
|
// Get customer ID from context
|
|
customerID, err := h.getCustomerIDFromContext(c)
|
|
if err != nil {
|
|
return response.Unauthorized(c, "Invalid authentication")
|
|
}
|
|
|
|
var form ChangePasswordForm
|
|
|
|
if err := c.Bind(&form); err != nil {
|
|
return response.BadRequest(c, "Invalid request body", err.Error())
|
|
}
|
|
|
|
// Validate form
|
|
if _, err := govalidator.ValidateStruct(form); err != nil {
|
|
return response.ValidationError(c, "Validation failed", err.Error())
|
|
}
|
|
|
|
// Change password
|
|
err = h.service.ChangePassword(c.Request().Context(), customerID, &form)
|
|
if err != nil {
|
|
return response.BadRequest(c, err.Error(), "")
|
|
}
|
|
|
|
return response.Success(c, nil, "Password changed successfully")
|
|
}
|
|
|
|
// AddDeviceToken adds a device token for push notifications
|
|
// @Summary Add device token
|
|
// @Description Add a device token for push notifications
|
|
// @Tags customers
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Security BearerAuth
|
|
// @Param device_token body AddDeviceTokenForm true "Device token information"
|
|
// @Success 200 {object} response.APIResponse "Device token added successfully"
|
|
// @Failure 400 {object} response.APIResponse "Bad request"
|
|
// @Failure 401 {object} response.APIResponse "Unauthorized"
|
|
// @Failure 500 {object} response.APIResponse "Internal server error"
|
|
// @Router /customers/device-token [post]
|
|
func (h *Handler) AddDeviceToken(c echo.Context) error {
|
|
// Get customer ID from context
|
|
customerID, err := h.getCustomerIDFromContext(c)
|
|
if err != nil {
|
|
return response.Unauthorized(c, "Invalid authentication")
|
|
}
|
|
|
|
var form AddDeviceTokenForm
|
|
|
|
if err := c.Bind(&form); err != nil {
|
|
return response.BadRequest(c, "Invalid request body", err.Error())
|
|
}
|
|
|
|
// Validate form
|
|
if _, err := govalidator.ValidateStruct(form); err != nil {
|
|
return response.ValidationError(c, "Validation failed", err.Error())
|
|
}
|
|
|
|
// Add device token
|
|
err = h.service.AddDeviceToken(c.Request().Context(), customerID, &form)
|
|
if err != nil {
|
|
return response.BadRequest(c, err.Error(), "")
|
|
}
|
|
|
|
return response.Success(c, nil, "Device token added successfully")
|
|
}
|
|
|
|
// RemoveDeviceToken removes a device token
|
|
// @Summary Remove device token
|
|
// @Description Remove a device token for push notifications
|
|
// @Tags customers
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Security BearerAuth
|
|
// @Param device_token body RemoveDeviceTokenForm true "Device token information"
|
|
// @Success 200 {object} response.APIResponse "Device token removed successfully"
|
|
// @Failure 400 {object} response.APIResponse "Bad request"
|
|
// @Failure 401 {object} response.APIResponse "Unauthorized"
|
|
// @Failure 500 {object} response.APIResponse "Internal server error"
|
|
// @Router /customers/device-token [delete]
|
|
func (h *Handler) RemoveDeviceToken(c echo.Context) error {
|
|
// Get customer ID from context
|
|
customerID, err := h.getCustomerIDFromContext(c)
|
|
if err != nil {
|
|
return response.Unauthorized(c, "Invalid authentication")
|
|
}
|
|
|
|
var form RemoveDeviceTokenForm
|
|
|
|
if err := c.Bind(&form); err != nil {
|
|
return response.BadRequest(c, "Invalid request body", err.Error())
|
|
}
|
|
|
|
// Validate form
|
|
if _, err := govalidator.ValidateStruct(form); err != nil {
|
|
return response.ValidationError(c, "Validation failed", err.Error())
|
|
}
|
|
|
|
// Remove device token
|
|
err = h.service.RemoveDeviceToken(c.Request().Context(), customerID, &form)
|
|
if err != nil {
|
|
return response.BadRequest(c, err.Error(), "")
|
|
}
|
|
|
|
return response.Success(c, nil, "Device token removed successfully")
|
|
}
|
|
|
|
// Logout handles customer logout
|
|
// @Summary Customer logout
|
|
// @Description Logout customer and invalidate device token
|
|
// @Tags customers
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Security BearerAuth
|
|
// @Param logout body object true "Logout request with device token"
|
|
// @Success 200 {object} response.APIResponse "Logged out successfully"
|
|
// @Failure 400 {object} response.APIResponse "Bad request"
|
|
// @Failure 401 {object} response.APIResponse "Unauthorized"
|
|
// @Failure 500 {object} response.APIResponse "Internal server error"
|
|
// @Router /customers/logout [post]
|
|
func (h *Handler) Logout(c echo.Context) error {
|
|
// Get customer ID from context
|
|
customerID, err := h.getCustomerIDFromContext(c)
|
|
if err != nil {
|
|
return response.Unauthorized(c, "Invalid authentication")
|
|
}
|
|
|
|
// Get device token from request body
|
|
var req struct {
|
|
DeviceToken string `json:"device_token" valid:"required"`
|
|
}
|
|
|
|
if err := c.Bind(&req); err != nil {
|
|
return response.BadRequest(c, "Invalid request body", err.Error())
|
|
}
|
|
|
|
// Validate request
|
|
if _, err := govalidator.ValidateStruct(req); err != nil {
|
|
return response.ValidationError(c, "Validation failed", err.Error())
|
|
}
|
|
|
|
// Logout
|
|
err = h.service.Logout(c.Request().Context(), customerID, req.DeviceToken)
|
|
if err != nil {
|
|
return response.BadRequest(c, err.Error(), "")
|
|
}
|
|
|
|
return response.Success(c, nil, "Logged out successfully")
|
|
}
|
|
|
|
// ListCustomers lists customers (admin only)
|
|
// @Summary List customers
|
|
// @Description Retrieve a paginated list of customers (admin only)
|
|
// @Tags customers
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Security BearerAuth
|
|
// @Param page query int false "Page number" default(1)
|
|
// @Param limit query int false "Number of items per page" default(20)
|
|
// @Param search query string false "Search term"
|
|
// @Success 200 {object} response.APIResponse{data=[]customer.CustomerResponse} "Customers retrieved successfully"
|
|
// @Failure 400 {object} response.APIResponse "Bad request"
|
|
// @Failure 401 {object} response.APIResponse "Unauthorized"
|
|
// @Failure 500 {object} response.APIResponse "Internal server error"
|
|
// @Router /admin/customers [get]
|
|
func (h *Handler) ListCustomers(c echo.Context) error {
|
|
var form ListCustomersForm
|
|
|
|
// Bind query parameters
|
|
if err := c.Bind(&form); err != nil {
|
|
return response.BadRequest(c, "Invalid query parameters", err.Error())
|
|
}
|
|
|
|
// Validate form
|
|
if _, err := govalidator.ValidateStruct(form); err != nil {
|
|
return response.ValidationError(c, "Validation failed", err.Error())
|
|
}
|
|
|
|
// List customers
|
|
customers, total, err := h.service.ListCustomers(c.Request().Context(), &form)
|
|
if err != nil {
|
|
return response.InternalServerError(c, err.Error())
|
|
}
|
|
|
|
// Convert to responses
|
|
var responses []*CustomerResponse
|
|
for _, customer := range customers {
|
|
responses = append(responses, customer.ToResponse())
|
|
}
|
|
|
|
// Create metadata
|
|
meta := &response.Meta{
|
|
Total: total,
|
|
Limit: 20, // Default limit
|
|
Offset: 0, // Default offset
|
|
}
|
|
|
|
if form.Limit != nil {
|
|
meta.Limit = *form.Limit
|
|
}
|
|
if form.Offset != nil {
|
|
meta.Offset = *form.Offset
|
|
}
|
|
|
|
return response.SuccessWithMeta(c, responses, meta, "Customers retrieved successfully")
|
|
}
|
|
|
|
// GetCustomerByID retrieves a customer by ID (admin only)
|
|
// @Summary Get customer by ID
|
|
// @Description Retrieve customer information by ID (admin only)
|
|
// @Tags customers
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Security BearerAuth
|
|
// @Param id path string true "Customer ID"
|
|
// @Success 200 {object} response.APIResponse{data=customer.CustomerResponse} "Customer retrieved successfully"
|
|
// @Failure 400 {object} response.APIResponse "Bad request"
|
|
// @Failure 401 {object} response.APIResponse "Unauthorized"
|
|
// @Failure 404 {object} response.APIResponse "Customer not found"
|
|
// @Failure 500 {object} response.APIResponse "Internal server error"
|
|
// @Router /admin/customers/{id} [get]
|
|
func (h *Handler) GetCustomerByID(c echo.Context) error {
|
|
// Parse customer ID
|
|
customerIDStr := c.Param("id")
|
|
customerID, err := uuid.Parse(customerIDStr)
|
|
if err != nil {
|
|
return response.BadRequest(c, "Invalid customer ID", err.Error())
|
|
}
|
|
|
|
// Get customer
|
|
customer, err := h.service.GetCustomerByID(c.Request().Context(), customerID)
|
|
if err != nil {
|
|
return response.NotFound(c, "Customer not found")
|
|
}
|
|
|
|
return response.Success(c, customer.ToResponse(), "Customer retrieved successfully")
|
|
}
|
|
|
|
// UpdateCustomerStatus updates customer status (admin only)
|
|
// @Summary Update customer status
|
|
// @Description Update customer status (admin only)
|
|
// @Tags customers
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Security BearerAuth
|
|
// @Param id path string true "Customer ID"
|
|
// @Param status body UpdateCustomerStatusForm true "Status update information"
|
|
// @Success 200 {object} response.APIResponse "Customer status updated successfully"
|
|
// @Failure 400 {object} response.APIResponse "Bad request"
|
|
// @Failure 401 {object} response.APIResponse "Unauthorized"
|
|
// @Failure 404 {object} response.APIResponse "Customer not found"
|
|
// @Failure 500 {object} response.APIResponse "Internal server error"
|
|
// @Router /admin/customers/{id}/status [put]
|
|
func (h *Handler) UpdateCustomerStatus(c echo.Context) error {
|
|
// Parse customer ID
|
|
customerIDStr := c.Param("id")
|
|
customerID, err := uuid.Parse(customerIDStr)
|
|
if err != nil {
|
|
return response.BadRequest(c, "Invalid customer ID", err.Error())
|
|
}
|
|
|
|
var form UpdateCustomerStatusForm
|
|
|
|
if err := c.Bind(&form); err != nil {
|
|
return response.BadRequest(c, "Invalid request body", err.Error())
|
|
}
|
|
|
|
// Validate form
|
|
if _, err := govalidator.ValidateStruct(form); err != nil {
|
|
return response.ValidationError(c, "Validation failed", err.Error())
|
|
}
|
|
|
|
// Update status
|
|
err = h.service.UpdateCustomerStatus(c.Request().Context(), customerID, &form)
|
|
if err != nil {
|
|
return response.BadRequest(c, err.Error(), "")
|
|
}
|
|
|
|
return response.Success(c, nil, "Customer status updated successfully")
|
|
}
|
|
|
|
// GetAllDeviceTokens retrieves all device tokens (admin only)
|
|
// @Summary Get all device tokens
|
|
// @Description Retrieve all device tokens (admin only)
|
|
// @Tags customers
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Security BearerAuth
|
|
// @Success 200 {object} response.APIResponse{data=[]string} "Device tokens retrieved successfully"
|
|
// @Failure 401 {object} response.APIResponse "Unauthorized"
|
|
// @Failure 500 {object} response.APIResponse "Internal server error"
|
|
// @Router /admin/customers/device-tokens [get]
|
|
func (h *Handler) GetAllDeviceTokens(c echo.Context) error {
|
|
// Get all device tokens
|
|
tokens, err := h.service.GetAllDeviceTokens(c.Request().Context())
|
|
if err != nil {
|
|
return response.InternalServerError(c, err.Error())
|
|
}
|
|
|
|
return response.Success(c, tokens, "Device tokens retrieved successfully")
|
|
}
|
|
|
|
// Helper methods
|
|
|
|
// getCustomerIDFromContext extracts customer ID from context
|
|
func (h *Handler) getCustomerIDFromContext(c echo.Context) (uuid.UUID, error) {
|
|
// TODO: Implement proper JWT token validation and extraction
|
|
// For now, we'll use a header-based approach
|
|
customerIDStr := c.Request().Header.Get("X-Customer-ID")
|
|
if customerIDStr == "" {
|
|
return uuid.Nil, echo.NewHTTPError(http.StatusUnauthorized, "Missing customer ID")
|
|
}
|
|
|
|
customerID, err := uuid.Parse(customerIDStr)
|
|
if err != nil {
|
|
return uuid.Nil, echo.NewHTTPError(http.StatusUnauthorized, "Invalid customer ID")
|
|
}
|
|
|
|
return customerID, nil
|
|
}
|
|
|
|
// authMiddleware is a placeholder for authentication middleware
|
|
func (h *Handler) authMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
|
|
return func(c echo.Context) error {
|
|
// TODO: Implement proper JWT token validation
|
|
// For now, we'll just pass through
|
|
return next(c)
|
|
}
|
|
}
|
|
|
|
// adminAuthMiddleware is a placeholder for admin authentication middleware
|
|
func (h *Handler) adminAuthMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
|
|
return func(c echo.Context) error {
|
|
// TODO: Implement proper admin authentication
|
|
// For now, we'll just pass through
|
|
return next(c)
|
|
}
|
|
}
|