create random bet
This commit is contained in:
parent
8e271559ae
commit
b7b17fa8d2
|
|
@ -70,14 +70,13 @@ func main() {
|
||||||
|
|
||||||
eventSvc := event.New(cfg.Bet365Token, store)
|
eventSvc := event.New(cfg.Bet365Token, store)
|
||||||
oddsSvc := odds.New(store, cfg, logger)
|
oddsSvc := odds.New(store, cfg, logger)
|
||||||
resultSvc := result.NewService(store, cfg, logger)
|
|
||||||
ticketSvc := ticket.NewService(store)
|
ticketSvc := ticket.NewService(store)
|
||||||
betSvc := bet.NewService(store)
|
|
||||||
walletSvc := wallet.NewService(store, store)
|
walletSvc := wallet.NewService(store, store)
|
||||||
transactionSvc := transaction.NewService(store)
|
transactionSvc := transaction.NewService(store)
|
||||||
branchSvc := branch.NewService(store)
|
branchSvc := branch.NewService(store)
|
||||||
companySvc := company.NewService(store)
|
companySvc := company.NewService(store)
|
||||||
|
betSvc := bet.NewService(store, eventSvc, oddsSvc, *walletSvc, *branchSvc, logger)
|
||||||
|
resultSvc := result.NewService(store, cfg, logger, *betSvc)
|
||||||
notificationRepo := repository.NewNotificationRepository(store)
|
notificationRepo := repository.NewNotificationRepository(store)
|
||||||
referalRepo := repository.NewReferralRepository(store)
|
referalRepo := repository.NewReferralRepository(store)
|
||||||
vitualGameRepo := repository.NewVirtualGameRepository(store)
|
vitualGameRepo := repository.NewVirtualGameRepository(store)
|
||||||
|
|
|
||||||
|
|
@ -62,16 +62,16 @@ WHERE branch_id = $1;
|
||||||
SELECT *
|
SELECT *
|
||||||
FROM bet_outcomes
|
FROM bet_outcomes
|
||||||
WHERE event_id = $1;
|
WHERE event_id = $1;
|
||||||
|
|
||||||
-- name: UpdateCashOut :exec
|
-- name: UpdateCashOut :exec
|
||||||
UPDATE bets
|
UPDATE bets
|
||||||
SET cashed_out = $2,
|
SET cashed_out = $2,
|
||||||
updated_at = CURRENT_TIMESTAMP
|
updated_at = CURRENT_TIMESTAMP
|
||||||
WHERE id = $1;
|
WHERE id = $1;
|
||||||
-- name: UpdateBetOutcomeStatus :exec
|
-- name: UpdateBetOutcomeStatus :one
|
||||||
UPDATE bet_outcomes
|
UPDATE bet_outcomes
|
||||||
SET status = $1
|
SET status = $1
|
||||||
WHERE id = $2;
|
WHERE id = $2
|
||||||
|
RETURNING *;
|
||||||
-- name: UpdateStatus :exec
|
-- name: UpdateStatus :exec
|
||||||
UPDATE bets
|
UPDATE bets
|
||||||
SET status = $2,
|
SET status = $2,
|
||||||
|
|
|
||||||
|
|
@ -285,10 +285,11 @@ func (q *Queries) GetBetOutcomeByEventID(ctx context.Context, eventID int64) ([]
|
||||||
return items, nil
|
return items, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
const UpdateBetOutcomeStatus = `-- name: UpdateBetOutcomeStatus :exec
|
const UpdateBetOutcomeStatus = `-- name: UpdateBetOutcomeStatus :one
|
||||||
UPDATE bet_outcomes
|
UPDATE bet_outcomes
|
||||||
SET status = $1
|
SET status = $1
|
||||||
WHERE id = $2
|
WHERE id = $2
|
||||||
|
RETURNING 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
|
||||||
`
|
`
|
||||||
|
|
||||||
type UpdateBetOutcomeStatusParams struct {
|
type UpdateBetOutcomeStatusParams struct {
|
||||||
|
|
@ -296,9 +297,27 @@ type UpdateBetOutcomeStatusParams struct {
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) UpdateBetOutcomeStatus(ctx context.Context, arg UpdateBetOutcomeStatusParams) error {
|
func (q *Queries) UpdateBetOutcomeStatus(ctx context.Context, arg UpdateBetOutcomeStatusParams) (BetOutcome, error) {
|
||||||
_, err := q.db.Exec(ctx, UpdateBetOutcomeStatus, arg.Status, arg.ID)
|
row := q.db.QueryRow(ctx, UpdateBetOutcomeStatus, arg.Status, arg.ID)
|
||||||
return err
|
var i BetOutcome
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.BetID,
|
||||||
|
&i.SportID,
|
||||||
|
&i.EventID,
|
||||||
|
&i.OddID,
|
||||||
|
&i.HomeTeamName,
|
||||||
|
&i.AwayTeamName,
|
||||||
|
&i.MarketID,
|
||||||
|
&i.MarketName,
|
||||||
|
&i.Odd,
|
||||||
|
&i.OddName,
|
||||||
|
&i.OddHeader,
|
||||||
|
&i.OddHandicap,
|
||||||
|
&i.Status,
|
||||||
|
&i.Expires,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
||||||
const UpdateCashOut = `-- name: UpdateCashOut :exec
|
const UpdateCashOut = `-- name: UpdateCashOut :exec
|
||||||
|
|
|
||||||
|
|
@ -80,3 +80,82 @@ type CreateBet struct {
|
||||||
IsShopBet bool
|
IsShopBet bool
|
||||||
CashoutID string
|
CashoutID string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CreateBetOutcomeReq struct {
|
||||||
|
EventID int64 `json:"event_id" example:"1"`
|
||||||
|
OddID int64 `json:"odd_id" example:"1"`
|
||||||
|
MarketID int64 `json:"market_id" example:"1"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateBetReq struct {
|
||||||
|
Outcomes []CreateBetOutcomeReq `json:"outcomes"`
|
||||||
|
Amount float32 `json:"amount" example:"100.0"`
|
||||||
|
Status OutcomeStatus `json:"status" example:"1"`
|
||||||
|
FullName string `json:"full_name" example:"John"`
|
||||||
|
PhoneNumber string `json:"phone_number" example:"1234567890"`
|
||||||
|
BranchID *int64 `json:"branch_id,omitempty" example:"1"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RandomBetReq struct {
|
||||||
|
BranchID int64 `json:"branch_id,omitempty" example:"1"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateBetRes struct {
|
||||||
|
ID int64 `json:"id" example:"1"`
|
||||||
|
Amount float32 `json:"amount" example:"100.0"`
|
||||||
|
TotalOdds float32 `json:"total_odds" example:"4.22"`
|
||||||
|
Status OutcomeStatus `json:"status" example:"1"`
|
||||||
|
FullName string `json:"full_name" example:"John"`
|
||||||
|
PhoneNumber string `json:"phone_number" example:"1234567890"`
|
||||||
|
BranchID int64 `json:"branch_id" example:"2"`
|
||||||
|
UserID int64 `json:"user_id" example:"2"`
|
||||||
|
IsShopBet bool `json:"is_shop_bet" example:"false"`
|
||||||
|
CreatedNumber int64 `json:"created_number" example:"2"`
|
||||||
|
CashedID string `json:"cashed_id" example:"21234"`
|
||||||
|
}
|
||||||
|
type BetRes struct {
|
||||||
|
ID int64 `json:"id" example:"1"`
|
||||||
|
Outcomes []BetOutcome `json:"outcomes"`
|
||||||
|
Amount float32 `json:"amount" example:"100.0"`
|
||||||
|
TotalOdds float32 `json:"total_odds" example:"4.22"`
|
||||||
|
Status OutcomeStatus `json:"status" example:"1"`
|
||||||
|
FullName string `json:"full_name" example:"John"`
|
||||||
|
PhoneNumber string `json:"phone_number" example:"1234567890"`
|
||||||
|
BranchID int64 `json:"branch_id" example:"2"`
|
||||||
|
UserID int64 `json:"user_id" example:"2"`
|
||||||
|
IsShopBet bool `json:"is_shop_bet" example:"false"`
|
||||||
|
CashedOut bool `json:"cashed_out" example:"false"`
|
||||||
|
CashedID string `json:"cashed_id" example:"21234"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func ConvertCreateBet(bet Bet, createdNumber int64) CreateBetRes {
|
||||||
|
return CreateBetRes{
|
||||||
|
ID: bet.ID,
|
||||||
|
Amount: bet.Amount.Float32(),
|
||||||
|
TotalOdds: bet.TotalOdds,
|
||||||
|
Status: bet.Status,
|
||||||
|
FullName: bet.FullName,
|
||||||
|
PhoneNumber: bet.PhoneNumber,
|
||||||
|
BranchID: bet.BranchID.Value,
|
||||||
|
UserID: bet.UserID.Value,
|
||||||
|
CreatedNumber: createdNumber,
|
||||||
|
CashedID: bet.CashoutID,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ConvertBet(bet GetBet) BetRes {
|
||||||
|
return BetRes{
|
||||||
|
ID: bet.ID,
|
||||||
|
Amount: bet.Amount.Float32(),
|
||||||
|
TotalOdds: bet.TotalOdds,
|
||||||
|
Status: bet.Status,
|
||||||
|
FullName: bet.FullName,
|
||||||
|
PhoneNumber: bet.PhoneNumber,
|
||||||
|
BranchID: bet.BranchID.Value,
|
||||||
|
UserID: bet.UserID.Value,
|
||||||
|
Outcomes: bet.Outcomes,
|
||||||
|
IsShopBet: bet.IsShopBet,
|
||||||
|
CashedOut: bet.CashedOut,
|
||||||
|
CashedID: bet.CashoutID,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -225,12 +225,13 @@ func (s *Store) GetBetOutcomeByEventID(ctx context.Context, eventID int64) ([]do
|
||||||
}
|
}
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
func (s *Store) UpdateBetOutcomeStatus(ctx context.Context, id int64, status domain.OutcomeStatus) error {
|
func (s *Store) UpdateBetOutcomeStatus(ctx context.Context, id int64, status domain.OutcomeStatus) (domain.BetOutcome, error) {
|
||||||
err := s.queries.UpdateBetOutcomeStatus(ctx, dbgen.UpdateBetOutcomeStatusParams{
|
update, err := s.queries.UpdateBetOutcomeStatus(ctx, dbgen.UpdateBetOutcomeStatusParams{
|
||||||
Status: int32(status),
|
Status: int32(status),
|
||||||
ID: id,
|
ID: id,
|
||||||
})
|
})
|
||||||
return err
|
res := convertDBBetOutcomes(update)
|
||||||
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Store) DeleteBet(ctx context.Context, id int64) error {
|
func (s *Store) DeleteBet(ctx context.Context, id int64) error {
|
||||||
|
|
|
||||||
|
|
@ -13,8 +13,10 @@ type BetStore interface {
|
||||||
GetBetByID(ctx context.Context, id int64) (domain.GetBet, error)
|
GetBetByID(ctx context.Context, id int64) (domain.GetBet, error)
|
||||||
GetAllBets(ctx context.Context) ([]domain.GetBet, error)
|
GetAllBets(ctx context.Context) ([]domain.GetBet, error)
|
||||||
GetBetByBranchID(ctx context.Context, BranchID int64) ([]domain.GetBet, error)
|
GetBetByBranchID(ctx context.Context, BranchID int64) ([]domain.GetBet, error)
|
||||||
|
GetBetOutcomeByEventID(ctx context.Context, eventID int64) ([]domain.BetOutcome, 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) error
|
UpdateBetOutcomeStatus(ctx context.Context, id int64, status domain.OutcomeStatus) (domain.BetOutcome, error)
|
||||||
DeleteBet(ctx context.Context, id int64) error
|
DeleteBet(ctx context.Context, id int64) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,21 +3,50 @@ package bet
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
random "math/rand"
|
||||||
|
"slices"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/branch"
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/event"
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds"
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/wallet"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Service struct {
|
type Service struct {
|
||||||
betStore BetStore
|
betStore BetStore
|
||||||
|
eventSvc event.Service
|
||||||
|
prematchSvc odds.Service
|
||||||
|
walletSvc wallet.Service
|
||||||
|
branchSvc branch.Service
|
||||||
|
logger *slog.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewService(betStore BetStore) *Service {
|
func NewService(betStore BetStore, eventSvc event.Service, prematchSvc odds.Service, walletSvc wallet.Service, branchSvc branch.Service, logger *slog.Logger) *Service {
|
||||||
return &Service{
|
return &Service{
|
||||||
betStore: betStore,
|
betStore: betStore,
|
||||||
|
eventSvc: eventSvc,
|
||||||
|
prematchSvc: prematchSvc,
|
||||||
|
walletSvc: walletSvc,
|
||||||
|
branchSvc: branchSvc,
|
||||||
|
logger: logger,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrEventHasNotEnded = errors.New("Event has not ended yet")
|
||||||
|
ErrRawOddInvalid = errors.New("Prematch Raw Odd is Invalid")
|
||||||
|
ErrBranchIDRequired = errors.New("Branch ID required for this role")
|
||||||
|
ErrOutcomeLimit = errors.New("Too many outcomes on a single bet")
|
||||||
|
)
|
||||||
|
|
||||||
func (s *Service) GenerateCashoutID() (string, error) {
|
func (s *Service) GenerateCashoutID() (string, error) {
|
||||||
const chars = "abcdefghijklmnopqrstuvwxyz0123456789"
|
const chars = "abcdefghijklmnopqrstuvwxyz0123456789"
|
||||||
const length int = 13
|
const length int = 13
|
||||||
|
|
@ -33,8 +62,365 @@ func (s *Service) GenerateCashoutID() (string, error) {
|
||||||
return string(result), nil
|
return string(result), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) CreateBet(ctx context.Context, bet domain.CreateBet) (domain.Bet, error) {
|
func (s *Service) GenerateBetOutcome(ctx context.Context, eventID int64, marketID int64, oddID int64) (domain.CreateBetOutcome, error) {
|
||||||
|
// TODO: Change this when you refactor the database code
|
||||||
|
eventIDStr := strconv.FormatInt(eventID, 10)
|
||||||
|
marketIDStr := strconv.FormatInt(marketID, 10)
|
||||||
|
oddIDStr := strconv.FormatInt(oddID, 10)
|
||||||
|
|
||||||
|
event, err := s.eventSvc.GetUpcomingEventByID(ctx, eventIDStr)
|
||||||
|
if err != nil {
|
||||||
|
return domain.CreateBetOutcome{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
currentTime := time.Now()
|
||||||
|
if event.StartTime.Before(currentTime) {
|
||||||
|
return domain.CreateBetOutcome{}, ErrEventHasNotEnded
|
||||||
|
}
|
||||||
|
|
||||||
|
odds, err := s.prematchSvc.GetRawOddsByMarketID(ctx, marketIDStr, eventIDStr)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return domain.CreateBetOutcome{}, err
|
||||||
|
}
|
||||||
|
type rawOddType struct {
|
||||||
|
ID string
|
||||||
|
Name string
|
||||||
|
Odds string
|
||||||
|
Header string
|
||||||
|
Handicap string
|
||||||
|
}
|
||||||
|
|
||||||
|
var selectedOdd rawOddType
|
||||||
|
var isOddFound bool = false
|
||||||
|
|
||||||
|
for _, raw := range odds.RawOdds {
|
||||||
|
var rawOdd rawOddType
|
||||||
|
rawBytes, err := json.Marshal(raw)
|
||||||
|
err = json.Unmarshal(rawBytes, &rawOdd)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Failed to unmarshal raw odd %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if rawOdd.ID == oddIDStr {
|
||||||
|
selectedOdd = rawOdd
|
||||||
|
isOddFound = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !isOddFound {
|
||||||
|
return domain.CreateBetOutcome{}, ErrRawOddInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
parsedOdd, err := strconv.ParseFloat(selectedOdd.Odds, 32)
|
||||||
|
if err != nil {
|
||||||
|
return domain.CreateBetOutcome{}, err
|
||||||
|
}
|
||||||
|
sportID, err := strconv.ParseInt(event.SportID, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return domain.CreateBetOutcome{}, err
|
||||||
|
}
|
||||||
|
newOutcome := domain.CreateBetOutcome{
|
||||||
|
EventID: eventID,
|
||||||
|
OddID: oddID,
|
||||||
|
MarketID: marketID,
|
||||||
|
SportID: sportID,
|
||||||
|
HomeTeamName: event.HomeTeam,
|
||||||
|
AwayTeamName: event.AwayTeam,
|
||||||
|
MarketName: odds.MarketName,
|
||||||
|
Odd: float32(parsedOdd),
|
||||||
|
OddName: selectedOdd.Name,
|
||||||
|
OddHeader: selectedOdd.Header,
|
||||||
|
OddHandicap: selectedOdd.Handicap,
|
||||||
|
Expires: event.StartTime,
|
||||||
|
}
|
||||||
|
|
||||||
|
return newOutcome, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) PlaceBet(ctx context.Context, req domain.CreateBetReq, userID int64, role domain.Role) (domain.CreateBetRes, error) {
|
||||||
|
// You can move the loop over req.Outcomes and all the business logic here.
|
||||||
|
|
||||||
|
if len(req.Outcomes) > 30 {
|
||||||
|
return domain.CreateBetRes{}, ErrOutcomeLimit
|
||||||
|
}
|
||||||
|
|
||||||
|
var outcomes []domain.CreateBetOutcome = make([]domain.CreateBetOutcome, 0, len(req.Outcomes))
|
||||||
|
var totalOdds float32 = 1
|
||||||
|
|
||||||
|
for _, outcomeReq := range req.Outcomes {
|
||||||
|
newOutcome, err := s.GenerateBetOutcome(ctx, outcomeReq.EventID, outcomeReq.MarketID, outcomeReq.OddID)
|
||||||
|
if err != nil {
|
||||||
|
return domain.CreateBetRes{}, err
|
||||||
|
}
|
||||||
|
totalOdds = totalOdds * float32(newOutcome.Odd)
|
||||||
|
outcomes = append(outcomes, newOutcome)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle role-specific logic and wallet deduction if needed.
|
||||||
|
var cashoutID string
|
||||||
|
cashoutID, err := s.GenerateCashoutID()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return domain.CreateBetRes{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
newBet := domain.CreateBet{
|
||||||
|
Amount: domain.ToCurrency(req.Amount),
|
||||||
|
TotalOdds: totalOdds,
|
||||||
|
Status: req.Status,
|
||||||
|
FullName: req.FullName,
|
||||||
|
PhoneNumber: req.PhoneNumber,
|
||||||
|
CashoutID: cashoutID,
|
||||||
|
}
|
||||||
|
switch role {
|
||||||
|
case domain.RoleCashier:
|
||||||
|
branch, err := s.branchSvc.GetBranchByCashier(ctx, userID)
|
||||||
|
if err != nil {
|
||||||
|
return domain.CreateBetRes{}, err
|
||||||
|
}
|
||||||
|
// Deduct from wallet:
|
||||||
|
// TODO: Make this percentage come from the company
|
||||||
|
var deductedAmount = req.Amount / 10
|
||||||
|
err = s.walletSvc.DeductFromWallet(ctx, branch.WalletID, domain.ToCurrency(deductedAmount))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return domain.CreateBetRes{}, err
|
||||||
|
}
|
||||||
|
newBet.BranchID = domain.ValidInt64{
|
||||||
|
Value: branch.ID,
|
||||||
|
Valid: true,
|
||||||
|
}
|
||||||
|
newBet.UserID = domain.ValidInt64{
|
||||||
|
Value: userID,
|
||||||
|
Valid: true,
|
||||||
|
}
|
||||||
|
newBet.IsShopBet = true
|
||||||
|
// bet, err = s.betStore.CreateBet(ctx)
|
||||||
|
case domain.RoleBranchManager, domain.RoleAdmin, domain.RoleSuperAdmin:
|
||||||
|
// TODO: restrict the Branch ID of Admin and Branch Manager to only the branches within their own company
|
||||||
|
// If a non cashier wants to create a bet, they will need to provide the Branch ID
|
||||||
|
if req.BranchID == nil {
|
||||||
|
return domain.CreateBetRes{}, ErrBranchIDRequired
|
||||||
|
}
|
||||||
|
|
||||||
|
newBet.BranchID = domain.ValidInt64{
|
||||||
|
Value: *req.BranchID,
|
||||||
|
Valid: true,
|
||||||
|
}
|
||||||
|
newBet.UserID = domain.ValidInt64{
|
||||||
|
Value: userID,
|
||||||
|
Valid: true,
|
||||||
|
}
|
||||||
|
newBet.IsShopBet = true
|
||||||
|
case domain.RoleCustomer:
|
||||||
|
return domain.CreateBetRes{}, fmt.Errorf("Not yet implemented")
|
||||||
|
default:
|
||||||
|
return domain.CreateBetRes{}, fmt.Errorf("Unknown Role Type")
|
||||||
|
}
|
||||||
|
|
||||||
|
bet, err := s.CreateBet(ctx, newBet)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return domain.CreateBetRes{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Associate outcomes with the bet.
|
||||||
|
for i := range outcomes {
|
||||||
|
outcomes[i].BetID = bet.ID
|
||||||
|
}
|
||||||
|
rows, err := s.betStore.CreateBetOutcome(ctx, outcomes)
|
||||||
|
if err != nil {
|
||||||
|
return domain.CreateBetRes{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
res := domain.ConvertCreateBet(bet, rows)
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) GenerateRandomBetOutcomes(ctx context.Context, eventID, sportID, HomeTeam, AwayTeam string, StartTime time.Time) ([]domain.CreateBetOutcome, float32, error) {
|
||||||
|
|
||||||
|
var newOdds []domain.CreateBetOutcome
|
||||||
|
var totalOdds float32 = 1
|
||||||
|
|
||||||
|
markets, err := s.prematchSvc.GetPrematchOdds(ctx, eventID)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("failed to get odds for event", "event id", eventID, "error", err)
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(markets) == 0 {
|
||||||
|
s.logger.Error("empty odds for event", "event id", eventID)
|
||||||
|
return nil, 0, fmt.Errorf("empty odds or event", "event id", eventID)
|
||||||
|
}
|
||||||
|
|
||||||
|
var numMarkets = min(5, len(markets))
|
||||||
|
var randIndex []int = make([]int, numMarkets)
|
||||||
|
for i := 0; i < numMarkets; i++ {
|
||||||
|
// Guarantee that the odd is unique
|
||||||
|
var newRandMarket int
|
||||||
|
count := 0
|
||||||
|
for {
|
||||||
|
newRandMarket = random.Intn(len(markets))
|
||||||
|
if !slices.Contains(randIndex, newRandMarket) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// just in case
|
||||||
|
if count >= 5 {
|
||||||
|
s.logger.Warn("market overload", "event id", eventID)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
|
||||||
|
randIndex[i] = newRandMarket
|
||||||
|
|
||||||
|
rawOdds := markets[i].RawOdds
|
||||||
|
randomRawOdd := rawOdds[random.Intn(len(rawOdds))]
|
||||||
|
|
||||||
|
type rawOddType struct {
|
||||||
|
ID string
|
||||||
|
Name string
|
||||||
|
Odds string
|
||||||
|
Header string
|
||||||
|
Handicap string
|
||||||
|
}
|
||||||
|
|
||||||
|
var selectedOdd rawOddType
|
||||||
|
rawBytes, err := json.Marshal(randomRawOdd)
|
||||||
|
err = json.Unmarshal(rawBytes, &selectedOdd)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Failed to unmarshal raw odd %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
parsedOdd, err := strconv.ParseFloat(selectedOdd.Odds, 32)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("Failed to parse odd", "error", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
sportID, err := strconv.ParseInt(sportID, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("Failed to get sport id", "error", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
eventID, err := strconv.ParseInt(eventID, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("Failed to get event id", "error", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
oddID, err := strconv.ParseInt(selectedOdd.ID, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("Failed to get odd id", "error", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
marketID, err := strconv.ParseInt(markets[i].MarketID, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("Failed to get odd id", "error", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
marketName := markets[i].MarketName
|
||||||
|
|
||||||
|
newOdds = append(newOdds, domain.CreateBetOutcome{
|
||||||
|
EventID: eventID,
|
||||||
|
OddID: oddID,
|
||||||
|
MarketID: marketID,
|
||||||
|
SportID: sportID,
|
||||||
|
HomeTeamName: HomeTeam,
|
||||||
|
AwayTeamName: AwayTeam,
|
||||||
|
MarketName: marketName,
|
||||||
|
Odd: float32(parsedOdd),
|
||||||
|
OddName: selectedOdd.Name,
|
||||||
|
OddHeader: selectedOdd.Header,
|
||||||
|
OddHandicap: selectedOdd.Handicap,
|
||||||
|
Expires: StartTime,
|
||||||
|
})
|
||||||
|
|
||||||
|
totalOdds = totalOdds * float32(parsedOdd)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(newOdds) == 0 {
|
||||||
|
s.logger.Error("Failed to generate random outcomes")
|
||||||
|
return nil, 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return newOdds, totalOdds, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) PlaceRandomBet(ctx context.Context, userID, branchID int64) (domain.CreateBetRes, error) {
|
||||||
|
|
||||||
|
// Get a unexpired event id
|
||||||
|
events, _, err := s.eventSvc.GetPaginatedUpcomingEvents(ctx, 5, 0, domain.ValidString{}, domain.ValidString{})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return domain.CreateBetRes{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get market and odds for that
|
||||||
|
var randomOdds []domain.CreateBetOutcome
|
||||||
|
var totalOdds float32 = 1
|
||||||
|
for _, event := range events {
|
||||||
|
|
||||||
|
newOdds, total, err := s.GenerateRandomBetOutcomes(ctx, event.ID, event.SportID, event.HomeTeam, event.AwayTeam, event.StartTime)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("failed to generate random bet outcome", "event id", event.ID, "error", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
randomOdds = append(randomOdds, newOdds...)
|
||||||
|
totalOdds = totalOdds * total
|
||||||
|
|
||||||
|
}
|
||||||
|
if len(randomOdds) == 0 {
|
||||||
|
s.logger.Error("Failed to generate random outcomes")
|
||||||
|
return domain.CreateBetRes{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var cashoutID string
|
||||||
|
|
||||||
|
cashoutID, err = s.GenerateCashoutID()
|
||||||
|
if err != nil {
|
||||||
|
return domain.CreateBetRes{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
randomNumber := strconv.FormatInt(int64(random.Intn(10)), 10)
|
||||||
|
newBet := domain.CreateBet{
|
||||||
|
Amount: 123,
|
||||||
|
TotalOdds: totalOdds,
|
||||||
|
Status: domain.OUTCOME_STATUS_PENDING,
|
||||||
|
FullName: "test" + randomNumber,
|
||||||
|
PhoneNumber: randomNumber,
|
||||||
|
CashoutID: cashoutID,
|
||||||
|
BranchID: domain.ValidInt64{Valid: true, Value: branchID},
|
||||||
|
UserID: domain.ValidInt64{Valid: true, Value: userID},
|
||||||
|
}
|
||||||
|
|
||||||
|
bet, err := s.CreateBet(ctx, newBet)
|
||||||
|
if err != nil {
|
||||||
|
return domain.CreateBetRes{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range randomOdds {
|
||||||
|
randomOdds[i].BetID = bet.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
rows, err := s.betStore.CreateBetOutcome(ctx, randomOdds)
|
||||||
|
if err != nil {
|
||||||
|
return domain.CreateBetRes{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
res := domain.ConvertCreateBet(bet, rows)
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) CreateBet(ctx context.Context, bet domain.CreateBet) (domain.Bet, error) {
|
||||||
return s.betStore.CreateBet(ctx, bet)
|
return s.betStore.CreateBet(ctx, bet)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -64,8 +450,43 @@ func (s *Service) UpdateStatus(ctx context.Context, id int64, status domain.Outc
|
||||||
return s.betStore.UpdateStatus(ctx, id, status)
|
return s.betStore.UpdateStatus(ctx, id, status)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Service) checkBetOutcomeForBet(ctx context.Context, eventID int64) error {
|
||||||
|
betOutcomes, err := s.betStore.GetBetOutcomeByEventID(ctx, eventID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
status := domain.OUTCOME_STATUS_PENDING
|
||||||
|
|
||||||
|
for _, betOutcome := range betOutcomes {
|
||||||
|
// Check if any of them are pending
|
||||||
|
if betOutcome.Status == domain.OUTCOME_STATUS_PENDING {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if status == domain.OUTCOME_STATUS_PENDING {
|
||||||
|
status = betOutcome.Status
|
||||||
|
} else if status == domain.OUTCOME_STATUS_WIN {
|
||||||
|
status = betOutcome.Status
|
||||||
|
} else if status == domain.OUTCOME_STATUS_LOSS {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if status != domain.OUTCOME_STATUS_PENDING {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.UpdateStatus(ctx, eventID, status)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Service) UpdateBetOutcomeStatus(ctx context.Context, id int64, status domain.OutcomeStatus) error {
|
func (s *Service) UpdateBetOutcomeStatus(ctx context.Context, id int64, status domain.OutcomeStatus) error {
|
||||||
return s.betStore.UpdateBetOutcomeStatus(ctx, id, status)
|
betOutcome, err := s.betStore.UpdateBetOutcomeStatus(ctx, id, status)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return s.checkBetOutcomeForBet(ctx, betOutcome.EventID)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) DeleteBet(ctx context.Context, id int64) error {
|
func (s *Service) DeleteBet(ctx context.Context, id int64) error {
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ import (
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/config"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/config"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/repository"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/repository"
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/bet"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Service struct {
|
type Service struct {
|
||||||
|
|
@ -20,14 +21,16 @@ type Service struct {
|
||||||
config *config.Config
|
config *config.Config
|
||||||
logger *slog.Logger
|
logger *slog.Logger
|
||||||
client *http.Client
|
client *http.Client
|
||||||
|
betSvc bet.Service
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewService(repo *repository.Store, cfg *config.Config, logger *slog.Logger) *Service {
|
func NewService(repo *repository.Store, cfg *config.Config, logger *slog.Logger, betSvc bet.Service) *Service {
|
||||||
return &Service{
|
return &Service{
|
||||||
repo: repo,
|
repo: repo,
|
||||||
config: cfg,
|
config: cfg,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
client: &http.Client{Timeout: 10 * time.Second},
|
client: &http.Client{Timeout: 10 * time.Second},
|
||||||
|
betSvc: betSvc,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -85,7 +88,7 @@ func (s *Service) FetchAndProcessResults(ctx context.Context) error {
|
||||||
// continue
|
// continue
|
||||||
// }
|
// }
|
||||||
|
|
||||||
err = s.repo.UpdateBetOutcomeStatus(ctx, outcome.ID, result.Status)
|
_, err = s.repo.UpdateBetOutcomeStatus(ctx, outcome.ID, result.Status)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Error("Failed to update bet outcome status", "bet_outcome_id", outcome.ID, "error", err)
|
s.logger.Error("Failed to update bet outcome status", "bet_outcome_id", outcome.ID, "error", err)
|
||||||
continue
|
continue
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
package handlers
|
package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"log/slog"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||||
|
|
@ -10,88 +8,13 @@ import (
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
type CreateBetOutcomeReq struct {
|
|
||||||
EventID int64 `json:"event_id" example:"1"`
|
|
||||||
OddID int64 `json:"odd_id" example:"1"`
|
|
||||||
MarketID int64 `json:"market_id" example:"1"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type CreateBetReq struct {
|
|
||||||
Outcomes []CreateBetOutcomeReq `json:"outcomes"`
|
|
||||||
Amount float32 `json:"amount" example:"100.0"`
|
|
||||||
Status domain.OutcomeStatus `json:"status" example:"1"`
|
|
||||||
FullName string `json:"full_name" example:"John"`
|
|
||||||
PhoneNumber string `json:"phone_number" example:"1234567890"`
|
|
||||||
BranchID *int64 `json:"branch_id,omitempty" example:"1"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type CreateBetRes struct {
|
|
||||||
ID int64 `json:"id" example:"1"`
|
|
||||||
Amount float32 `json:"amount" example:"100.0"`
|
|
||||||
TotalOdds float32 `json:"total_odds" example:"4.22"`
|
|
||||||
Status domain.OutcomeStatus `json:"status" example:"1"`
|
|
||||||
FullName string `json:"full_name" example:"John"`
|
|
||||||
PhoneNumber string `json:"phone_number" example:"1234567890"`
|
|
||||||
BranchID int64 `json:"branch_id" example:"2"`
|
|
||||||
UserID int64 `json:"user_id" example:"2"`
|
|
||||||
IsShopBet bool `json:"is_shop_bet" example:"false"`
|
|
||||||
CreatedNumber int64 `json:"created_number" example:"2"`
|
|
||||||
CashedID string `json:"cashed_id" example:"21234"`
|
|
||||||
}
|
|
||||||
type BetRes struct {
|
|
||||||
ID int64 `json:"id" example:"1"`
|
|
||||||
Outcomes []domain.BetOutcome `json:"outcomes"`
|
|
||||||
Amount float32 `json:"amount" example:"100.0"`
|
|
||||||
TotalOdds float32 `json:"total_odds" example:"4.22"`
|
|
||||||
Status domain.OutcomeStatus `json:"status" example:"1"`
|
|
||||||
FullName string `json:"full_name" example:"John"`
|
|
||||||
PhoneNumber string `json:"phone_number" example:"1234567890"`
|
|
||||||
BranchID int64 `json:"branch_id" example:"2"`
|
|
||||||
UserID int64 `json:"user_id" example:"2"`
|
|
||||||
IsShopBet bool `json:"is_shop_bet" example:"false"`
|
|
||||||
CashedOut bool `json:"cashed_out" example:"false"`
|
|
||||||
CashedID string `json:"cashed_id" example:"21234"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func convertCreateBet(bet domain.Bet, createdNumber int64) CreateBetRes {
|
|
||||||
return CreateBetRes{
|
|
||||||
ID: bet.ID,
|
|
||||||
Amount: bet.Amount.Float32(),
|
|
||||||
TotalOdds: bet.TotalOdds,
|
|
||||||
Status: bet.Status,
|
|
||||||
FullName: bet.FullName,
|
|
||||||
PhoneNumber: bet.PhoneNumber,
|
|
||||||
BranchID: bet.BranchID.Value,
|
|
||||||
UserID: bet.UserID.Value,
|
|
||||||
CreatedNumber: createdNumber,
|
|
||||||
CashedID: bet.CashoutID,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func convertBet(bet domain.GetBet) BetRes {
|
|
||||||
return BetRes{
|
|
||||||
ID: bet.ID,
|
|
||||||
Amount: bet.Amount.Float32(),
|
|
||||||
TotalOdds: bet.TotalOdds,
|
|
||||||
Status: bet.Status,
|
|
||||||
FullName: bet.FullName,
|
|
||||||
PhoneNumber: bet.PhoneNumber,
|
|
||||||
BranchID: bet.BranchID.Value,
|
|
||||||
UserID: bet.UserID.Value,
|
|
||||||
Outcomes: bet.Outcomes,
|
|
||||||
IsShopBet: bet.IsShopBet,
|
|
||||||
CashedOut: bet.CashedOut,
|
|
||||||
CashedID: bet.CashoutID,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateBet godoc
|
// CreateBet godoc
|
||||||
// @Summary Create a bet
|
// @Summary Create a bet
|
||||||
// @Description Creates a bet
|
// @Description Creates a bet
|
||||||
// @Tags bet
|
// @Tags bet
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param createBet body CreateBetReq true "Creates bet"
|
// @Param createBet body domain.CreateBetReq true "Creates bet"
|
||||||
// @Success 200 {object} BetRes
|
// @Success 200 {object} BetRes
|
||||||
// @Failure 400 {object} response.APIResponse
|
// @Failure 400 {object} response.APIResponse
|
||||||
// @Failure 500 {object} response.APIResponse
|
// @Failure 500 {object} response.APIResponse
|
||||||
|
|
@ -102,7 +25,7 @@ func (h *Handler) CreateBet(c *fiber.Ctx) error {
|
||||||
userID := c.Locals("user_id").(int64)
|
userID := c.Locals("user_id").(int64)
|
||||||
role := c.Locals("role").(domain.Role)
|
role := c.Locals("role").(domain.Role)
|
||||||
|
|
||||||
var req CreateBetReq
|
var req domain.CreateBetReq
|
||||||
if err := c.BodyParser(&req); err != nil {
|
if err := c.BodyParser(&req); err != nil {
|
||||||
h.logger.Error("Failed to parse CreateBet request", "error", err)
|
h.logger.Error("Failed to parse CreateBet request", "error", err)
|
||||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
|
||||||
|
|
@ -113,199 +36,52 @@ 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO Validate Outcomes Here and make sure they didn't expire
|
res, err := h.betSvc.PlaceBet(c.Context(), req, userID, role)
|
||||||
// Validation for creating tickets
|
|
||||||
if len(req.Outcomes) > 30 {
|
|
||||||
return response.WriteJSON(c, fiber.StatusBadRequest, "Too many odds/outcomes selected", nil, nil)
|
|
||||||
}
|
|
||||||
var outcomes []domain.CreateBetOutcome = make([]domain.CreateBetOutcome, 0, len(req.Outcomes))
|
|
||||||
var totalOdds float32 = 1
|
|
||||||
for _, outcome := range req.Outcomes {
|
|
||||||
eventIDStr := strconv.FormatInt(outcome.EventID, 10)
|
|
||||||
marketIDStr := strconv.FormatInt(outcome.MarketID, 10)
|
|
||||||
oddIDStr := strconv.FormatInt(outcome.OddID, 10)
|
|
||||||
event, err := h.eventSvc.GetUpcomingEventByID(c.Context(), eventIDStr)
|
|
||||||
if err != nil {
|
|
||||||
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid event id", err, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Checking to make sure the event hasn't already started
|
|
||||||
// currentTime := time.Now()
|
|
||||||
// if event.StartTime.Before(currentTime) {
|
|
||||||
// return response.WriteJSON(c, fiber.StatusBadRequest, "The event has already expired", nil, nil)
|
|
||||||
// }
|
|
||||||
|
|
||||||
odds, err := h.prematchSvc.GetRawOddsByMarketID(c.Context(), marketIDStr, eventIDStr)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid market id", err, nil)
|
|
||||||
}
|
|
||||||
type rawOddType struct {
|
|
||||||
ID string
|
|
||||||
Name string
|
|
||||||
Odds string
|
|
||||||
Header string
|
|
||||||
Handicap string
|
|
||||||
}
|
|
||||||
var selectedOdd rawOddType
|
|
||||||
var isOddFound bool = false
|
|
||||||
for _, raw := range odds.RawOdds {
|
|
||||||
var rawOdd rawOddType
|
|
||||||
rawBytes, err := json.Marshal(raw)
|
|
||||||
err = json.Unmarshal(rawBytes, &rawOdd)
|
|
||||||
if err != nil {
|
|
||||||
h.logger.Error("Failed to unmarshal raw odd", "error", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if rawOdd.ID == oddIDStr {
|
|
||||||
selectedOdd = rawOdd
|
|
||||||
isOddFound = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !isOddFound {
|
|
||||||
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid odd id", nil, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
parsedOdd, err := strconv.ParseFloat(selectedOdd.Odds, 32)
|
|
||||||
totalOdds = totalOdds * float32(parsedOdd)
|
|
||||||
|
|
||||||
sportID, err := strconv.ParseInt(event.SportID, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid sport id", nil, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
h.logger.Info("Create Bet", slog.Int64("sportId", sportID))
|
|
||||||
|
|
||||||
outcomes = append(outcomes, domain.CreateBetOutcome{
|
|
||||||
EventID: outcome.EventID,
|
|
||||||
OddID: outcome.OddID,
|
|
||||||
MarketID: outcome.MarketID,
|
|
||||||
SportID: sportID,
|
|
||||||
HomeTeamName: event.HomeTeam,
|
|
||||||
AwayTeamName: event.AwayTeam,
|
|
||||||
MarketName: odds.MarketName,
|
|
||||||
Odd: float32(parsedOdd),
|
|
||||||
OddName: selectedOdd.Name,
|
|
||||||
OddHeader: selectedOdd.Header,
|
|
||||||
OddHandicap: selectedOdd.Handicap,
|
|
||||||
Expires: event.StartTime,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validating user by role
|
|
||||||
// Differentiating between offline and online bets
|
|
||||||
cashoutID, err := h.betSvc.GenerateCashoutID()
|
|
||||||
if err != nil {
|
|
||||||
h.logger.Error("CreateBetReq failed, unable to create cashout id")
|
|
||||||
return response.WriteJSON(c, fiber.StatusInternalServerError, "Invalid request", err, nil)
|
|
||||||
}
|
|
||||||
var bet domain.Bet
|
|
||||||
if role == domain.RoleCashier {
|
|
||||||
|
|
||||||
// Get the branch from the branch ID
|
|
||||||
branch, err := h.branchSvc.GetBranchByCashier(c.Context(), userID)
|
|
||||||
if err != nil {
|
|
||||||
h.logger.Error("CreateBetReq failed, branch id invalid")
|
|
||||||
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", err, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deduct a percentage of the amount
|
|
||||||
// TODO move to service layer. Make it fetch dynamically from company
|
|
||||||
var deductedAmount = req.Amount / 10
|
|
||||||
err = h.walletSvc.DeductFromWallet(c.Context(), branch.WalletID, domain.ToCurrency(deductedAmount))
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
h.logger.Error("CreateBetReq failed, unable to deduct from WalletID")
|
|
||||||
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", err, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
bet, err = h.betSvc.CreateBet(c.Context(), domain.CreateBet{
|
|
||||||
Amount: domain.ToCurrency(req.Amount),
|
|
||||||
TotalOdds: totalOdds,
|
|
||||||
Status: req.Status,
|
|
||||||
FullName: req.FullName,
|
|
||||||
PhoneNumber: req.PhoneNumber,
|
|
||||||
|
|
||||||
BranchID: domain.ValidInt64{
|
|
||||||
Value: branch.ID,
|
|
||||||
Valid: true,
|
|
||||||
},
|
|
||||||
UserID: domain.ValidInt64{
|
|
||||||
Value: userID,
|
|
||||||
Valid: false,
|
|
||||||
},
|
|
||||||
IsShopBet: true,
|
|
||||||
CashoutID: cashoutID,
|
|
||||||
})
|
|
||||||
} else if role == domain.RoleSuperAdmin || role == domain.RoleAdmin || role == domain.RoleBranchManager {
|
|
||||||
// If a non cashier wants to create a bet, they will need to provide the Branch ID
|
|
||||||
// TODO: restrict the Branch ID of Admin and Branch Manager to only the branches within their own company
|
|
||||||
if req.BranchID == nil {
|
|
||||||
h.logger.Error("CreateBetReq failed, Branch ID is required for this type of user")
|
|
||||||
return response.WriteJSON(c, fiber.StatusBadRequest, "Branch ID is required for this type of user", nil, nil)
|
|
||||||
}
|
|
||||||
// h.logger.Info("Branch ID", slog.Int64("branch_id", *req.BranchID))
|
|
||||||
bet, err = h.betSvc.CreateBet(c.Context(), domain.CreateBet{
|
|
||||||
Amount: domain.ToCurrency(req.Amount),
|
|
||||||
TotalOdds: totalOdds,
|
|
||||||
Status: req.Status,
|
|
||||||
FullName: req.FullName,
|
|
||||||
PhoneNumber: req.PhoneNumber,
|
|
||||||
BranchID: domain.ValidInt64{
|
|
||||||
Value: *req.BranchID,
|
|
||||||
Valid: true,
|
|
||||||
},
|
|
||||||
UserID: domain.ValidInt64{
|
|
||||||
Value: userID,
|
|
||||||
Valid: true,
|
|
||||||
},
|
|
||||||
IsShopBet: true,
|
|
||||||
CashoutID: cashoutID,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
// TODO if user is customer, get id from the token then get the wallet id from there and reduce the amount
|
|
||||||
bet, err = h.betSvc.CreateBet(c.Context(), domain.CreateBet{
|
|
||||||
Amount: domain.ToCurrency(req.Amount),
|
|
||||||
TotalOdds: totalOdds,
|
|
||||||
Status: req.Status,
|
|
||||||
FullName: req.FullName,
|
|
||||||
PhoneNumber: req.PhoneNumber,
|
|
||||||
|
|
||||||
BranchID: domain.ValidInt64{
|
|
||||||
Value: 0,
|
|
||||||
Valid: false,
|
|
||||||
},
|
|
||||||
UserID: domain.ValidInt64{
|
|
||||||
Value: userID,
|
|
||||||
Valid: true,
|
|
||||||
},
|
|
||||||
IsShopBet: false,
|
|
||||||
CashoutID: cashoutID,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logger.Error("CreateBetReq failed", "error", err)
|
h.logger.Error("PlaceBet failed", "error", err)
|
||||||
return response.WriteJSON(c, fiber.StatusInternalServerError, "Internal Server Error", err, nil)
|
return fiber.NewError(fiber.StatusInternalServerError, "Unable to create bet")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Updating the bet id for outcomes
|
return response.WriteJSON(c, fiber.StatusOK, "Bet Created", res, nil)
|
||||||
for index := range outcomes {
|
|
||||||
outcomes[index].BetID = bet.ID
|
}
|
||||||
|
|
||||||
|
// RandomBet godoc
|
||||||
|
// @Summary Generate a random bet
|
||||||
|
// @Description Generate a random bet
|
||||||
|
// @Tags bet
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param createBet body domain.RandomBetReq true "Create Random bet"
|
||||||
|
// @Success 200 {object} BetRes
|
||||||
|
// @Failure 400 {object} response.APIResponse
|
||||||
|
// @Failure 500 {object} response.APIResponse
|
||||||
|
// @Router /random/bet [post]
|
||||||
|
func (h *Handler) RandomBet(c *fiber.Ctx) error {
|
||||||
|
|
||||||
|
// Get user_id from middleware
|
||||||
|
userID := c.Locals("user_id").(int64)
|
||||||
|
// role := c.Locals("role").(domain.Role)
|
||||||
|
|
||||||
|
var req domain.RandomBetReq
|
||||||
|
if err := c.BodyParser(&req); err != nil {
|
||||||
|
h.logger.Error("Failed to parse RandomBet request", "error", err)
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
|
||||||
}
|
}
|
||||||
|
|
||||||
rows, err := h.betSvc.CreateBetOutcome(c.Context(), outcomes)
|
valErrs, ok := h.validator.Validate(c, req)
|
||||||
|
if !ok {
|
||||||
|
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := h.betSvc.PlaceRandomBet(c.Context(), userID, req.BranchID)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logger.Error("CreateBetReq failed to create outcomes", "error", err)
|
h.logger.Error("Random Bet failed", "error", err)
|
||||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
return fiber.NewError(fiber.StatusInternalServerError, "Unable to create random bet")
|
||||||
"error": "Internal server error",
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
res := convertCreateBet(bet, rows)
|
|
||||||
|
|
||||||
return response.WriteJSON(c, fiber.StatusOK, "Bet Created", res, nil)
|
return response.WriteJSON(c, fiber.StatusOK, "Bet Created", res, nil)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -327,9 +103,9 @@ func (h *Handler) GetAllBet(c *fiber.Ctx) error {
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve bets")
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve bets")
|
||||||
}
|
}
|
||||||
|
|
||||||
res := make([]BetRes, len(bets))
|
res := make([]domain.BetRes, len(bets))
|
||||||
for i, bet := range bets {
|
for i, bet := range bets {
|
||||||
res[i] = convertBet(bet)
|
res[i] = domain.ConvertBet(bet)
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.WriteJSON(c, fiber.StatusOK, "All bets retrieved successfully", res, nil)
|
return response.WriteJSON(c, fiber.StatusOK, "All bets retrieved successfully", res, nil)
|
||||||
|
|
@ -360,7 +136,7 @@ func (h *Handler) GetBetByID(c *fiber.Ctx) error {
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve bet")
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve bet")
|
||||||
}
|
}
|
||||||
|
|
||||||
res := convertBet(bet)
|
res := domain.ConvertBet(bet)
|
||||||
|
|
||||||
return response.WriteJSON(c, fiber.StatusOK, "Bet retrieved successfully", res, nil)
|
return response.WriteJSON(c, fiber.StatusOK, "Bet retrieved successfully", res, nil)
|
||||||
|
|
||||||
|
|
@ -392,7 +168,7 @@ func (h *Handler) GetBetByCashoutID(c *fiber.Ctx) error {
|
||||||
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve bet", err, nil)
|
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve bet", err, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
res := convertBet(bet)
|
res := domain.ConvertBet(bet)
|
||||||
|
|
||||||
return response.WriteJSON(c, fiber.StatusOK, "Bet retrieved successfully", res, nil)
|
return response.WriteJSON(c, fiber.StatusOK, "Bet retrieved successfully", res, nil)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -517,9 +517,9 @@ func (h *Handler) GetBetByBranchID(c *fiber.Ctx) error {
|
||||||
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve bets", err, nil)
|
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve bets", err, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
var res []BetRes = make([]BetRes, 0, len(bets))
|
var res []domain.BetRes = make([]domain.BetRes, 0, len(bets))
|
||||||
for _, bet := range bets {
|
for _, bet := range bets {
|
||||||
res = append(res, convertBet(bet))
|
res = append(res, domain.ConvertBet(bet))
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.WriteJSON(c, fiber.StatusOK, "Branch Bets Retrieved", res, nil)
|
return response.WriteJSON(c, fiber.StatusOK, "Branch Bets Retrieved", res, nil)
|
||||||
|
|
|
||||||
|
|
@ -149,7 +149,9 @@ func (a *App) initAppRoutes() {
|
||||||
a.fiber.Get("/bet/cashout/:id", a.authMiddleware, h.GetBetByCashoutID)
|
a.fiber.Get("/bet/cashout/:id", a.authMiddleware, h.GetBetByCashoutID)
|
||||||
a.fiber.Patch("/bet/:id", a.authMiddleware, h.UpdateCashOut)
|
a.fiber.Patch("/bet/:id", a.authMiddleware, h.UpdateCashOut)
|
||||||
a.fiber.Delete("/bet/:id", a.authMiddleware, h.DeleteBet)
|
a.fiber.Delete("/bet/:id", a.authMiddleware, h.DeleteBet)
|
||||||
|
|
||||||
|
a.fiber.Post("/random/bet", a.authMiddleware, h.RandomBet)
|
||||||
|
|
||||||
// Wallet
|
// Wallet
|
||||||
a.fiber.Get("/wallet", h.GetAllWallets)
|
a.fiber.Get("/wallet", h.GetAllWallets)
|
||||||
a.fiber.Get("/wallet/:id", h.GetWalletByID)
|
a.fiber.Get("/wallet/:id", h.GetWalletByID)
|
||||||
|
|
@ -176,6 +178,7 @@ func (a *App) initAppRoutes() {
|
||||||
// Virtual Game Routes
|
// Virtual Game Routes
|
||||||
a.fiber.Post("/virtual-game/launch", a.authMiddleware, h.LaunchVirtualGame)
|
a.fiber.Post("/virtual-game/launch", a.authMiddleware, h.LaunchVirtualGame)
|
||||||
a.fiber.Post("/virtual-game/callback", h.HandleVirtualGameCallback)
|
a.fiber.Post("/virtual-game/callback", h.HandleVirtualGameCallback)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
///user/profile get
|
///user/profile get
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user