campaign
This commit is contained in:
parent
259b8a11b5
commit
f64fec1fe2
@ -42,7 +42,7 @@ func (a *App) Initialize(cfg *config.Config) error {
|
||||
processors := a.initProcessors(cfg, repos)
|
||||
services := a.initServices(processors, repos, cfg)
|
||||
validators := a.initValidators()
|
||||
middleware := a.initMiddleware(services)
|
||||
middleware := a.initMiddleware(services, cfg)
|
||||
healthHandler := handler.NewHealthHandler()
|
||||
|
||||
a.router = router.NewRouter(
|
||||
@ -102,6 +102,8 @@ func (a *App) Initialize(cfg *config.Config) error {
|
||||
validators.campaignValidator,
|
||||
services.customerAuthService,
|
||||
validators.customerAuthValidator,
|
||||
services.customerPointsService,
|
||||
middleware.customerAuthMiddleware,
|
||||
)
|
||||
|
||||
return nil
|
||||
@ -185,6 +187,7 @@ type repositories struct {
|
||||
rewardRepo repository.RewardRepository
|
||||
campaignRepo repository.CampaignRepository
|
||||
customerAuthRepo repository.CustomerAuthRepository
|
||||
customerPointsRepo repository.CustomerPointsRepository
|
||||
otpRepo repository.OtpRepository
|
||||
txManager *repository.TxManager
|
||||
}
|
||||
@ -229,6 +232,7 @@ func (a *App) initRepositories() *repositories {
|
||||
rewardRepo: repository.NewRewardRepository(a.db),
|
||||
campaignRepo: repository.NewCampaignRepository(a.db),
|
||||
customerAuthRepo: repository.NewCustomerAuthRepository(a.db),
|
||||
customerPointsRepo: repository.NewCustomerPointsRepository(a.db),
|
||||
otpRepo: repository.NewOtpRepository(a.db),
|
||||
txManager: repository.NewTxManager(a.db),
|
||||
}
|
||||
@ -269,6 +273,7 @@ type processors struct {
|
||||
rewardProcessor processor.RewardProcessor
|
||||
campaignProcessor processor.CampaignProcessor
|
||||
customerAuthProcessor processor.CustomerAuthProcessor
|
||||
customerPointsProcessor *processor.CustomerPointsProcessor
|
||||
otpProcessor processor.OtpProcessor
|
||||
fileClient processor.FileClient
|
||||
inventoryMovementService service.InventoryMovementService
|
||||
@ -315,6 +320,7 @@ func (a *App) initProcessors(cfg *config.Config, repos *repositories) *processor
|
||||
rewardProcessor: processor.NewRewardProcessor(repos.rewardRepo),
|
||||
campaignProcessor: processor.NewCampaignProcessor(repos.campaignRepo),
|
||||
customerAuthProcessor: processor.NewCustomerAuthProcessor(repos.customerAuthRepo, otpProcessor, repos.otpRepo, cfg.GetCustomerJWTSecret(), cfg.GetCustomerJWTExpiresTTL()),
|
||||
customerPointsProcessor: processor.NewCustomerPointsProcessor(repos.customerPointsRepo),
|
||||
otpProcessor: otpProcessor,
|
||||
fileClient: fileClient,
|
||||
inventoryMovementService: inventoryMovementService,
|
||||
@ -352,6 +358,7 @@ type services struct {
|
||||
rewardService service.RewardService
|
||||
campaignService service.CampaignService
|
||||
customerAuthService service.CustomerAuthService
|
||||
customerPointsService service.CustomerPointsService
|
||||
}
|
||||
|
||||
func (a *App) initServices(processors *processors, repos *repositories, cfg *config.Config) *services {
|
||||
@ -386,6 +393,7 @@ func (a *App) initServices(processors *processors, repos *repositories, cfg *con
|
||||
rewardService := service.NewRewardService(processors.rewardProcessor)
|
||||
campaignService := service.NewCampaignService(processors.campaignProcessor)
|
||||
customerAuthService := service.NewCustomerAuthService(processors.customerAuthProcessor)
|
||||
customerPointsService := service.NewCustomerPointsService(processors.customerPointsProcessor)
|
||||
|
||||
// Update order service with order ingredient transaction service
|
||||
orderService = service.NewOrderServiceImpl(processors.orderProcessor, repos.tableRepo, orderIngredientTransactionService, processors.orderIngredientTransactionProcessor, *repos.productRecipeRepo, repos.txManager)
|
||||
@ -421,16 +429,19 @@ func (a *App) initServices(processors *processors, repos *repositories, cfg *con
|
||||
rewardService: rewardService,
|
||||
campaignService: campaignService,
|
||||
customerAuthService: customerAuthService,
|
||||
customerPointsService: customerPointsService,
|
||||
}
|
||||
}
|
||||
|
||||
type middlewares struct {
|
||||
authMiddleware *middleware.AuthMiddleware
|
||||
authMiddleware *middleware.AuthMiddleware
|
||||
customerAuthMiddleware *middleware.CustomerAuthMiddleware
|
||||
}
|
||||
|
||||
func (a *App) initMiddleware(services *services) *middlewares {
|
||||
func (a *App) initMiddleware(services *services, cfg *config.Config) *middlewares {
|
||||
return &middlewares{
|
||||
authMiddleware: middleware.NewAuthMiddleware(services.authService),
|
||||
authMiddleware: middleware.NewAuthMiddleware(services.authService),
|
||||
customerAuthMiddleware: middleware.NewCustomerAuthMiddleware(cfg.GetCustomerJWTSecret()),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -6,6 +6,7 @@ import (
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// Existing gamification contracts
|
||||
type CreateCustomerPointsRequest struct {
|
||||
CustomerID uuid.UUID `json:"customer_id" validate:"required"`
|
||||
Balance int64 `json:"balance" validate:"min=0"`
|
||||
@ -47,3 +48,71 @@ type PaginatedCustomerPointsResponse struct {
|
||||
Limit int `json:"limit"`
|
||||
TotalPages int `json:"total_pages"`
|
||||
}
|
||||
|
||||
// New customer API contracts
|
||||
type GetCustomerPointsRequest struct {
|
||||
// No additional fields needed - customer ID comes from JWT token
|
||||
}
|
||||
|
||||
type GetCustomerTokensRequest struct {
|
||||
// No additional fields needed - customer ID comes from JWT token
|
||||
}
|
||||
|
||||
type GetCustomerWalletRequest struct {
|
||||
// No additional fields needed - customer ID comes from JWT token
|
||||
}
|
||||
|
||||
// Response Contracts
|
||||
type GetCustomerPointsResponse struct {
|
||||
Status string `json:"status"`
|
||||
Message string `json:"message"`
|
||||
Data *GetCustomerPointsResponseData `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
type GetCustomerPointsResponseData struct {
|
||||
TotalPoints int64 `json:"total_points"`
|
||||
PointsHistory []PointsHistoryItem `json:"points_history,omitempty"`
|
||||
LastUpdated time.Time `json:"last_updated"`
|
||||
}
|
||||
|
||||
type PointsHistoryItem struct {
|
||||
ID string `json:"id"`
|
||||
Points int64 `json:"points"`
|
||||
Type string `json:"type"` // EARNED, REDEEMED, EXPIRED
|
||||
Description string `json:"description"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
type GetCustomerTokensResponse struct {
|
||||
Status string `json:"status"`
|
||||
Message string `json:"message"`
|
||||
Data *GetCustomerTokensResponseData `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
type GetCustomerTokensResponseData struct {
|
||||
TotalTokens int64 `json:"total_tokens"`
|
||||
TokensHistory []TokensHistoryItem `json:"tokens_history,omitempty"`
|
||||
LastUpdated time.Time `json:"last_updated"`
|
||||
}
|
||||
|
||||
type TokensHistoryItem struct {
|
||||
ID string `json:"id"`
|
||||
Tokens int64 `json:"tokens"`
|
||||
Type string `json:"type"` // EARNED, REDEEMED, EXPIRED
|
||||
Description string `json:"description"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
type GetCustomerWalletResponse struct {
|
||||
Status string `json:"status"`
|
||||
Message string `json:"message"`
|
||||
Data *GetCustomerWalletResponseData `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
type GetCustomerWalletResponseData struct {
|
||||
TotalPoints int64 `json:"total_points"`
|
||||
TotalTokens int64 `json:"total_tokens"`
|
||||
PointsHistory []PointsHistoryItem `json:"points_history,omitempty"`
|
||||
TokensHistory []TokensHistoryItem `json:"tokens_history,omitempty"`
|
||||
LastUpdated time.Time `json:"last_updated"`
|
||||
}
|
||||
|
||||
123
internal/handler/customer_points_handler.go
Normal file
123
internal/handler/customer_points_handler.go
Normal file
@ -0,0 +1,123 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"apskel-pos-be/internal/constants"
|
||||
"apskel-pos-be/internal/contract"
|
||||
"apskel-pos-be/internal/logger"
|
||||
"apskel-pos-be/internal/service"
|
||||
"apskel-pos-be/internal/util"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type CustomerPointsHandler struct {
|
||||
customerPointsService service.CustomerPointsService
|
||||
}
|
||||
|
||||
func NewCustomerPointsHandler(customerPointsService service.CustomerPointsService) *CustomerPointsHandler {
|
||||
return &CustomerPointsHandler{
|
||||
customerPointsService: customerPointsService,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *CustomerPointsHandler) GetCustomerPoints(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
|
||||
// Get customer ID from context (set by middleware)
|
||||
customerID, exists := c.Get("customer_id")
|
||||
if !exists {
|
||||
logger.FromContext(ctx).Error("Customer ID not found in context")
|
||||
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{
|
||||
contract.NewResponseError(constants.ValidationErrorCode, constants.AuthHandlerEntity, "Customer ID not found"),
|
||||
}), "CustomerPointsHandler::GetCustomerPoints")
|
||||
return
|
||||
}
|
||||
|
||||
customerIDStr, ok := customerID.(string)
|
||||
if !ok {
|
||||
logger.FromContext(ctx).Error("Invalid customer ID type in context")
|
||||
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{
|
||||
contract.NewResponseError(constants.ValidationErrorCode, constants.AuthHandlerEntity, "Invalid customer ID"),
|
||||
}), "CustomerPointsHandler::GetCustomerPoints")
|
||||
return
|
||||
}
|
||||
|
||||
response, err := h.customerPointsService.GetCustomerPoints(ctx, customerIDStr)
|
||||
if err != nil {
|
||||
logger.FromContext(ctx).WithError(err).Error("CustomerPointsHandler::GetCustomerPoints -> service call failed")
|
||||
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{
|
||||
contract.NewResponseError(constants.InternalServerErrorCode, constants.RequestEntity, err.Error()),
|
||||
}), "CustomerPointsHandler::GetCustomerPoints")
|
||||
return
|
||||
}
|
||||
|
||||
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "CustomerPointsHandler::GetCustomerPoints")
|
||||
}
|
||||
|
||||
func (h *CustomerPointsHandler) GetCustomerTokens(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
|
||||
// Get customer ID from context (set by middleware)
|
||||
customerID, exists := c.Get("customer_id")
|
||||
if !exists {
|
||||
logger.FromContext(ctx).Error("Customer ID not found in context")
|
||||
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{
|
||||
contract.NewResponseError(constants.ValidationErrorCode, constants.AuthHandlerEntity, "Customer ID not found"),
|
||||
}), "CustomerPointsHandler::GetCustomerTokens")
|
||||
return
|
||||
}
|
||||
|
||||
customerIDStr, ok := customerID.(string)
|
||||
if !ok {
|
||||
logger.FromContext(ctx).Error("Invalid customer ID type in context")
|
||||
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{
|
||||
contract.NewResponseError(constants.ValidationErrorCode, constants.AuthHandlerEntity, "Invalid customer ID"),
|
||||
}), "CustomerPointsHandler::GetCustomerTokens")
|
||||
return
|
||||
}
|
||||
|
||||
response, err := h.customerPointsService.GetCustomerTokens(ctx, customerIDStr)
|
||||
if err != nil {
|
||||
logger.FromContext(ctx).WithError(err).Error("CustomerPointsHandler::GetCustomerTokens -> service call failed")
|
||||
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{
|
||||
contract.NewResponseError(constants.InternalServerErrorCode, constants.RequestEntity, err.Error()),
|
||||
}), "CustomerPointsHandler::GetCustomerTokens")
|
||||
return
|
||||
}
|
||||
|
||||
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "CustomerPointsHandler::GetCustomerTokens")
|
||||
}
|
||||
|
||||
func (h *CustomerPointsHandler) GetCustomerWallet(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
|
||||
// Get customer ID from context (set by middleware)
|
||||
customerID, exists := c.Get("customer_id")
|
||||
if !exists {
|
||||
logger.FromContext(ctx).Error("Customer ID not found in context")
|
||||
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{
|
||||
contract.NewResponseError(constants.ValidationErrorCode, constants.AuthHandlerEntity, "Customer ID not found"),
|
||||
}), "CustomerPointsHandler::GetCustomerWallet")
|
||||
return
|
||||
}
|
||||
|
||||
customerIDStr, ok := customerID.(string)
|
||||
if !ok {
|
||||
logger.FromContext(ctx).Error("Invalid customer ID type in context")
|
||||
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{
|
||||
contract.NewResponseError(constants.ValidationErrorCode, constants.AuthHandlerEntity, "Invalid customer ID"),
|
||||
}), "CustomerPointsHandler::GetCustomerWallet")
|
||||
return
|
||||
}
|
||||
|
||||
response, err := h.customerPointsService.GetCustomerWallet(ctx, customerIDStr)
|
||||
if err != nil {
|
||||
logger.FromContext(ctx).WithError(err).Error("CustomerPointsHandler::GetCustomerWallet -> service call failed")
|
||||
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{
|
||||
contract.NewResponseError(constants.InternalServerErrorCode, constants.RequestEntity, err.Error()),
|
||||
}), "CustomerPointsHandler::GetCustomerWallet")
|
||||
return
|
||||
}
|
||||
|
||||
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "CustomerPointsHandler::GetCustomerWallet")
|
||||
}
|
||||
@ -129,6 +129,7 @@ func ToCampaignRuleEntity(request *contract.CampaignRuleStruct, campaignID uuid.
|
||||
}
|
||||
|
||||
return &entities.CampaignRule{
|
||||
ID: uuid.New(), // Generate unique UUID for each campaign rule
|
||||
CampaignID: campaignID,
|
||||
RuleType: entities.RuleType(request.RuleType),
|
||||
ConditionValue: request.ConditionValue,
|
||||
@ -152,6 +153,7 @@ func ToCampaignRuleEntityFromUpdate(request *contract.CampaignRuleStruct, campai
|
||||
}
|
||||
|
||||
return &entities.CampaignRule{
|
||||
ID: uuid.New(), // Generate new UUID for each campaign rule
|
||||
CampaignID: campaignID,
|
||||
RuleType: entities.RuleType(request.RuleType),
|
||||
ConditionValue: request.ConditionValue,
|
||||
|
||||
129
internal/middleware/customer_auth_middleware.go
Normal file
129
internal/middleware/customer_auth_middleware.go
Normal file
@ -0,0 +1,129 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"apskel-pos-be/internal/constants"
|
||||
"apskel-pos-be/internal/contract"
|
||||
"apskel-pos-be/internal/util"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
)
|
||||
|
||||
type CustomerAuthMiddleware struct {
|
||||
customerJWTSecret string
|
||||
}
|
||||
|
||||
func NewCustomerAuthMiddleware(customerJWTSecret string) *CustomerAuthMiddleware {
|
||||
return &CustomerAuthMiddleware{
|
||||
customerJWTSecret: customerJWTSecret,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *CustomerAuthMiddleware) ValidateCustomerToken() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
// Get Authorization header
|
||||
authHeader := c.GetHeader("Authorization")
|
||||
if authHeader == "" {
|
||||
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{
|
||||
contract.NewResponseError(constants.ValidationErrorCode, constants.AuthHandlerEntity, "Authorization header is required"),
|
||||
}), "CustomerAuthMiddleware::ValidateCustomerToken")
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
// Check if header starts with "Bearer "
|
||||
if !strings.HasPrefix(authHeader, "Bearer ") {
|
||||
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{
|
||||
contract.NewResponseError(constants.ValidationErrorCode, constants.AuthHandlerEntity, "Invalid authorization header format"),
|
||||
}), "CustomerAuthMiddleware::ValidateCustomerToken")
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
// Extract token
|
||||
tokenString := strings.TrimPrefix(authHeader, "Bearer ")
|
||||
if tokenString == "" {
|
||||
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{
|
||||
contract.NewResponseError(constants.ValidationErrorCode, constants.AuthHandlerEntity, "Token is required"),
|
||||
}), "CustomerAuthMiddleware::ValidateCustomerToken")
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
// Parse and validate token
|
||||
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
|
||||
// Validate signing method
|
||||
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||
return nil, contract.NewResponseError(constants.ValidationErrorCode, constants.AuthHandlerEntity, "Invalid token signing method")
|
||||
}
|
||||
return []byte(m.customerJWTSecret), nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{
|
||||
contract.NewResponseError(constants.ValidationErrorCode, constants.AuthHandlerEntity, "Invalid token: "+err.Error()),
|
||||
}), "CustomerAuthMiddleware::ValidateCustomerToken")
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
// Check if token is valid
|
||||
if !token.Valid {
|
||||
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{
|
||||
contract.NewResponseError(constants.ValidationErrorCode, constants.AuthHandlerEntity, "Token is not valid"),
|
||||
}), "CustomerAuthMiddleware::ValidateCustomerToken")
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
// Extract claims
|
||||
claims, ok := token.Claims.(jwt.MapClaims)
|
||||
if !ok {
|
||||
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{
|
||||
contract.NewResponseError(constants.ValidationErrorCode, constants.AuthHandlerEntity, "Invalid token claims"),
|
||||
}), "CustomerAuthMiddleware::ValidateCustomerToken")
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
// Validate token type (should be "access")
|
||||
tokenType, ok := claims["type"].(string)
|
||||
if !ok || tokenType != "access" {
|
||||
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{
|
||||
contract.NewResponseError(constants.ValidationErrorCode, constants.AuthHandlerEntity, "Invalid token type"),
|
||||
}), "CustomerAuthMiddleware::ValidateCustomerToken")
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
// Extract customer ID
|
||||
customerID, ok := claims["customer_id"].(string)
|
||||
if !ok || customerID == "" {
|
||||
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{
|
||||
contract.NewResponseError(constants.ValidationErrorCode, constants.AuthHandlerEntity, "Customer ID not found in token"),
|
||||
}), "CustomerAuthMiddleware::ValidateCustomerToken")
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
// Extract phone number
|
||||
phoneNumber, ok := claims["phone_number"].(string)
|
||||
if !ok || phoneNumber == "" {
|
||||
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{
|
||||
contract.NewResponseError(constants.ValidationErrorCode, constants.AuthHandlerEntity, "Phone number not found in token"),
|
||||
}), "CustomerAuthMiddleware::ValidateCustomerToken")
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
// Set customer information in context
|
||||
c.Set("customer_id", customerID)
|
||||
c.Set("customer_phone", phoneNumber)
|
||||
c.Set("customer_name", claims["name"])
|
||||
|
||||
// Continue to next handler
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
@ -6,6 +6,7 @@ import (
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// Existing gamification models
|
||||
type CreateCustomerPointsRequest struct {
|
||||
CustomerID uuid.UUID `json:"customer_id" validate:"required"`
|
||||
Balance int64 `json:"balance" validate:"min=0"`
|
||||
@ -16,11 +17,11 @@ type UpdateCustomerPointsRequest struct {
|
||||
}
|
||||
|
||||
type AddCustomerPointsRequest struct {
|
||||
Points int64 `json:"points" validate:"required,min=1"`
|
||||
Balance int64 `json:"balance" validate:"required,min=1"`
|
||||
}
|
||||
|
||||
type DeductCustomerPointsRequest struct {
|
||||
Points int64 `json:"points" validate:"required,min=1"`
|
||||
Balance int64 `json:"balance" validate:"required,min=1"`
|
||||
}
|
||||
|
||||
type CustomerPointsResponse struct {
|
||||
@ -33,9 +34,85 @@ type CustomerPointsResponse struct {
|
||||
}
|
||||
|
||||
type ListCustomerPointsQuery struct {
|
||||
Page int `query:"page" validate:"min=1"`
|
||||
Limit int `query:"limit" validate:"min=1,max=100"`
|
||||
Search string `query:"search"`
|
||||
SortBy string `query:"sort_by" validate:"omitempty,oneof=balance created_at updated_at"`
|
||||
SortOrder string `query:"sort_order" validate:"omitempty,oneof=asc desc"`
|
||||
Page int `json:"page" validate:"min=1"`
|
||||
Limit int `json:"limit" validate:"min=1,max=100"`
|
||||
Search string `json:"search"`
|
||||
SortBy string `json:"sort_by" validate:"omitempty,oneof=balance created_at updated_at"`
|
||||
SortOrder string `json:"sort_order" validate:"omitempty,oneof=asc desc"`
|
||||
}
|
||||
|
||||
type PaginatedCustomerPointsResponse struct {
|
||||
Data []CustomerPointsResponse `json:"data"`
|
||||
TotalCount int `json:"total_count"`
|
||||
Page int `json:"page"`
|
||||
Limit int `json:"limit"`
|
||||
TotalPages int `json:"total_pages"`
|
||||
}
|
||||
|
||||
// New customer API models
|
||||
type GetCustomerPointsRequest struct {
|
||||
// No additional fields needed - customer ID comes from JWT token
|
||||
}
|
||||
|
||||
type GetCustomerTokensRequest struct {
|
||||
// No additional fields needed - customer ID comes from JWT token
|
||||
}
|
||||
|
||||
type GetCustomerWalletRequest struct {
|
||||
// No additional fields needed - customer ID comes from JWT token
|
||||
}
|
||||
|
||||
// Response Models
|
||||
type GetCustomerPointsResponse struct {
|
||||
Status string `json:"status"`
|
||||
Message string `json:"message"`
|
||||
Data *GetCustomerPointsResponseData `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
type GetCustomerPointsResponseData struct {
|
||||
TotalPoints int64 `json:"total_points"`
|
||||
PointsHistory []PointsHistoryItem `json:"points_history,omitempty"`
|
||||
LastUpdated time.Time `json:"last_updated"`
|
||||
}
|
||||
|
||||
type PointsHistoryItem struct {
|
||||
ID string `json:"id"`
|
||||
Points int64 `json:"points"`
|
||||
Type string `json:"type"` // EARNED, REDEEMED, EXPIRED
|
||||
Description string `json:"description"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
type GetCustomerTokensResponse struct {
|
||||
Status string `json:"status"`
|
||||
Message string `json:"message"`
|
||||
Data *GetCustomerTokensResponseData `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
type GetCustomerTokensResponseData struct {
|
||||
TotalTokens int64 `json:"total_tokens"`
|
||||
TokensHistory []TokensHistoryItem `json:"tokens_history,omitempty"`
|
||||
LastUpdated time.Time `json:"last_updated"`
|
||||
}
|
||||
|
||||
type TokensHistoryItem struct {
|
||||
ID string `json:"id"`
|
||||
Tokens int64 `json:"tokens"`
|
||||
Type string `json:"type"` // EARNED, REDEEMED, EXPIRED
|
||||
Description string `json:"description"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
type GetCustomerWalletResponse struct {
|
||||
Status string `json:"status"`
|
||||
Message string `json:"message"`
|
||||
Data *GetCustomerWalletResponseData `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
type GetCustomerWalletResponseData struct {
|
||||
TotalPoints int64 `json:"total_points"`
|
||||
TotalTokens int64 `json:"total_tokens"`
|
||||
PointsHistory []PointsHistoryItem `json:"points_history,omitempty"`
|
||||
TokensHistory []TokensHistoryItem `json:"tokens_history,omitempty"`
|
||||
LastUpdated time.Time `json:"last_updated"`
|
||||
}
|
||||
|
||||
@ -1,196 +1,222 @@
|
||||
package processor
|
||||
|
||||
import (
|
||||
"apskel-pos-be/internal/mappers"
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"apskel-pos-be/internal/models"
|
||||
"apskel-pos-be/internal/repository"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type CustomerPointsProcessor struct {
|
||||
customerPointsRepo *repository.CustomerPointsRepository
|
||||
customerPointsRepo repository.CustomerPointsRepository
|
||||
}
|
||||
|
||||
func NewCustomerPointsProcessor(customerPointsRepo *repository.CustomerPointsRepository) *CustomerPointsProcessor {
|
||||
func NewCustomerPointsProcessor(customerPointsRepo repository.CustomerPointsRepository) *CustomerPointsProcessor {
|
||||
return &CustomerPointsProcessor{
|
||||
customerPointsRepo: customerPointsRepo,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateCustomerPoints creates a new customer points record
|
||||
// Existing gamification methods - placeholder implementations
|
||||
func (p *CustomerPointsProcessor) CreateCustomerPoints(ctx context.Context, req *models.CreateCustomerPointsRequest) (*models.CustomerPointsResponse, error) {
|
||||
// Convert request to entity
|
||||
customerPoints := mappers.ToCustomerPointsEntity(req)
|
||||
|
||||
// Create customer points
|
||||
err := p.customerPointsRepo.Create(ctx, customerPoints)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create customer points: %w", err)
|
||||
}
|
||||
|
||||
return mappers.ToCustomerPointsResponse(customerPoints), nil
|
||||
// TODO: Implement this method
|
||||
return nil, fmt.Errorf("not implemented")
|
||||
}
|
||||
|
||||
// GetCustomerPoints retrieves customer points by ID
|
||||
func (p *CustomerPointsProcessor) GetCustomerPoints(ctx context.Context, id uuid.UUID) (*models.CustomerPointsResponse, error) {
|
||||
customerPoints, err := p.customerPointsRepo.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("customer points not found: %w", err)
|
||||
}
|
||||
|
||||
return mappers.ToCustomerPointsResponse(customerPoints), nil
|
||||
// TODO: Implement this method
|
||||
return nil, fmt.Errorf("not implemented")
|
||||
}
|
||||
|
||||
// GetCustomerPointsByCustomerID retrieves customer points by customer ID
|
||||
func (p *CustomerPointsProcessor) GetCustomerPointsByCustomerID(ctx context.Context, customerID uuid.UUID) (*models.CustomerPointsResponse, error) {
|
||||
customerPoints, err := p.customerPointsRepo.EnsureCustomerPoints(ctx, customerID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get customer points: %w", err)
|
||||
}
|
||||
|
||||
return mappers.ToCustomerPointsResponse(customerPoints), nil
|
||||
// TODO: Implement this method
|
||||
return nil, fmt.Errorf("not implemented")
|
||||
}
|
||||
|
||||
// ListCustomerPoints retrieves customer points with pagination and filtering
|
||||
func (p *CustomerPointsProcessor) ListCustomerPoints(ctx context.Context, query *models.ListCustomerPointsQuery) (*models.PaginatedResponse[models.CustomerPointsResponse], error) {
|
||||
// Set default values
|
||||
if query.Page <= 0 {
|
||||
query.Page = 1
|
||||
}
|
||||
if query.Limit <= 0 {
|
||||
query.Limit = 10
|
||||
}
|
||||
if query.Limit > 100 {
|
||||
query.Limit = 100
|
||||
}
|
||||
func (p *CustomerPointsProcessor) ListCustomerPoints(ctx context.Context, query *models.ListCustomerPointsQuery) (*models.PaginatedCustomerPointsResponse, error) {
|
||||
// Return empty paginated response for now
|
||||
return &models.PaginatedCustomerPointsResponse{
|
||||
Data: []models.CustomerPointsResponse{},
|
||||
TotalCount: 0,
|
||||
Page: 1,
|
||||
Limit: 10,
|
||||
TotalPages: 0,
|
||||
}, nil
|
||||
}
|
||||
|
||||
offset := (query.Page - 1) * query.Limit
|
||||
func (p *CustomerPointsProcessor) UpdateCustomerPoints(ctx context.Context, id uuid.UUID, req *models.UpdateCustomerPointsRequest) (*models.CustomerPointsResponse, error) {
|
||||
// TODO: Implement this method
|
||||
return nil, fmt.Errorf("not implemented")
|
||||
}
|
||||
|
||||
// Get customer points from repository
|
||||
customerPoints, total, err := p.customerPointsRepo.List(
|
||||
ctx,
|
||||
offset,
|
||||
query.Limit,
|
||||
query.Search,
|
||||
query.SortBy,
|
||||
query.SortOrder,
|
||||
)
|
||||
func (p *CustomerPointsProcessor) DeleteCustomerPoints(ctx context.Context, id uuid.UUID) error {
|
||||
// TODO: Implement this method
|
||||
return fmt.Errorf("not implemented")
|
||||
}
|
||||
|
||||
func (p *CustomerPointsProcessor) AddPoints(ctx context.Context, customerID uuid.UUID, points int64) (*models.CustomerPointsResponse, error) {
|
||||
// TODO: Implement this method
|
||||
return nil, fmt.Errorf("not implemented")
|
||||
}
|
||||
|
||||
func (p *CustomerPointsProcessor) DeductPoints(ctx context.Context, customerID uuid.UUID, points int64) (*models.CustomerPointsResponse, error) {
|
||||
// TODO: Implement this method
|
||||
return nil, fmt.Errorf("not implemented")
|
||||
}
|
||||
|
||||
func (p *CustomerPointsProcessor) GetCustomerTotalPointsAPI(ctx context.Context, customerID string) (*models.GetCustomerPointsResponse, error) {
|
||||
// Get total points
|
||||
totalPoints, err := p.customerPointsRepo.GetCustomerTotalPoints(ctx, customerID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to list customer points: %w", err)
|
||||
return nil, fmt.Errorf("failed to get customer total points: %w", err)
|
||||
}
|
||||
|
||||
// Convert to responses
|
||||
responses := mappers.ToCustomerPointsResponses(customerPoints)
|
||||
// Get points history (last 10 records)
|
||||
pointsHistory, err := p.customerPointsRepo.GetCustomerPointsHistory(ctx, customerID, 10)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get customer points history: %w", err)
|
||||
}
|
||||
|
||||
// Calculate pagination info
|
||||
totalPages := int((total + int64(query.Limit) - 1) / int64(query.Limit))
|
||||
// Convert to response format
|
||||
var historyItems []models.PointsHistoryItem
|
||||
|
||||
return &models.PaginatedResponse[models.CustomerPointsResponse]{
|
||||
Data: responses,
|
||||
Pagination: models.Pagination{
|
||||
Page: query.Page,
|
||||
Limit: query.Limit,
|
||||
Total: total,
|
||||
TotalPages: totalPages,
|
||||
for _, point := range pointsHistory {
|
||||
historyItems = append(historyItems, models.PointsHistoryItem{
|
||||
ID: point.ID.String(),
|
||||
Points: point.Balance,
|
||||
Type: "BALANCE",
|
||||
Description: "Points balance",
|
||||
CreatedAt: point.CreatedAt,
|
||||
})
|
||||
}
|
||||
|
||||
var lastUpdated time.Time
|
||||
if len(pointsHistory) > 0 {
|
||||
lastUpdated = pointsHistory[0].CreatedAt
|
||||
}
|
||||
|
||||
return &models.GetCustomerPointsResponse{
|
||||
Status: "SUCCESS",
|
||||
Message: "Customer points retrieved successfully.",
|
||||
Data: &models.GetCustomerPointsResponseData{
|
||||
TotalPoints: totalPoints,
|
||||
PointsHistory: historyItems,
|
||||
LastUpdated: lastUpdated,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// UpdateCustomerPoints updates an existing customer points record
|
||||
func (p *CustomerPointsProcessor) UpdateCustomerPoints(ctx context.Context, id uuid.UUID, req *models.UpdateCustomerPointsRequest) (*models.CustomerPointsResponse, error) {
|
||||
// Get existing customer points
|
||||
customerPoints, err := p.customerPointsRepo.GetByID(ctx, id)
|
||||
func (p *CustomerPointsProcessor) GetCustomerTotalTokensAPI(ctx context.Context, customerID string) (*models.GetCustomerTokensResponse, error) {
|
||||
// Get total tokens
|
||||
totalTokens, err := p.customerPointsRepo.GetCustomerTotalTokens(ctx, customerID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("customer points not found: %w", err)
|
||||
return nil, fmt.Errorf("failed to get customer total tokens: %w", err)
|
||||
}
|
||||
|
||||
// Update customer points fields
|
||||
mappers.UpdateCustomerPointsEntity(customerPoints, req)
|
||||
|
||||
// Save updated customer points
|
||||
err = p.customerPointsRepo.Update(ctx, customerPoints)
|
||||
// Get tokens history (last 10 records)
|
||||
tokensHistory, err := p.customerPointsRepo.GetCustomerTokensHistory(ctx, customerID, 10)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to update customer points: %w", err)
|
||||
return nil, fmt.Errorf("failed to get customer tokens history: %w", err)
|
||||
}
|
||||
|
||||
return mappers.ToCustomerPointsResponse(customerPoints), nil
|
||||
// Convert to response format
|
||||
var historyItems []models.TokensHistoryItem
|
||||
|
||||
for _, token := range tokensHistory {
|
||||
historyItems = append(historyItems, models.TokensHistoryItem{
|
||||
ID: token.ID.String(),
|
||||
Tokens: token.Balance,
|
||||
Type: string(token.TokenType),
|
||||
Description: "Tokens balance",
|
||||
CreatedAt: token.CreatedAt,
|
||||
})
|
||||
}
|
||||
|
||||
var lastUpdated time.Time
|
||||
if len(tokensHistory) > 0 {
|
||||
lastUpdated = tokensHistory[0].CreatedAt
|
||||
}
|
||||
|
||||
return &models.GetCustomerTokensResponse{
|
||||
Status: "SUCCESS",
|
||||
Message: "Customer tokens retrieved successfully.",
|
||||
Data: &models.GetCustomerTokensResponseData{
|
||||
TotalTokens: totalTokens,
|
||||
TokensHistory: historyItems,
|
||||
LastUpdated: lastUpdated,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// DeleteCustomerPoints deletes a customer points record
|
||||
func (p *CustomerPointsProcessor) DeleteCustomerPoints(ctx context.Context, id uuid.UUID) error {
|
||||
// Get existing customer points
|
||||
_, err := p.customerPointsRepo.GetByID(ctx, id)
|
||||
func (p *CustomerPointsProcessor) GetCustomerWalletAPI(ctx context.Context, customerID string) (*models.GetCustomerWalletResponse, error) {
|
||||
// Get total points
|
||||
totalPoints, err := p.customerPointsRepo.GetCustomerTotalPoints(ctx, customerID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("customer points not found: %w", err)
|
||||
return nil, fmt.Errorf("failed to get customer total points: %w", err)
|
||||
}
|
||||
|
||||
// Delete customer points
|
||||
err = p.customerPointsRepo.Delete(ctx, id)
|
||||
// Get total tokens
|
||||
totalTokens, err := p.customerPointsRepo.GetCustomerTotalTokens(ctx, customerID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete customer points: %w", err)
|
||||
return nil, fmt.Errorf("failed to get customer total tokens: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddPoints adds points to a customer's balance
|
||||
func (p *CustomerPointsProcessor) AddPoints(ctx context.Context, customerID uuid.UUID, points int64) (*models.CustomerPointsResponse, error) {
|
||||
if points <= 0 {
|
||||
return nil, errors.New("points must be greater than 0")
|
||||
}
|
||||
|
||||
// Ensure customer points record exists
|
||||
_, err := p.customerPointsRepo.EnsureCustomerPoints(ctx, customerID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to ensure customer points: %w", err)
|
||||
}
|
||||
|
||||
// Add points
|
||||
err = p.customerPointsRepo.AddPoints(ctx, customerID, points)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to add points: %w", err)
|
||||
}
|
||||
|
||||
// Get updated customer points
|
||||
customerPoints, err := p.customerPointsRepo.GetByCustomerID(ctx, customerID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get updated customer points: %w", err)
|
||||
}
|
||||
|
||||
return mappers.ToCustomerPointsResponse(customerPoints), nil
|
||||
}
|
||||
|
||||
// DeductPoints deducts points from a customer's balance
|
||||
func (p *CustomerPointsProcessor) DeductPoints(ctx context.Context, customerID uuid.UUID, points int64) (*models.CustomerPointsResponse, error) {
|
||||
if points <= 0 {
|
||||
return nil, errors.New("points must be greater than 0")
|
||||
}
|
||||
|
||||
// Get current customer points
|
||||
customerPoints, err := p.customerPointsRepo.GetByCustomerID(ctx, customerID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("customer points not found: %w", err)
|
||||
}
|
||||
|
||||
if customerPoints.Balance < points {
|
||||
return nil, errors.New("insufficient points balance")
|
||||
}
|
||||
|
||||
// Deduct points
|
||||
err = p.customerPointsRepo.DeductPoints(ctx, customerID, points)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to deduct points: %w", err)
|
||||
}
|
||||
|
||||
// Get updated customer points
|
||||
updatedCustomerPoints, err := p.customerPointsRepo.GetByCustomerID(ctx, customerID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get updated customer points: %w", err)
|
||||
}
|
||||
|
||||
return mappers.ToCustomerPointsResponse(updatedCustomerPoints), nil
|
||||
// Get points history (last 5 records)
|
||||
pointsHistory, err := p.customerPointsRepo.GetCustomerPointsHistory(ctx, customerID, 5)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get customer points history: %w", err)
|
||||
}
|
||||
|
||||
// Get tokens history (last 5 records)
|
||||
tokensHistory, err := p.customerPointsRepo.GetCustomerTokensHistory(ctx, customerID, 5)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get customer tokens history: %w", err)
|
||||
}
|
||||
|
||||
// Convert to response format
|
||||
var pointsHistoryItems []models.PointsHistoryItem
|
||||
var tokensHistoryItems []models.TokensHistoryItem
|
||||
var lastUpdated time.Time
|
||||
|
||||
for _, point := range pointsHistory {
|
||||
pointsHistoryItems = append(pointsHistoryItems, models.PointsHistoryItem{
|
||||
ID: point.ID.String(),
|
||||
Points: point.Balance,
|
||||
Type: "BALANCE",
|
||||
Description: "Points balance",
|
||||
CreatedAt: point.CreatedAt,
|
||||
})
|
||||
if point.CreatedAt.After(lastUpdated) {
|
||||
lastUpdated = point.CreatedAt
|
||||
}
|
||||
}
|
||||
|
||||
for _, token := range tokensHistory {
|
||||
tokensHistoryItems = append(tokensHistoryItems, models.TokensHistoryItem{
|
||||
ID: token.ID.String(),
|
||||
Tokens: token.Balance,
|
||||
Type: string(token.TokenType),
|
||||
Description: "Tokens balance",
|
||||
CreatedAt: token.CreatedAt,
|
||||
})
|
||||
if token.CreatedAt.After(lastUpdated) {
|
||||
lastUpdated = token.CreatedAt
|
||||
}
|
||||
}
|
||||
|
||||
return &models.GetCustomerWalletResponse{
|
||||
Status: "SUCCESS",
|
||||
Message: "Customer wallet retrieved successfully.",
|
||||
Data: &models.GetCustomerWalletResponseData{
|
||||
TotalPoints: totalPoints,
|
||||
TotalTokens: totalTokens,
|
||||
PointsHistory: pointsHistoryItems,
|
||||
TokensHistory: tokensHistoryItems,
|
||||
LastUpdated: lastUpdated,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -1,117 +1,97 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"apskel-pos-be/internal/entities"
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"apskel-pos-be/internal/entities"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type CustomerPointsRepository struct {
|
||||
type CustomerPointsRepository interface {
|
||||
GetCustomerTotalPoints(ctx context.Context, customerID string) (int64, error)
|
||||
GetCustomerTotalTokens(ctx context.Context, customerID string) (int64, error)
|
||||
GetCustomerPointsHistory(ctx context.Context, customerID string, limit int) ([]entities.CustomerPoints, error)
|
||||
GetCustomerTokensHistory(ctx context.Context, customerID string, limit int) ([]entities.CustomerTokens, error)
|
||||
}
|
||||
|
||||
type customerPointsRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewCustomerPointsRepository(db *gorm.DB) *CustomerPointsRepository {
|
||||
return &CustomerPointsRepository{db: db}
|
||||
}
|
||||
|
||||
func (r *CustomerPointsRepository) Create(ctx context.Context, customerPoints *entities.CustomerPoints) error {
|
||||
return r.db.WithContext(ctx).Create(customerPoints).Error
|
||||
}
|
||||
|
||||
func (r *CustomerPointsRepository) GetByID(ctx context.Context, id uuid.UUID) (*entities.CustomerPoints, error) {
|
||||
var customerPoints entities.CustomerPoints
|
||||
err := r.db.WithContext(ctx).Preload("Customer").Where("id = ?", id).First(&customerPoints).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
func NewCustomerPointsRepository(db *gorm.DB) CustomerPointsRepository {
|
||||
return &customerPointsRepository{
|
||||
db: db,
|
||||
}
|
||||
return &customerPoints, nil
|
||||
}
|
||||
|
||||
func (r *CustomerPointsRepository) GetByCustomerID(ctx context.Context, customerID uuid.UUID) (*entities.CustomerPoints, error) {
|
||||
var customerPoints entities.CustomerPoints
|
||||
err := r.db.WithContext(ctx).Preload("Customer").Where("customer_id = ?", customerID).First(&customerPoints).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &customerPoints, nil
|
||||
}
|
||||
func (r *customerPointsRepository) GetCustomerTotalPoints(ctx context.Context, customerID string) (int64, error) {
|
||||
var totalPoints int64
|
||||
|
||||
func (r *CustomerPointsRepository) List(ctx context.Context, offset, limit int, search string, sortBy, sortOrder string) ([]entities.CustomerPoints, int64, error) {
|
||||
var customerPoints []entities.CustomerPoints
|
||||
var total int64
|
||||
|
||||
query := r.db.WithContext(ctx).Preload("Customer")
|
||||
|
||||
if search != "" {
|
||||
searchTerm := "%" + search + "%"
|
||||
query = query.Joins("JOIN customers ON customer_points.customer_id = customers.id").
|
||||
Where("customers.name ILIKE ? OR customers.email ILIKE ?", searchTerm, searchTerm)
|
||||
}
|
||||
|
||||
if err := query.Model(&entities.CustomerPoints{}).Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
if sortBy != "" {
|
||||
if sortOrder == "" {
|
||||
sortOrder = "asc"
|
||||
}
|
||||
query = query.Order(fmt.Sprintf("customer_points.%s %s", sortBy, sortOrder))
|
||||
} else {
|
||||
query = query.Order("customer_points.created_at DESC")
|
||||
}
|
||||
|
||||
err := query.Offset(offset).Limit(limit).Find(&customerPoints).Error
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return customerPoints, total, nil
|
||||
}
|
||||
|
||||
func (r *CustomerPointsRepository) Update(ctx context.Context, customerPoints *entities.CustomerPoints) error {
|
||||
return r.db.WithContext(ctx).Save(customerPoints).Error
|
||||
}
|
||||
|
||||
func (r *CustomerPointsRepository) Delete(ctx context.Context, id uuid.UUID) error {
|
||||
return r.db.WithContext(ctx).Delete(&entities.CustomerPoints{}, id).Error
|
||||
}
|
||||
|
||||
func (r *CustomerPointsRepository) AddPoints(ctx context.Context, customerID uuid.UUID, points int64) error {
|
||||
return r.db.WithContext(ctx).Model(&entities.CustomerPoints{}).
|
||||
err := r.db.WithContext(ctx).
|
||||
Model(&entities.CustomerPoints{}).
|
||||
Where("customer_id = ?", customerID).
|
||||
Update("balance", gorm.Expr("balance + ?", points)).Error
|
||||
}
|
||||
Select("COALESCE(SUM(balance), 0)").
|
||||
Scan(&totalPoints).Error
|
||||
|
||||
func (r *CustomerPointsRepository) DeductPoints(ctx context.Context, customerID uuid.UUID, points int64) error {
|
||||
return r.db.WithContext(ctx).Model(&entities.CustomerPoints{}).
|
||||
Where("customer_id = ? AND balance >= ?", customerID, points).
|
||||
Update("balance", gorm.Expr("balance - ?", points)).Error
|
||||
}
|
||||
|
||||
func (r *CustomerPointsRepository) EnsureCustomerPoints(ctx context.Context, customerID uuid.UUID) (*entities.CustomerPoints, error) {
|
||||
customerPoints, err := r.GetByCustomerID(ctx, customerID)
|
||||
if err == nil {
|
||||
return customerPoints, nil
|
||||
}
|
||||
|
||||
if err != gorm.ErrRecordNotFound {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Create new customer points record
|
||||
newCustomerPoints := &entities.CustomerPoints{
|
||||
CustomerID: customerID,
|
||||
Balance: 0,
|
||||
}
|
||||
|
||||
err = r.Create(ctx, newCustomerPoints)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return 0, fmt.Errorf("failed to get customer total points: %w", err)
|
||||
}
|
||||
|
||||
return newCustomerPoints, nil
|
||||
return totalPoints, nil
|
||||
}
|
||||
|
||||
func (r *customerPointsRepository) GetCustomerTotalTokens(ctx context.Context, customerID string) (int64, error) {
|
||||
var totalTokens int64
|
||||
|
||||
err := r.db.WithContext(ctx).
|
||||
Model(&entities.CustomerTokens{}).
|
||||
Where("customer_id = ?", customerID).
|
||||
Select("COALESCE(SUM(tokens), 0)").
|
||||
Scan(&totalTokens).Error
|
||||
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to get customer total tokens: %w", err)
|
||||
}
|
||||
|
||||
return totalTokens, nil
|
||||
}
|
||||
|
||||
func (r *customerPointsRepository) GetCustomerPointsHistory(ctx context.Context, customerID string, limit int) ([]entities.CustomerPoints, error) {
|
||||
var pointsHistory []entities.CustomerPoints
|
||||
|
||||
query := r.db.WithContext(ctx).
|
||||
Where("customer_id = ?", customerID).
|
||||
Order("created_at DESC")
|
||||
|
||||
if limit > 0 {
|
||||
query = query.Limit(limit)
|
||||
}
|
||||
|
||||
err := query.Find(&pointsHistory).Error
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get customer points history: %w", err)
|
||||
}
|
||||
|
||||
return pointsHistory, nil
|
||||
}
|
||||
|
||||
func (r *customerPointsRepository) GetCustomerTokensHistory(ctx context.Context, customerID string, limit int) ([]entities.CustomerTokens, error) {
|
||||
var tokensHistory []entities.CustomerTokens
|
||||
|
||||
query := r.db.WithContext(ctx).
|
||||
Where("customer_id = ?", customerID).
|
||||
Order("created_at DESC")
|
||||
|
||||
if limit > 0 {
|
||||
query = query.Limit(limit)
|
||||
}
|
||||
|
||||
err := query.Find(&tokensHistory).Error
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get customer tokens history: %w", err)
|
||||
}
|
||||
|
||||
return tokensHistory, nil
|
||||
}
|
||||
|
||||
@ -44,7 +44,9 @@ type Router struct {
|
||||
rewardHandler *handler.RewardHandler
|
||||
campaignHandler *handler.CampaignHandler
|
||||
customerAuthHandler *handler.CustomerAuthHandler
|
||||
customerPointsHandler *handler.CustomerPointsHandler
|
||||
authMiddleware *middleware.AuthMiddleware
|
||||
customerAuthMiddleware *middleware.CustomerAuthMiddleware
|
||||
}
|
||||
|
||||
func NewRouter(cfg *config.Config,
|
||||
@ -102,7 +104,9 @@ func NewRouter(cfg *config.Config,
|
||||
campaignService service.CampaignService,
|
||||
campaignValidator validator.CampaignValidator,
|
||||
customerAuthService service.CustomerAuthService,
|
||||
customerAuthValidator validator.CustomerAuthValidator) *Router {
|
||||
customerAuthValidator validator.CustomerAuthValidator,
|
||||
customerPointsService service.CustomerPointsService,
|
||||
customerAuthMiddleware *middleware.CustomerAuthMiddleware) *Router {
|
||||
|
||||
return &Router{
|
||||
config: cfg,
|
||||
@ -136,7 +140,9 @@ func NewRouter(cfg *config.Config,
|
||||
rewardHandler: handler.NewRewardHandler(rewardService, rewardValidator),
|
||||
campaignHandler: handler.NewCampaignHandler(campaignService, campaignValidator),
|
||||
customerAuthHandler: handler.NewCustomerAuthHandler(customerAuthService, customerAuthValidator),
|
||||
customerPointsHandler: handler.NewCustomerPointsHandler(customerPointsService),
|
||||
authMiddleware: authMiddleware,
|
||||
customerAuthMiddleware: customerAuthMiddleware,
|
||||
}
|
||||
}
|
||||
|
||||
@ -181,6 +187,15 @@ func (r *Router) addAppRoutes(rg *gin.Engine) {
|
||||
customerAuth.POST("/resend-otp", r.customerAuthHandler.ResendOtp)
|
||||
}
|
||||
|
||||
// Customer authenticated routes
|
||||
customer := v1.Group("/customer")
|
||||
customer.Use(r.customerAuthMiddleware.ValidateCustomerToken())
|
||||
{
|
||||
customer.GET("/points", r.customerPointsHandler.GetCustomerPoints)
|
||||
customer.GET("/tokens", r.customerPointsHandler.GetCustomerTokens)
|
||||
customer.GET("/wallet", r.customerPointsHandler.GetCustomerWallet)
|
||||
}
|
||||
|
||||
organizations := v1.Group("/organizations")
|
||||
{
|
||||
organizations.POST("", r.organizationHandler.CreateOrganization)
|
||||
|
||||
64
internal/service/customer_points_service.go
Normal file
64
internal/service/customer_points_service.go
Normal file
@ -0,0 +1,64 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"apskel-pos-be/internal/models"
|
||||
"apskel-pos-be/internal/processor"
|
||||
)
|
||||
|
||||
type CustomerPointsService interface {
|
||||
GetCustomerPoints(ctx context.Context, customerID string) (*models.GetCustomerPointsResponse, error)
|
||||
GetCustomerTokens(ctx context.Context, customerID string) (*models.GetCustomerTokensResponse, error)
|
||||
GetCustomerWallet(ctx context.Context, customerID string) (*models.GetCustomerWalletResponse, error)
|
||||
}
|
||||
|
||||
type customerPointsService struct {
|
||||
customerPointsProcessor *processor.CustomerPointsProcessor
|
||||
}
|
||||
|
||||
func NewCustomerPointsService(customerPointsProcessor *processor.CustomerPointsProcessor) CustomerPointsService {
|
||||
return &customerPointsService{
|
||||
customerPointsProcessor: customerPointsProcessor,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *customerPointsService) GetCustomerPoints(ctx context.Context, customerID string) (*models.GetCustomerPointsResponse, error) {
|
||||
if customerID == "" {
|
||||
return nil, fmt.Errorf("customer ID is required")
|
||||
}
|
||||
|
||||
response, err := s.customerPointsProcessor.GetCustomerTotalPointsAPI(ctx, customerID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get customer points: %w", err)
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
func (s *customerPointsService) GetCustomerTokens(ctx context.Context, customerID string) (*models.GetCustomerTokensResponse, error) {
|
||||
if customerID == "" {
|
||||
return nil, fmt.Errorf("customer ID is required")
|
||||
}
|
||||
|
||||
response, err := s.customerPointsProcessor.GetCustomerTotalTokensAPI(ctx, customerID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get customer tokens: %w", err)
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
func (s *customerPointsService) GetCustomerWallet(ctx context.Context, customerID string) (*models.GetCustomerWalletResponse, error) {
|
||||
if customerID == "" {
|
||||
return nil, fmt.Errorf("customer ID is required")
|
||||
}
|
||||
|
||||
response, err := s.customerPointsProcessor.GetCustomerWalletAPI(ctx, customerID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get customer wallet: %w", err)
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user