merger + auth fix

This commit is contained in:
Yared Yemane 2025-06-18 11:22:02 +03:00
commit 22ec5d3ff8
27 changed files with 404 additions and 153 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

@ -1,18 +1,18 @@
-- CREATE TABLE virtual_games ( CREATE TABLE IF NOT EXISTS virtual_games (
-- id BIGSERIAL PRIMARY KEY, id BIGSERIAL PRIMARY KEY,
-- name VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL,
-- provider VARCHAR(100) NOT NULL, provider VARCHAR(100) NOT NULL,
-- category VARCHAR(100) NOT NULL, category VARCHAR(100) NOT NULL,
-- min_bet DECIMAL(15,2) NOT NULL, min_bet DECIMAL(15,2) NOT NULL,
-- max_bet DECIMAL(15,2) NOT NULL, max_bet DECIMAL(15,2) NOT NULL,
-- volatility VARCHAR(50) NOT NULL, volatility VARCHAR(50) NOT NULL,
-- rtp DECIMAL(5,2) NOT NULL, rtp DECIMAL(5,2) NOT NULL,
-- is_featured BOOLEAN DEFAULT false, is_featured BOOLEAN DEFAULT false,
-- popularity_score INTEGER DEFAULT 0, popularity_score INTEGER DEFAULT 0,
-- thumbnail_url TEXT, thumbnail_url TEXT,
-- created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
-- updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
-- ); );
CREATE TABLE user_game_interactions ( CREATE TABLE user_game_interactions (
id BIGSERIAL PRIMARY KEY, id BIGSERIAL PRIMARY KEY,

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

@ -61,7 +61,8 @@ SET name = COALESCE(sqlc.narg(name), name),
location = COALESCE(sqlc.narg(location), location), location = COALESCE(sqlc.narg(location), location),
branch_manager_id = COALESCE(sqlc.narg(branch_manager_id), branch_manager_id), branch_manager_id = COALESCE(sqlc.narg(branch_manager_id), branch_manager_id),
company_id = COALESCE(sqlc.narg(company_id), company_id), company_id = COALESCE(sqlc.narg(company_id), company_id),
is_self_owned = COALESCE(sqlc.narg(is_self_owned), is_self_owned) is_self_owned = COALESCE(sqlc.narg(is_self_owned), is_self_owned),
is_active = COALESCE(sqlc.narg(is_active), is_active)
WHERE id = $1 WHERE id = $1
RETURNING *; RETURNING *;
-- name: DeleteBranch :exec -- name: DeleteBranch :exec

View File

@ -22,10 +22,11 @@ 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 {
@ -39,6 +40,7 @@ type CreateBetParams struct {
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

@ -443,7 +443,8 @@ SET name = COALESCE($2, name),
location = COALESCE($3, location), location = COALESCE($3, location),
branch_manager_id = COALESCE($4, branch_manager_id), branch_manager_id = COALESCE($4, branch_manager_id),
company_id = COALESCE($5, company_id), company_id = COALESCE($5, company_id),
is_self_owned = COALESCE($6, is_self_owned) is_self_owned = COALESCE($6, is_self_owned),
is_active = COALESCE($7, is_active)
WHERE id = $1 WHERE id = $1
RETURNING id, name, location, is_active, wallet_id, branch_manager_id, company_id, is_self_owned, created_at, updated_at RETURNING id, name, location, is_active, wallet_id, branch_manager_id, company_id, is_self_owned, created_at, updated_at
` `
@ -455,6 +456,7 @@ type UpdateBranchParams struct {
BranchManagerID pgtype.Int8 `json:"branch_manager_id"` BranchManagerID pgtype.Int8 `json:"branch_manager_id"`
CompanyID pgtype.Int8 `json:"company_id"` CompanyID pgtype.Int8 `json:"company_id"`
IsSelfOwned pgtype.Bool `json:"is_self_owned"` IsSelfOwned pgtype.Bool `json:"is_self_owned"`
IsActive pgtype.Bool `json:"is_active"`
} }
func (q *Queries) UpdateBranch(ctx context.Context, arg UpdateBranchParams) (Branch, error) { func (q *Queries) UpdateBranch(ctx context.Context, arg UpdateBranchParams) (Branch, error) {
@ -465,6 +467,7 @@ func (q *Queries) UpdateBranch(ctx context.Context, arg UpdateBranchParams) (Bra
arg.BranchManagerID, arg.BranchManagerID,
arg.CompanyID, arg.CompanyID,
arg.IsSelfOwned, arg.IsSelfOwned,
arg.IsActive,
) )
var i Branch var i Branch
err := row.Scan( err := row.Scan(

View File

@ -70,6 +70,7 @@ type Bet struct {
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 {
@ -105,6 +106,7 @@ type BetWithOutcome struct {
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"`
Outcomes []BetOutcome `json:"outcomes"` Outcomes []BetOutcome `json:"outcomes"`
} }
@ -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 {

8
go.mod
View File

@ -78,6 +78,12 @@ require (
) )
require ( require (
github.com/go-resty/resty/v2 v2.16.5 // indirect github.com/go-resty/resty/v2 v2.16.5
github.com/twilio/twilio-go v1.26.3
)
require (
github.com/golang/mock v1.6.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
go.uber.org/atomic v1.9.0 // indirect go.uber.org/atomic v1.9.0 // indirect
) )

17
go.sum
View File

@ -9,6 +9,7 @@ github.com/amanuelabay/afrosms-go v1.0.6/go.mod h1:5mzzZtWSCDdvQsA0OyYf5CtbdGpl9
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A=
github.com/bytedance/sonic v1.13.2 h1:8/H1FempDZqC4VqjptGo14QQlJx8VdZJegxs6wwfqpQ= github.com/bytedance/sonic v1.13.2 h1:8/H1FempDZqC4VqjptGo14QQlJx8VdZJegxs6wwfqpQ=
github.com/bytedance/sonic v1.13.2/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4= github.com/bytedance/sonic v1.13.2/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4=
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
@ -56,6 +57,8 @@ github.com/gofiber/fiber/v2 v2.52.6 h1:Rfp+ILPiYSvvVuIPvxrBns+HJp8qGLDnLJawAu27X
github.com/gofiber/fiber/v2 v2.52.6/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw= github.com/gofiber/fiber/v2 v2.52.6/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw=
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8= github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
@ -96,6 +99,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/localtunnel/go-localtunnel v0.0.0-20170326223115-8a804488f275 h1:IZycmTpoUtQK3PD60UYBwjaCUHUP7cML494ao9/O8+Q=
github.com/localtunnel/go-localtunnel v0.0.0-20170326223115-8a804488f275/go.mod h1:zt6UU74K6Z6oMOYJbJzYpYucqdcQwSMPBEdSvGiaUMw=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
@ -116,6 +121,8 @@ github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6
github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo=
github.com/otiai10/mint v1.3.3/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= github.com/otiai10/mint v1.3.3/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/resend/resend-go/v2 v2.20.0 h1:MrIrgV0aHhwRgmcRPw33Nexn6aGJvCvG2XwfFpAMBGM= github.com/resend/resend-go/v2 v2.20.0 h1:MrIrgV0aHhwRgmcRPw33Nexn6aGJvCvG2XwfFpAMBGM=
@ -152,6 +159,8 @@ github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe/go.mod h1:lKJPbtWzJ9J
github.com/swaggo/swag v1.8.1/go.mod h1:ugemnJsPZm/kRwFUnzBlbHRd0JY9zE1M4F+uy2pAaPQ= github.com/swaggo/swag v1.8.1/go.mod h1:ugemnJsPZm/kRwFUnzBlbHRd0JY9zE1M4F+uy2pAaPQ=
github.com/swaggo/swag v1.16.4 h1:clWJtd9LStiG3VeijiCfOVODP6VpHtKdQy9ELFG3s1A= github.com/swaggo/swag v1.16.4 h1:clWJtd9LStiG3VeijiCfOVODP6VpHtKdQy9ELFG3s1A=
github.com/swaggo/swag v1.16.4/go.mod h1:VBsHJRsDvfYvqoiMKnsdwhNV9LEMHgEDZcyVYX0sxPg= github.com/swaggo/swag v1.16.4/go.mod h1:VBsHJRsDvfYvqoiMKnsdwhNV9LEMHgEDZcyVYX0sxPg=
github.com/twilio/twilio-go v1.26.3 h1:K2mYBzbhPVyWF+Jq5Sw53edBFvkgWo4sKTvgaO7461I=
github.com/twilio/twilio-go v1.26.3/go.mod h1:FpgNWMoD8CFnmukpKq9RNpUSGXC0BwnbeKZj2YHlIkw=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
@ -172,13 +181,13 @@ github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZ
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM= github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM=
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI= github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.mongodb.org/mongo-driver v1.17.3 h1:TQyXhnsWfWtgAhMtOgtYHMTkZIfBTpMTsMnd9ZBeHxQ= go.mongodb.org/mongo-driver v1.17.3 h1:TQyXhnsWfWtgAhMtOgtYHMTkZIfBTpMTsMnd9ZBeHxQ=
go.mongodb.org/mongo-driver v1.17.3/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ= go.mongodb.org/mongo-driver v1.17.3/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ=
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
@ -201,6 +210,7 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM= golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
@ -216,8 +226,10 @@ golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@ -236,9 +248,12 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U=
golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU= golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU=

View File

@ -32,6 +32,9 @@ var (
ErrInvalidVeliSecretKey = errors.New("Veli secret key is invalid") ErrInvalidVeliSecretKey = errors.New("Veli secret key is invalid")
ErrMissingResendApiKey = errors.New("missing Resend Api key") ErrMissingResendApiKey = errors.New("missing Resend Api key")
ErrMissingResendSenderEmail = errors.New("missing Resend sender name") ErrMissingResendSenderEmail = errors.New("missing Resend sender name")
ErrMissingTwilioAccountSid = errors.New("missing twilio account sid")
ErrMissingTwilioAuthToken = errors.New("missing twilio auth token")
ErrMissingTwilioSenderPhoneNumber = errors.New("missing twilio sender phone number")
) )
type AleaPlayConfig struct { type AleaPlayConfig struct {
@ -85,6 +88,9 @@ type Config struct {
VeliGames VeliConfig `mapstructure:"veli_games"` VeliGames VeliConfig `mapstructure:"veli_games"`
ResendApiKey string ResendApiKey string
ResendSenderEmail string ResendSenderEmail string
TwilioAccountSid string
TwilioAuthToken string
TwilioSenderPhoneNumber string
} }
func NewConfig() (*Config, error) { func NewConfig() (*Config, error) {
@ -324,6 +330,24 @@ func (c *Config) loadEnv() error {
} }
c.ResendSenderEmail = resendSenderEmail c.ResendSenderEmail = resendSenderEmail
twilioAccountSid := os.Getenv("TWILIO_ACCOUNT_SID")
if twilioAccountSid == "" {
return ErrMissingTwilioAccountSid
}
c.TwilioAccountSid = twilioAccountSid
twilioAuthToken := os.Getenv("TWILIO_AUTH_TOKEN")
if twilioAuthToken == "" {
return ErrMissingTwilioAuthToken
}
c.TwilioAuthToken = twilioAuthToken
twilioSenderPhoneNumber := os.Getenv("TWILIO_SENDER_PHONE_NUMBER")
if twilioSenderPhoneNumber == "" {
return ErrMissingTwilioSenderPhoneNumber
}
c.TwilioSenderPhoneNumber = twilioSenderPhoneNumber
return nil return nil
} }

View File

@ -90,6 +90,7 @@ type CreateBet struct {
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

@ -53,6 +53,7 @@ type UpdateBranch struct {
BranchManagerID *int64 BranchManagerID *int64
CompanyID *int64 CompanyID *int64
IsSelfOwned *bool IsSelfOwned *bool
IsActive *bool
} }
type CreateSupportedOperation struct { type CreateSupportedOperation struct {

View File

@ -26,6 +26,13 @@ const (
OtpMediumSms OtpMedium = "sms" OtpMediumSms OtpMedium = "sms"
) )
type OtpProvider string
const (
TwilioSms OtpProvider = "twilio"
AfroMessage OtpProvider = "aformessage"
)
type Otp struct { type Otp struct {
ID int64 ID int64
SentTo string SentTo string

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

@ -83,6 +83,12 @@ func convertUpdateBranch(updateBranch domain.UpdateBranch) dbgen.UpdateBranchPar
Valid: true, Valid: true,
} }
} }
if updateBranch.IsActive != nil {
newUpdateBranch.IsActive = pgtype.Bool{
Bool: *updateBranch.IsActive,
Valid: true,
}
}
return newUpdateBranch return newUpdateBranch
} }

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",
@ -227,6 +249,7 @@ func (s *Service) PlaceBet(ctx context.Context, req domain.CreateBetReq, userID
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

@ -10,19 +10,30 @@ import (
"github.com/SamuelTariku/FortuneBet-Backend/internal/pkgs/helpers" "github.com/SamuelTariku/FortuneBet-Backend/internal/pkgs/helpers"
afro "github.com/amanuelabay/afrosms-go" afro "github.com/amanuelabay/afrosms-go"
"github.com/resend/resend-go/v2" "github.com/resend/resend-go/v2"
"github.com/twilio/twilio-go"
twilioApi "github.com/twilio/twilio-go/rest/api/v2010"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
) )
func (s *Service) SendOtp(ctx context.Context, sentTo string, otpFor domain.OtpFor, medium domain.OtpMedium) error { func (s *Service) SendOtp(ctx context.Context, sentTo string, otpFor domain.OtpFor, medium domain.OtpMedium, provider domain.OtpProvider) error {
otpCode := helpers.GenerateOTP() otpCode := helpers.GenerateOTP()
message := fmt.Sprintf("Welcome to Fortune bets, your OTP is %s please don't share with anyone.", otpCode) message := fmt.Sprintf("Welcome to Fortune bets, your OTP is %s please don't share with anyone.", otpCode)
switch medium { switch medium {
case domain.OtpMediumSms: case domain.OtpMediumSms:
if err := s.SendSMSOTP(ctx, sentTo, message); err != nil { switch provider {
case "twilio":
if err := s.SendTwilioSMSOTP(ctx, sentTo, message, provider); err != nil {
return err return err
} }
case "afromessage":
if err := s.SendAfroMessageSMSOTP(ctx, sentTo, message, provider); err != nil {
return err
}
default:
return fmt.Errorf("invalid sms provider: %s", provider)
}
case domain.OtpMediumEmail: case domain.OtpMediumEmail:
if err := s.SendEmailOTP(ctx, sentTo, message); err != nil { if err := s.SendEmailOTP(ctx, sentTo, message); err != nil {
return err return err
@ -51,7 +62,7 @@ func hashPassword(plaintextPassword string) ([]byte, error) {
return hash, nil return hash, nil
} }
func (s *Service) SendSMSOTP(ctx context.Context, receiverPhone, message string) error { func (s *Service) SendAfroMessageSMSOTP(ctx context.Context, receiverPhone, message string, provider domain.OtpProvider) error {
apiKey := s.config.AFRO_SMS_API_KEY apiKey := s.config.AFRO_SMS_API_KEY
senderName := s.config.AFRO_SMS_SENDER_NAME senderName := s.config.AFRO_SMS_SENDER_NAME
hostURL := s.config.ADRO_SMS_HOST_URL hostURL := s.config.ADRO_SMS_HOST_URL
@ -79,6 +90,29 @@ func (s *Service) SendSMSOTP(ctx context.Context, receiverPhone, message string)
} }
} }
func (s *Service) SendTwilioSMSOTP(ctx context.Context, receiverPhone, message string, provider domain.OtpProvider) error {
accountSid := s.config.TwilioAccountSid
authToken := s.config.TwilioAuthToken
senderPhone := s.config.TwilioSenderPhoneNumber
client := twilio.NewRestClientWithParams(twilio.ClientParams{
Username: accountSid,
Password: authToken,
})
params := &twilioApi.CreateMessageParams{}
params.SetTo(receiverPhone)
params.SetFrom(senderPhone)
params.SetBody(message)
_, err := client.Api.CreateMessage(params)
if err != nil {
return fmt.Errorf("Error sending SMS message: %s" + err.Error())
}
return nil
}
func (s *Service) SendEmailOTP(ctx context.Context, receiverEmail, message string) error { func (s *Service) SendEmailOTP(ctx context.Context, receiverEmail, message string) error {
apiKey := s.config.ResendApiKey apiKey := s.config.ResendApiKey
client := resend.NewClient(apiKey) client := resend.NewClient(apiKey)

View File

@ -10,7 +10,7 @@ import (
func (s *Service) CheckPhoneEmailExist(ctx context.Context, phoneNum, email string) (bool, bool, error) { // email,phone,error func (s *Service) CheckPhoneEmailExist(ctx context.Context, phoneNum, email string) (bool, bool, error) { // email,phone,error
return s.userStore.CheckPhoneEmailExist(ctx, phoneNum, email) return s.userStore.CheckPhoneEmailExist(ctx, phoneNum, email)
} }
func (s *Service) SendRegisterCode(ctx context.Context, medium domain.OtpMedium, sentTo string) error { func (s *Service) SendRegisterCode(ctx context.Context, medium domain.OtpMedium, sentTo string, provider domain.OtpProvider) error {
var err error var err error
// check if user exists // check if user exists
switch medium { switch medium {
@ -26,7 +26,7 @@ func (s *Service) SendRegisterCode(ctx context.Context, medium domain.OtpMedium,
} }
// send otp based on the medium // send otp based on the medium
return s.SendOtp(ctx, sentTo, domain.OtpRegister, medium) return s.SendOtp(ctx, sentTo, domain.OtpRegister, medium, provider)
} }
func (s *Service) RegisterUser(ctx context.Context, registerReq domain.RegisterUserReq) (domain.User, error) { // normal func (s *Service) RegisterUser(ctx context.Context, registerReq domain.RegisterUserReq) (domain.User, error) { // normal
// get otp // get otp

View File

@ -8,7 +8,7 @@ import (
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
) )
func (s *Service) SendResetCode(ctx context.Context, medium domain.OtpMedium, sentTo string) error { func (s *Service) SendResetCode(ctx context.Context, medium domain.OtpMedium, sentTo string, provider domain.OtpProvider) error {
var err error var err error
// check if user exists // check if user exists
@ -23,7 +23,7 @@ func (s *Service) SendResetCode(ctx context.Context, medium domain.OtpMedium, se
return err return err
} }
return s.SendOtp(ctx, sentTo, domain.OtpReset, medium) return s.SendOtp(ctx, sentTo, domain.OtpReset, medium, provider)
} }

View File

@ -46,22 +46,22 @@ func StartDataFetchingCrons(eventService eventsvc.Service, oddsService oddssvc.S
spec string spec string
task func() task func()
}{ }{
// { {
// spec: "0 0 * * * *", // Every 1 hour spec: "0 0 * * * *", // Every 1 hour
// task: func() { task: func() {
// if err := eventService.FetchUpcomingEvents(context.Background()); err != nil { if err := eventService.FetchUpcomingEvents(context.Background()); err != nil {
// log.Printf("FetchUpcomingEvents error: %v", err) log.Printf("FetchUpcomingEvents error: %v", err)
// } }
// }, },
// }, },
// { {
// spec: "0 0 * * * *", // Every 15 minutes spec: "0 0 * * * *", // Every 15 minutes
// task: func() { task: func() {
// if err := oddsService.FetchNonLiveOdds(context.Background()); err != nil { if err := oddsService.FetchNonLiveOdds(context.Background()); err != nil {
// log.Printf("FetchNonLiveOdds error: %v", err) log.Printf("FetchNonLiveOdds error: %v", err)
// } }
// }, },
// }, },
{ {
spec: "0 */5 * * * *", // Every 5 Minutes spec: "0 */5 * * * *", // Every 5 Minutes
task: func() { task: func() {
@ -89,7 +89,7 @@ func StartDataFetchingCrons(eventService eventsvc.Service, oddsService oddssvc.S
} }
for _, job := range schedule { for _, job := range schedule {
job.task() // job.task()
if _, err := c.AddFunc(job.spec, job.task); err != nil { if _, err := c.AddFunc(job.spec, job.task); err != nil {
log.Fatalf("Failed to schedule cron job: %v", err) log.Fatalf("Failed to schedule cron job: %v", err)
} }

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)

View File

@ -2,6 +2,7 @@ package handlers
import ( import (
"strconv" "strconv"
"strings"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/authentication" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/authentication"
@ -682,6 +683,42 @@ func (h *Handler) UpdateBranch(c *fiber.Ctx) error {
} }
func (h *Handler) UpdateBranchStatus(c *fiber.Ctx) error {
branchID := c.Params("id")
id, err := strconv.ParseInt(branchID, 10, 64)
if err != nil {
h.logger.Error("Invalid branch ID", "branchID", branchID, "error", err)
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid branch ID", err, nil)
}
var isActive bool
path := strings.Split(strings.Trim(c.Path(), "/"), "/")
if path[len(path)-1] == "set-active" {
isActive = true
} else if path[len(path)-1] == "set-inactive" {
isActive = false
} else {
h.logger.Error("Invalid branch status", "status", isActive, "error", err)
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid branch status", err, nil)
}
branch, err := h.branchSvc.UpdateBranch(c.Context(), domain.UpdateBranch{
ID: id,
IsActive: &isActive,
})
if err != nil {
h.logger.Error("Failed to update branch", "branchID", id, "error", err)
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to update branch", err, nil)
}
res := convertBranch(branch)
return response.WriteJSON(c, fiber.StatusOK, "Branch Updated", res, nil)
}
// DeleteBranch godoc // DeleteBranch godoc
// @Summary Delete the branch // @Summary Delete the branch
// @Description Delete the branch // @Description Delete the branch

View File

@ -12,7 +12,7 @@ import (
func GetLogsHandler(appCtx context.Context) fiber.Handler { func GetLogsHandler(appCtx context.Context) fiber.Handler {
return func(c *fiber.Ctx) error { return func(c *fiber.Ctx) error {
client, err := mongo.Connect(appCtx, options.Client().ApplyURI("mongodb://root:secret@mongo:27017/?authSource=admin")) client, err := mongo.Connect(appCtx, options.Client().ApplyURI("mongodb://root:secret@localhost:27017/?authSource=admin"))
if err != nil { if err != nil {
return fiber.NewError(fiber.StatusInternalServerError, "MongoDB connection failed: "+err.Error()) return fiber.NewError(fiber.StatusInternalServerError, "MongoDB connection failed: "+err.Error())
} }
@ -32,7 +32,6 @@ func GetLogsHandler(appCtx context.Context) fiber.Handler {
return fiber.NewError(fiber.StatusInternalServerError, "Cursor decoding error: "+err.Error()) return fiber.NewError(fiber.StatusInternalServerError, "Cursor decoding error: "+err.Error())
} }
return c.JSON(logs) return c.JSON(logs)
} }
} }

View File

@ -61,6 +61,7 @@ func (h *Handler) CheckPhoneEmailExist(c *fiber.Ctx) error {
type RegisterCodeReq struct { type RegisterCodeReq struct {
Email string `json:"email" example:"john.doe@example.com"` Email string `json:"email" example:"john.doe@example.com"`
PhoneNumber string `json:"phone_number" example:"1234567890"` PhoneNumber string `json:"phone_number" example:"1234567890"`
Provider domain.OtpProvider `json:"provider" validate:"required" example:"twilio"`
} }
// SendRegisterCode godoc // SendRegisterCode godoc
@ -98,7 +99,7 @@ func (h *Handler) SendRegisterCode(c *fiber.Ctx) error {
return fiber.NewError(fiber.StatusBadRequest, "Email or PhoneNumber must be provided") return fiber.NewError(fiber.StatusBadRequest, "Email or PhoneNumber must be provided")
} }
if err := h.userSvc.SendRegisterCode(c.Context(), medium, sentTo); err != nil { if err := h.userSvc.SendRegisterCode(c.Context(), medium, sentTo, req.Provider); err != nil {
h.logger.Error("Failed to send register code", "error", err) h.logger.Error("Failed to send register code", "error", err)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to send register code") return fiber.NewError(fiber.StatusInternalServerError, "Failed to send register code")
} }
@ -114,6 +115,7 @@ type RegisterUserReq struct {
Password string `json:"password" example:"password123"` Password string `json:"password" example:"password123"`
Otp string `json:"otp" example:"123456"` Otp string `json:"otp" example:"123456"`
ReferalCode string `json:"referal_code" example:"ABC123"` ReferalCode string `json:"referal_code" example:"ABC123"`
Provider domain.OtpProvider `json:"provider" validate:"required" example:"twilio"`
} }
// RegisterUser godoc // RegisterUser godoc
@ -205,6 +207,7 @@ func (h *Handler) RegisterUser(c *fiber.Ctx) error {
type ResetCodeReq struct { type ResetCodeReq struct {
Email string `json:"email" example:"john.doe@example.com"` Email string `json:"email" example:"john.doe@example.com"`
PhoneNumber string `json:"phone_number" validate:"required_without=Email" example:"1234567890"` PhoneNumber string `json:"phone_number" validate:"required_without=Email" example:"1234567890"`
Provider domain.OtpProvider `json:"provider" validate:"required" example:"twilio"`
} }
// SendResetCode godoc // SendResetCode godoc
@ -242,7 +245,7 @@ func (h *Handler) SendResetCode(c *fiber.Ctx) error {
return fiber.NewError(fiber.StatusBadRequest, "Email or PhoneNumber must be provided") return fiber.NewError(fiber.StatusBadRequest, "Email or PhoneNumber must be provided")
} }
if err := h.userSvc.SendResetCode(c.Context(), medium, sentTo); err != nil { if err := h.userSvc.SendResetCode(c.Context(), medium, sentTo, req.Provider); err != nil {
h.logger.Error("Failed to send reset code", "error", err) h.logger.Error("Failed to send reset code", "error", err)
fmt.Println(err) fmt.Println(err)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to send reset code") return fiber.NewError(fiber.StatusInternalServerError, "Failed to send reset code")

View File

@ -52,7 +52,7 @@ func (a *App) initAppRoutes() {
a.fiber.Get("/", func(c *fiber.Ctx) error { a.fiber.Get("/", func(c *fiber.Ctx) error {
return c.JSON(fiber.Map{ return c.JSON(fiber.Map{
"message": "Welcome to the FortuneBet API", "message": "Welcome to the FortuneBet API",
"version": "1.0dev3", "version": "1.0dev6",
}) })
}) })
@ -146,6 +146,8 @@ func (a *App) initAppRoutes() {
a.fiber.Get("/branch/:id", a.authMiddleware, h.GetBranchByID) a.fiber.Get("/branch/:id", a.authMiddleware, h.GetBranchByID)
a.fiber.Get("/branch/:id/bets", a.authMiddleware, h.GetBetByBranchID) a.fiber.Get("/branch/:id/bets", a.authMiddleware, h.GetBetByBranchID)
a.fiber.Put("/branch/:id", a.authMiddleware, h.UpdateBranch) a.fiber.Put("/branch/:id", a.authMiddleware, h.UpdateBranch)
a.fiber.Put("/branch/:id/set-active", a.authMiddleware, h.UpdateBranchStatus)
a.fiber.Put("/branch/:id/set-inactive", a.authMiddleware, h.UpdateBranchStatus)
a.fiber.Delete("/branch/:id", a.authMiddleware, h.DeleteBranch) a.fiber.Delete("/branch/:id", a.authMiddleware, h.DeleteBranch)
a.fiber.Get("/search/branch", a.authMiddleware, h.SearchBranch) a.fiber.Get("/search/branch", a.authMiddleware, h.SearchBranch)
// /branch/search // /branch/search