fix: web integration issues

This commit is contained in:
Samuel Tariku 2025-07-14 20:52:58 +03:00
parent aa4bddef58
commit 472e4490f8
18 changed files with 527 additions and 254 deletions

2
.gitignore vendored
View File

@ -8,4 +8,4 @@ build
logs/ logs/
app_logs/ app_logs/
backup/ backup/
reports/

View File

@ -216,7 +216,11 @@ CREATE TABLE IF NOT EXISTS branches (
is_self_owned BOOLEAN NOT NULL DEFAULT false, is_self_owned BOOLEAN NOT NULL DEFAULT false,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE(wallet_id) UNIQUE(wallet_id),
CONSTRAINT profit_percentage_check CHECK (
profit_percent >= 0
AND profit_percent < 1
)
); );
CREATE TABLE IF NOT EXISTS branch_operations ( CREATE TABLE IF NOT EXISTS branch_operations (
id BIGSERIAL PRIMARY KEY, id BIGSERIAL PRIMARY KEY,
@ -258,7 +262,8 @@ CREATE TABLE events (
status TEXT, status TEXT,
fetched_at TIMESTAMP DEFAULT now(), fetched_at TIMESTAMP DEFAULT now(),
source TEXT DEFAULT 'b365api', source TEXT DEFAULT 'b365api',
flagged BOOLEAN NOT NULL DEFAULT false is_featured BOOLEAN NOT NULL DEFAULT FALSE,
is_active BOOLEAN NOT NULL DEFAULT TRUE
); );
CREATE TABLE odds ( CREATE TABLE odds (
id SERIAL PRIMARY KEY, id SERIAL PRIMARY KEY,
@ -289,7 +294,11 @@ CREATE TABLE companies (
deducted_percentage REAL NOT NULL, deducted_percentage REAL NOT NULL,
is_active BOOLEAN NOT NULL DEFAULT false, is_active BOOLEAN NOT NULL DEFAULT false,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT deducted_percentage_check CHECK (
deducted_percentage >= 0
AND deducted_percentage < 1
)
); );
CREATE TABLE leagues ( CREATE TABLE leagues (
id BIGINT PRIMARY KEY, id BIGINT PRIMARY KEY,

View File

@ -5,9 +5,10 @@ INSERT INTO branches (
wallet_id, wallet_id,
branch_manager_id, branch_manager_id,
company_id, company_id,
is_self_owned is_self_owned,
profit_percent
) )
VALUES ($1, $2, $3, $4, $5, $6) VALUES ($1, $2, $3, $4, $5, $6, $7)
RETURNING *; RETURNING *;
-- name: CreateSupportedOperation :one -- name: CreateSupportedOperation :one
INSERT INTO supported_operations (name, description) INSERT INTO supported_operations (name, description)
@ -88,6 +89,7 @@ SET name = COALESCE(sqlc.narg(name), name),
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), is_active = COALESCE(sqlc.narg(is_active), is_active),
profit_percent = COALESCE(sqlc.narg(profit_percent), profit_percent),
updated_at = CURRENT_TIMESTAMP updated_at = CURRENT_TIMESTAMP
WHERE id = $1 WHERE id = $1
RETURNING *; RETURNING *;

View File

@ -157,6 +157,11 @@ WHERE is_live = false
events.sport_id = sqlc.narg('sport_id') events.sport_id = sqlc.narg('sport_id')
OR sqlc.narg('sport_id') IS NULL OR sqlc.narg('sport_id') IS NULL
) )
AND (
match_name ILIKE '%' || sqlc.narg('query') || '%'
OR league_name ILIKE '%' || sqlc.narg('query') || '%'
OR sqlc.narg('query') IS NULL
)
AND ( AND (
start_time < sqlc.narg('last_start_time') start_time < sqlc.narg('last_start_time')
OR sqlc.narg('last_start_time') IS NULL OR sqlc.narg('last_start_time') IS NULL
@ -170,8 +175,8 @@ WHERE is_live = false
OR sqlc.narg('country_code') IS NULL OR sqlc.narg('country_code') IS NULL
) )
AND ( AND (
flagged = sqlc.narg('flagged') events.is_featured = sqlc.narg('is_featured')
OR sqlc.narg('flagged') IS NULL OR sqlc.narg('is_featured') IS NULL
); );
-- name: GetPaginatedUpcomingEvents :many -- name: GetPaginatedUpcomingEvents :many
SELECT events.*, SELECT events.*,
@ -189,6 +194,11 @@ WHERE start_time > now()
events.sport_id = sqlc.narg('sport_id') events.sport_id = sqlc.narg('sport_id')
OR sqlc.narg('sport_id') IS NULL OR sqlc.narg('sport_id') IS NULL
) )
AND (
match_name ILIKE '%' || sqlc.narg('query') || '%'
OR league_name ILIKE '%' || sqlc.narg('query') || '%'
OR sqlc.narg('query') IS NULL
)
AND ( AND (
start_time < sqlc.narg('last_start_time') start_time < sqlc.narg('last_start_time')
OR sqlc.narg('last_start_time') IS NULL OR sqlc.narg('last_start_time') IS NULL
@ -202,8 +212,8 @@ WHERE start_time > now()
OR sqlc.narg('country_code') IS NULL OR sqlc.narg('country_code') IS NULL
) )
AND ( AND (
flagged = sqlc.narg('flagged') events.is_featured = sqlc.narg('is_featured')
OR sqlc.narg('flagged') IS NULL OR sqlc.narg('is_featured') IS NULL
) )
ORDER BY start_time ASC ORDER BY start_time ASC
LIMIT sqlc.narg('limit') OFFSET sqlc.narg('offset'); LIMIT sqlc.narg('limit') OFFSET sqlc.narg('offset');
@ -219,9 +229,9 @@ UPDATE events
SET score = $1, SET score = $1,
status = $2 status = $2
WHERE id = $3; WHERE id = $3;
-- name: UpdateFlagged :exec -- name: UpdateFeatured :exec
UPDATE events UPDATE events
SET flagged = $1 SET is_featured = $1
WHERE id = $2; WHERE id = $2;
-- name: DeleteEvent :exec -- name: DeleteEvent :exec
DELETE FROM events DELETE FROM events

View File

@ -18,19 +18,21 @@ INSERT INTO branches (
wallet_id, wallet_id,
branch_manager_id, branch_manager_id,
company_id, company_id,
is_self_owned is_self_owned,
profit_percent
) )
VALUES ($1, $2, $3, $4, $5, $6) VALUES ($1, $2, $3, $4, $5, $6, $7)
RETURNING id, name, location, profit_percent, is_active, wallet_id, branch_manager_id, company_id, is_self_owned, created_at, updated_at RETURNING id, name, location, profit_percent, is_active, wallet_id, branch_manager_id, company_id, is_self_owned, created_at, updated_at
` `
type CreateBranchParams struct { type CreateBranchParams struct {
Name string `json:"name"` Name string `json:"name"`
Location string `json:"location"` Location string `json:"location"`
WalletID int64 `json:"wallet_id"` WalletID int64 `json:"wallet_id"`
BranchManagerID int64 `json:"branch_manager_id"` BranchManagerID int64 `json:"branch_manager_id"`
CompanyID int64 `json:"company_id"` CompanyID int64 `json:"company_id"`
IsSelfOwned bool `json:"is_self_owned"` IsSelfOwned bool `json:"is_self_owned"`
ProfitPercent float32 `json:"profit_percent"`
} }
func (q *Queries) CreateBranch(ctx context.Context, arg CreateBranchParams) (Branch, error) { func (q *Queries) CreateBranch(ctx context.Context, arg CreateBranchParams) (Branch, error) {
@ -41,6 +43,7 @@ func (q *Queries) CreateBranch(ctx context.Context, arg CreateBranchParams) (Bra
arg.BranchManagerID, arg.BranchManagerID,
arg.CompanyID, arg.CompanyID,
arg.IsSelfOwned, arg.IsSelfOwned,
arg.ProfitPercent,
) )
var i Branch var i Branch
err := row.Scan( err := row.Scan(
@ -498,19 +501,21 @@ SET name = COALESCE($2, name),
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), is_active = COALESCE($7, is_active),
profit_percent = COALESCE($8, profit_percent),
updated_at = CURRENT_TIMESTAMP updated_at = CURRENT_TIMESTAMP
WHERE id = $1 WHERE id = $1
RETURNING id, name, location, profit_percent, is_active, wallet_id, branch_manager_id, company_id, is_self_owned, created_at, updated_at RETURNING id, name, location, profit_percent, is_active, wallet_id, branch_manager_id, company_id, is_self_owned, created_at, updated_at
` `
type UpdateBranchParams struct { type UpdateBranchParams struct {
ID int64 `json:"id"` ID int64 `json:"id"`
Name pgtype.Text `json:"name"` Name pgtype.Text `json:"name"`
Location pgtype.Text `json:"location"` Location pgtype.Text `json:"location"`
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"` IsActive pgtype.Bool `json:"is_active"`
ProfitPercent pgtype.Float4 `json:"profit_percent"`
} }
func (q *Queries) UpdateBranch(ctx context.Context, arg UpdateBranchParams) (Branch, error) { func (q *Queries) UpdateBranch(ctx context.Context, arg UpdateBranchParams) (Branch, error) {
@ -522,6 +527,7 @@ func (q *Queries) UpdateBranch(ctx context.Context, arg UpdateBranchParams) (Bra
arg.CompanyID, arg.CompanyID,
arg.IsSelfOwned, arg.IsSelfOwned,
arg.IsActive, arg.IsActive,
arg.ProfitPercent,
) )
var i Branch var i Branch
err := row.Scan( err := row.Scan(

View File

@ -22,7 +22,7 @@ func (q *Queries) DeleteEvent(ctx context.Context, id string) error {
} }
const GetAllUpcomingEvents = `-- name: GetAllUpcomingEvents :many const GetAllUpcomingEvents = `-- name: GetAllUpcomingEvents :many
SELECT id, sport_id, match_name, home_team, away_team, home_team_id, away_team_id, home_kit_image, away_kit_image, league_id, league_name, league_cc, start_time, score, match_minute, timer_status, added_time, match_period, is_live, status, fetched_at, source, flagged SELECT id, sport_id, match_name, home_team, away_team, home_team_id, away_team_id, home_kit_image, away_kit_image, league_id, league_name, league_cc, start_time, score, match_minute, timer_status, added_time, match_period, is_live, status, fetched_at, source, is_featured, is_active
FROM events FROM events
WHERE start_time > now() WHERE start_time > now()
AND is_live = false AND is_live = false
@ -62,7 +62,8 @@ func (q *Queries) GetAllUpcomingEvents(ctx context.Context) ([]Event, error) {
&i.Status, &i.Status,
&i.FetchedAt, &i.FetchedAt,
&i.Source, &i.Source,
&i.Flagged, &i.IsFeatured,
&i.IsActive,
); err != nil { ); err != nil {
return nil, err return nil, err
} }
@ -75,7 +76,7 @@ func (q *Queries) GetAllUpcomingEvents(ctx context.Context) ([]Event, error) {
} }
const GetExpiredUpcomingEvents = `-- name: GetExpiredUpcomingEvents :many const GetExpiredUpcomingEvents = `-- name: GetExpiredUpcomingEvents :many
SELECT events.id, events.sport_id, events.match_name, events.home_team, events.away_team, events.home_team_id, events.away_team_id, events.home_kit_image, events.away_kit_image, events.league_id, events.league_name, events.league_cc, events.start_time, events.score, events.match_minute, events.timer_status, events.added_time, events.match_period, events.is_live, events.status, events.fetched_at, events.source, events.flagged, SELECT events.id, events.sport_id, events.match_name, events.home_team, events.away_team, events.home_team_id, events.away_team_id, events.home_kit_image, events.away_kit_image, events.league_id, events.league_name, events.league_cc, events.start_time, events.score, events.match_minute, events.timer_status, events.added_time, events.match_period, events.is_live, events.status, events.fetched_at, events.source, events.is_featured, events.is_active,
leagues.country_code as league_cc leagues.country_code as league_cc
FROM events FROM events
LEFT JOIN leagues ON leagues.id = league_id LEFT JOIN leagues ON leagues.id = league_id
@ -110,7 +111,8 @@ type GetExpiredUpcomingEventsRow struct {
Status pgtype.Text `json:"status"` Status pgtype.Text `json:"status"`
FetchedAt pgtype.Timestamp `json:"fetched_at"` FetchedAt pgtype.Timestamp `json:"fetched_at"`
Source pgtype.Text `json:"source"` Source pgtype.Text `json:"source"`
Flagged bool `json:"flagged"` IsFeatured bool `json:"is_featured"`
IsActive bool `json:"is_active"`
LeagueCc_2 pgtype.Text `json:"league_cc_2"` LeagueCc_2 pgtype.Text `json:"league_cc_2"`
} }
@ -146,7 +148,8 @@ func (q *Queries) GetExpiredUpcomingEvents(ctx context.Context, status pgtype.Te
&i.Status, &i.Status,
&i.FetchedAt, &i.FetchedAt,
&i.Source, &i.Source,
&i.Flagged, &i.IsFeatured,
&i.IsActive,
&i.LeagueCc_2, &i.LeagueCc_2,
); err != nil { ); err != nil {
return nil, err return nil, err
@ -160,7 +163,7 @@ func (q *Queries) GetExpiredUpcomingEvents(ctx context.Context, status pgtype.Te
} }
const GetPaginatedUpcomingEvents = `-- name: GetPaginatedUpcomingEvents :many const GetPaginatedUpcomingEvents = `-- name: GetPaginatedUpcomingEvents :many
SELECT events.id, events.sport_id, events.match_name, events.home_team, events.away_team, events.home_team_id, events.away_team_id, events.home_kit_image, events.away_kit_image, events.league_id, events.league_name, events.league_cc, events.start_time, events.score, events.match_minute, events.timer_status, events.added_time, events.match_period, events.is_live, events.status, events.fetched_at, events.source, events.flagged, SELECT events.id, events.sport_id, events.match_name, events.home_team, events.away_team, events.home_team_id, events.away_team_id, events.home_kit_image, events.away_kit_image, events.league_id, events.league_name, events.league_cc, events.start_time, events.score, events.match_minute, events.timer_status, events.added_time, events.match_period, events.is_live, events.status, events.fetched_at, events.source, events.is_featured, events.is_active,
leagues.country_code as league_cc leagues.country_code as league_cc
FROM events FROM events
LEFT JOIN leagues ON leagues.id = league_id LEFT JOIN leagues ON leagues.id = league_id
@ -176,32 +179,38 @@ WHERE start_time > now()
OR $2 IS NULL OR $2 IS NULL
) )
AND ( AND (
start_time < $3 match_name ILIKE '%' || $3 || '%'
OR league_name ILIKE '%' || $3 || '%'
OR $3 IS NULL OR $3 IS NULL
) )
AND ( AND (
start_time > $4 start_time < $4
OR $4 IS NULL OR $4 IS NULL
) )
AND ( AND (
leagues.country_code = $5 start_time > $5
OR $5 IS NULL OR $5 IS NULL
) )
AND ( AND (
flagged = $6 leagues.country_code = $6
OR $6 IS NULL OR $6 IS NULL
) )
AND (
events.is_featured = $7
OR $7 IS NULL
)
ORDER BY start_time ASC ORDER BY start_time ASC
LIMIT $8 OFFSET $7 LIMIT $9 OFFSET $8
` `
type GetPaginatedUpcomingEventsParams struct { type GetPaginatedUpcomingEventsParams struct {
LeagueID pgtype.Int4 `json:"league_id"` LeagueID pgtype.Int4 `json:"league_id"`
SportID pgtype.Int4 `json:"sport_id"` SportID pgtype.Int4 `json:"sport_id"`
Query pgtype.Text `json:"query"`
LastStartTime pgtype.Timestamp `json:"last_start_time"` LastStartTime pgtype.Timestamp `json:"last_start_time"`
FirstStartTime pgtype.Timestamp `json:"first_start_time"` FirstStartTime pgtype.Timestamp `json:"first_start_time"`
CountryCode pgtype.Text `json:"country_code"` CountryCode pgtype.Text `json:"country_code"`
Flagged pgtype.Bool `json:"flagged"` IsFeatured pgtype.Bool `json:"is_featured"`
Offset pgtype.Int4 `json:"offset"` Offset pgtype.Int4 `json:"offset"`
Limit pgtype.Int4 `json:"limit"` Limit pgtype.Int4 `json:"limit"`
} }
@ -229,7 +238,8 @@ type GetPaginatedUpcomingEventsRow struct {
Status pgtype.Text `json:"status"` Status pgtype.Text `json:"status"`
FetchedAt pgtype.Timestamp `json:"fetched_at"` FetchedAt pgtype.Timestamp `json:"fetched_at"`
Source pgtype.Text `json:"source"` Source pgtype.Text `json:"source"`
Flagged bool `json:"flagged"` IsFeatured bool `json:"is_featured"`
IsActive bool `json:"is_active"`
LeagueCc_2 pgtype.Text `json:"league_cc_2"` LeagueCc_2 pgtype.Text `json:"league_cc_2"`
} }
@ -237,10 +247,11 @@ func (q *Queries) GetPaginatedUpcomingEvents(ctx context.Context, arg GetPaginat
rows, err := q.db.Query(ctx, GetPaginatedUpcomingEvents, rows, err := q.db.Query(ctx, GetPaginatedUpcomingEvents,
arg.LeagueID, arg.LeagueID,
arg.SportID, arg.SportID,
arg.Query,
arg.LastStartTime, arg.LastStartTime,
arg.FirstStartTime, arg.FirstStartTime,
arg.CountryCode, arg.CountryCode,
arg.Flagged, arg.IsFeatured,
arg.Offset, arg.Offset,
arg.Limit, arg.Limit,
) )
@ -274,7 +285,8 @@ func (q *Queries) GetPaginatedUpcomingEvents(ctx context.Context, arg GetPaginat
&i.Status, &i.Status,
&i.FetchedAt, &i.FetchedAt,
&i.Source, &i.Source,
&i.Flagged, &i.IsFeatured,
&i.IsActive,
&i.LeagueCc_2, &i.LeagueCc_2,
); err != nil { ); err != nil {
return nil, err return nil, err
@ -302,40 +314,47 @@ WHERE is_live = false
OR $2 IS NULL OR $2 IS NULL
) )
AND ( AND (
start_time < $3 match_name ILIKE '%' || $3 || '%'
OR league_name ILIKE '%' || $3 || '%'
OR $3 IS NULL OR $3 IS NULL
) )
AND ( AND (
start_time > $4 start_time < $4
OR $4 IS NULL OR $4 IS NULL
) )
AND ( AND (
leagues.country_code = $5 start_time > $5
OR $5 IS NULL OR $5 IS NULL
) )
AND ( AND (
flagged = $6 leagues.country_code = $6
OR $6 IS NULL OR $6 IS NULL
) )
AND (
events.is_featured = $7
OR $7 IS NULL
)
` `
type GetTotalEventsParams struct { type GetTotalEventsParams struct {
LeagueID pgtype.Int4 `json:"league_id"` LeagueID pgtype.Int4 `json:"league_id"`
SportID pgtype.Int4 `json:"sport_id"` SportID pgtype.Int4 `json:"sport_id"`
Query pgtype.Text `json:"query"`
LastStartTime pgtype.Timestamp `json:"last_start_time"` LastStartTime pgtype.Timestamp `json:"last_start_time"`
FirstStartTime pgtype.Timestamp `json:"first_start_time"` FirstStartTime pgtype.Timestamp `json:"first_start_time"`
CountryCode pgtype.Text `json:"country_code"` CountryCode pgtype.Text `json:"country_code"`
Flagged pgtype.Bool `json:"flagged"` IsFeatured pgtype.Bool `json:"is_featured"`
} }
func (q *Queries) GetTotalEvents(ctx context.Context, arg GetTotalEventsParams) (int64, error) { func (q *Queries) GetTotalEvents(ctx context.Context, arg GetTotalEventsParams) (int64, error) {
row := q.db.QueryRow(ctx, GetTotalEvents, row := q.db.QueryRow(ctx, GetTotalEvents,
arg.LeagueID, arg.LeagueID,
arg.SportID, arg.SportID,
arg.Query,
arg.LastStartTime, arg.LastStartTime,
arg.FirstStartTime, arg.FirstStartTime,
arg.CountryCode, arg.CountryCode,
arg.Flagged, arg.IsFeatured,
) )
var count int64 var count int64
err := row.Scan(&count) err := row.Scan(&count)
@ -343,7 +362,7 @@ func (q *Queries) GetTotalEvents(ctx context.Context, arg GetTotalEventsParams)
} }
const GetUpcomingByID = `-- name: GetUpcomingByID :one const GetUpcomingByID = `-- name: GetUpcomingByID :one
SELECT id, sport_id, match_name, home_team, away_team, home_team_id, away_team_id, home_kit_image, away_kit_image, league_id, league_name, league_cc, start_time, score, match_minute, timer_status, added_time, match_period, is_live, status, fetched_at, source, flagged SELECT id, sport_id, match_name, home_team, away_team, home_team_id, away_team_id, home_kit_image, away_kit_image, league_id, league_name, league_cc, start_time, score, match_minute, timer_status, added_time, match_period, is_live, status, fetched_at, source, is_featured, is_active
FROM events FROM events
WHERE id = $1 WHERE id = $1
AND is_live = false AND is_live = false
@ -377,7 +396,8 @@ func (q *Queries) GetUpcomingByID(ctx context.Context, id string) (Event, error)
&i.Status, &i.Status,
&i.FetchedAt, &i.FetchedAt,
&i.Source, &i.Source,
&i.Flagged, &i.IsFeatured,
&i.IsActive,
) )
return i, err return i, err
} }
@ -623,19 +643,19 @@ func (q *Queries) ListLiveEvents(ctx context.Context) ([]string, error) {
return items, nil return items, nil
} }
const UpdateFlagged = `-- name: UpdateFlagged :exec const UpdateFeatured = `-- name: UpdateFeatured :exec
UPDATE events UPDATE events
SET flagged = $1 SET is_featured = $1
WHERE id = $2 WHERE id = $2
` `
type UpdateFlaggedParams struct { type UpdateFeaturedParams struct {
Flagged bool `json:"flagged"` IsFeatured bool `json:"is_featured"`
ID string `json:"id"` ID string `json:"id"`
} }
func (q *Queries) UpdateFlagged(ctx context.Context, arg UpdateFlaggedParams) error { func (q *Queries) UpdateFeatured(ctx context.Context, arg UpdateFeaturedParams) error {
_, err := q.db.Exec(ctx, UpdateFlagged, arg.Flagged, arg.ID) _, err := q.db.Exec(ctx, UpdateFeatured, arg.IsFeatured, arg.ID)
return err return err
} }

View File

@ -257,7 +257,8 @@ type Event struct {
Status pgtype.Text `json:"status"` Status pgtype.Text `json:"status"`
FetchedAt pgtype.Timestamp `json:"fetched_at"` FetchedAt pgtype.Timestamp `json:"fetched_at"`
Source pgtype.Text `json:"source"` Source pgtype.Text `json:"source"`
Flagged bool `json:"flagged"` IsFeatured bool `json:"is_featured"`
IsActive bool `json:"is_active"`
} }
type ExchangeRate struct { type ExchangeRate struct {

View File

@ -93,12 +93,12 @@ type CreateBetOutcomeReq struct {
} }
type CreateBetReq struct { type CreateBetReq struct {
Outcomes []CreateBetOutcomeReq `json:"outcomes" validate:"required"` Outcomes []CreateBetOutcomeReq `json:"outcomes" validate:"required"`
Amount float32 `json:"amount" validate:"required,gt=0" example:"100.0"` Amount float32 `json:"amount" validate:"required,gt=0" example:"100.0"`
BranchID *int64 `json:"branch_id,omitempty" validate:"required" example:"1"` BranchID *int64 `json:"branch_id,omitempty" example:"1"`
} }
type CreateBetWithFastCodeReq struct { type CreateBetWithFastCodeReq struct {
FastCode string `json:"fast_code"` FastCode string `json:"fast_code"`
Amount float32 `json:"amount"` Amount float32 `json:"amount"`
BranchID *int64 `json:"branch_id"` BranchID *int64 `json:"branch_id"`
@ -117,6 +117,7 @@ type CreateBetRes struct {
UserID int64 `json:"user_id" example:"2"` UserID int64 `json:"user_id" example:"2"`
IsShopBet bool `json:"is_shop_bet" example:"false"` IsShopBet bool `json:"is_shop_bet" example:"false"`
CreatedNumber int64 `json:"created_number" example:"2"` CreatedNumber int64 `json:"created_number" example:"2"`
FastCode string `json:"fast_code"`
} }
type BetRes struct { type BetRes struct {
ID int64 `json:"id" example:"1"` ID int64 `json:"id" example:"1"`
@ -140,6 +141,8 @@ func ConvertCreateBet(bet Bet, createdNumber int64) CreateBetRes {
Status: bet.Status, Status: bet.Status,
UserID: bet.UserID, UserID: bet.UserID,
CreatedNumber: createdNumber, CreatedNumber: createdNumber,
IsShopBet: bet.IsShopBet,
FastCode: bet.FastCode,
} }
} }

View File

@ -1,14 +1,20 @@
package domain package domain
import (
dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
"github.com/jackc/pgx/v5/pgtype"
)
type Branch struct { type Branch struct {
ID int64 ID int64
Name string Name string
Location string Location string
WalletID int64 WalletID int64
BranchManagerID int64 BranchManagerID int64
CompanyID int64 CompanyID int64
IsActive bool IsActive bool
IsSelfOwned bool IsSelfOwned bool
ProfitPercentage float32
} }
type BranchLocation struct { type BranchLocation struct {
@ -38,6 +44,7 @@ type BranchDetail struct {
ManagerName string ManagerName string
ManagerPhoneNumber string ManagerPhoneNumber string
WalletIsActive bool WalletIsActive bool
ProfitPercentage float32
} }
type SupportedOperation struct { type SupportedOperation struct {
@ -53,22 +60,24 @@ type BranchOperation struct {
} }
type CreateBranch struct { type CreateBranch struct {
Name string Name string
Location string Location string
WalletID int64 WalletID int64
BranchManagerID int64 BranchManagerID int64
CompanyID int64 CompanyID int64
IsSelfOwned bool IsSelfOwned bool
ProfitPercentage float32
} }
type UpdateBranch struct { type UpdateBranch struct {
ID int64 ID int64
Name *string Name *string
Location *string Location *string
BranchManagerID *int64 BranchManagerID *int64
CompanyID *int64 CompanyID *int64
IsSelfOwned *bool IsSelfOwned *bool
IsActive *bool IsActive *bool
ProfitPercentage *float32
} }
type CreateSupportedOperation struct { type CreateSupportedOperation struct {
@ -81,21 +90,23 @@ type CreateBranchOperation struct {
} }
type CreateBranchReq struct { type CreateBranchReq struct {
Name string `json:"name" validate:"required,min=3,max=100" example:"4-kilo Branch"` Name string `json:"name" validate:"required,min=3,max=100" example:"4-kilo Branch"`
Location string `json:"location" validate:"required,min=3,max=100" example:"Addis Ababa"` Location string `json:"location" validate:"required,min=3,max=100" example:"Addis Ababa"`
BranchManagerID int64 `json:"branch_manager_id" validate:"required,gt=0" example:"1"` BranchManagerID int64 `json:"branch_manager_id" validate:"required,gt=0" example:"1"`
CompanyID *int64 `json:"company_id,omitempty" example:"1"` ProfitPercentage float32 `json:"profit_percentage" example:"0.1" validate:"lt=1" `
IsSelfOwned *bool `json:"is_self_owned,omitempty" example:"false"` CompanyID *int64 `json:"company_id,omitempty" example:"1"`
Operations []int64 `json:"operations" validate:"required,dive,gt=0"` IsSelfOwned *bool `json:"is_self_owned,omitempty" example:"false"`
Operations []int64 `json:"operations" validate:"required,dive,gt=0"`
} }
type UpdateBranchReq struct { type UpdateBranchReq struct {
Name *string `json:"name,omitempty" example:"4-kilo Branch"` Name *string `json:"name,omitempty" example:"4-kilo Branch"`
Location *string `json:"location,omitempty" example:"Addis Ababa"` Location *string `json:"location,omitempty" example:"Addis Ababa"`
BranchManagerID *int64 `json:"branch_manager_id,omitempty" example:"1"` BranchManagerID *int64 `json:"branch_manager_id,omitempty" example:"1"`
CompanyID *int64 `json:"company_id,omitempty" example:"1"` CompanyID *int64 `json:"company_id,omitempty" example:"1"`
IsSelfOwned *bool `json:"is_self_owned,omitempty" example:"false"` IsSelfOwned *bool `json:"is_self_owned,omitempty" example:"false"`
IsActive *bool `json:"is_active,omitempty" example:"false"` IsActive *bool `json:"is_active,omitempty" example:"false"`
ProfitPercentage *float32 `json:"profit_percentage,omitempty" example:"0.1" validate:"lt=1" `
} }
type CreateSupportedOperationReq struct { type CreateSupportedOperationReq struct {
@ -120,14 +131,15 @@ type BranchOperationRes struct {
} }
type BranchRes struct { type BranchRes struct {
ID int64 `json:"id" example:"1"` ID int64 `json:"id" example:"1"`
Name string `json:"name" example:"4-kilo Branch"` Name string `json:"name" example:"4-kilo Branch"`
Location string `json:"location" example:"Addis Ababa"` Location string `json:"location" example:"Addis Ababa"`
WalletID int64 `json:"wallet_id" example:"1"` WalletID int64 `json:"wallet_id" example:"1"`
BranchManagerID int64 `json:"branch_manager_id" example:"1"` BranchManagerID int64 `json:"branch_manager_id" example:"1"`
CompanyID int64 `json:"company_id" example:"1"` CompanyID int64 `json:"company_id" example:"1"`
IsSelfOwned bool `json:"is_self_owned" example:"false"` IsSelfOwned bool `json:"is_self_owned" example:"false"`
IsActive bool `json:"is_active" example:"false"` IsActive bool `json:"is_active" example:"false"`
ProfitPercentage float32 `json:"profit_percentage" example:"0.1"`
} }
type BranchDetailRes struct { type BranchDetailRes struct {
@ -143,18 +155,20 @@ type BranchDetailRes struct {
Balance float32 `json:"balance" example:"100.5"` Balance float32 `json:"balance" example:"100.5"`
IsActive bool `json:"is_active" example:"false"` IsActive bool `json:"is_active" example:"false"`
WalletIsActive bool `json:"is_wallet_active" example:"false"` WalletIsActive bool `json:"is_wallet_active" example:"false"`
ProfitPercentage float32 `json:"profit_percentage" example:"0.1"`
} }
func ConvertBranch(branch Branch) BranchRes { func ConvertBranch(branch Branch) BranchRes {
return BranchRes{ return BranchRes{
ID: branch.ID, ID: branch.ID,
Name: branch.Name, Name: branch.Name,
Location: branch.Location, Location: branch.Location,
WalletID: branch.WalletID, WalletID: branch.WalletID,
BranchManagerID: branch.BranchManagerID, BranchManagerID: branch.BranchManagerID,
CompanyID: branch.CompanyID, CompanyID: branch.CompanyID,
IsSelfOwned: branch.IsSelfOwned, IsSelfOwned: branch.IsSelfOwned,
IsActive: branch.IsActive, IsActive: branch.IsActive,
ProfitPercentage: branch.ProfitPercentage,
} }
} }
@ -172,5 +186,103 @@ func ConvertBranchDetail(branch BranchDetail) BranchDetailRes {
Balance: branch.Balance.Float32(), Balance: branch.Balance.Float32(),
IsActive: branch.IsActive, IsActive: branch.IsActive,
WalletIsActive: branch.WalletIsActive, WalletIsActive: branch.WalletIsActive,
ProfitPercentage: branch.ProfitPercentage,
} }
} }
func ConvertCreateBranch(branch CreateBranch) dbgen.CreateBranchParams {
return dbgen.CreateBranchParams{
Name: branch.Name,
Location: branch.Location,
WalletID: branch.WalletID,
BranchManagerID: branch.BranchManagerID,
CompanyID: branch.CompanyID,
IsSelfOwned: branch.IsSelfOwned,
ProfitPercent: branch.ProfitPercentage,
}
}
func ConvertDBBranchDetail(dbBranch dbgen.BranchDetail) BranchDetail {
return BranchDetail{
ID: dbBranch.ID,
Name: dbBranch.Name,
Location: dbBranch.Location,
WalletID: dbBranch.WalletID,
BranchManagerID: dbBranch.BranchManagerID,
CompanyID: dbBranch.CompanyID,
IsSelfOwned: dbBranch.IsSelfOwned,
ManagerName: dbBranch.ManagerName.(string),
ManagerPhoneNumber: dbBranch.ManagerPhoneNumber.String,
Balance: Currency(dbBranch.Balance.Int64),
IsActive: dbBranch.IsActive,
WalletIsActive: dbBranch.WalletIsActive.Bool,
ProfitPercentage: dbBranch.ProfitPercent,
}
}
func ConvertDBBranch(dbBranch dbgen.Branch) Branch {
return Branch{
ID: dbBranch.ID,
Name: dbBranch.Name,
Location: dbBranch.Location,
WalletID: dbBranch.WalletID,
BranchManagerID: dbBranch.BranchManagerID,
CompanyID: dbBranch.CompanyID,
IsSelfOwned: dbBranch.IsSelfOwned,
IsActive: dbBranch.IsActive,
ProfitPercentage: dbBranch.ProfitPercent,
}
}
func ConvertUpdateBranch(updateBranch UpdateBranch) dbgen.UpdateBranchParams {
var newUpdateBranch dbgen.UpdateBranchParams
newUpdateBranch.ID = updateBranch.ID
if updateBranch.Name != nil {
newUpdateBranch.Name = pgtype.Text{
String: *updateBranch.Name,
Valid: true,
}
}
if updateBranch.Location != nil {
newUpdateBranch.Location = pgtype.Text{
String: *updateBranch.Location,
Valid: true,
}
}
if updateBranch.BranchManagerID != nil {
newUpdateBranch.BranchManagerID = pgtype.Int8{
Int64: *updateBranch.BranchManagerID,
Valid: true,
}
}
if updateBranch.CompanyID != nil {
newUpdateBranch.CompanyID = pgtype.Int8{
Int64: *updateBranch.CompanyID,
Valid: true,
}
}
if updateBranch.IsSelfOwned != nil {
newUpdateBranch.IsSelfOwned = pgtype.Bool{
Bool: *updateBranch.IsSelfOwned,
Valid: true,
}
}
if updateBranch.IsActive != nil {
newUpdateBranch.IsActive = pgtype.Bool{
Bool: *updateBranch.IsActive,
Valid: true,
}
}
if updateBranch.ProfitPercentage != nil {
newUpdateBranch.ProfitPercent = pgtype.Float4{
Float32: *updateBranch.ProfitPercentage,
Valid: true,
}
}
return newUpdateBranch
}

View File

@ -101,7 +101,8 @@ type UpcomingEvent struct {
StartTime time.Time `json:"start_time"` // Converted from "time" field in UNIX format StartTime time.Time `json:"start_time"` // Converted from "time" field in UNIX format
Source string `json:"source"` // bet api provider (bet365, betfair) Source string `json:"source"` // bet api provider (bet365, betfair)
Status EventStatus `json:"status"` //Match Status for event Status EventStatus `json:"status"` //Match Status for event
Flagged bool `json:"flagged"` //Whether the event is flagged or not IsFeatured bool `json:"is_featured"` //Whether the event is featured or not
IsActive bool `json:"is_active"` //Whether the event is featured or not
} }
type MatchResult struct { type MatchResult struct {
EventID string EventID string
@ -120,6 +121,7 @@ type Odds struct {
} }
type EventFilter struct { type EventFilter struct {
Query ValidString
SportID ValidInt32 SportID ValidInt32
LeagueID ValidInt32 LeagueID ValidInt32
CountryCode ValidString CountryCode ValidString
@ -128,5 +130,5 @@ type EventFilter struct {
Limit ValidInt64 Limit ValidInt64
Offset ValidInt64 Offset ValidInt64
MatchStatus ValidString // e.g., "upcoming", "in_play", "ended" MatchStatus ValidString // e.g., "upcoming", "in_play", "ended"
Flagged ValidBool Featured ValidBool
} }

View File

@ -9,100 +9,15 @@ import (
"github.com/jackc/pgx/v5/pgtype" "github.com/jackc/pgx/v5/pgtype"
) )
func convertCreateBranch(branch domain.CreateBranch) dbgen.CreateBranchParams {
return dbgen.CreateBranchParams{
Name: branch.Name,
Location: branch.Location,
WalletID: branch.WalletID,
BranchManagerID: branch.BranchManagerID,
CompanyID: branch.CompanyID,
IsSelfOwned: branch.IsSelfOwned,
}
}
func convertDBBranchDetail(dbBranch dbgen.BranchDetail) domain.BranchDetail {
return domain.BranchDetail{
ID: dbBranch.ID,
Name: dbBranch.Name,
Location: dbBranch.Location,
WalletID: dbBranch.WalletID,
BranchManagerID: dbBranch.BranchManagerID,
CompanyID: dbBranch.CompanyID,
IsSelfOwned: dbBranch.IsSelfOwned,
ManagerName: dbBranch.ManagerName.(string),
ManagerPhoneNumber: dbBranch.ManagerPhoneNumber.String,
Balance: domain.Currency(dbBranch.Balance.Int64),
IsActive: dbBranch.IsActive,
WalletIsActive: dbBranch.WalletIsActive.Bool,
}
}
func convertDBBranch(dbBranch dbgen.Branch) domain.Branch {
return domain.Branch{
ID: dbBranch.ID,
Name: dbBranch.Name,
Location: dbBranch.Location,
WalletID: dbBranch.WalletID,
BranchManagerID: dbBranch.BranchManagerID,
CompanyID: dbBranch.CompanyID,
IsSelfOwned: dbBranch.IsSelfOwned,
}
}
func convertUpdateBranch(updateBranch domain.UpdateBranch) dbgen.UpdateBranchParams {
var newUpdateBranch dbgen.UpdateBranchParams
newUpdateBranch.ID = updateBranch.ID
if updateBranch.Name != nil {
newUpdateBranch.Name = pgtype.Text{
String: *updateBranch.Name,
Valid: true,
}
}
if updateBranch.Location != nil {
newUpdateBranch.Location = pgtype.Text{
String: *updateBranch.Location,
Valid: true,
}
}
if updateBranch.BranchManagerID != nil {
newUpdateBranch.BranchManagerID = pgtype.Int8{
Int64: *updateBranch.BranchManagerID,
Valid: true,
}
}
if updateBranch.CompanyID != nil {
newUpdateBranch.CompanyID = pgtype.Int8{
Int64: *updateBranch.CompanyID,
Valid: true,
}
}
if updateBranch.IsSelfOwned != nil {
newUpdateBranch.IsSelfOwned = pgtype.Bool{
Bool: *updateBranch.IsSelfOwned,
Valid: true,
}
}
if updateBranch.IsActive != nil {
newUpdateBranch.IsActive = pgtype.Bool{
Bool: *updateBranch.IsActive,
Valid: true,
}
}
return newUpdateBranch
}
func (s *Store) CreateBranch(ctx context.Context, branch domain.CreateBranch) (domain.Branch, error) { func (s *Store) CreateBranch(ctx context.Context, branch domain.CreateBranch) (domain.Branch, error) {
dbBranch, err := s.queries.CreateBranch(ctx, convertCreateBranch(branch)) dbBranch, err := s.queries.CreateBranch(ctx, domain.ConvertCreateBranch(branch))
if err != nil { if err != nil {
return domain.Branch{}, err return domain.Branch{}, err
} }
return convertDBBranch(dbBranch), nil return domain.ConvertDBBranch(dbBranch), nil
} }
func (s *Store) GetBranchByID(ctx context.Context, id int64) (domain.BranchDetail, error) { func (s *Store) GetBranchByID(ctx context.Context, id int64) (domain.BranchDetail, error) {
@ -110,7 +25,7 @@ func (s *Store) GetBranchByID(ctx context.Context, id int64) (domain.BranchDetai
if err != nil { if err != nil {
return domain.BranchDetail{}, err return domain.BranchDetail{}, err
} }
return convertDBBranchDetail(dbBranch), nil return domain.ConvertDBBranchDetail(dbBranch), nil
} }
func (s *Store) GetBranchByManagerID(ctx context.Context, branchManagerID int64) ([]domain.BranchDetail, error) { func (s *Store) GetBranchByManagerID(ctx context.Context, branchManagerID int64) ([]domain.BranchDetail, error) {
@ -120,7 +35,7 @@ func (s *Store) GetBranchByManagerID(ctx context.Context, branchManagerID int64)
} }
var branches []domain.BranchDetail = make([]domain.BranchDetail, 0, len(dbBranches)) var branches []domain.BranchDetail = make([]domain.BranchDetail, 0, len(dbBranches))
for _, dbBranch := range dbBranches { for _, dbBranch := range dbBranches {
branches = append(branches, convertDBBranchDetail(dbBranch)) branches = append(branches, domain.ConvertDBBranchDetail(dbBranch))
} }
return branches, nil return branches, nil
} }
@ -131,7 +46,7 @@ func (s *Store) GetBranchByCompanyID(ctx context.Context, companyID int64) ([]do
} }
var branches []domain.BranchDetail = make([]domain.BranchDetail, 0, len(dbBranches)) var branches []domain.BranchDetail = make([]domain.BranchDetail, 0, len(dbBranches))
for _, dbBranch := range dbBranches { for _, dbBranch := range dbBranches {
branches = append(branches, convertDBBranchDetail(dbBranch)) branches = append(branches, domain.ConvertDBBranchDetail(dbBranch))
} }
return branches, nil return branches, nil
} }
@ -164,7 +79,7 @@ func (s *Store) GetAllBranches(ctx context.Context, filter domain.BranchFilter)
} }
var branches []domain.BranchDetail = make([]domain.BranchDetail, 0, len(dbBranches)) var branches []domain.BranchDetail = make([]domain.BranchDetail, 0, len(dbBranches))
for _, dbBranch := range dbBranches { for _, dbBranch := range dbBranches {
branches = append(branches, convertDBBranchDetail(dbBranch)) branches = append(branches, domain.ConvertDBBranchDetail(dbBranch))
} }
return branches, nil return branches, nil
} }
@ -177,18 +92,18 @@ func (s *Store) SearchBranchByName(ctx context.Context, name string) ([]domain.B
var branches []domain.BranchDetail = make([]domain.BranchDetail, 0, len(dbBranches)) var branches []domain.BranchDetail = make([]domain.BranchDetail, 0, len(dbBranches))
for _, dbBranch := range dbBranches { for _, dbBranch := range dbBranches {
branches = append(branches, convertDBBranchDetail(dbBranch)) branches = append(branches, domain.ConvertDBBranchDetail(dbBranch))
} }
return branches, nil return branches, nil
} }
func (s *Store) UpdateBranch(ctx context.Context, branch domain.UpdateBranch) (domain.Branch, error) { func (s *Store) UpdateBranch(ctx context.Context, branch domain.UpdateBranch) (domain.Branch, error) {
dbBranch, err := s.queries.UpdateBranch(ctx, convertUpdateBranch(branch)) dbBranch, err := s.queries.UpdateBranch(ctx, domain.ConvertUpdateBranch(branch))
if err != nil { if err != nil {
return domain.Branch{}, err return domain.Branch{}, err
} }
return convertDBBranch(dbBranch), nil return domain.ConvertDBBranch(dbBranch), nil
} }
func (s *Store) DeleteBranch(ctx context.Context, id int64) error { func (s *Store) DeleteBranch(ctx context.Context, id int64) error {
@ -272,7 +187,7 @@ func (s *Store) GetBranchByCashier(ctx context.Context, userID int64) (domain.Br
return domain.Branch{}, err return domain.Branch{}, err
} }
return convertDBBranch(branch), err return domain.ConvertDBBranch(branch), err
} }
func (s *Store) DeleteBranchOperation(ctx context.Context, branchID int64, operationID int64) error { func (s *Store) DeleteBranchOperation(ctx context.Context, branchID int64, operationID int64) error {

View File

@ -89,7 +89,7 @@ func (s *Store) GetAllUpcomingEvents(ctx context.Context) ([]domain.UpcomingEven
StartTime: e.StartTime.Time.UTC(), StartTime: e.StartTime.Time.UTC(),
Source: e.Source.String, Source: e.Source.String,
Status: domain.EventStatus(e.Status.String), Status: domain.EventStatus(e.Status.String),
Flagged: e.Flagged, IsFeatured: e.IsFeatured,
} }
} }
return upcomingEvents, nil return upcomingEvents, nil
@ -122,7 +122,8 @@ func (s *Store) GetExpiredUpcomingEvents(ctx context.Context, filter domain.Even
StartTime: e.StartTime.Time.UTC(), StartTime: e.StartTime.Time.UTC(),
Source: e.Source.String, Source: e.Source.String,
Status: domain.EventStatus(e.Status.String), Status: domain.EventStatus(e.Status.String),
Flagged: e.Flagged, IsFeatured: e.IsFeatured,
IsActive: e.IsActive,
} }
} }
return upcomingEvents, nil return upcomingEvents, nil
@ -139,6 +140,10 @@ func (s *Store) GetPaginatedUpcomingEvents(ctx context.Context, filter domain.Ev
Int32: int32(filter.SportID.Value), Int32: int32(filter.SportID.Value),
Valid: filter.SportID.Valid, Valid: filter.SportID.Valid,
}, },
Query: pgtype.Text{
String: filter.Query.Value,
Valid: filter.Query.Valid,
},
Limit: pgtype.Int4{ Limit: pgtype.Int4{
Int32: int32(filter.Limit.Value), Int32: int32(filter.Limit.Value),
Valid: filter.Limit.Valid, Valid: filter.Limit.Valid,
@ -159,9 +164,9 @@ func (s *Store) GetPaginatedUpcomingEvents(ctx context.Context, filter domain.Ev
String: filter.CountryCode.Value, String: filter.CountryCode.Value,
Valid: filter.CountryCode.Valid, Valid: filter.CountryCode.Valid,
}, },
Flagged: pgtype.Bool{ IsFeatured: pgtype.Bool{
Bool: filter.Flagged.Valid, Bool: filter.Featured.Valid,
Valid: filter.Flagged.Valid, Valid: filter.Featured.Valid,
}, },
}) })
@ -186,7 +191,8 @@ func (s *Store) GetPaginatedUpcomingEvents(ctx context.Context, filter domain.Ev
StartTime: e.StartTime.Time.UTC(), StartTime: e.StartTime.Time.UTC(),
Source: e.Source.String, Source: e.Source.String,
Status: domain.EventStatus(e.Status.String), Status: domain.EventStatus(e.Status.String),
Flagged: e.Flagged, IsFeatured: e.IsFeatured,
IsActive: e.IsActive,
} }
} }
totalCount, err := s.queries.GetTotalEvents(ctx, dbgen.GetTotalEventsParams{ totalCount, err := s.queries.GetTotalEvents(ctx, dbgen.GetTotalEventsParams{
@ -198,6 +204,10 @@ func (s *Store) GetPaginatedUpcomingEvents(ctx context.Context, filter domain.Ev
Int32: int32(filter.SportID.Value), Int32: int32(filter.SportID.Value),
Valid: filter.SportID.Valid, Valid: filter.SportID.Valid,
}, },
Query: pgtype.Text{
String: filter.Query.Value,
Valid: filter.Query.Valid,
},
FirstStartTime: pgtype.Timestamp{ FirstStartTime: pgtype.Timestamp{
Time: filter.FirstStartTime.Value.UTC(), Time: filter.FirstStartTime.Value.UTC(),
Valid: filter.FirstStartTime.Valid, Valid: filter.FirstStartTime.Valid,
@ -210,9 +220,9 @@ func (s *Store) GetPaginatedUpcomingEvents(ctx context.Context, filter domain.Ev
String: filter.CountryCode.Value, String: filter.CountryCode.Value,
Valid: filter.CountryCode.Valid, Valid: filter.CountryCode.Valid,
}, },
Flagged: pgtype.Bool{ IsFeatured: pgtype.Bool{
Bool: filter.Flagged.Valid, Bool: filter.Featured.Valid,
Valid: filter.Flagged.Valid, Valid: filter.Featured.Valid,
}, },
}) })
if err != nil { if err != nil {
@ -244,7 +254,7 @@ func (s *Store) GetUpcomingEventByID(ctx context.Context, ID string) (domain.Upc
StartTime: event.StartTime.Time.UTC(), StartTime: event.StartTime.Time.UTC(),
Source: event.Source.String, Source: event.Source.String,
Status: domain.EventStatus(event.Status.String), Status: domain.EventStatus(event.Status.String),
Flagged: event.Flagged, IsFeatured: event.IsFeatured,
}, nil }, nil
} }
func (s *Store) UpdateFinalScore(ctx context.Context, eventID, fullScore string, status domain.EventStatus) error { func (s *Store) UpdateFinalScore(ctx context.Context, eventID, fullScore string, status domain.EventStatus) error {
@ -280,10 +290,10 @@ func (s *Store) UpdateEventStatus(ctx context.Context, eventID string, status do
} }
func (s *Store) UpdateFlagged(ctx context.Context, eventID string, flagged bool) error { func (s *Store) UpdateFeatured(ctx context.Context, eventID string, isFeatured bool) error {
return s.queries.UpdateFlagged(ctx, dbgen.UpdateFlaggedParams{ return s.queries.UpdateFeatured(ctx, dbgen.UpdateFeaturedParams{
ID: eventID, ID: eventID,
Flagged: flagged, IsFeatured: isFeatured,
}) })
} }

View File

@ -16,5 +16,5 @@ type Service interface {
// GetAndStoreMatchResult(ctx context.Context, eventID string) error // GetAndStoreMatchResult(ctx context.Context, eventID string) error
UpdateFinalScore(ctx context.Context, eventID, fullScore string, status domain.EventStatus) error UpdateFinalScore(ctx context.Context, eventID, fullScore string, status domain.EventStatus) error
UpdateEventStatus(ctx context.Context, eventID string, status domain.EventStatus) error UpdateEventStatus(ctx context.Context, eventID string, status domain.EventStatus) error
UpdateFlagged(ctx context.Context, eventID string, flagged bool) error UpdateFeatured(ctx context.Context, eventID string, flagged bool) error
} }

View File

@ -369,8 +369,8 @@ func (s *service) UpdateEventStatus(ctx context.Context, eventID string, status
return s.store.UpdateEventStatus(ctx, eventID, status) return s.store.UpdateEventStatus(ctx, eventID, status)
} }
func (s *service) UpdateFlagged(ctx context.Context, eventID string, flagged bool) error { func (s *service) UpdateFeatured(ctx context.Context, eventID string, flagged bool) error {
return s.store.UpdateFlagged(ctx, eventID, flagged) return s.store.UpdateFeatured(ctx, eventID, flagged)
} }
// func (s *service) GetAndStoreMatchResult(ctx context.Context, eventID string) error { // func (s *service) GetAndStoreMatchResult(ctx context.Context, eventID string) error {

View File

@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"time" "time"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/authentication" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/authentication"
jwtutil "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/jwt" jwtutil "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/jwt"
"github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response" "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response"
@ -37,7 +38,7 @@ type loginCustomerRes struct {
// @Failure 400 {object} response.APIResponse // @Failure 400 {object} response.APIResponse
// @Failure 401 {object} response.APIResponse // @Failure 401 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /api/v1/auth/login [post] // @Router /api/v1/auth/customer-login [post]
func (h *Handler) LoginCustomer(c *fiber.Ctx) error { func (h *Handler) LoginCustomer(c *fiber.Ctx) error {
var req loginCustomerReq var req loginCustomerReq
if err := c.BodyParser(&req); err != nil { if err := c.BodyParser(&req); err != nil {
@ -59,7 +60,6 @@ func (h *Handler) LoginCustomer(c *fiber.Ctx) error {
successRes, err := h.authSvc.Login(c.Context(), req.Email, req.PhoneNumber, req.Password) successRes, err := h.authSvc.Login(c.Context(), req.Email, req.PhoneNumber, req.Password)
if err != nil { if err != nil {
switch { switch {
case errors.Is(err, authentication.ErrInvalidPassword), errors.Is(err, authentication.ErrUserNotFound): case errors.Is(err, authentication.ErrInvalidPassword), errors.Is(err, authentication.ErrUserNotFound):
h.mongoLoggerSvc.Info("Login attempt failed: Invalid credentials", h.mongoLoggerSvc.Info("Login attempt failed: Invalid credentials",
@ -89,6 +89,133 @@ func (h *Handler) LoginCustomer(c *fiber.Ctx) error {
} }
} }
if successRes.Role != domain.RoleCustomer {
h.mongoLoggerSvc.Info("Login attempt: customer login of other role",
zap.Int("status_code", fiber.StatusForbidden),
zap.String("role", string(successRes.Role)),
zap.String("email", req.Email),
zap.String("phone", req.PhoneNumber),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusForbidden, "Only customers are allowed to login ")
}
accessToken, err := jwtutil.CreateJwt(successRes.UserId, successRes.Role, successRes.CompanyID, h.jwtConfig.JwtAccessKey, h.jwtConfig.JwtAccessExpiry)
if err != nil {
h.mongoLoggerSvc.Error("Failed to create access token",
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Int64("user_id", successRes.UserId),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, "Failed to generate access token")
}
res := loginCustomerRes{
AccessToken: accessToken,
RefreshToken: successRes.RfToken,
Role: string(successRes.Role),
}
h.mongoLoggerSvc.Info("Login successful",
zap.Int("status_code", fiber.StatusOK),
zap.Int64("user_id", successRes.UserId),
zap.String("role", string(successRes.Role)),
zap.Time("timestamp", time.Now()),
)
return response.WriteJSON(c, fiber.StatusOK, "Login successful", res, nil)
}
// loginAdminReq represents the request body for the LoginAdmin endpoint.
type loginAdminReq struct {
Email string `json:"email" validate:"email" example:"john.doe@example.com"`
PhoneNumber string `json:"phone_number" validate:"required_without=Email" example:"1234567890"`
Password string `json:"password" validate:"required" example:"password123"`
}
// loginAdminRes represents the response body for the LoginAdmin endpoint.
type loginAdminRes struct {
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
Role string `json:"role"`
}
// LoginAdmin godoc
// @Summary Login customer
// @Description Login customer
// @Tags auth
// @Accept json
// @Produce json
// @Param login body loginAdminReq true "Login admin"
// @Success 200 {object} loginAdminRes
// @Failure 400 {object} response.APIResponse
// @Failure 401 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /api/v1/auth/admin-login [post]
func (h *Handler) LoginAdmin(c *fiber.Ctx) error {
var req loginAdminReq
if err := c.BodyParser(&req); err != nil {
h.mongoLoggerSvc.Info("Failed to parse LoginAdmin request",
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body"+err.Error())
}
if valErrs, ok := h.validator.Validate(c, req); !ok {
var errMsg string
for field, msg := range valErrs {
errMsg += fmt.Sprintf("%s: %s; ", field, msg)
}
return fiber.NewError(fiber.StatusBadRequest, errMsg)
}
successRes, err := h.authSvc.Login(c.Context(), req.Email, req.PhoneNumber, req.Password)
if err != nil {
switch {
case errors.Is(err, authentication.ErrInvalidPassword), errors.Is(err, authentication.ErrUserNotFound):
h.mongoLoggerSvc.Info("Login attempt failed: Invalid credentials",
zap.Int("status_code", fiber.StatusUnauthorized),
zap.String("email", req.Email),
zap.String("phone", req.PhoneNumber),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusUnauthorized, "Invalid credentials")
case errors.Is(err, authentication.ErrUserSuspended):
h.mongoLoggerSvc.Info("Login attempt failed: User login has been locked",
zap.Int("status_code", fiber.StatusUnauthorized),
zap.String("email", req.Email),
zap.String("phone", req.PhoneNumber),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusUnauthorized, "User login has been locked")
default:
h.mongoLoggerSvc.Error("Login failed",
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusInternalServerError, "Internal server error")
}
}
if successRes.Role == domain.RoleCustomer {
h.mongoLoggerSvc.Warn("Login attempt: admin login of customer",
zap.Int("status_code", fiber.StatusForbidden),
zap.String("role", string(successRes.Role)),
zap.String("email", req.Email),
zap.String("phone", req.PhoneNumber),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusForbidden, "Only admin roles are allowed")
}
accessToken, err := jwtutil.CreateJwt(successRes.UserId, successRes.Role, successRes.CompanyID, h.jwtConfig.JwtAccessKey, h.jwtConfig.JwtAccessExpiry) accessToken, err := jwtutil.CreateJwt(successRes.UserId, successRes.Role, successRes.CompanyID, h.jwtConfig.JwtAccessKey, h.jwtConfig.JwtAccessExpiry)
if err != nil { if err != nil {
h.mongoLoggerSvc.Error("Failed to create access token", h.mongoLoggerSvc.Error("Failed to create access token",

View File

@ -45,6 +45,7 @@ func (h *Handler) CreateBet(c *fiber.Ctx) error {
zap.Int("status_code", fiber.StatusInternalServerError), zap.Int("status_code", fiber.StatusInternalServerError),
zap.Int64("user_id", userID), zap.Int64("user_id", userID),
zap.String("role", string(role)), zap.String("role", string(role)),
zap.Error(err),
zap.Time("timestamp", time.Now()), zap.Time("timestamp", time.Now()),
) )
return fiber.NewError(fiber.StatusInternalServerError, "Failed to create bet:"+err.Error()) return fiber.NewError(fiber.StatusInternalServerError, "Failed to create bet:"+err.Error())
@ -97,6 +98,15 @@ func (h *Handler) CreateBetWithFastCode(c *fiber.Ctx) error {
return fiber.NewError(fiber.StatusBadRequest, "failed to get bet with fast code:"+err.Error()) return fiber.NewError(fiber.StatusBadRequest, "failed to get bet with fast code:"+err.Error())
} }
if bet.UserID == userID {
h.mongoLoggerSvc.Info("User cannot refer himself",
zap.Int64("bet_id", bet.ID),
zap.Int("status_code", fiber.StatusBadRequest),
zap.Time("timestamp", time.Now()),
zap.Error(err),
)
return fiber.NewError(fiber.StatusBadRequest, "User cannot use his own referral code")
}
outcomes, err := h.betSvc.GetBetOutcomeByBetID(c.Context(), bet.ID) outcomes, err := h.betSvc.GetBetOutcomeByBetID(c.Context(), bet.ID)
if err != nil { if err != nil {
h.mongoLoggerSvc.Info("failed to get BetOutcomes by BetID", h.mongoLoggerSvc.Info("failed to get BetOutcomes by BetID",
@ -190,7 +200,7 @@ func (h *Handler) CreateBetInternal(c *fiber.Ctx, req domain.CreateBetReq, userI
zap.Error(err), zap.Error(err),
zap.Time("timestamp", time.Now()), zap.Time("timestamp", time.Now()),
) )
return domain.CreateBetRes{}, fiber.NewError(fiber.StatusBadRequest, err.Error()) return domain.CreateBetRes{}, err
} }
h.mongoLoggerSvc.Error("PlaceBet failed", h.mongoLoggerSvc.Error("PlaceBet failed",
@ -202,7 +212,7 @@ func (h *Handler) CreateBetInternal(c *fiber.Ctx, req domain.CreateBetReq, userI
zap.Time("timestamp", time.Now()), zap.Time("timestamp", time.Now()),
) )
return domain.CreateBetRes{}, fiber.NewError(fiber.StatusInternalServerError, "Unable to create bet") return domain.CreateBetRes{}, err
} }
return res, nil return res, nil
@ -490,6 +500,42 @@ func (h *Handler) GetBetByID(c *fiber.Ctx) error {
return response.WriteJSON(c, fiber.StatusOK, "Bet retrieved successfully", res, nil) return response.WriteJSON(c, fiber.StatusOK, "Bet retrieved successfully", res, nil)
} }
// GetBetByFastCode godoc
// @Summary Gets bet by fast_code
// @Description Gets a single bet by fast_code
// @Tags bet
// @Accept json
// @Produce json
// @Param fast_code path int true "Bet ID"
// @Success 200 {object} domain.BetRes
// @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /api/v1/sport/bet/fastcode/{fast_code} [get]
func (h *Handler) GetBetByFastCode(c *fiber.Ctx) error {
fastCode := c.Params("fast_code")
bet, err := h.betSvc.GetBetByFastCode(c.Context(), fastCode)
if err != nil {
h.mongoLoggerSvc.Info("Failed to get bet by fast code",
zap.String("fast_code", fastCode),
zap.Int("status_code", fiber.StatusNotFound),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusNotFound, "Failed to find bet by fast code")
}
res := domain.ConvertBet(bet)
// h.mongoLoggerSvc.Info("Bet retrieved successfully",
// zap.Int64("betID", id),
// zap.Int("status_code", fiber.StatusOK),
// zap.Time("timestamp", time.Now()),
// )
return response.WriteJSON(c, fiber.StatusOK, "Bet retrieved successfully", res, nil)
}
type UpdateCashOutReq struct { type UpdateCashOutReq struct {
CashedOut bool CashedOut bool
} }

View File

@ -74,6 +74,13 @@ func (h *Handler) GetAllUpcomingEvents(c *fiber.Ctx) error {
Valid: true, Valid: true,
} }
} }
searchQuery := c.Query("query")
searchString := domain.ValidString{
Value: searchQuery,
Valid: searchQuery != "",
}
firstStartTimeQuery := c.Query("first_start_time") firstStartTimeQuery := c.Query("first_start_time")
var firstStartTime domain.ValidTime var firstStartTime domain.ValidTime
if firstStartTimeQuery != "" { if firstStartTimeQuery != "" {
@ -98,7 +105,7 @@ func (h *Handler) GetAllUpcomingEvents(c *fiber.Ctx) error {
if lastStartTimeQuery != "" { if lastStartTimeQuery != "" {
lastStartTimeParsed, err := time.Parse(time.RFC3339, lastStartTimeQuery) lastStartTimeParsed, err := time.Parse(time.RFC3339, lastStartTimeQuery)
if err != nil { if err != nil {
h.mongoLoggerSvc.Info("invalid start_time format", h.mongoLoggerSvc.Info("invalid last_start_time format",
zap.String("last_start_time", lastStartTimeQuery), zap.String("last_start_time", lastStartTimeQuery),
zap.Int("status_code", fiber.StatusBadRequest), zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err), zap.Error(err),
@ -118,12 +125,12 @@ func (h *Handler) GetAllUpcomingEvents(c *fiber.Ctx) error {
Valid: countryCodeQuery != "", Valid: countryCodeQuery != "",
} }
flaggedQuery := c.Query("flagged") isFeaturedQuery := c.Query("is_featured")
var flagged domain.ValidBool var isFeatured domain.ValidBool
if flaggedQuery != "" { if isFeaturedQuery != "" {
flaggedParsed, err := strconv.ParseBool(flaggedQuery) isFeaturedParsed, err := strconv.ParseBool(isFeaturedQuery)
if err != nil { if err != nil {
h.mongoLoggerSvc.Error("Failed to parse flagged", h.mongoLoggerSvc.Error("Failed to parse isFeatured",
zap.Int("status_code", fiber.StatusBadRequest), zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err), zap.Error(err),
zap.Time("timestamp", time.Now()), zap.Time("timestamp", time.Now()),
@ -131,8 +138,8 @@ func (h *Handler) GetAllUpcomingEvents(c *fiber.Ctx) error {
return fiber.NewError(fiber.StatusBadRequest, "Failed to parse is_shop_bet") return fiber.NewError(fiber.StatusBadRequest, "Failed to parse is_shop_bet")
} }
flagged = domain.ValidBool{ isFeatured = domain.ValidBool{
Value: flaggedParsed, Value: isFeaturedParsed,
Valid: true, Valid: true,
} }
} }
@ -141,12 +148,13 @@ func (h *Handler) GetAllUpcomingEvents(c *fiber.Ctx) error {
c.Context(), domain.EventFilter{ c.Context(), domain.EventFilter{
SportID: sportID, SportID: sportID,
LeagueID: leagueID, LeagueID: leagueID,
Query: searchString,
FirstStartTime: firstStartTime, FirstStartTime: firstStartTime,
LastStartTime: lastStartTime, LastStartTime: lastStartTime,
Limit: limit, Limit: limit,
Offset: offset, Offset: offset,
CountryCode: countryCode, CountryCode: countryCode,
Flagged: flagged, Featured: isFeatured,
}) })
// fmt.Printf("League ID: %v", leagueID) // fmt.Printf("League ID: %v", leagueID)
@ -299,13 +307,13 @@ func (h *Handler) SetEventStatusToRemoved(c *fiber.Ctx) error {
} }
type UpdateEventFlaggedReq struct { type UpdateEventFeaturedReq struct {
Flagged bool `json:"flagged" example:"true"` Featured bool `json:"is_featured" example:"true"`
} }
// UpdateEventFlagged godoc // UpdateEventFeatured godoc
// @Summary update the event flagged // @Summary update the event featured
// @Description Update the event flagged // @Description Update the event featured
// @Tags event // @Tags event
// @Accept json // @Accept json
// @Produce json // @Produce json
@ -314,10 +322,10 @@ type UpdateEventFlaggedReq struct {
// @Failure 400 {object} response.APIResponse // @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Router /api/v1/events/{id}/flag [put] // @Router /api/v1/events/{id}/flag [put]
func (h *Handler) UpdateEventFlagged(c *fiber.Ctx) error { func (h *Handler) UpdateEventFeatured(c *fiber.Ctx) error {
eventID := c.Params("id") eventID := c.Params("id")
var req UpdateEventFlaggedReq var req UpdateEventFeaturedReq
if err := c.BodyParser(&req); err != nil { if err := c.BodyParser(&req); err != nil {
h.mongoLoggerSvc.Info("Failed to parse user id", h.mongoLoggerSvc.Info("Failed to parse user id",
@ -335,17 +343,17 @@ func (h *Handler) UpdateEventFlagged(c *fiber.Ctx) error {
for field, msg := range valErrs { for field, msg := range valErrs {
errMsg += fmt.Sprintf("%s: %s; ", field, msg) errMsg += fmt.Sprintf("%s: %s; ", field, msg)
} }
h.mongoLoggerSvc.Error("Failed to update event flagged", h.mongoLoggerSvc.Error("Failed to update event featured",
zap.Any("request", req), zap.Any("request", req),
zap.Int("status_code", fiber.StatusInternalServerError), zap.Int("status_code", fiber.StatusInternalServerError),
zap.Time("timestamp", time.Now()), zap.Time("timestamp", time.Now()),
) )
return fiber.NewError(fiber.StatusBadRequest, errMsg) return fiber.NewError(fiber.StatusBadRequest, errMsg)
} }
err := h.eventSvc.UpdateFlagged(c.Context(), eventID, req.Flagged) err := h.eventSvc.UpdateFeatured(c.Context(), eventID, req.Featured)
if err != nil { if err != nil {
h.mongoLoggerSvc.Error("Failed to update event flagged", h.mongoLoggerSvc.Error("Failed to update event featured",
zap.String("eventID", eventID), zap.String("eventID", eventID),
zap.Int("status_code", fiber.StatusInternalServerError), zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err), zap.Error(err),

View File

@ -69,7 +69,8 @@ func (a *App) initAppRoutes() {
}) })
}) })
// Auth Routes // Auth Routes
groupV1.Post("/auth/login", h.LoginCustomer) groupV1.Post("/auth/customer-login", h.LoginCustomer)
groupV1.Post("/auth/admin-login", h.LoginAdmin)
groupV1.Post("/auth/refresh", h.RefreshToken) groupV1.Post("/auth/refresh", h.RefreshToken)
groupV1.Post("/auth/logout", a.authMiddleware, h.LogOutCustomer) groupV1.Post("/auth/logout", a.authMiddleware, h.LogOutCustomer)
groupV1.Get("/auth/test", a.authMiddleware, func(c *fiber.Ctx) error { groupV1.Get("/auth/test", a.authMiddleware, func(c *fiber.Ctx) error {
@ -153,7 +154,7 @@ func (a *App) initAppRoutes() {
groupV1.Get("/events/:id", h.GetUpcomingEventByID) groupV1.Get("/events/:id", h.GetUpcomingEventByID)
groupV1.Delete("/events/:id", a.authMiddleware, a.SuperAdminOnly, h.SetEventStatusToRemoved) groupV1.Delete("/events/:id", a.authMiddleware, a.SuperAdminOnly, h.SetEventStatusToRemoved)
groupV1.Get("/top-leagues", h.GetTopLeagues) groupV1.Get("/top-leagues", h.GetTopLeagues)
groupV1.Get("/events/:id/flag", h.UpdateEventFlagged) groupV1.Put("/events/:id/featured", h.UpdateEventFeatured)
// Leagues // Leagues
groupV1.Get("/leagues", h.GetAllLeagues) groupV1.Get("/leagues", h.GetAllLeagues)
@ -204,6 +205,7 @@ func (a *App) initAppRoutes() {
// Bet Routes // Bet Routes
groupV1.Post("/sport/bet", a.authMiddleware, h.CreateBet) groupV1.Post("/sport/bet", a.authMiddleware, h.CreateBet)
groupV1.Post("/sport/bet/fastcode", a.authMiddleware, h.CreateBetWithFastCode) groupV1.Post("/sport/bet/fastcode", a.authMiddleware, h.CreateBetWithFastCode)
groupV1.Get("/sport/bet/fastcode/:fast_code", h.GetBetByFastCode)
groupV1.Get("/sport/bet", a.authMiddleware, h.GetAllBet) groupV1.Get("/sport/bet", a.authMiddleware, h.GetAllBet)
groupV1.Get("/sport/bet/:id", h.GetBetByID) groupV1.Get("/sport/bet/:id", h.GetBetByID)
groupV1.Patch("/sport/bet/:id", a.authMiddleware, h.UpdateCashOut) groupV1.Patch("/sport/bet/:id", a.authMiddleware, h.UpdateCashOut)