fix campaign rules
This commit is contained in:
parent
c7828a5cad
commit
a520d0ed11
@ -103,6 +103,7 @@ func (a *App) Initialize(cfg *config.Config) error {
|
||||
services.customerAuthService,
|
||||
validators.customerAuthValidator,
|
||||
services.customerPointsService,
|
||||
services.spinGameService,
|
||||
middleware.customerAuthMiddleware,
|
||||
)
|
||||
|
||||
@ -181,10 +182,11 @@ type repositories struct {
|
||||
tierRepo *repository.TierRepository
|
||||
gameRepo *repository.GameRepository
|
||||
gamePrizeRepo *repository.GamePrizeRepository
|
||||
gamePlayRepo *repository.GamePlayRepository
|
||||
gamePlayRepo repository.GamePlayRepository
|
||||
omsetTrackerRepo *repository.OmsetTrackerRepository
|
||||
rewardRepo repository.RewardRepository
|
||||
campaignRepo repository.CampaignRepository
|
||||
campaignRuleRepo repository.CampaignRuleRepository
|
||||
customerAuthRepo repository.CustomerAuthRepository
|
||||
customerPointsRepo repository.CustomerPointsRepository
|
||||
otpRepo repository.OtpRepository
|
||||
@ -229,6 +231,7 @@ func (a *App) initRepositories() *repositories {
|
||||
omsetTrackerRepo: repository.NewOmsetTrackerRepository(a.db),
|
||||
rewardRepo: repository.NewRewardRepository(a.db),
|
||||
campaignRepo: repository.NewCampaignRepository(a.db),
|
||||
campaignRuleRepo: repository.NewCampaignRuleRepository(a.db),
|
||||
customerAuthRepo: repository.NewCustomerAuthRepository(a.db),
|
||||
customerPointsRepo: repository.NewCustomerPointsRepository(a.db),
|
||||
otpRepo: repository.NewOtpRepository(a.db),
|
||||
@ -269,6 +272,7 @@ type processors struct {
|
||||
omsetTrackerProcessor *processor.OmsetTrackerProcessor
|
||||
rewardProcessor processor.RewardProcessor
|
||||
campaignProcessor processor.CampaignProcessor
|
||||
campaignRuleProcessor processor.CampaignRuleProcessor
|
||||
customerAuthProcessor processor.CustomerAuthProcessor
|
||||
customerPointsProcessor *processor.CustomerPointsProcessor
|
||||
otpProcessor processor.OtpProcessor
|
||||
@ -315,6 +319,7 @@ func (a *App) initProcessors(cfg *config.Config, repos *repositories) *processor
|
||||
omsetTrackerProcessor: processor.NewOmsetTrackerProcessor(repos.omsetTrackerRepo),
|
||||
rewardProcessor: processor.NewRewardProcessor(repos.rewardRepo),
|
||||
campaignProcessor: processor.NewCampaignProcessor(repos.campaignRepo),
|
||||
campaignRuleProcessor: processor.NewCampaignRuleProcessor(repos.campaignRuleRepo),
|
||||
customerAuthProcessor: processor.NewCustomerAuthProcessor(repos.customerAuthRepo, otpProcessor, repos.otpRepo, cfg.GetCustomerJWTSecret(), cfg.GetCustomerJWTExpiresTTL()),
|
||||
customerPointsProcessor: processor.NewCustomerPointsProcessor(repos.customerPointsRepo, repos.gameRepo),
|
||||
otpProcessor: otpProcessor,
|
||||
@ -355,6 +360,7 @@ type services struct {
|
||||
campaignService service.CampaignService
|
||||
customerAuthService service.CustomerAuthService
|
||||
customerPointsService service.CustomerPointsService
|
||||
spinGameService service.SpinGameService
|
||||
}
|
||||
|
||||
func (a *App) initServices(processors *processors, repos *repositories, cfg *config.Config) *services {
|
||||
@ -387,9 +393,10 @@ func (a *App) initServices(processors *processors, repos *repositories, cfg *con
|
||||
orderIngredientTransactionService := service.NewOrderIngredientTransactionService(processors.orderIngredientTransactionProcessor, repos.txManager)
|
||||
gamificationService := service.NewGamificationService(processors.customerPointsProcessor, processors.customerTokensProcessor, processors.tierProcessor, processors.gameProcessor, processors.gamePrizeProcessor, processors.gamePlayProcessor, processors.omsetTrackerProcessor)
|
||||
rewardService := service.NewRewardService(processors.rewardProcessor)
|
||||
campaignService := service.NewCampaignService(processors.campaignProcessor)
|
||||
campaignService := service.NewCampaignService(processors.campaignProcessor, processors.campaignRuleProcessor)
|
||||
customerAuthService := service.NewCustomerAuthService(processors.customerAuthProcessor)
|
||||
customerPointsService := service.NewCustomerPointsService(processors.customerPointsProcessor)
|
||||
spinGameService := service.NewSpinGameService(processors.gamePlayProcessor, repos.txManager)
|
||||
|
||||
// Update order service with order ingredient transaction service
|
||||
orderService = service.NewOrderServiceImpl(processors.orderProcessor, repos.tableRepo, orderIngredientTransactionService, processors.orderIngredientTransactionProcessor, *repos.productRecipeRepo, repos.txManager)
|
||||
@ -426,6 +433,7 @@ func (a *App) initServices(processors *processors, repos *repositories, cfg *con
|
||||
campaignService: campaignService,
|
||||
customerAuthService: customerAuthService,
|
||||
customerPointsService: customerPointsService,
|
||||
spinGameService: spinGameService,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -54,6 +54,7 @@ const (
|
||||
CampaignEntity = "campaign"
|
||||
CampaignRuleEntity = "campaign_rule"
|
||||
CustomerEntity = "customer"
|
||||
SpinGameHandlerEntity = "spin_game_handler"
|
||||
)
|
||||
|
||||
var HttpErrorMap = map[string]int{
|
||||
|
||||
@ -17,7 +17,6 @@ type CreateCampaignRequest struct {
|
||||
ShowOnApp bool `json:"show_on_app"`
|
||||
Position int `json:"position" binding:"min=0"`
|
||||
Metadata *map[string]interface{} `json:"metadata,omitempty"`
|
||||
Rules []CampaignRuleStruct `json:"rules" binding:"required,min=1"`
|
||||
}
|
||||
|
||||
type UpdateCampaignRequest struct {
|
||||
@ -31,7 +30,6 @@ type UpdateCampaignRequest struct {
|
||||
ShowOnApp bool `json:"show_on_app"`
|
||||
Position int `json:"position" binding:"min=0"`
|
||||
Metadata *map[string]interface{} `json:"metadata,omitempty"`
|
||||
Rules []CampaignRuleStruct `json:"rules" binding:"required,min=1"`
|
||||
}
|
||||
|
||||
type ListCampaignsRequest struct {
|
||||
|
||||
20
internal/contract/spin_game_contract.go
Normal file
20
internal/contract/spin_game_contract.go
Normal file
@ -0,0 +1,20 @@
|
||||
package contract
|
||||
|
||||
// SpinGameRequest represents the request to play a spin game
|
||||
type SpinGameRequest struct {
|
||||
SpinID string `json:"spin_id" validate:"required,uuid"`
|
||||
}
|
||||
|
||||
// SpinGameResponse represents the response from playing a spin game
|
||||
type SpinGameResponse struct {
|
||||
Status string `json:"status"`
|
||||
Message string `json:"message"`
|
||||
Data *SpinGameResponseData `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// SpinGameResponseData contains the game play result
|
||||
type SpinGameResponseData struct {
|
||||
GamePlay GamePlayResponse `json:"game_play"`
|
||||
PrizeWon *CustomerGamePrizeResponse `json:"prize_won,omitempty"`
|
||||
TokensRemaining int64 `json:"tokens_remaining"`
|
||||
}
|
||||
@ -177,3 +177,131 @@ func (h *CampaignHandler) GetCampaignsForApp(c *gin.Context) {
|
||||
|
||||
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "CampaignHandler::GetCampaignsForApp")
|
||||
}
|
||||
|
||||
// Campaign Rules Handlers
|
||||
|
||||
func (h *CampaignHandler) CreateCampaignRule(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
|
||||
var req contract.CreateCampaignRuleRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
logger.FromContext(c.Request.Context()).WithError(err).Error("CampaignHandler::CreateCampaignRule -> request binding failed")
|
||||
validationResponseError := contract.NewResponseError(constants.MissingFieldErrorCode, constants.RequestEntity, err.Error())
|
||||
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "CampaignHandler::CreateCampaignRule")
|
||||
return
|
||||
}
|
||||
|
||||
response, err := h.campaignService.CreateCampaignRule(ctx, &req)
|
||||
if err != nil {
|
||||
logger.FromContext(c.Request.Context()).WithError(err).Error("CampaignHandler::CreateCampaignRule -> service call failed")
|
||||
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError(constants.InternalServerErrorCode, constants.CampaignRuleEntity, err.Error())}), "CampaignHandler::CreateCampaignRule")
|
||||
return
|
||||
}
|
||||
|
||||
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "CampaignHandler::CreateCampaignRule")
|
||||
}
|
||||
|
||||
func (h *CampaignHandler) GetCampaignRule(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
|
||||
idStr := c.Param("id")
|
||||
if idStr == "" {
|
||||
logger.FromContext(c.Request.Context()).Error("CampaignHandler::GetCampaignRule -> missing ID parameter")
|
||||
validationResponseError := contract.NewResponseError(constants.MissingFieldErrorCode, constants.CampaignRuleEntity, "ID parameter is required")
|
||||
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "CampaignHandler::GetCampaignRule")
|
||||
return
|
||||
}
|
||||
|
||||
response, err := h.campaignService.GetCampaignRule(ctx, idStr)
|
||||
if err != nil {
|
||||
logger.FromContext(c.Request.Context()).WithError(err).Error("CampaignHandler::GetCampaignRule -> service call failed")
|
||||
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError(constants.InternalServerErrorCode, constants.CampaignRuleEntity, err.Error())}), "CampaignHandler::GetCampaignRule")
|
||||
return
|
||||
}
|
||||
|
||||
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "CampaignHandler::GetCampaignRule")
|
||||
}
|
||||
|
||||
func (h *CampaignHandler) ListCampaignRules(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
|
||||
var req contract.ListCampaignRulesRequest
|
||||
if err := c.ShouldBindQuery(&req); err != nil {
|
||||
logger.FromContext(c.Request.Context()).WithError(err).Error("CampaignHandler::ListCampaignRules -> request binding failed")
|
||||
validationResponseError := contract.NewResponseError(constants.MissingFieldErrorCode, constants.RequestEntity, err.Error())
|
||||
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "CampaignHandler::ListCampaignRules")
|
||||
return
|
||||
}
|
||||
|
||||
response, err := h.campaignService.ListCampaignRules(ctx, &req)
|
||||
if err != nil {
|
||||
logger.FromContext(c.Request.Context()).WithError(err).Error("CampaignHandler::ListCampaignRules -> service call failed")
|
||||
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError(constants.InternalServerErrorCode, constants.CampaignRuleEntity, err.Error())}), "CampaignHandler::ListCampaignRules")
|
||||
return
|
||||
}
|
||||
|
||||
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "CampaignHandler::ListCampaignRules")
|
||||
}
|
||||
|
||||
func (h *CampaignHandler) UpdateCampaignRule(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
|
||||
var req contract.UpdateCampaignRuleRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
logger.FromContext(c.Request.Context()).WithError(err).Error("CampaignHandler::UpdateCampaignRule -> request binding failed")
|
||||
validationResponseError := contract.NewResponseError(constants.MissingFieldErrorCode, constants.RequestEntity, err.Error())
|
||||
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "CampaignHandler::UpdateCampaignRule")
|
||||
return
|
||||
}
|
||||
|
||||
response, err := h.campaignService.UpdateCampaignRule(ctx, &req)
|
||||
if err != nil {
|
||||
logger.FromContext(c.Request.Context()).WithError(err).Error("CampaignHandler::UpdateCampaignRule -> service call failed")
|
||||
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError(constants.InternalServerErrorCode, constants.CampaignRuleEntity, err.Error())}), "CampaignHandler::UpdateCampaignRule")
|
||||
return
|
||||
}
|
||||
|
||||
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "CampaignHandler::UpdateCampaignRule")
|
||||
}
|
||||
|
||||
func (h *CampaignHandler) DeleteCampaignRule(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
|
||||
idStr := c.Param("id")
|
||||
if idStr == "" {
|
||||
logger.FromContext(c.Request.Context()).Error("CampaignHandler::DeleteCampaignRule -> missing ID parameter")
|
||||
validationResponseError := contract.NewResponseError(constants.MissingFieldErrorCode, constants.CampaignRuleEntity, "ID parameter is required")
|
||||
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "CampaignHandler::DeleteCampaignRule")
|
||||
return
|
||||
}
|
||||
|
||||
err := h.campaignService.DeleteCampaignRule(ctx, idStr)
|
||||
if err != nil {
|
||||
logger.FromContext(c.Request.Context()).WithError(err).Error("CampaignHandler::DeleteCampaignRule -> service call failed")
|
||||
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError(constants.InternalServerErrorCode, constants.CampaignRuleEntity, err.Error())}), "CampaignHandler::DeleteCampaignRule")
|
||||
return
|
||||
}
|
||||
|
||||
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse("Campaign rule deleted successfully"), "CampaignHandler::DeleteCampaignRule")
|
||||
}
|
||||
|
||||
func (h *CampaignHandler) GetCampaignRulesByCampaignID(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
|
||||
campaignIDStr := c.Param("campaign_id")
|
||||
if campaignIDStr == "" {
|
||||
logger.FromContext(c.Request.Context()).Error("CampaignHandler::GetCampaignRulesByCampaignID -> missing campaign_id parameter")
|
||||
validationResponseError := contract.NewResponseError(constants.MissingFieldErrorCode, constants.CampaignRuleEntity, "campaign_id parameter is required")
|
||||
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "CampaignHandler::GetCampaignRulesByCampaignID")
|
||||
return
|
||||
}
|
||||
|
||||
response, err := h.campaignService.GetCampaignRulesByCampaignID(ctx, campaignIDStr)
|
||||
if err != nil {
|
||||
logger.FromContext(c.Request.Context()).WithError(err).Error("CampaignHandler::GetCampaignRulesByCampaignID -> service call failed")
|
||||
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError(constants.InternalServerErrorCode, constants.CampaignRuleEntity, err.Error())}), "CampaignHandler::GetCampaignRulesByCampaignID")
|
||||
return
|
||||
}
|
||||
|
||||
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "CampaignHandler::GetCampaignRulesByCampaignID")
|
||||
}
|
||||
|
||||
62
internal/handler/spin_game_handler.go
Normal file
62
internal/handler/spin_game_handler.go
Normal file
@ -0,0 +1,62 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"apskel-pos-be/internal/constants"
|
||||
"apskel-pos-be/internal/contract"
|
||||
"apskel-pos-be/internal/mappers"
|
||||
"apskel-pos-be/internal/models"
|
||||
"apskel-pos-be/internal/service"
|
||||
"apskel-pos-be/internal/util"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type SpinGameHandler struct {
|
||||
spinGameService service.SpinGameService
|
||||
}
|
||||
|
||||
func NewSpinGameHandler(spinGameService service.SpinGameService) *SpinGameHandler {
|
||||
return &SpinGameHandler{
|
||||
spinGameService: spinGameService,
|
||||
}
|
||||
}
|
||||
|
||||
// PlaySpinGame handles the spin game play request
|
||||
func (h *SpinGameHandler) PlaySpinGame(c *gin.Context) {
|
||||
// Get customer ID from JWT token (set by middleware)
|
||||
customerID, exists := c.Get("customer_id")
|
||||
if !exists {
|
||||
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{
|
||||
contract.NewResponseError(constants.ValidationErrorCode, constants.SpinGameHandlerEntity, "Customer ID not found in context"),
|
||||
}), "SpinGameHandler::PlaySpinGame")
|
||||
return
|
||||
}
|
||||
|
||||
// Parse spin ID from request body
|
||||
var req contract.SpinGameRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{
|
||||
contract.NewResponseError(constants.ValidationErrorCode, constants.SpinGameHandlerEntity, err.Error()),
|
||||
}), "SpinGameHandler::PlaySpinGame")
|
||||
return
|
||||
}
|
||||
|
||||
// Convert to model
|
||||
modelReq := &models.SpinGameRequest{
|
||||
SpinID: req.SpinID,
|
||||
}
|
||||
|
||||
// Play the spin game
|
||||
response, err := h.spinGameService.PlaySpinGame(c.Request.Context(), customerID.(string), modelReq)
|
||||
if err != nil {
|
||||
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{
|
||||
contract.NewResponseError(constants.InternalServerErrorCode, constants.SpinGameHandlerEntity, err.Error()),
|
||||
}), "SpinGameHandler::PlaySpinGame")
|
||||
return
|
||||
}
|
||||
|
||||
// Convert model response to contract response
|
||||
contractResponse := mappers.SpinGameResponseModelToContract(response)
|
||||
|
||||
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(contractResponse.Data), "SpinGameHandler::PlaySpinGame")
|
||||
}
|
||||
@ -68,14 +68,6 @@ func ToCampaignEntity(request *contract.CreateCampaignRequest) *entities.Campaig
|
||||
return nil
|
||||
}
|
||||
|
||||
var rules []entities.CampaignRule
|
||||
if request.Rules != nil {
|
||||
for _, rule := range request.Rules {
|
||||
// For create request, we'll use a temporary UUID that will be set by the repository
|
||||
rules = append(rules, *ToCampaignRuleEntity(&rule, uuid.Nil))
|
||||
}
|
||||
}
|
||||
|
||||
return &entities.Campaign{
|
||||
Name: request.Name,
|
||||
Description: request.Description,
|
||||
@ -86,7 +78,7 @@ func ToCampaignEntity(request *contract.CreateCampaignRequest) *entities.Campaig
|
||||
ShowOnApp: request.ShowOnApp,
|
||||
Position: request.Position,
|
||||
Metadata: request.Metadata,
|
||||
Rules: rules,
|
||||
Rules: nil, // Rules will be managed separately
|
||||
}
|
||||
}
|
||||
|
||||
@ -95,13 +87,6 @@ func ToCampaignEntityFromUpdate(request *contract.UpdateCampaignRequest) *entiti
|
||||
return nil
|
||||
}
|
||||
|
||||
var rules []entities.CampaignRule
|
||||
if request.Rules != nil {
|
||||
for _, rule := range request.Rules {
|
||||
rules = append(rules, *ToCampaignRuleEntityFromUpdate(&rule, request.ID))
|
||||
}
|
||||
}
|
||||
|
||||
return &entities.Campaign{
|
||||
ID: request.ID,
|
||||
Name: request.Name,
|
||||
@ -113,7 +98,7 @@ func ToCampaignEntityFromUpdate(request *contract.UpdateCampaignRequest) *entiti
|
||||
ShowOnApp: request.ShowOnApp,
|
||||
Position: request.Position,
|
||||
Metadata: request.Metadata,
|
||||
Rules: rules,
|
||||
Rules: nil, // Rules will be managed separately
|
||||
}
|
||||
}
|
||||
|
||||
@ -129,7 +114,7 @@ func ToCampaignRuleEntity(request *contract.CampaignRuleStruct, campaignID uuid.
|
||||
}
|
||||
|
||||
return &entities.CampaignRule{
|
||||
ID: uuid.New(), // Generate unique UUID for each campaign rule
|
||||
ID: uuid.Nil, // Let GORM's BeforeCreate hook generate the UUID
|
||||
CampaignID: campaignID,
|
||||
RuleType: entities.RuleType(request.RuleType),
|
||||
ConditionValue: request.ConditionValue,
|
||||
@ -153,7 +138,7 @@ func ToCampaignRuleEntityFromUpdate(request *contract.CampaignRuleStruct, campai
|
||||
}
|
||||
|
||||
return &entities.CampaignRule{
|
||||
ID: uuid.New(), // Generate new UUID for each campaign rule
|
||||
ID: uuid.Nil, // Let GORM's BeforeCreate hook generate the UUID
|
||||
CampaignID: campaignID,
|
||||
RuleType: entities.RuleType(request.RuleType),
|
||||
ConditionValue: request.ConditionValue,
|
||||
|
||||
@ -34,6 +34,15 @@ func ToGamePlayResponses(gamePlays []entities.GamePlay) []models.GamePlayRespons
|
||||
return responses
|
||||
}
|
||||
|
||||
// ToGamePlayResponsesFromPointers converts a slice of game play entity pointers to game play responses
|
||||
func ToGamePlayResponsesFromPointers(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{
|
||||
|
||||
80
internal/mappers/spin_game_mapper.go
Normal file
80
internal/mappers/spin_game_mapper.go
Normal file
@ -0,0 +1,80 @@
|
||||
package mappers
|
||||
|
||||
import (
|
||||
"apskel-pos-be/internal/contract"
|
||||
"apskel-pos-be/internal/models"
|
||||
)
|
||||
|
||||
// SpinGameRequestContractToModel converts spin game request from contract to model
|
||||
func SpinGameRequestContractToModel(req *contract.SpinGameRequest) *models.SpinGameRequest {
|
||||
if req == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &models.SpinGameRequest{
|
||||
SpinID: req.SpinID,
|
||||
}
|
||||
}
|
||||
|
||||
// SpinGameResponseModelToContract converts spin game response from model to contract
|
||||
func SpinGameResponseModelToContract(resp *models.SpinGameResponse) *contract.SpinGameResponse {
|
||||
if resp == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &contract.SpinGameResponse{
|
||||
Status: resp.Status,
|
||||
Message: resp.Message,
|
||||
Data: SpinGameResponseDataModelToContract(resp.Data),
|
||||
}
|
||||
}
|
||||
|
||||
// SpinGameResponseDataModelToContract converts spin game response data from model to contract
|
||||
func SpinGameResponseDataModelToContract(data *models.SpinGameResponseData) *contract.SpinGameResponseData {
|
||||
if data == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &contract.SpinGameResponseData{
|
||||
GamePlay: GamePlayResponseModelToContract(&data.GamePlay),
|
||||
PrizeWon: CustomerGamePrizeResponseModelToContract(data.PrizeWon),
|
||||
TokensRemaining: data.TokensRemaining,
|
||||
}
|
||||
}
|
||||
|
||||
// GamePlayResponseModelToContract converts game play response from model to contract
|
||||
func GamePlayResponseModelToContract(resp *models.GamePlayResponse) contract.GamePlayResponse {
|
||||
if resp == nil {
|
||||
return contract.GamePlayResponse{}
|
||||
}
|
||||
|
||||
return contract.GamePlayResponse{
|
||||
ID: resp.ID,
|
||||
GameID: resp.GameID,
|
||||
CustomerID: resp.CustomerID,
|
||||
PrizeID: resp.PrizeID,
|
||||
TokenUsed: resp.TokenUsed,
|
||||
RandomSeed: resp.RandomSeed,
|
||||
CreatedAt: resp.CreatedAt,
|
||||
Game: nil, // Optional field - can be populated separately if needed
|
||||
Customer: nil, // Optional field - can be populated separately if needed
|
||||
Prize: nil, // Optional field - can be populated separately if needed
|
||||
}
|
||||
}
|
||||
|
||||
// CustomerGamePrizeResponseModelToContract converts customer game prize response from model to contract
|
||||
func CustomerGamePrizeResponseModelToContract(resp *models.CustomerGamePrizeResponse) *contract.CustomerGamePrizeResponse {
|
||||
if resp == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &contract.CustomerGamePrizeResponse{
|
||||
ID: resp.ID,
|
||||
GameID: resp.GameID,
|
||||
Name: resp.Name,
|
||||
Image: resp.Image,
|
||||
Metadata: resp.Metadata,
|
||||
CreatedAt: resp.CreatedAt,
|
||||
UpdatedAt: resp.UpdatedAt,
|
||||
}
|
||||
}
|
||||
37
internal/models/spin_game.go
Normal file
37
internal/models/spin_game.go
Normal file
@ -0,0 +1,37 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// SpinGameRequest represents the request to play a spin game
|
||||
type SpinGameRequest struct {
|
||||
SpinID string `json:"spin_id" validate:"required,uuid"`
|
||||
}
|
||||
|
||||
// SpinGameResponse represents the response from playing a spin game
|
||||
type SpinGameResponse struct {
|
||||
Status string `json:"status"`
|
||||
Message string `json:"message"`
|
||||
Data *SpinGameResponseData `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// SpinGameResponseData contains the game play result
|
||||
type SpinGameResponseData struct {
|
||||
GamePlay GamePlayResponse `json:"game_play"`
|
||||
PrizeWon *CustomerGamePrizeResponse `json:"prize_won,omitempty"`
|
||||
TokensRemaining int64 `json:"tokens_remaining"`
|
||||
}
|
||||
|
||||
// SpinGamePrizeResponse represents a prize won in the spin game
|
||||
type SpinGamePrizeResponse struct {
|
||||
ID uuid.UUID `json:"id"`
|
||||
GameID uuid.UUID `json:"game_id"`
|
||||
Name string `json:"name"`
|
||||
Image *string `json:"image,omitempty"`
|
||||
Metadata *map[string]interface{} `json:"metadata,omitempty"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
@ -15,7 +15,7 @@ import (
|
||||
)
|
||||
|
||||
type GamePlayProcessor struct {
|
||||
gamePlayRepo *repository.GamePlayRepository
|
||||
gamePlayRepo repository.GamePlayRepository
|
||||
gameRepo *repository.GameRepository
|
||||
gamePrizeRepo *repository.GamePrizeRepository
|
||||
customerTokensRepo *repository.CustomerTokensRepository
|
||||
@ -23,7 +23,7 @@ type GamePlayProcessor struct {
|
||||
}
|
||||
|
||||
func NewGamePlayProcessor(
|
||||
gamePlayRepo *repository.GamePlayRepository,
|
||||
gamePlayRepo repository.GamePlayRepository,
|
||||
gameRepo *repository.GameRepository,
|
||||
gamePrizeRepo *repository.GamePrizeRepository,
|
||||
customerTokensRepo *repository.CustomerTokensRepository,
|
||||
@ -94,7 +94,7 @@ func (p *GamePlayProcessor) ListGamePlays(ctx context.Context, query *models.Lis
|
||||
}
|
||||
|
||||
// Convert to responses
|
||||
responses := mappers.ToGamePlayResponses(gamePlays)
|
||||
responses := mappers.ToGamePlayResponsesFromPointers(gamePlays)
|
||||
|
||||
// Calculate pagination info
|
||||
totalPages := int((total + int64(query.Limit) - 1) / int64(query.Limit))
|
||||
|
||||
@ -30,24 +30,7 @@ func NewCampaignRepository(db *gorm.DB) CampaignRepository {
|
||||
}
|
||||
|
||||
func (r *campaignRepository) Create(ctx context.Context, campaign *entities.Campaign) error {
|
||||
return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
// Create campaign first
|
||||
if err := tx.Create(campaign).Error; err != nil {
|
||||
return fmt.Errorf("failed to create campaign: %w", err)
|
||||
}
|
||||
|
||||
// Create campaign rules
|
||||
if len(campaign.Rules) > 0 {
|
||||
for i := range campaign.Rules {
|
||||
campaign.Rules[i].CampaignID = campaign.ID
|
||||
}
|
||||
if err := tx.Create(&campaign.Rules).Error; err != nil {
|
||||
return fmt.Errorf("failed to create campaign rules: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
return r.db.WithContext(ctx).Create(campaign).Error
|
||||
}
|
||||
|
||||
func (r *campaignRepository) GetByID(ctx context.Context, id string) (*entities.Campaign, error) {
|
||||
@ -114,29 +97,7 @@ func (r *campaignRepository) List(ctx context.Context, req *entities.ListCampaig
|
||||
}
|
||||
|
||||
func (r *campaignRepository) Update(ctx context.Context, campaign *entities.Campaign) error {
|
||||
return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
// Update campaign
|
||||
if err := tx.Save(campaign).Error; err != nil {
|
||||
return fmt.Errorf("failed to update campaign: %w", err)
|
||||
}
|
||||
|
||||
// Delete existing rules
|
||||
if err := tx.Where("campaign_id = ?", campaign.ID).Delete(&entities.CampaignRule{}).Error; err != nil {
|
||||
return fmt.Errorf("failed to delete existing campaign rules: %w", err)
|
||||
}
|
||||
|
||||
// Create new rules
|
||||
if len(campaign.Rules) > 0 {
|
||||
for i := range campaign.Rules {
|
||||
campaign.Rules[i].CampaignID = campaign.ID
|
||||
}
|
||||
if err := tx.Create(&campaign.Rules).Error; err != nil {
|
||||
return fmt.Errorf("failed to create campaign rules: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
return r.db.WithContext(ctx).Save(campaign).Error
|
||||
}
|
||||
|
||||
func (r *campaignRepository) Delete(ctx context.Context, id string) error {
|
||||
|
||||
@ -1,111 +1,170 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"apskel-pos-be/internal/entities"
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"apskel-pos-be/internal/entities"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type GamePlayRepository struct {
|
||||
type GamePlayRepository interface {
|
||||
Create(ctx context.Context, gamePlay *entities.GamePlay) error
|
||||
GetByID(ctx context.Context, id uuid.UUID) (*entities.GamePlay, error)
|
||||
List(ctx context.Context, offset, limit int, search string, gameID, customerID, prizeID *uuid.UUID, sortBy, sortOrder string) ([]*entities.GamePlay, int64, error)
|
||||
GetByCustomerID(ctx context.Context, customerID uuid.UUID, limit int) ([]*entities.GamePlay, error)
|
||||
GetByGameID(ctx context.Context, gameID uuid.UUID, limit int) ([]*entities.GamePlay, error)
|
||||
CountByCustomerID(ctx context.Context, customerID uuid.UUID) (int64, error)
|
||||
CountByGameID(ctx context.Context, gameID uuid.UUID) (int64, error)
|
||||
}
|
||||
|
||||
type gamePlayRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewGamePlayRepository(db *gorm.DB) *GamePlayRepository {
|
||||
return &GamePlayRepository{db: 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) Create(ctx context.Context, gamePlay *entities.GamePlay) error {
|
||||
if err := r.db.WithContext(ctx).Create(gamePlay).Error; err != nil {
|
||||
return fmt.Errorf("failed to create game play: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *GamePlayRepository) GetByID(ctx context.Context, id uuid.UUID) (*entities.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
|
||||
err := r.db.WithContext(ctx).
|
||||
Preload("Game").
|
||||
Preload("Customer").
|
||||
Preload("Prize").
|
||||
First(&gamePlay, "id = ?", id).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, fmt.Errorf("game play not found")
|
||||
}
|
||||
return nil, fmt.Errorf("failed to get game play: %w", 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
|
||||
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")
|
||||
query := r.db.WithContext(ctx).Model(&entities.GamePlay{})
|
||||
|
||||
// Apply filters
|
||||
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)
|
||||
query = query.Where("random_seed ILIKE ?", "%"+search+"%")
|
||||
}
|
||||
|
||||
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
|
||||
// Count total records
|
||||
if err := query.Count(&total).Error; err != nil {
|
||||
return nil, 0, fmt.Errorf("failed to count game plays: %w", err)
|
||||
}
|
||||
|
||||
// Apply sorting
|
||||
if sortBy != "" {
|
||||
if sortOrder == "" {
|
||||
sortOrder = "asc"
|
||||
}
|
||||
query = query.Order(fmt.Sprintf("game_plays.%s %s", sortBy, sortOrder))
|
||||
if sortOrder == "desc" {
|
||||
query = query.Order(fmt.Sprintf("%s DESC", sortBy))
|
||||
} else {
|
||||
query = query.Order("game_plays.created_at DESC")
|
||||
query = query.Order(fmt.Sprintf("%s ASC", sortBy))
|
||||
}
|
||||
} else {
|
||||
query = query.Order("created_at DESC")
|
||||
}
|
||||
|
||||
err := query.Offset(offset).Limit(limit).Find(&gamePlays).Error
|
||||
// Apply pagination and preload relations
|
||||
err := query.
|
||||
Preload("Game").
|
||||
Preload("Customer").
|
||||
Preload("Prize").
|
||||
Offset(offset).
|
||||
Limit(limit).
|
||||
Find(&gamePlays).Error
|
||||
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
return nil, 0, fmt.Errorf("failed to list game plays: %w", 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").
|
||||
func (r *gamePlayRepository) GetByCustomerID(ctx context.Context, customerID uuid.UUID, limit int) ([]*entities.GamePlay, error) {
|
||||
var gamePlays []*entities.GamePlay
|
||||
query := r.db.WithContext(ctx).
|
||||
Where("customer_id = ?", customerID).
|
||||
Order("created_at DESC").
|
||||
Limit(limit).
|
||||
Find(&gamePlays).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
Preload("Game").
|
||||
Preload("Prize").
|
||||
Order("created_at DESC")
|
||||
|
||||
if limit > 0 {
|
||||
query = query.Limit(limit)
|
||||
}
|
||||
|
||||
err := query.Find(&gamePlays).Error
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get game plays by customer ID: %w", 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").
|
||||
func (r *gamePlayRepository) GetByGameID(ctx context.Context, gameID uuid.UUID, limit int) ([]*entities.GamePlay, error) {
|
||||
var gamePlays []*entities.GamePlay
|
||||
query := r.db.WithContext(ctx).
|
||||
Where("game_id = ?", gameID).
|
||||
Scan(&stats).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
Preload("Customer").
|
||||
Preload("Prize").
|
||||
Order("created_at DESC")
|
||||
|
||||
if limit > 0 {
|
||||
query = query.Limit(limit)
|
||||
}
|
||||
|
||||
return stats, nil
|
||||
err := query.Find(&gamePlays).Error
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get game plays by game ID: %w", err)
|
||||
}
|
||||
|
||||
return gamePlays, nil
|
||||
}
|
||||
|
||||
func (r *gamePlayRepository) CountByCustomerID(ctx context.Context, customerID uuid.UUID) (int64, error) {
|
||||
var count int64
|
||||
err := r.db.WithContext(ctx).
|
||||
Model(&entities.GamePlay{}).
|
||||
Where("customer_id = ?", customerID).
|
||||
Count(&count).Error
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to count game plays by customer ID: %w", err)
|
||||
}
|
||||
return count, nil
|
||||
}
|
||||
|
||||
func (r *gamePlayRepository) CountByGameID(ctx context.Context, gameID uuid.UUID) (int64, error) {
|
||||
var count int64
|
||||
err := r.db.WithContext(ctx).
|
||||
Model(&entities.GamePlay{}).
|
||||
Where("game_id = ?", gameID).
|
||||
Count(&count).Error
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to count game plays by game ID: %w", err)
|
||||
}
|
||||
return count, nil
|
||||
}
|
||||
|
||||
@ -45,6 +45,7 @@ type Router struct {
|
||||
campaignHandler *handler.CampaignHandler
|
||||
customerAuthHandler *handler.CustomerAuthHandler
|
||||
customerPointsHandler *handler.CustomerPointsHandler
|
||||
spinGameHandler *handler.SpinGameHandler
|
||||
authMiddleware *middleware.AuthMiddleware
|
||||
customerAuthMiddleware *middleware.CustomerAuthMiddleware
|
||||
}
|
||||
@ -106,6 +107,7 @@ func NewRouter(cfg *config.Config,
|
||||
customerAuthService service.CustomerAuthService,
|
||||
customerAuthValidator validator.CustomerAuthValidator,
|
||||
customerPointsService service.CustomerPointsService,
|
||||
spinGameService service.SpinGameService,
|
||||
customerAuthMiddleware *middleware.CustomerAuthMiddleware) *Router {
|
||||
|
||||
return &Router{
|
||||
@ -141,6 +143,7 @@ func NewRouter(cfg *config.Config,
|
||||
campaignHandler: handler.NewCampaignHandler(campaignService, campaignValidator),
|
||||
customerAuthHandler: handler.NewCustomerAuthHandler(customerAuthService, customerAuthValidator),
|
||||
customerPointsHandler: handler.NewCustomerPointsHandler(customerPointsService),
|
||||
spinGameHandler: handler.NewSpinGameHandler(spinGameService),
|
||||
authMiddleware: authMiddleware,
|
||||
customerAuthMiddleware: customerAuthMiddleware,
|
||||
}
|
||||
@ -196,6 +199,7 @@ func (r *Router) addAppRoutes(rg *gin.Engine) {
|
||||
customer.GET("/wallet", r.customerPointsHandler.GetCustomerWallet)
|
||||
customer.GET("/games", r.customerPointsHandler.GetCustomerGames)
|
||||
customer.GET("/ferris-wheel", r.customerPointsHandler.GetFerrisWheelGame)
|
||||
customer.POST("/spin", r.spinGameHandler.PlaySpinGame)
|
||||
}
|
||||
|
||||
organizations := v1.Group("/organizations")
|
||||
@ -574,6 +578,17 @@ func (r *Router) addAppRoutes(rg *gin.Engine) {
|
||||
campaigns.PUT("/:id", r.campaignHandler.UpdateCampaign)
|
||||
campaigns.DELETE("/:id", r.campaignHandler.DeleteCampaign)
|
||||
}
|
||||
|
||||
// Campaign Rules
|
||||
campaignRules := gamification.Group("/campaign-rules")
|
||||
{
|
||||
campaignRules.POST("", r.campaignHandler.CreateCampaignRule)
|
||||
campaignRules.GET("", r.campaignHandler.ListCampaignRules)
|
||||
campaignRules.GET("/:id", r.campaignHandler.GetCampaignRule)
|
||||
campaignRules.PUT("/:id", r.campaignHandler.UpdateCampaignRule)
|
||||
campaignRules.DELETE("/:id", r.campaignHandler.DeleteCampaignRule)
|
||||
campaignRules.GET("/campaign/:campaign_id", r.campaignHandler.GetCampaignRulesByCampaignID)
|
||||
}
|
||||
}
|
||||
|
||||
outlets := protected.Group("/outlets")
|
||||
|
||||
@ -18,15 +18,24 @@ type CampaignService interface {
|
||||
DeleteCampaign(ctx context.Context, id string) error
|
||||
GetActiveCampaigns(ctx context.Context) ([]models.CampaignResponse, error)
|
||||
GetCampaignsForApp(ctx context.Context) ([]models.CampaignResponse, error)
|
||||
// Campaign Rules methods
|
||||
CreateCampaignRule(ctx context.Context, req *contract.CreateCampaignRuleRequest) (*models.CampaignRuleResponse, error)
|
||||
GetCampaignRule(ctx context.Context, id string) (*models.CampaignRuleResponse, error)
|
||||
ListCampaignRules(ctx context.Context, req *contract.ListCampaignRulesRequest) (*models.ListCampaignRulesResponse, error)
|
||||
UpdateCampaignRule(ctx context.Context, req *contract.UpdateCampaignRuleRequest) (*models.CampaignRuleResponse, error)
|
||||
DeleteCampaignRule(ctx context.Context, id string) error
|
||||
GetCampaignRulesByCampaignID(ctx context.Context, campaignID string) ([]models.CampaignRuleResponse, error)
|
||||
}
|
||||
|
||||
type campaignService struct {
|
||||
campaignProcessor processor.CampaignProcessor
|
||||
campaignRuleProcessor processor.CampaignRuleProcessor
|
||||
}
|
||||
|
||||
func NewCampaignService(campaignProcessor processor.CampaignProcessor) CampaignService {
|
||||
func NewCampaignService(campaignProcessor processor.CampaignProcessor, campaignRuleProcessor processor.CampaignRuleProcessor) CampaignService {
|
||||
return &campaignService{
|
||||
campaignProcessor: campaignProcessor,
|
||||
campaignRuleProcessor: campaignRuleProcessor,
|
||||
}
|
||||
}
|
||||
|
||||
@ -41,11 +50,6 @@ func (s *campaignService) CreateCampaign(ctx context.Context, req *contract.Crea
|
||||
return nil, fmt.Errorf("validation failed: %w", err)
|
||||
}
|
||||
|
||||
// Validate rules
|
||||
if err := s.validateCampaignRules(req.Rules); err != nil {
|
||||
return nil, fmt.Errorf("validation failed: %w", err)
|
||||
}
|
||||
|
||||
// Validate position
|
||||
if req.Position < 0 {
|
||||
return nil, fmt.Errorf("position cannot be negative")
|
||||
@ -115,11 +119,6 @@ func (s *campaignService) UpdateCampaign(ctx context.Context, req *contract.Upda
|
||||
return nil, fmt.Errorf("validation failed: %w", err)
|
||||
}
|
||||
|
||||
// Validate rules
|
||||
if err := s.validateCampaignRules(req.Rules); err != nil {
|
||||
return nil, fmt.Errorf("validation failed: %w", err)
|
||||
}
|
||||
|
||||
// Validate position
|
||||
if req.Position < 0 {
|
||||
return nil, fmt.Errorf("position cannot be negative")
|
||||
@ -164,6 +163,137 @@ func (s *campaignService) GetCampaignsForApp(ctx context.Context) ([]models.Camp
|
||||
return campaigns, nil
|
||||
}
|
||||
|
||||
// Campaign Rules methods
|
||||
|
||||
func (s *campaignService) CreateCampaignRule(ctx context.Context, req *contract.CreateCampaignRuleRequest) (*models.CampaignRuleResponse, error) {
|
||||
// Validate rule type
|
||||
if err := s.validateRuleType(req.RuleType); err != nil {
|
||||
return nil, fmt.Errorf("validation failed: %w", err)
|
||||
}
|
||||
|
||||
// Validate reward type
|
||||
if err := s.validateRewardType(req.RewardType); err != nil {
|
||||
return nil, fmt.Errorf("validation failed: %w", err)
|
||||
}
|
||||
|
||||
// Validate reward value based on reward type
|
||||
if req.RewardType == "POINTS" || req.RewardType == "TOKENS" {
|
||||
if req.RewardValue == nil || *req.RewardValue <= 0 {
|
||||
return nil, fmt.Errorf("reward value must be positive for %s type", req.RewardType)
|
||||
}
|
||||
}
|
||||
|
||||
// Validate reward reference ID for REWARD type
|
||||
if req.RewardType == "REWARD" {
|
||||
if req.RewardRefID == nil {
|
||||
return nil, fmt.Errorf("reward reference ID is required for REWARD type")
|
||||
}
|
||||
}
|
||||
|
||||
response, err := s.campaignRuleProcessor.CreateCampaignRule(ctx, req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create campaign rule: %w", err)
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
func (s *campaignService) GetCampaignRule(ctx context.Context, id string) (*models.CampaignRuleResponse, error) {
|
||||
if id == "" {
|
||||
return nil, fmt.Errorf("campaign rule ID is required")
|
||||
}
|
||||
|
||||
response, err := s.campaignRuleProcessor.GetCampaignRule(ctx, id)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get campaign rule: %w", err)
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
func (s *campaignService) ListCampaignRules(ctx context.Context, req *contract.ListCampaignRulesRequest) (*models.ListCampaignRulesResponse, error) {
|
||||
// Set default pagination
|
||||
if req.Page <= 0 {
|
||||
req.Page = 1
|
||||
}
|
||||
if req.Limit <= 0 {
|
||||
req.Limit = 10
|
||||
}
|
||||
if req.Limit > 100 {
|
||||
req.Limit = 100
|
||||
}
|
||||
|
||||
response, err := s.campaignRuleProcessor.ListCampaignRules(ctx, req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to list campaign rules: %w", err)
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
func (s *campaignService) UpdateCampaignRule(ctx context.Context, req *contract.UpdateCampaignRuleRequest) (*models.CampaignRuleResponse, error) {
|
||||
if req.ID.String() == "" {
|
||||
return nil, fmt.Errorf("campaign rule ID is required")
|
||||
}
|
||||
|
||||
// Validate rule type
|
||||
if err := s.validateRuleType(req.RuleType); err != nil {
|
||||
return nil, fmt.Errorf("validation failed: %w", err)
|
||||
}
|
||||
|
||||
// Validate reward type
|
||||
if err := s.validateRewardType(req.RewardType); err != nil {
|
||||
return nil, fmt.Errorf("validation failed: %w", err)
|
||||
}
|
||||
|
||||
// Validate reward value based on reward type
|
||||
if req.RewardType == "POINTS" || req.RewardType == "TOKENS" {
|
||||
if req.RewardValue == nil || *req.RewardValue <= 0 {
|
||||
return nil, fmt.Errorf("reward value must be positive for %s type", req.RewardType)
|
||||
}
|
||||
}
|
||||
|
||||
// Validate reward reference ID for REWARD type
|
||||
if req.RewardType == "REWARD" {
|
||||
if req.RewardRefID == nil {
|
||||
return nil, fmt.Errorf("reward reference ID is required for REWARD type")
|
||||
}
|
||||
}
|
||||
|
||||
response, err := s.campaignRuleProcessor.UpdateCampaignRule(ctx, req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to update campaign rule: %w", err)
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
func (s *campaignService) DeleteCampaignRule(ctx context.Context, id string) error {
|
||||
if id == "" {
|
||||
return fmt.Errorf("campaign rule ID is required")
|
||||
}
|
||||
|
||||
err := s.campaignRuleProcessor.DeleteCampaignRule(ctx, id)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete campaign rule: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *campaignService) GetCampaignRulesByCampaignID(ctx context.Context, campaignID string) ([]models.CampaignRuleResponse, error) {
|
||||
if campaignID == "" {
|
||||
return nil, fmt.Errorf("campaign ID is required")
|
||||
}
|
||||
|
||||
rules, err := s.campaignRuleProcessor.GetCampaignRulesByCampaignID(ctx, campaignID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get campaign rules by campaign ID: %w", err)
|
||||
}
|
||||
|
||||
return rules, nil
|
||||
}
|
||||
|
||||
func (s *campaignService) validateCampaignType(campaignType string) error {
|
||||
validTypes := []string{"REWARD", "POINTS", "TOKENS", "MIXED"}
|
||||
for _, validType := range validTypes {
|
||||
|
||||
98
internal/service/spin_game_service.go
Normal file
98
internal/service/spin_game_service.go
Normal file
@ -0,0 +1,98 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"apskel-pos-be/internal/models"
|
||||
"apskel-pos-be/internal/processor"
|
||||
"apskel-pos-be/internal/repository"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type SpinGameService interface {
|
||||
PlaySpinGame(ctx context.Context, customerID string, req *models.SpinGameRequest) (*models.SpinGameResponse, error)
|
||||
}
|
||||
|
||||
type spinGameService struct {
|
||||
gamePlayProcessor *processor.GamePlayProcessor
|
||||
txManager *repository.TxManager
|
||||
}
|
||||
|
||||
func NewSpinGameService(gamePlayProcessor *processor.GamePlayProcessor, txManager *repository.TxManager) SpinGameService {
|
||||
return &spinGameService{
|
||||
gamePlayProcessor: gamePlayProcessor,
|
||||
txManager: txManager,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *spinGameService) PlaySpinGame(ctx context.Context, customerID string, req *models.SpinGameRequest) (*models.SpinGameResponse, error) {
|
||||
// Validate customer ID
|
||||
if customerID == "" {
|
||||
return nil, fmt.Errorf("customer ID is required")
|
||||
}
|
||||
|
||||
// Parse spin ID to UUID
|
||||
spinID, err := uuid.Parse(req.SpinID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid spin ID format: %w", err)
|
||||
}
|
||||
|
||||
// Parse customer ID to UUID
|
||||
customerUUID, err := uuid.Parse(customerID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid customer ID format: %w", err)
|
||||
}
|
||||
|
||||
// Use transaction to ensure consistency
|
||||
var playResponse *models.PlayGameResponse
|
||||
err = s.txManager.WithTransaction(ctx, func(txCtx context.Context) error {
|
||||
// Create play game request
|
||||
playGameReq := &models.PlayGameRequest{
|
||||
GameID: spinID,
|
||||
CustomerID: customerUUID,
|
||||
TokenUsed: 1, // Default to 1 token per spin
|
||||
}
|
||||
|
||||
// Play the game within transaction
|
||||
var playErr error
|
||||
playResponse, playErr = s.gamePlayProcessor.PlayGame(txCtx, playGameReq)
|
||||
if playErr != nil {
|
||||
return fmt.Errorf("failed to play spin game: %w", playErr)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Convert prize to customer-facing format
|
||||
var customerPrize *models.CustomerGamePrizeResponse
|
||||
if playResponse.PrizeWon != nil {
|
||||
customerPrize = &models.CustomerGamePrizeResponse{
|
||||
ID: playResponse.PrizeWon.ID,
|
||||
GameID: playResponse.PrizeWon.GameID,
|
||||
Name: playResponse.PrizeWon.Name,
|
||||
Image: playResponse.PrizeWon.Image,
|
||||
Metadata: &playResponse.PrizeWon.Metadata,
|
||||
CreatedAt: playResponse.PrizeWon.CreatedAt,
|
||||
UpdatedAt: playResponse.PrizeWon.UpdatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
// Convert to spin game response
|
||||
response := &models.SpinGameResponse{
|
||||
Status: "SUCCESS",
|
||||
Message: "Spin game completed successfully.",
|
||||
Data: &models.SpinGameResponseData{
|
||||
GamePlay: playResponse.GamePlay,
|
||||
PrizeWon: customerPrize,
|
||||
TokensRemaining: playResponse.TokensRemaining,
|
||||
},
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
@ -56,17 +56,6 @@ func (v *CampaignValidatorImpl) ValidateCreateCampaignRequest(req *contract.Crea
|
||||
return errors.New("position cannot be negative"), constants.ValidationErrorCode
|
||||
}
|
||||
|
||||
// Validate rules
|
||||
if len(req.Rules) == 0 {
|
||||
return errors.New("at least one rule is required"), constants.ValidationErrorCode
|
||||
}
|
||||
|
||||
for i, rule := range req.Rules {
|
||||
if err := v.validateCampaignRule(&rule, i+1); err != nil {
|
||||
return err, constants.ValidationErrorCode
|
||||
}
|
||||
}
|
||||
|
||||
return nil, ""
|
||||
}
|
||||
|
||||
@ -103,17 +92,6 @@ func (v *CampaignValidatorImpl) ValidateUpdateCampaignRequest(req *contract.Upda
|
||||
return errors.New("position cannot be negative"), constants.ValidationErrorCode
|
||||
}
|
||||
|
||||
// Validate rules
|
||||
if len(req.Rules) == 0 {
|
||||
return errors.New("at least one rule is required"), constants.ValidationErrorCode
|
||||
}
|
||||
|
||||
for i, rule := range req.Rules {
|
||||
if err := v.validateCampaignRule(&rule, i+1); err != nil {
|
||||
return err, constants.ValidationErrorCode
|
||||
}
|
||||
}
|
||||
|
||||
return nil, ""
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user