test wheels
This commit is contained in:
parent
f64fec1fe2
commit
be92ec8b23
@ -177,7 +177,6 @@ type repositories struct {
|
|||||||
chartOfAccountRepo *repository.ChartOfAccountRepositoryImpl
|
chartOfAccountRepo *repository.ChartOfAccountRepositoryImpl
|
||||||
accountRepo *repository.AccountRepositoryImpl
|
accountRepo *repository.AccountRepositoryImpl
|
||||||
orderIngredientTransactionRepo *repository.OrderIngredientTransactionRepositoryImpl
|
orderIngredientTransactionRepo *repository.OrderIngredientTransactionRepositoryImpl
|
||||||
customerPointsRepo *repository.CustomerPointsRepository
|
|
||||||
customerTokensRepo *repository.CustomerTokensRepository
|
customerTokensRepo *repository.CustomerTokensRepository
|
||||||
tierRepo *repository.TierRepository
|
tierRepo *repository.TierRepository
|
||||||
gameRepo *repository.GameRepository
|
gameRepo *repository.GameRepository
|
||||||
@ -222,7 +221,6 @@ func (a *App) initRepositories() *repositories {
|
|||||||
chartOfAccountRepo: repository.NewChartOfAccountRepositoryImpl(a.db),
|
chartOfAccountRepo: repository.NewChartOfAccountRepositoryImpl(a.db),
|
||||||
accountRepo: repository.NewAccountRepositoryImpl(a.db),
|
accountRepo: repository.NewAccountRepositoryImpl(a.db),
|
||||||
orderIngredientTransactionRepo: repository.NewOrderIngredientTransactionRepositoryImpl(a.db).(*repository.OrderIngredientTransactionRepositoryImpl),
|
orderIngredientTransactionRepo: repository.NewOrderIngredientTransactionRepositoryImpl(a.db).(*repository.OrderIngredientTransactionRepositoryImpl),
|
||||||
customerPointsRepo: repository.NewCustomerPointsRepository(a.db),
|
|
||||||
customerTokensRepo: repository.NewCustomerTokensRepository(a.db),
|
customerTokensRepo: repository.NewCustomerTokensRepository(a.db),
|
||||||
tierRepo: repository.NewTierRepository(a.db),
|
tierRepo: repository.NewTierRepository(a.db),
|
||||||
gameRepo: repository.NewGameRepository(a.db),
|
gameRepo: repository.NewGameRepository(a.db),
|
||||||
@ -263,7 +261,6 @@ type processors struct {
|
|||||||
chartOfAccountProcessor *processor.ChartOfAccountProcessorImpl
|
chartOfAccountProcessor *processor.ChartOfAccountProcessorImpl
|
||||||
accountProcessor *processor.AccountProcessorImpl
|
accountProcessor *processor.AccountProcessorImpl
|
||||||
orderIngredientTransactionProcessor *processor.OrderIngredientTransactionProcessorImpl
|
orderIngredientTransactionProcessor *processor.OrderIngredientTransactionProcessorImpl
|
||||||
customerPointsProcessor *processor.CustomerPointsProcessor
|
|
||||||
customerTokensProcessor *processor.CustomerTokensProcessor
|
customerTokensProcessor *processor.CustomerTokensProcessor
|
||||||
tierProcessor *processor.TierProcessor
|
tierProcessor *processor.TierProcessor
|
||||||
gameProcessor *processor.GameProcessor
|
gameProcessor *processor.GameProcessor
|
||||||
@ -310,7 +307,6 @@ func (a *App) initProcessors(cfg *config.Config, repos *repositories) *processor
|
|||||||
chartOfAccountProcessor: processor.NewChartOfAccountProcessorImpl(repos.chartOfAccountRepo, repos.chartOfAccountTypeRepo),
|
chartOfAccountProcessor: processor.NewChartOfAccountProcessorImpl(repos.chartOfAccountRepo, repos.chartOfAccountTypeRepo),
|
||||||
accountProcessor: processor.NewAccountProcessorImpl(repos.accountRepo, repos.chartOfAccountRepo),
|
accountProcessor: processor.NewAccountProcessorImpl(repos.accountRepo, repos.chartOfAccountRepo),
|
||||||
orderIngredientTransactionProcessor: processor.NewOrderIngredientTransactionProcessorImpl(repos.orderIngredientTransactionRepo, repos.productRecipeRepo, repos.ingredientRepo, repos.unitRepo).(*processor.OrderIngredientTransactionProcessorImpl),
|
orderIngredientTransactionProcessor: processor.NewOrderIngredientTransactionProcessorImpl(repos.orderIngredientTransactionRepo, repos.productRecipeRepo, repos.ingredientRepo, repos.unitRepo).(*processor.OrderIngredientTransactionProcessorImpl),
|
||||||
customerPointsProcessor: processor.NewCustomerPointsProcessor(repos.customerPointsRepo),
|
|
||||||
customerTokensProcessor: processor.NewCustomerTokensProcessor(repos.customerTokensRepo),
|
customerTokensProcessor: processor.NewCustomerTokensProcessor(repos.customerTokensRepo),
|
||||||
tierProcessor: processor.NewTierProcessor(repos.tierRepo),
|
tierProcessor: processor.NewTierProcessor(repos.tierRepo),
|
||||||
gameProcessor: processor.NewGameProcessor(repos.gameRepo),
|
gameProcessor: processor.NewGameProcessor(repos.gameRepo),
|
||||||
@ -320,7 +316,7 @@ func (a *App) initProcessors(cfg *config.Config, repos *repositories) *processor
|
|||||||
rewardProcessor: processor.NewRewardProcessor(repos.rewardRepo),
|
rewardProcessor: processor.NewRewardProcessor(repos.rewardRepo),
|
||||||
campaignProcessor: processor.NewCampaignProcessor(repos.campaignRepo),
|
campaignProcessor: processor.NewCampaignProcessor(repos.campaignRepo),
|
||||||
customerAuthProcessor: processor.NewCustomerAuthProcessor(repos.customerAuthRepo, otpProcessor, repos.otpRepo, cfg.GetCustomerJWTSecret(), cfg.GetCustomerJWTExpiresTTL()),
|
customerAuthProcessor: processor.NewCustomerAuthProcessor(repos.customerAuthRepo, otpProcessor, repos.otpRepo, cfg.GetCustomerJWTSecret(), cfg.GetCustomerJWTExpiresTTL()),
|
||||||
customerPointsProcessor: processor.NewCustomerPointsProcessor(repos.customerPointsRepo),
|
customerPointsProcessor: processor.NewCustomerPointsProcessor(repos.customerPointsRepo, repos.gameRepo),
|
||||||
otpProcessor: otpProcessor,
|
otpProcessor: otpProcessor,
|
||||||
fileClient: fileClient,
|
fileClient: fileClient,
|
||||||
inventoryMovementService: inventoryMovementService,
|
inventoryMovementService: inventoryMovementService,
|
||||||
|
|||||||
60
internal/contract/customer_game_contract.go
Normal file
60
internal/contract/customer_game_contract.go
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
package contract
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Request Contracts
|
||||||
|
type GetCustomerGamesRequest struct {
|
||||||
|
// No additional fields needed - customer ID comes from JWT token
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response Contracts
|
||||||
|
type GetCustomerGamesResponse struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
Data *GetCustomerGamesResponseData `json:"data,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetCustomerGamesResponseData struct {
|
||||||
|
Games []CustomerGameResponse `json:"games"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CustomerGameResponse struct {
|
||||||
|
ID uuid.UUID `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
IsActive bool `json:"is_active"`
|
||||||
|
Metadata *map[string]interface{} `json:"metadata,omitempty"`
|
||||||
|
Prizes []CustomerGamePrizeResponse `json:"prizes,omitempty"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CustomerGamePrizeResponse struct {
|
||||||
|
ID uuid.UUID `json:"id"`
|
||||||
|
GameID uuid.UUID `json:"game_id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Image *string `json:"image,omitempty"`
|
||||||
|
Metadata *map[string]interface{} `json:"metadata,omitempty"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ferris Wheel Game Contracts
|
||||||
|
type GetFerrisWheelGameRequest struct {
|
||||||
|
// No additional fields needed - customer ID comes from JWT token
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetFerrisWheelGameResponse struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
Data *GetFerrisWheelGameResponseData `json:"data,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetFerrisWheelGameResponseData struct {
|
||||||
|
Game CustomerGameResponse `json:"game"`
|
||||||
|
Prizes []CustomerGamePrizeResponse `json:"prizes"`
|
||||||
|
}
|
||||||
@ -14,6 +14,7 @@ type CreateGamePrizeRequest struct {
|
|||||||
MaxStock *int `json:"max_stock,omitempty"`
|
MaxStock *int `json:"max_stock,omitempty"`
|
||||||
Threshold *int64 `json:"threshold,omitempty"`
|
Threshold *int64 `json:"threshold,omitempty"`
|
||||||
FallbackPrizeID *uuid.UUID `json:"fallback_prize_id,omitempty"`
|
FallbackPrizeID *uuid.UUID `json:"fallback_prize_id,omitempty"`
|
||||||
|
Image *string `json:"image,omitempty" validate:"omitempty,max=500"`
|
||||||
Metadata map[string]interface{} `json:"metadata"`
|
Metadata map[string]interface{} `json:"metadata"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -24,6 +25,7 @@ type UpdateGamePrizeRequest struct {
|
|||||||
MaxStock *int `json:"max_stock,omitempty"`
|
MaxStock *int `json:"max_stock,omitempty"`
|
||||||
Threshold *int64 `json:"threshold,omitempty"`
|
Threshold *int64 `json:"threshold,omitempty"`
|
||||||
FallbackPrizeID *uuid.UUID `json:"fallback_prize_id,omitempty"`
|
FallbackPrizeID *uuid.UUID `json:"fallback_prize_id,omitempty"`
|
||||||
|
Image *string `json:"image,omitempty" validate:"omitempty,max=500"`
|
||||||
Metadata map[string]interface{} `json:"metadata,omitempty"`
|
Metadata map[string]interface{} `json:"metadata,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -36,6 +38,7 @@ type GamePrizeResponse struct {
|
|||||||
MaxStock *int `json:"max_stock,omitempty"`
|
MaxStock *int `json:"max_stock,omitempty"`
|
||||||
Threshold *int64 `json:"threshold,omitempty"`
|
Threshold *int64 `json:"threshold,omitempty"`
|
||||||
FallbackPrizeID *uuid.UUID `json:"fallback_prize_id,omitempty"`
|
FallbackPrizeID *uuid.UUID `json:"fallback_prize_id,omitempty"`
|
||||||
|
Image *string `json:"image,omitempty"`
|
||||||
Metadata map[string]interface{} `json:"metadata"`
|
Metadata map[string]interface{} `json:"metadata"`
|
||||||
Game *GameResponse `json:"game,omitempty"`
|
Game *GameResponse `json:"game,omitempty"`
|
||||||
FallbackPrize *GamePrizeResponse `json:"fallback_prize,omitempty"`
|
FallbackPrize *GamePrizeResponse `json:"fallback_prize,omitempty"`
|
||||||
|
|||||||
@ -16,6 +16,7 @@ type GamePrize struct {
|
|||||||
MaxStock *int `gorm:"" json:"max_stock,omitempty"`
|
MaxStock *int `gorm:"" json:"max_stock,omitempty"`
|
||||||
Threshold *int64 `gorm:"" json:"threshold,omitempty"`
|
Threshold *int64 `gorm:"" json:"threshold,omitempty"`
|
||||||
FallbackPrizeID *uuid.UUID `gorm:"type:uuid" json:"fallback_prize_id,omitempty"`
|
FallbackPrizeID *uuid.UUID `gorm:"type:uuid" json:"fallback_prize_id,omitempty"`
|
||||||
|
Image *string `gorm:"type:varchar(500)" json:"image,omitempty"`
|
||||||
Metadata Metadata `gorm:"type:jsonb;default:'{}'" json:"metadata"`
|
Metadata Metadata `gorm:"type:jsonb;default:'{}'" json:"metadata"`
|
||||||
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
|
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
|
||||||
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
|
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
|
||||||
|
|||||||
@ -17,6 +17,7 @@ type OtpSession struct {
|
|||||||
IsUsed bool `gorm:"default:false;index" json:"is_used"`
|
IsUsed bool `gorm:"default:false;index" json:"is_used"`
|
||||||
AttemptsCount int `gorm:"default:0" json:"attempts_count"`
|
AttemptsCount int `gorm:"default:0" json:"attempts_count"`
|
||||||
MaxAttempts int `gorm:"default:3" json:"max_attempts"`
|
MaxAttempts int `gorm:"default:3" json:"max_attempts"`
|
||||||
|
Metadata Metadata `gorm:"type:jsonb;default:'{}'" json:"metadata"`
|
||||||
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
|
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
|
||||||
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
|
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
|
||||||
}
|
}
|
||||||
|
|||||||
@ -121,3 +121,33 @@ func (h *CustomerPointsHandler) GetCustomerWallet(c *gin.Context) {
|
|||||||
|
|
||||||
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "CustomerPointsHandler::GetCustomerWallet")
|
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "CustomerPointsHandler::GetCustomerWallet")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *CustomerPointsHandler) GetCustomerGames(c *gin.Context) {
|
||||||
|
ctx := c.Request.Context()
|
||||||
|
|
||||||
|
response, err := h.customerPointsService.GetCustomerGames(ctx)
|
||||||
|
if err != nil {
|
||||||
|
logger.FromContext(ctx).WithError(err).Error("CustomerPointsHandler::GetCustomerGames -> service call failed")
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{
|
||||||
|
contract.NewResponseError(constants.InternalServerErrorCode, constants.RequestEntity, err.Error()),
|
||||||
|
}), "CustomerPointsHandler::GetCustomerGames")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "CustomerPointsHandler::GetCustomerGames")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *CustomerPointsHandler) GetFerrisWheelGame(c *gin.Context) {
|
||||||
|
ctx := c.Request.Context()
|
||||||
|
|
||||||
|
response, err := h.customerPointsService.GetFerrisWheelGame(ctx)
|
||||||
|
if err != nil {
|
||||||
|
logger.FromContext(ctx).WithError(err).Error("CustomerPointsHandler::GetFerrisWheelGame -> service call failed")
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{
|
||||||
|
contract.NewResponseError(constants.InternalServerErrorCode, constants.RequestEntity, err.Error()),
|
||||||
|
}), "CustomerPointsHandler::GetFerrisWheelGame")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "CustomerPointsHandler::GetFerrisWheelGame")
|
||||||
|
}
|
||||||
|
|||||||
@ -20,6 +20,7 @@ func ToGamePrizeResponse(gamePrize *entities.GamePrize) *models.GamePrizeRespons
|
|||||||
MaxStock: gamePrize.MaxStock,
|
MaxStock: gamePrize.MaxStock,
|
||||||
Threshold: gamePrize.Threshold,
|
Threshold: gamePrize.Threshold,
|
||||||
FallbackPrizeID: gamePrize.FallbackPrizeID,
|
FallbackPrizeID: gamePrize.FallbackPrizeID,
|
||||||
|
Image: gamePrize.Image,
|
||||||
Metadata: gamePrize.Metadata,
|
Metadata: gamePrize.Metadata,
|
||||||
Game: ToGameResponse(&gamePrize.Game),
|
Game: ToGameResponse(&gamePrize.Game),
|
||||||
FallbackPrize: ToGamePrizeResponse(gamePrize.FallbackPrize),
|
FallbackPrize: ToGamePrizeResponse(gamePrize.FallbackPrize),
|
||||||
@ -47,6 +48,7 @@ func ToGamePrizeEntity(req *models.CreateGamePrizeRequest) *entities.GamePrize {
|
|||||||
MaxStock: req.MaxStock,
|
MaxStock: req.MaxStock,
|
||||||
Threshold: req.Threshold,
|
Threshold: req.Threshold,
|
||||||
FallbackPrizeID: req.FallbackPrizeID,
|
FallbackPrizeID: req.FallbackPrizeID,
|
||||||
|
Image: req.Image,
|
||||||
Metadata: req.Metadata,
|
Metadata: req.Metadata,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -71,6 +73,9 @@ func UpdateGamePrizeEntity(gamePrize *entities.GamePrize, req *models.UpdateGame
|
|||||||
if req.FallbackPrizeID != nil {
|
if req.FallbackPrizeID != nil {
|
||||||
gamePrize.FallbackPrizeID = req.FallbackPrizeID
|
gamePrize.FallbackPrizeID = req.FallbackPrizeID
|
||||||
}
|
}
|
||||||
|
if req.Image != nil {
|
||||||
|
gamePrize.Image = req.Image
|
||||||
|
}
|
||||||
if req.Metadata != nil {
|
if req.Metadata != nil {
|
||||||
gamePrize.Metadata = req.Metadata
|
gamePrize.Metadata = req.Metadata
|
||||||
}
|
}
|
||||||
|
|||||||
60
internal/models/customer_game.go
Normal file
60
internal/models/customer_game.go
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Request Models
|
||||||
|
type GetCustomerGamesRequest struct {
|
||||||
|
// No additional fields needed - customer ID comes from JWT token
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response Models
|
||||||
|
type GetCustomerGamesResponse struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
Data *GetCustomerGamesResponseData `json:"data,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetCustomerGamesResponseData struct {
|
||||||
|
Games []CustomerGameResponse `json:"games"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CustomerGameResponse struct {
|
||||||
|
ID uuid.UUID `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
IsActive bool `json:"is_active"`
|
||||||
|
Metadata *map[string]interface{} `json:"metadata,omitempty"`
|
||||||
|
Prizes []CustomerGamePrizeResponse `json:"prizes,omitempty"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CustomerGamePrizeResponse struct {
|
||||||
|
ID uuid.UUID `json:"id"`
|
||||||
|
GameID uuid.UUID `json:"game_id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Image *string `json:"image,omitempty"`
|
||||||
|
Metadata *map[string]interface{} `json:"metadata,omitempty"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ferris Wheel Game Models
|
||||||
|
type GetFerrisWheelGameRequest struct {
|
||||||
|
// No additional fields needed - customer ID comes from JWT token
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetFerrisWheelGameResponse struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
Data *GetFerrisWheelGameResponseData `json:"data,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetFerrisWheelGameResponseData struct {
|
||||||
|
Game CustomerGameResponse `json:"game"`
|
||||||
|
Prizes []CustomerGamePrizeResponse `json:"prizes"`
|
||||||
|
}
|
||||||
@ -14,6 +14,7 @@ type CreateGamePrizeRequest struct {
|
|||||||
MaxStock *int `json:"max_stock,omitempty"`
|
MaxStock *int `json:"max_stock,omitempty"`
|
||||||
Threshold *int64 `json:"threshold,omitempty"`
|
Threshold *int64 `json:"threshold,omitempty"`
|
||||||
FallbackPrizeID *uuid.UUID `json:"fallback_prize_id,omitempty"`
|
FallbackPrizeID *uuid.UUID `json:"fallback_prize_id,omitempty"`
|
||||||
|
Image *string `json:"image,omitempty" validate:"omitempty,max=500"`
|
||||||
Metadata map[string]interface{} `json:"metadata"`
|
Metadata map[string]interface{} `json:"metadata"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -24,6 +25,7 @@ type UpdateGamePrizeRequest struct {
|
|||||||
MaxStock *int `json:"max_stock,omitempty"`
|
MaxStock *int `json:"max_stock,omitempty"`
|
||||||
Threshold *int64 `json:"threshold,omitempty"`
|
Threshold *int64 `json:"threshold,omitempty"`
|
||||||
FallbackPrizeID *uuid.UUID `json:"fallback_prize_id,omitempty"`
|
FallbackPrizeID *uuid.UUID `json:"fallback_prize_id,omitempty"`
|
||||||
|
Image *string `json:"image,omitempty" validate:"omitempty,max=500"`
|
||||||
Metadata map[string]interface{} `json:"metadata,omitempty"`
|
Metadata map[string]interface{} `json:"metadata,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -36,6 +38,7 @@ type GamePrizeResponse struct {
|
|||||||
MaxStock *int `json:"max_stock,omitempty"`
|
MaxStock *int `json:"max_stock,omitempty"`
|
||||||
Threshold *int64 `json:"threshold,omitempty"`
|
Threshold *int64 `json:"threshold,omitempty"`
|
||||||
FallbackPrizeID *uuid.UUID `json:"fallback_prize_id,omitempty"`
|
FallbackPrizeID *uuid.UUID `json:"fallback_prize_id,omitempty"`
|
||||||
|
Image *string `json:"image,omitempty"`
|
||||||
Metadata map[string]interface{} `json:"metadata"`
|
Metadata map[string]interface{} `json:"metadata"`
|
||||||
Game *GameResponse `json:"game,omitempty"`
|
Game *GameResponse `json:"game,omitempty"`
|
||||||
FallbackPrize *GamePrizeResponse `json:"fallback_prize,omitempty"`
|
FallbackPrize *GamePrizeResponse `json:"fallback_prize,omitempty"`
|
||||||
|
|||||||
@ -30,8 +30,6 @@ type customerAuthProcessor struct {
|
|||||||
otpRepo repository.OtpRepository
|
otpRepo repository.OtpRepository
|
||||||
jwtSecret string
|
jwtSecret string
|
||||||
tokenTTLMinutes int
|
tokenTTLMinutes int
|
||||||
otpStorage map[string]*models.OtpSession // In-memory storage for OTP sessions
|
|
||||||
registrationStorage map[string]*models.RegistrationSession // In-memory storage for registration sessions
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewCustomerAuthProcessor(customerAuthRepo repository.CustomerAuthRepository, otpProcessor OtpProcessor, otpRepo repository.OtpRepository, jwtSecret string, tokenTTLMinutes int) CustomerAuthProcessor {
|
func NewCustomerAuthProcessor(customerAuthRepo repository.CustomerAuthRepository, otpProcessor OtpProcessor, otpRepo repository.OtpRepository, jwtSecret string, tokenTTLMinutes int) CustomerAuthProcessor {
|
||||||
@ -41,8 +39,6 @@ func NewCustomerAuthProcessor(customerAuthRepo repository.CustomerAuthRepository
|
|||||||
otpRepo: otpRepo,
|
otpRepo: otpRepo,
|
||||||
jwtSecret: jwtSecret,
|
jwtSecret: jwtSecret,
|
||||||
tokenTTLMinutes: tokenTTLMinutes,
|
tokenTTLMinutes: tokenTTLMinutes,
|
||||||
otpStorage: make(map[string]*models.OtpSession),
|
|
||||||
registrationStorage: make(map[string]*models.RegistrationSession),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -155,17 +151,19 @@ func (p *customerAuthProcessor) StartRegistration(ctx context.Context, req *cont
|
|||||||
return nil, fmt.Errorf("failed to create OTP session: %w", err)
|
return nil, fmt.Errorf("failed to create OTP session: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store registration session
|
// Store registration data in OTP session metadata
|
||||||
registrationSession := &models.RegistrationSession{
|
registrationData := map[string]interface{}{
|
||||||
Token: registrationToken,
|
"registration_token": registrationToken,
|
||||||
PhoneNumber: req.PhoneNumber,
|
"name": req.Name,
|
||||||
Name: req.Name,
|
"birth_date": req.BirthDate,
|
||||||
BirthDate: req.BirthDate,
|
"step": "otp_sent",
|
||||||
ExpiresAt: time.Now().Add(10 * time.Minute),
|
|
||||||
Step: "otp_sent",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
p.registrationStorage[registrationToken] = registrationSession
|
// Update OTP session with registration metadata
|
||||||
|
otpSession.Metadata = registrationData
|
||||||
|
if err := p.otpRepo.UpdateOtpSession(ctx, otpSession); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to update OTP session with registration data: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
// Send OTP via WhatsApp
|
// Send OTP via WhatsApp
|
||||||
if err := p.otpProcessor.SendOtpViaWhatsApp(req.PhoneNumber, otpSession.Code, "registration"); err != nil {
|
if err := p.otpProcessor.SendOtpViaWhatsApp(req.PhoneNumber, otpSession.Code, "registration"); err != nil {
|
||||||
@ -184,18 +182,19 @@ func (p *customerAuthProcessor) StartRegistration(ctx context.Context, req *cont
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *customerAuthProcessor) VerifyOtp(ctx context.Context, req *contract.RegisterVerifyOtpRequest) (*models.RegisterVerifyOtpResponse, error) {
|
func (p *customerAuthProcessor) VerifyOtp(ctx context.Context, req *contract.RegisterVerifyOtpRequest) (*models.RegisterVerifyOtpResponse, error) {
|
||||||
// Get registration session
|
otpSession, err := p.otpRepo.GetOtpSessionByRegistrationToken(ctx, req.RegistrationToken)
|
||||||
registrationSession, exists := p.registrationStorage[req.RegistrationToken]
|
if err != nil {
|
||||||
if !exists {
|
return nil, fmt.Errorf("failed to get OTP session: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if otpSession == nil {
|
||||||
return nil, fmt.Errorf("invalid or expired registration token")
|
return nil, fmt.Errorf("invalid or expired registration token")
|
||||||
}
|
}
|
||||||
|
|
||||||
if time.Now().After(registrationSession.ExpiresAt) {
|
if otpSession.IsExpired() {
|
||||||
delete(p.registrationStorage, req.RegistrationToken)
|
|
||||||
return nil, fmt.Errorf("registration token expired")
|
return nil, fmt.Errorf("registration token expired")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate OTP format
|
|
||||||
if !p.otpProcessor.ValidateOtpCode(req.OtpCode) {
|
if !p.otpProcessor.ValidateOtpCode(req.OtpCode) {
|
||||||
return &models.RegisterVerifyOtpResponse{
|
return &models.RegisterVerifyOtpResponse{
|
||||||
Status: "FAILED",
|
Status: "FAILED",
|
||||||
@ -203,34 +202,33 @@ func (p *customerAuthProcessor) VerifyOtp(ctx context.Context, req *contract.Reg
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the OTP session for this phone number and purpose
|
|
||||||
otpSession, err := p.otpRepo.GetOtpSessionByPhoneAndPurpose(ctx, registrationSession.PhoneNumber, "registration")
|
|
||||||
if err != nil {
|
|
||||||
return &models.RegisterVerifyOtpResponse{
|
|
||||||
Status: "FAILED",
|
|
||||||
Message: "Failed to validate OTP session.",
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if otpSession == nil {
|
|
||||||
return &models.RegisterVerifyOtpResponse{
|
|
||||||
Status: "FAILED",
|
|
||||||
Message: "No active OTP session found.",
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify OTP code and mark as used
|
|
||||||
if otpSession.Code != req.OtpCode {
|
if otpSession.Code != req.OtpCode {
|
||||||
otpSession.IncrementAttempts()
|
otpSession.IncrementAttempts()
|
||||||
p.otpRepo.UpdateOtpSession(ctx, otpSession)
|
if err := p.otpRepo.UpdateOtpSession(ctx, otpSession); err != nil {
|
||||||
|
fmt.Printf("Warning: failed to update OTP session attempts: %v\n", err)
|
||||||
|
}
|
||||||
return &models.RegisterVerifyOtpResponse{
|
return &models.RegisterVerifyOtpResponse{
|
||||||
Status: "FAILED",
|
Status: "FAILED",
|
||||||
Message: "Invalid OTP code.",
|
Message: "Invalid OTP code.",
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if otpSession.IsUsed || otpSession.IsMaxAttemptsReached() {
|
||||||
|
return &models.RegisterVerifyOtpResponse{
|
||||||
|
Status: "FAILED",
|
||||||
|
Message: "OTP code already used or max attempts reached.",
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Mark OTP as used
|
// Mark OTP as used
|
||||||
otpSession.MarkAsUsed()
|
otpSession.MarkAsUsed()
|
||||||
|
|
||||||
|
// Update registration step in metadata
|
||||||
|
if otpSession.Metadata == nil {
|
||||||
|
otpSession.Metadata = make(map[string]interface{})
|
||||||
|
}
|
||||||
|
otpSession.Metadata["step"] = "otp_verified"
|
||||||
|
|
||||||
if err := p.otpRepo.UpdateOtpSession(ctx, otpSession); err != nil {
|
if err := p.otpRepo.UpdateOtpSession(ctx, otpSession); err != nil {
|
||||||
return &models.RegisterVerifyOtpResponse{
|
return &models.RegisterVerifyOtpResponse{
|
||||||
Status: "FAILED",
|
Status: "FAILED",
|
||||||
@ -238,10 +236,6 @@ func (p *customerAuthProcessor) VerifyOtp(ctx context.Context, req *contract.Reg
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update registration session
|
|
||||||
registrationSession.Step = "otp_verified"
|
|
||||||
p.registrationStorage[req.RegistrationToken] = registrationSession
|
|
||||||
|
|
||||||
return &models.RegisterVerifyOtpResponse{
|
return &models.RegisterVerifyOtpResponse{
|
||||||
Status: "OTP_VERIFIED",
|
Status: "OTP_VERIFIED",
|
||||||
Message: "OTP verified, continue to set password.",
|
Message: "OTP verified, continue to set password.",
|
||||||
@ -252,23 +246,26 @@ func (p *customerAuthProcessor) VerifyOtp(ctx context.Context, req *contract.Reg
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *customerAuthProcessor) SetPassword(ctx context.Context, req *contract.RegisterSetPasswordRequest) (*models.RegisterSetPasswordResponse, error) {
|
func (p *customerAuthProcessor) SetPassword(ctx context.Context, req *contract.RegisterSetPasswordRequest) (*models.RegisterSetPasswordResponse, error) {
|
||||||
// Validate passwords match
|
|
||||||
if req.Password != req.ConfirmPassword {
|
if req.Password != req.ConfirmPassword {
|
||||||
return nil, fmt.Errorf("passwords do not match")
|
return nil, fmt.Errorf("passwords do not match")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get registration session
|
// Get OTP session by registration token from metadata
|
||||||
registrationSession, exists := p.registrationStorage[req.RegistrationToken]
|
otpSession, err := p.otpRepo.GetOtpSessionByRegistrationToken(ctx, req.RegistrationToken)
|
||||||
if !exists {
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get OTP session: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if otpSession == nil {
|
||||||
return nil, fmt.Errorf("invalid or expired registration token")
|
return nil, fmt.Errorf("invalid or expired registration token")
|
||||||
}
|
}
|
||||||
|
|
||||||
if time.Now().After(registrationSession.ExpiresAt) {
|
if otpSession.IsExpired() {
|
||||||
delete(p.registrationStorage, req.RegistrationToken)
|
|
||||||
return nil, fmt.Errorf("registration token expired")
|
return nil, fmt.Errorf("registration token expired")
|
||||||
}
|
}
|
||||||
|
|
||||||
if registrationSession.Step != "otp_verified" {
|
step, ok := otpSession.Metadata["step"].(string)
|
||||||
|
if !ok || step != "otp_verified" {
|
||||||
return nil, fmt.Errorf("OTP verification required before setting password")
|
return nil, fmt.Errorf("OTP verification required before setting password")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -280,31 +277,38 @@ func (p *customerAuthProcessor) SetPassword(ctx context.Context, req *contract.R
|
|||||||
|
|
||||||
passwordHashStr := string(passwordHash)
|
passwordHashStr := string(passwordHash)
|
||||||
|
|
||||||
|
// Extract registration data from OTP session metadata
|
||||||
|
name, ok := otpSession.Metadata["name"].(string)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("invalid registration data: name not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
birthDateStr, ok := otpSession.Metadata["birth_date"].(string)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("invalid registration data: birth_date not found")
|
||||||
|
}
|
||||||
|
|
||||||
// Parse birth date
|
// Parse birth date
|
||||||
birthDate, err := time.Parse("2006-01-02", registrationSession.BirthDate)
|
birthDate, err := time.Parse("2006-01-02", birthDateStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("invalid birth date format: %w", err)
|
return nil, fmt.Errorf("invalid birth date format: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create customer
|
defaultOrgID := uuid.MustParse("87bec7c1-e274-4f66-bac5-84e632208470") // This should be configurable
|
||||||
|
|
||||||
customer := &entities.Customer{
|
customer := &entities.Customer{
|
||||||
Name: registrationSession.Name,
|
OrganizationID: defaultOrgID,
|
||||||
PhoneNumber: ®istrationSession.PhoneNumber,
|
Name: name,
|
||||||
|
PhoneNumber: &otpSession.PhoneNumber,
|
||||||
BirthDate: &birthDate,
|
BirthDate: &birthDate,
|
||||||
PasswordHash: &passwordHashStr,
|
PasswordHash: &passwordHashStr,
|
||||||
IsActive: true,
|
IsActive: true,
|
||||||
// Note: OrganizationID should be set based on your business logic
|
|
||||||
// For now, we'll use a default organization or require it in the request
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := p.customerAuthRepo.CreateCustomer(ctx, customer); err != nil {
|
if err := p.customerAuthRepo.CreateCustomer(ctx, customer); err != nil {
|
||||||
return nil, fmt.Errorf("failed to create customer: %w", err)
|
return nil, fmt.Errorf("failed to create customer: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clean up registration session
|
|
||||||
delete(p.registrationStorage, req.RegistrationToken)
|
|
||||||
|
|
||||||
// Generate JWT tokens using customer JWT util
|
|
||||||
accessToken, refreshToken, _, err := util.GenerateCustomerTokens(customer, p.jwtSecret, p.tokenTTLMinutes)
|
accessToken, refreshToken, _, err := util.GenerateCustomerTokens(customer, p.jwtSecret, p.tokenTTLMinutes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to generate tokens: %w", err)
|
return nil, fmt.Errorf("failed to generate tokens: %w", err)
|
||||||
@ -320,7 +324,7 @@ func (p *customerAuthProcessor) SetPassword(ctx context.Context, req *contract.R
|
|||||||
ID: customer.ID,
|
ID: customer.ID,
|
||||||
Name: customer.Name,
|
Name: customer.Name,
|
||||||
PhoneNumber: *customer.PhoneNumber,
|
PhoneNumber: *customer.PhoneNumber,
|
||||||
BirthDate: registrationSession.BirthDate,
|
BirthDate: birthDate.Format("2006-01-02"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}, nil
|
}, nil
|
||||||
|
|||||||
@ -13,11 +13,13 @@ import (
|
|||||||
|
|
||||||
type CustomerPointsProcessor struct {
|
type CustomerPointsProcessor struct {
|
||||||
customerPointsRepo repository.CustomerPointsRepository
|
customerPointsRepo repository.CustomerPointsRepository
|
||||||
|
gameRepo *repository.GameRepository
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewCustomerPointsProcessor(customerPointsRepo repository.CustomerPointsRepository) *CustomerPointsProcessor {
|
func NewCustomerPointsProcessor(customerPointsRepo repository.CustomerPointsRepository, gameRepo *repository.GameRepository) *CustomerPointsProcessor {
|
||||||
return &CustomerPointsProcessor{
|
return &CustomerPointsProcessor{
|
||||||
customerPointsRepo: customerPointsRepo,
|
customerPointsRepo: customerPointsRepo,
|
||||||
|
gameRepo: gameRepo,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -220,3 +222,92 @@ func (p *CustomerPointsProcessor) GetCustomerWalletAPI(ctx context.Context, cust
|
|||||||
},
|
},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetCustomerGamesAPI gets active SPIN games for customers
|
||||||
|
func (p *CustomerPointsProcessor) GetCustomerGamesAPI(ctx context.Context) (*models.GetCustomerGamesResponse, error) {
|
||||||
|
// Get active SPIN games
|
||||||
|
games, err := p.gameRepo.GetActiveSpinGames(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get active SPIN games: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert to response format
|
||||||
|
var gameResponses []models.CustomerGameResponse
|
||||||
|
for _, game := range games {
|
||||||
|
var prizeResponses []models.CustomerGamePrizeResponse
|
||||||
|
for _, prize := range game.Prizes {
|
||||||
|
prizeResponses = append(prizeResponses, models.CustomerGamePrizeResponse{
|
||||||
|
ID: prize.ID,
|
||||||
|
GameID: prize.GameID,
|
||||||
|
Name: prize.Name,
|
||||||
|
Image: prize.Image,
|
||||||
|
Metadata: (*map[string]interface{})(&prize.Metadata),
|
||||||
|
CreatedAt: prize.CreatedAt,
|
||||||
|
UpdatedAt: prize.UpdatedAt,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
gameResponses = append(gameResponses, models.CustomerGameResponse{
|
||||||
|
ID: game.ID,
|
||||||
|
Name: game.Name,
|
||||||
|
Type: string(game.Type),
|
||||||
|
IsActive: game.IsActive,
|
||||||
|
Metadata: (*map[string]interface{})(&game.Metadata),
|
||||||
|
Prizes: prizeResponses,
|
||||||
|
CreatedAt: game.CreatedAt,
|
||||||
|
UpdatedAt: game.UpdatedAt,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return &models.GetCustomerGamesResponse{
|
||||||
|
Status: "SUCCESS",
|
||||||
|
Message: "Customer games retrieved successfully.",
|
||||||
|
Data: &models.GetCustomerGamesResponseData{
|
||||||
|
Games: gameResponses,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFerrisWheelGameAPI gets the Ferris Wheel game for customers
|
||||||
|
func (p *CustomerPointsProcessor) GetFerrisWheelGameAPI(ctx context.Context) (*models.GetFerrisWheelGameResponse, error) {
|
||||||
|
// Get Ferris Wheel game
|
||||||
|
game, err := p.gameRepo.GetFerrisWheelGame(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get Ferris Wheel game: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert prizes to response format
|
||||||
|
var prizeResponses []models.CustomerGamePrizeResponse
|
||||||
|
for _, prize := range game.Prizes {
|
||||||
|
prizeResponses = append(prizeResponses, models.CustomerGamePrizeResponse{
|
||||||
|
ID: prize.ID,
|
||||||
|
GameID: prize.GameID,
|
||||||
|
Name: prize.Name,
|
||||||
|
Image: prize.Image,
|
||||||
|
Metadata: (*map[string]interface{})(&prize.Metadata),
|
||||||
|
CreatedAt: prize.CreatedAt,
|
||||||
|
UpdatedAt: prize.UpdatedAt,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert game to response format
|
||||||
|
gameResponse := models.CustomerGameResponse{
|
||||||
|
ID: game.ID,
|
||||||
|
Name: game.Name,
|
||||||
|
Type: string(game.Type),
|
||||||
|
IsActive: game.IsActive,
|
||||||
|
Metadata: (*map[string]interface{})(&game.Metadata),
|
||||||
|
Prizes: prizeResponses,
|
||||||
|
CreatedAt: game.CreatedAt,
|
||||||
|
UpdatedAt: game.UpdatedAt,
|
||||||
|
}
|
||||||
|
|
||||||
|
return &models.GetFerrisWheelGameResponse{
|
||||||
|
Status: "SUCCESS",
|
||||||
|
Message: "Ferris Wheel game retrieved successfully.",
|
||||||
|
Data: &models.GetFerrisWheelGameResponseData{
|
||||||
|
Game: gameResponse,
|
||||||
|
Prizes: prizeResponses,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|||||||
@ -19,7 +19,7 @@ type GamePlayProcessor struct {
|
|||||||
gameRepo *repository.GameRepository
|
gameRepo *repository.GameRepository
|
||||||
gamePrizeRepo *repository.GamePrizeRepository
|
gamePrizeRepo *repository.GamePrizeRepository
|
||||||
customerTokensRepo *repository.CustomerTokensRepository
|
customerTokensRepo *repository.CustomerTokensRepository
|
||||||
customerPointsRepo *repository.CustomerPointsRepository
|
customerPointsRepo repository.CustomerPointsRepository
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewGamePlayProcessor(
|
func NewGamePlayProcessor(
|
||||||
@ -27,7 +27,7 @@ func NewGamePlayProcessor(
|
|||||||
gameRepo *repository.GameRepository,
|
gameRepo *repository.GameRepository,
|
||||||
gamePrizeRepo *repository.GamePrizeRepository,
|
gamePrizeRepo *repository.GamePrizeRepository,
|
||||||
customerTokensRepo *repository.CustomerTokensRepository,
|
customerTokensRepo *repository.CustomerTokensRepository,
|
||||||
customerPointsRepo *repository.CustomerPointsRepository,
|
customerPointsRepo repository.CustomerPointsRepository,
|
||||||
) *GamePlayProcessor {
|
) *GamePlayProcessor {
|
||||||
return &GamePlayProcessor{
|
return &GamePlayProcessor{
|
||||||
gamePlayRepo: gamePlayRepo,
|
gamePlayRepo: gamePlayRepo,
|
||||||
|
|||||||
@ -86,3 +86,27 @@ func (r *GameRepository) GetActiveGames(ctx context.Context) ([]entities.Game, e
|
|||||||
}
|
}
|
||||||
return games, nil
|
return games, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *GameRepository) GetActiveSpinGames(ctx context.Context) ([]entities.Game, error) {
|
||||||
|
var games []entities.Game
|
||||||
|
err := r.db.WithContext(ctx).
|
||||||
|
Preload("Prizes").
|
||||||
|
Where("is_active = ? AND type = ?", true, entities.GameTypeSpin).
|
||||||
|
Find(&games).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return games, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *GameRepository) GetFerrisWheelGame(ctx context.Context) (*entities.Game, error) {
|
||||||
|
var game entities.Game
|
||||||
|
err := r.db.WithContext(ctx).
|
||||||
|
Preload("Prizes").
|
||||||
|
Where("is_active = ? AND LOWER(name) LIKE ?", true, "%ferris wheel%").
|
||||||
|
First(&game).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &game, nil
|
||||||
|
}
|
||||||
|
|||||||
@ -15,6 +15,7 @@ type OtpRepository interface {
|
|||||||
GetOtpSessionByToken(ctx context.Context, token string) (*entities.OtpSession, error)
|
GetOtpSessionByToken(ctx context.Context, token string) (*entities.OtpSession, error)
|
||||||
GetOtpSessionByPhoneAndPurpose(ctx context.Context, phoneNumber string, purpose string) (*entities.OtpSession, error)
|
GetOtpSessionByPhoneAndPurpose(ctx context.Context, phoneNumber string, purpose string) (*entities.OtpSession, error)
|
||||||
GetLastOtpSessionByPhoneAndPurpose(ctx context.Context, phoneNumber string, purpose string) (*entities.OtpSession, error)
|
GetLastOtpSessionByPhoneAndPurpose(ctx context.Context, phoneNumber string, purpose string) (*entities.OtpSession, error)
|
||||||
|
GetOtpSessionByRegistrationToken(ctx context.Context, registrationToken string) (*entities.OtpSession, error)
|
||||||
UpdateOtpSession(ctx context.Context, otpSession *entities.OtpSession) error
|
UpdateOtpSession(ctx context.Context, otpSession *entities.OtpSession) error
|
||||||
DeleteOtpSession(ctx context.Context, token string) error
|
DeleteOtpSession(ctx context.Context, token string) error
|
||||||
DeleteExpiredOtpSessions(ctx context.Context) error
|
DeleteExpiredOtpSessions(ctx context.Context) error
|
||||||
@ -73,6 +74,18 @@ func (r *otpRepository) GetLastOtpSessionByPhoneAndPurpose(ctx context.Context,
|
|||||||
return &otpSession, nil
|
return &otpSession, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *otpRepository) GetOtpSessionByRegistrationToken(ctx context.Context, registrationToken string) (*entities.OtpSession, error) {
|
||||||
|
var otpSession entities.OtpSession
|
||||||
|
if err := r.db.WithContext(ctx).Where("metadata->>'registration_token' = ? AND purpose = ?",
|
||||||
|
registrationToken, "registration").First(&otpSession).Error; err != nil {
|
||||||
|
if err == gorm.ErrRecordNotFound {
|
||||||
|
return nil, nil // OTP session not found
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("failed to get OTP session by registration token: %w", err)
|
||||||
|
}
|
||||||
|
return &otpSession, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (r *otpRepository) UpdateOtpSession(ctx context.Context, otpSession *entities.OtpSession) error {
|
func (r *otpRepository) UpdateOtpSession(ctx context.Context, otpSession *entities.OtpSession) error {
|
||||||
if err := r.db.WithContext(ctx).Save(otpSession).Error; err != nil {
|
if err := r.db.WithContext(ctx).Save(otpSession).Error; err != nil {
|
||||||
return fmt.Errorf("failed to update OTP session: %w", err)
|
return fmt.Errorf("failed to update OTP session: %w", err)
|
||||||
|
|||||||
@ -194,6 +194,8 @@ func (r *Router) addAppRoutes(rg *gin.Engine) {
|
|||||||
customer.GET("/points", r.customerPointsHandler.GetCustomerPoints)
|
customer.GET("/points", r.customerPointsHandler.GetCustomerPoints)
|
||||||
customer.GET("/tokens", r.customerPointsHandler.GetCustomerTokens)
|
customer.GET("/tokens", r.customerPointsHandler.GetCustomerTokens)
|
||||||
customer.GET("/wallet", r.customerPointsHandler.GetCustomerWallet)
|
customer.GET("/wallet", r.customerPointsHandler.GetCustomerWallet)
|
||||||
|
customer.GET("/games", r.customerPointsHandler.GetCustomerGames)
|
||||||
|
customer.GET("/ferris-wheel", r.customerPointsHandler.GetFerrisWheelGame)
|
||||||
}
|
}
|
||||||
|
|
||||||
organizations := v1.Group("/organizations")
|
organizations := v1.Group("/organizations")
|
||||||
|
|||||||
@ -12,6 +12,8 @@ type CustomerPointsService interface {
|
|||||||
GetCustomerPoints(ctx context.Context, customerID string) (*models.GetCustomerPointsResponse, error)
|
GetCustomerPoints(ctx context.Context, customerID string) (*models.GetCustomerPointsResponse, error)
|
||||||
GetCustomerTokens(ctx context.Context, customerID string) (*models.GetCustomerTokensResponse, error)
|
GetCustomerTokens(ctx context.Context, customerID string) (*models.GetCustomerTokensResponse, error)
|
||||||
GetCustomerWallet(ctx context.Context, customerID string) (*models.GetCustomerWalletResponse, error)
|
GetCustomerWallet(ctx context.Context, customerID string) (*models.GetCustomerWalletResponse, error)
|
||||||
|
GetCustomerGames(ctx context.Context) (*models.GetCustomerGamesResponse, error)
|
||||||
|
GetFerrisWheelGame(ctx context.Context) (*models.GetFerrisWheelGameResponse, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type customerPointsService struct {
|
type customerPointsService struct {
|
||||||
@ -62,3 +64,21 @@ func (s *customerPointsService) GetCustomerWallet(ctx context.Context, customerI
|
|||||||
|
|
||||||
return response, nil
|
return response, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *customerPointsService) GetCustomerGames(ctx context.Context) (*models.GetCustomerGamesResponse, error) {
|
||||||
|
response, err := s.customerPointsProcessor.GetCustomerGamesAPI(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get customer games: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *customerPointsService) GetFerrisWheelGame(ctx context.Context) (*models.GetFerrisWheelGameResponse, error) {
|
||||||
|
response, err := s.customerPointsProcessor.GetFerrisWheelGameAPI(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get Ferris Wheel game: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
|||||||
@ -49,7 +49,11 @@ func CustomerPointsModelToResponse(model *models.CustomerPointsResponse) *contra
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func PaginatedCustomerPointsResponseToContract(model *models.PaginatedResponse[models.CustomerPointsResponse]) *contract.PaginatedCustomerPointsResponse {
|
func PaginatedCustomerPointsResponseToContract(model *models.PaginatedCustomerPointsResponse) *contract.PaginatedCustomerPointsResponse {
|
||||||
|
if model == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
responses := make([]contract.CustomerPointsResponse, len(model.Data))
|
responses := make([]contract.CustomerPointsResponse, len(model.Data))
|
||||||
for i, item := range model.Data {
|
for i, item := range model.Data {
|
||||||
responses[i] = *CustomerPointsModelToResponse(&item)
|
responses[i] = *CustomerPointsModelToResponse(&item)
|
||||||
@ -57,10 +61,10 @@ func PaginatedCustomerPointsResponseToContract(model *models.PaginatedResponse[m
|
|||||||
|
|
||||||
return &contract.PaginatedCustomerPointsResponse{
|
return &contract.PaginatedCustomerPointsResponse{
|
||||||
Data: responses,
|
Data: responses,
|
||||||
TotalCount: int(model.Pagination.Total),
|
TotalCount: model.TotalCount,
|
||||||
Page: model.Pagination.Page,
|
Page: model.Page,
|
||||||
Limit: model.Pagination.Limit,
|
Limit: model.Limit,
|
||||||
TotalPages: model.Pagination.TotalPages,
|
TotalPages: model.TotalPages,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
3
migrations/000059_add_image_to_game_prizes.down.sql
Normal file
3
migrations/000059_add_image_to_game_prizes.down.sql
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
-- Remove image field from game_prizes table
|
||||||
|
ALTER TABLE game_prizes
|
||||||
|
DROP COLUMN image;
|
||||||
6
migrations/000059_add_image_to_game_prizes.up.sql
Normal file
6
migrations/000059_add_image_to_game_prizes.up.sql
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
-- Add image field to game_prizes table
|
||||||
|
ALTER TABLE game_prizes
|
||||||
|
ADD COLUMN image VARCHAR(500);
|
||||||
|
|
||||||
|
-- Add comment for the new column
|
||||||
|
COMMENT ON COLUMN game_prizes.image IS 'URL or path to the prize image';
|
||||||
3
migrations/000060_add_metadata_to_otp_sessions.down.sql
Normal file
3
migrations/000060_add_metadata_to_otp_sessions.down.sql
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
-- Remove metadata field from otp_sessions table
|
||||||
|
ALTER TABLE otp_sessions
|
||||||
|
DROP COLUMN metadata;
|
||||||
6
migrations/000060_add_metadata_to_otp_sessions.up.sql
Normal file
6
migrations/000060_add_metadata_to_otp_sessions.up.sql
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
-- Add metadata field to otp_sessions table
|
||||||
|
ALTER TABLE otp_sessions
|
||||||
|
ADD COLUMN metadata JSONB DEFAULT '{}';
|
||||||
|
|
||||||
|
-- Add comment for the new column
|
||||||
|
COMMENT ON COLUMN otp_sessions.metadata IS 'Additional data stored as JSON for the OTP session';
|
||||||
Loading…
x
Reference in New Issue
Block a user