bet on same bet only twice

This commit is contained in:
Asher Samuel 2025-06-17 01:17:22 +03:00
parent dc2144a91e
commit 78d351cae9
10 changed files with 186 additions and 73 deletions

View File

@ -55,6 +55,7 @@ CREATE TABLE IF NOT EXISTS bets (
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
is_shop_bet BOOLEAN NOT NULL, is_shop_bet BOOLEAN NOT NULL,
outcomes_hash TEXT NOT NULL,
UNIQUE(cashout_id), UNIQUE(cashout_id),
CHECK ( CHECK (
user_id IS NOT NULL user_id IS NOT NULL
@ -310,7 +311,7 @@ ALTER TABLE bets
ADD CONSTRAINT fk_bets_users FOREIGN KEY (user_id) REFERENCES users(id), ADD CONSTRAINT fk_bets_users FOREIGN KEY (user_id) REFERENCES users(id),
ADD CONSTRAINT fk_bets_branches FOREIGN KEY (branch_id) REFERENCES branches(id); ADD CONSTRAINT fk_bets_branches FOREIGN KEY (branch_id) REFERENCES branches(id);
ALTER TABLE wallets ALTER TABLE wallets
ADD CONSTRAINT fk_wallets_users FOREIGN KEY (user_id) REFERENCES users(id); ADD CONSTRAINT fk_wallets_users FOREIGN KEY (user_id) REFERENCES users(id),
ADD COLUMN currency VARCHAR(3) NOT NULL DEFAULT 'ETB'; ADD COLUMN currency VARCHAR(3) NOT NULL DEFAULT 'ETB';
ALTER TABLE customer_wallets ALTER TABLE customer_wallets
ADD CONSTRAINT fk_customer_wallets_customers FOREIGN KEY (customer_id) REFERENCES users(id), ADD CONSTRAINT fk_customer_wallets_customers FOREIGN KEY (customer_id) REFERENCES users(id),

View File

@ -9,9 +9,10 @@ INSERT INTO bets (
user_id, user_id,
is_shop_bet, is_shop_bet,
cashout_id, cashout_id,
company_id company_id,
outcomes_hash
) )
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
RETURNING *; RETURNING *;
-- name: CreateBetOutcome :copyfrom -- name: CreateBetOutcome :copyfrom
INSERT INTO bet_outcomes ( INSERT INTO bet_outcomes (
@ -83,6 +84,11 @@ WHERE event_id = $1;
SELECT * SELECT *
FROM bet_outcomes FROM bet_outcomes
WHERE bet_id = $1; WHERE bet_id = $1;
-- name: GetBetCount :one
SELECT COUNT(*)
FROM bets
where user_id = $1
AND outcomes_hash = $2;
-- name: UpdateCashOut :exec -- name: UpdateCashOut :exec
UPDATE bets UPDATE bets
SET cashed_out = $2, SET cashed_out = $2,

View File

@ -22,23 +22,25 @@ INSERT INTO bets (
user_id, user_id,
is_shop_bet, is_shop_bet,
cashout_id, cashout_id,
company_id company_id,
outcomes_hash
) )
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
RETURNING id, amount, total_odds, status, full_name, phone_number, company_id, branch_id, user_id, cashed_out, cashout_id, created_at, updated_at, is_shop_bet RETURNING id, amount, total_odds, status, full_name, phone_number, company_id, branch_id, user_id, cashed_out, cashout_id, created_at, updated_at, is_shop_bet, outcomes_hash
` `
type CreateBetParams struct { type CreateBetParams struct {
Amount int64 `json:"amount"` Amount int64 `json:"amount"`
TotalOdds float32 `json:"total_odds"` TotalOdds float32 `json:"total_odds"`
Status int32 `json:"status"` Status int32 `json:"status"`
FullName string `json:"full_name"` FullName string `json:"full_name"`
PhoneNumber string `json:"phone_number"` PhoneNumber string `json:"phone_number"`
BranchID pgtype.Int8 `json:"branch_id"` BranchID pgtype.Int8 `json:"branch_id"`
UserID pgtype.Int8 `json:"user_id"` UserID pgtype.Int8 `json:"user_id"`
IsShopBet bool `json:"is_shop_bet"` IsShopBet bool `json:"is_shop_bet"`
CashoutID string `json:"cashout_id"` CashoutID string `json:"cashout_id"`
CompanyID pgtype.Int8 `json:"company_id"` CompanyID pgtype.Int8 `json:"company_id"`
OutcomesHash string `json:"outcomes_hash"`
} }
func (q *Queries) CreateBet(ctx context.Context, arg CreateBetParams) (Bet, error) { func (q *Queries) CreateBet(ctx context.Context, arg CreateBetParams) (Bet, error) {
@ -53,6 +55,7 @@ func (q *Queries) CreateBet(ctx context.Context, arg CreateBetParams) (Bet, erro
arg.IsShopBet, arg.IsShopBet,
arg.CashoutID, arg.CashoutID,
arg.CompanyID, arg.CompanyID,
arg.OutcomesHash,
) )
var i Bet var i Bet
err := row.Scan( err := row.Scan(
@ -70,6 +73,7 @@ func (q *Queries) CreateBet(ctx context.Context, arg CreateBetParams) (Bet, erro
&i.CreatedAt, &i.CreatedAt,
&i.UpdatedAt, &i.UpdatedAt,
&i.IsShopBet, &i.IsShopBet,
&i.OutcomesHash,
) )
return i, err return i, err
} }
@ -111,7 +115,7 @@ func (q *Queries) DeleteBetOutcome(ctx context.Context, betID int64) error {
} }
const GetAllBets = `-- name: GetAllBets :many const GetAllBets = `-- name: GetAllBets :many
SELECT id, amount, total_odds, status, full_name, phone_number, company_id, branch_id, user_id, cashed_out, cashout_id, created_at, updated_at, is_shop_bet, outcomes SELECT id, amount, total_odds, status, full_name, phone_number, company_id, branch_id, user_id, cashed_out, cashout_id, created_at, updated_at, is_shop_bet, outcomes_hash, outcomes
FROM bet_with_outcomes FROM bet_with_outcomes
wHERE ( wHERE (
branch_id = $1 branch_id = $1
@ -157,6 +161,7 @@ func (q *Queries) GetAllBets(ctx context.Context, arg GetAllBetsParams) ([]BetWi
&i.CreatedAt, &i.CreatedAt,
&i.UpdatedAt, &i.UpdatedAt,
&i.IsShopBet, &i.IsShopBet,
&i.OutcomesHash,
&i.Outcomes, &i.Outcomes,
); err != nil { ); err != nil {
return nil, err return nil, err
@ -170,7 +175,7 @@ func (q *Queries) GetAllBets(ctx context.Context, arg GetAllBetsParams) ([]BetWi
} }
const GetBetByBranchID = `-- name: GetBetByBranchID :many const GetBetByBranchID = `-- name: GetBetByBranchID :many
SELECT id, amount, total_odds, status, full_name, phone_number, company_id, branch_id, user_id, cashed_out, cashout_id, created_at, updated_at, is_shop_bet, outcomes SELECT id, amount, total_odds, status, full_name, phone_number, company_id, branch_id, user_id, cashed_out, cashout_id, created_at, updated_at, is_shop_bet, outcomes_hash, outcomes
FROM bet_with_outcomes FROM bet_with_outcomes
WHERE branch_id = $1 WHERE branch_id = $1
` `
@ -199,6 +204,7 @@ func (q *Queries) GetBetByBranchID(ctx context.Context, branchID pgtype.Int8) ([
&i.CreatedAt, &i.CreatedAt,
&i.UpdatedAt, &i.UpdatedAt,
&i.IsShopBet, &i.IsShopBet,
&i.OutcomesHash,
&i.Outcomes, &i.Outcomes,
); err != nil { ); err != nil {
return nil, err return nil, err
@ -212,7 +218,7 @@ func (q *Queries) GetBetByBranchID(ctx context.Context, branchID pgtype.Int8) ([
} }
const GetBetByCashoutID = `-- name: GetBetByCashoutID :one const GetBetByCashoutID = `-- name: GetBetByCashoutID :one
SELECT id, amount, total_odds, status, full_name, phone_number, company_id, branch_id, user_id, cashed_out, cashout_id, created_at, updated_at, is_shop_bet, outcomes SELECT id, amount, total_odds, status, full_name, phone_number, company_id, branch_id, user_id, cashed_out, cashout_id, created_at, updated_at, is_shop_bet, outcomes_hash, outcomes
FROM bet_with_outcomes FROM bet_with_outcomes
WHERE cashout_id = $1 WHERE cashout_id = $1
` `
@ -235,13 +241,14 @@ func (q *Queries) GetBetByCashoutID(ctx context.Context, cashoutID string) (BetW
&i.CreatedAt, &i.CreatedAt,
&i.UpdatedAt, &i.UpdatedAt,
&i.IsShopBet, &i.IsShopBet,
&i.OutcomesHash,
&i.Outcomes, &i.Outcomes,
) )
return i, err return i, err
} }
const GetBetByID = `-- name: GetBetByID :one const GetBetByID = `-- name: GetBetByID :one
SELECT id, amount, total_odds, status, full_name, phone_number, company_id, branch_id, user_id, cashed_out, cashout_id, created_at, updated_at, is_shop_bet, outcomes SELECT id, amount, total_odds, status, full_name, phone_number, company_id, branch_id, user_id, cashed_out, cashout_id, created_at, updated_at, is_shop_bet, outcomes_hash, outcomes
FROM bet_with_outcomes FROM bet_with_outcomes
WHERE id = $1 WHERE id = $1
` `
@ -264,13 +271,14 @@ func (q *Queries) GetBetByID(ctx context.Context, id int64) (BetWithOutcome, err
&i.CreatedAt, &i.CreatedAt,
&i.UpdatedAt, &i.UpdatedAt,
&i.IsShopBet, &i.IsShopBet,
&i.OutcomesHash,
&i.Outcomes, &i.Outcomes,
) )
return i, err return i, err
} }
const GetBetByUserID = `-- name: GetBetByUserID :many const GetBetByUserID = `-- name: GetBetByUserID :many
SELECT id, amount, total_odds, status, full_name, phone_number, company_id, branch_id, user_id, cashed_out, cashout_id, created_at, updated_at, is_shop_bet, outcomes SELECT id, amount, total_odds, status, full_name, phone_number, company_id, branch_id, user_id, cashed_out, cashout_id, created_at, updated_at, is_shop_bet, outcomes_hash, outcomes
FROM bet_with_outcomes FROM bet_with_outcomes
WHERE user_id = $1 WHERE user_id = $1
` `
@ -299,6 +307,7 @@ func (q *Queries) GetBetByUserID(ctx context.Context, userID pgtype.Int8) ([]Bet
&i.CreatedAt, &i.CreatedAt,
&i.UpdatedAt, &i.UpdatedAt,
&i.IsShopBet, &i.IsShopBet,
&i.OutcomesHash,
&i.Outcomes, &i.Outcomes,
); err != nil { ); err != nil {
return nil, err return nil, err
@ -311,6 +320,25 @@ func (q *Queries) GetBetByUserID(ctx context.Context, userID pgtype.Int8) ([]Bet
return items, nil return items, nil
} }
const GetBetCount = `-- name: GetBetCount :one
SELECT COUNT(*)
FROM bets
where user_id = $1
AND outcomes_hash = $2
`
type GetBetCountParams struct {
UserID pgtype.Int8 `json:"user_id"`
OutcomesHash string `json:"outcomes_hash"`
}
func (q *Queries) GetBetCount(ctx context.Context, arg GetBetCountParams) (int64, error) {
row := q.db.QueryRow(ctx, GetBetCount, arg.UserID, arg.OutcomesHash)
var count int64
err := row.Scan(&count)
return count, err
}
const GetBetOutcomeByBetID = `-- name: GetBetOutcomeByBetID :many const GetBetOutcomeByBetID = `-- name: GetBetOutcomeByBetID :many
SELECT id, bet_id, sport_id, event_id, odd_id, home_team_name, away_team_name, market_id, market_name, odd, odd_name, odd_header, odd_handicap, status, expires SELECT id, bet_id, sport_id, event_id, odd_id, home_team_name, away_team_name, market_id, market_name, odd, odd_name, odd_header, odd_handicap, status, expires
FROM bet_outcomes FROM bet_outcomes

View File

@ -56,20 +56,21 @@ func (ns NullReferralstatus) Value() (driver.Value, error) {
} }
type Bet struct { type Bet struct {
ID int64 `json:"id"` ID int64 `json:"id"`
Amount int64 `json:"amount"` Amount int64 `json:"amount"`
TotalOdds float32 `json:"total_odds"` TotalOdds float32 `json:"total_odds"`
Status int32 `json:"status"` Status int32 `json:"status"`
FullName string `json:"full_name"` FullName string `json:"full_name"`
PhoneNumber string `json:"phone_number"` PhoneNumber string `json:"phone_number"`
CompanyID pgtype.Int8 `json:"company_id"` CompanyID pgtype.Int8 `json:"company_id"`
BranchID pgtype.Int8 `json:"branch_id"` BranchID pgtype.Int8 `json:"branch_id"`
UserID pgtype.Int8 `json:"user_id"` UserID pgtype.Int8 `json:"user_id"`
CashedOut bool `json:"cashed_out"` CashedOut bool `json:"cashed_out"`
CashoutID string `json:"cashout_id"` CashoutID string `json:"cashout_id"`
CreatedAt pgtype.Timestamp `json:"created_at"` CreatedAt pgtype.Timestamp `json:"created_at"`
UpdatedAt pgtype.Timestamp `json:"updated_at"` UpdatedAt pgtype.Timestamp `json:"updated_at"`
IsShopBet bool `json:"is_shop_bet"` IsShopBet bool `json:"is_shop_bet"`
OutcomesHash string `json:"outcomes_hash"`
} }
type BetOutcome struct { type BetOutcome struct {
@ -91,21 +92,22 @@ type BetOutcome struct {
} }
type BetWithOutcome struct { type BetWithOutcome struct {
ID int64 `json:"id"` ID int64 `json:"id"`
Amount int64 `json:"amount"` Amount int64 `json:"amount"`
TotalOdds float32 `json:"total_odds"` TotalOdds float32 `json:"total_odds"`
Status int32 `json:"status"` Status int32 `json:"status"`
FullName string `json:"full_name"` FullName string `json:"full_name"`
PhoneNumber string `json:"phone_number"` PhoneNumber string `json:"phone_number"`
CompanyID pgtype.Int8 `json:"company_id"` CompanyID pgtype.Int8 `json:"company_id"`
BranchID pgtype.Int8 `json:"branch_id"` BranchID pgtype.Int8 `json:"branch_id"`
UserID pgtype.Int8 `json:"user_id"` UserID pgtype.Int8 `json:"user_id"`
CashedOut bool `json:"cashed_out"` CashedOut bool `json:"cashed_out"`
CashoutID string `json:"cashout_id"` CashoutID string `json:"cashout_id"`
CreatedAt pgtype.Timestamp `json:"created_at"` CreatedAt pgtype.Timestamp `json:"created_at"`
UpdatedAt pgtype.Timestamp `json:"updated_at"` UpdatedAt pgtype.Timestamp `json:"updated_at"`
IsShopBet bool `json:"is_shop_bet"` IsShopBet bool `json:"is_shop_bet"`
Outcomes []BetOutcome `json:"outcomes"` OutcomesHash string `json:"outcomes_hash"`
Outcomes []BetOutcome `json:"outcomes"`
} }
type Branch struct { type Branch struct {
@ -204,6 +206,15 @@ type Event struct {
Source pgtype.Text `json:"source"` Source pgtype.Text `json:"source"`
} }
type ExchangeRate struct {
ID int32 `json:"id"`
FromCurrency string `json:"from_currency"`
ToCurrency string `json:"to_currency"`
Rate pgtype.Numeric `json:"rate"`
ValidUntil pgtype.Timestamp `json:"valid_until"`
CreatedAt pgtype.Timestamp `json:"created_at"`
}
type League struct { type League struct {
ID int64 `json:"id"` ID int64 `json:"id"`
Name string `json:"name"` Name string `json:"name"`
@ -470,6 +481,7 @@ type Wallet struct {
IsActive bool `json:"is_active"` IsActive bool `json:"is_active"`
CreatedAt pgtype.Timestamp `json:"created_at"` CreatedAt pgtype.Timestamp `json:"created_at"`
UpdatedAt pgtype.Timestamp `json:"updated_at"` UpdatedAt pgtype.Timestamp `json:"updated_at"`
Currency string `json:"currency"`
BonusBalance pgtype.Numeric `json:"bonus_balance"` BonusBalance pgtype.Numeric `json:"bonus_balance"`
CashBalance pgtype.Numeric `json:"cash_balance"` CashBalance pgtype.Numeric `json:"cash_balance"`
} }

View File

@ -49,7 +49,7 @@ INSERT INTO wallets (
user_id user_id
) )
VALUES ($1, $2, $3, $4) VALUES ($1, $2, $3, $4)
RETURNING id, balance, is_withdraw, is_bettable, is_transferable, user_id, is_active, created_at, updated_at, bonus_balance, cash_balance RETURNING id, balance, is_withdraw, is_bettable, is_transferable, user_id, is_active, created_at, updated_at, currency, bonus_balance, cash_balance
` `
type CreateWalletParams struct { type CreateWalletParams struct {
@ -77,6 +77,7 @@ func (q *Queries) CreateWallet(ctx context.Context, arg CreateWalletParams) (Wal
&i.IsActive, &i.IsActive,
&i.CreatedAt, &i.CreatedAt,
&i.UpdatedAt, &i.UpdatedAt,
&i.Currency,
&i.BonusBalance, &i.BonusBalance,
&i.CashBalance, &i.CashBalance,
) )
@ -143,7 +144,7 @@ func (q *Queries) GetAllBranchWallets(ctx context.Context) ([]GetAllBranchWallet
} }
const GetAllWallets = `-- name: GetAllWallets :many const GetAllWallets = `-- name: GetAllWallets :many
SELECT id, balance, is_withdraw, is_bettable, is_transferable, user_id, is_active, created_at, updated_at, bonus_balance, cash_balance SELECT id, balance, is_withdraw, is_bettable, is_transferable, user_id, is_active, created_at, updated_at, currency, bonus_balance, cash_balance
FROM wallets FROM wallets
` `
@ -166,6 +167,7 @@ func (q *Queries) GetAllWallets(ctx context.Context) ([]Wallet, error) {
&i.IsActive, &i.IsActive,
&i.CreatedAt, &i.CreatedAt,
&i.UpdatedAt, &i.UpdatedAt,
&i.Currency,
&i.BonusBalance, &i.BonusBalance,
&i.CashBalance, &i.CashBalance,
); err != nil { ); err != nil {
@ -225,7 +227,7 @@ func (q *Queries) GetCustomerWallet(ctx context.Context, customerID int64) (GetC
} }
const GetWalletByID = `-- name: GetWalletByID :one const GetWalletByID = `-- name: GetWalletByID :one
SELECT id, balance, is_withdraw, is_bettable, is_transferable, user_id, is_active, created_at, updated_at, bonus_balance, cash_balance SELECT id, balance, is_withdraw, is_bettable, is_transferable, user_id, is_active, created_at, updated_at, currency, bonus_balance, cash_balance
FROM wallets FROM wallets
WHERE id = $1 WHERE id = $1
` `
@ -243,6 +245,7 @@ func (q *Queries) GetWalletByID(ctx context.Context, id int64) (Wallet, error) {
&i.IsActive, &i.IsActive,
&i.CreatedAt, &i.CreatedAt,
&i.UpdatedAt, &i.UpdatedAt,
&i.Currency,
&i.BonusBalance, &i.BonusBalance,
&i.CashBalance, &i.CashBalance,
) )
@ -250,7 +253,7 @@ func (q *Queries) GetWalletByID(ctx context.Context, id int64) (Wallet, error) {
} }
const GetWalletByUserID = `-- name: GetWalletByUserID :many const GetWalletByUserID = `-- name: GetWalletByUserID :many
SELECT id, balance, is_withdraw, is_bettable, is_transferable, user_id, is_active, created_at, updated_at, bonus_balance, cash_balance SELECT id, balance, is_withdraw, is_bettable, is_transferable, user_id, is_active, created_at, updated_at, currency, bonus_balance, cash_balance
FROM wallets FROM wallets
WHERE user_id = $1 WHERE user_id = $1
` `
@ -274,6 +277,7 @@ func (q *Queries) GetWalletByUserID(ctx context.Context, userID int64) ([]Wallet
&i.IsActive, &i.IsActive,
&i.CreatedAt, &i.CreatedAt,
&i.UpdatedAt, &i.UpdatedAt,
&i.Currency,
&i.BonusBalance, &i.BonusBalance,
&i.CashBalance, &i.CashBalance,
); err != nil { ); err != nil {

View File

@ -80,16 +80,17 @@ type GetBet struct {
} }
type CreateBet struct { type CreateBet struct {
Amount Currency Amount Currency
TotalOdds float32 TotalOdds float32
Status OutcomeStatus Status OutcomeStatus
FullName string FullName string
PhoneNumber string PhoneNumber string
CompanyID ValidInt64 // Can Be Nullable CompanyID ValidInt64 // Can Be Nullable
BranchID ValidInt64 // Can Be Nullable BranchID ValidInt64 // Can Be Nullable
UserID ValidInt64 // Can Be Nullable UserID ValidInt64 // Can Be Nullable
IsShopBet bool IsShopBet bool
CashoutID string CashoutID string
OutcomesHash string
} }
type CreateBetOutcomeReq struct { type CreateBetOutcomeReq struct {
@ -173,4 +174,3 @@ func ConvertBet(bet GetBet) BetRes {
CreatedAt: bet.CreatedAt, CreatedAt: bet.CreatedAt,
} }
} }

View File

@ -265,6 +265,19 @@ func (s *Store) GetBetByUserID(ctx context.Context, UserID int64) ([]domain.GetB
return result, nil return result, nil
} }
func (s *Store) GetBetCount(ctx context.Context, UserID int64, outcomesHash string) (int64, error) {
count, err := s.queries.GetBetCount(ctx, dbgen.GetBetCountParams{
UserID: pgtype.Int8{Int64: UserID},
OutcomesHash: outcomesHash,
})
if err != nil {
return 0, err
}
return count, nil
}
func (s *Store) UpdateCashOut(ctx context.Context, id int64, cashedOut bool) error { func (s *Store) UpdateCashOut(ctx context.Context, id int64, cashedOut bool) error {
err := s.queries.UpdateCashOut(ctx, dbgen.UpdateCashOutParams{ err := s.queries.UpdateCashOut(ctx, dbgen.UpdateCashOutParams{
ID: id, ID: id,

View File

@ -17,6 +17,7 @@ type BetStore interface {
GetBetByUserID(ctx context.Context, UserID int64) ([]domain.GetBet, error) GetBetByUserID(ctx context.Context, UserID int64) ([]domain.GetBet, error)
GetBetOutcomeByEventID(ctx context.Context, eventID int64) ([]domain.BetOutcome, error) GetBetOutcomeByEventID(ctx context.Context, eventID int64) ([]domain.BetOutcome, error)
GetBetOutcomeByBetID(ctx context.Context, betID int64) ([]domain.BetOutcome, error) GetBetOutcomeByBetID(ctx context.Context, betID int64) ([]domain.BetOutcome, error)
GetBetCount(ctx context.Context, userID int64, outcomesHash string) (int64, error)
UpdateCashOut(ctx context.Context, id int64, cashedOut bool) error UpdateCashOut(ctx context.Context, id int64, cashedOut bool) error
UpdateStatus(ctx context.Context, id int64, status domain.OutcomeStatus) error UpdateStatus(ctx context.Context, id int64, status domain.OutcomeStatus) error
UpdateBetOutcomeStatus(ctx context.Context, id int64, status domain.OutcomeStatus) (domain.BetOutcome, error) UpdateBetOutcomeStatus(ctx context.Context, id int64, status domain.OutcomeStatus) (domain.BetOutcome, error)

View File

@ -3,13 +3,17 @@ package bet
import ( import (
"context" "context"
"crypto/rand" "crypto/rand"
"crypto/sha256"
"encoding/hex"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"log/slog" "log/slog"
"math/big" "math/big"
random "math/rand" random "math/rand"
"sort"
"strconv" "strconv"
"strings"
"time" "time"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
@ -196,6 +200,7 @@ func (s *Service) PlaceBet(ctx context.Context, req domain.CreateBetReq, userID
var totalOdds float32 = 1 var totalOdds float32 = 1
for _, outcomeReq := range req.Outcomes { for _, outcomeReq := range req.Outcomes {
fmt.Println("reqq: ", outcomeReq)
newOutcome, err := s.GenerateBetOutcome(ctx, outcomeReq.EventID, outcomeReq.MarketID, outcomeReq.OddID) newOutcome, err := s.GenerateBetOutcome(ctx, outcomeReq.EventID, outcomeReq.MarketID, outcomeReq.OddID)
if err != nil { if err != nil {
s.mongoLogger.Error("failed to generate outcome", s.mongoLogger.Error("failed to generate outcome",
@ -211,6 +216,23 @@ func (s *Service) PlaceBet(ctx context.Context, req domain.CreateBetReq, userID
outcomes = append(outcomes, newOutcome) outcomes = append(outcomes, newOutcome)
} }
outcomesHash, err := generateOutcomeHash(outcomes)
if err != nil {
s.mongoLogger.Error("failed to generate outcome hash",
zap.Int64("user_id", userID),
zap.Error(err),
)
return domain.CreateBetRes{}, err
}
count, err := s.GetBetCount(ctx, userID, outcomesHash)
if err != nil {
return domain.CreateBetRes{}, err
}
if count == 2 {
return domain.CreateBetRes{}, fmt.Errorf("bet already pleaced twice")
}
cashoutID, err := s.GenerateCashoutID() cashoutID, err := s.GenerateCashoutID()
if err != nil { if err != nil {
s.mongoLogger.Error("failed to generate cashout ID", s.mongoLogger.Error("failed to generate cashout ID",
@ -221,12 +243,13 @@ func (s *Service) PlaceBet(ctx context.Context, req domain.CreateBetReq, userID
} }
newBet := domain.CreateBet{ newBet := domain.CreateBet{
Amount: domain.ToCurrency(req.Amount), Amount: domain.ToCurrency(req.Amount),
TotalOdds: totalOdds, TotalOdds: totalOdds,
Status: req.Status, Status: req.Status,
FullName: req.FullName, FullName: req.FullName,
PhoneNumber: req.PhoneNumber, PhoneNumber: req.PhoneNumber,
CashoutID: cashoutID, CashoutID: cashoutID,
OutcomesHash: outcomesHash,
} }
switch role { switch role {
@ -321,6 +344,7 @@ func (s *Service) PlaceBet(ctx context.Context, req domain.CreateBetReq, userID
return domain.CreateBetRes{}, fmt.Errorf("Unknown Role Type") return domain.CreateBetRes{}, fmt.Errorf("Unknown Role Type")
} }
fmt.Println("Bet is: ", newBet)
bet, err := s.CreateBet(ctx, newBet) bet, err := s.CreateBet(ctx, newBet)
if err != nil { if err != nil {
s.mongoLogger.Error("failed to create bet", s.mongoLogger.Error("failed to create bet",
@ -636,6 +660,10 @@ func (s *Service) GetBetByUserID(ctx context.Context, UserID int64) ([]domain.Ge
return s.betStore.GetBetByUserID(ctx, UserID) return s.betStore.GetBetByUserID(ctx, UserID)
} }
func (s *Service) GetBetCount(ctx context.Context, UserID int64, outcomesHash string) (int64, error) {
return s.betStore.GetBetCount(ctx, UserID, outcomesHash)
}
func (s *Service) UpdateCashOut(ctx context.Context, id int64, cashedOut bool) error { func (s *Service) UpdateCashOut(ctx context.Context, id int64, cashedOut bool) error {
return s.betStore.UpdateCashOut(ctx, id, cashedOut) return s.betStore.UpdateCashOut(ctx, id, cashedOut)
} }
@ -785,3 +813,24 @@ func (s *Service) UpdateBetOutcomeStatus(ctx context.Context, id int64, status d
func (s *Service) DeleteBet(ctx context.Context, id int64) error { func (s *Service) DeleteBet(ctx context.Context, id int64) error {
return s.betStore.DeleteBet(ctx, id) return s.betStore.DeleteBet(ctx, id)
} }
func generateOutcomeHash(outcomes []domain.CreateBetOutcome) (string, error) {
// should always be in the same order for producing the same hash
sort.Slice(outcomes, func(i, j int) bool {
if outcomes[i].EventID != outcomes[j].EventID {
return outcomes[i].EventID < outcomes[j].EventID
}
if outcomes[i].MarketID != outcomes[j].MarketID {
return outcomes[i].MarketID < outcomes[j].MarketID
}
return outcomes[i].OddID < outcomes[j].OddID
})
var sb strings.Builder
for _, o := range outcomes {
sb.WriteString(fmt.Sprintf("%d-%d-%d;", o.EventID, o.MarketID, o.OddID))
}
sum := sha256.Sum256([]byte(sb.String()))
return hex.EncodeToString(sum[:]), nil
}

View File

@ -40,8 +40,7 @@ func (h *Handler) CreateBet(c *fiber.Ctx) error {
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
} }
res, err := h.betSvc. res, err := h.betSvc.PlaceBet(c.Context(), req, userID, role)
PlaceBet(c.Context(), req, userID, role)
if err != nil { if err != nil {
h.logger.Error("PlaceBet failed", "error", err) h.logger.Error("PlaceBet failed", "error", err)