diff --git a/Dockerfile b/Dockerfile index bdbd381..128cda3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,6 +15,6 @@ COPY --from=build /out/app /app ENV TZ=Asia/Jakarta EXPOSE 4000 HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \ - CMD curl -fsS http://localhost:3300/health || exit 1 + CMD curl -fsS http://localhost:4000/health || exit 1 USER appuser ENTRYPOINT ["/app"] diff --git a/deployment.sh b/deployment.sh index f1fb342..7586274 100644 --- a/deployment.sh +++ b/deployment.sh @@ -1,62 +1,177 @@ #!/bin/bash set -euo pipefail +# Usage: +# ./deployment.sh staging deploy staging (branch staging, port 4001) +# ./deployment.sh production deploy production (branch main, port 4000) +# ./deployment.sh production --rollback kembali ke image sebelum deploy terakhir +# Tambahkan --yes untuk melewati konfirmasi production. + APP_NAME="apskel-pos" +LEGACY_CONTAINER="apskel-pos" # nama lama sebelum nama container memakai environment -# ─── Deteksi environment dari branch aktif ─────────────────────────────────── -CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD) +usage() { + echo "Usage: $0 [--rollback] [--yes]" + exit 1 +} -case "$CURRENT_BRANCH" in - main) - ENV_MODE="production" +fail() { + echo "❌ $*" + exit 1 +} + +# ─── Argumen ────────────────────────────────────────────────────────────────── +ENV_MODE="${1:-}" +shift || true + +ROLLBACK=false +ASSUME_YES=false +for arg in "$@"; do + case "$arg" in + --rollback) ROLLBACK=true ;; + --yes|-y) ASSUME_YES=true ;; + *) usage ;; + esac +done + +# Environment wajib disebut eksplisit, tidak lagi ditebak dari branch aktif +case "$ENV_MODE" in + production) + EXPECTED_BRANCH="main" PORT="4000" ;; staging) - ENV_MODE="staging" + EXPECTED_BRANCH="staging" PORT="4001" ;; *) - echo "❌ Branch '$CURRENT_BRANCH' tidak dikenali untuk deployment." - echo " Gunakan branch 'main' (production) atau 'staging' (staging)." - exit 1 + usage ;; esac -CONTAINER_NAME="$APP_NAME" +CONTAINER_NAME="$APP_NAME-$ENV_MODE" IMAGE_NAME="$APP_NAME:$ENV_MODE" +PREVIOUS_IMAGE="$APP_NAME:$ENV_MODE-previous" +CONFIG_FILE="infra/$ENV_MODE.yaml" + +# ─── Validasi repo ──────────────────────────────────────────────────────────── +CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD) + +if [ "$CURRENT_BRANCH" = "HEAD" ]; then + fail "Repo sedang detached HEAD. Jalankan 'git checkout $EXPECTED_BRANCH' dulu." +fi + +if [ "$CURRENT_BRANCH" != "$EXPECTED_BRANCH" ]; then + fail "Deploy $ENV_MODE harus dari branch '$EXPECTED_BRANCH', folder ini di branch '$CURRENT_BRANCH'. + Pakai folder terpisah per environment, jangan pindah branch di folder yang sedang dipakai container lain." +fi + +if [ -n "$(git status --porcelain --untracked-files=no)" ]; then + fail "Ada perubahan yang belum di-commit di folder ini. Bersihkan dulu sebelum deploy." +fi + +[ -f "$CONFIG_FILE" ] || fail "Config file '$CONFIG_FILE' tidak ditemukan." + +# ─── Pastikan port tidak dipakai container environment lain ────────────────── +OTHER_ON_PORT=$(docker ps --filter "publish=$PORT" --format '{{.Names}}' \ + | grep -vx "$CONTAINER_NAME" \ + | { if [ "$ENV_MODE" = "production" ]; then grep -vx "$LEGACY_CONTAINER"; else cat; fi; } \ + || true) +if [ -n "$OTHER_ON_PORT" ]; then + fail "Port $PORT sedang dipakai container lain: $OTHER_ON_PORT" +fi echo "📦 Environment : $ENV_MODE" echo "🌿 Branch : $CURRENT_BRANCH" echo "🐳 Container : $CONTAINER_NAME" echo "🔌 Port : $PORT" +echo "📁 Folder : $(pwd)" +[ "$ROLLBACK" = true ] && echo "⏪ Mode : ROLLBACK ke $PREVIOUS_IMAGE" echo "" -# ─── Pastikan config file ada ───────────────────────────────────────────────── -CONFIG_FILE="infra/$ENV_MODE.yaml" -if [ ! -f "$CONFIG_FILE" ]; then - echo "❌ Config file '$CONFIG_FILE' tidak ditemukan." - exit 1 +# ─── Konfirmasi production ──────────────────────────────────────────────────── +if [ "$ENV_MODE" = "production" ] && [ "$ASSUME_YES" != true ]; then + read -r -p "⚠️ Ini PRODUCTION. Ketik 'production' untuk lanjut: " CONFIRM + [ "$CONFIRM" = "production" ] || fail "Dibatalkan." fi -echo "🔄 Pulling latest code..." -git pull +run_container() { + local image="$1" + + echo "🛑 Stopping and removing old container..." + docker rm -f "$CONTAINER_NAME" 2>/dev/null || true + # Container production lama masih bernama 'apskel-pos'; staging tidak boleh menyentuhnya + if [ "$ENV_MODE" = "production" ]; then + docker rm -f "$LEGACY_CONTAINER" 2>/dev/null || true + fi + + echo "🚀 Running container from $image..." + docker run -d --name "$CONTAINER_NAME" \ + --restart unless-stopped \ + -p "$PORT:4000" \ + -e TZ=Asia/Jakarta \ + -e ENV_MODE="$ENV_MODE" \ + -v "$(pwd)/infra":/infra:ro \ + -v "$(pwd)/templates":/templates:ro \ + "$image" >/dev/null +} + +wait_healthy() { + echo "🩺 Waiting for healthcheck..." + for _ in $(seq 1 36); do + local status + status=$(docker inspect -f '{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}' "$CONTAINER_NAME" 2>/dev/null || echo "missing") + case "$status" in + healthy) return 0 ;; + unhealthy|exited|dead|missing) return 1 ;; + esac + sleep 5 + done + return 1 +} + +# ─── Rollback ───────────────────────────────────────────────────────────────── +if [ "$ROLLBACK" = true ]; then + docker image inspect "$PREVIOUS_IMAGE" >/dev/null 2>&1 || fail "Image '$PREVIOUS_IMAGE' tidak ada, tidak bisa rollback." + run_container "$PREVIOUS_IMAGE" + wait_healthy || fail "Container hasil rollback tidak healthy. Cek: docker logs $CONTAINER_NAME" + docker tag "$PREVIOUS_IMAGE" "$IMAGE_NAME" + echo "✅ Rollback $ENV_MODE complete." + exit 0 +fi + +# ─── Deploy ─────────────────────────────────────────────────────────────────── +echo "🔄 Pulling latest code (origin/$EXPECTED_BRANCH)..." +git fetch origin "$EXPECTED_BRANCH" +git merge --ff-only "origin/$EXPECTED_BRANCH" +echo " Commit: $(git log -1 --format='%h %s')" + +# Simpan image yang sedang jalan untuk rollback +if docker image inspect "$IMAGE_NAME" >/dev/null 2>&1; then + docker tag "$IMAGE_NAME" "$PREVIOUS_IMAGE" +fi echo "🐳 Building Docker image ($ENV_MODE)..." docker build --target production -t "$IMAGE_NAME" . -echo "🛑 Stopping and removing old container..." -docker rm -f "$CONTAINER_NAME" 2>/dev/null || true +run_container "$IMAGE_NAME" -echo "🚀 Running new container..." -docker run -d --name "$CONTAINER_NAME" \ - -p "$PORT:4000" \ - -e TZ=Asia/Jakarta \ - -e ENV_MODE="$ENV_MODE" \ - -v "$(pwd)/infra":/infra:ro \ - -v "$(pwd)/templates":/templates:ro \ - "$IMAGE_NAME" +if ! wait_healthy; then + echo "❌ Container baru tidak healthy. Log terakhir:" + docker logs --tail 50 "$CONTAINER_NAME" || true + if docker image inspect "$PREVIOUS_IMAGE" >/dev/null 2>&1; then + echo "⏪ Rolling back ke $PREVIOUS_IMAGE..." + run_container "$PREVIOUS_IMAGE" + wait_healthy || fail "Rollback juga tidak healthy. Cek manual: docker logs $CONTAINER_NAME" + # Jangan biarkan image rusak jadi 'previous' di deploy berikutnya + docker tag "$PREVIOUS_IMAGE" "$IMAGE_NAME" + fail "Deploy gagal, $ENV_MODE sudah dikembalikan ke image sebelumnya." + fi + fail "Deploy gagal dan tidak ada image sebelumnya untuk rollback." +fi echo "" echo "✅ Deployment $ENV_MODE complete." echo " Container : $CONTAINER_NAME" echo " Port : $PORT" +echo " Rollback : $0 $ENV_MODE --rollback" diff --git a/docs/api-enakpoint.md b/docs/api-enakpoint.md index 8bd3837..1aeb01b 100644 --- a/docs/api-enakpoint.md +++ b/docs/api-enakpoint.md @@ -426,14 +426,11 @@ Semua nilai `data` berupa string. Push hanya sampai ke device yang terdaftar lew ### Endpoint dan field deprecated -Masih jalan dan membaca wallet, tapi akan dihapus setelah semua versi aplikasi pindah. +Masih jalan dan membaca wallet, tapi akan dihapus setelah semua versi aplikasi pindah. Semua yang bernama token (`/customer/tokens`, `total_tokens`, `tokens_history`, `token_used`, `tokens_remaining`, campaign `TOKENS`) sudah dihapus; pakai `coin_balance`, `coins_used`, `coins_remaining`, dan `COINS`. | Lama | Pengganti | | --- | --- | | `GET /customer/points` | `GET /customer/wallet` → `point_balance` | -| `GET /customer/tokens` | `GET /customer/wallet` → `coin_balance` | -| `total_points`, `total_tokens`, `points_history`, `tokens_history`, `last_updated` di `/customer/wallet` | `point_balance`, `coin_balance`, `recent_transactions` | -| `token_used`, `tokens_remaining` di response game | `coins_used`, `coins_remaining` | -| `sort_by=token_used` di daftar game play | `sort_by=coins_used` | +| `total_points`, `points_history`, `last_updated` di `/customer/wallet` | `point_balance`, `recent_transactions` | Panduan alur lengkap per tim ada di [`integration-enakpoint.md`](./integration-enakpoint.md). diff --git a/docs/integration-enakpoint.md b/docs/integration-enakpoint.md index 1b5e816..58b459a 100644 --- a/docs/integration-enakpoint.md +++ b/docs/integration-enakpoint.md @@ -474,16 +474,14 @@ Response: ```json { - "game_play": { "id": "…", "game_id": "…", "coins_used": 1, "token_used": 1, "created_at": "…" }, + "game_play": { "id": "…", "game_id": "…", "coins_used": 1, "created_at": "…" }, "prize_won": { "id": "…", "name": "Voucher 10rb", … }, - "coins_remaining": 7, - "tokens_remaining": 7 + "coins_remaining": 7 } ``` EnakCoin kurang, game nonaktif, atau hadiah baru saja habis dijawab `304`; tidak ada -EnakCoin yang terpotong. Baca `coins_used` dan `coins_remaining`; `token_used` dan -`tokens_remaining` hanya salinan untuk versi aplikasi lama. +EnakCoin yang terpotong. Di dashboard, `metadata.coin_cost` diisi per game dengan bilangan bulat ≥ 1. @@ -497,13 +495,14 @@ pindah. Aplikasi baru jangan memakainya. | Lama | Ganti dengan | |---|---| | `GET /customer/points` | `GET /customer/wallet` (`point_balance`) | -| `GET /customer/tokens` | `GET /customer/wallet` (`coin_balance`) | -| `total_points`, `total_tokens`, `points_history`, `tokens_history`, `last_updated` di `/customer/wallet` | `point_balance`, `coin_balance`, `recent_transactions` | -| `token_used`, `tokens_remaining` di respons game | `coins_used`, `coins_remaining` | -| `sort_by=token_used` di daftar game play | `sort_by=coins_used` | +| `total_points`, `points_history`, `last_updated` di `/customer/wallet` | `point_balance`, `recent_transactions` | Beri tahu tim backend setelah aplikasi yang beredar tidak lagi memakai kolom kiri, -supaya alias dan tabel lama (`customer_points`, `customer_tokens`) bisa dihapus. +supaya alias ini bisa dihapus. + +Semua yang bernama token sudah dihapus: `GET /customer/tokens`, `total_tokens`, +`tokens_history`, `token_used`, `tokens_remaining`, dan nilai `TOKENS` di campaign. Pakai +`coin_balance`, `coins_used`, `coins_remaining`, dan `COINS`. --- diff --git a/internal/constants/error.go b/internal/constants/error.go index d0967fc..e0ed8a6 100644 --- a/internal/constants/error.go +++ b/internal/constants/error.go @@ -52,7 +52,6 @@ const ( TableEntity = "table" // Gamification entities CustomerPointsEntity = "customer_points" - CustomerTokensEntity = "customer_tokens" TierEntity = "tier" GameEntity = "game" GamePrizeEntity = "game_prize" diff --git a/internal/contract/campaign_contract.go b/internal/contract/campaign_contract.go index f19d4a2..a62c078 100644 --- a/internal/contract/campaign_contract.go +++ b/internal/contract/campaign_contract.go @@ -12,7 +12,7 @@ import ( type CreateCampaignRequest struct { Name string `json:"name" binding:"required,min=1,max=150"` Description *string `json:"description,omitempty"` - Type string `json:"type" binding:"required,oneof=REWARD POINTS COINS TOKENS MIXED"` + Type string `json:"type" binding:"required,oneof=REWARD POINTS COINS MIXED"` StartDate time.Time `json:"start_date" binding:"required"` EndDate time.Time `json:"end_date" binding:"required"` IsActive bool `json:"is_active"` @@ -25,7 +25,7 @@ type UpdateCampaignRequest struct { ID uuid.UUID `json:"id" binding:"required"` Name string `json:"name" binding:"required,min=1,max=150"` Description *string `json:"description,omitempty"` - Type string `json:"type" binding:"required,oneof=REWARD POINTS COINS TOKENS MIXED"` + Type string `json:"type" binding:"required,oneof=REWARD POINTS COINS MIXED"` StartDate time.Time `json:"start_date" binding:"required"` EndDate time.Time `json:"end_date" binding:"required"` IsActive bool `json:"is_active"` @@ -58,7 +58,7 @@ type CreateCampaignRuleRequest struct { CampaignID uuid.UUID `json:"campaign_id" binding:"required"` RuleType string `json:"rule_type" binding:"required,oneof=TIER SPEND PRODUCT CATEGORY DAY LOCATION"` ConditionValue *string `json:"condition_value,omitempty"` - RewardType string `json:"reward_type" binding:"required,oneof=POINTS COINS TOKENS REWARD"` + RewardType string `json:"reward_type" binding:"required,oneof=POINTS COINS REWARD"` RewardValue *int64 `json:"reward_value,omitempty"` RewardSubtype *string `json:"reward_subtype,omitempty"` RewardRefID *uuid.UUID `json:"reward_ref_id,omitempty"` @@ -70,7 +70,7 @@ type UpdateCampaignRuleRequest struct { CampaignID uuid.UUID `json:"campaign_id" binding:"required"` RuleType string `json:"rule_type" binding:"required,oneof=TIER SPEND PRODUCT CATEGORY DAY LOCATION"` ConditionValue *string `json:"condition_value,omitempty"` - RewardType string `json:"reward_type" binding:"required,oneof=POINTS COINS TOKENS REWARD"` + RewardType string `json:"reward_type" binding:"required,oneof=POINTS COINS REWARD"` RewardValue *int64 `json:"reward_value,omitempty"` RewardSubtype *string `json:"reward_subtype,omitempty"` RewardRefID *uuid.UUID `json:"reward_ref_id,omitempty"` @@ -142,7 +142,7 @@ type ListCampaignRulesResponse struct { type CampaignRuleStruct struct { RuleType string `json:"rule_type" binding:"required,oneof=TIER SPEND PRODUCT CATEGORY DAY LOCATION"` ConditionValue *string `json:"condition_value,omitempty"` - RewardType string `json:"reward_type" binding:"required,oneof=POINTS COINS TOKENS REWARD"` + RewardType string `json:"reward_type" binding:"required,oneof=POINTS COINS REWARD"` RewardValue *int64 `json:"reward_value,omitempty"` RewardSubtype *string `json:"reward_subtype,omitempty"` RewardRefID *uuid.UUID `json:"reward_ref_id,omitempty"` diff --git a/internal/contract/customer_points_contract.go b/internal/contract/customer_points_contract.go index 591758c..29d4b12 100644 --- a/internal/contract/customer_points_contract.go +++ b/internal/contract/customer_points_contract.go @@ -25,26 +25,6 @@ type PointsHistoryItem struct { CreatedAt time.Time `json:"created_at"` } -type GetCustomerTokensResponse struct { - Status string `json:"status"` - Message string `json:"message"` - Data *GetCustomerTokensResponseData `json:"data,omitempty"` -} - -type GetCustomerTokensResponseData struct { - TotalTokens int64 `json:"total_tokens"` - TokensHistory []TokensHistoryItem `json:"tokens_history,omitempty"` - LastUpdated time.Time `json:"last_updated"` -} - -type TokensHistoryItem struct { - ID string `json:"id"` - Tokens int64 `json:"tokens"` - Type string `json:"type"` // EARNED, REDEEMED, EXPIRED - Description string `json:"description"` - CreatedAt time.Time `json:"created_at"` -} - type GetCustomerWalletResponse struct { Status string `json:"status"` Message string `json:"message"` @@ -53,8 +33,6 @@ type GetCustomerWalletResponse struct { type GetCustomerWalletResponseData struct { TotalPoints int64 `json:"total_points"` - TotalTokens int64 `json:"total_tokens"` PointsHistory []PointsHistoryItem `json:"points_history,omitempty"` - TokensHistory []TokensHistoryItem `json:"tokens_history,omitempty"` LastUpdated time.Time `json:"last_updated"` } diff --git a/internal/contract/game_play_contract.go b/internal/contract/game_play_contract.go index b32ad4f..a7454e9 100644 --- a/internal/contract/game_play_contract.go +++ b/internal/contract/game_play_contract.go @@ -14,13 +14,11 @@ type CreateGamePlayRequest struct { } 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"` - CoinsUsed int `json:"coins_used"` - // Deprecated: the same as CoinsUsed, kept until the apps read coins_used. - TokenUsed int `json:"token_used"` + ID uuid.UUID `json:"id"` + GameID uuid.UUID `json:"game_id"` + CustomerID uuid.UUID `json:"customer_id"` + PrizeID *uuid.UUID `json:"prize_id,omitempty"` + CoinsUsed int `json:"coins_used"` RandomSeed *string `json:"random_seed,omitempty"` CreatedAt time.Time `json:"created_at"` Game *GameResponse `json:"game,omitempty"` @@ -35,7 +33,7 @@ type ListGamePlaysRequest struct { 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 coins_used token_used"` + SortBy string `json:"sort_by" validate:"omitempty,oneof=created_at coins_used"` SortOrder string `json:"sort_order" validate:"omitempty,oneof=asc desc"` } @@ -58,6 +56,4 @@ type PlayGameResponse struct { GamePlay GamePlayResponse `json:"game_play"` PrizeWon *GamePrizeResponse `json:"prize_won,omitempty"` CoinsRemaining int64 `json:"coins_remaining"` - // Deprecated: the same as CoinsRemaining, kept until the apps read coins_remaining. - TokensRemaining int64 `json:"tokens_remaining"` } diff --git a/internal/contract/spin_game_contract.go b/internal/contract/spin_game_contract.go index 67b50b7..b234f2b 100644 --- a/internal/contract/spin_game_contract.go +++ b/internal/contract/spin_game_contract.go @@ -17,6 +17,4 @@ type SpinGameResponseData struct { GamePlay GamePlayResponse `json:"game_play"` PrizeWon *CustomerGamePrizeResponse `json:"prize_won,omitempty"` CoinsRemaining int64 `json:"coins_remaining"` - // Deprecated: the same as CoinsRemaining, kept until the apps read coins_remaining. - TokensRemaining int64 `json:"tokens_remaining"` } diff --git a/internal/entities/campaign.go b/internal/entities/campaign.go index 5eabd76..1e5aac0 100644 --- a/internal/entities/campaign.go +++ b/internal/entities/campaign.go @@ -16,27 +16,6 @@ const ( CampaignTypeMixed CampaignType = "MIXED" ) -// legacyCampaignTokens is what COINS was called before tokens became EnakCoin -// (docs/prd-point-coin.md §10). Requests may still send it; it is stored as COINS. -const legacyCampaignTokens = "TOKENS" - -// NormalizeCampaignType maps the legacy TOKENS to COINS and leaves anything else as is. -func NormalizeCampaignType(t string) CampaignType { - if t == legacyCampaignTokens { - return CampaignTypeCoins - } - return CampaignType(t) -} - -// NormalizeCampaignRewardType maps the legacy TOKENS to COINS and leaves anything -// else as is. -func NormalizeCampaignRewardType(t string) CampaignRewardType { - if t == legacyCampaignTokens { - return CampaignRewardTypeCoins - } - return CampaignRewardType(t) -} - type RuleType string const ( diff --git a/internal/entities/campaign_test.go b/internal/entities/campaign_test.go deleted file mode 100644 index 5dd1cd0..0000000 --- a/internal/entities/campaign_test.go +++ /dev/null @@ -1,18 +0,0 @@ -package entities - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestNormalizeCampaignTypesMapsLegacyTokensToCoins(t *testing.T) { - assert.Equal(t, CampaignTypeCoins, NormalizeCampaignType("TOKENS")) - assert.Equal(t, CampaignTypeCoins, NormalizeCampaignType("COINS")) - assert.Equal(t, CampaignTypePoints, NormalizeCampaignType("POINTS")) - assert.Equal(t, CampaignType(""), NormalizeCampaignType(""), "an empty filter stays empty") - - assert.Equal(t, CampaignRewardTypeCoins, NormalizeCampaignRewardType("TOKENS")) - assert.Equal(t, CampaignRewardTypeReward, NormalizeCampaignRewardType("REWARD")) - assert.Equal(t, CampaignRewardType(""), NormalizeCampaignRewardType("")) -} diff --git a/internal/handler/customer_points_handler.go b/internal/handler/customer_points_handler.go index 0df8ff2..8875d79 100644 --- a/internal/handler/customer_points_handler.go +++ b/internal/handler/customer_points_handler.go @@ -59,40 +59,6 @@ func (h *CustomerPointsHandler) GetCustomerPoints(c *gin.Context) { util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "CustomerPointsHandler::GetCustomerPoints") } -func (h *CustomerPointsHandler) GetCustomerTokens(c *gin.Context) { - ctx := c.Request.Context() - - // Get customer ID from context (set by middleware) - customerID, exists := c.Get("customer_id") - if !exists { - logger.FromContext(ctx).Error("Customer ID not found in context") - util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{ - contract.NewResponseError(constants.ValidationErrorCode, constants.AuthHandlerEntity, "Customer ID not found"), - }), "CustomerPointsHandler::GetCustomerTokens") - return - } - - customerIDStr, ok := customerID.(string) - if !ok { - logger.FromContext(ctx).Error("Invalid customer ID type in context") - util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{ - contract.NewResponseError(constants.ValidationErrorCode, constants.AuthHandlerEntity, "Invalid customer ID"), - }), "CustomerPointsHandler::GetCustomerTokens") - return - } - - response, err := h.customerPointsService.GetCustomerTokens(ctx, customerIDStr) - if err != nil { - logger.FromContext(ctx).WithError(err).Error("CustomerPointsHandler::GetCustomerTokens -> service call failed") - util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{ - contract.NewResponseError(walletErrorCode(err), constants.RequestEntity, err.Error()), - }), "CustomerPointsHandler::GetCustomerTokens") - return - } - - util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "CustomerPointsHandler::GetCustomerTokens") -} - func (h *CustomerPointsHandler) GetCustomerWallet(c *gin.Context) { ctx := c.Request.Context() diff --git a/internal/handler/customer_wallet_db_test.go b/internal/handler/customer_wallet_db_test.go index d9e2467..6cc88d6 100644 --- a/internal/handler/customer_wallet_db_test.go +++ b/internal/handler/customer_wallet_db_test.go @@ -25,7 +25,7 @@ import ( ) // Drives the customer wallet endpoints over HTTP down to Postgres, to show the app -// gets the right numbers from the new endpoints and from the old /points, /tokens +// gets the right numbers from the new endpoints and from the old /points // and /wallet shapes. Needs TEST_DATABASE_URL pointing at a migrated database; see // internal/repository/wallet_repository_test.go. func TestCustomerWalletEndpoints_AgainstPostgres(t *testing.T) { @@ -111,7 +111,6 @@ func TestCustomerWalletEndpoints_AgainstPostgres(t *testing.T) { }{{"/a", customer}, {"/b", other}, {"/gone", uuid.New()}} { g := router.Group(who.prefix, as(who.id)) g.GET("/points", h.GetCustomerPoints) - g.GET("/tokens", h.GetCustomerTokens) g.GET("/wallet", h.GetCustomerWallet) g.GET("/wallet/transactions", h.GetCustomerWalletTransactions) } @@ -128,16 +127,12 @@ func TestCustomerWalletEndpoints_AgainstPostgres(t *testing.T) { return body["data"].(map[string]any)["data"].(map[string]any) } - // Old app: /points and /tokens keep their shape and show the spendable balance. + // Old app: /points keeps its shape and shows the spendable balance. status, body := get("/a/points") require.Equal(t, http.StatusOK, status, body) assert.EqualValues(t, 250, inner(body)["total_points"], "300 - 50; the expired 20 is not spendable") assert.Len(t, inner(body)["points_history"], 3) - status, body = get("/a/tokens") - require.Equal(t, http.StatusOK, status, body) - assert.EqualValues(t, 8, inner(body)["total_tokens"]) - // /wallet: new fields, with the old ones still there. status, body = get("/a/wallet") require.Equal(t, http.StatusOK, status, body) @@ -151,9 +146,10 @@ func TestCustomerWalletEndpoints_AgainstPostgres(t *testing.T) { assert.Equal(t, map[string]any{"amount": float64(250), "date": time.Date(next, 7, 2, 0, 0, 0, 0, time.UTC).Format("2006-01-02")}, nearest["point"], "both lots fall on 2 July in Jakarta although they are on different UTC days") assert.Nil(t, nearest["coin"]) - for _, legacy := range []string{"total_points", "total_tokens", "points_history", "tokens_history", "last_updated"} { + for _, legacy := range []string{"total_points", "points_history", "last_updated"} { assert.Contains(t, w, legacy) } + assert.NotContains(t, w, "total_tokens", "tokens are gone; EnakCoin is coin_balance") assert.EqualValues(t, 250, w["total_points"]) recent := w["recent_transactions"].([]any) diff --git a/internal/mappers/campaign_mapper.go b/internal/mappers/campaign_mapper.go index ffd2940..f662108 100644 --- a/internal/mappers/campaign_mapper.go +++ b/internal/mappers/campaign_mapper.go @@ -71,7 +71,7 @@ func ToCampaignEntity(request *contract.CreateCampaignRequest) *entities.Campaig return &entities.Campaign{ Name: request.Name, Description: request.Description, - Type: entities.NormalizeCampaignType(request.Type), + Type: entities.CampaignType(request.Type), StartDate: request.StartDate, EndDate: request.EndDate, IsActive: request.IsActive, @@ -91,7 +91,7 @@ func ToCampaignEntityFromUpdate(request *contract.UpdateCampaignRequest) *entiti ID: request.ID, Name: request.Name, Description: request.Description, - Type: entities.NormalizeCampaignType(request.Type), + Type: entities.CampaignType(request.Type), StartDate: request.StartDate, EndDate: request.EndDate, IsActive: request.IsActive, @@ -118,7 +118,7 @@ func ToCampaignRuleEntity(request *contract.CampaignRuleStruct, campaignID uuid. CampaignID: campaignID, RuleType: entities.RuleType(request.RuleType), ConditionValue: request.ConditionValue, - RewardType: entities.NormalizeCampaignRewardType(request.RewardType), + RewardType: entities.CampaignRewardType(request.RewardType), RewardValue: request.RewardValue, RewardSubtype: rewardSubtype, RewardRefID: request.RewardRefID, @@ -142,7 +142,7 @@ func ToCampaignRuleEntityFromUpdate(request *contract.CampaignRuleStruct, campai CampaignID: campaignID, RuleType: entities.RuleType(request.RuleType), ConditionValue: request.ConditionValue, - RewardType: entities.NormalizeCampaignRewardType(request.RewardType), + RewardType: entities.CampaignRewardType(request.RewardType), RewardValue: request.RewardValue, RewardSubtype: rewardSubtype, RewardRefID: request.RewardRefID, diff --git a/internal/mappers/spin_game_mapper.go b/internal/mappers/spin_game_mapper.go index dd4b184..b38be64 100644 --- a/internal/mappers/spin_game_mapper.go +++ b/internal/mappers/spin_game_mapper.go @@ -36,10 +36,9 @@ func SpinGameResponseDataModelToContract(data *models.SpinGameResponseData) *con } return &contract.SpinGameResponseData{ - GamePlay: GamePlayResponseModelToContract(&data.GamePlay), - PrizeWon: CustomerGamePrizeResponseModelToContract(data.PrizeWon), - CoinsRemaining: data.CoinsRemaining, - TokensRemaining: data.CoinsRemaining, + GamePlay: GamePlayResponseModelToContract(&data.GamePlay), + PrizeWon: CustomerGamePrizeResponseModelToContract(data.PrizeWon), + CoinsRemaining: data.CoinsRemaining, } } @@ -55,7 +54,6 @@ func GamePlayResponseModelToContract(resp *models.GamePlayResponse) contract.Gam CustomerID: resp.CustomerID, PrizeID: resp.PrizeID, CoinsUsed: resp.CoinsUsed, - TokenUsed: resp.CoinsUsed, RandomSeed: resp.RandomSeed, CreatedAt: resp.CreatedAt, Game: nil, // Optional field - can be populated separately if needed diff --git a/internal/models/campaign.go b/internal/models/campaign.go index 9720694..2b14007 100644 --- a/internal/models/campaign.go +++ b/internal/models/campaign.go @@ -11,7 +11,7 @@ import ( type CreateCampaignRequest struct { Name string `json:"name" binding:"required,min=1,max=150"` Description *string `json:"description,omitempty"` - Type string `json:"type" binding:"required,oneof=REWARD POINTS COINS TOKENS MIXED"` + Type string `json:"type" binding:"required,oneof=REWARD POINTS COINS MIXED"` StartDate time.Time `json:"start_date" binding:"required"` EndDate time.Time `json:"end_date" binding:"required"` IsActive bool `json:"is_active"` @@ -25,7 +25,7 @@ type UpdateCampaignRequest struct { ID uuid.UUID `json:"id" binding:"required"` Name string `json:"name" binding:"required,min=1,max=150"` Description *string `json:"description,omitempty"` - Type string `json:"type" binding:"required,oneof=REWARD POINTS COINS TOKENS MIXED"` + Type string `json:"type" binding:"required,oneof=REWARD POINTS COINS MIXED"` StartDate time.Time `json:"start_date" binding:"required"` EndDate time.Time `json:"end_date" binding:"required"` IsActive bool `json:"is_active"` @@ -88,7 +88,7 @@ type CreateCampaignRuleRequest struct { CampaignID uuid.UUID `json:"campaign_id" binding:"required"` RuleType string `json:"rule_type" binding:"required,oneof=TIER SPEND PRODUCT CATEGORY DAY LOCATION"` ConditionValue *string `json:"condition_value,omitempty"` - RewardType string `json:"reward_type" binding:"required,oneof=POINTS COINS TOKENS REWARD"` + RewardType string `json:"reward_type" binding:"required,oneof=POINTS COINS REWARD"` RewardValue *int64 `json:"reward_value,omitempty"` RewardSubtype *string `json:"reward_subtype,omitempty"` RewardRefID *uuid.UUID `json:"reward_ref_id,omitempty"` @@ -100,7 +100,7 @@ type UpdateCampaignRuleRequest struct { CampaignID uuid.UUID `json:"campaign_id" binding:"required"` RuleType string `json:"rule_type" binding:"required,oneof=TIER SPEND PRODUCT CATEGORY DAY LOCATION"` ConditionValue *string `json:"condition_value,omitempty"` - RewardType string `json:"reward_type" binding:"required,oneof=POINTS COINS TOKENS REWARD"` + RewardType string `json:"reward_type" binding:"required,oneof=POINTS COINS REWARD"` RewardValue *int64 `json:"reward_value,omitempty"` RewardSubtype *string `json:"reward_subtype,omitempty"` RewardRefID *uuid.UUID `json:"reward_ref_id,omitempty"` @@ -126,7 +126,7 @@ type ListCampaignRulesResponse struct { type CampaignRuleStruct struct { RuleType string `json:"rule_type" binding:"required,oneof=TIER SPEND PRODUCT CATEGORY DAY LOCATION"` ConditionValue *string `json:"condition_value,omitempty"` - RewardType string `json:"reward_type" binding:"required,oneof=POINTS COINS TOKENS REWARD"` + RewardType string `json:"reward_type" binding:"required,oneof=POINTS COINS REWARD"` RewardValue *int64 `json:"reward_value,omitempty"` RewardSubtype *string `json:"reward_subtype,omitempty"` RewardRefID *uuid.UUID `json:"reward_ref_id,omitempty"` diff --git a/internal/models/customer_points.go b/internal/models/customer_points.go index 9e1c84f..75e7916 100644 --- a/internal/models/customer_points.go +++ b/internal/models/customer_points.go @@ -25,26 +25,6 @@ type PointsHistoryItem struct { CreatedAt time.Time `json:"created_at"` } -type GetCustomerTokensResponse struct { - Status string `json:"status"` - Message string `json:"message"` - Data *GetCustomerTokensResponseData `json:"data,omitempty"` -} - -type GetCustomerTokensResponseData struct { - TotalTokens int64 `json:"total_tokens"` - TokensHistory []TokensHistoryItem `json:"tokens_history,omitempty"` - LastUpdated time.Time `json:"last_updated"` -} - -type TokensHistoryItem struct { - ID string `json:"id"` - Tokens int64 `json:"tokens"` - Type string `json:"type"` // EARNED, REDEEMED, EXPIRED - Description string `json:"description"` - CreatedAt time.Time `json:"created_at"` -} - type GetCustomerWalletResponse struct { Status string `json:"status"` Message string `json:"message"` @@ -66,8 +46,6 @@ type GetCustomerWalletResponseData struct { // Deprecated: the shape of this endpoint before the wallet, kept for app versions // that still read it. Filled from the wallet like the fields above. TotalPoints int64 `json:"total_points"` - TotalTokens int64 `json:"total_tokens"` PointsHistory []PointsHistoryItem `json:"points_history,omitempty"` - TokensHistory []TokensHistoryItem `json:"tokens_history,omitempty"` LastUpdated time.Time `json:"last_updated"` } diff --git a/internal/models/game_play.go b/internal/models/game_play.go index f2378b3..db62ce3 100644 --- a/internal/models/game_play.go +++ b/internal/models/game_play.go @@ -33,7 +33,7 @@ type ListGamePlaysQuery struct { 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 coins_used token_used"` + SortBy string `query:"sort_by" validate:"omitempty,oneof=created_at coins_used"` SortOrder string `query:"sort_order" validate:"omitempty,oneof=asc desc"` } diff --git a/internal/processor/campaign_processor.go b/internal/processor/campaign_processor.go index 57f2c8f..2823a26 100644 --- a/internal/processor/campaign_processor.go +++ b/internal/processor/campaign_processor.go @@ -70,7 +70,7 @@ func (p *campaignProcessor) ListCampaigns(ctx context.Context, req *contract.Lis Page: req.Page, Limit: req.Limit, Search: req.Search, - Type: string(entities.NormalizeCampaignType(req.Type)), + Type: req.Type, IsActive: req.IsActive, ShowOnApp: req.ShowOnApp, StartDate: req.StartDate, @@ -178,7 +178,7 @@ func (p *campaignRuleProcessor) CreateCampaignRule(ctx context.Context, req *con CampaignID: req.CampaignID, RuleType: entities.RuleType(req.RuleType), ConditionValue: req.ConditionValue, - RewardType: entities.NormalizeCampaignRewardType(req.RewardType), + RewardType: entities.CampaignRewardType(req.RewardType), RewardValue: req.RewardValue, RewardSubtype: (*entities.RewardSubtype)(req.RewardSubtype), RewardRefID: req.RewardRefID, @@ -218,7 +218,7 @@ func (p *campaignRuleProcessor) ListCampaignRules(ctx context.Context, req *cont Limit: req.Limit, CampaignID: req.CampaignID, RuleType: req.RuleType, - RewardType: string(entities.NormalizeCampaignRewardType(req.RewardType)), + RewardType: req.RewardType, } // Get from repository @@ -247,7 +247,7 @@ func (p *campaignRuleProcessor) UpdateCampaignRule(ctx context.Context, req *con CampaignID: req.CampaignID, RuleType: entities.RuleType(req.RuleType), ConditionValue: req.ConditionValue, - RewardType: entities.NormalizeCampaignRewardType(req.RewardType), + RewardType: entities.CampaignRewardType(req.RewardType), RewardValue: req.RewardValue, RewardSubtype: (*entities.RewardSubtype)(req.RewardSubtype), RewardRefID: req.RewardRefID, diff --git a/internal/processor/customer_points_processor.go b/internal/processor/customer_points_processor.go index a5a96aa..6e6095e 100644 --- a/internal/processor/customer_points_processor.go +++ b/internal/processor/customer_points_processor.go @@ -22,7 +22,7 @@ func NewCustomerPointsProcessor(walletQuery *WalletQueryProcessor, gameRepo *rep } } -// The three endpoints below are the customer app's balance screens. They read the +// The endpoints below are the customer app's balance screens. They read the // EnakPoint & EnakCoin wallet; customer_points and customer_tokens are no longer used // for balances (docs/prd-point-coin.md §9). @@ -42,22 +42,6 @@ func (p *CustomerPointsProcessor) GetCustomerTotalPointsAPI(ctx context.Context, }, nil } -func (p *CustomerPointsProcessor) GetCustomerTotalTokensAPI(ctx context.Context, customerID string) (*models.GetCustomerTokensResponse, error) { - id, err := parseWalletCustomerID(customerID) - if err != nil { - return nil, err - } - data, err := p.walletQuery.LegacyTokens(ctx, id) - if err != nil { - return nil, fmt.Errorf("failed to get customer tokens: %w", err) - } - return &models.GetCustomerTokensResponse{ - Status: "SUCCESS", - Message: "Customer tokens retrieved successfully.", - Data: data, - }, nil -} - func (p *CustomerPointsProcessor) GetCustomerWalletAPI(ctx context.Context, customerID string) (*models.GetCustomerWalletResponse, error) { id, err := parseWalletCustomerID(customerID) if err != nil { diff --git a/internal/processor/game_play_processor.go b/internal/processor/game_play_processor.go index 15e3337..e6fef4f 100644 --- a/internal/processor/game_play_processor.go +++ b/internal/processor/game_play_processor.go @@ -99,10 +99,6 @@ func (p *GamePlayProcessor) ListGamePlays(ctx context.Context, query *models.Lis } offset := (query.Page - 1) * query.Limit - // The column was renamed; the old name still sorts the same way. - if query.SortBy == "token_used" { - query.SortBy = "coins_used" - } // Get game plays from repository gamePlays, total, err := p.gamePlayRepo.List( diff --git a/internal/processor/wallet_query_processor.go b/internal/processor/wallet_query_processor.go index 3a83fb6..7812191 100644 --- a/internal/processor/wallet_query_processor.go +++ b/internal/processor/wallet_query_processor.go @@ -82,10 +82,6 @@ func (p *WalletQueryProcessor) Summary(ctx context.Context, customerID uuid.UUID if err != nil { return nil, err } - tokens, err := p.legacyTokens(ctx, customerID, balances) - if err != nil { - return nil, err - } data := &models.GetCustomerWalletResponseData{ PointBalance: balances[constants.WalletCurrencyPoint], @@ -94,13 +90,10 @@ func (p *WalletQueryProcessor) Summary(ctx context.Context, customerID uuid.UUID PointDiscountValue: balances[constants.WalletCurrencyPoint] * pointValue, RecentTransactions: recent, TotalPoints: points.TotalPoints, - TotalTokens: tokens.TotalTokens, PointsHistory: points.PointsHistory, - TokensHistory: tokens.TokensHistory, - LastUpdated: points.LastUpdated, } - if tokens.LastUpdated.After(data.LastUpdated) { - data.LastUpdated = tokens.LastUpdated + if len(recent) > 0 { + data.LastUpdated = recent[0].CreatedAt } for _, e := range expiring { entry := &models.CustomerWalletExpiring{Amount: e.Amount, Date: e.Date} @@ -161,32 +154,6 @@ func (p *WalletQueryProcessor) legacyPoints(ctx context.Context, customerID uuid return data, nil } -// LegacyTokens is the pre-wallet GET /customer/tokens. Tokens are EnakCoin now. -func (p *WalletQueryProcessor) LegacyTokens(ctx context.Context, customerID uuid.UUID) (*models.GetCustomerTokensResponseData, error) { - balances, err := p.repo.SpendableBalances(ctx, customerID, p.now()) - if err != nil { - return nil, err - } - return p.legacyTokens(ctx, customerID, balances) -} - -func (p *WalletQueryProcessor) legacyTokens(ctx context.Context, customerID uuid.UUID, balances map[string]int64) (*models.GetCustomerTokensResponseData, error) { - recent, err := p.recent(ctx, customerID, constants.WalletCurrencyCoin, walletLegacyHistorySize) - if err != nil { - return nil, err - } - data := &models.GetCustomerTokensResponseData{TotalTokens: balances[constants.WalletCurrencyCoin]} - for _, tx := range recent { - data.TokensHistory = append(data.TokensHistory, models.TokensHistoryItem{ - ID: tx.ID.String(), Tokens: tx.Amount, Type: tx.Type, Description: tx.Description, CreatedAt: tx.CreatedAt, - }) - } - if len(recent) > 0 { - data.LastUpdated = recent[0].CreatedAt - } - return data, nil -} - func (p *WalletQueryProcessor) recent(ctx context.Context, customerID uuid.UUID, currency string, limit int) ([]models.CustomerWalletTransaction, error) { rows, _, err := p.repo.ListTransactions(ctx, repository.WalletTransactionFilter{ CustomerID: customerID, Currency: currency, Limit: limit, diff --git a/internal/processor/wallet_query_processor_test.go b/internal/processor/wallet_query_processor_test.go index bf6def7..bc24cf2 100644 --- a/internal/processor/wallet_query_processor_test.go +++ b/internal/processor/wallet_query_processor_test.go @@ -122,7 +122,6 @@ func TestWalletQueryProcessor_SummaryShowsWhereEachRowCameFromOrWent(t *testing. // The pre-wallet fields are still filled, from the wallet. assert.Equal(t, int64(250), data.TotalPoints) - assert.Equal(t, int64(8), data.TotalTokens) require.Len(t, data.PointsHistory, 2) assert.Equal(t, int64(-50), data.PointsHistory[0].Points) assert.Equal(t, constants.WalletTxTypePayment, data.PointsHistory[0].Type) @@ -174,12 +173,6 @@ func TestWalletQueryProcessor_LegacyEndpointsSplitByCurrency(t *testing.T) { assert.Equal(t, int64(100), points.TotalPoints) require.Len(t, points.PointsHistory, 1) assert.Equal(t, int64(100), points.PointsHistory[0].Points) - - tokens, err := p.LegacyTokens(context.Background(), uuid.New()) - require.NoError(t, err) - assert.Equal(t, int64(8), tokens.TotalTokens) - require.Len(t, tokens.TokensHistory, 1) - assert.Equal(t, int64(8), tokens.TokensHistory[0].Tokens) } func TestWalletQueryProcessor_TransactionsQuery(t *testing.T) { diff --git a/internal/router/router.go b/internal/router/router.go index a06e26e..f72a8c5 100644 --- a/internal/router/router.go +++ b/internal/router/router.go @@ -169,7 +169,6 @@ func (r *Router) addAppRoutes(rg *gin.Engine) { customer.Use(r.customerAuthMiddleware.ValidateCustomerToken()) { customer.GET("/points", r.customerPointsHandler.GetCustomerPoints) - customer.GET("/tokens", r.customerPointsHandler.GetCustomerTokens) customer.GET("/wallet", r.customerPointsHandler.GetCustomerWallet) customer.GET("/wallet/transactions", r.customerPointsHandler.GetCustomerWalletTransactions) customer.GET("/wallet/expiring", r.customerPointsHandler.GetCustomerWalletExpiring) diff --git a/internal/service/campaign_service.go b/internal/service/campaign_service.go index bc42ba2..5e76fda 100644 --- a/internal/service/campaign_service.go +++ b/internal/service/campaign_service.go @@ -177,7 +177,7 @@ func (s *campaignService) CreateCampaignRule(ctx context.Context, req *contract. } // Validate reward value based on reward type - if req.RewardType == "POINTS" || req.RewardType == "COINS" || req.RewardType == "TOKENS" { + if req.RewardType == "POINTS" || req.RewardType == "COINS" { if req.RewardValue == nil || *req.RewardValue <= 0 { return nil, fmt.Errorf("reward value must be positive for %s type", req.RewardType) } @@ -247,7 +247,7 @@ func (s *campaignService) UpdateCampaignRule(ctx context.Context, req *contract. } // Validate reward value based on reward type - if req.RewardType == "POINTS" || req.RewardType == "COINS" || req.RewardType == "TOKENS" { + if req.RewardType == "POINTS" || req.RewardType == "COINS" { if req.RewardValue == nil || *req.RewardValue <= 0 { return nil, fmt.Errorf("reward value must be positive for %s type", req.RewardType) } @@ -295,7 +295,7 @@ func (s *campaignService) GetCampaignRulesByCampaignID(ctx context.Context, camp } func (s *campaignService) validateCampaignType(campaignType string) error { - validTypes := []string{"REWARD", "POINTS", "COINS", "TOKENS", "MIXED"} + validTypes := []string{"REWARD", "POINTS", "COINS", "MIXED"} for _, validType := range validTypes { if campaignType == validType { return nil @@ -335,7 +335,7 @@ func (s *campaignService) validateCampaignRules(rules []contract.CampaignRuleStr } // Validate reward value based on reward type - if rule.RewardType == "POINTS" || rule.RewardType == "COINS" || rule.RewardType == "TOKENS" { + if rule.RewardType == "POINTS" || rule.RewardType == "COINS" { if rule.RewardValue == nil || *rule.RewardValue <= 0 { return fmt.Errorf("reward value must be positive for %s type in rule %d", rule.RewardType, i+1) } @@ -365,7 +365,7 @@ func (s *campaignService) validateRuleType(ruleType string) error { } func (s *campaignService) validateRewardType(rewardType string) error { - validTypes := []string{"POINTS", "COINS", "TOKENS", "REWARD"} + validTypes := []string{"POINTS", "COINS", "REWARD"} for _, validType := range validTypes { if rewardType == validType { return nil diff --git a/internal/service/customer_points_service.go b/internal/service/customer_points_service.go index 6e85854..33c0728 100644 --- a/internal/service/customer_points_service.go +++ b/internal/service/customer_points_service.go @@ -10,7 +10,6 @@ import ( type CustomerPointsService interface { GetCustomerPoints(ctx context.Context, customerID string) (*models.GetCustomerPointsResponse, error) - GetCustomerTokens(ctx context.Context, customerID string) (*models.GetCustomerTokensResponse, error) GetCustomerWallet(ctx context.Context, customerID string) (*models.GetCustomerWalletResponse, error) GetCustomerWalletTransactions(ctx context.Context, customerID string, query models.ListCustomerWalletTransactionsQuery) (*models.PaginatedResponse[models.CustomerWalletTransaction], error) GetCustomerWalletExpiring(ctx context.Context, customerID string) (*models.CustomerWalletExpiringList, error) @@ -41,19 +40,6 @@ func (s *customerPointsService) GetCustomerPoints(ctx context.Context, customerI return response, nil } -func (s *customerPointsService) GetCustomerTokens(ctx context.Context, customerID string) (*models.GetCustomerTokensResponse, error) { - if customerID == "" { - return nil, fmt.Errorf("customer ID is required") - } - - response, err := s.customerPointsProcessor.GetCustomerTotalTokensAPI(ctx, customerID) - if err != nil { - return nil, fmt.Errorf("failed to get customer tokens: %w", err) - } - - return response, nil -} - func (s *customerPointsService) GetCustomerWallet(ctx context.Context, customerID string) (*models.GetCustomerWalletResponse, error) { if customerID == "" { return nil, fmt.Errorf("customer ID is required") diff --git a/internal/transformer/gamification_transformer.go b/internal/transformer/gamification_transformer.go index 0b66a63..8c17e3c 100644 --- a/internal/transformer/gamification_transformer.go +++ b/internal/transformer/gamification_transformer.go @@ -276,7 +276,6 @@ func GamePlayModelToResponse(model *models.GamePlayResponse) *contract.GamePlayR CustomerID: model.CustomerID, PrizeID: model.PrizeID, CoinsUsed: model.CoinsUsed, - TokenUsed: model.CoinsUsed, RandomSeed: model.RandomSeed, CreatedAt: model.CreatedAt, Game: game, @@ -306,10 +305,9 @@ func PlayGameModelToResponse(model *models.PlayGameResponse) *contract.PlayGameR } return &contract.PlayGameResponse{ - GamePlay: gamePlayValue, - PrizeWon: prizeWon, - CoinsRemaining: model.CoinsRemaining, - TokensRemaining: model.CoinsRemaining, + GamePlay: gamePlayValue, + PrizeWon: prizeWon, + CoinsRemaining: model.CoinsRemaining, } } diff --git a/internal/validator/campaign_validator.go b/internal/validator/campaign_validator.go index 8618fc8..fedcf77 100644 --- a/internal/validator/campaign_validator.go +++ b/internal/validator/campaign_validator.go @@ -254,7 +254,7 @@ func (v *CampaignValidatorImpl) ValidateDeleteCampaignRuleRequest(req *contract. } func (v *CampaignValidatorImpl) isValidCampaignType(campaignType string) bool { - validTypes := []string{"REWARD", "POINTS", "COINS", "TOKENS", "MIXED"} + validTypes := []string{"REWARD", "POINTS", "COINS", "MIXED"} return contains(validTypes, campaignType) } @@ -264,7 +264,7 @@ func (v *CampaignValidatorImpl) isValidRuleType(ruleType string) bool { } func (v *CampaignValidatorImpl) isValidRewardType(rewardType string) bool { - validTypes := []string{"POINTS", "COINS", "TOKENS", "REWARD"} + validTypes := []string{"POINTS", "COINS", "REWARD"} return contains(validTypes, rewardType) } @@ -297,7 +297,7 @@ func (v *CampaignValidatorImpl) validateCampaignRule(rule *contract.CampaignRule } // Validate reward value based on reward type - if rule.RewardType == "POINTS" || rule.RewardType == "COINS" || rule.RewardType == "TOKENS" { + if rule.RewardType == "POINTS" || rule.RewardType == "COINS" { if rule.RewardValue == nil || *rule.RewardValue <= 0 { return errors.New("reward value must be positive for " + rule.RewardType + " type in rule " + string(rune(ruleNumber))) }