Add Tiers and Game Prize

This commit is contained in:
Aditya Siregar 2025-09-17 19:30:17 +07:00
parent 12ee54390f
commit 201e24041b
66 changed files with 6365 additions and 2 deletions

View File

@ -94,6 +94,8 @@ func (a *App) Initialize(cfg *config.Config) error {
validators.accountValidator,
*services.orderIngredientTransactionService,
validators.orderIngredientTransactionValidator,
services.gamificationService,
validators.gamificationValidator,
)
return nil
@ -167,6 +169,13 @@ type repositories struct {
chartOfAccountRepo *repository.ChartOfAccountRepositoryImpl
accountRepo *repository.AccountRepositoryImpl
orderIngredientTransactionRepo *repository.OrderIngredientTransactionRepositoryImpl
customerPointsRepo *repository.CustomerPointsRepository
customerTokensRepo *repository.CustomerTokensRepository
tierRepo *repository.TierRepository
gameRepo *repository.GameRepository
gamePrizeRepo *repository.GamePrizeRepository
gamePlayRepo *repository.GamePlayRepository
omsetTrackerRepo *repository.OmsetTrackerRepository
txManager *repository.TxManager
}
@ -200,7 +209,14 @@ func (a *App) initRepositories() *repositories {
chartOfAccountRepo: repository.NewChartOfAccountRepositoryImpl(a.db),
accountRepo: repository.NewAccountRepositoryImpl(a.db),
orderIngredientTransactionRepo: repository.NewOrderIngredientTransactionRepositoryImpl(a.db).(*repository.OrderIngredientTransactionRepositoryImpl),
txManager: repository.NewTxManager(a.db),
customerPointsRepo: repository.NewCustomerPointsRepository(a.db),
customerTokensRepo: repository.NewCustomerTokensRepository(a.db),
tierRepo: repository.NewTierRepository(a.db),
gameRepo: repository.NewGameRepository(a.db),
gamePrizeRepo: repository.NewGamePrizeRepository(a.db),
gamePlayRepo: repository.NewGamePlayRepository(a.db),
omsetTrackerRepo: repository.NewOmsetTrackerRepository(a.db),
txManager: repository.NewTxManager(a.db),
}
}
@ -229,6 +245,13 @@ type processors struct {
chartOfAccountProcessor *processor.ChartOfAccountProcessorImpl
accountProcessor *processor.AccountProcessorImpl
orderIngredientTransactionProcessor *processor.OrderIngredientTransactionProcessorImpl
customerPointsProcessor *processor.CustomerPointsProcessor
customerTokensProcessor *processor.CustomerTokensProcessor
tierProcessor *processor.TierProcessor
gameProcessor *processor.GameProcessor
gamePrizeProcessor *processor.GamePrizeProcessor
gamePlayProcessor *processor.GamePlayProcessor
omsetTrackerProcessor *processor.OmsetTrackerProcessor
fileClient processor.FileClient
inventoryMovementService service.InventoryMovementService
}
@ -262,6 +285,13 @@ func (a *App) initProcessors(cfg *config.Config, repos *repositories) *processor
chartOfAccountProcessor: processor.NewChartOfAccountProcessorImpl(repos.chartOfAccountRepo, repos.chartOfAccountTypeRepo),
accountProcessor: processor.NewAccountProcessorImpl(repos.accountRepo, repos.chartOfAccountRepo),
orderIngredientTransactionProcessor: processor.NewOrderIngredientTransactionProcessorImpl(repos.orderIngredientTransactionRepo, repos.productRecipeRepo, repos.ingredientRepo, repos.unitRepo).(*processor.OrderIngredientTransactionProcessorImpl),
customerPointsProcessor: processor.NewCustomerPointsProcessor(repos.customerPointsRepo),
customerTokensProcessor: processor.NewCustomerTokensProcessor(repos.customerTokensRepo),
tierProcessor: processor.NewTierProcessor(repos.tierRepo),
gameProcessor: processor.NewGameProcessor(repos.gameRepo),
gamePrizeProcessor: processor.NewGamePrizeProcessor(repos.gamePrizeRepo),
gamePlayProcessor: processor.NewGamePlayProcessor(repos.gamePlayRepo, repos.gameRepo, repos.gamePrizeRepo, repos.customerTokensRepo, repos.customerPointsRepo),
omsetTrackerProcessor: processor.NewOmsetTrackerProcessor(repos.omsetTrackerRepo),
fileClient: fileClient,
inventoryMovementService: inventoryMovementService,
}
@ -294,6 +324,7 @@ type services struct {
chartOfAccountService service.ChartOfAccountService
accountService service.AccountService
orderIngredientTransactionService *service.OrderIngredientTransactionService
gamificationService service.GamificationService
}
func (a *App) initServices(processors *processors, repos *repositories, cfg *config.Config) *services {
@ -324,6 +355,7 @@ func (a *App) initServices(processors *processors, repos *repositories, cfg *con
chartOfAccountService := service.NewChartOfAccountService(processors.chartOfAccountProcessor)
accountService := service.NewAccountService(processors.accountProcessor)
orderIngredientTransactionService := service.NewOrderIngredientTransactionService(processors.orderIngredientTransactionProcessor, repos.txManager)
gamificationService := service.NewGamificationService(processors.customerPointsProcessor, processors.customerTokensProcessor, processors.tierProcessor, processors.gameProcessor, processors.gamePrizeProcessor, processors.gamePlayProcessor, processors.omsetTrackerProcessor)
// Update order service with order ingredient transaction service
orderService = service.NewOrderServiceImpl(processors.orderProcessor, repos.tableRepo, orderIngredientTransactionService, processors.orderIngredientTransactionProcessor, *repos.productRecipeRepo, repos.txManager)
@ -355,6 +387,7 @@ func (a *App) initServices(processors *processors, repos *repositories, cfg *con
chartOfAccountService: chartOfAccountService,
accountService: accountService,
orderIngredientTransactionService: orderIngredientTransactionService,
gamificationService: gamificationService,
}
}
@ -388,6 +421,7 @@ type validators struct {
chartOfAccountValidator *validator.ChartOfAccountValidatorImpl
accountValidator *validator.AccountValidatorImpl
orderIngredientTransactionValidator *validator.OrderIngredientTransactionValidatorImpl
gamificationValidator *validator.GamificationValidatorImpl
}
func (a *App) initValidators() *validators {
@ -411,5 +445,6 @@ func (a *App) initValidators() *validators {
chartOfAccountValidator: validator.NewChartOfAccountValidator().(*validator.ChartOfAccountValidatorImpl),
accountValidator: validator.NewAccountValidator().(*validator.AccountValidatorImpl),
orderIngredientTransactionValidator: validator.NewOrderIngredientTransactionValidator().(*validator.OrderIngredientTransactionValidatorImpl),
gamificationValidator: validator.NewGamificationValidator(),
}
}

View File

@ -42,6 +42,14 @@ const (
PurchaseOrderServiceEntity = "purchase_order_service"
IngredientUnitConverterServiceEntity = "ingredient_unit_converter_service"
TableEntity = "table"
// Gamification entities
CustomerPointsEntity = "customer_points"
CustomerTokensEntity = "customer_tokens"
TierEntity = "tier"
GameEntity = "game"
GamePrizeEntity = "game_prize"
GamePlayEntity = "game_play"
OmsetTrackerEntity = "omset_tracker"
)
var HttpErrorMap = map[string]int{

View File

@ -0,0 +1,49 @@
package contract
import (
"time"
"github.com/google/uuid"
)
type CreateCustomerPointsRequest struct {
CustomerID uuid.UUID `json:"customer_id" validate:"required"`
Balance int64 `json:"balance" validate:"min=0"`
}
type UpdateCustomerPointsRequest struct {
Balance int64 `json:"balance" validate:"min=0"`
}
type AddCustomerPointsRequest struct {
Points int64 `json:"points" validate:"required,min=1"`
}
type DeductCustomerPointsRequest struct {
Points int64 `json:"points" validate:"required,min=1"`
}
type CustomerPointsResponse struct {
ID uuid.UUID `json:"id"`
CustomerID uuid.UUID `json:"customer_id"`
Balance int64 `json:"balance"`
Customer *CustomerResponse `json:"customer,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
type ListCustomerPointsRequest struct {
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"`
}

View File

@ -0,0 +1,52 @@
package contract
import (
"time"
"github.com/google/uuid"
)
type CreateCustomerTokensRequest struct {
CustomerID uuid.UUID `json:"customer_id" validate:"required"`
TokenType string `json:"token_type" validate:"required,oneof=SPIN RAFFLE MINIGAME"`
Balance int64 `json:"balance" validate:"min=0"`
}
type UpdateCustomerTokensRequest struct {
Balance int64 `json:"balance" validate:"min=0"`
}
type AddCustomerTokensRequest struct {
Tokens int64 `json:"tokens" validate:"required,min=1"`
}
type DeductCustomerTokensRequest struct {
Tokens int64 `json:"tokens" validate:"required,min=1"`
}
type CustomerTokensResponse struct {
ID uuid.UUID `json:"id"`
CustomerID uuid.UUID `json:"customer_id"`
TokenType string `json:"token_type"`
Balance int64 `json:"balance"`
Customer *CustomerResponse `json:"customer,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
type ListCustomerTokensRequest struct {
Page int `json:"page" validate:"min=1"`
Limit int `json:"limit" validate:"min=1,max=100"`
Search string `json:"search"`
TokenType string `json:"token_type" validate:"omitempty,oneof=SPIN RAFFLE MINIGAME"`
SortBy string `json:"sort_by" validate:"omitempty,oneof=balance token_type created_at updated_at"`
SortOrder string `json:"sort_order" validate:"omitempty,oneof=asc desc"`
}
type PaginatedCustomerTokensResponse struct {
Data []CustomerTokensResponse `json:"data"`
TotalCount int `json:"total_count"`
Page int `json:"page"`
Limit int `json:"limit"`
TotalPages int `json:"total_pages"`
}

View File

@ -0,0 +1,49 @@
package contract
import (
"time"
"github.com/google/uuid"
)
type CreateGameRequest struct {
Name string `json:"name" validate:"required"`
Type string `json:"type" validate:"required,oneof=SPIN RAFFLE MINIGAME"`
IsActive bool `json:"is_active"`
Metadata map[string]interface{} `json:"metadata"`
}
type UpdateGameRequest struct {
Name *string `json:"name,omitempty" validate:"omitempty,required"`
Type *string `json:"type,omitempty" validate:"omitempty,oneof=SPIN RAFFLE MINIGAME"`
IsActive *bool `json:"is_active,omitempty"`
Metadata map[string]interface{} `json:"metadata,omitempty"`
}
type GameResponse 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"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
type ListGamesRequest struct {
Page int `json:"page" form:"page" validate:"min=1"`
Limit int `json:"limit" form:"limit" validate:"min=1,max=100"`
Search string `json:"search" form:"search"`
Type string `json:"type" form:"type" validate:"omitempty,oneof=SPIN RAFFLE MINIGAME"`
IsActive *bool `json:"is_active" form:"is_active"`
SortBy string `json:"sort_by" form:"sort_by" validate:"omitempty,oneof=name type created_at updated_at"`
SortOrder string `json:"sort_order" form:"sort_order" validate:"omitempty,oneof=asc desc"`
}
type PaginatedGamesResponse struct {
Data []GameResponse `json:"data"`
TotalCount int `json:"total_count"`
Page int `json:"page"`
Limit int `json:"limit"`
TotalPages int `json:"total_pages"`
}

View File

@ -0,0 +1,58 @@
package contract
import (
"time"
"github.com/google/uuid"
)
type CreateGamePlayRequest struct {
GameID uuid.UUID `json:"game_id" validate:"required"`
CustomerID uuid.UUID `json:"customer_id" validate:"required"`
TokenUsed int `json:"token_used" validate:"min=0"`
RandomSeed *string `json:"random_seed,omitempty"`
}
type GamePlayResponse struct {
ID uuid.UUID `json:"id"`
GameID uuid.UUID `json:"game_id"`
CustomerID uuid.UUID `json:"customer_id"`
PrizeID *uuid.UUID `json:"prize_id,omitempty"`
TokenUsed int `json:"token_used"`
RandomSeed *string `json:"random_seed,omitempty"`
CreatedAt time.Time `json:"created_at"`
Game *GameResponse `json:"game,omitempty"`
Customer *CustomerResponse `json:"customer,omitempty"`
Prize *GamePrizeResponse `json:"prize,omitempty"`
}
type ListGamePlaysRequest struct {
Page int `json:"page" validate:"min=1"`
Limit int `json:"limit" validate:"min=1,max=100"`
Search string `json:"search"`
GameID *uuid.UUID `json:"game_id"`
CustomerID *uuid.UUID `json:"customer_id"`
PrizeID *uuid.UUID `json:"prize_id"`
SortBy string `json:"sort_by" validate:"omitempty,oneof=created_at token_used"`
SortOrder string `json:"sort_order" validate:"omitempty,oneof=asc desc"`
}
type PaginatedGamePlaysResponse struct {
Data []GamePlayResponse `json:"data"`
TotalCount int `json:"total_count"`
Page int `json:"page"`
Limit int `json:"limit"`
TotalPages int `json:"total_pages"`
}
type PlayGameRequest struct {
GameID uuid.UUID `json:"game_id" validate:"required"`
CustomerID uuid.UUID `json:"customer_id" validate:"required"`
TokenUsed int `json:"token_used" validate:"min=0"`
}
type PlayGameResponse struct {
GamePlay GamePlayResponse `json:"game_play"`
PrizeWon *GamePrizeResponse `json:"prize_won,omitempty"`
TokensRemaining int64 `json:"tokens_remaining"`
}

View File

@ -0,0 +1,61 @@
package contract
import (
"time"
"github.com/google/uuid"
)
type CreateGamePrizeRequest struct {
GameID uuid.UUID `json:"game_id" validate:"required"`
Name string `json:"name" validate:"required"`
Weight int `json:"weight" validate:"min=1"`
Stock int `json:"stock" validate:"min=0"`
MaxStock *int `json:"max_stock,omitempty"`
Threshold *int64 `json:"threshold,omitempty"`
FallbackPrizeID *uuid.UUID `json:"fallback_prize_id,omitempty"`
Metadata map[string]interface{} `json:"metadata"`
}
type UpdateGamePrizeRequest struct {
Name *string `json:"name,omitempty" validate:"omitempty,required"`
Weight *int `json:"weight,omitempty" validate:"omitempty,min=1"`
Stock *int `json:"stock,omitempty" validate:"omitempty,min=0"`
MaxStock *int `json:"max_stock,omitempty"`
Threshold *int64 `json:"threshold,omitempty"`
FallbackPrizeID *uuid.UUID `json:"fallback_prize_id,omitempty"`
Metadata map[string]interface{} `json:"metadata,omitempty"`
}
type GamePrizeResponse struct {
ID uuid.UUID `json:"id"`
GameID uuid.UUID `json:"game_id"`
Name string `json:"name"`
Weight int `json:"weight"`
Stock int `json:"stock"`
MaxStock *int `json:"max_stock,omitempty"`
Threshold *int64 `json:"threshold,omitempty"`
FallbackPrizeID *uuid.UUID `json:"fallback_prize_id,omitempty"`
Metadata map[string]interface{} `json:"metadata"`
Game *GameResponse `json:"game,omitempty"`
FallbackPrize *GamePrizeResponse `json:"fallback_prize,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
type ListGamePrizesRequest struct {
Page int `json:"page" form:"page" validate:"min=1"`
Limit int `json:"limit" form:"limit" validate:"min=1,max=100"`
Search string `json:"search" form:"search"`
GameID *uuid.UUID `json:"game_id" form:"game_id"`
SortBy string `json:"sort_by" form:"sort_by" validate:"omitempty,oneof=name weight stock created_at updated_at"`
SortOrder string `json:"sort_order" form:"sort_order" validate:"omitempty,oneof=asc desc"`
}
type PaginatedGamePrizesResponse struct {
Data []GamePrizeResponse `json:"data"`
TotalCount int `json:"total_count"`
Page int `json:"page"`
Limit int `json:"limit"`
TotalPages int `json:"total_pages"`
}

View File

@ -0,0 +1,59 @@
package contract
import (
"time"
"github.com/google/uuid"
)
type CreateOmsetTrackerRequest struct {
PeriodType string `json:"period_type" validate:"required,oneof=DAILY WEEKLY MONTHLY TOTAL"`
PeriodStart time.Time `json:"period_start" validate:"required"`
PeriodEnd time.Time `json:"period_end" validate:"required"`
Total int64 `json:"total" validate:"min=0"`
GameID *uuid.UUID `json:"game_id,omitempty"`
}
type UpdateOmsetTrackerRequest struct {
PeriodType *string `json:"period_type,omitempty" validate:"omitempty,oneof=DAILY WEEKLY MONTHLY TOTAL"`
PeriodStart *time.Time `json:"period_start,omitempty"`
PeriodEnd *time.Time `json:"period_end,omitempty"`
Total *int64 `json:"total,omitempty" validate:"omitempty,min=0"`
GameID *uuid.UUID `json:"game_id,omitempty"`
}
type AddOmsetRequest struct {
Amount int64 `json:"amount" validate:"required,min=1"`
}
type OmsetTrackerResponse struct {
ID uuid.UUID `json:"id"`
PeriodType string `json:"period_type"`
PeriodStart time.Time `json:"period_start"`
PeriodEnd time.Time `json:"period_end"`
Total int64 `json:"total"`
GameID *uuid.UUID `json:"game_id,omitempty"`
Game *GameResponse `json:"game,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
type ListOmsetTrackerRequest struct {
Page int `json:"page" validate:"min=1"`
Limit int `json:"limit" validate:"min=1,max=100"`
Search string `json:"search"`
PeriodType string `json:"period_type" validate:"omitempty,oneof=DAILY WEEKLY MONTHLY TOTAL"`
GameID *uuid.UUID `json:"game_id"`
From *time.Time `json:"from"`
To *time.Time `json:"to"`
SortBy string `json:"sort_by" validate:"omitempty,oneof=period_type period_start total created_at updated_at"`
SortOrder string `json:"sort_order" validate:"omitempty,oneof=asc desc"`
}
type PaginatedOmsetTrackerResponse struct {
Data []OmsetTrackerResponse `json:"data"`
TotalCount int `json:"total_count"`
Page int `json:"page"`
Limit int `json:"limit"`
TotalPages int `json:"total_pages"`
}

View File

@ -0,0 +1,44 @@
package contract
import (
"time"
"github.com/google/uuid"
)
type CreateTierRequest struct {
Name string `json:"name" validate:"required"`
MinPoints int64 `json:"min_points" validate:"min=0"`
Benefits map[string]interface{} `json:"benefits"`
}
type UpdateTierRequest struct {
Name *string `json:"name,omitempty" validate:"omitempty,required"`
MinPoints *int64 `json:"min_points,omitempty" validate:"omitempty,min=0"`
Benefits map[string]interface{} `json:"benefits,omitempty"`
}
type TierResponse struct {
ID uuid.UUID `json:"id"`
Name string `json:"name"`
MinPoints int64 `json:"min_points"`
Benefits map[string]interface{} `json:"benefits"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
type ListTiersRequest struct {
Page int `form:"page" validate:"min=1"`
Limit int `form:"limit" validate:"min=1,max=100"`
Search string `form:"search"`
SortBy string `form:"sort_by" validate:"omitempty,oneof=name min_points created_at updated_at"`
SortOrder string `form:"sort_order" validate:"omitempty,oneof=asc desc"`
}
type PaginatedTiersResponse struct {
Data []TierResponse `json:"data"`
TotalCount int `json:"total_count"`
Page int `json:"page"`
Limit int `json:"limit"`
TotalPages int `json:"total_pages"`
}

View File

@ -0,0 +1,29 @@
package entities
import (
"time"
"github.com/google/uuid"
"gorm.io/gorm"
)
type CustomerPoints struct {
ID uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid()" json:"id"`
CustomerID uuid.UUID `gorm:"type:uuid;not null;index" json:"customer_id" validate:"required"`
Balance int64 `gorm:"not null;default:0" json:"balance" validate:"min=0"`
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
Customer Customer `gorm:"foreignKey:CustomerID" json:"customer,omitempty"`
}
func (cp *CustomerPoints) BeforeCreate(tx *gorm.DB) error {
if cp.ID == uuid.Nil {
cp.ID = uuid.New()
}
return nil
}
func (CustomerPoints) TableName() string {
return "customer_points"
}

View File

@ -0,0 +1,38 @@
package entities
import (
"time"
"github.com/google/uuid"
"gorm.io/gorm"
)
type TokenType string
const (
TokenTypeSpin TokenType = "SPIN"
TokenTypeRaffle TokenType = "RAFFLE"
TokenTypeMinigame TokenType = "MINIGAME"
)
type CustomerTokens struct {
ID uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid()" json:"id"`
CustomerID uuid.UUID `gorm:"type:uuid;not null;index" json:"customer_id" validate:"required"`
TokenType TokenType `gorm:"type:varchar(50);not null" json:"token_type" validate:"required,oneof=SPIN RAFFLE MINIGAME"`
Balance int64 `gorm:"not null;default:0" json:"balance" validate:"min=0"`
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
Customer Customer `gorm:"foreignKey:CustomerID" json:"customer,omitempty"`
}
func (ct *CustomerTokens) BeforeCreate(tx *gorm.DB) error {
if ct.ID == uuid.Nil {
ct.ID = uuid.New()
}
return nil
}
func (CustomerTokens) TableName() string {
return "customer_tokens"
}

View File

@ -23,6 +23,14 @@ func GetAllEntities() []interface{} {
&PurchaseOrderItem{},
&PurchaseOrderAttachment{},
&IngredientUnitConverter{},
// Gamification entities
&CustomerPoints{},
&CustomerTokens{},
&Tier{},
&Game{},
&GamePrize{},
&GamePlay{},
&OmsetTracker{},
// Analytics entities are not database tables, they are query results
}
}

40
internal/entities/game.go Normal file
View File

@ -0,0 +1,40 @@
package entities
import (
"time"
"github.com/google/uuid"
"gorm.io/gorm"
)
type GameType string
const (
GameTypeSpin GameType = "SPIN"
GameTypeRaffle GameType = "RAFFLE"
GameTypeMinigame GameType = "MINIGAME"
)
type Game struct {
ID uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid()" json:"id"`
Name string `gorm:"type:varchar(255);not null" json:"name" validate:"required"`
Type GameType `gorm:"type:varchar(50);not null" json:"type" validate:"required,oneof=SPIN RAFFLE MINIGAME"`
IsActive bool `gorm:"default:true" json:"is_active"`
Metadata Metadata `gorm:"type:jsonb;default:'{}'" json:"metadata"`
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
Prizes []GamePrize `gorm:"foreignKey:GameID" json:"prizes,omitempty"`
Plays []GamePlay `gorm:"foreignKey:GameID" json:"plays,omitempty"`
}
func (g *Game) BeforeCreate(tx *gorm.DB) error {
if g.ID == uuid.Nil {
g.ID = uuid.New()
}
return nil
}
func (Game) TableName() string {
return "games"
}

View File

@ -0,0 +1,33 @@
package entities
import (
"time"
"github.com/google/uuid"
"gorm.io/gorm"
)
type GamePlay struct {
ID uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid()" json:"id"`
GameID uuid.UUID `gorm:"type:uuid;not null;index" json:"game_id" validate:"required"`
CustomerID uuid.UUID `gorm:"type:uuid;not null;index" json:"customer_id" validate:"required"`
PrizeID *uuid.UUID `gorm:"type:uuid" json:"prize_id,omitempty"`
TokenUsed int `gorm:"default:0" json:"token_used" validate:"min=0"`
RandomSeed *string `gorm:"type:varchar(255)" json:"random_seed,omitempty"`
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
Game Game `gorm:"foreignKey:GameID" json:"game,omitempty"`
Customer Customer `gorm:"foreignKey:CustomerID" json:"customer,omitempty"`
Prize *GamePrize `gorm:"foreignKey:PrizeID" json:"prize,omitempty"`
}
func (gp *GamePlay) BeforeCreate(tx *gorm.DB) error {
if gp.ID == uuid.Nil {
gp.ID = uuid.New()
}
return nil
}
func (GamePlay) TableName() string {
return "game_plays"
}

View File

@ -0,0 +1,37 @@
package entities
import (
"time"
"github.com/google/uuid"
"gorm.io/gorm"
)
type GamePrize struct {
ID uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid()" json:"id"`
GameID uuid.UUID `gorm:"type:uuid;not null;index" json:"game_id" validate:"required"`
Name string `gorm:"type:varchar(255);not null" json:"name" validate:"required"`
Weight int `gorm:"not null" json:"weight" validate:"min=1"`
Stock int `gorm:"default:0" json:"stock" validate:"min=0"`
MaxStock *int `gorm:"" json:"max_stock,omitempty"`
Threshold *int64 `gorm:"" json:"threshold,omitempty"`
FallbackPrizeID *uuid.UUID `gorm:"type:uuid" json:"fallback_prize_id,omitempty"`
Metadata Metadata `gorm:"type:jsonb;default:'{}'" json:"metadata"`
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
Game Game `gorm:"foreignKey:GameID" json:"game,omitempty"`
FallbackPrize *GamePrize `gorm:"foreignKey:FallbackPrizeID" json:"fallback_prize,omitempty"`
Plays []GamePlay `gorm:"foreignKey:PrizeID" json:"plays,omitempty"`
}
func (gp *GamePrize) BeforeCreate(tx *gorm.DB) error {
if gp.ID == uuid.Nil {
gp.ID = uuid.New()
}
return nil
}
func (GamePrize) TableName() string {
return "game_prizes"
}

View File

@ -0,0 +1,41 @@
package entities
import (
"time"
"github.com/google/uuid"
"gorm.io/gorm"
)
type PeriodType string
const (
PeriodTypeDaily PeriodType = "DAILY"
PeriodTypeWeekly PeriodType = "WEEKLY"
PeriodTypeMonthly PeriodType = "MONTHLY"
PeriodTypeTotal PeriodType = "TOTAL"
)
type OmsetTracker struct {
ID uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid()" json:"id"`
PeriodType PeriodType `gorm:"type:varchar(20);not null" json:"period_type" validate:"required,oneof=DAILY WEEKLY MONTHLY TOTAL"`
PeriodStart time.Time `gorm:"type:date;not null" json:"period_start" validate:"required"`
PeriodEnd time.Time `gorm:"type:date;not null" json:"period_end" validate:"required"`
Total int64 `gorm:"not null;default:0" json:"total" validate:"min=0"`
GameID *uuid.UUID `gorm:"type:uuid" json:"game_id,omitempty"`
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
Game *Game `gorm:"foreignKey:GameID" json:"game,omitempty"`
}
func (ot *OmsetTracker) BeforeCreate(tx *gorm.DB) error {
if ot.ID == uuid.Nil {
ot.ID = uuid.New()
}
return nil
}
func (OmsetTracker) TableName() string {
return "omset_tracker"
}

28
internal/entities/tier.go Normal file
View File

@ -0,0 +1,28 @@
package entities
import (
"time"
"github.com/google/uuid"
"gorm.io/gorm"
)
type Tier struct {
ID uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid()" json:"id"`
Name string `gorm:"type:varchar(100);not null;unique" json:"name" validate:"required"`
MinPoints int64 `gorm:"not null" json:"min_points" validate:"min=0"`
Benefits Metadata `gorm:"type:jsonb;default:'{}'" json:"benefits"`
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
}
func (t *Tier) BeforeCreate(tx *gorm.DB) error {
if t.ID == uuid.Nil {
t.ID = uuid.New()
}
return nil
}
func (Tier) TableName() string {
return "tiers"
}

View File

@ -0,0 +1,527 @@
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"
"apskel-pos-be/internal/validator"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
)
type GamificationHandler struct {
gamificationService service.GamificationService
gamificationValidator validator.GamificationValidator
}
func NewGamificationHandler(
gamificationService service.GamificationService,
gamificationValidator validator.GamificationValidator,
) *GamificationHandler {
return &GamificationHandler{
gamificationService: gamificationService,
gamificationValidator: gamificationValidator,
}
}
// Customer Points Handlers
func (h *GamificationHandler) CreateCustomerPoints(c *gin.Context) {
ctx := c.Request.Context()
var req contract.CreateCustomerPointsRequest
if err := c.ShouldBindJSON(&req); err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::CreateCustomerPoints -> request binding failed")
validationResponseError := contract.NewResponseError(constants.MissingFieldErrorCode, constants.RequestEntity, err.Error())
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "GamificationHandler::CreateCustomerPoints")
return
}
validationError, validationErrorCode := h.gamificationValidator.ValidateCreateCustomerPointsRequest(&req)
if validationError != nil {
logger.FromContext(c.Request.Context()).WithError(validationError).Error("GamificationHandler::CreateCustomerPoints -> request validation failed")
validationResponseError := contract.NewResponseError(validationErrorCode, constants.RequestEntity, validationError.Error())
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "GamificationHandler::CreateCustomerPoints")
return
}
response, err := h.gamificationService.CreateCustomerPoints(ctx, &req)
if err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::CreateCustomerPoints -> service call failed")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError(constants.InternalServerErrorCode, constants.CustomerPointsEntity, err.Error())}), "GamificationHandler::CreateCustomerPoints")
return
}
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "GamificationHandler::CreateCustomerPoints")
}
func (h *GamificationHandler) GetCustomerPoints(c *gin.Context) {
ctx := c.Request.Context()
idStr := c.Param("id")
id, err := uuid.Parse(idStr)
if err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::GetCustomerPoints -> invalid ID")
validationResponseError := contract.NewResponseError(constants.InvalidFieldErrorCode, constants.CustomerPointsEntity, "Invalid ID format")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "GamificationHandler::GetCustomerPoints")
return
}
response, err := h.gamificationService.GetCustomerPoints(ctx, id)
if err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::GetCustomerPoints -> service call failed")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError(constants.InternalServerErrorCode, constants.CustomerPointsEntity, err.Error())}), "GamificationHandler::GetCustomerPoints")
return
}
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "GamificationHandler::GetCustomerPoints")
}
func (h *GamificationHandler) GetCustomerPointsByCustomerID(c *gin.Context) {
ctx := c.Request.Context()
customerIDStr := c.Param("customer_id")
customerID, err := uuid.Parse(customerIDStr)
if err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::GetCustomerPointsByCustomerID -> invalid customer ID")
validationResponseError := contract.NewResponseError(constants.InvalidFieldErrorCode, constants.CustomerPointsEntity, "Invalid customer ID format")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "GamificationHandler::GetCustomerPointsByCustomerID")
return
}
response, err := h.gamificationService.GetCustomerPointsByCustomerID(ctx, customerID)
if err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::GetCustomerPointsByCustomerID -> service call failed")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError(constants.InternalServerErrorCode, constants.CustomerPointsEntity, err.Error())}), "GamificationHandler::GetCustomerPointsByCustomerID")
return
}
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "GamificationHandler::GetCustomerPointsByCustomerID")
}
func (h *GamificationHandler) ListCustomerPoints(c *gin.Context) {
ctx := c.Request.Context()
var req contract.ListCustomerPointsRequest
if err := c.ShouldBindQuery(&req); err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::ListCustomerPoints -> request binding failed")
validationResponseError := contract.NewResponseError(constants.MissingFieldErrorCode, constants.RequestEntity, err.Error())
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "GamificationHandler::ListCustomerPoints")
return
}
validationError, validationErrorCode := h.gamificationValidator.ValidateListCustomerPointsRequest(&req)
if validationError != nil {
logger.FromContext(c.Request.Context()).WithError(validationError).Error("GamificationHandler::ListCustomerPoints -> request validation failed")
validationResponseError := contract.NewResponseError(validationErrorCode, constants.RequestEntity, validationError.Error())
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "GamificationHandler::ListCustomerPoints")
return
}
response, err := h.gamificationService.ListCustomerPoints(ctx, &req)
if err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::ListCustomerPoints -> service call failed")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError(constants.InternalServerErrorCode, constants.CustomerPointsEntity, err.Error())}), "GamificationHandler::ListCustomerPoints")
return
}
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "GamificationHandler::ListCustomerPoints")
}
func (h *GamificationHandler) UpdateCustomerPoints(c *gin.Context) {
ctx := c.Request.Context()
idStr := c.Param("id")
id, err := uuid.Parse(idStr)
if err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::UpdateCustomerPoints -> invalid ID")
validationResponseError := contract.NewResponseError(constants.InvalidFieldErrorCode, constants.CustomerPointsEntity, "Invalid ID format")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "GamificationHandler::UpdateCustomerPoints")
return
}
var req contract.UpdateCustomerPointsRequest
if err := c.ShouldBindJSON(&req); err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::UpdateCustomerPoints -> request binding failed")
validationResponseError := contract.NewResponseError(constants.MissingFieldErrorCode, constants.RequestEntity, err.Error())
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "GamificationHandler::UpdateCustomerPoints")
return
}
validationError, validationErrorCode := h.gamificationValidator.ValidateUpdateCustomerPointsRequest(&req)
if validationError != nil {
logger.FromContext(c.Request.Context()).WithError(validationError).Error("GamificationHandler::UpdateCustomerPoints -> request validation failed")
validationResponseError := contract.NewResponseError(validationErrorCode, constants.RequestEntity, validationError.Error())
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "GamificationHandler::UpdateCustomerPoints")
return
}
response, err := h.gamificationService.UpdateCustomerPoints(ctx, id, &req)
if err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::UpdateCustomerPoints -> service call failed")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError(constants.InternalServerErrorCode, constants.CustomerPointsEntity, err.Error())}), "GamificationHandler::UpdateCustomerPoints")
return
}
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "GamificationHandler::UpdateCustomerPoints")
}
func (h *GamificationHandler) DeleteCustomerPoints(c *gin.Context) {
ctx := c.Request.Context()
idStr := c.Param("id")
id, err := uuid.Parse(idStr)
if err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::DeleteCustomerPoints -> invalid ID")
validationResponseError := contract.NewResponseError(constants.InvalidFieldErrorCode, constants.CustomerPointsEntity, "Invalid ID format")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "GamificationHandler::DeleteCustomerPoints")
return
}
err = h.gamificationService.DeleteCustomerPoints(ctx, id)
if err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::DeleteCustomerPoints -> service call failed")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError(constants.InternalServerErrorCode, constants.CustomerPointsEntity, err.Error())}), "GamificationHandler::DeleteCustomerPoints")
return
}
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(nil), "GamificationHandler::DeleteCustomerPoints")
}
func (h *GamificationHandler) AddCustomerPoints(c *gin.Context) {
ctx := c.Request.Context()
customerIDStr := c.Param("customer_id")
customerID, err := uuid.Parse(customerIDStr)
if err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::AddCustomerPoints -> invalid customer ID")
validationResponseError := contract.NewResponseError(constants.InvalidFieldErrorCode, constants.CustomerPointsEntity, "Invalid customer ID format")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "GamificationHandler::AddCustomerPoints")
return
}
var req contract.AddCustomerPointsRequest
if err := c.ShouldBindJSON(&req); err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::AddCustomerPoints -> request binding failed")
validationResponseError := contract.NewResponseError(constants.MissingFieldErrorCode, constants.RequestEntity, err.Error())
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "GamificationHandler::AddCustomerPoints")
return
}
validationError, validationErrorCode := h.gamificationValidator.ValidateAddCustomerPointsRequest(&req)
if validationError != nil {
logger.FromContext(c.Request.Context()).WithError(validationError).Error("GamificationHandler::AddCustomerPoints -> request validation failed")
validationResponseError := contract.NewResponseError(validationErrorCode, constants.RequestEntity, validationError.Error())
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "GamificationHandler::AddCustomerPoints")
return
}
response, err := h.gamificationService.AddCustomerPoints(ctx, customerID, &req)
if err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::AddCustomerPoints -> service call failed")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError(constants.InternalServerErrorCode, constants.CustomerPointsEntity, err.Error())}), "GamificationHandler::AddCustomerPoints")
return
}
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "GamificationHandler::AddCustomerPoints")
}
func (h *GamificationHandler) DeductCustomerPoints(c *gin.Context) {
ctx := c.Request.Context()
customerIDStr := c.Param("customer_id")
customerID, err := uuid.Parse(customerIDStr)
if err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::DeductCustomerPoints -> invalid customer ID")
validationResponseError := contract.NewResponseError(constants.InvalidFieldErrorCode, constants.CustomerPointsEntity, "Invalid customer ID format")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "GamificationHandler::DeductCustomerPoints")
return
}
var req contract.DeductCustomerPointsRequest
if err := c.ShouldBindJSON(&req); err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::DeductCustomerPoints -> request binding failed")
validationResponseError := contract.NewResponseError(constants.MissingFieldErrorCode, constants.RequestEntity, err.Error())
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "GamificationHandler::DeductCustomerPoints")
return
}
validationError, validationErrorCode := h.gamificationValidator.ValidateDeductCustomerPointsRequest(&req)
if validationError != nil {
logger.FromContext(c.Request.Context()).WithError(validationError).Error("GamificationHandler::DeductCustomerPoints -> request validation failed")
validationResponseError := contract.NewResponseError(validationErrorCode, constants.RequestEntity, validationError.Error())
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "GamificationHandler::DeductCustomerPoints")
return
}
response, err := h.gamificationService.DeductCustomerPoints(ctx, customerID, &req)
if err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::DeductCustomerPoints -> service call failed")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError(constants.InternalServerErrorCode, constants.CustomerPointsEntity, err.Error())}), "GamificationHandler::DeductCustomerPoints")
return
}
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "GamificationHandler::DeductCustomerPoints")
}
// Play Game Handler
func (h *GamificationHandler) PlayGame(c *gin.Context) {
ctx := c.Request.Context()
var req contract.PlayGameRequest
if err := c.ShouldBindJSON(&req); err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::PlayGame -> request binding failed")
validationResponseError := contract.NewResponseError(constants.MissingFieldErrorCode, constants.RequestEntity, err.Error())
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "GamificationHandler::PlayGame")
return
}
validationError, validationErrorCode := h.gamificationValidator.ValidatePlayGameRequest(&req)
if validationError != nil {
logger.FromContext(c.Request.Context()).WithError(validationError).Error("GamificationHandler::PlayGame -> request validation failed")
validationResponseError := contract.NewResponseError(validationErrorCode, constants.RequestEntity, validationError.Error())
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "GamificationHandler::PlayGame")
return
}
response, err := h.gamificationService.PlayGame(ctx, &req)
if err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::PlayGame -> service call failed")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError(constants.InternalServerErrorCode, constants.GameEntity, err.Error())}), "GamificationHandler::PlayGame")
return
}
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "GamificationHandler::PlayGame")
}
// Additional handler methods for other gamification features
func (h *GamificationHandler) CreateCustomerTokens(c *gin.Context) {
ctx := c.Request.Context()
var req contract.CreateCustomerTokensRequest
if err := c.ShouldBindJSON(&req); err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::CreateCustomerTokens -> request binding failed")
validationResponseError := contract.NewResponseError(constants.MissingFieldErrorCode, constants.RequestEntity, err.Error())
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "GamificationHandler::CreateCustomerTokens")
return
}
validationError, validationErrorCode := h.gamificationValidator.ValidateCreateCustomerTokensRequest(&req)
if validationError != nil {
logger.FromContext(c.Request.Context()).WithError(validationError).Error("GamificationHandler::CreateCustomerTokens -> request validation failed")
validationResponseError := contract.NewResponseError(validationErrorCode, constants.RequestEntity, validationError.Error())
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "GamificationHandler::CreateCustomerTokens")
return
}
response, err := h.gamificationService.CreateCustomerTokens(ctx, &req)
if err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::CreateCustomerTokens -> service call failed")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError(constants.InternalServerErrorCode, constants.CustomerTokensEntity, err.Error())}), "GamificationHandler::CreateCustomerTokens")
return
}
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "GamificationHandler::CreateCustomerTokens")
}
func (h *GamificationHandler) GetCustomerTokens(c *gin.Context) {
ctx := c.Request.Context()
idStr := c.Param("id")
id, err := uuid.Parse(idStr)
if err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::GetCustomerTokens -> invalid ID")
validationResponseError := contract.NewResponseError(constants.InvalidFieldErrorCode, constants.CustomerTokensEntity, "Invalid ID format")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "GamificationHandler::GetCustomerTokens")
return
}
response, err := h.gamificationService.GetCustomerTokens(ctx, id)
if err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::GetCustomerTokens -> service call failed")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError(constants.InternalServerErrorCode, constants.CustomerTokensEntity, err.Error())}), "GamificationHandler::GetCustomerTokens")
return
}
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "GamificationHandler::GetCustomerTokens")
}
func (h *GamificationHandler) GetCustomerTokensByCustomerIDAndType(c *gin.Context) {
ctx := c.Request.Context()
customerIDStr := c.Param("customer_id")
customerID, err := uuid.Parse(customerIDStr)
if err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::GetCustomerTokensByCustomerIDAndType -> invalid customer ID")
validationResponseError := contract.NewResponseError(constants.InvalidFieldErrorCode, constants.CustomerTokensEntity, "Invalid customer ID format")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "GamificationHandler::GetCustomerTokensByCustomerIDAndType")
return
}
tokenType := c.Param("token_type")
response, err := h.gamificationService.GetCustomerTokensByCustomerIDAndType(ctx, customerID, tokenType)
if err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::GetCustomerTokensByCustomerIDAndType -> service call failed")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError(constants.InternalServerErrorCode, constants.CustomerTokensEntity, err.Error())}), "GamificationHandler::GetCustomerTokensByCustomerIDAndType")
return
}
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "GamificationHandler::GetCustomerTokensByCustomerIDAndType")
}
func (h *GamificationHandler) ListCustomerTokens(c *gin.Context) {
ctx := c.Request.Context()
var req contract.ListCustomerTokensRequest
if err := c.ShouldBindQuery(&req); err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::ListCustomerTokens -> request binding failed")
validationResponseError := contract.NewResponseError(constants.MissingFieldErrorCode, constants.RequestEntity, err.Error())
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "GamificationHandler::ListCustomerTokens")
return
}
validationError, validationErrorCode := h.gamificationValidator.ValidateListCustomerTokensRequest(&req)
if validationError != nil {
logger.FromContext(c.Request.Context()).WithError(validationError).Error("GamificationHandler::ListCustomerTokens -> request validation failed")
validationResponseError := contract.NewResponseError(validationErrorCode, constants.RequestEntity, validationError.Error())
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "GamificationHandler::ListCustomerTokens")
return
}
response, err := h.gamificationService.ListCustomerTokens(ctx, &req)
if err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::ListCustomerTokens -> service call failed")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError(constants.InternalServerErrorCode, constants.CustomerTokensEntity, err.Error())}), "GamificationHandler::ListCustomerTokens")
return
}
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "GamificationHandler::ListCustomerTokens")
}
func (h *GamificationHandler) UpdateCustomerTokens(c *gin.Context) {
ctx := c.Request.Context()
idStr := c.Param("id")
id, err := uuid.Parse(idStr)
if err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::UpdateCustomerTokens -> invalid ID")
validationResponseError := contract.NewResponseError(constants.InvalidFieldErrorCode, constants.CustomerTokensEntity, "Invalid ID format")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "GamificationHandler::UpdateCustomerTokens")
return
}
var req contract.UpdateCustomerTokensRequest
if err := c.ShouldBindJSON(&req); err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::UpdateCustomerTokens -> request binding failed")
validationResponseError := contract.NewResponseError(constants.MissingFieldErrorCode, constants.RequestEntity, err.Error())
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "GamificationHandler::UpdateCustomerTokens")
return
}
validationError, validationErrorCode := h.gamificationValidator.ValidateUpdateCustomerTokensRequest(&req)
if validationError != nil {
logger.FromContext(c.Request.Context()).WithError(validationError).Error("GamificationHandler::UpdateCustomerTokens -> request validation failed")
validationResponseError := contract.NewResponseError(validationErrorCode, constants.RequestEntity, validationError.Error())
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "GamificationHandler::UpdateCustomerTokens")
return
}
response, err := h.gamificationService.UpdateCustomerTokens(ctx, id, &req)
if err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::UpdateCustomerTokens -> service call failed")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError(constants.InternalServerErrorCode, constants.CustomerTokensEntity, err.Error())}), "GamificationHandler::UpdateCustomerTokens")
return
}
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "GamificationHandler::UpdateCustomerTokens")
}
func (h *GamificationHandler) DeleteCustomerTokens(c *gin.Context) {
ctx := c.Request.Context()
idStr := c.Param("id")
id, err := uuid.Parse(idStr)
if err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::DeleteCustomerTokens -> invalid ID")
validationResponseError := contract.NewResponseError(constants.InvalidFieldErrorCode, constants.CustomerTokensEntity, "Invalid ID format")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "GamificationHandler::DeleteCustomerTokens")
return
}
err = h.gamificationService.DeleteCustomerTokens(ctx, id)
if err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::DeleteCustomerTokens -> service call failed")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError(constants.InternalServerErrorCode, constants.CustomerTokensEntity, err.Error())}), "GamificationHandler::DeleteCustomerTokens")
return
}
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(nil), "GamificationHandler::DeleteCustomerTokens")
}
func (h *GamificationHandler) AddCustomerTokens(c *gin.Context) {
ctx := c.Request.Context()
customerIDStr := c.Param("customer_id")
customerID, err := uuid.Parse(customerIDStr)
if err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::AddCustomerTokens -> invalid customer ID")
validationResponseError := contract.NewResponseError(constants.InvalidFieldErrorCode, constants.CustomerTokensEntity, "Invalid customer ID format")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "GamificationHandler::AddCustomerTokens")
return
}
tokenType := c.Param("token_type")
var req contract.AddCustomerTokensRequest
if err := c.ShouldBindJSON(&req); err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::AddCustomerTokens -> request binding failed")
validationResponseError := contract.NewResponseError(constants.MissingFieldErrorCode, constants.RequestEntity, err.Error())
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "GamificationHandler::AddCustomerTokens")
return
}
validationError, validationErrorCode := h.gamificationValidator.ValidateAddCustomerTokensRequest(&req)
if validationError != nil {
logger.FromContext(c.Request.Context()).WithError(validationError).Error("GamificationHandler::AddCustomerTokens -> request validation failed")
validationResponseError := contract.NewResponseError(validationErrorCode, constants.RequestEntity, validationError.Error())
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "GamificationHandler::AddCustomerTokens")
return
}
response, err := h.gamificationService.AddCustomerTokens(ctx, customerID, tokenType, &req)
if err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::AddCustomerTokens -> service call failed")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError(constants.InternalServerErrorCode, constants.CustomerTokensEntity, err.Error())}), "GamificationHandler::AddCustomerTokens")
return
}
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "GamificationHandler::AddCustomerTokens")
}
func (h *GamificationHandler) DeductCustomerTokens(c *gin.Context) {
ctx := c.Request.Context()
customerIDStr := c.Param("customer_id")
customerID, err := uuid.Parse(customerIDStr)
if err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::DeductCustomerTokens -> invalid customer ID")
validationResponseError := contract.NewResponseError(constants.InvalidFieldErrorCode, constants.CustomerTokensEntity, "Invalid customer ID format")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "GamificationHandler::DeductCustomerTokens")
return
}
tokenType := c.Param("token_type")
var req contract.DeductCustomerTokensRequest
if err := c.ShouldBindJSON(&req); err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::DeductCustomerTokens -> request binding failed")
validationResponseError := contract.NewResponseError(constants.MissingFieldErrorCode, constants.RequestEntity, err.Error())
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "GamificationHandler::DeductCustomerTokens")
return
}
validationError, validationErrorCode := h.gamificationValidator.ValidateDeductCustomerTokensRequest(&req)
if validationError != nil {
logger.FromContext(c.Request.Context()).WithError(validationError).Error("GamificationHandler::DeductCustomerTokens -> request validation failed")
validationResponseError := contract.NewResponseError(validationErrorCode, constants.RequestEntity, validationError.Error())
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "GamificationHandler::DeductCustomerTokens")
return
}
response, err := h.gamificationService.DeductCustomerTokens(ctx, customerID, tokenType, &req)
if err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::DeductCustomerTokens -> service call failed")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError(constants.InternalServerErrorCode, constants.CustomerTokensEntity, err.Error())}), "GamificationHandler::DeductCustomerTokens")
return
}
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "GamificationHandler::DeductCustomerTokens")
}

View File

@ -0,0 +1,709 @@
package handler
import (
"apskel-pos-be/internal/constants"
"apskel-pos-be/internal/contract"
"apskel-pos-be/internal/logger"
"apskel-pos-be/internal/util"
"strconv"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
)
// Tier Handlers
func (h *GamificationHandler) CreateTier(c *gin.Context) {
ctx := c.Request.Context()
var req contract.CreateTierRequest
if err := c.ShouldBindJSON(&req); err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::CreateTier -> request binding failed")
validationResponseError := contract.NewResponseError(constants.MissingFieldErrorCode, constants.RequestEntity, err.Error())
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "GamificationHandler::CreateTier")
return
}
validationError, validationErrorCode := h.gamificationValidator.ValidateCreateTierRequest(&req)
if validationError != nil {
logger.FromContext(c.Request.Context()).WithError(validationError).Error("GamificationHandler::CreateTier -> request validation failed")
validationResponseError := contract.NewResponseError(validationErrorCode, constants.RequestEntity, validationError.Error())
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "GamificationHandler::CreateTier")
return
}
response, err := h.gamificationService.CreateTier(ctx, &req)
if err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::CreateTier -> service call failed")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError(constants.InternalServerErrorCode, constants.TierEntity, err.Error())}), "GamificationHandler::CreateTier")
return
}
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "GamificationHandler::CreateTier")
}
func (h *GamificationHandler) GetTier(c *gin.Context) {
ctx := c.Request.Context()
idStr := c.Param("id")
id, err := uuid.Parse(idStr)
if err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::GetTier -> invalid ID")
validationResponseError := contract.NewResponseError(constants.InvalidFieldErrorCode, constants.TierEntity, "Invalid ID format")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "GamificationHandler::GetTier")
return
}
response, err := h.gamificationService.GetTier(ctx, id)
if err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::GetTier -> service call failed")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError(constants.InternalServerErrorCode, constants.TierEntity, err.Error())}), "GamificationHandler::GetTier")
return
}
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "GamificationHandler::GetTier")
}
func (h *GamificationHandler) ListTiers(c *gin.Context) {
ctx := c.Request.Context()
var req contract.ListTiersRequest
if err := c.ShouldBindQuery(&req); err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::ListTiers -> request binding failed")
validationResponseError := contract.NewResponseError(constants.MissingFieldErrorCode, constants.RequestEntity, err.Error())
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "GamificationHandler::ListTiers")
return
}
validationError, validationErrorCode := h.gamificationValidator.ValidateListTiersRequest(&req)
if validationError != nil {
logger.FromContext(c.Request.Context()).WithError(validationError).Error("GamificationHandler::ListTiers -> request validation failed")
validationResponseError := contract.NewResponseError(validationErrorCode, constants.RequestEntity, validationError.Error())
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "GamificationHandler::ListTiers")
return
}
response, err := h.gamificationService.ListTiers(ctx, &req)
if err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::ListTiers -> service call failed")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError(constants.InternalServerErrorCode, constants.TierEntity, err.Error())}), "GamificationHandler::ListTiers")
return
}
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "GamificationHandler::ListTiers")
}
func (h *GamificationHandler) UpdateTier(c *gin.Context) {
ctx := c.Request.Context()
idStr := c.Param("id")
id, err := uuid.Parse(idStr)
if err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::UpdateTier -> invalid ID")
validationResponseError := contract.NewResponseError(constants.InvalidFieldErrorCode, constants.TierEntity, "Invalid ID format")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "GamificationHandler::UpdateTier")
return
}
var req contract.UpdateTierRequest
if err := c.ShouldBindJSON(&req); err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::UpdateTier -> request binding failed")
validationResponseError := contract.NewResponseError(constants.MissingFieldErrorCode, constants.RequestEntity, err.Error())
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "GamificationHandler::UpdateTier")
return
}
validationError, validationErrorCode := h.gamificationValidator.ValidateUpdateTierRequest(&req)
if validationError != nil {
logger.FromContext(c.Request.Context()).WithError(validationError).Error("GamificationHandler::UpdateTier -> request validation failed")
validationResponseError := contract.NewResponseError(validationErrorCode, constants.RequestEntity, validationError.Error())
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "GamificationHandler::UpdateTier")
return
}
response, err := h.gamificationService.UpdateTier(ctx, id, &req)
if err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::UpdateTier -> service call failed")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError(constants.InternalServerErrorCode, constants.TierEntity, err.Error())}), "GamificationHandler::UpdateTier")
return
}
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "GamificationHandler::UpdateTier")
}
func (h *GamificationHandler) DeleteTier(c *gin.Context) {
ctx := c.Request.Context()
idStr := c.Param("id")
id, err := uuid.Parse(idStr)
if err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::DeleteTier -> invalid ID")
validationResponseError := contract.NewResponseError(constants.InvalidFieldErrorCode, constants.TierEntity, "Invalid ID format")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "GamificationHandler::DeleteTier")
return
}
err = h.gamificationService.DeleteTier(ctx, id)
if err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::DeleteTier -> service call failed")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError(constants.InternalServerErrorCode, constants.TierEntity, err.Error())}), "GamificationHandler::DeleteTier")
return
}
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(nil), "GamificationHandler::DeleteTier")
}
func (h *GamificationHandler) GetTierByPoints(c *gin.Context) {
ctx := c.Request.Context()
pointsStr := c.Param("points")
points, err := strconv.ParseInt(pointsStr, 10, 64)
if err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::GetTierByPoints -> invalid points")
validationResponseError := contract.NewResponseError(constants.InvalidFieldErrorCode, constants.TierEntity, "Invalid points format")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "GamificationHandler::GetTierByPoints")
return
}
response, err := h.gamificationService.GetTierByPoints(ctx, points)
if err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::GetTierByPoints -> service call failed")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError(constants.InternalServerErrorCode, constants.TierEntity, err.Error())}), "GamificationHandler::GetTierByPoints")
return
}
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "GamificationHandler::GetTierByPoints")
}
// Game Handlers
func (h *GamificationHandler) CreateGame(c *gin.Context) {
ctx := c.Request.Context()
var req contract.CreateGameRequest
if err := c.ShouldBindJSON(&req); err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::CreateGame -> request binding failed")
validationResponseError := contract.NewResponseError(constants.MissingFieldErrorCode, constants.RequestEntity, err.Error())
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "GamificationHandler::CreateGame")
return
}
validationError, validationErrorCode := h.gamificationValidator.ValidateCreateGameRequest(&req)
if validationError != nil {
logger.FromContext(c.Request.Context()).WithError(validationError).Error("GamificationHandler::CreateGame -> request validation failed")
validationResponseError := contract.NewResponseError(validationErrorCode, constants.RequestEntity, validationError.Error())
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "GamificationHandler::CreateGame")
return
}
response, err := h.gamificationService.CreateGame(ctx, &req)
if err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::CreateGame -> service call failed")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError(constants.InternalServerErrorCode, constants.GameEntity, err.Error())}), "GamificationHandler::CreateGame")
return
}
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "GamificationHandler::CreateGame")
}
func (h *GamificationHandler) GetGame(c *gin.Context) {
ctx := c.Request.Context()
idStr := c.Param("id")
id, err := uuid.Parse(idStr)
if err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::GetGame -> invalid ID")
validationResponseError := contract.NewResponseError(constants.InvalidFieldErrorCode, constants.GameEntity, "Invalid ID format")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "GamificationHandler::GetGame")
return
}
response, err := h.gamificationService.GetGame(ctx, id)
if err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::GetGame -> service call failed")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError(constants.InternalServerErrorCode, constants.GameEntity, err.Error())}), "GamificationHandler::GetGame")
return
}
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "GamificationHandler::GetGame")
}
func (h *GamificationHandler) ListGames(c *gin.Context) {
ctx := c.Request.Context()
var req contract.ListGamesRequest
if err := c.ShouldBindQuery(&req); err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::ListGames -> request binding failed")
validationResponseError := contract.NewResponseError(constants.MissingFieldErrorCode, constants.RequestEntity, err.Error())
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "GamificationHandler::ListGames")
return
}
validationError, validationErrorCode := h.gamificationValidator.ValidateListGamesRequest(&req)
if validationError != nil {
logger.FromContext(c.Request.Context()).WithError(validationError).Error("GamificationHandler::ListGames -> request validation failed")
validationResponseError := contract.NewResponseError(validationErrorCode, constants.RequestEntity, validationError.Error())
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "GamificationHandler::ListGames")
return
}
response, err := h.gamificationService.ListGames(ctx, &req)
if err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::ListGames -> service call failed")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError(constants.InternalServerErrorCode, constants.GameEntity, err.Error())}), "GamificationHandler::ListGames")
return
}
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "GamificationHandler::ListGames")
}
func (h *GamificationHandler) GetActiveGames(c *gin.Context) {
ctx := c.Request.Context()
response, err := h.gamificationService.GetActiveGames(ctx)
if err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::GetActiveGames -> service call failed")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError(constants.InternalServerErrorCode, constants.GameEntity, err.Error())}), "GamificationHandler::GetActiveGames")
return
}
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "GamificationHandler::GetActiveGames")
}
func (h *GamificationHandler) UpdateGame(c *gin.Context) {
ctx := c.Request.Context()
idStr := c.Param("id")
id, err := uuid.Parse(idStr)
if err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::UpdateGame -> invalid ID")
validationResponseError := contract.NewResponseError(constants.InvalidFieldErrorCode, constants.GameEntity, "Invalid ID format")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "GamificationHandler::UpdateGame")
return
}
var req contract.UpdateGameRequest
if err := c.ShouldBindJSON(&req); err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::UpdateGame -> request binding failed")
validationResponseError := contract.NewResponseError(constants.MissingFieldErrorCode, constants.RequestEntity, err.Error())
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "GamificationHandler::UpdateGame")
return
}
validationError, validationErrorCode := h.gamificationValidator.ValidateUpdateGameRequest(&req)
if validationError != nil {
logger.FromContext(c.Request.Context()).WithError(validationError).Error("GamificationHandler::UpdateGame -> request validation failed")
validationResponseError := contract.NewResponseError(validationErrorCode, constants.RequestEntity, validationError.Error())
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "GamificationHandler::UpdateGame")
return
}
response, err := h.gamificationService.UpdateGame(ctx, id, &req)
if err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::UpdateGame -> service call failed")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError(constants.InternalServerErrorCode, constants.GameEntity, err.Error())}), "GamificationHandler::UpdateGame")
return
}
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "GamificationHandler::UpdateGame")
}
func (h *GamificationHandler) DeleteGame(c *gin.Context) {
ctx := c.Request.Context()
idStr := c.Param("id")
id, err := uuid.Parse(idStr)
if err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::DeleteGame -> invalid ID")
validationResponseError := contract.NewResponseError(constants.InvalidFieldErrorCode, constants.GameEntity, "Invalid ID format")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "GamificationHandler::DeleteGame")
return
}
err = h.gamificationService.DeleteGame(ctx, id)
if err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::DeleteGame -> service call failed")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError(constants.InternalServerErrorCode, constants.GameEntity, err.Error())}), "GamificationHandler::DeleteGame")
return
}
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(nil), "GamificationHandler::DeleteGame")
}
// Game Prize Handlers
func (h *GamificationHandler) CreateGamePrize(c *gin.Context) {
ctx := c.Request.Context()
var req contract.CreateGamePrizeRequest
if err := c.ShouldBindJSON(&req); err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::CreateGamePrize -> request binding failed")
validationResponseError := contract.NewResponseError(constants.MissingFieldErrorCode, constants.RequestEntity, err.Error())
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "GamificationHandler::CreateGamePrize")
return
}
validationError, validationErrorCode := h.gamificationValidator.ValidateCreateGamePrizeRequest(&req)
if validationError != nil {
logger.FromContext(c.Request.Context()).WithError(validationError).Error("GamificationHandler::CreateGamePrize -> request validation failed")
validationResponseError := contract.NewResponseError(validationErrorCode, constants.RequestEntity, validationError.Error())
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "GamificationHandler::CreateGamePrize")
return
}
response, err := h.gamificationService.CreateGamePrize(ctx, &req)
if err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::CreateGamePrize -> service call failed")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError(constants.InternalServerErrorCode, constants.GamePrizeEntity, err.Error())}), "GamificationHandler::CreateGamePrize")
return
}
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "GamificationHandler::CreateGamePrize")
}
func (h *GamificationHandler) GetGamePrize(c *gin.Context) {
ctx := c.Request.Context()
idStr := c.Param("id")
id, err := uuid.Parse(idStr)
if err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::GetGamePrize -> invalid ID")
validationResponseError := contract.NewResponseError(constants.InvalidFieldErrorCode, constants.GamePrizeEntity, "Invalid ID format")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "GamificationHandler::GetGamePrize")
return
}
response, err := h.gamificationService.GetGamePrize(ctx, id)
if err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::GetGamePrize -> service call failed")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError(constants.InternalServerErrorCode, constants.GamePrizeEntity, err.Error())}), "GamificationHandler::GetGamePrize")
return
}
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "GamificationHandler::GetGamePrize")
}
func (h *GamificationHandler) ListGamePrizes(c *gin.Context) {
ctx := c.Request.Context()
var req contract.ListGamePrizesRequest
if err := c.ShouldBindQuery(&req); err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::ListGamePrizes -> request binding failed")
validationResponseError := contract.NewResponseError(constants.MissingFieldErrorCode, constants.RequestEntity, err.Error())
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "GamificationHandler::ListGamePrizes")
return
}
validationError, validationErrorCode := h.gamificationValidator.ValidateListGamePrizesRequest(&req)
if validationError != nil {
logger.FromContext(c.Request.Context()).WithError(validationError).Error("GamificationHandler::ListGamePrizes -> request validation failed")
validationResponseError := contract.NewResponseError(validationErrorCode, constants.RequestEntity, validationError.Error())
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "GamificationHandler::ListGamePrizes")
return
}
response, err := h.gamificationService.ListGamePrizes(ctx, &req)
if err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::ListGamePrizes -> service call failed")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError(constants.InternalServerErrorCode, constants.GamePrizeEntity, err.Error())}), "GamificationHandler::ListGamePrizes")
return
}
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "GamificationHandler::ListGamePrizes")
}
func (h *GamificationHandler) UpdateGamePrize(c *gin.Context) {
ctx := c.Request.Context()
idStr := c.Param("id")
id, err := uuid.Parse(idStr)
if err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::UpdateGamePrize -> invalid ID")
validationResponseError := contract.NewResponseError(constants.InvalidFieldErrorCode, constants.GamePrizeEntity, "Invalid ID format")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "GamificationHandler::UpdateGamePrize")
return
}
var req contract.UpdateGamePrizeRequest
if err := c.ShouldBindJSON(&req); err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::UpdateGamePrize -> request binding failed")
validationResponseError := contract.NewResponseError(constants.MissingFieldErrorCode, constants.RequestEntity, err.Error())
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "GamificationHandler::UpdateGamePrize")
return
}
validationError, validationErrorCode := h.gamificationValidator.ValidateUpdateGamePrizeRequest(&req)
if validationError != nil {
logger.FromContext(c.Request.Context()).WithError(validationError).Error("GamificationHandler::UpdateGamePrize -> request validation failed")
validationResponseError := contract.NewResponseError(validationErrorCode, constants.RequestEntity, validationError.Error())
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "GamificationHandler::UpdateGamePrize")
return
}
response, err := h.gamificationService.UpdateGamePrize(ctx, id, &req)
if err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::UpdateGamePrize -> service call failed")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError(constants.InternalServerErrorCode, constants.GamePrizeEntity, err.Error())}), "GamificationHandler::UpdateGamePrize")
return
}
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "GamificationHandler::UpdateGamePrize")
}
func (h *GamificationHandler) DeleteGamePrize(c *gin.Context) {
ctx := c.Request.Context()
idStr := c.Param("id")
id, err := uuid.Parse(idStr)
if err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::DeleteGamePrize -> invalid ID")
validationResponseError := contract.NewResponseError(constants.InvalidFieldErrorCode, constants.GamePrizeEntity, "Invalid ID format")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "GamificationHandler::DeleteGamePrize")
return
}
err = h.gamificationService.DeleteGamePrize(ctx, id)
if err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::DeleteGamePrize -> service call failed")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError(constants.InternalServerErrorCode, constants.GamePrizeEntity, err.Error())}), "GamificationHandler::DeleteGamePrize")
return
}
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(nil), "GamificationHandler::DeleteGamePrize")
}
func (h *GamificationHandler) GetGamePrizesByGameID(c *gin.Context) {
ctx := c.Request.Context()
gameIDStr := c.Param("game_id")
gameID, err := uuid.Parse(gameIDStr)
if err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::GetGamePrizesByGameID -> invalid game ID")
validationResponseError := contract.NewResponseError(constants.InvalidFieldErrorCode, constants.GamePrizeEntity, "Invalid game ID format")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "GamificationHandler::GetGamePrizesByGameID")
return
}
response, err := h.gamificationService.GetGamePrizesByGameID(ctx, gameID)
if err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::GetGamePrizesByGameID -> service call failed")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError(constants.InternalServerErrorCode, constants.GamePrizeEntity, err.Error())}), "GamificationHandler::GetGamePrizesByGameID")
return
}
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "GamificationHandler::GetGamePrizesByGameID")
}
func (h *GamificationHandler) GetAvailablePrizes(c *gin.Context) {
ctx := c.Request.Context()
gameIDStr := c.Param("game_id")
gameID, err := uuid.Parse(gameIDStr)
if err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::GetAvailablePrizes -> invalid game ID")
validationResponseError := contract.NewResponseError(constants.InvalidFieldErrorCode, constants.GamePrizeEntity, "Invalid game ID format")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "GamificationHandler::GetAvailablePrizes")
return
}
response, err := h.gamificationService.GetAvailablePrizes(ctx, gameID)
if err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::GetAvailablePrizes -> service call failed")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError(constants.InternalServerErrorCode, constants.GamePrizeEntity, err.Error())}), "GamificationHandler::GetAvailablePrizes")
return
}
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "GamificationHandler::GetAvailablePrizes")
}
// Game Play Handlers
func (h *GamificationHandler) CreateGamePlay(c *gin.Context) {
ctx := c.Request.Context()
var req contract.CreateGamePlayRequest
if err := c.ShouldBindJSON(&req); err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::CreateGamePlay -> request binding failed")
validationResponseError := contract.NewResponseError(constants.MissingFieldErrorCode, constants.RequestEntity, err.Error())
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "GamificationHandler::CreateGamePlay")
return
}
validationError, validationErrorCode := h.gamificationValidator.ValidateCreateGamePlayRequest(&req)
if validationError != nil {
logger.FromContext(c.Request.Context()).WithError(validationError).Error("GamificationHandler::CreateGamePlay -> request validation failed")
validationResponseError := contract.NewResponseError(validationErrorCode, constants.RequestEntity, validationError.Error())
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "GamificationHandler::CreateGamePlay")
return
}
response, err := h.gamificationService.CreateGamePlay(ctx, &req)
if err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::CreateGamePlay -> service call failed")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError(constants.InternalServerErrorCode, constants.GamePlayEntity, err.Error())}), "GamificationHandler::CreateGamePlay")
return
}
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "GamificationHandler::CreateGamePlay")
}
func (h *GamificationHandler) GetGamePlay(c *gin.Context) {
ctx := c.Request.Context()
idStr := c.Param("id")
id, err := uuid.Parse(idStr)
if err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::GetGamePlay -> invalid ID")
validationResponseError := contract.NewResponseError(constants.InvalidFieldErrorCode, constants.GamePlayEntity, "Invalid ID format")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "GamificationHandler::GetGamePlay")
return
}
response, err := h.gamificationService.GetGamePlay(ctx, id)
if err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::GetGamePlay -> service call failed")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError(constants.InternalServerErrorCode, constants.GamePlayEntity, err.Error())}), "GamificationHandler::GetGamePlay")
return
}
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "GamificationHandler::GetGamePlay")
}
func (h *GamificationHandler) ListGamePlays(c *gin.Context) {
ctx := c.Request.Context()
var req contract.ListGamePlaysRequest
if err := c.ShouldBindQuery(&req); err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::ListGamePlays -> request binding failed")
validationResponseError := contract.NewResponseError(constants.MissingFieldErrorCode, constants.RequestEntity, err.Error())
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "GamificationHandler::ListGamePlays")
return
}
validationError, validationErrorCode := h.gamificationValidator.ValidateListGamePlaysRequest(&req)
if validationError != nil {
logger.FromContext(c.Request.Context()).WithError(validationError).Error("GamificationHandler::ListGamePlays -> request validation failed")
validationResponseError := contract.NewResponseError(validationErrorCode, constants.RequestEntity, validationError.Error())
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "GamificationHandler::ListGamePlays")
return
}
response, err := h.gamificationService.ListGamePlays(ctx, &req)
if err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::ListGamePlays -> service call failed")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError(constants.InternalServerErrorCode, constants.GamePlayEntity, err.Error())}), "GamificationHandler::ListGamePlays")
return
}
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "GamificationHandler::ListGamePlays")
}
// Omset Tracker Handlers
func (h *GamificationHandler) CreateOmsetTracker(c *gin.Context) {
ctx := c.Request.Context()
var req contract.CreateOmsetTrackerRequest
if err := c.ShouldBindJSON(&req); err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::CreateOmsetTracker -> request binding failed")
validationResponseError := contract.NewResponseError(constants.MissingFieldErrorCode, constants.RequestEntity, err.Error())
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "GamificationHandler::CreateOmsetTracker")
return
}
validationError, validationErrorCode := h.gamificationValidator.ValidateCreateOmsetTrackerRequest(&req)
if validationError != nil {
logger.FromContext(c.Request.Context()).WithError(validationError).Error("GamificationHandler::CreateOmsetTracker -> request validation failed")
validationResponseError := contract.NewResponseError(validationErrorCode, constants.RequestEntity, validationError.Error())
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "GamificationHandler::CreateOmsetTracker")
return
}
response, err := h.gamificationService.CreateOmsetTracker(ctx, &req)
if err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::CreateOmsetTracker -> service call failed")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError(constants.InternalServerErrorCode, constants.OmsetTrackerEntity, err.Error())}), "GamificationHandler::CreateOmsetTracker")
return
}
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "GamificationHandler::CreateOmsetTracker")
}
func (h *GamificationHandler) GetOmsetTracker(c *gin.Context) {
ctx := c.Request.Context()
idStr := c.Param("id")
id, err := uuid.Parse(idStr)
if err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::GetOmsetTracker -> invalid ID")
validationResponseError := contract.NewResponseError(constants.InvalidFieldErrorCode, constants.OmsetTrackerEntity, "Invalid ID format")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "GamificationHandler::GetOmsetTracker")
return
}
response, err := h.gamificationService.GetOmsetTracker(ctx, id)
if err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::GetOmsetTracker -> service call failed")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError(constants.InternalServerErrorCode, constants.OmsetTrackerEntity, err.Error())}), "GamificationHandler::GetOmsetTracker")
return
}
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "GamificationHandler::GetOmsetTracker")
}
func (h *GamificationHandler) ListOmsetTrackers(c *gin.Context) {
ctx := c.Request.Context()
var req contract.ListOmsetTrackerRequest
if err := c.ShouldBindQuery(&req); err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::ListOmsetTrackers -> request binding failed")
validationResponseError := contract.NewResponseError(constants.MissingFieldErrorCode, constants.RequestEntity, err.Error())
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "GamificationHandler::ListOmsetTrackers")
return
}
validationError, validationErrorCode := h.gamificationValidator.ValidateListOmsetTrackerRequest(&req)
if validationError != nil {
logger.FromContext(c.Request.Context()).WithError(validationError).Error("GamificationHandler::ListOmsetTrackers -> request validation failed")
validationResponseError := contract.NewResponseError(validationErrorCode, constants.RequestEntity, validationError.Error())
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "GamificationHandler::ListOmsetTrackers")
return
}
response, err := h.gamificationService.ListOmsetTrackers(ctx, &req)
if err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::ListOmsetTrackers -> service call failed")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError(constants.InternalServerErrorCode, constants.OmsetTrackerEntity, err.Error())}), "GamificationHandler::ListOmsetTrackers")
return
}
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "GamificationHandler::ListOmsetTrackers")
}
func (h *GamificationHandler) UpdateOmsetTracker(c *gin.Context) {
ctx := c.Request.Context()
idStr := c.Param("id")
id, err := uuid.Parse(idStr)
if err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::UpdateOmsetTracker -> invalid ID")
validationResponseError := contract.NewResponseError(constants.InvalidFieldErrorCode, constants.OmsetTrackerEntity, "Invalid ID format")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "GamificationHandler::UpdateOmsetTracker")
return
}
var req contract.UpdateOmsetTrackerRequest
if err := c.ShouldBindJSON(&req); err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::UpdateOmsetTracker -> request binding failed")
validationResponseError := contract.NewResponseError(constants.MissingFieldErrorCode, constants.RequestEntity, err.Error())
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "GamificationHandler::UpdateOmsetTracker")
return
}
validationError, validationErrorCode := h.gamificationValidator.ValidateUpdateOmsetTrackerRequest(&req)
if validationError != nil {
logger.FromContext(c.Request.Context()).WithError(validationError).Error("GamificationHandler::UpdateOmsetTracker -> request validation failed")
validationResponseError := contract.NewResponseError(validationErrorCode, constants.RequestEntity, validationError.Error())
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "GamificationHandler::UpdateOmsetTracker")
return
}
response, err := h.gamificationService.UpdateOmsetTracker(ctx, id, &req)
if err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::UpdateOmsetTracker -> service call failed")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError(constants.InternalServerErrorCode, constants.OmsetTrackerEntity, err.Error())}), "GamificationHandler::UpdateOmsetTracker")
return
}
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "GamificationHandler::UpdateOmsetTracker")
}
func (h *GamificationHandler) DeleteOmsetTracker(c *gin.Context) {
ctx := c.Request.Context()
idStr := c.Param("id")
id, err := uuid.Parse(idStr)
if err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::DeleteOmsetTracker -> invalid ID")
validationResponseError := contract.NewResponseError(constants.InvalidFieldErrorCode, constants.OmsetTrackerEntity, "Invalid ID format")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "GamificationHandler::DeleteOmsetTracker")
return
}
err = h.gamificationService.DeleteOmsetTracker(ctx, id)
if err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("GamificationHandler::DeleteOmsetTracker -> service call failed")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError(constants.InternalServerErrorCode, constants.OmsetTrackerEntity, err.Error())}), "GamificationHandler::DeleteOmsetTracker")
return
}
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(nil), "GamificationHandler::DeleteOmsetTracker")
}

View File

@ -0,0 +1,46 @@
package mappers
import (
"apskel-pos-be/internal/entities"
"apskel-pos-be/internal/models"
)
// ToCustomerPointsResponse converts a customer points entity to a customer points response
func ToCustomerPointsResponse(customerPoints *entities.CustomerPoints) *models.CustomerPointsResponse {
if customerPoints == nil {
return nil
}
return &models.CustomerPointsResponse{
ID: customerPoints.ID,
CustomerID: customerPoints.CustomerID,
Balance: customerPoints.Balance,
Customer: ToCustomerResponse(&customerPoints.Customer),
CreatedAt: customerPoints.CreatedAt,
UpdatedAt: customerPoints.UpdatedAt,
}
}
// ToCustomerPointsResponses converts a slice of customer points entities to customer points responses
func ToCustomerPointsResponses(customerPoints []entities.CustomerPoints) []models.CustomerPointsResponse {
responses := make([]models.CustomerPointsResponse, len(customerPoints))
for i, cp := range customerPoints {
responses[i] = *ToCustomerPointsResponse(&cp)
}
return responses
}
// ToCustomerPointsEntity converts a create customer points request to a customer points entity
func ToCustomerPointsEntity(req *models.CreateCustomerPointsRequest) *entities.CustomerPoints {
return &entities.CustomerPoints{
CustomerID: req.CustomerID,
Balance: req.Balance,
}
}
// UpdateCustomerPointsEntity updates a customer points entity with update request data
func UpdateCustomerPointsEntity(customerPoints *entities.CustomerPoints, req *models.UpdateCustomerPointsRequest) {
if req.Balance >= 0 {
customerPoints.Balance = req.Balance
}
}

View File

@ -0,0 +1,48 @@
package mappers
import (
"apskel-pos-be/internal/entities"
"apskel-pos-be/internal/models"
)
// ToCustomerTokensResponse converts a customer tokens entity to a customer tokens response
func ToCustomerTokensResponse(customerTokens *entities.CustomerTokens) *models.CustomerTokensResponse {
if customerTokens == nil {
return nil
}
return &models.CustomerTokensResponse{
ID: customerTokens.ID,
CustomerID: customerTokens.CustomerID,
TokenType: string(customerTokens.TokenType),
Balance: customerTokens.Balance,
Customer: ToCustomerResponse(&customerTokens.Customer),
CreatedAt: customerTokens.CreatedAt,
UpdatedAt: customerTokens.UpdatedAt,
}
}
// ToCustomerTokensResponses converts a slice of customer tokens entities to customer tokens responses
func ToCustomerTokensResponses(customerTokens []entities.CustomerTokens) []models.CustomerTokensResponse {
responses := make([]models.CustomerTokensResponse, len(customerTokens))
for i, ct := range customerTokens {
responses[i] = *ToCustomerTokensResponse(&ct)
}
return responses
}
// ToCustomerTokensEntity converts a create customer tokens request to a customer tokens entity
func ToCustomerTokensEntity(req *models.CreateCustomerTokensRequest) *entities.CustomerTokens {
return &entities.CustomerTokens{
CustomerID: req.CustomerID,
TokenType: entities.TokenType(req.TokenType),
Balance: req.Balance,
}
}
// UpdateCustomerTokensEntity updates a customer tokens entity with update request data
func UpdateCustomerTokensEntity(customerTokens *entities.CustomerTokens, req *models.UpdateCustomerTokensRequest) {
if req.Balance >= 0 {
customerTokens.Balance = req.Balance
}
}

View File

@ -0,0 +1,58 @@
package mappers
import (
"apskel-pos-be/internal/entities"
"apskel-pos-be/internal/models"
)
// ToGameResponse converts a game entity to a game response
func ToGameResponse(game *entities.Game) *models.GameResponse {
if game == nil {
return nil
}
return &models.GameResponse{
ID: game.ID,
Name: game.Name,
Type: string(game.Type),
IsActive: game.IsActive,
Metadata: game.Metadata,
CreatedAt: game.CreatedAt,
UpdatedAt: game.UpdatedAt,
}
}
// ToGameResponses converts a slice of game entities to game responses
func ToGameResponses(games []entities.Game) []models.GameResponse {
responses := make([]models.GameResponse, len(games))
for i, game := range games {
responses[i] = *ToGameResponse(&game)
}
return responses
}
// ToGameEntity converts a create game request to a game entity
func ToGameEntity(req *models.CreateGameRequest) *entities.Game {
return &entities.Game{
Name: req.Name,
Type: entities.GameType(req.Type),
IsActive: req.IsActive,
Metadata: req.Metadata,
}
}
// UpdateGameEntity updates a game entity with update request data
func UpdateGameEntity(game *entities.Game, req *models.UpdateGameRequest) {
if req.Name != nil {
game.Name = *req.Name
}
if req.Type != nil {
game.Type = entities.GameType(*req.Type)
}
if req.IsActive != nil {
game.IsActive = *req.IsActive
}
if req.Metadata != nil {
game.Metadata = req.Metadata
}
}

View File

@ -0,0 +1,45 @@
package mappers
import (
"apskel-pos-be/internal/entities"
"apskel-pos-be/internal/models"
)
// ToGamePlayResponse converts a game play entity to a game play response
func ToGamePlayResponse(gamePlay *entities.GamePlay) *models.GamePlayResponse {
if gamePlay == nil {
return nil
}
return &models.GamePlayResponse{
ID: gamePlay.ID,
GameID: gamePlay.GameID,
CustomerID: gamePlay.CustomerID,
PrizeID: gamePlay.PrizeID,
TokenUsed: gamePlay.TokenUsed,
RandomSeed: gamePlay.RandomSeed,
CreatedAt: gamePlay.CreatedAt,
Game: ToGameResponse(&gamePlay.Game),
Customer: ToCustomerResponse(&gamePlay.Customer),
Prize: ToGamePrizeResponse(gamePlay.Prize),
}
}
// ToGamePlayResponses converts a slice of game play entities to game play responses
func ToGamePlayResponses(gamePlays []entities.GamePlay) []models.GamePlayResponse {
responses := make([]models.GamePlayResponse, len(gamePlays))
for i, gp := range gamePlays {
responses[i] = *ToGamePlayResponse(&gp)
}
return responses
}
// ToGamePlayEntity converts a create game play request to a game play entity
func ToGamePlayEntity(req *models.CreateGamePlayRequest) *entities.GamePlay {
return &entities.GamePlay{
GameID: req.GameID,
CustomerID: req.CustomerID,
TokenUsed: req.TokenUsed,
RandomSeed: req.RandomSeed,
}
}

View File

@ -0,0 +1,77 @@
package mappers
import (
"apskel-pos-be/internal/entities"
"apskel-pos-be/internal/models"
)
// ToGamePrizeResponse converts a game prize entity to a game prize response
func ToGamePrizeResponse(gamePrize *entities.GamePrize) *models.GamePrizeResponse {
if gamePrize == nil {
return nil
}
return &models.GamePrizeResponse{
ID: gamePrize.ID,
GameID: gamePrize.GameID,
Name: gamePrize.Name,
Weight: gamePrize.Weight,
Stock: gamePrize.Stock,
MaxStock: gamePrize.MaxStock,
Threshold: gamePrize.Threshold,
FallbackPrizeID: gamePrize.FallbackPrizeID,
Metadata: gamePrize.Metadata,
Game: ToGameResponse(&gamePrize.Game),
FallbackPrize: ToGamePrizeResponse(gamePrize.FallbackPrize),
CreatedAt: gamePrize.CreatedAt,
UpdatedAt: gamePrize.UpdatedAt,
}
}
// ToGamePrizeResponses converts a slice of game prize entities to game prize responses
func ToGamePrizeResponses(gamePrizes []entities.GamePrize) []models.GamePrizeResponse {
responses := make([]models.GamePrizeResponse, len(gamePrizes))
for i, gp := range gamePrizes {
responses[i] = *ToGamePrizeResponse(&gp)
}
return responses
}
// ToGamePrizeEntity converts a create game prize request to a game prize entity
func ToGamePrizeEntity(req *models.CreateGamePrizeRequest) *entities.GamePrize {
return &entities.GamePrize{
GameID: req.GameID,
Name: req.Name,
Weight: req.Weight,
Stock: req.Stock,
MaxStock: req.MaxStock,
Threshold: req.Threshold,
FallbackPrizeID: req.FallbackPrizeID,
Metadata: req.Metadata,
}
}
// UpdateGamePrizeEntity updates a game prize entity with update request data
func UpdateGamePrizeEntity(gamePrize *entities.GamePrize, req *models.UpdateGamePrizeRequest) {
if req.Name != nil {
gamePrize.Name = *req.Name
}
if req.Weight != nil {
gamePrize.Weight = *req.Weight
}
if req.Stock != nil {
gamePrize.Stock = *req.Stock
}
if req.MaxStock != nil {
gamePrize.MaxStock = req.MaxStock
}
if req.Threshold != nil {
gamePrize.Threshold = req.Threshold
}
if req.FallbackPrizeID != nil {
gamePrize.FallbackPrizeID = req.FallbackPrizeID
}
if req.Metadata != nil {
gamePrize.Metadata = req.Metadata
}
}

View File

@ -0,0 +1,64 @@
package mappers
import (
"apskel-pos-be/internal/entities"
"apskel-pos-be/internal/models"
)
// ToOmsetTrackerResponse converts an omset tracker entity to an omset tracker response
func ToOmsetTrackerResponse(omsetTracker *entities.OmsetTracker) *models.OmsetTrackerResponse {
if omsetTracker == nil {
return nil
}
return &models.OmsetTrackerResponse{
ID: omsetTracker.ID,
PeriodType: string(omsetTracker.PeriodType),
PeriodStart: omsetTracker.PeriodStart,
PeriodEnd: omsetTracker.PeriodEnd,
Total: omsetTracker.Total,
GameID: omsetTracker.GameID,
Game: ToGameResponse(omsetTracker.Game),
CreatedAt: omsetTracker.CreatedAt,
UpdatedAt: omsetTracker.UpdatedAt,
}
}
// ToOmsetTrackerResponses converts a slice of omset tracker entities to omset tracker responses
func ToOmsetTrackerResponses(omsetTrackers []entities.OmsetTracker) []models.OmsetTrackerResponse {
responses := make([]models.OmsetTrackerResponse, len(omsetTrackers))
for i, ot := range omsetTrackers {
responses[i] = *ToOmsetTrackerResponse(&ot)
}
return responses
}
// ToOmsetTrackerEntity converts a create omset tracker request to an omset tracker entity
func ToOmsetTrackerEntity(req *models.CreateOmsetTrackerRequest) *entities.OmsetTracker {
return &entities.OmsetTracker{
PeriodType: entities.PeriodType(req.PeriodType),
PeriodStart: req.PeriodStart,
PeriodEnd: req.PeriodEnd,
Total: req.Total,
GameID: req.GameID,
}
}
// UpdateOmsetTrackerEntity updates an omset tracker entity with update request data
func UpdateOmsetTrackerEntity(omsetTracker *entities.OmsetTracker, req *models.UpdateOmsetTrackerRequest) {
if req.PeriodType != nil {
omsetTracker.PeriodType = entities.PeriodType(*req.PeriodType)
}
if req.PeriodStart != nil {
omsetTracker.PeriodStart = *req.PeriodStart
}
if req.PeriodEnd != nil {
omsetTracker.PeriodEnd = *req.PeriodEnd
}
if req.Total != nil {
omsetTracker.Total = *req.Total
}
if req.GameID != nil {
omsetTracker.GameID = req.GameID
}
}

View File

@ -0,0 +1,53 @@
package mappers
import (
"apskel-pos-be/internal/entities"
"apskel-pos-be/internal/models"
)
// ToTierResponse converts a tier entity to a tier response
func ToTierResponse(tier *entities.Tier) *models.TierResponse {
if tier == nil {
return nil
}
return &models.TierResponse{
ID: tier.ID,
Name: tier.Name,
MinPoints: tier.MinPoints,
Benefits: tier.Benefits,
CreatedAt: tier.CreatedAt,
UpdatedAt: tier.UpdatedAt,
}
}
// ToTierResponses converts a slice of tier entities to tier responses
func ToTierResponses(tiers []entities.Tier) []models.TierResponse {
responses := make([]models.TierResponse, len(tiers))
for i, tier := range tiers {
responses[i] = *ToTierResponse(&tier)
}
return responses
}
// ToTierEntity converts a create tier request to a tier entity
func ToTierEntity(req *models.CreateTierRequest) *entities.Tier {
return &entities.Tier{
Name: req.Name,
MinPoints: req.MinPoints,
Benefits: req.Benefits,
}
}
// UpdateTierEntity updates a tier entity with update request data
func UpdateTierEntity(tier *entities.Tier, req *models.UpdateTierRequest) {
if req.Name != nil {
tier.Name = *req.Name
}
if req.MinPoints != nil {
tier.MinPoints = *req.MinPoints
}
if req.Benefits != nil {
tier.Benefits = req.Benefits
}
}

View File

@ -0,0 +1,41 @@
package models
import (
"time"
"github.com/google/uuid"
)
type CreateCustomerPointsRequest struct {
CustomerID uuid.UUID `json:"customer_id" validate:"required"`
Balance int64 `json:"balance" validate:"min=0"`
}
type UpdateCustomerPointsRequest struct {
Balance int64 `json:"balance" validate:"min=0"`
}
type AddCustomerPointsRequest struct {
Points int64 `json:"points" validate:"required,min=1"`
}
type DeductCustomerPointsRequest struct {
Points int64 `json:"points" validate:"required,min=1"`
}
type CustomerPointsResponse struct {
ID uuid.UUID `json:"id"`
CustomerID uuid.UUID `json:"customer_id"`
Balance int64 `json:"balance"`
Customer *CustomerResponse `json:"customer,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
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"`
}

View File

@ -0,0 +1,44 @@
package models
import (
"time"
"github.com/google/uuid"
)
type CreateCustomerTokensRequest struct {
CustomerID uuid.UUID `json:"customer_id" validate:"required"`
TokenType string `json:"token_type" validate:"required,oneof=SPIN RAFFLE MINIGAME"`
Balance int64 `json:"balance" validate:"min=0"`
}
type UpdateCustomerTokensRequest struct {
Balance int64 `json:"balance" validate:"min=0"`
}
type AddCustomerTokensRequest struct {
Tokens int64 `json:"tokens" validate:"required,min=1"`
}
type DeductCustomerTokensRequest struct {
Tokens int64 `json:"tokens" validate:"required,min=1"`
}
type CustomerTokensResponse struct {
ID uuid.UUID `json:"id"`
CustomerID uuid.UUID `json:"customer_id"`
TokenType string `json:"token_type"`
Balance int64 `json:"balance"`
Customer *CustomerResponse `json:"customer,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
type ListCustomerTokensQuery struct {
Page int `query:"page" validate:"min=1"`
Limit int `query:"limit" validate:"min=1,max=100"`
Search string `query:"search"`
TokenType string `query:"token_type" validate:"omitempty,oneof=SPIN RAFFLE MINIGAME"`
SortBy string `query:"sort_by" validate:"omitempty,oneof=balance token_type created_at updated_at"`
SortOrder string `query:"sort_order" validate:"omitempty,oneof=asc desc"`
}

41
internal/models/game.go Normal file
View File

@ -0,0 +1,41 @@
package models
import (
"time"
"github.com/google/uuid"
)
type CreateGameRequest struct {
Name string `json:"name" validate:"required"`
Type string `json:"type" validate:"required,oneof=SPIN RAFFLE MINIGAME"`
IsActive bool `json:"is_active"`
Metadata map[string]interface{} `json:"metadata"`
}
type UpdateGameRequest struct {
Name *string `json:"name,omitempty" validate:"omitempty,required"`
Type *string `json:"type,omitempty" validate:"omitempty,oneof=SPIN RAFFLE MINIGAME"`
IsActive *bool `json:"is_active,omitempty"`
Metadata map[string]interface{} `json:"metadata,omitempty"`
}
type GameResponse 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"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
type ListGamesQuery struct {
Page int `query:"page" validate:"min=1"`
Limit int `query:"limit" validate:"min=1,max=100"`
Search string `query:"search"`
Type string `query:"type" validate:"omitempty,oneof=SPIN RAFFLE MINIGAME"`
IsActive *bool `query:"is_active"`
SortBy string `query:"sort_by" validate:"omitempty,oneof=name type created_at updated_at"`
SortOrder string `query:"sort_order" validate:"omitempty,oneof=asc desc"`
}

View File

@ -0,0 +1,50 @@
package models
import (
"time"
"github.com/google/uuid"
)
type CreateGamePlayRequest struct {
GameID uuid.UUID `json:"game_id" validate:"required"`
CustomerID uuid.UUID `json:"customer_id" validate:"required"`
TokenUsed int `json:"token_used" validate:"min=0"`
RandomSeed *string `json:"random_seed,omitempty"`
}
type GamePlayResponse struct {
ID uuid.UUID `json:"id"`
GameID uuid.UUID `json:"game_id"`
CustomerID uuid.UUID `json:"customer_id"`
PrizeID *uuid.UUID `json:"prize_id,omitempty"`
TokenUsed int `json:"token_used"`
RandomSeed *string `json:"random_seed,omitempty"`
CreatedAt time.Time `json:"created_at"`
Game *GameResponse `json:"game,omitempty"`
Customer *CustomerResponse `json:"customer,omitempty"`
Prize *GamePrizeResponse `json:"prize,omitempty"`
}
type ListGamePlaysQuery struct {
Page int `query:"page" validate:"min=1"`
Limit int `query:"limit" validate:"min=1,max=100"`
Search string `query:"search"`
GameID *uuid.UUID `query:"game_id"`
CustomerID *uuid.UUID `query:"customer_id"`
PrizeID *uuid.UUID `query:"prize_id"`
SortBy string `query:"sort_by" validate:"omitempty,oneof=created_at token_used"`
SortOrder string `query:"sort_order" validate:"omitempty,oneof=asc desc"`
}
type PlayGameRequest struct {
GameID uuid.UUID `json:"game_id" validate:"required"`
CustomerID uuid.UUID `json:"customer_id" validate:"required"`
TokenUsed int `json:"token_used" validate:"min=0"`
}
type PlayGameResponse struct {
GamePlay GamePlayResponse `json:"game_play"`
PrizeWon *GamePrizeResponse `json:"prize_won,omitempty"`
TokensRemaining int64 `json:"tokens_remaining"`
}

View File

@ -0,0 +1,53 @@
package models
import (
"time"
"github.com/google/uuid"
)
type CreateGamePrizeRequest struct {
GameID uuid.UUID `json:"game_id" validate:"required"`
Name string `json:"name" validate:"required"`
Weight int `json:"weight" validate:"min=1"`
Stock int `json:"stock" validate:"min=0"`
MaxStock *int `json:"max_stock,omitempty"`
Threshold *int64 `json:"threshold,omitempty"`
FallbackPrizeID *uuid.UUID `json:"fallback_prize_id,omitempty"`
Metadata map[string]interface{} `json:"metadata"`
}
type UpdateGamePrizeRequest struct {
Name *string `json:"name,omitempty" validate:"omitempty,required"`
Weight *int `json:"weight,omitempty" validate:"omitempty,min=1"`
Stock *int `json:"stock,omitempty" validate:"omitempty,min=0"`
MaxStock *int `json:"max_stock,omitempty"`
Threshold *int64 `json:"threshold,omitempty"`
FallbackPrizeID *uuid.UUID `json:"fallback_prize_id,omitempty"`
Metadata map[string]interface{} `json:"metadata,omitempty"`
}
type GamePrizeResponse struct {
ID uuid.UUID `json:"id"`
GameID uuid.UUID `json:"game_id"`
Name string `json:"name"`
Weight int `json:"weight"`
Stock int `json:"stock"`
MaxStock *int `json:"max_stock,omitempty"`
Threshold *int64 `json:"threshold,omitempty"`
FallbackPrizeID *uuid.UUID `json:"fallback_prize_id,omitempty"`
Metadata map[string]interface{} `json:"metadata"`
Game *GameResponse `json:"game,omitempty"`
FallbackPrize *GamePrizeResponse `json:"fallback_prize,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
type ListGamePrizesQuery struct {
Page int `query:"page" validate:"min=1"`
Limit int `query:"limit" validate:"min=1,max=100"`
Search string `query:"search"`
GameID *uuid.UUID `query:"game_id"`
SortBy string `query:"sort_by" validate:"omitempty,oneof=name weight stock created_at updated_at"`
SortOrder string `query:"sort_order" validate:"omitempty,oneof=asc desc"`
}

View File

@ -0,0 +1,51 @@
package models
import (
"time"
"github.com/google/uuid"
)
type CreateOmsetTrackerRequest struct {
PeriodType string `json:"period_type" validate:"required,oneof=DAILY WEEKLY MONTHLY TOTAL"`
PeriodStart time.Time `json:"period_start" validate:"required"`
PeriodEnd time.Time `json:"period_end" validate:"required"`
Total int64 `json:"total" validate:"min=0"`
GameID *uuid.UUID `json:"game_id,omitempty"`
}
type UpdateOmsetTrackerRequest struct {
PeriodType *string `json:"period_type,omitempty" validate:"omitempty,oneof=DAILY WEEKLY MONTHLY TOTAL"`
PeriodStart *time.Time `json:"period_start,omitempty"`
PeriodEnd *time.Time `json:"period_end,omitempty"`
Total *int64 `json:"total,omitempty" validate:"omitempty,min=0"`
GameID *uuid.UUID `json:"game_id,omitempty"`
}
type AddOmsetRequest struct {
Amount int64 `json:"amount" validate:"required,min=1"`
}
type OmsetTrackerResponse struct {
ID uuid.UUID `json:"id"`
PeriodType string `json:"period_type"`
PeriodStart time.Time `json:"period_start"`
PeriodEnd time.Time `json:"period_end"`
Total int64 `json:"total"`
GameID *uuid.UUID `json:"game_id,omitempty"`
Game *GameResponse `json:"game,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
type ListOmsetTrackerQuery struct {
Page int `query:"page" validate:"min=1"`
Limit int `query:"limit" validate:"min=1,max=100"`
Search string `query:"search"`
PeriodType string `query:"period_type" validate:"omitempty,oneof=DAILY WEEKLY MONTHLY TOTAL"`
GameID *uuid.UUID `query:"game_id"`
From *time.Time `query:"from"`
To *time.Time `query:"to"`
SortBy string `query:"sort_by" validate:"omitempty,oneof=period_type period_start total created_at updated_at"`
SortOrder string `query:"sort_order" validate:"omitempty,oneof=asc desc"`
}

36
internal/models/tier.go Normal file
View File

@ -0,0 +1,36 @@
package models
import (
"time"
"github.com/google/uuid"
)
type CreateTierRequest struct {
Name string `json:"name" validate:"required"`
MinPoints int64 `json:"min_points" validate:"min=0"`
Benefits map[string]interface{} `json:"benefits"`
}
type UpdateTierRequest struct {
Name *string `json:"name,omitempty" validate:"omitempty,required"`
MinPoints *int64 `json:"min_points,omitempty" validate:"omitempty,min=0"`
Benefits map[string]interface{} `json:"benefits,omitempty"`
}
type TierResponse struct {
ID uuid.UUID `json:"id"`
Name string `json:"name"`
MinPoints int64 `json:"min_points"`
Benefits map[string]interface{} `json:"benefits"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
type ListTiersQuery 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=name min_points created_at updated_at"`
SortOrder string `query:"sort_order" validate:"omitempty,oneof=asc desc"`
}

View File

@ -0,0 +1,196 @@
package processor
import (
"apskel-pos-be/internal/mappers"
"apskel-pos-be/internal/models"
"apskel-pos-be/internal/repository"
"context"
"errors"
"fmt"
"github.com/google/uuid"
)
type CustomerPointsProcessor struct {
customerPointsRepo *repository.CustomerPointsRepository
}
func NewCustomerPointsProcessor(customerPointsRepo *repository.CustomerPointsRepository) *CustomerPointsProcessor {
return &CustomerPointsProcessor{
customerPointsRepo: customerPointsRepo,
}
}
// CreateCustomerPoints creates a new customer points record
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
}
// 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
}
// 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
}
// 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
}
offset := (query.Page - 1) * query.Limit
// Get customer points from repository
customerPoints, total, err := p.customerPointsRepo.List(
ctx,
offset,
query.Limit,
query.Search,
query.SortBy,
query.SortOrder,
)
if err != nil {
return nil, fmt.Errorf("failed to list customer points: %w", err)
}
// Convert to responses
responses := mappers.ToCustomerPointsResponses(customerPoints)
// Calculate pagination info
totalPages := int((total + int64(query.Limit) - 1) / int64(query.Limit))
return &models.PaginatedResponse[models.CustomerPointsResponse]{
Data: responses,
Pagination: models.Pagination{
Page: query.Page,
Limit: query.Limit,
Total: total,
TotalPages: totalPages,
},
}, 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)
if err != nil {
return nil, fmt.Errorf("customer points not found: %w", err)
}
// Update customer points fields
mappers.UpdateCustomerPointsEntity(customerPoints, req)
// Save updated customer points
err = p.customerPointsRepo.Update(ctx, customerPoints)
if err != nil {
return nil, fmt.Errorf("failed to update customer points: %w", err)
}
return mappers.ToCustomerPointsResponse(customerPoints), 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)
if err != nil {
return fmt.Errorf("customer points not found: %w", err)
}
// Delete customer points
err = p.customerPointsRepo.Delete(ctx, id)
if err != nil {
return fmt.Errorf("failed to delete customer points: %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
}

View File

@ -0,0 +1,198 @@
package processor
import (
"apskel-pos-be/internal/entities"
"apskel-pos-be/internal/mappers"
"apskel-pos-be/internal/models"
"apskel-pos-be/internal/repository"
"context"
"errors"
"fmt"
"github.com/google/uuid"
)
type CustomerTokensProcessor struct {
customerTokensRepo *repository.CustomerTokensRepository
}
func NewCustomerTokensProcessor(customerTokensRepo *repository.CustomerTokensRepository) *CustomerTokensProcessor {
return &CustomerTokensProcessor{
customerTokensRepo: customerTokensRepo,
}
}
// CreateCustomerTokens creates a new customer tokens record
func (p *CustomerTokensProcessor) CreateCustomerTokens(ctx context.Context, req *models.CreateCustomerTokensRequest) (*models.CustomerTokensResponse, error) {
// Convert request to entity
customerTokens := mappers.ToCustomerTokensEntity(req)
// Create customer tokens
err := p.customerTokensRepo.Create(ctx, customerTokens)
if err != nil {
return nil, fmt.Errorf("failed to create customer tokens: %w", err)
}
return mappers.ToCustomerTokensResponse(customerTokens), nil
}
// GetCustomerTokens retrieves customer tokens by ID
func (p *CustomerTokensProcessor) GetCustomerTokens(ctx context.Context, id uuid.UUID) (*models.CustomerTokensResponse, error) {
customerTokens, err := p.customerTokensRepo.GetByID(ctx, id)
if err != nil {
return nil, fmt.Errorf("customer tokens not found: %w", err)
}
return mappers.ToCustomerTokensResponse(customerTokens), nil
}
// GetCustomerTokensByCustomerIDAndType retrieves customer tokens by customer ID and token type
func (p *CustomerTokensProcessor) GetCustomerTokensByCustomerIDAndType(ctx context.Context, customerID uuid.UUID, tokenType string) (*models.CustomerTokensResponse, error) {
customerTokens, err := p.customerTokensRepo.EnsureCustomerTokens(ctx, customerID, entities.TokenType(tokenType))
if err != nil {
return nil, fmt.Errorf("failed to get customer tokens: %w", err)
}
return mappers.ToCustomerTokensResponse(customerTokens), nil
}
// ListCustomerTokens retrieves customer tokens with pagination and filtering
func (p *CustomerTokensProcessor) ListCustomerTokens(ctx context.Context, query *models.ListCustomerTokensQuery) (*models.PaginatedResponse[models.CustomerTokensResponse], 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
}
offset := (query.Page - 1) * query.Limit
// Get customer tokens from repository
customerTokens, total, err := p.customerTokensRepo.List(
ctx,
offset,
query.Limit,
query.Search,
query.TokenType,
query.SortBy,
query.SortOrder,
)
if err != nil {
return nil, fmt.Errorf("failed to list customer tokens: %w", err)
}
// Convert to responses
responses := mappers.ToCustomerTokensResponses(customerTokens)
// Calculate pagination info
totalPages := int((total + int64(query.Limit) - 1) / int64(query.Limit))
return &models.PaginatedResponse[models.CustomerTokensResponse]{
Data: responses,
Pagination: models.Pagination{
Page: query.Page,
Limit: query.Limit,
Total: total,
TotalPages: totalPages,
},
}, nil
}
// UpdateCustomerTokens updates an existing customer tokens record
func (p *CustomerTokensProcessor) UpdateCustomerTokens(ctx context.Context, id uuid.UUID, req *models.UpdateCustomerTokensRequest) (*models.CustomerTokensResponse, error) {
// Get existing customer tokens
customerTokens, err := p.customerTokensRepo.GetByID(ctx, id)
if err != nil {
return nil, fmt.Errorf("customer tokens not found: %w", err)
}
// Update customer tokens fields
mappers.UpdateCustomerTokensEntity(customerTokens, req)
// Save updated customer tokens
err = p.customerTokensRepo.Update(ctx, customerTokens)
if err != nil {
return nil, fmt.Errorf("failed to update customer tokens: %w", err)
}
return mappers.ToCustomerTokensResponse(customerTokens), nil
}
// DeleteCustomerTokens deletes a customer tokens record
func (p *CustomerTokensProcessor) DeleteCustomerTokens(ctx context.Context, id uuid.UUID) error {
// Get existing customer tokens
_, err := p.customerTokensRepo.GetByID(ctx, id)
if err != nil {
return fmt.Errorf("customer tokens not found: %w", err)
}
// Delete customer tokens
err = p.customerTokensRepo.Delete(ctx, id)
if err != nil {
return fmt.Errorf("failed to delete customer tokens: %w", err)
}
return nil
}
// AddTokens adds tokens to a customer's balance
func (p *CustomerTokensProcessor) AddTokens(ctx context.Context, customerID uuid.UUID, tokenType string, tokens int64) (*models.CustomerTokensResponse, error) {
if tokens <= 0 {
return nil, errors.New("tokens must be greater than 0")
}
// Ensure customer tokens record exists
_, err := p.customerTokensRepo.EnsureCustomerTokens(ctx, customerID, entities.TokenType(tokenType))
if err != nil {
return nil, fmt.Errorf("failed to ensure customer tokens: %w", err)
}
// Add tokens
err = p.customerTokensRepo.AddTokens(ctx, customerID, entities.TokenType(tokenType), tokens)
if err != nil {
return nil, fmt.Errorf("failed to add tokens: %w", err)
}
// Get updated customer tokens
customerTokens, err := p.customerTokensRepo.GetByCustomerIDAndType(ctx, customerID, entities.TokenType(tokenType))
if err != nil {
return nil, fmt.Errorf("failed to get updated customer tokens: %w", err)
}
return mappers.ToCustomerTokensResponse(customerTokens), nil
}
// DeductTokens deducts tokens from a customer's balance
func (p *CustomerTokensProcessor) DeductTokens(ctx context.Context, customerID uuid.UUID, tokenType string, tokens int64) (*models.CustomerTokensResponse, error) {
if tokens <= 0 {
return nil, errors.New("tokens must be greater than 0")
}
// Get current customer tokens
customerTokens, err := p.customerTokensRepo.GetByCustomerIDAndType(ctx, customerID, entities.TokenType(tokenType))
if err != nil {
return nil, fmt.Errorf("customer tokens not found: %w", err)
}
if customerTokens.Balance < tokens {
return nil, errors.New("insufficient tokens balance")
}
// Deduct tokens
err = p.customerTokensRepo.DeductTokens(ctx, customerID, entities.TokenType(tokenType), tokens)
if err != nil {
return nil, fmt.Errorf("failed to deduct tokens: %w", err)
}
// Get updated customer tokens
updatedCustomerTokens, err := p.customerTokensRepo.GetByCustomerIDAndType(ctx, customerID, entities.TokenType(tokenType))
if err != nil {
return nil, fmt.Errorf("failed to get updated customer tokens: %w", err)
}
return mappers.ToCustomerTokensResponse(updatedCustomerTokens), nil
}

View File

@ -0,0 +1,239 @@
package processor
import (
"apskel-pos-be/internal/entities"
"apskel-pos-be/internal/mappers"
"apskel-pos-be/internal/models"
"apskel-pos-be/internal/repository"
"context"
"errors"
"fmt"
"math/rand"
"time"
"github.com/google/uuid"
)
type GamePlayProcessor struct {
gamePlayRepo *repository.GamePlayRepository
gameRepo *repository.GameRepository
gamePrizeRepo *repository.GamePrizeRepository
customerTokensRepo *repository.CustomerTokensRepository
customerPointsRepo *repository.CustomerPointsRepository
}
func NewGamePlayProcessor(
gamePlayRepo *repository.GamePlayRepository,
gameRepo *repository.GameRepository,
gamePrizeRepo *repository.GamePrizeRepository,
customerTokensRepo *repository.CustomerTokensRepository,
customerPointsRepo *repository.CustomerPointsRepository,
) *GamePlayProcessor {
return &GamePlayProcessor{
gamePlayRepo: gamePlayRepo,
gameRepo: gameRepo,
gamePrizeRepo: gamePrizeRepo,
customerTokensRepo: customerTokensRepo,
customerPointsRepo: customerPointsRepo,
}
}
// CreateGamePlay creates a new game play record
func (p *GamePlayProcessor) CreateGamePlay(ctx context.Context, req *models.CreateGamePlayRequest) (*models.GamePlayResponse, error) {
// Convert request to entity
gamePlay := mappers.ToGamePlayEntity(req)
// Create game play
err := p.gamePlayRepo.Create(ctx, gamePlay)
if err != nil {
return nil, fmt.Errorf("failed to create game play: %w", err)
}
return mappers.ToGamePlayResponse(gamePlay), nil
}
// GetGamePlay retrieves a game play by ID
func (p *GamePlayProcessor) GetGamePlay(ctx context.Context, id uuid.UUID) (*models.GamePlayResponse, error) {
gamePlay, err := p.gamePlayRepo.GetByID(ctx, id)
if err != nil {
return nil, fmt.Errorf("game play not found: %w", err)
}
return mappers.ToGamePlayResponse(gamePlay), nil
}
// ListGamePlays retrieves game plays with pagination and filtering
func (p *GamePlayProcessor) ListGamePlays(ctx context.Context, query *models.ListGamePlaysQuery) (*models.PaginatedResponse[models.GamePlayResponse], 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
}
offset := (query.Page - 1) * query.Limit
// Get game plays from repository
gamePlays, total, err := p.gamePlayRepo.List(
ctx,
offset,
query.Limit,
query.Search,
query.GameID,
query.CustomerID,
query.PrizeID,
query.SortBy,
query.SortOrder,
)
if err != nil {
return nil, fmt.Errorf("failed to list game plays: %w", err)
}
// Convert to responses
responses := mappers.ToGamePlayResponses(gamePlays)
// Calculate pagination info
totalPages := int((total + int64(query.Limit) - 1) / int64(query.Limit))
return &models.PaginatedResponse[models.GamePlayResponse]{
Data: responses,
Pagination: models.Pagination{
Page: query.Page,
Limit: query.Limit,
Total: total,
TotalPages: totalPages,
},
}, nil
}
// PlayGame handles the game playing logic
func (p *GamePlayProcessor) PlayGame(ctx context.Context, req *models.PlayGameRequest) (*models.PlayGameResponse, error) {
// Verify game exists and is active
game, err := p.gameRepo.GetByID(ctx, req.GameID)
if err != nil {
return nil, fmt.Errorf("game not found: %w", err)
}
if !game.IsActive {
return nil, errors.New("game is not active")
}
// Convert GameType to TokenType
tokenType := entities.TokenType(game.Type)
// Check if customer has enough tokens
customerTokens, err := p.customerTokensRepo.GetByCustomerIDAndType(ctx, req.CustomerID, tokenType)
if err != nil {
return nil, fmt.Errorf("customer tokens not found: %w", err)
}
if customerTokens.Balance < int64(req.TokenUsed) {
return nil, errors.New("insufficient tokens")
}
// Deduct tokens
err = p.customerTokensRepo.DeductTokens(ctx, req.CustomerID, tokenType, int64(req.TokenUsed))
if err != nil {
return nil, fmt.Errorf("failed to deduct tokens: %w", err)
}
// Get available prizes
availablePrizes, err := p.gamePrizeRepo.GetAvailablePrizes(ctx, req.GameID)
if err != nil {
return nil, fmt.Errorf("failed to get available prizes: %w", err)
}
if len(availablePrizes) == 0 {
return nil, errors.New("no prizes available")
}
// Convert entities to models for prize selection
prizeResponses := make([]models.GamePrizeResponse, len(availablePrizes))
for i, prize := range availablePrizes {
prizeResponses[i] = *mappers.ToGamePrizeResponse(&prize)
}
// Select prize based on weight
selectedPrize := p.selectPrizeByWeight(prizeResponses)
// Generate random seed for audit
randomSeed := fmt.Sprintf("%d", time.Now().UnixNano())
// Create game play record
gamePlay := &models.CreateGamePlayRequest{
GameID: req.GameID,
CustomerID: req.CustomerID,
TokenUsed: req.TokenUsed,
RandomSeed: &randomSeed,
}
gamePlayEntity := mappers.ToGamePlayEntity(gamePlay)
if selectedPrize != nil {
gamePlayEntity.PrizeID = &selectedPrize.ID
}
err = p.gamePlayRepo.Create(ctx, gamePlayEntity)
if err != nil {
// Rollback token deduction
p.customerTokensRepo.AddTokens(ctx, req.CustomerID, tokenType, int64(req.TokenUsed))
return nil, fmt.Errorf("failed to create game play: %w", err)
}
// Decrease prize stock if prize was won
if selectedPrize != nil {
err = p.gamePrizeRepo.DecreaseStock(ctx, selectedPrize.ID, 1)
if err != nil {
// Log error but don't fail the transaction
fmt.Printf("Warning: failed to decrease prize stock: %v\n", err)
}
}
// Get updated token balance
updatedTokens, err := p.customerTokensRepo.GetByCustomerIDAndType(ctx, req.CustomerID, tokenType)
if err != nil {
return nil, fmt.Errorf("failed to get updated token balance: %w", err)
}
return &models.PlayGameResponse{
GamePlay: *mappers.ToGamePlayResponse(gamePlayEntity),
PrizeWon: selectedPrize,
TokensRemaining: updatedTokens.Balance,
}, nil
}
// selectPrizeByWeight selects a prize based on weight distribution
func (p *GamePlayProcessor) selectPrizeByWeight(prizes []models.GamePrizeResponse) *models.GamePrizeResponse {
if len(prizes) == 0 {
return nil
}
// Calculate total weight
totalWeight := 0
for _, prize := range prizes {
totalWeight += prize.Weight
}
if totalWeight == 0 {
return nil
}
// Generate random number
rand.Seed(time.Now().UnixNano())
randomNumber := rand.Intn(totalWeight)
// Select prize based on cumulative weight
currentWeight := 0
for _, prize := range prizes {
currentWeight += prize.Weight
if randomNumber < currentWeight {
return &prize
}
}
// Fallback to last prize
return &prizes[len(prizes)-1]
}

View File

@ -0,0 +1,148 @@
package processor
import (
"apskel-pos-be/internal/mappers"
"apskel-pos-be/internal/models"
"apskel-pos-be/internal/repository"
"context"
"fmt"
"github.com/google/uuid"
)
type GamePrizeProcessor struct {
gamePrizeRepo *repository.GamePrizeRepository
}
func NewGamePrizeProcessor(gamePrizeRepo *repository.GamePrizeRepository) *GamePrizeProcessor {
return &GamePrizeProcessor{
gamePrizeRepo: gamePrizeRepo,
}
}
// CreateGamePrize creates a new game prize
func (p *GamePrizeProcessor) CreateGamePrize(ctx context.Context, req *models.CreateGamePrizeRequest) (*models.GamePrizeResponse, error) {
// Convert request to entity
gamePrize := mappers.ToGamePrizeEntity(req)
// Create game prize
err := p.gamePrizeRepo.Create(ctx, gamePrize)
if err != nil {
return nil, fmt.Errorf("failed to create game prize: %w", err)
}
return mappers.ToGamePrizeResponse(gamePrize), nil
}
// GetGamePrize retrieves a game prize by ID
func (p *GamePrizeProcessor) GetGamePrize(ctx context.Context, id uuid.UUID) (*models.GamePrizeResponse, error) {
gamePrize, err := p.gamePrizeRepo.GetByID(ctx, id)
if err != nil {
return nil, fmt.Errorf("game prize not found: %w", err)
}
return mappers.ToGamePrizeResponse(gamePrize), nil
}
// GetGamePrizesByGameID retrieves all prizes for a specific game
func (p *GamePrizeProcessor) GetGamePrizesByGameID(ctx context.Context, gameID uuid.UUID) ([]models.GamePrizeResponse, error) {
gamePrizes, err := p.gamePrizeRepo.GetByGameID(ctx, gameID)
if err != nil {
return nil, fmt.Errorf("failed to get game prizes: %w", err)
}
return mappers.ToGamePrizeResponses(gamePrizes), nil
}
// ListGamePrizes retrieves game prizes with pagination and filtering
func (p *GamePrizeProcessor) ListGamePrizes(ctx context.Context, query *models.ListGamePrizesQuery) (*models.PaginatedResponse[models.GamePrizeResponse], 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
}
offset := (query.Page - 1) * query.Limit
// Get game prizes from repository
gamePrizes, total, err := p.gamePrizeRepo.List(
ctx,
offset,
query.Limit,
query.Search,
query.GameID,
query.SortBy,
query.SortOrder,
)
if err != nil {
return nil, fmt.Errorf("failed to list game prizes: %w", err)
}
// Convert to responses
responses := mappers.ToGamePrizeResponses(gamePrizes)
// Calculate pagination info
totalPages := int((total + int64(query.Limit) - 1) / int64(query.Limit))
return &models.PaginatedResponse[models.GamePrizeResponse]{
Data: responses,
Pagination: models.Pagination{
Page: query.Page,
Limit: query.Limit,
Total: total,
TotalPages: totalPages,
},
}, nil
}
// UpdateGamePrize updates an existing game prize
func (p *GamePrizeProcessor) UpdateGamePrize(ctx context.Context, id uuid.UUID, req *models.UpdateGamePrizeRequest) (*models.GamePrizeResponse, error) {
// Get existing game prize
gamePrize, err := p.gamePrizeRepo.GetByID(ctx, id)
if err != nil {
return nil, fmt.Errorf("game prize not found: %w", err)
}
// Update game prize fields
mappers.UpdateGamePrizeEntity(gamePrize, req)
// Save updated game prize
err = p.gamePrizeRepo.Update(ctx, gamePrize)
if err != nil {
return nil, fmt.Errorf("failed to update game prize: %w", err)
}
return mappers.ToGamePrizeResponse(gamePrize), nil
}
// DeleteGamePrize deletes a game prize
func (p *GamePrizeProcessor) DeleteGamePrize(ctx context.Context, id uuid.UUID) error {
// Get existing game prize
_, err := p.gamePrizeRepo.GetByID(ctx, id)
if err != nil {
return fmt.Errorf("game prize not found: %w", err)
}
// Delete game prize
err = p.gamePrizeRepo.Delete(ctx, id)
if err != nil {
return fmt.Errorf("failed to delete game prize: %w", err)
}
return nil
}
// GetAvailablePrizes gets all available prizes for a game (with stock > 0)
func (p *GamePrizeProcessor) GetAvailablePrizes(ctx context.Context, gameID uuid.UUID) ([]models.GamePrizeResponse, error) {
gamePrizes, err := p.gamePrizeRepo.GetAvailablePrizes(ctx, gameID)
if err != nil {
return nil, fmt.Errorf("failed to get available prizes: %w", err)
}
return mappers.ToGamePrizeResponses(gamePrizes), nil
}

View File

@ -0,0 +1,139 @@
package processor
import (
"apskel-pos-be/internal/mappers"
"apskel-pos-be/internal/models"
"apskel-pos-be/internal/repository"
"context"
"fmt"
"github.com/google/uuid"
)
type GameProcessor struct {
gameRepo *repository.GameRepository
}
func NewGameProcessor(gameRepo *repository.GameRepository) *GameProcessor {
return &GameProcessor{
gameRepo: gameRepo,
}
}
// CreateGame creates a new game
func (p *GameProcessor) CreateGame(ctx context.Context, req *models.CreateGameRequest) (*models.GameResponse, error) {
// Convert request to entity
game := mappers.ToGameEntity(req)
// Create game
err := p.gameRepo.Create(ctx, game)
if err != nil {
return nil, fmt.Errorf("failed to create game: %w", err)
}
return mappers.ToGameResponse(game), nil
}
// GetGame retrieves a game by ID
func (p *GameProcessor) GetGame(ctx context.Context, id uuid.UUID) (*models.GameResponse, error) {
game, err := p.gameRepo.GetByID(ctx, id)
if err != nil {
return nil, fmt.Errorf("game not found: %w", err)
}
return mappers.ToGameResponse(game), nil
}
// ListGames retrieves games with pagination and filtering
func (p *GameProcessor) ListGames(ctx context.Context, query *models.ListGamesQuery) (*models.PaginatedResponse[models.GameResponse], 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
}
offset := (query.Page - 1) * query.Limit
// Get games from repository
games, total, err := p.gameRepo.List(
ctx,
offset,
query.Limit,
query.Search,
query.Type,
query.IsActive,
query.SortBy,
query.SortOrder,
)
if err != nil {
return nil, fmt.Errorf("failed to list games: %w", err)
}
// Convert to responses
responses := mappers.ToGameResponses(games)
// Calculate pagination info
totalPages := int((total + int64(query.Limit) - 1) / int64(query.Limit))
return &models.PaginatedResponse[models.GameResponse]{
Data: responses,
Pagination: models.Pagination{
Page: query.Page,
Limit: query.Limit,
Total: total,
TotalPages: totalPages,
},
}, nil
}
// UpdateGame updates an existing game
func (p *GameProcessor) UpdateGame(ctx context.Context, id uuid.UUID, req *models.UpdateGameRequest) (*models.GameResponse, error) {
// Get existing game
game, err := p.gameRepo.GetByID(ctx, id)
if err != nil {
return nil, fmt.Errorf("game not found: %w", err)
}
// Update game fields
mappers.UpdateGameEntity(game, req)
// Save updated game
err = p.gameRepo.Update(ctx, game)
if err != nil {
return nil, fmt.Errorf("failed to update game: %w", err)
}
return mappers.ToGameResponse(game), nil
}
// DeleteGame deletes a game
func (p *GameProcessor) DeleteGame(ctx context.Context, id uuid.UUID) error {
// Get existing game
_, err := p.gameRepo.GetByID(ctx, id)
if err != nil {
return fmt.Errorf("game not found: %w", err)
}
// Delete game
err = p.gameRepo.Delete(ctx, id)
if err != nil {
return fmt.Errorf("failed to delete game: %w", err)
}
return nil
}
// GetActiveGames gets all active games
func (p *GameProcessor) GetActiveGames(ctx context.Context) ([]models.GameResponse, error) {
games, err := p.gameRepo.GetActiveGames(ctx)
if err != nil {
return nil, fmt.Errorf("failed to get active games: %w", err)
}
return mappers.ToGameResponses(games), nil
}

View File

@ -0,0 +1,163 @@
package processor
import (
"apskel-pos-be/internal/entities"
"apskel-pos-be/internal/mappers"
"apskel-pos-be/internal/models"
"apskel-pos-be/internal/repository"
"context"
"fmt"
"time"
"github.com/google/uuid"
)
type OmsetTrackerProcessor struct {
omsetTrackerRepo *repository.OmsetTrackerRepository
}
func NewOmsetTrackerProcessor(omsetTrackerRepo *repository.OmsetTrackerRepository) *OmsetTrackerProcessor {
return &OmsetTrackerProcessor{
omsetTrackerRepo: omsetTrackerRepo,
}
}
// CreateOmsetTracker creates a new omset tracker record
func (p *OmsetTrackerProcessor) CreateOmsetTracker(ctx context.Context, req *models.CreateOmsetTrackerRequest) (*models.OmsetTrackerResponse, error) {
// Convert request to entity
omsetTracker := mappers.ToOmsetTrackerEntity(req)
// Create omset tracker
err := p.omsetTrackerRepo.Create(ctx, omsetTracker)
if err != nil {
return nil, fmt.Errorf("failed to create omset tracker: %w", err)
}
return mappers.ToOmsetTrackerResponse(omsetTracker), nil
}
// GetOmsetTracker retrieves an omset tracker by ID
func (p *OmsetTrackerProcessor) GetOmsetTracker(ctx context.Context, id uuid.UUID) (*models.OmsetTrackerResponse, error) {
omsetTracker, err := p.omsetTrackerRepo.GetByID(ctx, id)
if err != nil {
return nil, fmt.Errorf("omset tracker not found: %w", err)
}
return mappers.ToOmsetTrackerResponse(omsetTracker), nil
}
// ListOmsetTrackers retrieves omset trackers with pagination and filtering
func (p *OmsetTrackerProcessor) ListOmsetTrackers(ctx context.Context, query *models.ListOmsetTrackerQuery) (*models.PaginatedResponse[models.OmsetTrackerResponse], 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
}
offset := (query.Page - 1) * query.Limit
// Get omset trackers from repository
omsetTrackers, total, err := p.omsetTrackerRepo.List(
ctx,
offset,
query.Limit,
query.Search,
query.PeriodType,
query.GameID,
query.From,
query.To,
query.SortBy,
query.SortOrder,
)
if err != nil {
return nil, fmt.Errorf("failed to list omset trackers: %w", err)
}
// Convert to responses
responses := mappers.ToOmsetTrackerResponses(omsetTrackers)
// Calculate pagination info
totalPages := int((total + int64(query.Limit) - 1) / int64(query.Limit))
return &models.PaginatedResponse[models.OmsetTrackerResponse]{
Data: responses,
Pagination: models.Pagination{
Page: query.Page,
Limit: query.Limit,
Total: total,
TotalPages: totalPages,
},
}, nil
}
// UpdateOmsetTracker updates an existing omset tracker
func (p *OmsetTrackerProcessor) UpdateOmsetTracker(ctx context.Context, id uuid.UUID, req *models.UpdateOmsetTrackerRequest) (*models.OmsetTrackerResponse, error) {
// Get existing omset tracker
omsetTracker, err := p.omsetTrackerRepo.GetByID(ctx, id)
if err != nil {
return nil, fmt.Errorf("omset tracker not found: %w", err)
}
// Update omset tracker fields
mappers.UpdateOmsetTrackerEntity(omsetTracker, req)
// Save updated omset tracker
err = p.omsetTrackerRepo.Update(ctx, omsetTracker)
if err != nil {
return nil, fmt.Errorf("failed to update omset tracker: %w", err)
}
return mappers.ToOmsetTrackerResponse(omsetTracker), nil
}
// DeleteOmsetTracker deletes an omset tracker
func (p *OmsetTrackerProcessor) DeleteOmsetTracker(ctx context.Context, id uuid.UUID) error {
// Get existing omset tracker
_, err := p.omsetTrackerRepo.GetByID(ctx, id)
if err != nil {
return fmt.Errorf("omset tracker not found: %w", err)
}
// Delete omset tracker
err = p.omsetTrackerRepo.Delete(ctx, id)
if err != nil {
return fmt.Errorf("failed to delete omset tracker: %w", err)
}
return nil
}
// AddOmset adds omset to a specific period
func (p *OmsetTrackerProcessor) AddOmset(ctx context.Context, periodType string, periodStart, periodEnd time.Time, amount int64, gameID *uuid.UUID) (*models.OmsetTrackerResponse, error) {
if amount <= 0 {
return nil, fmt.Errorf("amount must be greater than 0")
}
// Convert string to PeriodType
periodTypeEnum := entities.PeriodType(periodType)
// Get or create period tracker
omsetTracker, err := p.omsetTrackerRepo.GetOrCreatePeriod(ctx, periodTypeEnum, periodStart, periodEnd, gameID)
if err != nil {
return nil, fmt.Errorf("failed to get or create period tracker: %w", err)
}
// Add omset
err = p.omsetTrackerRepo.AddOmset(ctx, periodTypeEnum, periodStart, periodEnd, amount, gameID)
if err != nil {
return nil, fmt.Errorf("failed to add omset: %w", err)
}
// Get updated tracker
updatedTracker, err := p.omsetTrackerRepo.GetByID(ctx, omsetTracker.ID)
if err != nil {
return nil, fmt.Errorf("failed to get updated tracker: %w", err)
}
return mappers.ToOmsetTrackerResponse(updatedTracker), nil
}

View File

@ -0,0 +1,137 @@
package processor
import (
"apskel-pos-be/internal/mappers"
"apskel-pos-be/internal/models"
"apskel-pos-be/internal/repository"
"context"
"fmt"
"github.com/google/uuid"
)
type TierProcessor struct {
tierRepo *repository.TierRepository
}
func NewTierProcessor(tierRepo *repository.TierRepository) *TierProcessor {
return &TierProcessor{
tierRepo: tierRepo,
}
}
// CreateTier creates a new tier
func (p *TierProcessor) CreateTier(ctx context.Context, req *models.CreateTierRequest) (*models.TierResponse, error) {
// Convert request to entity
tier := mappers.ToTierEntity(req)
// Create tier
err := p.tierRepo.Create(ctx, tier)
if err != nil {
return nil, fmt.Errorf("failed to create tier: %w", err)
}
return mappers.ToTierResponse(tier), nil
}
// GetTier retrieves a tier by ID
func (p *TierProcessor) GetTier(ctx context.Context, id uuid.UUID) (*models.TierResponse, error) {
tier, err := p.tierRepo.GetByID(ctx, id)
if err != nil {
return nil, fmt.Errorf("tier not found: %w", err)
}
return mappers.ToTierResponse(tier), nil
}
// ListTiers retrieves tiers with pagination and filtering
func (p *TierProcessor) ListTiers(ctx context.Context, query *models.ListTiersQuery) (*models.PaginatedResponse[models.TierResponse], 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
}
offset := (query.Page - 1) * query.Limit
// Get tiers from repository
tiers, total, err := p.tierRepo.List(
ctx,
offset,
query.Limit,
query.Search,
query.SortBy,
query.SortOrder,
)
if err != nil {
return nil, fmt.Errorf("failed to list tiers: %w", err)
}
// Convert to responses
responses := mappers.ToTierResponses(tiers)
// Calculate pagination info
totalPages := int((total + int64(query.Limit) - 1) / int64(query.Limit))
return &models.PaginatedResponse[models.TierResponse]{
Data: responses,
Pagination: models.Pagination{
Page: query.Page,
Limit: query.Limit,
Total: total,
TotalPages: totalPages,
},
}, nil
}
// UpdateTier updates an existing tier
func (p *TierProcessor) UpdateTier(ctx context.Context, id uuid.UUID, req *models.UpdateTierRequest) (*models.TierResponse, error) {
// Get existing tier
tier, err := p.tierRepo.GetByID(ctx, id)
if err != nil {
return nil, fmt.Errorf("tier not found: %w", err)
}
// Update tier fields
mappers.UpdateTierEntity(tier, req)
// Save updated tier
err = p.tierRepo.Update(ctx, tier)
if err != nil {
return nil, fmt.Errorf("failed to update tier: %w", err)
}
return mappers.ToTierResponse(tier), nil
}
// DeleteTier deletes a tier
func (p *TierProcessor) DeleteTier(ctx context.Context, id uuid.UUID) error {
// Get existing tier
_, err := p.tierRepo.GetByID(ctx, id)
if err != nil {
return fmt.Errorf("tier not found: %w", err)
}
// Delete tier
err = p.tierRepo.Delete(ctx, id)
if err != nil {
return fmt.Errorf("failed to delete tier: %w", err)
}
return nil
}
// GetTierByPoints gets the appropriate tier for a given point amount
func (p *TierProcessor) GetTierByPoints(ctx context.Context, points int64) (*models.TierResponse, error) {
tier, err := p.tierRepo.GetTierByPoints(ctx, points)
if err != nil {
return nil, fmt.Errorf("tier not found for points: %w", err)
}
return mappers.ToTierResponse(tier), nil
}

View File

@ -0,0 +1,117 @@
package repository
import (
"apskel-pos-be/internal/entities"
"context"
"fmt"
"github.com/google/uuid"
"gorm.io/gorm"
)
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
}
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) 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{}).
Where("customer_id = ?", customerID).
Update("balance", gorm.Expr("balance + ?", points)).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 newCustomerPoints, nil
}

View File

@ -0,0 +1,131 @@
package repository
import (
"apskel-pos-be/internal/entities"
"context"
"fmt"
"github.com/google/uuid"
"gorm.io/gorm"
)
type CustomerTokensRepository struct {
db *gorm.DB
}
func NewCustomerTokensRepository(db *gorm.DB) *CustomerTokensRepository {
return &CustomerTokensRepository{db: db}
}
func (r *CustomerTokensRepository) Create(ctx context.Context, customerTokens *entities.CustomerTokens) error {
return r.db.WithContext(ctx).Create(customerTokens).Error
}
func (r *CustomerTokensRepository) GetByID(ctx context.Context, id uuid.UUID) (*entities.CustomerTokens, error) {
var customerTokens entities.CustomerTokens
err := r.db.WithContext(ctx).Preload("Customer").Where("id = ?", id).First(&customerTokens).Error
if err != nil {
return nil, err
}
return &customerTokens, nil
}
func (r *CustomerTokensRepository) GetByCustomerIDAndType(ctx context.Context, customerID uuid.UUID, tokenType entities.TokenType) (*entities.CustomerTokens, error) {
var customerTokens entities.CustomerTokens
err := r.db.WithContext(ctx).Preload("Customer").Where("customer_id = ? AND token_type = ?", customerID, tokenType).First(&customerTokens).Error
if err != nil {
return nil, err
}
return &customerTokens, nil
}
func (r *CustomerTokensRepository) GetByCustomerID(ctx context.Context, customerID uuid.UUID) ([]entities.CustomerTokens, error) {
var customerTokens []entities.CustomerTokens
err := r.db.WithContext(ctx).Preload("Customer").Where("customer_id = ?", customerID).Find(&customerTokens).Error
if err != nil {
return nil, err
}
return customerTokens, nil
}
func (r *CustomerTokensRepository) List(ctx context.Context, offset, limit int, search, tokenType string, sortBy, sortOrder string) ([]entities.CustomerTokens, int64, error) {
var customerTokens []entities.CustomerTokens
var total int64
query := r.db.WithContext(ctx).Preload("Customer")
if search != "" {
searchTerm := "%" + search + "%"
query = query.Joins("JOIN customers ON customer_tokens.customer_id = customers.id").
Where("customers.name ILIKE ? OR customers.email ILIKE ?", searchTerm, searchTerm)
}
if tokenType != "" {
query = query.Where("token_type = ?", tokenType)
}
if err := query.Model(&entities.CustomerTokens{}).Count(&total).Error; err != nil {
return nil, 0, err
}
if sortBy != "" {
if sortOrder == "" {
sortOrder = "asc"
}
query = query.Order(fmt.Sprintf("customer_tokens.%s %s", sortBy, sortOrder))
} else {
query = query.Order("customer_tokens.created_at DESC")
}
err := query.Offset(offset).Limit(limit).Find(&customerTokens).Error
if err != nil {
return nil, 0, err
}
return customerTokens, total, nil
}
func (r *CustomerTokensRepository) Update(ctx context.Context, customerTokens *entities.CustomerTokens) error {
return r.db.WithContext(ctx).Save(customerTokens).Error
}
func (r *CustomerTokensRepository) Delete(ctx context.Context, id uuid.UUID) error {
return r.db.WithContext(ctx).Delete(&entities.CustomerTokens{}, id).Error
}
func (r *CustomerTokensRepository) AddTokens(ctx context.Context, customerID uuid.UUID, tokenType entities.TokenType, tokens int64) error {
return r.db.WithContext(ctx).Model(&entities.CustomerTokens{}).
Where("customer_id = ? AND token_type = ?", customerID, tokenType).
Update("balance", gorm.Expr("balance + ?", tokens)).Error
}
func (r *CustomerTokensRepository) DeductTokens(ctx context.Context, customerID uuid.UUID, tokenType entities.TokenType, tokens int64) error {
return r.db.WithContext(ctx).Model(&entities.CustomerTokens{}).
Where("customer_id = ? AND token_type = ? AND balance >= ?", customerID, tokenType, tokens).
Update("balance", gorm.Expr("balance - ?", tokens)).Error
}
func (r *CustomerTokensRepository) EnsureCustomerTokens(ctx context.Context, customerID uuid.UUID, tokenType entities.TokenType) (*entities.CustomerTokens, error) {
customerTokens, err := r.GetByCustomerIDAndType(ctx, customerID, tokenType)
if err == nil {
return customerTokens, nil
}
if err != gorm.ErrRecordNotFound {
return nil, err
}
// Create new customer tokens record
newCustomerTokens := &entities.CustomerTokens{
CustomerID: customerID,
TokenType: tokenType,
Balance: 0,
}
err = r.Create(ctx, newCustomerTokens)
if err != nil {
return nil, err
}
return newCustomerTokens, nil
}

View File

@ -0,0 +1,111 @@
package repository
import (
"apskel-pos-be/internal/entities"
"context"
"fmt"
"github.com/google/uuid"
"gorm.io/gorm"
)
type GamePlayRepository struct {
db *gorm.DB
}
func NewGamePlayRepository(db *gorm.DB) *GamePlayRepository {
return &GamePlayRepository{db: db}
}
func (r *GamePlayRepository) Create(ctx context.Context, gamePlay *entities.GamePlay) error {
return r.db.WithContext(ctx).Create(gamePlay).Error
}
func (r *GamePlayRepository) GetByID(ctx context.Context, id uuid.UUID) (*entities.GamePlay, error) {
var gamePlay entities.GamePlay
err := r.db.WithContext(ctx).Preload("Game").Preload("Customer").Preload("Prize").Where("id = ?", id).First(&gamePlay).Error
if err != nil {
return nil, err
}
return &gamePlay, nil
}
func (r *GamePlayRepository) List(ctx context.Context, offset, limit int, search string, gameID, customerID, prizeID *uuid.UUID, sortBy, sortOrder string) ([]entities.GamePlay, int64, error) {
var gamePlays []entities.GamePlay
var total int64
query := r.db.WithContext(ctx).Preload("Game").Preload("Customer").Preload("Prize")
if search != "" {
searchTerm := "%" + search + "%"
query = query.Joins("JOIN customers ON game_plays.customer_id = customers.id").
Where("customers.name ILIKE ? OR customers.email ILIKE ?", searchTerm, searchTerm)
}
if gameID != nil {
query = query.Where("game_id = ?", *gameID)
}
if customerID != nil {
query = query.Where("customer_id = ?", *customerID)
}
if prizeID != nil {
query = query.Where("prize_id = ?", *prizeID)
}
if err := query.Model(&entities.GamePlay{}).Count(&total).Error; err != nil {
return nil, 0, err
}
if sortBy != "" {
if sortOrder == "" {
sortOrder = "asc"
}
query = query.Order(fmt.Sprintf("game_plays.%s %s", sortBy, sortOrder))
} else {
query = query.Order("game_plays.created_at DESC")
}
err := query.Offset(offset).Limit(limit).Find(&gamePlays).Error
if err != nil {
return nil, 0, err
}
return gamePlays, total, nil
}
func (r *GamePlayRepository) Update(ctx context.Context, gamePlay *entities.GamePlay) error {
return r.db.WithContext(ctx).Save(gamePlay).Error
}
func (r *GamePlayRepository) Delete(ctx context.Context, id uuid.UUID) error {
return r.db.WithContext(ctx).Delete(&entities.GamePlay{}, id).Error
}
func (r *GamePlayRepository) GetCustomerPlays(ctx context.Context, customerID uuid.UUID, limit int) ([]entities.GamePlay, error) {
var gamePlays []entities.GamePlay
err := r.db.WithContext(ctx).Preload("Game").Preload("Prize").
Where("customer_id = ?", customerID).
Order("created_at DESC").
Limit(limit).
Find(&gamePlays).Error
if err != nil {
return nil, err
}
return gamePlays, nil
}
func (r *GamePlayRepository) GetGameStats(ctx context.Context, gameID uuid.UUID) (map[string]interface{}, error) {
var stats map[string]interface{}
err := r.db.WithContext(ctx).Model(&entities.GamePlay{}).
Select("COUNT(*) as total_plays, SUM(token_used) as total_tokens_used").
Where("game_id = ?", gameID).
Scan(&stats).Error
if err != nil {
return nil, err
}
return stats, nil
}

View File

@ -0,0 +1,102 @@
package repository
import (
"apskel-pos-be/internal/entities"
"context"
"fmt"
"github.com/google/uuid"
"gorm.io/gorm"
)
type GamePrizeRepository struct {
db *gorm.DB
}
func NewGamePrizeRepository(db *gorm.DB) *GamePrizeRepository {
return &GamePrizeRepository{db: db}
}
func (r *GamePrizeRepository) Create(ctx context.Context, gamePrize *entities.GamePrize) error {
return r.db.WithContext(ctx).Create(gamePrize).Error
}
func (r *GamePrizeRepository) GetByID(ctx context.Context, id uuid.UUID) (*entities.GamePrize, error) {
var gamePrize entities.GamePrize
err := r.db.WithContext(ctx).Preload("Game").Preload("FallbackPrize").Where("id = ?", id).First(&gamePrize).Error
if err != nil {
return nil, err
}
return &gamePrize, nil
}
func (r *GamePrizeRepository) GetByGameID(ctx context.Context, gameID uuid.UUID) ([]entities.GamePrize, error) {
var gamePrizes []entities.GamePrize
err := r.db.WithContext(ctx).Preload("Game").Preload("FallbackPrize").Where("game_id = ?", gameID).Find(&gamePrizes).Error
if err != nil {
return nil, err
}
return gamePrizes, nil
}
func (r *GamePrizeRepository) List(ctx context.Context, offset, limit int, search string, gameID *uuid.UUID, sortBy, sortOrder string) ([]entities.GamePrize, int64, error) {
var gamePrizes []entities.GamePrize
var total int64
query := r.db.WithContext(ctx).Preload("Game").Preload("FallbackPrize")
if search != "" {
searchTerm := "%" + search + "%"
query = query.Where("name ILIKE ?", searchTerm)
}
if gameID != nil {
query = query.Where("game_id = ?", *gameID)
}
if err := query.Model(&entities.GamePrize{}).Count(&total).Error; err != nil {
return nil, 0, err
}
if sortBy != "" {
if sortOrder == "" {
sortOrder = "asc"
}
query = query.Order(fmt.Sprintf("game_prizes.%s %s", sortBy, sortOrder))
} else {
query = query.Order("game_prizes.weight DESC")
}
err := query.Offset(offset).Limit(limit).Find(&gamePrizes).Error
if err != nil {
return nil, 0, err
}
return gamePrizes, total, nil
}
func (r *GamePrizeRepository) Update(ctx context.Context, gamePrize *entities.GamePrize) error {
return r.db.WithContext(ctx).Save(gamePrize).Error
}
func (r *GamePrizeRepository) Delete(ctx context.Context, id uuid.UUID) error {
return r.db.WithContext(ctx).Delete(&entities.GamePrize{}, id).Error
}
func (r *GamePrizeRepository) DecreaseStock(ctx context.Context, id uuid.UUID, amount int) error {
return r.db.WithContext(ctx).Model(&entities.GamePrize{}).
Where("id = ? AND stock >= ?", id, amount).
Update("stock", gorm.Expr("stock - ?", amount)).Error
}
func (r *GamePrizeRepository) GetAvailablePrizes(ctx context.Context, gameID uuid.UUID) ([]entities.GamePrize, error) {
var gamePrizes []entities.GamePrize
err := r.db.WithContext(ctx).Preload("Game").Preload("FallbackPrize").
Where("game_id = ? AND stock > 0", gameID).
Order("weight DESC").
Find(&gamePrizes).Error
if err != nil {
return nil, err
}
return gamePrizes, nil
}

View File

@ -0,0 +1,88 @@
package repository
import (
"apskel-pos-be/internal/entities"
"context"
"fmt"
"github.com/google/uuid"
"gorm.io/gorm"
)
type GameRepository struct {
db *gorm.DB
}
func NewGameRepository(db *gorm.DB) *GameRepository {
return &GameRepository{db: db}
}
func (r *GameRepository) Create(ctx context.Context, game *entities.Game) error {
return r.db.WithContext(ctx).Create(game).Error
}
func (r *GameRepository) GetByID(ctx context.Context, id uuid.UUID) (*entities.Game, error) {
var game entities.Game
err := r.db.WithContext(ctx).Preload("Prizes").Where("id = ?", id).First(&game).Error
if err != nil {
return nil, err
}
return &game, nil
}
func (r *GameRepository) List(ctx context.Context, offset, limit int, search, gameType string, isActive *bool, sortBy, sortOrder string) ([]entities.Game, int64, error) {
var games []entities.Game
var total int64
query := r.db.WithContext(ctx).Preload("Prizes")
if search != "" {
searchTerm := "%" + search + "%"
query = query.Where("name ILIKE ?", searchTerm)
}
if gameType != "" {
query = query.Where("type = ?", gameType)
}
if isActive != nil {
query = query.Where("is_active = ?", *isActive)
}
if err := query.Model(&entities.Game{}).Count(&total).Error; err != nil {
return nil, 0, err
}
if sortBy != "" {
if sortOrder == "" {
sortOrder = "asc"
}
query = query.Order(fmt.Sprintf("%s %s", sortBy, sortOrder))
} else {
query = query.Order("created_at DESC")
}
err := query.Offset(offset).Limit(limit).Find(&games).Error
if err != nil {
return nil, 0, err
}
return games, total, nil
}
func (r *GameRepository) Update(ctx context.Context, game *entities.Game) error {
return r.db.WithContext(ctx).Save(game).Error
}
func (r *GameRepository) Delete(ctx context.Context, id uuid.UUID) error {
return r.db.WithContext(ctx).Delete(&entities.Game{}, id).Error
}
func (r *GameRepository) GetActiveGames(ctx context.Context) ([]entities.Game, error) {
var games []entities.Game
err := r.db.WithContext(ctx).Preload("Prizes").Where("is_active = ?", true).Find(&games).Error
if err != nil {
return nil, err
}
return games, nil
}

View File

@ -0,0 +1,150 @@
package repository
import (
"apskel-pos-be/internal/entities"
"context"
"fmt"
"time"
"github.com/google/uuid"
"gorm.io/gorm"
)
type OmsetTrackerRepository struct {
db *gorm.DB
}
func NewOmsetTrackerRepository(db *gorm.DB) *OmsetTrackerRepository {
return &OmsetTrackerRepository{db: db}
}
func (r *OmsetTrackerRepository) Create(ctx context.Context, omsetTracker *entities.OmsetTracker) error {
return r.db.WithContext(ctx).Create(omsetTracker).Error
}
func (r *OmsetTrackerRepository) GetByID(ctx context.Context, id uuid.UUID) (*entities.OmsetTracker, error) {
var omsetTracker entities.OmsetTracker
err := r.db.WithContext(ctx).Preload("Game").Where("id = ?", id).First(&omsetTracker).Error
if err != nil {
return nil, err
}
return &omsetTracker, nil
}
func (r *OmsetTrackerRepository) List(ctx context.Context, offset, limit int, search, periodType string, gameID *uuid.UUID, from, to *time.Time, sortBy, sortOrder string) ([]entities.OmsetTracker, int64, error) {
var omsetTrackers []entities.OmsetTracker
var total int64
query := r.db.WithContext(ctx).Preload("Game")
if search != "" {
searchTerm := "%" + search + "%"
query = query.Joins("LEFT JOIN games ON omset_tracker.game_id = games.id").
Where("games.name ILIKE ?", searchTerm)
}
if periodType != "" {
query = query.Where("period_type = ?", periodType)
}
if gameID != nil {
query = query.Where("game_id = ?", *gameID)
}
if from != nil {
query = query.Where("period_start >= ?", *from)
}
if to != nil {
query = query.Where("period_end <= ?", *to)
}
if err := query.Model(&entities.OmsetTracker{}).Count(&total).Error; err != nil {
return nil, 0, err
}
if sortBy != "" {
if sortOrder == "" {
sortOrder = "asc"
}
query = query.Order(fmt.Sprintf("omset_tracker.%s %s", sortBy, sortOrder))
} else {
query = query.Order("omset_tracker.period_start DESC")
}
err := query.Offset(offset).Limit(limit).Find(&omsetTrackers).Error
if err != nil {
return nil, 0, err
}
return omsetTrackers, total, nil
}
func (r *OmsetTrackerRepository) Update(ctx context.Context, omsetTracker *entities.OmsetTracker) error {
return r.db.WithContext(ctx).Save(omsetTracker).Error
}
func (r *OmsetTrackerRepository) Delete(ctx context.Context, id uuid.UUID) error {
return r.db.WithContext(ctx).Delete(&entities.OmsetTracker{}, id).Error
}
func (r *OmsetTrackerRepository) AddOmset(ctx context.Context, periodType entities.PeriodType, periodStart, periodEnd time.Time, amount int64, gameID *uuid.UUID) error {
return r.db.WithContext(ctx).Model(&entities.OmsetTracker{}).
Where("period_type = ? AND period_start = ? AND period_end = ? AND game_id = ?", periodType, periodStart, periodEnd, gameID).
Update("total", gorm.Expr("total + ?", amount)).Error
}
func (r *OmsetTrackerRepository) GetOrCreatePeriod(ctx context.Context, periodType entities.PeriodType, periodStart, periodEnd time.Time, gameID *uuid.UUID) (*entities.OmsetTracker, error) {
var omsetTracker entities.OmsetTracker
err := r.db.WithContext(ctx).Preload("Game").
Where("period_type = ? AND period_start = ? AND period_end = ? AND game_id = ?", periodType, periodStart, periodEnd, gameID).
First(&omsetTracker).Error
if err == nil {
return &omsetTracker, nil
}
if err != gorm.ErrRecordNotFound {
return nil, err
}
// Create new period
newOmsetTracker := &entities.OmsetTracker{
PeriodType: periodType,
PeriodStart: periodStart,
PeriodEnd: periodEnd,
Total: 0,
GameID: gameID,
}
err = r.Create(ctx, newOmsetTracker)
if err != nil {
return nil, err
}
return newOmsetTracker, nil
}
func (r *OmsetTrackerRepository) GetPeriodSummary(ctx context.Context, periodType entities.PeriodType, from, to time.Time) (map[string]interface{}, error) {
var summary map[string]interface{}
query := r.db.WithContext(ctx).Model(&entities.OmsetTracker{}).
Select("SUM(total) as total_omset, COUNT(*) as period_count").
Where("period_type = ?", periodType)
if !from.IsZero() {
query = query.Where("period_start >= ?", from)
}
if !to.IsZero() {
query = query.Where("period_end <= ?", to)
}
err := query.Scan(&summary).Error
if err != nil {
return nil, err
}
return summary, nil
}

View File

@ -0,0 +1,89 @@
package repository
import (
"apskel-pos-be/internal/entities"
"context"
"fmt"
"github.com/google/uuid"
"gorm.io/gorm"
)
type TierRepository struct {
db *gorm.DB
}
func NewTierRepository(db *gorm.DB) *TierRepository {
return &TierRepository{db: db}
}
func (r *TierRepository) Create(ctx context.Context, tier *entities.Tier) error {
return r.db.WithContext(ctx).Create(tier).Error
}
func (r *TierRepository) GetByID(ctx context.Context, id uuid.UUID) (*entities.Tier, error) {
var tier entities.Tier
err := r.db.WithContext(ctx).Where("id = ?", id).First(&tier).Error
if err != nil {
return nil, err
}
return &tier, nil
}
func (r *TierRepository) GetByName(ctx context.Context, name string) (*entities.Tier, error) {
var tier entities.Tier
err := r.db.WithContext(ctx).Where("name = ?", name).First(&tier).Error
if err != nil {
return nil, err
}
return &tier, nil
}
func (r *TierRepository) List(ctx context.Context, offset, limit int, search string, sortBy, sortOrder string) ([]entities.Tier, int64, error) {
var tiers []entities.Tier
var total int64
query := r.db.WithContext(ctx)
if search != "" {
searchTerm := "%" + search + "%"
query = query.Where("name ILIKE ?", searchTerm)
}
if err := query.Model(&entities.Tier{}).Count(&total).Error; err != nil {
return nil, 0, err
}
if sortBy != "" {
if sortOrder == "" {
sortOrder = "asc"
}
query = query.Order(fmt.Sprintf("%s %s", sortBy, sortOrder))
} else {
query = query.Order("min_points ASC")
}
err := query.Offset(offset).Limit(limit).Find(&tiers).Error
if err != nil {
return nil, 0, err
}
return tiers, total, nil
}
func (r *TierRepository) Update(ctx context.Context, tier *entities.Tier) error {
return r.db.WithContext(ctx).Save(tier).Error
}
func (r *TierRepository) Delete(ctx context.Context, id uuid.UUID) error {
return r.db.WithContext(ctx).Delete(&entities.Tier{}, id).Error
}
func (r *TierRepository) GetTierByPoints(ctx context.Context, points int64) (*entities.Tier, error) {
var tier entities.Tier
err := r.db.WithContext(ctx).Where("min_points <= ?", points).Order("min_points DESC").First(&tier).Error
if err != nil {
return nil, err
}
return &tier, nil
}

View File

@ -40,6 +40,7 @@ type Router struct {
chartOfAccountHandler *handler.ChartOfAccountHandler
accountHandler *handler.AccountHandler
orderIngredientTransactionHandler *handler.OrderIngredientTransactionHandler
gamificationHandler *handler.GamificationHandler
authMiddleware *middleware.AuthMiddleware
}
@ -90,7 +91,9 @@ func NewRouter(cfg *config.Config,
accountService service.AccountService,
accountValidator validator.AccountValidator,
orderIngredientTransactionService service.OrderIngredientTransactionService,
orderIngredientTransactionValidator validator.OrderIngredientTransactionValidator) *Router {
orderIngredientTransactionValidator validator.OrderIngredientTransactionValidator,
gamificationService service.GamificationService,
gamificationValidator validator.GamificationValidator) *Router {
return &Router{
config: cfg,
@ -120,6 +123,7 @@ func NewRouter(cfg *config.Config,
chartOfAccountHandler: handler.NewChartOfAccountHandler(chartOfAccountService, chartOfAccountValidator),
accountHandler: handler.NewAccountHandler(accountService, accountValidator),
orderIngredientTransactionHandler: handler.NewOrderIngredientTransactionHandler(&orderIngredientTransactionService, orderIngredientTransactionValidator),
gamificationHandler: handler.NewGamificationHandler(gamificationService, gamificationValidator),
authMiddleware: authMiddleware,
}
}
@ -426,6 +430,88 @@ func (r *Router) addAppRoutes(rg *gin.Engine) {
orderIngredientTransactions.POST("/bulk", r.orderIngredientTransactionHandler.BulkCreateOrderIngredientTransactions)
}
gamification := protected.Group("/marketing")
gamification.Use(r.authMiddleware.RequireAdminOrManager())
{
//customerPoints := gamification.Group("/customer-points")
//{
// customerPoints.POST("", r.gamificationHandler.CreateCustomerPoints)
// customerPoints.GET("", r.gamificationHandler.ListCustomerPoints)
// customerPoints.GET("/:id", r.gamificationHandler.GetCustomerPoints)
// customerPoints.PUT("/:id", r.gamificationHandler.UpdateCustomerPoints)
// customerPoints.DELETE("/:id", r.gamificationHandler.DeleteCustomerPoints)
// customerPoints.GET("/customer/:customer_id", r.gamificationHandler.GetCustomerPointsByCustomerID)
// customerPoints.POST("/customer/:customer_id/add", r.gamificationHandler.AddCustomerPoints)
// customerPoints.POST("/customer/:customer_id/deduct", r.gamificationHandler.DeductCustomerPoints)
//}
// Customer Tokens
//customerTokens := gamification.Group("/customer-tokens")
//{
// customerTokens.POST("", r.gamificationHandler.CreateCustomerTokens)
// customerTokens.GET("", r.gamificationHandler.ListCustomerTokens)
// customerTokens.GET("/:id", r.gamificationHandler.GetCustomerTokens)
// customerTokens.PUT("/:id", r.gamificationHandler.UpdateCustomerTokens)
// customerTokens.DELETE("/:id", r.gamificationHandler.DeleteCustomerTokens)
// customerTokens.GET("/customer/:customer_id/type/:token_type", r.gamificationHandler.GetCustomerTokensByCustomerIDAndType)
// customerTokens.POST("/customer/:customer_id/type/:token_type/add", r.gamificationHandler.AddCustomerTokens)
// customerTokens.POST("/customer/:customer_id/type/:token_type/deduct", r.gamificationHandler.DeductCustomerTokens)
//}
// Tiers
tiers := gamification.Group("/tiers")
{
tiers.POST("", r.gamificationHandler.CreateTier)
tiers.GET("", r.gamificationHandler.ListTiers)
tiers.GET("/:id", r.gamificationHandler.GetTier)
tiers.PUT("/:id", r.gamificationHandler.UpdateTier)
tiers.DELETE("/:id", r.gamificationHandler.DeleteTier)
tiers.GET("/by-points/:points", r.gamificationHandler.GetTierByPoints)
}
// Games
games := gamification.Group("/games")
{
games.POST("", r.gamificationHandler.CreateGame)
games.GET("", r.gamificationHandler.ListGames)
games.GET("/active", r.gamificationHandler.GetActiveGames)
games.GET("/:id", r.gamificationHandler.GetGame)
games.PUT("/:id", r.gamificationHandler.UpdateGame)
games.DELETE("/:id", r.gamificationHandler.DeleteGame)
}
// Game Prizes
gamePrizes := gamification.Group("/game-prizes")
{
gamePrizes.POST("", r.gamificationHandler.CreateGamePrize)
gamePrizes.GET("", r.gamificationHandler.ListGamePrizes)
gamePrizes.GET("/:id", r.gamificationHandler.GetGamePrize)
gamePrizes.PUT("/:id", r.gamificationHandler.UpdateGamePrize)
gamePrizes.DELETE("/:id", r.gamificationHandler.DeleteGamePrize)
gamePrizes.GET("/game/:game_id", r.gamificationHandler.GetGamePrizesByGameID)
gamePrizes.GET("/game/:game_id/available", r.gamificationHandler.GetAvailablePrizes)
}
//// Game Plays
//gamePlays := gamification.Group("/game-plays")
//{
// gamePlays.POST("", r.gamificationHandler.CreateGamePlay)
// gamePlays.GET("", r.gamificationHandler.ListGamePlays)
// gamePlays.GET("/:id", r.gamificationHandler.GetGamePlay)
// gamePlays.POST("/play", r.gamificationHandler.PlayGame)
//}
// Omset Tracker
//omsetTracker := gamification.Group("/omset-tracker")
//{
// omsetTracker.POST("", r.gamificationHandler.CreateOmsetTracker)
// omsetTracker.GET("", r.gamificationHandler.ListOmsetTrackers)
// omsetTracker.GET("/:id", r.gamificationHandler.GetOmsetTracker)
// omsetTracker.PUT("/:id", r.gamificationHandler.UpdateOmsetTracker)
// omsetTracker.DELETE("/:id", r.gamificationHandler.DeleteOmsetTracker)
//}
}
outlets := protected.Group("/outlets")
outlets.Use(r.authMiddleware.RequireAdminOrManager())
{

View File

@ -0,0 +1,466 @@
package service
import (
"apskel-pos-be/internal/contract"
"apskel-pos-be/internal/processor"
"apskel-pos-be/internal/transformer"
"context"
"time"
"github.com/google/uuid"
)
type GamificationService interface {
// Customer Points
CreateCustomerPoints(ctx context.Context, req *contract.CreateCustomerPointsRequest) (*contract.CustomerPointsResponse, error)
GetCustomerPoints(ctx context.Context, id uuid.UUID) (*contract.CustomerPointsResponse, error)
GetCustomerPointsByCustomerID(ctx context.Context, customerID uuid.UUID) (*contract.CustomerPointsResponse, error)
ListCustomerPoints(ctx context.Context, query *contract.ListCustomerPointsRequest) (*contract.PaginatedCustomerPointsResponse, error)
UpdateCustomerPoints(ctx context.Context, id uuid.UUID, req *contract.UpdateCustomerPointsRequest) (*contract.CustomerPointsResponse, error)
DeleteCustomerPoints(ctx context.Context, id uuid.UUID) error
AddCustomerPoints(ctx context.Context, customerID uuid.UUID, req *contract.AddCustomerPointsRequest) (*contract.CustomerPointsResponse, error)
DeductCustomerPoints(ctx context.Context, customerID uuid.UUID, req *contract.DeductCustomerPointsRequest) (*contract.CustomerPointsResponse, error)
// Customer Tokens
CreateCustomerTokens(ctx context.Context, req *contract.CreateCustomerTokensRequest) (*contract.CustomerTokensResponse, error)
GetCustomerTokens(ctx context.Context, id uuid.UUID) (*contract.CustomerTokensResponse, error)
GetCustomerTokensByCustomerIDAndType(ctx context.Context, customerID uuid.UUID, tokenType string) (*contract.CustomerTokensResponse, error)
ListCustomerTokens(ctx context.Context, query *contract.ListCustomerTokensRequest) (*contract.PaginatedCustomerTokensResponse, error)
UpdateCustomerTokens(ctx context.Context, id uuid.UUID, req *contract.UpdateCustomerTokensRequest) (*contract.CustomerTokensResponse, error)
DeleteCustomerTokens(ctx context.Context, id uuid.UUID) error
AddCustomerTokens(ctx context.Context, customerID uuid.UUID, tokenType string, req *contract.AddCustomerTokensRequest) (*contract.CustomerTokensResponse, error)
DeductCustomerTokens(ctx context.Context, customerID uuid.UUID, tokenType string, req *contract.DeductCustomerTokensRequest) (*contract.CustomerTokensResponse, error)
// Tiers
CreateTier(ctx context.Context, req *contract.CreateTierRequest) (*contract.TierResponse, error)
GetTier(ctx context.Context, id uuid.UUID) (*contract.TierResponse, error)
ListTiers(ctx context.Context, query *contract.ListTiersRequest) (*contract.PaginatedTiersResponse, error)
UpdateTier(ctx context.Context, id uuid.UUID, req *contract.UpdateTierRequest) (*contract.TierResponse, error)
DeleteTier(ctx context.Context, id uuid.UUID) error
GetTierByPoints(ctx context.Context, points int64) (*contract.TierResponse, error)
// Games
CreateGame(ctx context.Context, req *contract.CreateGameRequest) (*contract.GameResponse, error)
GetGame(ctx context.Context, id uuid.UUID) (*contract.GameResponse, error)
ListGames(ctx context.Context, query *contract.ListGamesRequest) (*contract.PaginatedGamesResponse, error)
UpdateGame(ctx context.Context, id uuid.UUID, req *contract.UpdateGameRequest) (*contract.GameResponse, error)
DeleteGame(ctx context.Context, id uuid.UUID) error
GetActiveGames(ctx context.Context) ([]contract.GameResponse, error)
// Game Prizes
CreateGamePrize(ctx context.Context, req *contract.CreateGamePrizeRequest) (*contract.GamePrizeResponse, error)
GetGamePrize(ctx context.Context, id uuid.UUID) (*contract.GamePrizeResponse, error)
GetGamePrizesByGameID(ctx context.Context, gameID uuid.UUID) ([]contract.GamePrizeResponse, error)
ListGamePrizes(ctx context.Context, query *contract.ListGamePrizesRequest) (*contract.PaginatedGamePrizesResponse, error)
UpdateGamePrize(ctx context.Context, id uuid.UUID, req *contract.UpdateGamePrizeRequest) (*contract.GamePrizeResponse, error)
DeleteGamePrize(ctx context.Context, id uuid.UUID) error
GetAvailablePrizes(ctx context.Context, gameID uuid.UUID) ([]contract.GamePrizeResponse, error)
// Game Plays
CreateGamePlay(ctx context.Context, req *contract.CreateGamePlayRequest) (*contract.GamePlayResponse, error)
GetGamePlay(ctx context.Context, id uuid.UUID) (*contract.GamePlayResponse, error)
ListGamePlays(ctx context.Context, query *contract.ListGamePlaysRequest) (*contract.PaginatedGamePlaysResponse, error)
PlayGame(ctx context.Context, req *contract.PlayGameRequest) (*contract.PlayGameResponse, error)
// Omset Tracker
CreateOmsetTracker(ctx context.Context, req *contract.CreateOmsetTrackerRequest) (*contract.OmsetTrackerResponse, error)
GetOmsetTracker(ctx context.Context, id uuid.UUID) (*contract.OmsetTrackerResponse, error)
ListOmsetTrackers(ctx context.Context, query *contract.ListOmsetTrackerRequest) (*contract.PaginatedOmsetTrackerResponse, error)
UpdateOmsetTracker(ctx context.Context, id uuid.UUID, req *contract.UpdateOmsetTrackerRequest) (*contract.OmsetTrackerResponse, error)
DeleteOmsetTracker(ctx context.Context, id uuid.UUID) error
AddOmset(ctx context.Context, periodType string, periodStart, periodEnd time.Time, amount int64, gameID *uuid.UUID) (*contract.OmsetTrackerResponse, error)
}
type GamificationServiceImpl struct {
customerPointsProcessor *processor.CustomerPointsProcessor
customerTokensProcessor *processor.CustomerTokensProcessor
tierProcessor *processor.TierProcessor
gameProcessor *processor.GameProcessor
gamePrizeProcessor *processor.GamePrizeProcessor
gamePlayProcessor *processor.GamePlayProcessor
omsetTrackerProcessor *processor.OmsetTrackerProcessor
}
func NewGamificationService(
customerPointsProcessor *processor.CustomerPointsProcessor,
customerTokensProcessor *processor.CustomerTokensProcessor,
tierProcessor *processor.TierProcessor,
gameProcessor *processor.GameProcessor,
gamePrizeProcessor *processor.GamePrizeProcessor,
gamePlayProcessor *processor.GamePlayProcessor,
omsetTrackerProcessor *processor.OmsetTrackerProcessor,
) *GamificationServiceImpl {
return &GamificationServiceImpl{
customerPointsProcessor: customerPointsProcessor,
customerTokensProcessor: customerTokensProcessor,
tierProcessor: tierProcessor,
gameProcessor: gameProcessor,
gamePrizeProcessor: gamePrizeProcessor,
gamePlayProcessor: gamePlayProcessor,
omsetTrackerProcessor: omsetTrackerProcessor,
}
}
// Customer Points Service Methods
func (s *GamificationServiceImpl) CreateCustomerPoints(ctx context.Context, req *contract.CreateCustomerPointsRequest) (*contract.CustomerPointsResponse, error) {
modelReq := transformer.CreateCustomerPointsRequestToModel(req)
response, err := s.customerPointsProcessor.CreateCustomerPoints(ctx, modelReq)
if err != nil {
return nil, err
}
return transformer.CustomerPointsModelToResponse(response), nil
}
func (s *GamificationServiceImpl) GetCustomerPoints(ctx context.Context, id uuid.UUID) (*contract.CustomerPointsResponse, error) {
response, err := s.customerPointsProcessor.GetCustomerPoints(ctx, id)
if err != nil {
return nil, err
}
return transformer.CustomerPointsModelToResponse(response), nil
}
func (s *GamificationServiceImpl) GetCustomerPointsByCustomerID(ctx context.Context, customerID uuid.UUID) (*contract.CustomerPointsResponse, error) {
response, err := s.customerPointsProcessor.GetCustomerPointsByCustomerID(ctx, customerID)
if err != nil {
return nil, err
}
return transformer.CustomerPointsModelToResponse(response), nil
}
func (s *GamificationServiceImpl) ListCustomerPoints(ctx context.Context, query *contract.ListCustomerPointsRequest) (*contract.PaginatedCustomerPointsResponse, error) {
modelQuery := transformer.ListCustomerPointsRequestToModel(query)
response, err := s.customerPointsProcessor.ListCustomerPoints(ctx, modelQuery)
if err != nil {
return nil, err
}
return transformer.PaginatedCustomerPointsResponseToContract(response), nil
}
func (s *GamificationServiceImpl) UpdateCustomerPoints(ctx context.Context, id uuid.UUID, req *contract.UpdateCustomerPointsRequest) (*contract.CustomerPointsResponse, error) {
modelReq := transformer.UpdateCustomerPointsRequestToModel(req)
response, err := s.customerPointsProcessor.UpdateCustomerPoints(ctx, id, modelReq)
if err != nil {
return nil, err
}
return transformer.CustomerPointsModelToResponse(response), nil
}
func (s *GamificationServiceImpl) DeleteCustomerPoints(ctx context.Context, id uuid.UUID) error {
return s.customerPointsProcessor.DeleteCustomerPoints(ctx, id)
}
func (s *GamificationServiceImpl) AddCustomerPoints(ctx context.Context, customerID uuid.UUID, req *contract.AddCustomerPointsRequest) (*contract.CustomerPointsResponse, error) {
response, err := s.customerPointsProcessor.AddPoints(ctx, customerID, req.Points)
if err != nil {
return nil, err
}
return transformer.CustomerPointsModelToResponse(response), nil
}
func (s *GamificationServiceImpl) DeductCustomerPoints(ctx context.Context, customerID uuid.UUID, req *contract.DeductCustomerPointsRequest) (*contract.CustomerPointsResponse, error) {
response, err := s.customerPointsProcessor.DeductPoints(ctx, customerID, req.Points)
if err != nil {
return nil, err
}
return transformer.CustomerPointsModelToResponse(response), nil
}
// Customer Tokens Service Methods
func (s *GamificationServiceImpl) CreateCustomerTokens(ctx context.Context, req *contract.CreateCustomerTokensRequest) (*contract.CustomerTokensResponse, error) {
modelReq := transformer.CreateCustomerTokensRequestToModel(req)
response, err := s.customerTokensProcessor.CreateCustomerTokens(ctx, modelReq)
if err != nil {
return nil, err
}
return transformer.CustomerTokensModelToResponse(response), nil
}
func (s *GamificationServiceImpl) GetCustomerTokens(ctx context.Context, id uuid.UUID) (*contract.CustomerTokensResponse, error) {
response, err := s.customerTokensProcessor.GetCustomerTokens(ctx, id)
if err != nil {
return nil, err
}
return transformer.CustomerTokensModelToResponse(response), nil
}
func (s *GamificationServiceImpl) GetCustomerTokensByCustomerIDAndType(ctx context.Context, customerID uuid.UUID, tokenType string) (*contract.CustomerTokensResponse, error) {
response, err := s.customerTokensProcessor.GetCustomerTokensByCustomerIDAndType(ctx, customerID, tokenType)
if err != nil {
return nil, err
}
return transformer.CustomerTokensModelToResponse(response), nil
}
func (s *GamificationServiceImpl) ListCustomerTokens(ctx context.Context, query *contract.ListCustomerTokensRequest) (*contract.PaginatedCustomerTokensResponse, error) {
modelQuery := transformer.ListCustomerTokensRequestToModel(query)
response, err := s.customerTokensProcessor.ListCustomerTokens(ctx, modelQuery)
if err != nil {
return nil, err
}
return transformer.PaginatedCustomerTokensResponseToContract(response), nil
}
func (s *GamificationServiceImpl) UpdateCustomerTokens(ctx context.Context, id uuid.UUID, req *contract.UpdateCustomerTokensRequest) (*contract.CustomerTokensResponse, error) {
modelReq := transformer.UpdateCustomerTokensRequestToModel(req)
response, err := s.customerTokensProcessor.UpdateCustomerTokens(ctx, id, modelReq)
if err != nil {
return nil, err
}
return transformer.CustomerTokensModelToResponse(response), nil
}
func (s *GamificationServiceImpl) DeleteCustomerTokens(ctx context.Context, id uuid.UUID) error {
return s.customerTokensProcessor.DeleteCustomerTokens(ctx, id)
}
func (s *GamificationServiceImpl) AddCustomerTokens(ctx context.Context, customerID uuid.UUID, tokenType string, req *contract.AddCustomerTokensRequest) (*contract.CustomerTokensResponse, error) {
response, err := s.customerTokensProcessor.AddTokens(ctx, customerID, tokenType, req.Tokens)
if err != nil {
return nil, err
}
return transformer.CustomerTokensModelToResponse(response), nil
}
func (s *GamificationServiceImpl) DeductCustomerTokens(ctx context.Context, customerID uuid.UUID, tokenType string, req *contract.DeductCustomerTokensRequest) (*contract.CustomerTokensResponse, error) {
response, err := s.customerTokensProcessor.DeductTokens(ctx, customerID, tokenType, req.Tokens)
if err != nil {
return nil, err
}
return transformer.CustomerTokensModelToResponse(response), nil
}
// Tier Service Methods
func (s *GamificationServiceImpl) CreateTier(ctx context.Context, req *contract.CreateTierRequest) (*contract.TierResponse, error) {
modelReq := transformer.CreateTierRequestToModel(req)
response, err := s.tierProcessor.CreateTier(ctx, modelReq)
if err != nil {
return nil, err
}
return transformer.TierModelToResponse(response), nil
}
func (s *GamificationServiceImpl) GetTier(ctx context.Context, id uuid.UUID) (*contract.TierResponse, error) {
response, err := s.tierProcessor.GetTier(ctx, id)
if err != nil {
return nil, err
}
return transformer.TierModelToResponse(response), nil
}
func (s *GamificationServiceImpl) ListTiers(ctx context.Context, query *contract.ListTiersRequest) (*contract.PaginatedTiersResponse, error) {
modelQuery := transformer.ListTiersRequestToModel(query)
response, err := s.tierProcessor.ListTiers(ctx, modelQuery)
if err != nil {
return nil, err
}
return transformer.PaginatedTiersResponseToContract(response), nil
}
func (s *GamificationServiceImpl) UpdateTier(ctx context.Context, id uuid.UUID, req *contract.UpdateTierRequest) (*contract.TierResponse, error) {
modelReq := transformer.UpdateTierRequestToModel(req)
response, err := s.tierProcessor.UpdateTier(ctx, id, modelReq)
if err != nil {
return nil, err
}
return transformer.TierModelToResponse(response), nil
}
func (s *GamificationServiceImpl) DeleteTier(ctx context.Context, id uuid.UUID) error {
return s.tierProcessor.DeleteTier(ctx, id)
}
func (s *GamificationServiceImpl) GetTierByPoints(ctx context.Context, points int64) (*contract.TierResponse, error) {
response, err := s.tierProcessor.GetTierByPoints(ctx, points)
if err != nil {
return nil, err
}
return transformer.TierModelToResponse(response), nil
}
// Game Service Methods
func (s *GamificationServiceImpl) CreateGame(ctx context.Context, req *contract.CreateGameRequest) (*contract.GameResponse, error) {
modelReq := transformer.CreateGameRequestToModel(req)
response, err := s.gameProcessor.CreateGame(ctx, modelReq)
if err != nil {
return nil, err
}
return transformer.GameModelToResponse(response), nil
}
func (s *GamificationServiceImpl) GetGame(ctx context.Context, id uuid.UUID) (*contract.GameResponse, error) {
response, err := s.gameProcessor.GetGame(ctx, id)
if err != nil {
return nil, err
}
return transformer.GameModelToResponse(response), nil
}
func (s *GamificationServiceImpl) ListGames(ctx context.Context, query *contract.ListGamesRequest) (*contract.PaginatedGamesResponse, error) {
modelQuery := transformer.ListGamesRequestToModel(query)
response, err := s.gameProcessor.ListGames(ctx, modelQuery)
if err != nil {
return nil, err
}
return transformer.PaginatedGamesResponseToContract(response), nil
}
func (s *GamificationServiceImpl) UpdateGame(ctx context.Context, id uuid.UUID, req *contract.UpdateGameRequest) (*contract.GameResponse, error) {
modelReq := transformer.UpdateGameRequestToModel(req)
response, err := s.gameProcessor.UpdateGame(ctx, id, modelReq)
if err != nil {
return nil, err
}
return transformer.GameModelToResponse(response), nil
}
func (s *GamificationServiceImpl) DeleteGame(ctx context.Context, id uuid.UUID) error {
return s.gameProcessor.DeleteGame(ctx, id)
}
func (s *GamificationServiceImpl) GetActiveGames(ctx context.Context) ([]contract.GameResponse, error) {
response, err := s.gameProcessor.GetActiveGames(ctx)
if err != nil {
return nil, err
}
return transformer.GameModelsToResponses(response), nil
}
// Game Prize Service Methods
func (s *GamificationServiceImpl) CreateGamePrize(ctx context.Context, req *contract.CreateGamePrizeRequest) (*contract.GamePrizeResponse, error) {
modelReq := transformer.CreateGamePrizeRequestToModel(req)
response, err := s.gamePrizeProcessor.CreateGamePrize(ctx, modelReq)
if err != nil {
return nil, err
}
return transformer.GamePrizeModelToResponse(response), nil
}
func (s *GamificationServiceImpl) GetGamePrize(ctx context.Context, id uuid.UUID) (*contract.GamePrizeResponse, error) {
response, err := s.gamePrizeProcessor.GetGamePrize(ctx, id)
if err != nil {
return nil, err
}
return transformer.GamePrizeModelToResponse(response), nil
}
func (s *GamificationServiceImpl) GetGamePrizesByGameID(ctx context.Context, gameID uuid.UUID) ([]contract.GamePrizeResponse, error) {
response, err := s.gamePrizeProcessor.GetGamePrizesByGameID(ctx, gameID)
if err != nil {
return nil, err
}
return transformer.GamePrizeModelsToResponses(response), nil
}
func (s *GamificationServiceImpl) ListGamePrizes(ctx context.Context, query *contract.ListGamePrizesRequest) (*contract.PaginatedGamePrizesResponse, error) {
modelQuery := transformer.ListGamePrizesRequestToModel(query)
response, err := s.gamePrizeProcessor.ListGamePrizes(ctx, modelQuery)
if err != nil {
return nil, err
}
return transformer.PaginatedGamePrizesResponseToContract(response), nil
}
func (s *GamificationServiceImpl) UpdateGamePrize(ctx context.Context, id uuid.UUID, req *contract.UpdateGamePrizeRequest) (*contract.GamePrizeResponse, error) {
modelReq := transformer.UpdateGamePrizeRequestToModel(req)
response, err := s.gamePrizeProcessor.UpdateGamePrize(ctx, id, modelReq)
if err != nil {
return nil, err
}
return transformer.GamePrizeModelToResponse(response), nil
}
func (s *GamificationServiceImpl) DeleteGamePrize(ctx context.Context, id uuid.UUID) error {
return s.gamePrizeProcessor.DeleteGamePrize(ctx, id)
}
func (s *GamificationServiceImpl) GetAvailablePrizes(ctx context.Context, gameID uuid.UUID) ([]contract.GamePrizeResponse, error) {
response, err := s.gamePrizeProcessor.GetAvailablePrizes(ctx, gameID)
if err != nil {
return nil, err
}
return transformer.GamePrizeModelsToResponses(response), nil
}
// Game Play Service Methods
func (s *GamificationServiceImpl) CreateGamePlay(ctx context.Context, req *contract.CreateGamePlayRequest) (*contract.GamePlayResponse, error) {
modelReq := transformer.CreateGamePlayRequestToModel(req)
response, err := s.gamePlayProcessor.CreateGamePlay(ctx, modelReq)
if err != nil {
return nil, err
}
return transformer.GamePlayModelToResponse(response), nil
}
func (s *GamificationServiceImpl) GetGamePlay(ctx context.Context, id uuid.UUID) (*contract.GamePlayResponse, error) {
response, err := s.gamePlayProcessor.GetGamePlay(ctx, id)
if err != nil {
return nil, err
}
return transformer.GamePlayModelToResponse(response), nil
}
func (s *GamificationServiceImpl) ListGamePlays(ctx context.Context, query *contract.ListGamePlaysRequest) (*contract.PaginatedGamePlaysResponse, error) {
modelQuery := transformer.ListGamePlaysRequestToModel(query)
response, err := s.gamePlayProcessor.ListGamePlays(ctx, modelQuery)
if err != nil {
return nil, err
}
return transformer.PaginatedGamePlaysResponseToContract(response), nil
}
func (s *GamificationServiceImpl) PlayGame(ctx context.Context, req *contract.PlayGameRequest) (*contract.PlayGameResponse, error) {
modelReq := transformer.PlayGameRequestToModel(req)
response, err := s.gamePlayProcessor.PlayGame(ctx, modelReq)
if err != nil {
return nil, err
}
return transformer.PlayGameModelToResponse(response), nil
}
// Omset Tracker Service Methods
func (s *GamificationServiceImpl) CreateOmsetTracker(ctx context.Context, req *contract.CreateOmsetTrackerRequest) (*contract.OmsetTrackerResponse, error) {
modelReq := transformer.CreateOmsetTrackerRequestToModel(req)
response, err := s.omsetTrackerProcessor.CreateOmsetTracker(ctx, modelReq)
if err != nil {
return nil, err
}
return transformer.OmsetTrackerModelToResponse(response), nil
}
func (s *GamificationServiceImpl) GetOmsetTracker(ctx context.Context, id uuid.UUID) (*contract.OmsetTrackerResponse, error) {
response, err := s.omsetTrackerProcessor.GetOmsetTracker(ctx, id)
if err != nil {
return nil, err
}
return transformer.OmsetTrackerModelToResponse(response), nil
}
func (s *GamificationServiceImpl) ListOmsetTrackers(ctx context.Context, query *contract.ListOmsetTrackerRequest) (*contract.PaginatedOmsetTrackerResponse, error) {
modelQuery := transformer.ListOmsetTrackerRequestToModel(query)
response, err := s.omsetTrackerProcessor.ListOmsetTrackers(ctx, modelQuery)
if err != nil {
return nil, err
}
return transformer.PaginatedOmsetTrackerResponseToContract(response), nil
}
func (s *GamificationServiceImpl) UpdateOmsetTracker(ctx context.Context, id uuid.UUID, req *contract.UpdateOmsetTrackerRequest) (*contract.OmsetTrackerResponse, error) {
modelReq := transformer.UpdateOmsetTrackerRequestToModel(req)
response, err := s.omsetTrackerProcessor.UpdateOmsetTracker(ctx, id, modelReq)
if err != nil {
return nil, err
}
return transformer.OmsetTrackerModelToResponse(response), nil
}
func (s *GamificationServiceImpl) DeleteOmsetTracker(ctx context.Context, id uuid.UUID) error {
return s.omsetTrackerProcessor.DeleteOmsetTracker(ctx, id)
}
func (s *GamificationServiceImpl) AddOmset(ctx context.Context, periodType string, periodStart, periodEnd time.Time, amount int64, gameID *uuid.UUID) (*contract.OmsetTrackerResponse, error) {
response, err := s.omsetTrackerProcessor.AddOmset(ctx, periodType, periodStart, periodEnd, amount, gameID)
if err != nil {
return nil, err
}
return transformer.OmsetTrackerModelToResponse(response), nil
}

View File

@ -0,0 +1,439 @@
package transformer
import (
"apskel-pos-be/internal/contract"
"apskel-pos-be/internal/models"
)
// Customer Points Transformers
func CreateCustomerPointsRequestToModel(req *contract.CreateCustomerPointsRequest) *models.CreateCustomerPointsRequest {
return &models.CreateCustomerPointsRequest{
CustomerID: req.CustomerID,
Balance: req.Balance,
}
}
func UpdateCustomerPointsRequestToModel(req *contract.UpdateCustomerPointsRequest) *models.UpdateCustomerPointsRequest {
return &models.UpdateCustomerPointsRequest{
Balance: req.Balance,
}
}
func ListCustomerPointsRequestToModel(req *contract.ListCustomerPointsRequest) *models.ListCustomerPointsQuery {
return &models.ListCustomerPointsQuery{
Page: req.Page,
Limit: req.Limit,
Search: req.Search,
SortBy: req.SortBy,
SortOrder: req.SortOrder,
}
}
func CustomerPointsModelToResponse(model *models.CustomerPointsResponse) *contract.CustomerPointsResponse {
return &contract.CustomerPointsResponse{
ID: model.ID,
CustomerID: model.CustomerID,
Balance: model.Balance,
Customer: CustomerModelToResponse(model.Customer),
CreatedAt: model.CreatedAt,
UpdatedAt: model.UpdatedAt,
}
}
func PaginatedCustomerPointsResponseToContract(model *models.PaginatedResponse[models.CustomerPointsResponse]) *contract.PaginatedCustomerPointsResponse {
responses := make([]contract.CustomerPointsResponse, len(model.Data))
for i, item := range model.Data {
responses[i] = *CustomerPointsModelToResponse(&item)
}
return &contract.PaginatedCustomerPointsResponse{
Data: responses,
TotalCount: int(model.Pagination.Total),
Page: model.Pagination.Page,
Limit: model.Pagination.Limit,
TotalPages: model.Pagination.TotalPages,
}
}
// Customer Tokens Transformers
func CreateCustomerTokensRequestToModel(req *contract.CreateCustomerTokensRequest) *models.CreateCustomerTokensRequest {
return &models.CreateCustomerTokensRequest{
CustomerID: req.CustomerID,
TokenType: req.TokenType,
Balance: req.Balance,
}
}
func UpdateCustomerTokensRequestToModel(req *contract.UpdateCustomerTokensRequest) *models.UpdateCustomerTokensRequest {
return &models.UpdateCustomerTokensRequest{
Balance: req.Balance,
}
}
func ListCustomerTokensRequestToModel(req *contract.ListCustomerTokensRequest) *models.ListCustomerTokensQuery {
return &models.ListCustomerTokensQuery{
Page: req.Page,
Limit: req.Limit,
Search: req.Search,
TokenType: req.TokenType,
SortBy: req.SortBy,
SortOrder: req.SortOrder,
}
}
func CustomerTokensModelToResponse(model *models.CustomerTokensResponse) *contract.CustomerTokensResponse {
return &contract.CustomerTokensResponse{
ID: model.ID,
CustomerID: model.CustomerID,
TokenType: model.TokenType,
Balance: model.Balance,
Customer: CustomerModelToResponse(model.Customer),
CreatedAt: model.CreatedAt,
UpdatedAt: model.UpdatedAt,
}
}
func PaginatedCustomerTokensResponseToContract(model *models.PaginatedResponse[models.CustomerTokensResponse]) *contract.PaginatedCustomerTokensResponse {
responses := make([]contract.CustomerTokensResponse, len(model.Data))
for i, item := range model.Data {
responses[i] = *CustomerTokensModelToResponse(&item)
}
return &contract.PaginatedCustomerTokensResponse{
Data: responses,
TotalCount: int(model.Pagination.Total),
Page: model.Pagination.Page,
Limit: model.Pagination.Limit,
TotalPages: model.Pagination.TotalPages,
}
}
// Tier Transformers
func CreateTierRequestToModel(req *contract.CreateTierRequest) *models.CreateTierRequest {
return &models.CreateTierRequest{
Name: req.Name,
MinPoints: req.MinPoints,
Benefits: req.Benefits,
}
}
func UpdateTierRequestToModel(req *contract.UpdateTierRequest) *models.UpdateTierRequest {
return &models.UpdateTierRequest{
Name: req.Name,
MinPoints: req.MinPoints,
Benefits: req.Benefits,
}
}
func ListTiersRequestToModel(req *contract.ListTiersRequest) *models.ListTiersQuery {
return &models.ListTiersQuery{
Page: req.Page,
Limit: req.Limit,
Search: req.Search,
SortBy: req.SortBy,
SortOrder: req.SortOrder,
}
}
func TierModelToResponse(model *models.TierResponse) *contract.TierResponse {
return &contract.TierResponse{
ID: model.ID,
Name: model.Name,
MinPoints: model.MinPoints,
Benefits: model.Benefits,
CreatedAt: model.CreatedAt,
UpdatedAt: model.UpdatedAt,
}
}
func PaginatedTiersResponseToContract(model *models.PaginatedResponse[models.TierResponse]) *contract.PaginatedTiersResponse {
responses := make([]contract.TierResponse, len(model.Data))
for i, item := range model.Data {
responses[i] = *TierModelToResponse(&item)
}
return &contract.PaginatedTiersResponse{
Data: responses,
TotalCount: int(model.Pagination.Total),
Page: model.Pagination.Page,
Limit: model.Pagination.Limit,
TotalPages: model.Pagination.TotalPages,
}
}
// Game Transformers
func CreateGameRequestToModel(req *contract.CreateGameRequest) *models.CreateGameRequest {
return &models.CreateGameRequest{
Name: req.Name,
Type: req.Type,
IsActive: req.IsActive,
Metadata: req.Metadata,
}
}
func UpdateGameRequestToModel(req *contract.UpdateGameRequest) *models.UpdateGameRequest {
return &models.UpdateGameRequest{
Name: req.Name,
Type: req.Type,
IsActive: req.IsActive,
Metadata: req.Metadata,
}
}
func ListGamesRequestToModel(req *contract.ListGamesRequest) *models.ListGamesQuery {
return &models.ListGamesQuery{
Page: req.Page,
Limit: req.Limit,
Search: req.Search,
Type: req.Type,
IsActive: req.IsActive,
SortBy: req.SortBy,
SortOrder: req.SortOrder,
}
}
func GameModelToResponse(model *models.GameResponse) *contract.GameResponse {
return &contract.GameResponse{
ID: model.ID,
Name: model.Name,
Type: model.Type,
IsActive: model.IsActive,
Metadata: model.Metadata,
CreatedAt: model.CreatedAt,
UpdatedAt: model.UpdatedAt,
}
}
func GameModelsToResponses(models []models.GameResponse) []contract.GameResponse {
responses := make([]contract.GameResponse, len(models))
for i, model := range models {
responses[i] = *GameModelToResponse(&model)
}
return responses
}
func PaginatedGamesResponseToContract(model *models.PaginatedResponse[models.GameResponse]) *contract.PaginatedGamesResponse {
responses := make([]contract.GameResponse, len(model.Data))
for i, item := range model.Data {
responses[i] = *GameModelToResponse(&item)
}
return &contract.PaginatedGamesResponse{
Data: responses,
TotalCount: int(model.Pagination.Total),
Page: model.Pagination.Page,
Limit: model.Pagination.Limit,
TotalPages: model.Pagination.TotalPages,
}
}
// Game Prize Transformers
func CreateGamePrizeRequestToModel(req *contract.CreateGamePrizeRequest) *models.CreateGamePrizeRequest {
return &models.CreateGamePrizeRequest{
GameID: req.GameID,
Name: req.Name,
Weight: req.Weight,
Stock: req.Stock,
MaxStock: req.MaxStock,
Threshold: req.Threshold,
FallbackPrizeID: req.FallbackPrizeID,
Metadata: req.Metadata,
}
}
func UpdateGamePrizeRequestToModel(req *contract.UpdateGamePrizeRequest) *models.UpdateGamePrizeRequest {
return &models.UpdateGamePrizeRequest{
Name: req.Name,
Weight: req.Weight,
Stock: req.Stock,
MaxStock: req.MaxStock,
Threshold: req.Threshold,
FallbackPrizeID: req.FallbackPrizeID,
Metadata: req.Metadata,
}
}
func ListGamePrizesRequestToModel(req *contract.ListGamePrizesRequest) *models.ListGamePrizesQuery {
return &models.ListGamePrizesQuery{
Page: req.Page,
Limit: req.Limit,
Search: req.Search,
GameID: req.GameID,
SortBy: req.SortBy,
SortOrder: req.SortOrder,
}
}
func GamePrizeModelToResponse(model *models.GamePrizeResponse) *contract.GamePrizeResponse {
return &contract.GamePrizeResponse{
ID: model.ID,
GameID: model.GameID,
Name: model.Name,
Weight: model.Weight,
Stock: model.Stock,
MaxStock: model.MaxStock,
Threshold: model.Threshold,
FallbackPrizeID: model.FallbackPrizeID,
Metadata: model.Metadata,
Game: GameModelToResponse(model.Game),
FallbackPrize: GamePrizeModelToResponse(model.FallbackPrize),
CreatedAt: model.CreatedAt,
UpdatedAt: model.UpdatedAt,
}
}
func GamePrizeModelsToResponses(models []models.GamePrizeResponse) []contract.GamePrizeResponse {
responses := make([]contract.GamePrizeResponse, len(models))
for i, model := range models {
responses[i] = *GamePrizeModelToResponse(&model)
}
return responses
}
func PaginatedGamePrizesResponseToContract(model *models.PaginatedResponse[models.GamePrizeResponse]) *contract.PaginatedGamePrizesResponse {
responses := make([]contract.GamePrizeResponse, len(model.Data))
for i, item := range model.Data {
responses[i] = *GamePrizeModelToResponse(&item)
}
return &contract.PaginatedGamePrizesResponse{
Data: responses,
TotalCount: int(model.Pagination.Total),
Page: model.Pagination.Page,
Limit: model.Pagination.Limit,
TotalPages: model.Pagination.TotalPages,
}
}
// Game Play Transformers
func CreateGamePlayRequestToModel(req *contract.CreateGamePlayRequest) *models.CreateGamePlayRequest {
return &models.CreateGamePlayRequest{
GameID: req.GameID,
CustomerID: req.CustomerID,
TokenUsed: req.TokenUsed,
RandomSeed: req.RandomSeed,
}
}
func PlayGameRequestToModel(req *contract.PlayGameRequest) *models.PlayGameRequest {
return &models.PlayGameRequest{
GameID: req.GameID,
CustomerID: req.CustomerID,
TokenUsed: req.TokenUsed,
}
}
func ListGamePlaysRequestToModel(req *contract.ListGamePlaysRequest) *models.ListGamePlaysQuery {
return &models.ListGamePlaysQuery{
Page: req.Page,
Limit: req.Limit,
Search: req.Search,
GameID: req.GameID,
CustomerID: req.CustomerID,
PrizeID: req.PrizeID,
SortBy: req.SortBy,
SortOrder: req.SortOrder,
}
}
func GamePlayModelToResponse(model *models.GamePlayResponse) *contract.GamePlayResponse {
return &contract.GamePlayResponse{
ID: model.ID,
GameID: model.GameID,
CustomerID: model.CustomerID,
PrizeID: model.PrizeID,
TokenUsed: model.TokenUsed,
RandomSeed: model.RandomSeed,
CreatedAt: model.CreatedAt,
Game: GameModelToResponse(model.Game),
Customer: CustomerModelToResponse(model.Customer),
Prize: GamePrizeModelToResponse(model.Prize),
}
}
func PlayGameModelToResponse(model *models.PlayGameResponse) *contract.PlayGameResponse {
return &contract.PlayGameResponse{
GamePlay: *GamePlayModelToResponse(&model.GamePlay),
PrizeWon: GamePrizeModelToResponse(model.PrizeWon),
TokensRemaining: model.TokensRemaining,
}
}
func PaginatedGamePlaysResponseToContract(model *models.PaginatedResponse[models.GamePlayResponse]) *contract.PaginatedGamePlaysResponse {
responses := make([]contract.GamePlayResponse, len(model.Data))
for i, item := range model.Data {
responses[i] = *GamePlayModelToResponse(&item)
}
return &contract.PaginatedGamePlaysResponse{
Data: responses,
TotalCount: int(model.Pagination.Total),
Page: model.Pagination.Page,
Limit: model.Pagination.Limit,
TotalPages: model.Pagination.TotalPages,
}
}
// Omset Tracker Transformers
func CreateOmsetTrackerRequestToModel(req *contract.CreateOmsetTrackerRequest) *models.CreateOmsetTrackerRequest {
return &models.CreateOmsetTrackerRequest{
PeriodType: req.PeriodType,
PeriodStart: req.PeriodStart,
PeriodEnd: req.PeriodEnd,
Total: req.Total,
GameID: req.GameID,
}
}
func UpdateOmsetTrackerRequestToModel(req *contract.UpdateOmsetTrackerRequest) *models.UpdateOmsetTrackerRequest {
return &models.UpdateOmsetTrackerRequest{
PeriodType: req.PeriodType,
PeriodStart: req.PeriodStart,
PeriodEnd: req.PeriodEnd,
Total: req.Total,
GameID: req.GameID,
}
}
func ListOmsetTrackerRequestToModel(req *contract.ListOmsetTrackerRequest) *models.ListOmsetTrackerQuery {
return &models.ListOmsetTrackerQuery{
Page: req.Page,
Limit: req.Limit,
Search: req.Search,
PeriodType: req.PeriodType,
GameID: req.GameID,
From: req.From,
To: req.To,
SortBy: req.SortBy,
SortOrder: req.SortOrder,
}
}
func OmsetTrackerModelToResponse(model *models.OmsetTrackerResponse) *contract.OmsetTrackerResponse {
return &contract.OmsetTrackerResponse{
ID: model.ID,
PeriodType: model.PeriodType,
PeriodStart: model.PeriodStart,
PeriodEnd: model.PeriodEnd,
Total: model.Total,
GameID: model.GameID,
Game: GameModelToResponse(model.Game),
CreatedAt: model.CreatedAt,
UpdatedAt: model.UpdatedAt,
}
}
func PaginatedOmsetTrackerResponseToContract(model *models.PaginatedResponse[models.OmsetTrackerResponse]) *contract.PaginatedOmsetTrackerResponse {
responses := make([]contract.OmsetTrackerResponse, len(model.Data))
for i, item := range model.Data {
responses[i] = *OmsetTrackerModelToResponse(&item)
}
return &contract.PaginatedOmsetTrackerResponse{
Data: responses,
TotalCount: int(model.Pagination.Total),
Page: model.Pagination.Page,
Limit: model.Pagination.Limit,
TotalPages: model.Pagination.TotalPages,
}
}

View File

@ -0,0 +1,556 @@
package validator
import (
"apskel-pos-be/internal/contract"
"errors"
"strings"
"github.com/go-playground/validator/v10"
)
type GamificationValidator interface {
// Customer Points
ValidateCreateCustomerPointsRequest(req *contract.CreateCustomerPointsRequest) (error, string)
ValidateUpdateCustomerPointsRequest(req *contract.UpdateCustomerPointsRequest) (error, string)
ValidateListCustomerPointsRequest(req *contract.ListCustomerPointsRequest) (error, string)
ValidateAddCustomerPointsRequest(req *contract.AddCustomerPointsRequest) (error, string)
ValidateDeductCustomerPointsRequest(req *contract.DeductCustomerPointsRequest) (error, string)
// Customer Tokens
ValidateCreateCustomerTokensRequest(req *contract.CreateCustomerTokensRequest) (error, string)
ValidateUpdateCustomerTokensRequest(req *contract.UpdateCustomerTokensRequest) (error, string)
ValidateListCustomerTokensRequest(req *contract.ListCustomerTokensRequest) (error, string)
ValidateAddCustomerTokensRequest(req *contract.AddCustomerTokensRequest) (error, string)
ValidateDeductCustomerTokensRequest(req *contract.DeductCustomerTokensRequest) (error, string)
// Tiers
ValidateCreateTierRequest(req *contract.CreateTierRequest) (error, string)
ValidateUpdateTierRequest(req *contract.UpdateTierRequest) (error, string)
ValidateListTiersRequest(req *contract.ListTiersRequest) (error, string)
// Games
ValidateCreateGameRequest(req *contract.CreateGameRequest) (error, string)
ValidateUpdateGameRequest(req *contract.UpdateGameRequest) (error, string)
ValidateListGamesRequest(req *contract.ListGamesRequest) (error, string)
// Game Prizes
ValidateCreateGamePrizeRequest(req *contract.CreateGamePrizeRequest) (error, string)
ValidateUpdateGamePrizeRequest(req *contract.UpdateGamePrizeRequest) (error, string)
ValidateListGamePrizesRequest(req *contract.ListGamePrizesRequest) (error, string)
// Game Plays
ValidateCreateGamePlayRequest(req *contract.CreateGamePlayRequest) (error, string)
ValidateListGamePlaysRequest(req *contract.ListGamePlaysRequest) (error, string)
ValidatePlayGameRequest(req *contract.PlayGameRequest) (error, string)
// Omset Tracker
ValidateCreateOmsetTrackerRequest(req *contract.CreateOmsetTrackerRequest) (error, string)
ValidateUpdateOmsetTrackerRequest(req *contract.UpdateOmsetTrackerRequest) (error, string)
ValidateListOmsetTrackerRequest(req *contract.ListOmsetTrackerRequest) (error, string)
ValidateAddOmsetRequest(req *contract.AddOmsetRequest) (error, string)
}
type GamificationValidatorImpl struct {
validate *validator.Validate
}
func NewGamificationValidator() *GamificationValidatorImpl {
return &GamificationValidatorImpl{
validate: validator.New(),
}
}
// Customer Points Validators
func (v *GamificationValidatorImpl) ValidateCreateCustomerPointsRequest(req *contract.CreateCustomerPointsRequest) (error, string) {
if err := v.validate.Struct(req); err != nil {
return err, "VALIDATION_ERROR"
}
if req.Balance < 0 {
return errors.New("balance cannot be negative"), "INVALID_BALANCE"
}
return nil, ""
}
func (v *GamificationValidatorImpl) ValidateUpdateCustomerPointsRequest(req *contract.UpdateCustomerPointsRequest) (error, string) {
if err := v.validate.Struct(req); err != nil {
return err, "VALIDATION_ERROR"
}
if req.Balance < 0 {
return errors.New("balance cannot be negative"), "INVALID_BALANCE"
}
return nil, ""
}
func (v *GamificationValidatorImpl) ValidateListCustomerPointsRequest(req *contract.ListCustomerPointsRequest) (error, string) {
if err := v.validate.Struct(req); err != nil {
return err, "VALIDATION_ERROR"
}
if req.Page <= 0 {
req.Page = 1
}
if req.Limit <= 0 {
req.Limit = 10
}
if req.Limit > 100 {
req.Limit = 100
}
return nil, ""
}
func (v *GamificationValidatorImpl) ValidateAddCustomerPointsRequest(req *contract.AddCustomerPointsRequest) (error, string) {
if err := v.validate.Struct(req); err != nil {
return err, "VALIDATION_ERROR"
}
if req.Points <= 0 {
return errors.New("points must be greater than 0"), "INVALID_POINTS"
}
return nil, ""
}
func (v *GamificationValidatorImpl) ValidateDeductCustomerPointsRequest(req *contract.DeductCustomerPointsRequest) (error, string) {
if err := v.validate.Struct(req); err != nil {
return err, "VALIDATION_ERROR"
}
if req.Points <= 0 {
return errors.New("points must be greater than 0"), "INVALID_POINTS"
}
return nil, ""
}
// Customer Tokens Validators
func (v *GamificationValidatorImpl) ValidateCreateCustomerTokensRequest(req *contract.CreateCustomerTokensRequest) (error, string) {
if err := v.validate.Struct(req); err != nil {
return err, "VALIDATION_ERROR"
}
if req.Balance < 0 {
return errors.New("balance cannot be negative"), "INVALID_BALANCE"
}
validTokenTypes := []string{"SPIN", "RAFFLE", "MINIGAME"}
if !contains(validTokenTypes, req.TokenType) {
return errors.New("invalid token type"), "INVALID_TOKEN_TYPE"
}
return nil, ""
}
func (v *GamificationValidatorImpl) ValidateUpdateCustomerTokensRequest(req *contract.UpdateCustomerTokensRequest) (error, string) {
if err := v.validate.Struct(req); err != nil {
return err, "VALIDATION_ERROR"
}
if req.Balance < 0 {
return errors.New("balance cannot be negative"), "INVALID_BALANCE"
}
return nil, ""
}
func (v *GamificationValidatorImpl) ValidateListCustomerTokensRequest(req *contract.ListCustomerTokensRequest) (error, string) {
if err := v.validate.Struct(req); err != nil {
return err, "VALIDATION_ERROR"
}
if req.Page <= 0 {
req.Page = 1
}
if req.Limit <= 0 {
req.Limit = 10
}
if req.Limit > 100 {
req.Limit = 100
}
if req.TokenType != "" {
validTokenTypes := []string{"SPIN", "RAFFLE", "MINIGAME"}
if !contains(validTokenTypes, req.TokenType) {
return errors.New("invalid token type"), "INVALID_TOKEN_TYPE"
}
}
return nil, ""
}
func (v *GamificationValidatorImpl) ValidateAddCustomerTokensRequest(req *contract.AddCustomerTokensRequest) (error, string) {
if err := v.validate.Struct(req); err != nil {
return err, "VALIDATION_ERROR"
}
if req.Tokens <= 0 {
return errors.New("tokens must be greater than 0"), "INVALID_TOKENS"
}
return nil, ""
}
func (v *GamificationValidatorImpl) ValidateDeductCustomerTokensRequest(req *contract.DeductCustomerTokensRequest) (error, string) {
if err := v.validate.Struct(req); err != nil {
return err, "VALIDATION_ERROR"
}
if req.Tokens <= 0 {
return errors.New("tokens must be greater than 0"), "INVALID_TOKENS"
}
return nil, ""
}
// Tier Validators
func (v *GamificationValidatorImpl) ValidateCreateTierRequest(req *contract.CreateTierRequest) (error, string) {
if err := v.validate.Struct(req); err != nil {
return err, "VALIDATION_ERROR"
}
if req.Name != "" {
req.Name = strings.TrimSpace(req.Name)
if req.Name == "" {
return errors.New("name cannot be empty or whitespace only"), "INVALID_NAME"
}
if len(req.Name) > 100 {
return errors.New("name cannot exceed 100 characters"), "INVALID_NAME"
}
}
if req.MinPoints < 0 {
return errors.New("min points cannot be negative"), "INVALID_MIN_POINTS"
}
return nil, ""
}
func (v *GamificationValidatorImpl) ValidateUpdateTierRequest(req *contract.UpdateTierRequest) (error, string) {
if err := v.validate.Struct(req); err != nil {
return err, "VALIDATION_ERROR"
}
if req.Name != nil && *req.Name != "" {
*req.Name = strings.TrimSpace(*req.Name)
if *req.Name == "" {
return errors.New("name cannot be empty or whitespace only"), "INVALID_NAME"
}
if len(*req.Name) > 100 {
return errors.New("name cannot exceed 100 characters"), "INVALID_NAME"
}
}
if req.MinPoints != nil && *req.MinPoints < 0 {
return errors.New("min points cannot be negative"), "INVALID_MIN_POINTS"
}
return nil, ""
}
func (v *GamificationValidatorImpl) ValidateListTiersRequest(req *contract.ListTiersRequest) (error, string) {
if err := v.validate.Struct(req); err != nil {
return err, "VALIDATION_ERROR"
}
if req.Page <= 0 {
req.Page = 1
}
if req.Limit <= 0 {
req.Limit = 10
}
if req.Limit > 100 {
req.Limit = 100
}
return nil, ""
}
// Game Validators
func (v *GamificationValidatorImpl) ValidateCreateGameRequest(req *contract.CreateGameRequest) (error, string) {
if err := v.validate.Struct(req); err != nil {
return err, "VALIDATION_ERROR"
}
if req.Name != "" {
req.Name = strings.TrimSpace(req.Name)
if req.Name == "" {
return errors.New("name cannot be empty or whitespace only"), "INVALID_NAME"
}
if len(req.Name) > 255 {
return errors.New("name cannot exceed 255 characters"), "INVALID_NAME"
}
}
validGameTypes := []string{"SPIN", "RAFFLE", "MINIGAME"}
if !contains(validGameTypes, req.Type) {
return errors.New("invalid game type"), "INVALID_GAME_TYPE"
}
return nil, ""
}
func (v *GamificationValidatorImpl) ValidateUpdateGameRequest(req *contract.UpdateGameRequest) (error, string) {
if err := v.validate.Struct(req); err != nil {
return err, "VALIDATION_ERROR"
}
if req.Name != nil && *req.Name != "" {
*req.Name = strings.TrimSpace(*req.Name)
if *req.Name == "" {
return errors.New("name cannot be empty or whitespace only"), "INVALID_NAME"
}
if len(*req.Name) > 255 {
return errors.New("name cannot exceed 255 characters"), "INVALID_NAME"
}
}
if req.Type != nil {
validGameTypes := []string{"SPIN", "RAFFLE", "MINIGAME"}
if !contains(validGameTypes, *req.Type) {
return errors.New("invalid game type"), "INVALID_GAME_TYPE"
}
}
return nil, ""
}
func (v *GamificationValidatorImpl) ValidateListGamesRequest(req *contract.ListGamesRequest) (error, string) {
if err := v.validate.Struct(req); err != nil {
return err, "VALIDATION_ERROR"
}
if req.Page <= 0 {
req.Page = 1
}
if req.Limit <= 0 {
req.Limit = 10
}
if req.Limit > 100 {
req.Limit = 100
}
if req.Type != "" {
validGameTypes := []string{"SPIN", "RAFFLE", "MINIGAME"}
if !contains(validGameTypes, req.Type) {
return errors.New("invalid game type"), "INVALID_GAME_TYPE"
}
}
return nil, ""
}
// Game Prize Validators
func (v *GamificationValidatorImpl) ValidateCreateGamePrizeRequest(req *contract.CreateGamePrizeRequest) (error, string) {
if err := v.validate.Struct(req); err != nil {
return err, "VALIDATION_ERROR"
}
if req.Name != "" {
req.Name = strings.TrimSpace(req.Name)
if req.Name == "" {
return errors.New("name cannot be empty or whitespace only"), "INVALID_NAME"
}
if len(req.Name) > 255 {
return errors.New("name cannot exceed 255 characters"), "INVALID_NAME"
}
}
if req.Weight <= 0 {
return errors.New("weight must be greater than 0"), "INVALID_WEIGHT"
}
if req.Stock < 0 {
return errors.New("stock cannot be negative"), "INVALID_STOCK"
}
if req.MaxStock != nil && *req.MaxStock <= 0 {
return errors.New("max stock must be greater than 0"), "INVALID_MAX_STOCK"
}
if req.Threshold != nil && *req.Threshold < 0 {
return errors.New("threshold cannot be negative"), "INVALID_THRESHOLD"
}
return nil, ""
}
func (v *GamificationValidatorImpl) ValidateUpdateGamePrizeRequest(req *contract.UpdateGamePrizeRequest) (error, string) {
if err := v.validate.Struct(req); err != nil {
return err, "VALIDATION_ERROR"
}
if req.Name != nil && *req.Name != "" {
*req.Name = strings.TrimSpace(*req.Name)
if *req.Name == "" {
return errors.New("name cannot be empty or whitespace only"), "INVALID_NAME"
}
if len(*req.Name) > 255 {
return errors.New("name cannot exceed 255 characters"), "INVALID_NAME"
}
}
if req.Weight != nil && *req.Weight <= 0 {
return errors.New("weight must be greater than 0"), "INVALID_WEIGHT"
}
if req.Stock != nil && *req.Stock < 0 {
return errors.New("stock cannot be negative"), "INVALID_STOCK"
}
if req.MaxStock != nil && *req.MaxStock <= 0 {
return errors.New("max stock must be greater than 0"), "INVALID_MAX_STOCK"
}
if req.Threshold != nil && *req.Threshold < 0 {
return errors.New("threshold cannot be negative"), "INVALID_THRESHOLD"
}
return nil, ""
}
func (v *GamificationValidatorImpl) ValidateListGamePrizesRequest(req *contract.ListGamePrizesRequest) (error, string) {
if err := v.validate.Struct(req); err != nil {
return err, "VALIDATION_ERROR"
}
if req.Page <= 0 {
req.Page = 1
}
if req.Limit <= 0 {
req.Limit = 10
}
if req.Limit > 100 {
req.Limit = 100
}
return nil, ""
}
// Game Play Validators
func (v *GamificationValidatorImpl) ValidateCreateGamePlayRequest(req *contract.CreateGamePlayRequest) (error, string) {
if err := v.validate.Struct(req); err != nil {
return err, "VALIDATION_ERROR"
}
if req.TokenUsed < 0 {
return errors.New("token used cannot be negative"), "INVALID_TOKEN_USED"
}
return nil, ""
}
func (v *GamificationValidatorImpl) ValidateListGamePlaysRequest(req *contract.ListGamePlaysRequest) (error, string) {
if err := v.validate.Struct(req); err != nil {
return err, "VALIDATION_ERROR"
}
if req.Page <= 0 {
req.Page = 1
}
if req.Limit <= 0 {
req.Limit = 10
}
if req.Limit > 100 {
req.Limit = 100
}
return nil, ""
}
func (v *GamificationValidatorImpl) ValidatePlayGameRequest(req *contract.PlayGameRequest) (error, string) {
if err := v.validate.Struct(req); err != nil {
return err, "VALIDATION_ERROR"
}
if req.TokenUsed < 0 {
return errors.New("token used cannot be negative"), "INVALID_TOKEN_USED"
}
return nil, ""
}
// Omset Tracker Validators
func (v *GamificationValidatorImpl) ValidateCreateOmsetTrackerRequest(req *contract.CreateOmsetTrackerRequest) (error, string) {
if err := v.validate.Struct(req); err != nil {
return err, "VALIDATION_ERROR"
}
validPeriodTypes := []string{"DAILY", "WEEKLY", "MONTHLY", "TOTAL"}
if !contains(validPeriodTypes, req.PeriodType) {
return errors.New("invalid period type"), "INVALID_PERIOD_TYPE"
}
if req.Total < 0 {
return errors.New("total cannot be negative"), "INVALID_TOTAL"
}
if req.PeriodEnd.Before(req.PeriodStart) {
return errors.New("period end must be after period start"), "INVALID_PERIOD"
}
return nil, ""
}
func (v *GamificationValidatorImpl) ValidateUpdateOmsetTrackerRequest(req *contract.UpdateOmsetTrackerRequest) (error, string) {
if err := v.validate.Struct(req); err != nil {
return err, "VALIDATION_ERROR"
}
if req.PeriodType != nil {
validPeriodTypes := []string{"DAILY", "WEEKLY", "MONTHLY", "TOTAL"}
if !contains(validPeriodTypes, *req.PeriodType) {
return errors.New("invalid period type"), "INVALID_PERIOD_TYPE"
}
}
if req.Total != nil && *req.Total < 0 {
return errors.New("total cannot be negative"), "INVALID_TOTAL"
}
if req.PeriodStart != nil && req.PeriodEnd != nil && req.PeriodEnd.Before(*req.PeriodStart) {
return errors.New("period end must be after period start"), "INVALID_PERIOD"
}
return nil, ""
}
func (v *GamificationValidatorImpl) ValidateListOmsetTrackerRequest(req *contract.ListOmsetTrackerRequest) (error, string) {
if err := v.validate.Struct(req); err != nil {
return err, "VALIDATION_ERROR"
}
if req.Page <= 0 {
req.Page = 1
}
if req.Limit <= 0 {
req.Limit = 10
}
if req.Limit > 100 {
req.Limit = 100
}
if req.PeriodType != "" {
validPeriodTypes := []string{"DAILY", "WEEKLY", "MONTHLY", "TOTAL"}
if !contains(validPeriodTypes, req.PeriodType) {
return errors.New("invalid period type"), "INVALID_PERIOD_TYPE"
}
}
return nil, ""
}
func (v *GamificationValidatorImpl) ValidateAddOmsetRequest(req *contract.AddOmsetRequest) (error, string) {
if err := v.validate.Struct(req); err != nil {
return err, "VALIDATION_ERROR"
}
if req.Amount <= 0 {
return errors.New("amount must be greater than 0"), "INVALID_AMOUNT"
}
return nil, ""
}

View File

@ -0,0 +1 @@
DROP TABLE IF EXISTS customer_points;

View File

@ -0,0 +1,22 @@
CREATE TABLE customer_points (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
customer_id UUID NOT NULL,
balance BIGINT DEFAULT 0 NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
CONSTRAINT fk_customer_points_customer
FOREIGN KEY (customer_id)
REFERENCES customers(id)
ON DELETE CASCADE,
CONSTRAINT chk_customer_points_balance_non_negative
CHECK (balance >= 0)
);
-- Create indexes
CREATE INDEX idx_customer_points_customer_id ON customer_points(customer_id);
CREATE INDEX idx_customer_points_updated_at ON customer_points(updated_at);
-- Create unique constraint to ensure one point record per customer
CREATE UNIQUE INDEX idx_customer_points_unique_customer ON customer_points(customer_id);

View File

@ -0,0 +1 @@
DROP TABLE IF EXISTS customer_tokens;

View File

@ -0,0 +1,25 @@
CREATE TABLE customer_tokens (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
customer_id UUID NOT NULL,
token_type VARCHAR(50) NOT NULL,
balance BIGINT DEFAULT 0 NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
CONSTRAINT fk_customer_tokens_customer
FOREIGN KEY (customer_id)
REFERENCES customers(id)
ON DELETE CASCADE,
CONSTRAINT chk_customer_tokens_balance_non_negative
CHECK (balance >= 0),
CONSTRAINT chk_customer_tokens_type_valid
CHECK (token_type IN ('SPIN', 'RAFFLE', 'MINIGAME'))
);
CREATE INDEX idx_customer_tokens_customer_id ON customer_tokens(customer_id);
CREATE INDEX idx_customer_tokens_token_type ON customer_tokens(token_type);
CREATE INDEX idx_customer_tokens_updated_at ON customer_tokens(updated_at);
CREATE UNIQUE INDEX idx_customer_tokens_unique_customer_type ON customer_tokens(customer_id, token_type);

View File

@ -0,0 +1 @@
DROP TABLE IF EXISTS tiers;

View File

@ -0,0 +1,14 @@
CREATE TABLE tiers (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(100) NOT NULL UNIQUE,
min_points BIGINT NOT NULL,
benefits JSONB DEFAULT '{}',
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
CONSTRAINT chk_tiers_min_points_non_negative
CHECK (min_points >= 0)
);
CREATE INDEX idx_tiers_min_points ON tiers(min_points);
CREATE INDEX idx_tiers_created_at ON tiers(created_at);

View File

@ -0,0 +1 @@
DROP TABLE IF EXISTS games;

View File

@ -0,0 +1,17 @@
CREATE TABLE games (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(255) NOT NULL,
type VARCHAR(50) NOT NULL,
is_active BOOLEAN DEFAULT TRUE,
metadata JSONB DEFAULT '{}',
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
CONSTRAINT chk_games_type_valid
CHECK (type IN ('SPIN', 'RAFFLE', 'MINIGAME'))
);
-- Create indexes
CREATE INDEX idx_games_type ON games(type);
CREATE INDEX idx_games_is_active ON games(is_active);
CREATE INDEX idx_games_created_at ON games(created_at);

View File

@ -0,0 +1 @@
DROP TABLE IF EXISTS game_prizes;

View File

@ -0,0 +1,45 @@
CREATE TABLE game_prizes (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
game_id UUID NOT NULL,
name VARCHAR(255) NOT NULL,
weight INTEGER NOT NULL,
stock INTEGER DEFAULT 0,
max_stock INTEGER,
threshold BIGINT,
fallback_prize_id UUID,
metadata JSONB DEFAULT '{}',
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
CONSTRAINT fk_game_prizes_game
FOREIGN KEY (game_id)
REFERENCES games(id)
ON DELETE CASCADE,
CONSTRAINT fk_game_prizes_fallback
FOREIGN KEY (fallback_prize_id)
REFERENCES game_prizes(id)
ON DELETE SET NULL,
CONSTRAINT chk_game_prizes_weight_positive
CHECK (weight > 0),
CONSTRAINT chk_game_prizes_stock_non_negative
CHECK (stock >= 0),
CONSTRAINT chk_game_prizes_max_stock_positive
CHECK (max_stock IS NULL OR max_stock > 0),
CONSTRAINT chk_game_prizes_threshold_non_negative
CHECK (threshold IS NULL OR threshold >= 0),
CONSTRAINT chk_game_prizes_stock_not_exceed_max
CHECK (max_stock IS NULL OR stock <= max_stock)
);
-- Create indexes
CREATE INDEX idx_game_prizes_game_id ON game_prizes(game_id);
CREATE INDEX idx_game_prizes_weight ON game_prizes(weight);
CREATE INDEX idx_game_prizes_stock ON game_prizes(stock);
CREATE INDEX idx_game_prizes_fallback_prize_id ON game_prizes(fallback_prize_id);
CREATE INDEX idx_game_prizes_created_at ON game_prizes(created_at);

View File

@ -0,0 +1 @@
DROP TABLE IF EXISTS game_plays;

View File

@ -0,0 +1,34 @@
CREATE TABLE game_plays (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
game_id UUID NOT NULL,
customer_id UUID NOT NULL,
prize_id UUID,
token_used INTEGER DEFAULT 0,
random_seed VARCHAR(255),
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
CONSTRAINT fk_game_plays_game
FOREIGN KEY (game_id)
REFERENCES games(id)
ON DELETE CASCADE,
CONSTRAINT fk_game_plays_customer
FOREIGN KEY (customer_id)
REFERENCES customers(id)
ON DELETE CASCADE,
CONSTRAINT fk_game_plays_prize
FOREIGN KEY (prize_id)
REFERENCES game_prizes(id)
ON DELETE SET NULL,
CONSTRAINT chk_game_plays_token_used_non_negative
CHECK (token_used >= 0)
);
-- Create indexes
CREATE INDEX idx_game_plays_game_id ON game_plays(game_id);
CREATE INDEX idx_game_plays_customer_id ON game_plays(customer_id);
CREATE INDEX idx_game_plays_prize_id ON game_plays(prize_id);
CREATE INDEX idx_game_plays_created_at ON game_plays(created_at);
CREATE INDEX idx_game_plays_game_customer ON game_plays(game_id, customer_id);

View File

@ -0,0 +1 @@
DROP TABLE IF EXISTS omset_tracker;

View File

@ -0,0 +1,32 @@
CREATE TABLE omset_tracker (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
period_type VARCHAR(20) NOT NULL,
period_start DATE NOT NULL,
period_end DATE NOT NULL,
total BIGINT DEFAULT 0 NOT NULL,
game_id UUID,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
CONSTRAINT fk_omset_tracker_game
FOREIGN KEY (game_id)
REFERENCES games(id)
ON DELETE SET NULL,
CONSTRAINT chk_omset_tracker_period_type_valid
CHECK (period_type IN ('DAILY', 'WEEKLY', 'MONTHLY', 'TOTAL')),
CONSTRAINT chk_omset_tracker_total_non_negative
CHECK (total >= 0),
CONSTRAINT chk_omset_tracker_period_valid
CHECK (period_end >= period_start)
);
-- Create indexes
CREATE INDEX idx_omset_tracker_period_type ON omset_tracker(period_type);
CREATE INDEX idx_omset_tracker_period_start ON omset_tracker(period_start);
CREATE INDEX idx_omset_tracker_game_id ON omset_tracker(game_id);
CREATE INDEX idx_omset_tracker_created_at ON omset_tracker(created_at);
CREATE INDEX idx_omset_tracker_period_type_start ON omset_tracker(period_type, period_start);
CREATE INDEX idx_omset_tracker_game_period ON omset_tracker(game_id, period_type, period_start);

BIN
server

Binary file not shown.