diff --git a/.gitignore b/.gitignore index 548e7cb..32a2000 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ bin coverage.out -# coverage -# .env -# tmp -# build +coverage +.env +tmp +build *.log \ No newline at end of file diff --git a/app.log b/app.log deleted file mode 100644 index e69de29..0000000 diff --git a/bin/web b/bin/web deleted file mode 100644 index 150a02e..0000000 Binary files a/bin/web and /dev/null differ diff --git a/db/migrations/000001_fortune.up.sql b/db/migrations/000001_fortune.up.sql index 34bae36..6f44178 100644 --- a/db/migrations/000001_fortune.up.sql +++ b/db/migrations/000001_fortune.up.sql @@ -55,6 +55,7 @@ CREATE TABLE IF NOT EXISTS bets ( created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, is_shop_bet BOOLEAN NOT NULL, + outcomes_hash TEXT NOT NULL, UNIQUE(cashout_id), CHECK ( user_id IS NOT NULL @@ -308,7 +309,7 @@ ALTER TABLE bets ADD CONSTRAINT fk_bets_users FOREIGN KEY (user_id) REFERENCES users(id), ADD CONSTRAINT fk_bets_branches FOREIGN KEY (branch_id) REFERENCES branches(id); 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'; ALTER TABLE customer_wallets ADD CONSTRAINT fk_customer_wallets_customers FOREIGN KEY (customer_id) REFERENCES users(id), diff --git a/db/migrations/000006_recommendation.up.sql b/db/migrations/000006_recommendation.up.sql index 0e83986..e0900fa 100644 --- a/db/migrations/000006_recommendation.up.sql +++ b/db/migrations/000006_recommendation.up.sql @@ -1,18 +1,18 @@ --- CREATE TABLE virtual_games ( --- id BIGSERIAL PRIMARY KEY, --- name VARCHAR(255) NOT NULL, --- provider VARCHAR(100) NOT NULL, --- category VARCHAR(100) NOT NULL, --- min_bet DECIMAL(15,2) NOT NULL, --- max_bet DECIMAL(15,2) NOT NULL, --- volatility VARCHAR(50) NOT NULL, --- rtp DECIMAL(5,2) NOT NULL, --- is_featured BOOLEAN DEFAULT false, --- popularity_score INTEGER DEFAULT 0, --- thumbnail_url TEXT, --- created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, --- updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP --- ); +CREATE TABLE IF NOT EXISTS virtual_games ( + id BIGSERIAL PRIMARY KEY, + name VARCHAR(255) NOT NULL, + provider VARCHAR(100) NOT NULL, + category VARCHAR(100) NOT NULL, + min_bet DECIMAL(15,2) NOT NULL, + max_bet DECIMAL(15,2) NOT NULL, + volatility VARCHAR(50) NOT NULL, + rtp DECIMAL(5,2) NOT NULL, + is_featured BOOLEAN DEFAULT false, + popularity_score INTEGER DEFAULT 0, + thumbnail_url TEXT, + created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP +); CREATE TABLE user_game_interactions ( id BIGSERIAL PRIMARY KEY, diff --git a/db/query/bet.sql b/db/query/bet.sql index 8686f6b..8e9fda8 100644 --- a/db/query/bet.sql +++ b/db/query/bet.sql @@ -9,9 +9,10 @@ INSERT INTO bets ( user_id, is_shop_bet, 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 *; -- name: CreateBetOutcome :copyfrom INSERT INTO bet_outcomes ( @@ -87,6 +88,11 @@ WHERE event_id = $1; SELECT * FROM bet_outcomes WHERE bet_id = $1; +-- name: GetBetCount :one +SELECT COUNT(*) +FROM bets +where user_id = $1 + AND outcomes_hash = $2; -- name: UpdateCashOut :exec UPDATE bets SET cashed_out = $2, diff --git a/db/query/branch.sql b/db/query/branch.sql index eef1ae1..34070cc 100644 --- a/db/query/branch.sql +++ b/db/query/branch.sql @@ -69,7 +69,8 @@ SET name = COALESCE(sqlc.narg(name), name), location = COALESCE(sqlc.narg(location), location), branch_manager_id = COALESCE(sqlc.narg(branch_manager_id), branch_manager_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 RETURNING *; -- name: DeleteBranch :exec diff --git a/gen/db/bet.sql.go b/gen/db/bet.sql.go index 1852a08..bf6c3de 100644 --- a/gen/db/bet.sql.go +++ b/gen/db/bet.sql.go @@ -22,23 +22,25 @@ INSERT INTO bets ( user_id, is_shop_bet, cashout_id, - company_id + company_id, + outcomes_hash ) -VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) -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 +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, outcomes_hash ` type CreateBetParams struct { - Amount int64 `json:"amount"` - TotalOdds float32 `json:"total_odds"` - Status int32 `json:"status"` - FullName string `json:"full_name"` - PhoneNumber string `json:"phone_number"` - BranchID pgtype.Int8 `json:"branch_id"` - UserID pgtype.Int8 `json:"user_id"` - IsShopBet bool `json:"is_shop_bet"` - CashoutID string `json:"cashout_id"` - CompanyID pgtype.Int8 `json:"company_id"` + Amount int64 `json:"amount"` + TotalOdds float32 `json:"total_odds"` + Status int32 `json:"status"` + FullName string `json:"full_name"` + PhoneNumber string `json:"phone_number"` + BranchID pgtype.Int8 `json:"branch_id"` + UserID pgtype.Int8 `json:"user_id"` + IsShopBet bool `json:"is_shop_bet"` + CashoutID string `json:"cashout_id"` + CompanyID pgtype.Int8 `json:"company_id"` + OutcomesHash string `json:"outcomes_hash"` } 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.CashoutID, arg.CompanyID, + arg.OutcomesHash, ) var i Bet err := row.Scan( @@ -70,6 +73,7 @@ func (q *Queries) CreateBet(ctx context.Context, arg CreateBetParams) (Bet, erro &i.CreatedAt, &i.UpdatedAt, &i.IsShopBet, + &i.OutcomesHash, ) return i, err } @@ -111,7 +115,7 @@ func (q *Queries) DeleteBetOutcome(ctx context.Context, betID int64) error { } 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 wHERE ( branch_id = $1 @@ -167,6 +171,7 @@ func (q *Queries) GetAllBets(ctx context.Context, arg GetAllBetsParams) ([]BetWi &i.CreatedAt, &i.UpdatedAt, &i.IsShopBet, + &i.OutcomesHash, &i.Outcomes, ); err != nil { return nil, err @@ -180,7 +185,7 @@ func (q *Queries) GetAllBets(ctx context.Context, arg GetAllBetsParams) ([]BetWi } 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 WHERE branch_id = $1 ` @@ -209,6 +214,7 @@ func (q *Queries) GetBetByBranchID(ctx context.Context, branchID pgtype.Int8) ([ &i.CreatedAt, &i.UpdatedAt, &i.IsShopBet, + &i.OutcomesHash, &i.Outcomes, ); err != nil { return nil, err @@ -222,7 +228,7 @@ func (q *Queries) GetBetByBranchID(ctx context.Context, branchID pgtype.Int8) ([ } 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 WHERE cashout_id = $1 ` @@ -245,13 +251,14 @@ func (q *Queries) GetBetByCashoutID(ctx context.Context, cashoutID string) (BetW &i.CreatedAt, &i.UpdatedAt, &i.IsShopBet, + &i.OutcomesHash, &i.Outcomes, ) return i, err } 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 WHERE id = $1 ` @@ -274,13 +281,14 @@ func (q *Queries) GetBetByID(ctx context.Context, id int64) (BetWithOutcome, err &i.CreatedAt, &i.UpdatedAt, &i.IsShopBet, + &i.OutcomesHash, &i.Outcomes, ) return i, err } 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 WHERE user_id = $1 ` @@ -309,6 +317,7 @@ func (q *Queries) GetBetByUserID(ctx context.Context, userID pgtype.Int8) ([]Bet &i.CreatedAt, &i.UpdatedAt, &i.IsShopBet, + &i.OutcomesHash, &i.Outcomes, ); err != nil { return nil, err @@ -321,6 +330,25 @@ func (q *Queries) GetBetByUserID(ctx context.Context, userID pgtype.Int8) ([]Bet 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 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 diff --git a/gen/db/branch.sql.go b/gen/db/branch.sql.go index d762fa8..e343ce4 100644 --- a/gen/db/branch.sql.go +++ b/gen/db/branch.sql.go @@ -456,7 +456,8 @@ SET name = COALESCE($2, name), location = COALESCE($3, location), branch_manager_id = COALESCE($4, branch_manager_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 RETURNING id, name, location, is_active, wallet_id, branch_manager_id, company_id, is_self_owned, created_at, updated_at ` @@ -468,6 +469,7 @@ type UpdateBranchParams struct { BranchManagerID pgtype.Int8 `json:"branch_manager_id"` CompanyID pgtype.Int8 `json:"company_id"` IsSelfOwned pgtype.Bool `json:"is_self_owned"` + IsActive pgtype.Bool `json:"is_active"` } func (q *Queries) UpdateBranch(ctx context.Context, arg UpdateBranchParams) (Branch, error) { @@ -478,6 +480,7 @@ func (q *Queries) UpdateBranch(ctx context.Context, arg UpdateBranchParams) (Bra arg.BranchManagerID, arg.CompanyID, arg.IsSelfOwned, + arg.IsActive, ) var i Branch err := row.Scan( diff --git a/gen/db/models.go b/gen/db/models.go index fce563e..f86a6e4 100644 --- a/gen/db/models.go +++ b/gen/db/models.go @@ -56,20 +56,21 @@ func (ns NullReferralstatus) Value() (driver.Value, error) { } type Bet struct { - ID int64 `json:"id"` - Amount int64 `json:"amount"` - TotalOdds float32 `json:"total_odds"` - Status int32 `json:"status"` - FullName string `json:"full_name"` - PhoneNumber string `json:"phone_number"` - CompanyID pgtype.Int8 `json:"company_id"` - BranchID pgtype.Int8 `json:"branch_id"` - UserID pgtype.Int8 `json:"user_id"` - CashedOut bool `json:"cashed_out"` - CashoutID string `json:"cashout_id"` - CreatedAt pgtype.Timestamp `json:"created_at"` - UpdatedAt pgtype.Timestamp `json:"updated_at"` - IsShopBet bool `json:"is_shop_bet"` + ID int64 `json:"id"` + Amount int64 `json:"amount"` + TotalOdds float32 `json:"total_odds"` + Status int32 `json:"status"` + FullName string `json:"full_name"` + PhoneNumber string `json:"phone_number"` + CompanyID pgtype.Int8 `json:"company_id"` + BranchID pgtype.Int8 `json:"branch_id"` + UserID pgtype.Int8 `json:"user_id"` + CashedOut bool `json:"cashed_out"` + CashoutID string `json:"cashout_id"` + CreatedAt pgtype.Timestamp `json:"created_at"` + UpdatedAt pgtype.Timestamp `json:"updated_at"` + IsShopBet bool `json:"is_shop_bet"` + OutcomesHash string `json:"outcomes_hash"` } type BetOutcome struct { @@ -91,21 +92,22 @@ type BetOutcome struct { } type BetWithOutcome struct { - ID int64 `json:"id"` - Amount int64 `json:"amount"` - TotalOdds float32 `json:"total_odds"` - Status int32 `json:"status"` - FullName string `json:"full_name"` - PhoneNumber string `json:"phone_number"` - CompanyID pgtype.Int8 `json:"company_id"` - BranchID pgtype.Int8 `json:"branch_id"` - UserID pgtype.Int8 `json:"user_id"` - CashedOut bool `json:"cashed_out"` - CashoutID string `json:"cashout_id"` - CreatedAt pgtype.Timestamp `json:"created_at"` - UpdatedAt pgtype.Timestamp `json:"updated_at"` - IsShopBet bool `json:"is_shop_bet"` - Outcomes []BetOutcome `json:"outcomes"` + ID int64 `json:"id"` + Amount int64 `json:"amount"` + TotalOdds float32 `json:"total_odds"` + Status int32 `json:"status"` + FullName string `json:"full_name"` + PhoneNumber string `json:"phone_number"` + CompanyID pgtype.Int8 `json:"company_id"` + BranchID pgtype.Int8 `json:"branch_id"` + UserID pgtype.Int8 `json:"user_id"` + CashedOut bool `json:"cashed_out"` + CashoutID string `json:"cashout_id"` + CreatedAt pgtype.Timestamp `json:"created_at"` + UpdatedAt pgtype.Timestamp `json:"updated_at"` + IsShopBet bool `json:"is_shop_bet"` + OutcomesHash string `json:"outcomes_hash"` + Outcomes []BetOutcome `json:"outcomes"` } type Branch struct { diff --git a/go.mod b/go.mod index cfc550d..5372953 100644 --- a/go.mod +++ b/go.mod @@ -77,4 +77,9 @@ require ( go.uber.org/multierr v1.10.0 // indirect ) -require go.uber.org/atomic v1.9.0 // indirect +require ( + github.com/golang/mock v1.6.0 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/twilio/twilio-go v1.26.3 // indirect + go.uber.org/atomic v1.9.0 // indirect +) diff --git a/go.sum b/go.sum index 8420e2a..6faf62c 100644 --- a/go.sum +++ b/go.sum @@ -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.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= 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/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4= github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= @@ -54,6 +55,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/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/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/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= @@ -94,6 +97,7 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 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/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +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-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= @@ -114,6 +118,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.3/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= 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/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/resend/resend-go/v2 v2.20.0 h1:MrIrgV0aHhwRgmcRPw33Nexn6aGJvCvG2XwfFpAMBGM= @@ -150,6 +156,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.16.4 h1:clWJtd9LStiG3VeijiCfOVODP6VpHtKdQy9ELFG3s1A= 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/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= @@ -170,6 +178,7 @@ github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZ 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/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.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.mongodb.org/mongo-driver v1.17.3 h1:TQyXhnsWfWtgAhMtOgtYHMTkZIfBTpMTsMnd9ZBeHxQ= @@ -199,6 +208,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-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-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-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= @@ -214,8 +224,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-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-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-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-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -237,6 +249,7 @@ golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= 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-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.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU= diff --git a/internal/config/config.go b/internal/config/config.go index 802302e..362a38f 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -13,25 +13,28 @@ import ( ) var ( - ErrInvalidDbUrl = errors.New("db url is invalid") - ErrInvalidPort = errors.New("port number is invalid") - ErrRefreshExpiry = errors.New("refresh token expiry is invalid") - ErrAccessExpiry = errors.New("access token expiry is invalid") - ErrInvalidJwtKey = errors.New("jwt key is invalid") - ErrLogLevel = errors.New("log level not set") - ErrInvalidLevel = errors.New("invalid log level") - ErrInvalidEnv = errors.New("env not set or invalid") - ErrInvalidSMSAPIKey = errors.New("SMS API key is invalid") - ErrMissingBetToken = errors.New("missing BET365_TOKEN in .env") - ErrInvalidPopOKClientID = errors.New("PopOK client ID is invalid") - ErrInvalidPopOKSecretKey = errors.New("PopOK secret key is invalid") - ErrInvalidPopOKBaseURL = errors.New("PopOK base URL is invalid") - ErrInvalidPopOKCallbackURL = errors.New("PopOK callback URL is invalid") - ErrInvalidVeliAPIURL = errors.New("Veli API URL is invalid") - ErrInvalidVeliOperatorKey = errors.New("Veli operator key is invalid") - ErrInvalidVeliSecretKey = errors.New("Veli secret key is invalid") - ErrMissingResendApiKey = errors.New("missing Resend Api key") - ErrMissingResendSenderEmail = errors.New("missing Resend sender name") + ErrInvalidDbUrl = errors.New("db url is invalid") + ErrInvalidPort = errors.New("port number is invalid") + ErrRefreshExpiry = errors.New("refresh token expiry is invalid") + ErrAccessExpiry = errors.New("access token expiry is invalid") + ErrInvalidJwtKey = errors.New("jwt key is invalid") + ErrLogLevel = errors.New("log level not set") + ErrInvalidLevel = errors.New("invalid log level") + ErrInvalidEnv = errors.New("env not set or invalid") + ErrInvalidSMSAPIKey = errors.New("SMS API key is invalid") + ErrMissingBetToken = errors.New("missing BET365_TOKEN in .env") + ErrInvalidPopOKClientID = errors.New("PopOK client ID is invalid") + ErrInvalidPopOKSecretKey = errors.New("PopOK secret key is invalid") + ErrInvalidPopOKBaseURL = errors.New("PopOK base URL is invalid") + ErrInvalidPopOKCallbackURL = errors.New("PopOK callback URL is invalid") + ErrInvalidVeliAPIURL = errors.New("Veli API URL is invalid") + ErrInvalidVeliOperatorKey = errors.New("Veli operator key is invalid") + ErrInvalidVeliSecretKey = errors.New("Veli secret key is invalid") + ErrMissingResendApiKey = errors.New("missing Resend Api key") + 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 { @@ -85,6 +88,9 @@ type Config struct { VeliGames VeliGamesConfig `mapstructure:"veli_games"` ResendApiKey string ResendSenderEmail string + TwilioAccountSid string + TwilioAuthToken string + TwilioSenderPhoneNumber string } func NewConfig() (*Config, error) { @@ -324,6 +330,24 @@ func (c *Config) loadEnv() error { } 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 } diff --git a/internal/domain/bet.go b/internal/domain/bet.go index 832686b..ce740ce 100644 --- a/internal/domain/bet.go +++ b/internal/domain/bet.go @@ -81,16 +81,17 @@ type GetBet struct { } type CreateBet struct { - Amount Currency - TotalOdds float32 - Status OutcomeStatus - FullName string - PhoneNumber string - CompanyID ValidInt64 // Can Be Nullable - BranchID ValidInt64 // Can Be Nullable - UserID ValidInt64 // Can Be Nullable - IsShopBet bool - CashoutID string + Amount Currency + TotalOdds float32 + Status OutcomeStatus + FullName string + PhoneNumber string + CompanyID ValidInt64 // Can Be Nullable + BranchID ValidInt64 // Can Be Nullable + UserID ValidInt64 // Can Be Nullable + IsShopBet bool + CashoutID string + OutcomesHash string } type CreateBetOutcomeReq struct { diff --git a/internal/domain/branch.go b/internal/domain/branch.go index d27eb08..e6e26fa 100644 --- a/internal/domain/branch.go +++ b/internal/domain/branch.go @@ -58,6 +58,7 @@ type UpdateBranch struct { BranchManagerID *int64 CompanyID *int64 IsSelfOwned *bool + IsActive *bool } type CreateSupportedOperation struct { diff --git a/internal/domain/otp.go b/internal/domain/otp.go index a6904e4..23c8640 100644 --- a/internal/domain/otp.go +++ b/internal/domain/otp.go @@ -26,6 +26,13 @@ const ( OtpMediumSms OtpMedium = "sms" ) +type OtpProvider string + +const ( + TwilioSms OtpProvider = "twilio" + AfroMessage OtpProvider = "aformessage" +) + type Otp struct { ID int64 SentTo string diff --git a/internal/repository/bet.go b/internal/repository/bet.go index 14dc385..847b212 100644 --- a/internal/repository/bet.go +++ b/internal/repository/bet.go @@ -269,6 +269,19 @@ func (s *Store) GetBetByUserID(ctx context.Context, UserID int64) ([]domain.GetB 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 { err := s.queries.UpdateCashOut(ctx, dbgen.UpdateCashOutParams{ ID: id, diff --git a/internal/repository/branch.go b/internal/repository/branch.go index f504a0f..a02370c 100644 --- a/internal/repository/branch.go +++ b/internal/repository/branch.go @@ -83,6 +83,12 @@ func convertUpdateBranch(updateBranch domain.UpdateBranch) dbgen.UpdateBranchPar Valid: true, } } + if updateBranch.IsActive != nil { + newUpdateBranch.IsActive = pgtype.Bool{ + Bool: *updateBranch.IsActive, + Valid: true, + } + } return newUpdateBranch } diff --git a/internal/services/bet/port.go b/internal/services/bet/port.go index a249e43..753ec3c 100644 --- a/internal/services/bet/port.go +++ b/internal/services/bet/port.go @@ -17,6 +17,7 @@ type BetStore interface { GetBetByUserID(ctx context.Context, UserID int64) ([]domain.GetBet, error) GetBetOutcomeByEventID(ctx context.Context, eventID 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 UpdateStatus(ctx context.Context, id int64, status domain.OutcomeStatus) error UpdateBetOutcomeStatus(ctx context.Context, id int64, status domain.OutcomeStatus) (domain.BetOutcome, error) diff --git a/internal/services/bet/service.go b/internal/services/bet/service.go index ae8de24..0d311eb 100644 --- a/internal/services/bet/service.go +++ b/internal/services/bet/service.go @@ -3,13 +3,17 @@ package bet import ( "context" "crypto/rand" + "crypto/sha256" + "encoding/hex" "encoding/json" "errors" "fmt" "log/slog" "math/big" random "math/rand" + "sort" "strconv" + "strings" "time" "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 for _, outcomeReq := range req.Outcomes { + fmt.Println("reqq: ", outcomeReq) newOutcome, err := s.GenerateBetOutcome(ctx, outcomeReq.EventID, outcomeReq.MarketID, outcomeReq.OddID) if err != nil { 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) } + 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() if err != nil { s.mongoLogger.Error("failed to generate cashout ID", @@ -221,12 +243,13 @@ func (s *Service) PlaceBet(ctx context.Context, req domain.CreateBetReq, userID } newBet := domain.CreateBet{ - Amount: domain.ToCurrency(req.Amount), - TotalOdds: totalOdds, - Status: req.Status, - FullName: req.FullName, - PhoneNumber: req.PhoneNumber, - CashoutID: cashoutID, + Amount: domain.ToCurrency(req.Amount), + TotalOdds: totalOdds, + Status: req.Status, + FullName: req.FullName, + PhoneNumber: req.PhoneNumber, + CashoutID: cashoutID, + OutcomesHash: outcomesHash, } switch role { @@ -329,6 +352,7 @@ func (s *Service) PlaceBet(ctx context.Context, req domain.CreateBetReq, userID return domain.CreateBetRes{}, fmt.Errorf("Unknown Role Type") } + fmt.Println("Bet is: ", newBet) bet, err := s.CreateBet(ctx, newBet) if err != nil { s.mongoLogger.Error("failed to create bet", @@ -644,6 +668,10 @@ func (s *Service) GetBetByUserID(ctx context.Context, UserID int64) ([]domain.Ge 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 { return s.betStore.UpdateCashOut(ctx, id, cashedOut) } @@ -794,3 +822,24 @@ func (s *Service) UpdateBetOutcomeStatus(ctx context.Context, id int64, status d func (s *Service) DeleteBet(ctx context.Context, id int64) error { 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 +} diff --git a/internal/services/user/common.go b/internal/services/user/common.go index fd4f9aa..6cab064 100644 --- a/internal/services/user/common.go +++ b/internal/services/user/common.go @@ -10,18 +10,29 @@ import ( "github.com/SamuelTariku/FortuneBet-Backend/internal/pkgs/helpers" afro "github.com/amanuelabay/afrosms-go" "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" ) -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() message := fmt.Sprintf("Welcome to Fortune bets, your OTP is %s please don't share with anyone.", otpCode) switch medium { case domain.OtpMediumSms: - if err := s.SendSMSOTP(ctx, sentTo, message); err != nil { - return err + switch provider { + case "twilio": + if err := s.SendTwilioSMSOTP(ctx, sentTo, message, provider); err != nil { + 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: if err := s.SendEmailOTP(ctx, sentTo, message); err != nil { @@ -51,7 +62,7 @@ func hashPassword(plaintextPassword string) ([]byte, error) { 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 senderName := s.config.AFRO_SMS_SENDER_NAME 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 { apiKey := s.config.ResendApiKey client := resend.NewClient(apiKey) diff --git a/internal/services/user/register.go b/internal/services/user/register.go index 3169b7b..c7e0d83 100644 --- a/internal/services/user/register.go +++ b/internal/services/user/register.go @@ -10,7 +10,7 @@ import ( func (s *Service) CheckPhoneEmailExist(ctx context.Context, phoneNum, email string) (bool, bool, error) { // email,phone,error 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 // check if user exists switch medium { @@ -26,7 +26,7 @@ func (s *Service) SendRegisterCode(ctx context.Context, medium domain.OtpMedium, } // 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 // get otp diff --git a/internal/services/user/reset.go b/internal/services/user/reset.go index c6d3f47..7c4e5d5 100644 --- a/internal/services/user/reset.go +++ b/internal/services/user/reset.go @@ -8,7 +8,7 @@ import ( "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 // check if user exists @@ -23,7 +23,7 @@ func (s *Service) SendResetCode(ctx context.Context, medium domain.OtpMedium, se return err } - return s.SendOtp(ctx, sentTo, domain.OtpReset, medium) + return s.SendOtp(ctx, sentTo, domain.OtpReset, medium, provider) } @@ -57,7 +57,7 @@ func (s *Service) ResetPassword(ctx context.Context, resetReq domain.ResetPasswo return err } // reset pass and mark otp as used - + err = s.userStore.UpdatePassword(ctx, sentTo, hashedPassword, otp.ID) if err != nil { return err diff --git a/internal/web_server/cron.go b/internal/web_server/cron.go index 1bce7d2..749f1e0 100644 --- a/internal/web_server/cron.go +++ b/internal/web_server/cron.go @@ -46,26 +46,26 @@ func StartDataFetchingCrons(eventService eventsvc.Service, oddsService oddssvc.S spec string task func() }{ - // { - // spec: "0 0 * * * *", // Every 1 hour - // task: func() { - // if err := eventService.FetchUpcomingEvents(context.Background()); err != nil { - // log.Printf("FetchUpcomingEvents error: %v", err) - // } - // }, - // }, - // { - // spec: "0 0 * * * *", // Every 15 minutes - // task: func() { - // if err := oddsService.FetchNonLiveOdds(context.Background()); err != nil { - // log.Printf("FetchNonLiveOdds error: %v", err) - // } - // }, - // }, - // { - // spec: "0 */5 * * * *", // Every 5 Minutes - // task: func() { - // log.Println("Updating expired events status...") + { + spec: "0 0 * * * *", // Every 1 hour + task: func() { + if err := eventService.FetchUpcomingEvents(context.Background()); err != nil { + log.Printf("FetchUpcomingEvents error: %v", err) + } + }, + }, + { + spec: "0 0 * * * *", // Every 15 minutes + task: func() { + if err := oddsService.FetchNonLiveOdds(context.Background()); err != nil { + log.Printf("FetchNonLiveOdds error: %v", err) + } + }, + }, + { + spec: "0 */5 * * * *", // Every 5 Minutes + task: func() { + log.Println("Updating expired events status...") // if _, err := resultService.CheckAndUpdateExpiredEvents(context.Background()); err != nil { // log.Printf("Failed to update events: %v", err) @@ -89,7 +89,7 @@ func StartDataFetchingCrons(eventService eventsvc.Service, oddsService oddssvc.S } for _, job := range schedule { - job.task() + // job.task() if _, err := c.AddFunc(job.spec, job.task); err != nil { log.Fatalf("Failed to schedule cron job: %v", err) } diff --git a/internal/web_server/handlers/bet_handler.go b/internal/web_server/handlers/bet_handler.go index 7410044..1925280 100644 --- a/internal/web_server/handlers/bet_handler.go +++ b/internal/web_server/handlers/bet_handler.go @@ -38,8 +38,7 @@ func (h *Handler) CreateBet(c *fiber.Ctx) error { return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) } - res, err := h.betSvc. - PlaceBet(c.Context(), req, userID, role) + res, err := h.betSvc.PlaceBet(c.Context(), req, userID, role) if err != nil { h.logger.Error("PlaceBet failed", "error", err) diff --git a/internal/web_server/handlers/branch_handler.go b/internal/web_server/handlers/branch_handler.go index 290f040..59fe9bf 100644 --- a/internal/web_server/handlers/branch_handler.go +++ b/internal/web_server/handlers/branch_handler.go @@ -2,6 +2,7 @@ package handlers import ( "strconv" + "strings" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/authentication" @@ -697,6 +698,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 // @Summary Delete the branch // @Description Delete the branch diff --git a/internal/web_server/handlers/user.go b/internal/web_server/handlers/user.go index 57fed45..aa72107 100644 --- a/internal/web_server/handlers/user.go +++ b/internal/web_server/handlers/user.go @@ -59,8 +59,9 @@ func (h *Handler) CheckPhoneEmailExist(c *fiber.Ctx) error { } type RegisterCodeReq struct { - Email string `json:"email" example:"john.doe@example.com"` - PhoneNumber string `json:"phone_number" example:"1234567890"` + Email string `json:"email" example:"john.doe@example.com"` + PhoneNumber string `json:"phone_number" example:"1234567890"` + Provider domain.OtpProvider `json:"provider" validate:"required" example:"twilio"` } // 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") } - 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) return fiber.NewError(fiber.StatusInternalServerError, "Failed to send register code") } @@ -107,13 +108,14 @@ func (h *Handler) SendRegisterCode(c *fiber.Ctx) error { } type RegisterUserReq struct { - FirstName string `json:"first_name" example:"John"` - LastName string `json:"last_name" example:"Doe"` - Email string `json:"email" example:"john.doe@example.com"` - PhoneNumber string `json:"phone_number" example:"1234567890"` - Password string `json:"password" example:"password123"` - Otp string `json:"otp" example:"123456"` - ReferalCode string `json:"referal_code" example:"ABC123"` + FirstName string `json:"first_name" example:"John"` + LastName string `json:"last_name" example:"Doe"` + Email string `json:"email" example:"john.doe@example.com"` + PhoneNumber string `json:"phone_number" example:"1234567890"` + Password string `json:"password" example:"password123"` + Otp string `json:"otp" example:"123456"` + ReferalCode string `json:"referal_code" example:"ABC123"` + Provider domain.OtpProvider `json:"provider" validate:"required" example:"twilio"` } // RegisterUser godoc @@ -204,8 +206,9 @@ func (h *Handler) RegisterUser(c *fiber.Ctx) error { } type ResetCodeReq struct { - Email string `json:"email" example:"john.doe@example.com"` - PhoneNumber string `json:"phone_number" validate:"required_without=Email" example:"1234567890"` + Email string `json:"email" example:"john.doe@example.com"` + PhoneNumber string `json:"phone_number" validate:"required_without=Email" example:"1234567890"` + Provider domain.OtpProvider `json:"provider" validate:"required" example:"twilio"` } // SendResetCode godoc @@ -243,7 +246,7 @@ func (h *Handler) SendResetCode(c *fiber.Ctx) error { 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) fmt.Println(err) return fiber.NewError(fiber.StatusInternalServerError, "Failed to send reset code") diff --git a/internal/web_server/routes.go b/internal/web_server/routes.go index fc96133..e68d31b 100644 --- a/internal/web_server/routes.go +++ b/internal/web_server/routes.go @@ -52,7 +52,7 @@ func (a *App) initAppRoutes() { a.fiber.Get("/", func(c *fiber.Ctx) error { return c.JSON(fiber.Map{ "message": "Welcome to the FortuneBet API", - "version": "1.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/bets", a.authMiddleware, h.GetBetByBranchID) 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.Get("/search/branch", a.authMiddleware, h.SearchBranch) // /branch/search diff --git a/logs/app.log b/logs/app.log deleted file mode 100644 index 3d7d1a1..0000000 --- a/logs/app.log +++ /dev/null @@ -1,3 +0,0 @@ -time=2025-06-16T02:21:34.859+03:00 level=INFO msg="Authenticated WebSocket connection" service_info.env=development userID=3 -time=2025-06-16T02:23:59.721+03:00 level=INFO msg="Starting server" service_info.env=development port=8080 -time=2025-06-16T22:51:15.242+03:00 level=INFO msg="Starting server" service_info.env=development port=8080 diff --git a/logs/failed_markets.log b/logs/failed_markets.log deleted file mode 100644 index e69de29..0000000 diff --git a/tmp/build-errors.log b/tmp/build-errors.log deleted file mode 100644 index 949b4b8..0000000 --- a/tmp/build-errors.log +++ /dev/null @@ -1 +0,0 @@ -exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1 \ No newline at end of file diff --git a/tmp/main b/tmp/main deleted file mode 100644 index f50d767..0000000 Binary files a/tmp/main and /dev/null differ