diff --git a/cmd/main.go b/cmd/main.go index b13d7b9..bbe042e 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -115,6 +115,7 @@ func main() { wallet.WalletStore(store), wallet.TransferStore(store), notificatioStore, + notificationSvc, logger, ) @@ -206,22 +207,22 @@ func main() { httpserver.StartTicketCrons(*ticketSvc) // Fetch companies and branches for live wallet metrics update - ctx := context.Background() + // ctx := context.Background() - companies := []domain.GetCompany{ - {ID: 1, Name: "Company A", WalletBalance: 1000.0}, - } + // companies := []domain.GetCompany{ + // {ID: 1, Name: "Company A", WalletBalance: 1000.0}, + // } - branches := []domain.BranchWallet{ - {ID: 10, Name: "Branch Z", CompanyID: 1, Balance: 500.0}, - } + // branches := []domain.BranchWallet{ + // {ID: 10, Name: "Branch Z", CompanyID: 1, Balance: 500.0}, + // } - notificationSvc.UpdateLiveWalletMetrics(ctx, companies, branches) - if err != nil { - log.Println("Failed to update live metrics:", err) - } else { - log.Println("Live metrics broadcasted successfully") - } + // notificationSvc.UpdateLiveWalletMetrics(ctx, companies, branches) + // if err != nil { + // log.Println("Failed to update live metrics:", err) + // } else { + // log.Println("Live metrics broadcasted successfully") + // } issueReportingRepo := repository.NewReportedIssueRepository(store) diff --git a/db/migrations/000001_fortune.up.sql b/db/migrations/000001_fortune.up.sql index 57fd09a..981471c 100644 --- a/db/migrations/000001_fortune.up.sql +++ b/db/migrations/000001_fortune.up.sql @@ -295,7 +295,8 @@ CREATE TABLE IF NOT EXISTS settings ( ); CREATE TABLE bonus ( id BIGSERIAL PRIMARY KEY, - multiplier REAL NOT NULL + multiplier REAL NOT NULL, + balance_cap BIGINT NOT NULL DEFAULT 0 ); -- Views CREATE VIEW companies_details AS diff --git a/db/query/bonus.sql b/db/query/bonus.sql index c516162..82b3113 100644 --- a/db/query/bonus.sql +++ b/db/query/bonus.sql @@ -1,12 +1,17 @@ -- name: CreateBonusMultiplier :exec -INSERT INTO bonus (multiplier) -VALUES ($1); +INSERT INTO bonus (multiplier, balance_cap) +VALUES ($1, $2); -- name: GetBonusMultiplier :many SELECT id, multiplier FROM bonus; +-- name: GetBonusBalanceCap :many +SELECT id, balance_cap +FROM bonus; + -- name: UpdateBonusMultiplier :exec UPDATE bonus -SET multiplier = $1 -WHERE id = $2; \ No newline at end of file +SET multiplier = $1, + balance_cap = $2 +WHERE id = $3; \ No newline at end of file diff --git a/db/query/referal.sql b/db/query/referal.sql index a10b274..206606e 100644 --- a/db/query/referal.sql +++ b/db/query/referal.sql @@ -40,7 +40,6 @@ WHERE referrer_id = $1; -- name: GetReferralSettings :one SELECT * FROM referral_settings -WHERE id = 'default' LIMIT 1; -- name: UpdateReferralSettings :one @@ -70,3 +69,9 @@ INSERT INTO referral_settings ( -- name: GetReferralByReferredID :one SELECT * FROM referrals WHERE referred_id = $1 LIMIT 1; + +-- name: GetActiveReferralByReferrerID :one +SELECT * FROM referrals WHERE referrer_id = $1 AND status = 'PENDING' LIMIT 1; + +-- name: GetReferralCountByID :one +SELECT count(*) FROM referrals WHERE referrer_id = $1; \ No newline at end of file diff --git a/gen/db/bonus.sql.go b/gen/db/bonus.sql.go index 21ef5c7..12677b8 100644 --- a/gen/db/bonus.sql.go +++ b/gen/db/bonus.sql.go @@ -10,29 +10,69 @@ import ( ) const CreateBonusMultiplier = `-- name: CreateBonusMultiplier :exec -INSERT INTO bonus (multiplier) -VALUES ($1) +INSERT INTO bonus (multiplier, balance_cap) +VALUES ($1, $2) ` -func (q *Queries) CreateBonusMultiplier(ctx context.Context, multiplier float32) error { - _, err := q.db.Exec(ctx, CreateBonusMultiplier, multiplier) +type CreateBonusMultiplierParams struct { + Multiplier float32 `json:"multiplier"` + BalanceCap int64 `json:"balance_cap"` +} + +func (q *Queries) CreateBonusMultiplier(ctx context.Context, arg CreateBonusMultiplierParams) error { + _, err := q.db.Exec(ctx, CreateBonusMultiplier, arg.Multiplier, arg.BalanceCap) return err } +const GetBonusBalanceCap = `-- name: GetBonusBalanceCap :many +SELECT id, balance_cap +FROM bonus +` + +type GetBonusBalanceCapRow struct { + ID int64 `json:"id"` + BalanceCap int64 `json:"balance_cap"` +} + +func (q *Queries) GetBonusBalanceCap(ctx context.Context) ([]GetBonusBalanceCapRow, error) { + rows, err := q.db.Query(ctx, GetBonusBalanceCap) + if err != nil { + return nil, err + } + defer rows.Close() + var items []GetBonusBalanceCapRow + for rows.Next() { + var i GetBonusBalanceCapRow + if err := rows.Scan(&i.ID, &i.BalanceCap); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + const GetBonusMultiplier = `-- name: GetBonusMultiplier :many SELECT id, multiplier FROM bonus ` -func (q *Queries) GetBonusMultiplier(ctx context.Context) ([]Bonu, error) { +type GetBonusMultiplierRow struct { + ID int64 `json:"id"` + Multiplier float32 `json:"multiplier"` +} + +func (q *Queries) GetBonusMultiplier(ctx context.Context) ([]GetBonusMultiplierRow, error) { rows, err := q.db.Query(ctx, GetBonusMultiplier) if err != nil { return nil, err } defer rows.Close() - var items []Bonu + var items []GetBonusMultiplierRow for rows.Next() { - var i Bonu + var i GetBonusMultiplierRow if err := rows.Scan(&i.ID, &i.Multiplier); err != nil { return nil, err } @@ -46,16 +86,18 @@ func (q *Queries) GetBonusMultiplier(ctx context.Context) ([]Bonu, error) { const UpdateBonusMultiplier = `-- name: UpdateBonusMultiplier :exec UPDATE bonus -SET multiplier = $1 -WHERE id = $2 +SET multiplier = $1, + balance_cap = $2 +WHERE id = $3 ` type UpdateBonusMultiplierParams struct { Multiplier float32 `json:"multiplier"` + BalanceCap int64 `json:"balance_cap"` ID int64 `json:"id"` } func (q *Queries) UpdateBonusMultiplier(ctx context.Context, arg UpdateBonusMultiplierParams) error { - _, err := q.db.Exec(ctx, UpdateBonusMultiplier, arg.Multiplier, arg.ID) + _, err := q.db.Exec(ctx, UpdateBonusMultiplier, arg.Multiplier, arg.BalanceCap, arg.ID) return err } diff --git a/gen/db/models.go b/gen/db/models.go index 186429f..f139a7e 100644 --- a/gen/db/models.go +++ b/gen/db/models.go @@ -131,6 +131,7 @@ type BetWithOutcome struct { type Bonu struct { ID int64 `json:"id"` Multiplier float32 `json:"multiplier"` + BalanceCap int64 `json:"balance_cap"` } type Branch struct { diff --git a/gen/db/referal.sql.go b/gen/db/referal.sql.go index 3a7f337..b5ceeed 100644 --- a/gen/db/referal.sql.go +++ b/gen/db/referal.sql.go @@ -102,6 +102,28 @@ func (q *Queries) CreateReferralSettings(ctx context.Context, arg CreateReferral return i, err } +const GetActiveReferralByReferrerID = `-- name: GetActiveReferralByReferrerID :one +SELECT id, referral_code, referrer_id, referred_id, status, reward_amount, cashback_amount, created_at, updated_at, expires_at FROM referrals WHERE referrer_id = $1 AND status = 'PENDING' LIMIT 1 +` + +func (q *Queries) GetActiveReferralByReferrerID(ctx context.Context, referrerID string) (Referral, error) { + row := q.db.QueryRow(ctx, GetActiveReferralByReferrerID, referrerID) + var i Referral + err := row.Scan( + &i.ID, + &i.ReferralCode, + &i.ReferrerID, + &i.ReferredID, + &i.Status, + &i.RewardAmount, + &i.CashbackAmount, + &i.CreatedAt, + &i.UpdatedAt, + &i.ExpiresAt, + ) + return i, err +} + const GetReferralByCode = `-- name: GetReferralByCode :one SELECT id, referral_code, referrer_id, referred_id, status, reward_amount, cashback_amount, created_at, updated_at, expires_at FROM referrals WHERE referral_code = $1 @@ -147,9 +169,19 @@ func (q *Queries) GetReferralByReferredID(ctx context.Context, referredID pgtype return i, err } +const GetReferralCountByID = `-- name: GetReferralCountByID :one +SELECT count(*) FROM referrals WHERE referrer_id = $1 +` + +func (q *Queries) GetReferralCountByID(ctx context.Context, referrerID string) (int64, error) { + row := q.db.QueryRow(ctx, GetReferralCountByID, referrerID) + var count int64 + err := row.Scan(&count) + return count, err +} + const GetReferralSettings = `-- name: GetReferralSettings :one SELECT id, referral_reward_amount, cashback_percentage, bet_referral_bonus_percentage, max_referrals, expires_after_days, updated_by, created_at, updated_at, version FROM referral_settings -WHERE id = 'default' LIMIT 1 ` diff --git a/internal/domain/referal.go b/internal/domain/referal.go index 9923806..1e528a4 100644 --- a/internal/domain/referal.go +++ b/internal/domain/referal.go @@ -51,6 +51,14 @@ type ReferralSettings struct { Version int32 } +type ReferralSettingsReq struct { + ReferralRewardAmount float64 `json:"referral_reward_amount" validate:"required"` + CashbackPercentage float64 `json:"cashback_percentage" validate:"required"` + MaxReferrals int32 `json:"max_referrals" validate:"required"` + ExpiresAfterDays int32 `json:"expires_afterdays" validate:"required"` + UpdatedBy string `json:"updated_by" validate:"required"` +} + type Referral struct { ID int64 ReferralCode string diff --git a/internal/domain/virtual_game.go b/internal/domain/virtual_game.go index d2174cc..39177ba 100644 --- a/internal/domain/virtual_game.go +++ b/internal/domain/virtual_game.go @@ -269,3 +269,28 @@ type GameRecommendation struct { Bets []float64 `json:"bets"` Reason string `json:"reason"` // e.g., "Based on your activity", "Popular", "Random pick" } + +type PopokLaunchRequest struct { + Action string `json:"action"` + Platform int `json:"platform"` + PartnerID int `json:"partnerId"` + Time string `json:"time"` + Hash string `json:"hash"` + Data PopokLaunchRequestData `json:"data"` +} + +type PopokLaunchRequestData struct { + GameMode string `json:"gameMode"` + GameID string `json:"gameId"` + Lang string `json:"lang"` + Token string `json:"token"` + ExitURL string `json:"exitURL"` +} + +type PopokLaunchResponse struct { + Code int `json:"code"` + Message string `json:"message"` + Data struct { + LauncherURL string `json:"launcherURL"` + } `json:"data"` +} diff --git a/internal/repository/bonus.go b/internal/repository/bonus.go index b253ad2..c4f57ac 100644 --- a/internal/repository/bonus.go +++ b/internal/repository/bonus.go @@ -6,17 +6,25 @@ import ( dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db" ) -func (s *Store) CreateBonusMultiplier(ctx context.Context, multiplier float32) error { - return s.queries.CreateBonusMultiplier(ctx, multiplier) +func (s *Store) CreateBonusMultiplier(ctx context.Context, multiplier float32, balance_cap int64) error { + return s.queries.CreateBonusMultiplier(ctx, dbgen.CreateBonusMultiplierParams{ + Multiplier: multiplier, + BalanceCap: balance_cap, + }) } -func (s *Store) GetBonusMultiplier(ctx context.Context) ([]dbgen.Bonu, error) { +func (s *Store) GetBonusMultiplier(ctx context.Context) ([]dbgen.GetBonusMultiplierRow, error) { return s.queries.GetBonusMultiplier(ctx) } -func (s *Store) UpdateBonusMultiplier(ctx context.Context, id int64, mulitplier float32) error { +func (s *Store) GetBonusBalanceCap(ctx context.Context) ([]dbgen.GetBonusBalanceCapRow, error) { + return s.queries.GetBonusBalanceCap(ctx) +} + +func (s *Store) UpdateBonusMultiplier(ctx context.Context, id int64, mulitplier float32, balance_cap int64) error { return s.queries.UpdateBonusMultiplier(ctx, dbgen.UpdateBonusMultiplierParams{ ID: id, Multiplier: mulitplier, + BalanceCap: balance_cap, }) } diff --git a/internal/repository/referal.go b/internal/repository/referal.go index a782cfb..d214c54 100644 --- a/internal/repository/referal.go +++ b/internal/repository/referal.go @@ -4,6 +4,7 @@ import ( "context" "database/sql" "errors" + "fmt" "strconv" dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db" @@ -20,6 +21,8 @@ type ReferralRepository interface { UpdateSettings(ctx context.Context, settings *domain.ReferralSettings) error CreateSettings(ctx context.Context, settings *domain.ReferralSettings) error GetReferralByReferredID(ctx context.Context, referredID string) (*domain.Referral, error) // New method + GetReferralCountByID(ctx context.Context, referrerID string) (int64, error) + GetActiveReferralByReferrerID(ctx context.Context, referrerID string) (*domain.Referral, error) UpdateUserReferalCode(ctx context.Context, codedata domain.UpdateUserReferalCode) error } @@ -145,17 +148,17 @@ func (r *ReferralRepo) UpdateSettings(ctx context.Context, settings *domain.Refe func (r *ReferralRepo) CreateSettings(ctx context.Context, settings *domain.ReferralSettings) error { rewardAmount := pgtype.Numeric{} - if err := rewardAmount.Scan(settings.ReferralRewardAmount); err != nil { + if err := rewardAmount.Scan(fmt.Sprintf("%f", settings.ReferralRewardAmount)); err != nil { return err } cashbackPercentage := pgtype.Numeric{} - if err := cashbackPercentage.Scan(settings.CashbackPercentage); err != nil { + if err := cashbackPercentage.Scan(fmt.Sprintf("%f", settings.CashbackPercentage)); err != nil { return err } betReferralBonusPercentage := pgtype.Numeric{} - if err := betReferralBonusPercentage.Scan(settings.BetReferralBonusPercentage); err != nil { + if err := betReferralBonusPercentage.Scan(fmt.Sprintf("%f", settings.BetReferralBonusPercentage)); err != nil { return err } @@ -183,6 +186,30 @@ func (r *ReferralRepo) GetReferralByReferredID(ctx context.Context, referredID s return r.mapToDomainReferral(&dbReferral), nil } +func (r *ReferralRepo) GetReferralCountByID(ctx context.Context, referrerID string) (int64, error) { + count, err := r.store.queries.GetReferralCountByID(ctx, referrerID) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return 0, nil + } + return 0, err + } + + return count, nil +} + +func (r *ReferralRepo) GetActiveReferralByReferrerID(ctx context.Context, referrerID string) (*domain.Referral, error) { + referral, err := r.store.queries.GetActiveReferralByReferrerID(ctx, referrerID) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return &domain.Referral{}, nil + } + return &domain.Referral{}, err + } + + return r.mapToDomainReferral(&referral), nil +} + func (r *ReferralRepo) mapToDomainReferral(dbRef *dbgen.Referral) *domain.Referral { var referredID *string if dbRef.ReferredID.Valid { diff --git a/internal/services/bonus/port.go b/internal/services/bonus/port.go index 02b59ca..2147b51 100644 --- a/internal/services/bonus/port.go +++ b/internal/services/bonus/port.go @@ -7,7 +7,8 @@ import ( ) type BonusStore interface { - CreateBonusMultiplier(ctx context.Context, multiplier float32) error - GetBonusMultiplier(ctx context.Context) ([]dbgen.Bonu, error) - UpdateBonusMultiplier(ctx context.Context, id int64, mulitplier float32) error + CreateBonusMultiplier(ctx context.Context, multiplier float32, balance_cap int64) error + GetBonusMultiplier(ctx context.Context) ([]dbgen.GetBonusMultiplierRow, error) + GetBonusBalanceCap(ctx context.Context) ([]dbgen.GetBonusBalanceCapRow, error) + UpdateBonusMultiplier(ctx context.Context, id int64, mulitplier float32, balance_cap int64) error } diff --git a/internal/services/bonus/service.go b/internal/services/bonus/service.go index f55107c..51e008a 100644 --- a/internal/services/bonus/service.go +++ b/internal/services/bonus/service.go @@ -16,14 +16,18 @@ func NewService(bonusStore BonusStore) *Service { } } -func (s *Service) CreateBonusMultiplier(ctx context.Context, multiplier float32) error { - return s.bonusStore.CreateBonusMultiplier(ctx, multiplier) +func (s *Service) CreateBonusMultiplier(ctx context.Context, multiplier float32, balance_cap int64) error { + return s.bonusStore.CreateBonusMultiplier(ctx, multiplier, balance_cap) } -func (s *Service) GetBonusMultiplier(ctx context.Context) ([]dbgen.Bonu, error) { +func (s *Service) GetBonusMultiplier(ctx context.Context) ([]dbgen.GetBonusMultiplierRow, error) { return s.bonusStore.GetBonusMultiplier(ctx) } -func (s *Service) UpdateBonusMultiplier(ctx context.Context, id int64, mulitplier float32) error { - return s.bonusStore.UpdateBonusMultiplier(ctx, id, mulitplier) +func (s *Service) GetBonusBalanceCap(ctx context.Context) ([]dbgen.GetBonusBalanceCapRow, error) { + return s.bonusStore.GetBonusBalanceCap(ctx) +} + +func (s *Service) UpdateBonusMultiplier(ctx context.Context, id int64, mulitplier float32, balance_cap int64) error { + return s.bonusStore.UpdateBonusMultiplier(ctx, id, mulitplier, balance_cap) } diff --git a/internal/services/chapa/client.go b/internal/services/chapa/client.go index baac2fd..3beed5b 100644 --- a/internal/services/chapa/client.go +++ b/internal/services/chapa/client.go @@ -31,7 +31,7 @@ func NewClient(baseURL, secretKey string) *Client { func (c *Client) InitializePayment(ctx context.Context, req domain.ChapaDepositRequest) (domain.ChapaDepositResponse, error) { payload := map[string]interface{}{ - "amount": fmt.Sprintf("%.2f", float64(req.Amount)/100), + "amount": fmt.Sprintf("%.2f", float64(req.Amount)), "currency": req.Currency, // "email": req.Email, "first_name": req.FirstName, diff --git a/internal/services/referal/port.go b/internal/services/referal/port.go index 5fb867b..1946e99 100644 --- a/internal/services/referal/port.go +++ b/internal/services/referal/port.go @@ -12,7 +12,9 @@ type ReferralStore interface { ProcessReferral(ctx context.Context, referredID, referralCode string) error ProcessDepositBonus(ctx context.Context, userID string, amount float64) error GetReferralStats(ctx context.Context, userID string) (*domain.ReferralStats, error) + CreateReferralSettings(ctx context.Context, req domain.ReferralSettingsReq) error UpdateReferralSettings(ctx context.Context, settings *domain.ReferralSettings) error GetReferralSettings(ctx context.Context) (*domain.ReferralSettings, error) + GetReferralCountByID(ctx context.Context, referrerID string) (int64, error) ProcessBetReferral(ctx context.Context, userPhone string, betAmount float64) error } diff --git a/internal/services/referal/service.go b/internal/services/referal/service.go index 970a239..f75a809 100644 --- a/internal/services/referal/service.go +++ b/internal/services/referal/service.go @@ -54,16 +54,45 @@ func (s *Service) GenerateReferralCode() (string, error) { func (s *Service) CreateReferral(ctx context.Context, userID int64) error { s.logger.Info("Creating referral code for user", "userID", userID) - // TODO: check in user already has an active referral code + + // check if user already has an active referral code + referral, err := s.repo.GetActiveReferralByReferrerID(ctx, fmt.Sprintf("%d", userID)) + if err != nil { + s.logger.Error("Failed to check if user alredy has active referral code", "error", err) + return err + } + if referral != nil && referral.Status == domain.ReferralPending && referral.ExpiresAt.After(time.Now()) { + s.logger.Error("user already has an active referral code", "error", err) + return err + } + + settings, err := s.GetReferralSettings(ctx) + if err != nil || settings == nil { + s.logger.Error("Failed to fetch referral settings", "error", err) + return err + } + + // check referral count limit + referralCount, err := s.GetReferralCountByID(ctx, fmt.Sprintf("%d", userID)) + if err != nil { + s.logger.Error("Failed to get referral count", "userID", userID, "error", err) + return err + } + + fmt.Println("referralCount: ", referralCount) + if referralCount == int64(settings.MaxReferrals) { + s.logger.Error("referral count limit has been reached", "referralCount", referralCount, "error", err) + return err + } + code, err := s.GenerateReferralCode() if err != nil { s.logger.Error("Failed to generate referral code", "error", err) return err } - // TODO: get the referral settings from db - var rewardAmount float64 = 100 - var expireDuration time.Time = time.Now().Add(24 * time.Hour) + var rewardAmount float64 = settings.ReferralRewardAmount + var expireDuration time.Time = time.Now().Add(time.Duration((24 * settings.ExpiresAfterDays)) * time.Hour) if err := s.repo.CreateReferral(ctx, &domain.Referral{ ReferralCode: code, @@ -249,6 +278,26 @@ func (s *Service) GetReferralStats(ctx context.Context, userPhone string) (*doma return stats, nil } +func (s *Service) CreateReferralSettings(ctx context.Context, req domain.ReferralSettingsReq) error { + s.logger.Info("Creating referral setting") + + if err := s.repo.CreateSettings(ctx, &domain.ReferralSettings{ + ReferralRewardAmount: req.ReferralRewardAmount, + CashbackPercentage: req.CashbackPercentage, + MaxReferrals: req.MaxReferrals, + ExpiresAfterDays: req.ExpiresAfterDays, + UpdatedBy: req.UpdatedBy, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + }); err != nil { + s.logger.Error("Failed to create referral setting", "error", err) + return err + } + + s.logger.Info("Referral setting created succesfully") + return nil +} + func (s *Service) UpdateReferralSettings(ctx context.Context, settings *domain.ReferralSettings) error { s.logger.Info("Updating referral settings", "settingsID", settings.ID) @@ -272,6 +321,16 @@ func (s *Service) GetReferralSettings(ctx context.Context) (*domain.ReferralSett return nil, err } - s.logger.Info("Referral settings retrieved successfully", "settingsID", settings.ID) + s.logger.Info("Referral settings retrieved successfully", "settings", settings) return settings, nil } + +func (s *Service) GetReferralCountByID(ctx context.Context, referrerID string) (int64, error) { + count, err := s.repo.GetReferralCountByID(ctx, referrerID) + if err != nil { + s.logger.Error("Failed to get referral count", "userID", referrerID, "error", err) + return 0, err + } + + return count, nil +} diff --git a/internal/services/report/service.go b/internal/services/report/service.go index 3e047e3..6d4cb6a 100644 --- a/internal/services/report/service.go +++ b/internal/services/report/service.go @@ -465,7 +465,7 @@ func (s *Service) GenerateReport(ctx context.Context, period string) error { return fmt.Errorf("fetch data: %w", err) } - filePath := fmt.Sprintf("/host-desktop/report_%s_%s.csv", period, time.Now().Format("2006-01-02_15-04")) + filePath := fmt.Sprintf("reports/report_%s_%s.csv", period, time.Now().Format("2006-01-02_15-04")) file, err := os.Create(filePath) if err != nil { return fmt.Errorf("create file: %w", err) diff --git a/internal/services/virtualGame/service.go b/internal/services/virtualGame/service.go index 6a412dc..6831945 100644 --- a/internal/services/virtualGame/service.go +++ b/internal/services/virtualGame/service.go @@ -43,21 +43,23 @@ func New(repo repository.VirtualGameRepository, walletSvc wallet.Service, store } func (s *service) GenerateGameLaunchURL(ctx context.Context, userID int64, gameID, currency, mode string) (string, error) { + // 1. Fetch user user, err := s.store.GetUserByID(ctx, userID) if err != nil { s.logger.Error("Failed to get user", "userID", userID, "error", err) return "", err } - sessionId := fmt.Sprintf("%d-%s-%d", userID, gameID, time.Now().UnixNano()) + // 2. Generate session and token + sessionID := fmt.Sprintf("%d-%s-%d", userID, gameID, time.Now().UnixNano()) token, err := jwtutil.CreatePopOKJwt( userID, user.CompanyID, - user.FirstName, + user.PhoneNumber, currency, "en", mode, - sessionId, + sessionID, s.config.PopOK.SecretKey, 24*time.Hour, ) @@ -66,9 +68,9 @@ func (s *service) GenerateGameLaunchURL(ctx context.Context, userID int64, gameI return "", err } - // Record game launch as a transaction (for history and recommendation purposes) - tx := &domain.VirtualGameHistory{ - SessionID: sessionId, // Optional: populate if session tracking is implemented + // 3. Record virtual game history (optional but recommended) + history := &domain.VirtualGameHistory{ + SessionID: sessionID, UserID: userID, CompanyID: user.CompanyID.Value, Provider: string(domain.PROVIDER_POPOK), @@ -76,23 +78,66 @@ func (s *service) GenerateGameLaunchURL(ctx context.Context, userID int64, gameI TransactionType: "LAUNCH", Amount: 0, Currency: currency, - ExternalTransactionID: sessionId, + ExternalTransactionID: sessionID, Status: "COMPLETED", CreatedAt: time.Now(), UpdatedAt: time.Now(), } - - if err := s.repo.CreateVirtualGameHistory(ctx, tx); err != nil { + if err := s.repo.CreateVirtualGameHistory(ctx, history); err != nil { s.logger.Error("Failed to record game launch transaction", "error", err) - // Do not fail game launch on logging error — just log and continue + // Non-fatal: log and continue } - params := fmt.Sprintf( - "partnerId=%s&gameId=%s&gameMode=%s&lang=en&platform=%s&externalToken=%s", - s.config.PopOK.ClientID, gameID, mode, s.config.PopOK.Platform, token, - ) + // 4. Prepare PopOK API request + timestamp := time.Now().Format("02-01-2006 15:04:05") + partnerID, err := strconv.Atoi(s.config.PopOK.ClientID) + if err != nil { + return "", fmt.Errorf("invalid PopOK ClientID: %v", err) + } - return fmt.Sprintf("%s?%s", s.config.PopOK.BaseURL, params), nil + data := domain.PopokLaunchRequestData{ + GameMode: mode, + GameID: gameID, + Lang: "en", + Token: token, + ExitURL: "", + } + + hash, err := generatePopOKHash(s.config.PopOK.SecretKey, timestamp, data) + if err != nil { + return "", fmt.Errorf("failed to generate PopOK hash: %w", err) + } + + platformInt, err := strconv.Atoi(s.config.PopOK.Platform) + if err != nil { + return "", fmt.Errorf("invalid PopOK Platform: %v", err) + } + reqBody := domain.PopokLaunchRequest{ + Action: "getLauncherURL", + Platform: platformInt, + PartnerID: partnerID, + Time: timestamp, + Hash: hash, + Data: data, + } + + // 5. Make API request + bodyBytes, _ := json.Marshal(reqBody) + resp, err := http.Post(s.config.PopOK.BaseURL+"/serviceApi.php", "application/json", bytes.NewReader(bodyBytes)) + if err != nil { + return "", fmt.Errorf("PopOK POST failed: %w", err) + } + defer resp.Body.Close() + + var parsedResp domain.PopokLaunchResponse + if err := json.NewDecoder(resp.Body).Decode(&parsedResp); err != nil { + return "", fmt.Errorf("failed to parse PopOK response: %w", err) + } + if parsedResp.Code != 0 { + return "", fmt.Errorf("PopOK error: %s", parsedResp.Message) + } + + return parsedResp.Data.LauncherURL, nil } func (s *service) HandleCallback(ctx context.Context, callback *domain.PopOKCallback) error { @@ -126,7 +171,7 @@ func (s *service) HandleCallback(ctx context.Context, callback *domain.PopOKCall } walletID := wallets[0].ID - amount := int64(callback.Amount * 100) // Convert to cents + amount := int64(callback.Amount) // Convert to cents transactionType := callback.Type switch transactionType { @@ -187,7 +232,7 @@ func (s *service) GetPlayerInfo(ctx context.Context, req *domain.PopOKPlayerInfo return &domain.PopOKPlayerInfoResponse{ Country: "ET", Currency: claims.Currency, - Balance: float64(wallets[0].Balance) / 100, // Convert cents to currency + Balance: float64(wallets[0].Balance), // Convert cents to currency PlayerID: fmt.Sprintf("%d", claims.UserID), }, nil } @@ -200,7 +245,7 @@ func (s *service) ProcessBet(ctx context.Context, req *domain.PopOKBetRequest) ( } // Convert amount to cents (assuming wallet uses cents) - amountCents := int64(req.Amount * 100) + amountCents := int64(req.Amount) // Deduct from wallet @@ -237,7 +282,7 @@ func (s *service) ProcessBet(ctx context.Context, req *domain.PopOKBetRequest) ( return &domain.PopOKBetResponse{ TransactionID: req.TransactionID, ExternalTrxID: fmt.Sprintf("%v", tx.ID), // Your internal transaction ID - Balance: float64(userWallets[0].Balance) / 100, + Balance: float64(userWallets[0].Balance), }, nil } @@ -263,7 +308,7 @@ func (s *service) ProcessWin(ctx context.Context, req *domain.PopOKWinRequest) ( wallets, _ := s.walletSvc.GetWalletsByUser(ctx, claims.UserID) balance := 0.0 if len(wallets) > 0 { - balance = float64(wallets[0].Balance) / 100 + balance = float64(wallets[0].Balance) } return &domain.PopOKWinResponse{ TransactionID: req.TransactionID, @@ -273,7 +318,7 @@ func (s *service) ProcessWin(ctx context.Context, req *domain.PopOKWinRequest) ( } // 3. Convert amount to cents - amountCents := int64(req.Amount * 100) + amountCents := int64(req.Amount) // 4. Credit to wallet _, err = s.walletSvc.AddToWallet(ctx, claims.UserID, domain.Currency(amountCents), domain.ValidInt64{}, @@ -308,10 +353,12 @@ func (s *service) ProcessWin(ctx context.Context, req *domain.PopOKWinRequest) ( return nil, fmt.Errorf("transaction recording failed") } + fmt.Printf("\n\n Win balance is:%v\n\n", float64(userWallets[0].Balance)) + return &domain.PopOKWinResponse{ TransactionID: req.TransactionID, ExternalTrxID: fmt.Sprintf("%v", tx.ID), - Balance: float64(userWallets[0].Balance) / 100, + Balance: float64(userWallets[0].Balance), }, nil } @@ -334,7 +381,7 @@ func (s *service) ProcessTournamentWin(ctx context.Context, req *domain.PopOKWin wallets, _ := s.walletSvc.GetWalletsByUser(ctx, claims.UserID) balance := 0.0 if len(wallets) > 0 { - balance = float64(wallets[0].Balance) / 100 + balance = float64(wallets[0].Balance) } return &domain.PopOKWinResponse{ TransactionID: req.TransactionID, @@ -344,7 +391,7 @@ func (s *service) ProcessTournamentWin(ctx context.Context, req *domain.PopOKWin } // 3. Convert amount to cents - amountCents := int64(req.Amount * 100) + amountCents := int64(req.Amount) // 4. Credit user wallet _, err = s.walletSvc.AddToWallet(ctx, claims.UserID, domain.Currency(amountCents), domain.ValidInt64{}, domain.TRANSFER_DIRECT, domain.PaymentDetails{}, @@ -379,7 +426,7 @@ func (s *service) ProcessTournamentWin(ctx context.Context, req *domain.PopOKWin return &domain.PopOKWinResponse{ TransactionID: req.TransactionID, ExternalTrxID: fmt.Sprintf("%v", tx.ID), - Balance: float64(wallets[0].Balance) / 100, + Balance: float64(wallets[0].Balance), }, nil } @@ -400,7 +447,7 @@ func (s *service) ProcessPromoWin(ctx context.Context, req *domain.PopOKWinReque wallets, _ := s.walletSvc.GetWalletsByUser(ctx, claims.UserID) balance := 0.0 if len(wallets) > 0 { - balance = float64(wallets[0].Balance) / 100 + balance = float64(wallets[0].Balance) } return &domain.PopOKWinResponse{ TransactionID: req.TransactionID, @@ -440,7 +487,7 @@ func (s *service) ProcessPromoWin(ctx context.Context, req *domain.PopOKWinReque return &domain.PopOKWinResponse{ TransactionID: req.TransactionID, ExternalTrxID: fmt.Sprintf("%v", tx.ID), - Balance: float64(wallets[0].Balance) / 100, + Balance: float64(wallets[0].Balance), }, nil } @@ -506,7 +553,7 @@ func (s *service) ProcessCancel(ctx context.Context, req *domain.PopOKCancelRequ wallets, _ := s.walletSvc.GetWalletsByUser(ctx, claims.UserID) balance := 0.0 if len(wallets) > 0 { - balance = float64(wallets[0].Balance) / 100 + balance = float64(wallets[0].Balance) } return &domain.PopOKCancelResponse{ TransactionID: req.TransactionID, @@ -567,14 +614,23 @@ func (s *service) ProcessCancel(ctx context.Context, req *domain.PopOKCancelRequ return &domain.PopOKCancelResponse{ TransactionID: req.TransactionID, ExternalTrxID: fmt.Sprintf("%v", cancelTx.ID), - Balance: float64(userWallets[0].Balance) / 100, + Balance: float64(userWallets[0].Balance), }, nil } -func (s *service) GenerateSignature(params string) string { - h := hmac.New(sha256.New, []byte(s.config.PopOK.SecretKey)) - h.Write([]byte(params)) - return hex.EncodeToString(h.Sum(nil)) +func generatePopOKHash(privateKey, timestamp string, data domain.PopokLaunchRequestData) (string, error) { + // Marshal data to JSON (compact format, like json_encode in PHP) + dataBytes, err := json.Marshal(data) + if err != nil { + return "", err + } + + // Concatenate: privateKey + time + json_encoded(data) + hashInput := fmt.Sprintf("%s%s%s", privateKey, timestamp, string(dataBytes)) + + // SHA-256 hash + hash := sha256.Sum256([]byte(hashInput)) + return hex.EncodeToString(hash[:]), nil } func (s *service) verifySignature(callback *domain.PopOKCallback) bool { @@ -607,32 +663,46 @@ func (s *service) GetGameCounts(ctx context.Context, filter domain.ReportFilter) func (s *service) ListGames(ctx context.Context, currency string) ([]domain.PopOKGame, error) { now := time.Now().Format("02-01-2006 15:04:05") // dd-mm-yyyy hh:mm:ss - // Calculate hash: sha256(privateKey + time) - rawHash := s.config.PopOK.SecretKey + now - hash := fmt.Sprintf("%x", sha256.Sum256([]byte(rawHash))) - - // Construct request payload - payload := map[string]interface{}{ + // Step 1: Construct payload without the hash + data := map[string]interface{}{ "action": "gameList", "platform": s.config.PopOK.Platform, "partnerId": s.config.PopOK.ClientID, "currency": currency, "time": now, - "hash": hash, } - bodyBytes, err := json.Marshal(payload) + // Step 2: Marshal data to JSON for hash calculation + // dataBytes, err := json.Marshal(data) + // if err != nil { + // s.logger.Error("Failed to marshal data for hash generation", "error", err) + // return nil, err + // } + + // Step 3: Calculate hash: sha256(privateKey + time + json(data)) + rawHash := s.config.PopOK.SecretKey + now + hash := fmt.Sprintf("%x", sha256.Sum256([]byte(rawHash))) + + // Step 4: Add the hash to the payload + data["hash"] = hash + + // Step 5: Marshal full payload with hash + bodyBytes, err := json.Marshal(data) if err != nil { - s.logger.Error("Failed to marshal game list request", "error", err) + s.logger.Error("Failed to marshal final payload with hash", "error", err) return nil, err } + // Step 6: Create and send the request req, err := http.NewRequestWithContext(ctx, "POST", s.config.PopOK.BaseURL+"/serviceApi.php", bytes.NewReader(bodyBytes)) if err != nil { s.logger.Error("Failed to create game list request", "error", err) return nil, err } req.Header.Set("Content-Type", "application/json") + req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36") + req.Header.Set("Accept", "application/json, text/plain, */*") + req.Header.Set("Accept-Language", "en-US,en;q=0.9") client := &http.Client{Timeout: 10 * time.Second} resp, err := client.Do(req) @@ -642,6 +712,7 @@ func (s *service) ListGames(ctx context.Context, currency string) ([]domain.PopO } defer resp.Body.Close() + // Step 7: Handle response if resp.StatusCode != http.StatusOK { b, _ := io.ReadAll(resp.Body) return nil, fmt.Errorf("PopOK game list failed with status %d: %s", resp.StatusCode, string(b)) diff --git a/internal/services/virtualGame/veli/client.go b/internal/services/virtualGame/veli/client.go index 6c4b4ee..67079f6 100644 --- a/internal/services/virtualGame/veli/client.go +++ b/internal/services/virtualGame/veli/client.go @@ -15,6 +15,7 @@ import ( "time" "github.com/SamuelTariku/FortuneBet-Backend/internal/config" + "github.com/SamuelTariku/FortuneBet-Backend/internal/services/wallet" ) type Client struct { @@ -23,16 +24,17 @@ type Client struct { OperatorID string SecretKey string BrandID string - cfg *config.Config + walletSvc *wallet.Service } -func NewClient(cfg *config.Config) *Client { +func NewClient(cfg *config.Config, walletSvc *wallet.Service) *Client { return &Client{ http: &http.Client{Timeout: 10 * time.Second}, BaseURL: cfg.VeliGames.BaseURL, OperatorID: cfg.VeliGames.OperatorID, SecretKey: cfg.VeliGames.SecretKey, BrandID: cfg.VeliGames.BrandID, + walletSvc: walletSvc, } } diff --git a/internal/services/virtualGame/veli/service.go b/internal/services/virtualGame/veli/service.go index 634d03d..7622424 100644 --- a/internal/services/virtualGame/veli/service.go +++ b/internal/services/virtualGame/veli/service.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "strconv" "strings" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" @@ -105,6 +106,17 @@ func (c *Client) ProcessBet(ctx context.Context, req domain.BetRequest) (*domain var res domain.BetResponse err := c.post(ctx, "/bet", req, sigParams, &res) + playerIDInt64, err := strconv.ParseInt(req.PlayerID, 10, 64) + if err != nil { + return &domain.BetResponse{}, fmt.Errorf("invalid PlayerID: %w", err) + } + wallets, err := c.walletSvc.GetWalletsByUser(ctx, playerIDInt64) + if err != nil { + return &domain.BetResponse{}, err + } + + c.walletSvc.DeductFromWallet(ctx, wallets[0].ID, domain.Currency(req.Amount.Amount), domain.CustomerWalletType, domain.ValidInt64{}, domain.TRANSFER_DIRECT) + return &res, err } @@ -133,6 +145,19 @@ func (c *Client) ProcessWin(ctx context.Context, req domain.WinRequest) (*domain var res domain.WinResponse err := c.post(ctx, "/win", req, sigParams, &res) + + playerIDInt64, err := strconv.ParseInt(req.PlayerID, 10, 64) + if err != nil { + return &domain.WinResponse{}, fmt.Errorf("invalid PlayerID: %w", err) + } + + wallets, err := c.walletSvc.GetWalletsByUser(ctx, playerIDInt64) + if err != nil { + return &domain.WinResponse{}, err + } + + c.walletSvc.AddToWallet(ctx, wallets[0].ID, domain.Currency(req.Amount.Amount), domain.ValidInt64{}, domain.TRANSFER_DIRECT, domain.PaymentDetails{}) + return &res, err } @@ -163,6 +188,18 @@ func (c *Client) ProcessCancel(ctx context.Context, req domain.CancelRequest) (* var res domain.CancelResponse err := c.post(ctx, "/cancel", req, sigParams, &res) + + playerIDInt64, err := strconv.ParseInt(req.PlayerID, 10, 64) + if err != nil { + return &domain.CancelResponse{}, fmt.Errorf("invalid PlayerID: %w", err) + } + + wallets, err := c.walletSvc.GetWalletsByUser(ctx, playerIDInt64) + if err != nil { + return &domain.CancelResponse{}, err + } + + c.walletSvc.AddToWallet(ctx, wallets[0].ID, domain.Currency(req.AdjustmentRefund.Amount), domain.ValidInt64{}, domain.TRANSFER_DIRECT, domain.PaymentDetails{}) return &res, err } diff --git a/internal/services/wallet/monitor/service.go b/internal/services/wallet/monitor/service.go index 4f48115..e3f7bc5 100644 --- a/internal/services/wallet/monitor/service.go +++ b/internal/services/wallet/monitor/service.go @@ -209,7 +209,7 @@ func buildNotificationMessage(thresholdPercent int, currentBalance, initialDepos return fmt.Sprintf( "Company wallet balance has reached %d%% of initial deposit. Current balance: %.2f, Initial deposit: %.2f", thresholdPercent, - float64(currentBalance)/100, // Assuming currency is in cents - float64(initialDeposit)/100, + float64(currentBalance), // Assuming currency is in cents + float64(initialDeposit), ) } diff --git a/internal/services/wallet/service.go b/internal/services/wallet/service.go index 8186593..4d0bd76 100644 --- a/internal/services/wallet/service.go +++ b/internal/services/wallet/service.go @@ -14,11 +14,12 @@ type Service struct { logger *slog.Logger } -func NewService(walletStore WalletStore, transferStore TransferStore, notificationStore notificationservice.NotificationStore, logger *slog.Logger) *Service { +func NewService(walletStore WalletStore, transferStore TransferStore, notificationStore notificationservice.NotificationStore, notificationSvc *notificationservice.Service, logger *slog.Logger) *Service { return &Service{ walletStore: walletStore, transferStore: transferStore, notificationStore: notificationStore, + notificationSvc: notificationSvc, logger: logger, } } diff --git a/internal/services/wallet/wallet.go b/internal/services/wallet/wallet.go index a5c8ba3..a80b50c 100644 --- a/internal/services/wallet/wallet.go +++ b/internal/services/wallet/wallet.go @@ -95,7 +95,7 @@ func (s *Service) AddToWallet( return domain.Transfer{}, err } - go s.notificationSvc.UpdateLiveWalletMetricForWallet(ctx, wallet) + // go s.notificationSvc.UpdateLiveWalletMetricForWallet(ctx, wallet) // Log the transfer here for reference newTransfer, err := s.transferStore.CreateTransfer(ctx, domain.CreateTransfer{ @@ -135,7 +135,7 @@ func (s *Service) DeductFromWallet(ctx context.Context, id int64, amount domain. return domain.Transfer{}, nil } - go s.notificationSvc.UpdateLiveWalletMetricForWallet(ctx, wallet) + // go s.notificationSvc.UpdateLiveWalletMetricForWallet(ctx, wallet) // Log the transfer here for reference newTransfer, err := s.transferStore.CreateTransfer(ctx, domain.CreateTransfer{ diff --git a/internal/web_server/handlers/bonus.go b/internal/web_server/handlers/bonus.go index 19f0e4f..f4e5a27 100644 --- a/internal/web_server/handlers/bonus.go +++ b/internal/web_server/handlers/bonus.go @@ -8,6 +8,7 @@ import ( func (h *Handler) CreateBonusMultiplier(c *fiber.Ctx) error { var req struct { Multiplier float32 `json:"multiplier"` + BalanceCap int64 `json:"balance_cap"` } if err := c.BodyParser(&req); err != nil { @@ -27,7 +28,7 @@ func (h *Handler) CreateBonusMultiplier(c *fiber.Ctx) error { return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", err, nil) } - if err := h.bonusSvc.CreateBonusMultiplier(c.Context(), req.Multiplier); err != nil { + if err := h.bonusSvc.CreateBonusMultiplier(c.Context(), req.Multiplier, req.BalanceCap); err != nil { h.logger.Error("failed to create bonus multiplier", "error", err) return response.WriteJSON(c, fiber.StatusInternalServerError, "failed to create bonus mulitplier", nil, nil) } @@ -49,6 +50,7 @@ func (h *Handler) UpdateBonusMultiplier(c *fiber.Ctx) error { var req struct { ID int64 `json:"id"` Multiplier float32 `json:"multiplier"` + BalanceCap int64 `json:"balance_cap"` } if err := c.BodyParser(&req); err != nil { @@ -56,7 +58,7 @@ func (h *Handler) UpdateBonusMultiplier(c *fiber.Ctx) error { return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", err, nil) } - if err := h.bonusSvc.UpdateBonusMultiplier(c.Context(), req.ID, req.Multiplier); err != nil { + if err := h.bonusSvc.UpdateBonusMultiplier(c.Context(), req.ID, req.Multiplier, req.BalanceCap); err != nil { h.logger.Error("failed to update bonus multiplier", "error", err) return response.WriteJSON(c, fiber.StatusInternalServerError, "failed to update bonus mulitplier", nil, nil) } diff --git a/internal/web_server/handlers/chapa.go b/internal/web_server/handlers/chapa.go index e5107ff..00b771a 100644 --- a/internal/web_server/handlers/chapa.go +++ b/internal/web_server/handlers/chapa.go @@ -2,6 +2,7 @@ package handlers import ( "fmt" + "math" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "github.com/gofiber/fiber/v2" @@ -66,8 +67,16 @@ func (h *Handler) InitiateDeposit(c *fiber.Ctx) error { multiplier = bonusMultiplier[0].Multiplier } - _, err = h.walletSvc.AddToWallet(c.Context(), wallet.StaticID, domain.ToCurrency(float32(amount)*multiplier), domain.ValidInt64{}, domain.TRANSFER_DIRECT, domain.PaymentDetails{}, - fmt.Sprintf("Added %v to static wallet because of deposit bonus using multiplier %v", float32(amount)*multiplier, multiplier), + var balanceCap int64 = 0 + bonusBalanceCap, err := h.bonusSvc.GetBonusBalanceCap(c.Context()) + if err == nil { + balanceCap = bonusBalanceCap[0].BalanceCap + } + + capedBalanceAmount := domain.Currency((math.Min(req.Amount, float64(balanceCap)) * float64(multiplier)) * 100) + + _, err = h.walletSvc.AddToWallet(c.Context(), wallet.StaticID, capedBalanceAmount, domain.ValidInt64{}, domain.TRANSFER_DIRECT, domain.PaymentDetails{}, + fmt.Sprintf("Added %v to static wallet because of deposit bonus using multiplier %v", capedBalanceAmount, multiplier), ) if err != nil { h.logger.Error("Failed to add bonus to static wallet", "walletID", wallet.StaticID, "user id", userID, "error", err) diff --git a/internal/web_server/handlers/referal_handlers.go b/internal/web_server/handlers/referal_handlers.go index d978e0b..a2fe09e 100644 --- a/internal/web_server/handlers/referal_handlers.go +++ b/internal/web_server/handlers/referal_handlers.go @@ -21,6 +21,38 @@ func (h *Handler) CreateReferralCode(c *fiber.Ctx) error { return response.WriteJSON(c, fiber.StatusOK, "Referral created successfully", nil, nil) } +func (h *Handler) CreateReferralSettings(c *fiber.Ctx) error { + var req domain.ReferralSettingsReq + if err := c.BodyParser(&req); err != nil { + h.logger.Error("Failed to parse settings", "error", err) + return fiber.NewError(fiber.StatusBadRequest, "Invalid request body") + } + + if valErrs, ok := h.validator.Validate(c, req); !ok { + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) + } + + settings, err := h.referralSvc.GetReferralSettings(c.Context()) + if err != nil { + h.logger.Error("Failed to fetch previous referral setting", "error", err) + return fiber.NewError(fiber.StatusInternalServerError, "Failed to create referral") + } + + // only allow one referral setting for now + // for future it can be multiple and be able to choose from them + if settings != nil { + h.logger.Error("referral setting already exists", "error", err) + return fiber.NewError(fiber.StatusInternalServerError, "referral setting already exists") + } + + if err := h.referralSvc.CreateReferralSettings(c.Context(), req); err != nil { + h.logger.Error("Failed to create referral setting", "error", err) + return fiber.NewError(fiber.StatusInternalServerError, "Failed to create referral") + } + + return response.WriteJSON(c, fiber.StatusOK, "Referral created successfully", nil, nil) +} + // GetReferralStats godoc // @Summary Get referral statistics // @Description Retrieves referral statistics for the authenticated user @@ -112,11 +144,12 @@ func (h *Handler) UpdateReferralSettings(c *fiber.Ctx) error { // @Security Bearer // @Router /referral/settings [get] func (h *Handler) GetReferralSettings(c *fiber.Ctx) error { - userID, ok := c.Locals("user_id").(int64) - if !ok || userID == 0 { - h.logger.Error("Invalid user ID in context") - return fiber.NewError(fiber.StatusUnauthorized, "Invalid user identification") - } + // userID, ok := c.Locals("user_id").(int64) + // if !ok || userID == 0 { + // h.logger.Error("Invalid user ID in context") + // return fiber.NewError(fiber.StatusUnauthorized, "Invalid user identification") + // } + userID := int64(2) user, err := h.userSvc.GetUserByID(c.Context(), userID) if err != nil { diff --git a/internal/web_server/handlers/report.go b/internal/web_server/handlers/report.go index ed396a2..c2ed79f 100644 --- a/internal/web_server/handlers/report.go +++ b/internal/web_server/handlers/report.go @@ -137,16 +137,28 @@ func parseReportFilter(c *fiber.Ctx) (domain.ReportFilter, error) { // @Router /api/v1/report-files/download/{filename} [get] func (h *Handler) DownloadReportFile(c *fiber.Ctx) error { filename := c.Params("filename") - if filename == "" { + if filename == "" || strings.Contains(filename, "..") { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ - Message: "Missing filename parameter", - Error: "filename is required", + Message: "Invalid filename parameter", + Error: "filename is required and must not contain '..'", }) } - filePath := fmt.Sprintf("/host-desktop/%s", filename) + reportDir := "reports" - // Check if file exists + // Ensure reports directory exists + if _, err := os.Stat(reportDir); os.IsNotExist(err) { + if err := os.MkdirAll(reportDir, os.ModePerm); err != nil { + return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{ + Message: "Failed to create report directory", + Error: err.Error(), + }) + } + } + + filePath := fmt.Sprintf("%s/%s", reportDir, filename) + + // Check if the report file exists if _, err := os.Stat(filePath); os.IsNotExist(err) { return c.Status(fiber.StatusNotFound).JSON(domain.ErrorResponse{ Message: "Report file not found", @@ -154,10 +166,11 @@ func (h *Handler) DownloadReportFile(c *fiber.Ctx) error { }) } - // Set download headers and return file + // Set download headers c.Set("Content-Type", "text/csv") c.Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", filename)) + // Serve the file if err := c.SendFile(filePath); err != nil { return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{ Message: "Failed to serve file", @@ -177,7 +190,17 @@ func (h *Handler) DownloadReportFile(c *fiber.Ctx) error { // @Failure 500 {object} domain.ErrorResponse "Failed to read report directory" // @Router /api/v1/report-files/list [get] func (h *Handler) ListReportFiles(c *fiber.Ctx) error { - reportDir := "/host-desktop" + reportDir := "reports" + + // Create the reports directory if it doesn't exist + if _, err := os.Stat(reportDir); os.IsNotExist(err) { + if err := os.MkdirAll(reportDir, os.ModePerm); err != nil { + return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{ + Message: "Failed to create report directory", + Error: err.Error(), + }) + } + } files, err := os.ReadDir(reportDir) if err != nil { diff --git a/internal/web_server/handlers/virtual_games_hadlers.go b/internal/web_server/handlers/virtual_games_hadlers.go index 4b51f58..f3627b2 100644 --- a/internal/web_server/handlers/virtual_games_hadlers.go +++ b/internal/web_server/handlers/virtual_games_hadlers.go @@ -9,7 +9,7 @@ import ( ) type launchVirtualGameReq struct { - GameID string `json:"game_id" validate:"required" example:"crash_001"` + GameID string `json:"game_id" validate:"required" example:"1"` Currency string `json:"currency" validate:"required,len=3" example:"USD"` Mode string `json:"mode" validate:"required,oneof=fun real" example:"real"` } @@ -108,7 +108,11 @@ func (h *Handler) HandlePlayerInfo(c *fiber.Ctx) error { func (h *Handler) HandleBet(c *fiber.Ctx) error { var req domain.PopOKBetRequest if err := c.BodyParser(&req); err != nil { - return fiber.NewError(fiber.StatusBadRequest, "Invalid bet request") + return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ + Message: "Invalid bet request", + Error: err.Error(), + }) + // return fiber.NewError(fiber.StatusBadRequest, "Invalid bet request") } resp, _ := h.virtualGameSvc.ProcessBet(c.Context(), &req) @@ -179,7 +183,11 @@ func (h *Handler) GetGameList(c *fiber.Ctx) error { games, err := h.virtualGameSvc.ListGames(c.Context(), currency) if err != nil { - return fiber.NewError(fiber.StatusBadGateway, "failed to fetch games") + return c.Status(fiber.StatusBadGateway).JSON(domain.ErrorResponse{ + Message: "Falied to fetch games", + Error: err.Error(), + }) + // return fiber.NewError(fiber.StatusBadGateway, "failed to fetch games") } return c.JSON(games) } diff --git a/internal/web_server/routes.go b/internal/web_server/routes.go index 11f9240..66b1423 100644 --- a/internal/web_server/routes.go +++ b/internal/web_server/routes.go @@ -57,7 +57,7 @@ func (a *App) initAppRoutes() { a.fiber.Get("/", func(c *fiber.Ctx) error { return c.JSON(fiber.Map{ "message": "Welcome to the FortuneBet API", - "version": "1.0dev7", + "version": "1.0dev7.5", }) }) @@ -109,7 +109,8 @@ func (a *App) initAppRoutes() { // Referral Routes a.fiber.Post("/referral/create", a.authMiddleware, h.CreateReferralCode) a.fiber.Get("/referral/stats", a.authMiddleware, h.GetReferralStats) - a.fiber.Get("/referral/settings", h.GetReferralSettings) + a.fiber.Post("/referral/settings", a.authMiddleware, h.CreateReferralSettings) + a.fiber.Get("/referral/settings", a.authMiddleware, h.GetReferralSettings) a.fiber.Patch("/referral/settings", a.authMiddleware, h.UpdateReferralSettings) // Bonus Routes @@ -226,8 +227,8 @@ func (a *App) initAppRoutes() { //Report Routes group.Get("/reports/dashboard", h.GetDashboardReport) - group.Get("/report-files/download/:filename", a.authMiddleware, a.SuperAdminOnly, h.DownloadReportFile) - group.Get("/report-files/list", a.authMiddleware, a.SuperAdminOnly, h.ListReportFiles) + group.Get("/report-files/download/:filename", a.authMiddleware, a.OnlyAdminAndAbove, h.DownloadReportFile) + group.Get("/report-files/list", a.authMiddleware, a.OnlyAdminAndAbove, h.ListReportFiles) //Wallet Monitor Service // group.Get("/debug/wallet-monitor/status", func(c *fiber.Ctx) error { @@ -259,9 +260,9 @@ func (a *App) initAppRoutes() { group.Post("/veli/start-game", a.authMiddleware, h.StartGame) group.Post("/veli/start-demo-game", a.authMiddleware, h.StartDemoGame) a.fiber.Post("/balance", h.GetBalance) - a.fiber.Post("/bet", h.PlaceBet) - a.fiber.Post("/win", h.RegisterWin) - a.fiber.Post("/cancel", h.CancelTransaction) + // a.fiber.Post("/bet", h.PlaceBet) + // a.fiber.Post("/win", h.RegisterWin) + // a.fiber.Post("/cancel", h.CancelTransaction) group.Post("/veli/gaming-activity", h.GetGamingActivity) //mongoDB logs