resolved conflict

This commit is contained in:
Asher Samuel 2025-07-15 15:47:07 +03:00
commit f3e6e8c165
11 changed files with 277 additions and 14 deletions

View File

@ -79,4 +79,6 @@ DROP TABLE IF EXISTS events;
DROP TABLE IF EXISTS leagues;
DROP TABLE IF EXISTS teams;
DROP TABLE IF EXISTS settings;
DROP TABLE IF EXISTS bonus;
DROP TABLE IF EXISTS flags;
-- DELETE FROM wallet_transfer;

View File

@ -208,7 +208,7 @@ CREATE TABLE IF NOT EXISTS branches (
id BIGSERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
location TEXT NOT NULL,
profit_percent REAL NOt NULL,
profit_percent REAL NOT NULL,
is_active BOOLEAN NOT NULL DEFAULT false,
wallet_id BIGINT NOT NULL,
branch_manager_id BIGINT NOT NULL,
@ -328,6 +328,21 @@ CREATE TABLE bonus (
multiplier REAL NOT NULL,
balance_cap BIGINT NOT NULL DEFAULT 0
);
CREATE TABLE flags (
id BIGSERIAL PRIMARY KEY,
bet_id BIGINT REFERENCES bets(id) ON DELETE CASCADE,
odd_id BIGINT REFERENCES odds(id),
reason TEXT,
flagged_at TIMESTAMP DEFAULT NOW(),
resolved BOOLEAN DEFAULT FALSE,
-- either bet or odd is flagged (not at the same time)
CHECK (
(bet_id IS NOT NULL AND odd_id IS NULL)
OR
(bet_id IS NULL AND odd_id IS NOT NULL)
)
);
-- Views
CREATE VIEW companies_details AS
SELECT companies.*,

View File

@ -101,11 +101,19 @@ WHERE (event_id = $1)
SELECT *
FROM bet_outcomes
WHERE bet_id = $1;
-- name: GetBetCount :one
-- name: GetBetOutcomeCountByOddID :one
SELECT COUNT(*)
FROM bet_outcomes
WHERE odd_id = $1;
-- name: GetBetCountByUserID :one
SELECT COUNT(*)
FROM bets
WHERE user_id = $1
AND outcomes_hash = $2;
-- name: GetBetCountByOutcomesHash :one
SELECT COUNT(*)
FROM bets
WHERE outcomes_hash = $1;
-- name: UpdateCashOut :exec
UPDATE bets
SET cashed_out = $2,

8
db/query/flags.sql Normal file
View File

@ -0,0 +1,8 @@
-- name: CreateFlag :one
INSERT INTO flags (
bet_id,
odd_id,
reason
) VALUES (
$1, $2, $3
) RETURNING *;

View File

@ -282,20 +282,33 @@ func (q *Queries) GetBetByUserID(ctx context.Context, userID int64) ([]BetWithOu
return items, nil
}
const GetBetCount = `-- name: GetBetCount :one
const GetBetCountByOutcomesHash = `-- name: GetBetCountByOutcomesHash :one
SELECT COUNT(*)
FROM bets
WHERE outcomes_hash = $1
`
func (q *Queries) GetBetCountByOutcomesHash(ctx context.Context, outcomesHash string) (int64, error) {
row := q.db.QueryRow(ctx, GetBetCountByOutcomesHash, outcomesHash)
var count int64
err := row.Scan(&count)
return count, err
}
const GetBetCountByUserID = `-- name: GetBetCountByUserID :one
SELECT COUNT(*)
FROM bets
WHERE user_id = $1
AND outcomes_hash = $2
`
type GetBetCountParams struct {
type GetBetCountByUserIDParams struct {
UserID int64 `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)
func (q *Queries) GetBetCountByUserID(ctx context.Context, arg GetBetCountByUserIDParams) (int64, error) {
row := q.db.QueryRow(ctx, GetBetCountByUserID, arg.UserID, arg.OutcomesHash)
var count int64
err := row.Scan(&count)
return count, err
@ -397,6 +410,19 @@ func (q *Queries) GetBetOutcomeByEventID(ctx context.Context, arg GetBetOutcomeB
return items, nil
}
const GetBetOutcomeCountByOddID = `-- name: GetBetOutcomeCountByOddID :one
SELECT COUNT(*)
FROM bet_outcomes
WHERE odd_id = $1
`
func (q *Queries) GetBetOutcomeCountByOddID(ctx context.Context, oddID int64) (int64, error) {
row := q.db.QueryRow(ctx, GetBetOutcomeCountByOddID, oddID)
var count int64
err := row.Scan(&count)
return count, err
}
const GetBetsForCashback = `-- name: GetBetsForCashback :many
SELECT id, amount, total_odds, status, user_id, is_shop_bet, cashed_out, outcomes_hash, fast_code, processed, created_at, updated_at, full_name, phone_number, outcomes
FROM bet_with_outcomes

42
gen/db/flags.sql.go Normal file
View File

@ -0,0 +1,42 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.29.0
// source: flags.sql
package dbgen
import (
"context"
"github.com/jackc/pgx/v5/pgtype"
)
const CreateFlag = `-- name: CreateFlag :one
INSERT INTO flags (
bet_id,
odd_id,
reason
) VALUES (
$1, $2, $3
) RETURNING id, bet_id, odd_id, reason, flagged_at, resolved
`
type CreateFlagParams struct {
BetID pgtype.Int8 `json:"bet_id"`
OddID pgtype.Int8 `json:"odd_id"`
Reason pgtype.Text `json:"reason"`
}
func (q *Queries) CreateFlag(ctx context.Context, arg CreateFlagParams) (Flag, error) {
row := q.db.QueryRow(ctx, CreateFlag, arg.BetID, arg.OddID, arg.Reason)
var i Flag
err := row.Scan(
&i.ID,
&i.BetID,
&i.OddID,
&i.Reason,
&i.FlaggedAt,
&i.Resolved,
)
return i, err
}

View File

@ -277,6 +277,15 @@ type FavoriteGame struct {
CreatedAt pgtype.Timestamp `json:"created_at"`
}
type Flag struct {
ID int64 `json:"id"`
BetID pgtype.Int8 `json:"bet_id"`
OddID pgtype.Int8 `json:"odd_id"`
Reason pgtype.Text `json:"reason"`
FlaggedAt pgtype.Timestamp `json:"flagged_at"`
Resolved pgtype.Bool `json:"resolved"`
}
type League struct {
ID int64 `json:"id"`
Name string `json:"name"`

View File

@ -61,6 +61,15 @@ type BetFilter struct {
CreatedAfter ValidTime
}
type Flag struct {
ID int64
BetID int64
OddID int64
Reason string
FlaggedAt time.Time
Resolved bool
}
type GetBet struct {
ID int64
Amount Currency
@ -95,7 +104,7 @@ type CreateBetOutcomeReq struct {
type CreateBetReq struct {
Outcomes []CreateBetOutcomeReq `json:"outcomes" validate:"required"`
Amount float32 `json:"amount" validate:"required,gt=0" example:"100.0"`
BranchID *int64 `json:"branch_id,omitempty" example:"1"`
BranchID *int64 `json:"branch_id,omitempty" validate:"required" example:"1"`
}
type CreateBetWithFastCodeReq struct {
@ -104,6 +113,12 @@ type CreateBetWithFastCodeReq struct {
BranchID *int64 `json:"branch_id"`
}
type CreateFlagReq struct {
BetID int64
OddID int64
Reason string
}
type RandomBetReq struct {
BranchID int64 `json:"branch_id" validate:"required" example:"1"`
NumberOfBets int64 `json:"number_of_bets" validate:"required" example:"1"`

View File

@ -76,6 +76,17 @@ func convertDBBetWithOutcomes(bet dbgen.BetWithOutcome) domain.GetBet {
}
}
func convertDBFlag(flag dbgen.Flag) domain.Flag {
return domain.Flag{
ID: flag.ID,
BetID: flag.BetID.Int64,
OddID: flag.OddID.Int64,
Reason: flag.Reason.String,
FlaggedAt: flag.FlaggedAt.Time,
Resolved: flag.Resolved.Bool,
}
}
func convertDBCreateBetOutcome(betOutcome domain.CreateBetOutcome) dbgen.CreateBetOutcomeParams {
return dbgen.CreateBetOutcomeParams{
BetID: betOutcome.BetID,
@ -140,6 +151,35 @@ func (s *Store) CreateBetOutcome(ctx context.Context, outcomes []domain.CreateBe
return rows, nil
}
func (s *Store) CreateFlag(ctx context.Context, flag domain.CreateFlagReq) (domain.Flag, error) {
createFlag := dbgen.CreateFlagParams{
BetID: pgtype.Int8{
Int64: flag.BetID,
Valid: flag.BetID != 0,
},
OddID: pgtype.Int8{
Int64: flag.OddID,
Valid: flag.OddID != 0,
},
Reason: pgtype.Text{
String: flag.Reason,
Valid: true,
},
}
f, err := s.queries.CreateFlag(ctx, createFlag)
if err != nil {
domain.MongoDBLogger.Error("failed to create flag",
zap.String("flag", f.Reason.String),
zap.Any("flag_id", f.ID),
zap.Error(err),
)
return domain.Flag{}, err
}
return convertDBFlag(f), nil
}
func (s *Store) GetBetByID(ctx context.Context, id int64) (domain.GetBet, error) {
bet, err := s.queries.GetBetByID(ctx, id)
if err != nil {
@ -237,8 +277,8 @@ func (s *Store) GetBetsForCashback(ctx context.Context) ([]domain.GetBet, error)
return res, nil
}
func (s *Store) GetBetCount(ctx context.Context, UserID int64, outcomesHash string) (int64, error) {
count, err := s.queries.GetBetCount(ctx, dbgen.GetBetCountParams{
func (s *Store) GetBetCountByUserID(ctx context.Context, UserID int64, outcomesHash string) (int64, error) {
count, err := s.queries.GetBetCountByUserID(ctx, dbgen.GetBetCountByUserIDParams{
UserID: UserID,
OutcomesHash: outcomesHash,
})
@ -250,6 +290,24 @@ func (s *Store) GetBetCount(ctx context.Context, UserID int64, outcomesHash stri
return count, nil
}
func (s *Store) GetBetCountByOutcomesHash(ctx context.Context, outcomesHash string) (int64, error) {
count, err := s.queries.GetBetCountByOutcomesHash(ctx, outcomesHash)
if err != nil {
return 0, err
}
return count, nil
}
func (s *Store) GetBetOutcomeCountByOddID(ctx context.Context, oddID int64) (int64, error) {
count, err := s.queries.GetBetOutcomeCountByOddID(ctx, oddID)
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,

View File

@ -10,13 +10,16 @@ import (
type BetStore interface {
CreateBet(ctx context.Context, bet domain.CreateBet) (domain.Bet, error)
CreateBetOutcome(ctx context.Context, outcomes []domain.CreateBetOutcome) (int64, error)
CreateFlag(ctx context.Context, flag domain.CreateFlagReq) (domain.Flag, error)
GetBetByID(ctx context.Context, id int64) (domain.GetBet, error)
GetAllBets(ctx context.Context, filter domain.BetFilter) ([]domain.GetBet, error)
GetBetByUserID(ctx context.Context, UserID int64) ([]domain.GetBet, error)
GetBetByFastCode(ctx context.Context, fastcode string) (domain.GetBet, error)
GetBetOutcomeByEventID(ctx context.Context, eventID int64, is_filtered bool) ([]domain.BetOutcome, error)
GetBetOutcomeByBetID(ctx context.Context, betID int64) ([]domain.BetOutcome, error)
GetBetCount(ctx context.Context, userID int64, outcomesHash string) (int64, error)
GetBetOutcomeCountByOddID(ctx context.Context, oddID int64) (int64, error)
GetBetCountByUserID(ctx context.Context, userID int64, outcomesHash string) (int64, error)
GetBetCountByOutcomesHash(ctx context.Context, 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)

View File

@ -269,7 +269,7 @@ func (s *Service) PlaceBet(ctx context.Context, req domain.CreateBetReq, userID
return domain.CreateBetRes{}, err
}
count, err := s.GetBetCount(ctx, userID, outcomesHash)
count, err := s.GetBetCountByUserID(ctx, userID, outcomesHash)
if err != nil {
s.mongoLogger.Error("failed to generate cashout ID",
zap.Int64("user_id", userID),
@ -398,6 +398,79 @@ func (s *Service) PlaceBet(ctx context.Context, req domain.CreateBetReq, userID
return domain.CreateBetRes{}, err
}
for i := range outcomes {
// flag odds with large amount of users betting on them
count, err := s.betStore.GetBetOutcomeCountByOddID(ctx, outcomes[i].OddID)
if err != nil {
s.mongoLogger.Error("failed to get count of bet outcome",
zap.Int64("bet_id", bet.ID),
zap.Int64("odd_id", outcomes[i].OddID),
zap.Error(err),
)
return domain.CreateBetRes{}, err
}
// TODO: fetch cap from settings in db
if count > 20 {
flag := domain.CreateFlagReq{
BetID: 0,
OddID: outcomes[i].OddID,
Reason: fmt.Sprintf("too many users targeting odd - (%d)", outcomes[i].OddID),
}
_, err := s.betStore.CreateFlag(ctx, flag)
if err != nil {
s.mongoLogger.Error("failed to create flag for bet",
zap.Int64("bet_id", bet.ID),
zap.Error(err),
)
}
}
}
// flag bets that have more than three outcomes
if len(outcomes) > 3 {
flag := domain.CreateFlagReq{
BetID: bet.ID,
OddID: 0,
Reason: fmt.Sprintf("too many outcomes - (%d)", len(outcomes)),
}
_, err := s.betStore.CreateFlag(ctx, flag)
if err != nil {
s.mongoLogger.Error("failed to create flag for bet",
zap.Int64("bet_id", bet.ID),
zap.Error(err),
)
}
}
// large amount of users betting on the same bet_outcomes
total_bet_count, err := s.betStore.GetBetCountByOutcomesHash(ctx, outcomesHash)
if err != nil {
s.mongoLogger.Error("failed to get bet outcomes count",
zap.String("outcomes_hash", outcomesHash),
zap.Error(err),
)
return domain.CreateBetRes{}, err
}
if total_bet_count > 10 {
flag := domain.CreateFlagReq{
BetID: bet.ID,
OddID: 0,
Reason: fmt.Sprintf("too many users bet on same outcomes - (%s)", outcomesHash),
}
_, err := s.betStore.CreateFlag(ctx, flag)
if err != nil {
s.mongoLogger.Error("failed to get bet outcomes count",
zap.String("outcomes_hash", outcomesHash),
zap.Error(err),
)
}
}
res := domain.ConvertCreateBet(bet, rows)
return res, nil
@ -716,7 +789,7 @@ func (s *Service) PlaceRandomBet(ctx context.Context, userID, branchID int64, le
return domain.CreateBetRes{}, err
}
count, err := s.GetBetCount(ctx, userID, outcomesHash)
count, err := s.GetBetCountByUserID(ctx, userID, outcomesHash)
if err != nil {
s.mongoLogger.Error("failed to get bet count",
zap.Int64("user_id", userID),
@ -799,8 +872,12 @@ func (s *Service) GetBetByFastCode(ctx context.Context, fastcode string) (domain
return s.betStore.GetBetByFastCode(ctx, fastcode)
}
func (s *Service) GetBetCount(ctx context.Context, UserID int64, outcomesHash string) (int64, error) {
return s.betStore.GetBetCount(ctx, UserID, outcomesHash)
func (s *Service) GetBetCountByUserID(ctx context.Context, UserID int64, outcomesHash string) (int64, error) {
return s.betStore.GetBetCountByUserID(ctx, UserID, outcomesHash)
}
func (s *Service) GetBetCountByOutcomesHash(ctx context.Context, outcomesHash string) (int64, error) {
return s.betStore.GetBetCountByOutcomesHash(ctx, outcomesHash)
}
func (s *Service) UpdateCashOut(ctx context.Context, id int64, cashedOut bool) error {