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/
app_logs/
backup/
reports/

View File

@ -216,7 +216,11 @@ CREATE TABLE IF NOT EXISTS branches (
is_self_owned BOOLEAN NOT NULL DEFAULT false,
created_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 (
id BIGSERIAL PRIMARY KEY,
@ -258,7 +262,8 @@ CREATE TABLE events (
status TEXT,
fetched_at TIMESTAMP DEFAULT now(),
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 (
id SERIAL PRIMARY KEY,
@ -289,7 +294,11 @@ CREATE TABLE companies (
deducted_percentage REAL NOT NULL,
is_active BOOLEAN NOT NULL DEFAULT false,
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 (
id BIGINT PRIMARY KEY,

View File

@ -5,9 +5,10 @@ INSERT INTO branches (
wallet_id,
branch_manager_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 *;
-- name: CreateSupportedOperation :one
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),
is_self_owned = COALESCE(sqlc.narg(is_self_owned), is_self_owned),
is_active = COALESCE(sqlc.narg(is_active), is_active),
profit_percent = COALESCE(sqlc.narg(profit_percent), profit_percent),
updated_at = CURRENT_TIMESTAMP
WHERE id = $1
RETURNING *;

View File

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

View File

@ -18,19 +18,21 @@ INSERT INTO branches (
wallet_id,
branch_manager_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
`
type CreateBranchParams struct {
Name string `json:"name"`
Location string `json:"location"`
WalletID int64 `json:"wallet_id"`
BranchManagerID int64 `json:"branch_manager_id"`
CompanyID int64 `json:"company_id"`
IsSelfOwned bool `json:"is_self_owned"`
Name string `json:"name"`
Location string `json:"location"`
WalletID int64 `json:"wallet_id"`
BranchManagerID int64 `json:"branch_manager_id"`
CompanyID int64 `json:"company_id"`
IsSelfOwned bool `json:"is_self_owned"`
ProfitPercent float32 `json:"profit_percent"`
}
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.CompanyID,
arg.IsSelfOwned,
arg.ProfitPercent,
)
var i Branch
err := row.Scan(
@ -498,19 +501,21 @@ SET name = COALESCE($2, name),
company_id = COALESCE($5, company_id),
is_self_owned = COALESCE($6, is_self_owned),
is_active = COALESCE($7, is_active),
profit_percent = COALESCE($8, profit_percent),
updated_at = CURRENT_TIMESTAMP
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
`
type UpdateBranchParams struct {
ID int64 `json:"id"`
Name pgtype.Text `json:"name"`
Location pgtype.Text `json:"location"`
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"`
ID int64 `json:"id"`
Name pgtype.Text `json:"name"`
Location pgtype.Text `json:"location"`
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"`
ProfitPercent pgtype.Float4 `json:"profit_percent"`
}
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.IsSelfOwned,
arg.IsActive,
arg.ProfitPercent,
)
var i Branch
err := row.Scan(

View File

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

View File

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

View File

@ -93,16 +93,16 @@ 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" validate:"required" example:"1"`
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"`
}
type CreateBetWithFastCodeReq struct {
type CreateBetWithFastCodeReq struct {
FastCode string `json:"fast_code"`
Amount float32 `json:"amount"`
BranchID *int64 `json:"branch_id"`
}
}
type RandomBetReq struct {
BranchID int64 `json:"branch_id" validate:"required" example:"1"`
@ -117,6 +117,7 @@ type CreateBetRes struct {
UserID int64 `json:"user_id" example:"2"`
IsShopBet bool `json:"is_shop_bet" example:"false"`
CreatedNumber int64 `json:"created_number" example:"2"`
FastCode string `json:"fast_code"`
}
type BetRes struct {
ID int64 `json:"id" example:"1"`
@ -140,6 +141,8 @@ func ConvertCreateBet(bet Bet, createdNumber int64) CreateBetRes {
Status: bet.Status,
UserID: bet.UserID,
CreatedNumber: createdNumber,
IsShopBet: bet.IsShopBet,
FastCode: bet.FastCode,
}
}

View File

@ -1,14 +1,20 @@
package domain
import (
dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
"github.com/jackc/pgx/v5/pgtype"
)
type Branch struct {
ID int64
Name string
Location string
WalletID int64
BranchManagerID int64
CompanyID int64
IsActive bool
IsSelfOwned bool
ID int64
Name string
Location string
WalletID int64
BranchManagerID int64
CompanyID int64
IsActive bool
IsSelfOwned bool
ProfitPercentage float32
}
type BranchLocation struct {
@ -38,6 +44,7 @@ type BranchDetail struct {
ManagerName string
ManagerPhoneNumber string
WalletIsActive bool
ProfitPercentage float32
}
type SupportedOperation struct {
@ -53,22 +60,24 @@ type BranchOperation struct {
}
type CreateBranch struct {
Name string
Location string
WalletID int64
BranchManagerID int64
CompanyID int64
IsSelfOwned bool
Name string
Location string
WalletID int64
BranchManagerID int64
CompanyID int64
IsSelfOwned bool
ProfitPercentage float32
}
type UpdateBranch struct {
ID int64
Name *string
Location *string
BranchManagerID *int64
CompanyID *int64
IsSelfOwned *bool
IsActive *bool
ID int64
Name *string
Location *string
BranchManagerID *int64
CompanyID *int64
IsSelfOwned *bool
IsActive *bool
ProfitPercentage *float32
}
type CreateSupportedOperation struct {
@ -81,21 +90,23 @@ type CreateBranchOperation struct {
}
type CreateBranchReq struct {
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"`
BranchManagerID int64 `json:"branch_manager_id" validate:"required,gt=0" example:"1"`
CompanyID *int64 `json:"company_id,omitempty" example:"1"`
IsSelfOwned *bool `json:"is_self_owned,omitempty" example:"false"`
Operations []int64 `json:"operations" validate:"required,dive,gt=0"`
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"`
BranchManagerID int64 `json:"branch_manager_id" validate:"required,gt=0" example:"1"`
ProfitPercentage float32 `json:"profit_percentage" example:"0.1" validate:"lt=1" `
CompanyID *int64 `json:"company_id,omitempty" example:"1"`
IsSelfOwned *bool `json:"is_self_owned,omitempty" example:"false"`
Operations []int64 `json:"operations" validate:"required,dive,gt=0"`
}
type UpdateBranchReq struct {
Name *string `json:"name,omitempty" example:"4-kilo Branch"`
Location *string `json:"location,omitempty" example:"Addis Ababa"`
BranchManagerID *int64 `json:"branch_manager_id,omitempty" example:"1"`
CompanyID *int64 `json:"company_id,omitempty" example:"1"`
IsSelfOwned *bool `json:"is_self_owned,omitempty" example:"false"`
IsActive *bool `json:"is_active,omitempty" example:"false"`
Name *string `json:"name,omitempty" example:"4-kilo Branch"`
Location *string `json:"location,omitempty" example:"Addis Ababa"`
BranchManagerID *int64 `json:"branch_manager_id,omitempty" example:"1"`
CompanyID *int64 `json:"company_id,omitempty" example:"1"`
IsSelfOwned *bool `json:"is_self_owned,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 {
@ -120,14 +131,15 @@ type BranchOperationRes struct {
}
type BranchRes struct {
ID int64 `json:"id" example:"1"`
Name string `json:"name" example:"4-kilo Branch"`
Location string `json:"location" example:"Addis Ababa"`
WalletID int64 `json:"wallet_id" example:"1"`
BranchManagerID int64 `json:"branch_manager_id" example:"1"`
CompanyID int64 `json:"company_id" example:"1"`
IsSelfOwned bool `json:"is_self_owned" example:"false"`
IsActive bool `json:"is_active" example:"false"`
ID int64 `json:"id" example:"1"`
Name string `json:"name" example:"4-kilo Branch"`
Location string `json:"location" example:"Addis Ababa"`
WalletID int64 `json:"wallet_id" example:"1"`
BranchManagerID int64 `json:"branch_manager_id" example:"1"`
CompanyID int64 `json:"company_id" example:"1"`
IsSelfOwned bool `json:"is_self_owned" example:"false"`
IsActive bool `json:"is_active" example:"false"`
ProfitPercentage float32 `json:"profit_percentage" example:"0.1"`
}
type BranchDetailRes struct {
@ -143,18 +155,20 @@ type BranchDetailRes struct {
Balance float32 `json:"balance" example:"100.5"`
IsActive bool `json:"is_active" example:"false"`
WalletIsActive bool `json:"is_wallet_active" example:"false"`
ProfitPercentage float32 `json:"profit_percentage" example:"0.1"`
}
func ConvertBranch(branch Branch) BranchRes {
return BranchRes{
ID: branch.ID,
Name: branch.Name,
Location: branch.Location,
WalletID: branch.WalletID,
BranchManagerID: branch.BranchManagerID,
CompanyID: branch.CompanyID,
IsSelfOwned: branch.IsSelfOwned,
IsActive: branch.IsActive,
ID: branch.ID,
Name: branch.Name,
Location: branch.Location,
WalletID: branch.WalletID,
BranchManagerID: branch.BranchManagerID,
CompanyID: branch.CompanyID,
IsSelfOwned: branch.IsSelfOwned,
IsActive: branch.IsActive,
ProfitPercentage: branch.ProfitPercentage,
}
}
@ -172,5 +186,103 @@ func ConvertBranchDetail(branch BranchDetail) BranchDetailRes {
Balance: branch.Balance.Float32(),
IsActive: branch.IsActive,
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
Source string `json:"source"` // bet api provider (bet365, betfair)
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 {
EventID string
@ -120,6 +121,7 @@ type Odds struct {
}
type EventFilter struct {
Query ValidString
SportID ValidInt32
LeagueID ValidInt32
CountryCode ValidString
@ -128,5 +130,5 @@ type EventFilter struct {
Limit ValidInt64
Offset ValidInt64
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"
)
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) {
dbBranch, err := s.queries.CreateBranch(ctx, convertCreateBranch(branch))
dbBranch, err := s.queries.CreateBranch(ctx, domain.ConvertCreateBranch(branch))
if err != nil {
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) {
@ -110,7 +25,7 @@ func (s *Store) GetBranchByID(ctx context.Context, id int64) (domain.BranchDetai
if err != nil {
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) {
@ -120,7 +35,7 @@ func (s *Store) GetBranchByManagerID(ctx context.Context, branchManagerID int64)
}
var branches []domain.BranchDetail = make([]domain.BranchDetail, 0, len(dbBranches))
for _, dbBranch := range dbBranches {
branches = append(branches, convertDBBranchDetail(dbBranch))
branches = append(branches, domain.ConvertDBBranchDetail(dbBranch))
}
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))
for _, dbBranch := range dbBranches {
branches = append(branches, convertDBBranchDetail(dbBranch))
branches = append(branches, domain.ConvertDBBranchDetail(dbBranch))
}
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))
for _, dbBranch := range dbBranches {
branches = append(branches, convertDBBranchDetail(dbBranch))
branches = append(branches, domain.ConvertDBBranchDetail(dbBranch))
}
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))
for _, dbBranch := range dbBranches {
branches = append(branches, convertDBBranchDetail(dbBranch))
branches = append(branches, domain.ConvertDBBranchDetail(dbBranch))
}
return branches, nil
}
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 {
return domain.Branch{}, err
}
return convertDBBranch(dbBranch), nil
return domain.ConvertDBBranch(dbBranch), nil
}
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 convertDBBranch(branch), err
return domain.ConvertDBBranch(branch), err
}
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(),
Source: e.Source.String,
Status: domain.EventStatus(e.Status.String),
Flagged: e.Flagged,
IsFeatured: e.IsFeatured,
}
}
return upcomingEvents, nil
@ -122,7 +122,8 @@ func (s *Store) GetExpiredUpcomingEvents(ctx context.Context, filter domain.Even
StartTime: e.StartTime.Time.UTC(),
Source: e.Source.String,
Status: domain.EventStatus(e.Status.String),
Flagged: e.Flagged,
IsFeatured: e.IsFeatured,
IsActive: e.IsActive,
}
}
return upcomingEvents, nil
@ -139,6 +140,10 @@ func (s *Store) GetPaginatedUpcomingEvents(ctx context.Context, filter domain.Ev
Int32: int32(filter.SportID.Value),
Valid: filter.SportID.Valid,
},
Query: pgtype.Text{
String: filter.Query.Value,
Valid: filter.Query.Valid,
},
Limit: pgtype.Int4{
Int32: int32(filter.Limit.Value),
Valid: filter.Limit.Valid,
@ -159,9 +164,9 @@ func (s *Store) GetPaginatedUpcomingEvents(ctx context.Context, filter domain.Ev
String: filter.CountryCode.Value,
Valid: filter.CountryCode.Valid,
},
Flagged: pgtype.Bool{
Bool: filter.Flagged.Valid,
Valid: filter.Flagged.Valid,
IsFeatured: pgtype.Bool{
Bool: filter.Featured.Valid,
Valid: filter.Featured.Valid,
},
})
@ -186,7 +191,8 @@ func (s *Store) GetPaginatedUpcomingEvents(ctx context.Context, filter domain.Ev
StartTime: e.StartTime.Time.UTC(),
Source: e.Source.String,
Status: domain.EventStatus(e.Status.String),
Flagged: e.Flagged,
IsFeatured: e.IsFeatured,
IsActive: e.IsActive,
}
}
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),
Valid: filter.SportID.Valid,
},
Query: pgtype.Text{
String: filter.Query.Value,
Valid: filter.Query.Valid,
},
FirstStartTime: pgtype.Timestamp{
Time: filter.FirstStartTime.Value.UTC(),
Valid: filter.FirstStartTime.Valid,
@ -210,9 +220,9 @@ func (s *Store) GetPaginatedUpcomingEvents(ctx context.Context, filter domain.Ev
String: filter.CountryCode.Value,
Valid: filter.CountryCode.Valid,
},
Flagged: pgtype.Bool{
Bool: filter.Flagged.Valid,
Valid: filter.Flagged.Valid,
IsFeatured: pgtype.Bool{
Bool: filter.Featured.Valid,
Valid: filter.Featured.Valid,
},
})
if err != nil {
@ -244,7 +254,7 @@ func (s *Store) GetUpcomingEventByID(ctx context.Context, ID string) (domain.Upc
StartTime: event.StartTime.Time.UTC(),
Source: event.Source.String,
Status: domain.EventStatus(event.Status.String),
Flagged: event.Flagged,
IsFeatured: event.IsFeatured,
}, nil
}
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 {
return s.queries.UpdateFlagged(ctx, dbgen.UpdateFlaggedParams{
func (s *Store) UpdateFeatured(ctx context.Context, eventID string, isFeatured bool) error {
return s.queries.UpdateFeatured(ctx, dbgen.UpdateFeaturedParams{
ID: eventID,
Flagged: flagged,
IsFeatured: isFeatured,
})
}

View File

@ -16,5 +16,5 @@ type Service interface {
// GetAndStoreMatchResult(ctx context.Context, eventID string) error
UpdateFinalScore(ctx context.Context, eventID, fullScore 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)
}
func (s *service) UpdateFlagged(ctx context.Context, eventID string, flagged bool) error {
return s.store.UpdateFlagged(ctx, eventID, flagged)
func (s *service) UpdateFeatured(ctx context.Context, eventID string, flagged bool) error {
return s.store.UpdateFeatured(ctx, eventID, flagged)
}
// func (s *service) GetAndStoreMatchResult(ctx context.Context, eventID string) error {

View File

@ -5,6 +5,7 @@ import (
"fmt"
"time"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/authentication"
jwtutil "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/jwt"
"github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response"
@ -37,7 +38,7 @@ type loginCustomerRes struct {
// @Failure 400 {object} response.APIResponse
// @Failure 401 {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 {
var req loginCustomerReq
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)
if err != nil {
switch {
case errors.Is(err, authentication.ErrInvalidPassword), errors.Is(err, authentication.ErrUserNotFound):
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)
if err != nil {
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.Int64("user_id", userID),
zap.String("role", string(role)),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
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())
}
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)
if err != nil {
h.mongoLoggerSvc.Info("failed to get BetOutcomes by BetID",
@ -118,7 +128,7 @@ func (h *Handler) CreateBetWithFastCode(c *fiber.Ctx) error {
}
// This can be for both online and offline bets
// If bet is an online bet (if the customer role creates the bet on their own)
// If bet is an online bet (if the customer role creates the bet on their own)
// then the branchID is null
newReq := domain.CreateBetReq{
Amount: req.Amount,
@ -190,7 +200,7 @@ func (h *Handler) CreateBetInternal(c *fiber.Ctx, req domain.CreateBetReq, userI
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return domain.CreateBetRes{}, fiber.NewError(fiber.StatusBadRequest, err.Error())
return domain.CreateBetRes{}, err
}
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()),
)
return domain.CreateBetRes{}, fiber.NewError(fiber.StatusInternalServerError, "Unable to create bet")
return domain.CreateBetRes{}, err
}
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)
}
// 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 {
CashedOut bool
}

View File

@ -74,6 +74,13 @@ func (h *Handler) GetAllUpcomingEvents(c *fiber.Ctx) error {
Valid: true,
}
}
searchQuery := c.Query("query")
searchString := domain.ValidString{
Value: searchQuery,
Valid: searchQuery != "",
}
firstStartTimeQuery := c.Query("first_start_time")
var firstStartTime domain.ValidTime
if firstStartTimeQuery != "" {
@ -98,7 +105,7 @@ func (h *Handler) GetAllUpcomingEvents(c *fiber.Ctx) error {
if lastStartTimeQuery != "" {
lastStartTimeParsed, err := time.Parse(time.RFC3339, lastStartTimeQuery)
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.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
@ -118,12 +125,12 @@ func (h *Handler) GetAllUpcomingEvents(c *fiber.Ctx) error {
Valid: countryCodeQuery != "",
}
flaggedQuery := c.Query("flagged")
var flagged domain.ValidBool
if flaggedQuery != "" {
flaggedParsed, err := strconv.ParseBool(flaggedQuery)
isFeaturedQuery := c.Query("is_featured")
var isFeatured domain.ValidBool
if isFeaturedQuery != "" {
isFeaturedParsed, err := strconv.ParseBool(isFeaturedQuery)
if err != nil {
h.mongoLoggerSvc.Error("Failed to parse flagged",
h.mongoLoggerSvc.Error("Failed to parse isFeatured",
zap.Int("status_code", fiber.StatusBadRequest),
zap.Error(err),
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")
}
flagged = domain.ValidBool{
Value: flaggedParsed,
isFeatured = domain.ValidBool{
Value: isFeaturedParsed,
Valid: true,
}
}
@ -141,12 +148,13 @@ func (h *Handler) GetAllUpcomingEvents(c *fiber.Ctx) error {
c.Context(), domain.EventFilter{
SportID: sportID,
LeagueID: leagueID,
Query: searchString,
FirstStartTime: firstStartTime,
LastStartTime: lastStartTime,
Limit: limit,
Offset: offset,
CountryCode: countryCode,
Flagged: flagged,
Featured: isFeatured,
})
// fmt.Printf("League ID: %v", leagueID)
@ -299,13 +307,13 @@ func (h *Handler) SetEventStatusToRemoved(c *fiber.Ctx) error {
}
type UpdateEventFlaggedReq struct {
Flagged bool `json:"flagged" example:"true"`
type UpdateEventFeaturedReq struct {
Featured bool `json:"is_featured" example:"true"`
}
// UpdateEventFlagged godoc
// @Summary update the event flagged
// @Description Update the event flagged
// UpdateEventFeatured godoc
// @Summary update the event featured
// @Description Update the event featured
// @Tags event
// @Accept json
// @Produce json
@ -314,10 +322,10 @@ type UpdateEventFlaggedReq struct {
// @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @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")
var req UpdateEventFlaggedReq
var req UpdateEventFeaturedReq
if err := c.BodyParser(&req); err != nil {
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 {
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.Int("status_code", fiber.StatusInternalServerError),
zap.Time("timestamp", time.Now()),
)
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 {
h.mongoLoggerSvc.Error("Failed to update event flagged",
h.mongoLoggerSvc.Error("Failed to update event featured",
zap.String("eventID", eventID),
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),

View File

@ -69,7 +69,8 @@ func (a *App) initAppRoutes() {
})
})
// 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/logout", a.authMiddleware, h.LogOutCustomer)
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.Delete("/events/:id", a.authMiddleware, a.SuperAdminOnly, h.SetEventStatusToRemoved)
groupV1.Get("/top-leagues", h.GetTopLeagues)
groupV1.Get("/events/:id/flag", h.UpdateEventFlagged)
groupV1.Put("/events/:id/featured", h.UpdateEventFeatured)
// Leagues
groupV1.Get("/leagues", h.GetAllLeagues)
@ -161,7 +162,7 @@ func (a *App) initAppRoutes() {
groupV1.Put("/leagues/:id/featured", h.SetLeagueFeatured)
groupV1.Get("/result/:id", h.GetResultsByEventID)
// Branch
groupV1.Post("/branch", a.authMiddleware, h.CreateBranch)
groupV1.Get("/branch", a.authMiddleware, h.GetAllBranches)
@ -171,10 +172,10 @@ func (a *App) initAppRoutes() {
groupV1.Put("/branch/:id/set-active", a.authMiddleware, h.UpdateBranchStatus)
groupV1.Put("/branch/:id/set-inactive", a.authMiddleware, h.UpdateBranchStatus)
groupV1.Delete("/branch/:id", a.authMiddleware, h.DeleteBranch)
groupV1.Get("/search/branch", a.authMiddleware, h.SearchBranch)
groupV1.Get("/branchLocation", a.authMiddleware, h.GetAllBranchLocations)
groupV1.Get("/branch/:id/cashiers", a.authMiddleware, h.GetBranchCashiers)
groupV1.Get("/branchCashier", a.authMiddleware, h.GetBranchForCashier)
@ -204,6 +205,7 @@ func (a *App) initAppRoutes() {
// Bet Routes
groupV1.Post("/sport/bet", a.authMiddleware, h.CreateBet)
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/:id", h.GetBetByID)
groupV1.Patch("/sport/bet/:id", a.authMiddleware, h.UpdateCashOut)