fix: result log and notification

This commit is contained in:
Samuel Tariku 2025-07-30 16:55:57 +03:00
parent 7d8d824a94
commit 3fb3da6cc8
16 changed files with 644 additions and 159 deletions

1
.gitignore vendored
View File

@ -9,3 +9,4 @@ logs/
app_logs/ app_logs/
backup/ backup/
reports/ reports/
exports/

View File

@ -264,6 +264,7 @@ CREATE TABLE events (
fetched_at TIMESTAMP DEFAULT now(), fetched_at TIMESTAMP DEFAULT now(),
source TEXT DEFAULT 'b365api', source TEXT DEFAULT 'b365api',
is_featured BOOLEAN NOT NULL DEFAULT FALSE, is_featured BOOLEAN NOT NULL DEFAULT FALSE,
is_monitorred BOOLEAN NOT NULL DEFAULT FALSE,
is_active BOOLEAN NOT NULL DEFAULT TRUE is_active BOOLEAN NOT NULL DEFAULT TRUE
); );
CREATE TABLE odds ( CREATE TABLE odds (
@ -287,6 +288,22 @@ CREATE TABLE odds (
UNIQUE (event_id, market_id, name, handicap), UNIQUE (event_id, market_id, name, handicap),
UNIQUE (event_id, market_id) UNIQUE (event_id, market_id)
); );
CREATE TABLE result_log (
id BIGSERIAL PRIMARY KEY,
status_not_finished_count INT NOT NULL,
status_not_finished_bets INT NOT NULL,
status_to_be_fixed_count INT NOT NULL,
status_to_be_fixed_bets INT NOT NULL,
status_postponed_count INT NOT NULL,
status_postponed_bets INT NOT NULL,
status_ended_count INT NOT NULL,
status_ended_bets INT NOT NULL,
status_removed_count INT NOT NULL,
status_removed_bets INT NOT NULL,
removed_count INT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE companies ( CREATE TABLE companies (
id BIGSERIAL PRIMARY KEY, id BIGSERIAL PRIMARY KEY,
name TEXT NOT NULL, name TEXT NOT NULL,

View File

@ -17,3 +17,55 @@ WHERE (
) )
GROUP BY month GROUP BY month
ORDER BY month; ORDER BY month;
-- name: GetLeagueEventStat :many
SELECT leagues.id,
leagues.name,
COUNT(*) AS total_events,
COUNT(*) FILTER (
WHERE events.status = 'pending'
) AS pending,
COUNT(*) FILTER (
WHERE events.status = 'in_play'
) AS in_play,
COUNT(*) FILTER (
WHERE events.status = 'to_be_fixed'
) AS to_be_fixed,
COUNT(*) FILTER (
WHERE events.status = 'ended'
) AS ended,
COUNT(*) FILTER (
WHERE events.status = 'postponed'
) AS postponed,
COUNT(*) FILTER (
WHERE events.status = 'cancelled'
) AS cancelled,
COUNT(*) FILTER (
WHERE events.status = 'walkover'
) AS walkover,
COUNT(*) FILTER (
WHERE events.status = 'interrupted'
) AS interrupted,
COUNT(*) FILTER (
WHERE events.status = 'abandoned'
) AS abandoned,
COUNT(*) FILTER (
WHERE events.status = 'retired'
) AS retired,
COUNT(*) FILTER (
WHERE events.status = 'suspended'
) AS suspended,
COUNT(*) FILTER (
WHERE events.status = 'decided_by_fa'
) AS decided_by_fa,
COUNT(*) FILTER (
WHERE events.status = 'removed'
) AS removed
FROM leagues
JOIN events ON leagues.id = events.league_id
WHERE (
leagues.is_featured = sqlc.narg('is_league_featured')
OR sqlc.narg('is_league_featured') IS NULL
)
GROUP BY leagues.id,
leagues.name;

28
db/query/result_log.sql Normal file
View File

@ -0,0 +1,28 @@
-- name: CreateResultLog :one
INSERT INTO result_log (
status_not_finished_count,
status_not_finished_bets,
status_to_be_fixed_count,
status_to_be_fixed_bets,
status_postponed_count,
status_postponed_bets,
status_ended_count,
status_ended_bets,
status_removed_count,
status_removed_bets,
removed_count
)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
RETURNING *;
-- name: GetAllResultLog :many
SELECT *
FROM result_log
WHERE (
created_at < sqlc.narg('created_before')
OR sqlc.narg('created_before') IS NULL
)
AND (
created_at > sqlc.narg('created_after')
OR sqlc.narg('created_after') IS NULL
)
ORDER BY created_at DESC;

View File

@ -18,6 +18,7 @@ services:
retries: 5 retries: 5
volumes: volumes:
- postgres_data:/var/lib/postgresql/data - postgres_data:/var/lib/postgresql/data
- ./exports:/exports
mongo: mongo:
container_name: fortunebet-mongo container_name: fortunebet-mongo

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, is_featured, is_active 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_monitorred, is_active
FROM events FROM events
WHERE start_time > now() WHERE start_time > now()
AND is_live = false AND is_live = false
@ -63,6 +63,7 @@ func (q *Queries) GetAllUpcomingEvents(ctx context.Context) ([]Event, error) {
&i.FetchedAt, &i.FetchedAt,
&i.Source, &i.Source,
&i.IsFeatured, &i.IsFeatured,
&i.IsMonitorred,
&i.IsActive, &i.IsActive,
); err != nil { ); err != nil {
return nil, err return nil, err
@ -76,7 +77,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.is_featured, events.is_active, 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_monitorred, 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
@ -112,6 +113,7 @@ type GetExpiredUpcomingEventsRow struct {
FetchedAt pgtype.Timestamp `json:"fetched_at"` FetchedAt pgtype.Timestamp `json:"fetched_at"`
Source pgtype.Text `json:"source"` Source pgtype.Text `json:"source"`
IsFeatured bool `json:"is_featured"` IsFeatured bool `json:"is_featured"`
IsMonitorred bool `json:"is_monitorred"`
IsActive bool `json:"is_active"` IsActive bool `json:"is_active"`
LeagueCc_2 pgtype.Text `json:"league_cc_2"` LeagueCc_2 pgtype.Text `json:"league_cc_2"`
} }
@ -149,6 +151,7 @@ func (q *Queries) GetExpiredUpcomingEvents(ctx context.Context, status pgtype.Te
&i.FetchedAt, &i.FetchedAt,
&i.Source, &i.Source,
&i.IsFeatured, &i.IsFeatured,
&i.IsMonitorred,
&i.IsActive, &i.IsActive,
&i.LeagueCc_2, &i.LeagueCc_2,
); err != nil { ); err != nil {
@ -163,7 +166,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.is_featured, events.is_active, 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_monitorred, 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
@ -239,6 +242,7 @@ type GetPaginatedUpcomingEventsRow struct {
FetchedAt pgtype.Timestamp `json:"fetched_at"` FetchedAt pgtype.Timestamp `json:"fetched_at"`
Source pgtype.Text `json:"source"` Source pgtype.Text `json:"source"`
IsFeatured bool `json:"is_featured"` IsFeatured bool `json:"is_featured"`
IsMonitorred bool `json:"is_monitorred"`
IsActive bool `json:"is_active"` IsActive bool `json:"is_active"`
LeagueCc_2 pgtype.Text `json:"league_cc_2"` LeagueCc_2 pgtype.Text `json:"league_cc_2"`
} }
@ -286,6 +290,7 @@ func (q *Queries) GetPaginatedUpcomingEvents(ctx context.Context, arg GetPaginat
&i.FetchedAt, &i.FetchedAt,
&i.Source, &i.Source,
&i.IsFeatured, &i.IsFeatured,
&i.IsMonitorred,
&i.IsActive, &i.IsActive,
&i.LeagueCc_2, &i.LeagueCc_2,
); err != nil { ); err != nil {
@ -362,7 +367,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, is_featured, is_active 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_monitorred, is_active
FROM events FROM events
WHERE id = $1 WHERE id = $1
AND is_live = false AND is_live = false
@ -397,6 +402,7 @@ func (q *Queries) GetUpcomingByID(ctx context.Context, id string) (Event, error)
&i.FetchedAt, &i.FetchedAt,
&i.Source, &i.Source,
&i.IsFeatured, &i.IsFeatured,
&i.IsMonitorred,
&i.IsActive, &i.IsActive,
) )
return i, err return i, err

View File

@ -11,6 +11,115 @@ import (
"github.com/jackc/pgx/v5/pgtype" "github.com/jackc/pgx/v5/pgtype"
) )
const GetLeagueEventStat = `-- name: GetLeagueEventStat :many
SELECT leagues.id,
leagues.name,
COUNT(*) AS total_events,
COUNT(*) FILTER (
WHERE events.status = 'pending'
) AS pending,
COUNT(*) FILTER (
WHERE events.status = 'in_play'
) AS in_play,
COUNT(*) FILTER (
WHERE events.status = 'to_be_fixed'
) AS to_be_fixed,
COUNT(*) FILTER (
WHERE events.status = 'ended'
) AS ended,
COUNT(*) FILTER (
WHERE events.status = 'postponed'
) AS postponed,
COUNT(*) FILTER (
WHERE events.status = 'cancelled'
) AS cancelled,
COUNT(*) FILTER (
WHERE events.status = 'walkover'
) AS walkover,
COUNT(*) FILTER (
WHERE events.status = 'interrupted'
) AS interrupted,
COUNT(*) FILTER (
WHERE events.status = 'abandoned'
) AS abandoned,
COUNT(*) FILTER (
WHERE events.status = 'retired'
) AS retired,
COUNT(*) FILTER (
WHERE events.status = 'suspended'
) AS suspended,
COUNT(*) FILTER (
WHERE events.status = 'decided_by_fa'
) AS decided_by_fa,
COUNT(*) FILTER (
WHERE events.status = 'removed'
) AS removed
FROM leagues
JOIN events ON leagues.id = events.league_id
WHERE (
leagues.is_featured = $1
OR $1 IS NULL
)
GROUP BY leagues.id,
leagues.name
`
type GetLeagueEventStatRow struct {
ID int64 `json:"id"`
Name string `json:"name"`
TotalEvents int64 `json:"total_events"`
Pending int64 `json:"pending"`
InPlay int64 `json:"in_play"`
ToBeFixed int64 `json:"to_be_fixed"`
Ended int64 `json:"ended"`
Postponed int64 `json:"postponed"`
Cancelled int64 `json:"cancelled"`
Walkover int64 `json:"walkover"`
Interrupted int64 `json:"interrupted"`
Abandoned int64 `json:"abandoned"`
Retired int64 `json:"retired"`
Suspended int64 `json:"suspended"`
DecidedByFa int64 `json:"decided_by_fa"`
Removed int64 `json:"removed"`
}
func (q *Queries) GetLeagueEventStat(ctx context.Context, isLeagueFeatured pgtype.Bool) ([]GetLeagueEventStatRow, error) {
rows, err := q.db.Query(ctx, GetLeagueEventStat, isLeagueFeatured)
if err != nil {
return nil, err
}
defer rows.Close()
var items []GetLeagueEventStatRow
for rows.Next() {
var i GetLeagueEventStatRow
if err := rows.Scan(
&i.ID,
&i.Name,
&i.TotalEvents,
&i.Pending,
&i.InPlay,
&i.ToBeFixed,
&i.Ended,
&i.Postponed,
&i.Cancelled,
&i.Walkover,
&i.Interrupted,
&i.Abandoned,
&i.Retired,
&i.Suspended,
&i.DecidedByFa,
&i.Removed,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const GetTotalMontlyEventStat = `-- name: GetTotalMontlyEventStat :many const GetTotalMontlyEventStat = `-- name: GetTotalMontlyEventStat :many
SELECT DATE_TRUNC('month', start_time) AS month, SELECT DATE_TRUNC('month', start_time) AS month,
COUNT(*) AS event_count COUNT(*) AS event_count

View File

@ -258,6 +258,7 @@ type Event struct {
FetchedAt pgtype.Timestamp `json:"fetched_at"` FetchedAt pgtype.Timestamp `json:"fetched_at"`
Source pgtype.Text `json:"source"` Source pgtype.Text `json:"source"`
IsFeatured bool `json:"is_featured"` IsFeatured bool `json:"is_featured"`
IsMonitorred bool `json:"is_monitorred"`
IsActive bool `json:"is_active"` IsActive bool `json:"is_active"`
} }
@ -408,6 +409,23 @@ type Result struct {
UpdatedAt pgtype.Timestamp `json:"updated_at"` UpdatedAt pgtype.Timestamp `json:"updated_at"`
} }
type ResultLog struct {
ID int64 `json:"id"`
StatusNotFinishedCount int32 `json:"status_not_finished_count"`
StatusNotFinishedBets int32 `json:"status_not_finished_bets"`
StatusToBeFixedCount int32 `json:"status_to_be_fixed_count"`
StatusToBeFixedBets int32 `json:"status_to_be_fixed_bets"`
StatusPostponedCount int32 `json:"status_postponed_count"`
StatusPostponedBets int32 `json:"status_postponed_bets"`
StatusEndedCount int32 `json:"status_ended_count"`
StatusEndedBets int32 `json:"status_ended_bets"`
StatusRemovedCount int32 `json:"status_removed_count"`
StatusRemovedBets int32 `json:"status_removed_bets"`
RemovedCount int32 `json:"removed_count"`
CreatedAt pgtype.Timestamp `json:"created_at"`
UpdatedAt pgtype.Timestamp `json:"updated_at"`
}
type Setting struct { type Setting struct {
Key string `json:"key"` Key string `json:"key"`
Value string `json:"value"` Value string `json:"value"`

132
gen/db/result_log.sql.go Normal file
View File

@ -0,0 +1,132 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.29.0
// source: result_log.sql
package dbgen
import (
"context"
"github.com/jackc/pgx/v5/pgtype"
)
const CreateResultLog = `-- name: CreateResultLog :one
INSERT INTO result_log (
status_not_finished_count,
status_not_finished_bets,
status_to_be_fixed_count,
status_to_be_fixed_bets,
status_postponed_count,
status_postponed_bets,
status_ended_count,
status_ended_bets,
status_removed_count,
status_removed_bets,
removed_count
)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
RETURNING id, status_not_finished_count, status_not_finished_bets, status_to_be_fixed_count, status_to_be_fixed_bets, status_postponed_count, status_postponed_bets, status_ended_count, status_ended_bets, status_removed_count, status_removed_bets, removed_count, created_at, updated_at
`
type CreateResultLogParams struct {
StatusNotFinishedCount int32 `json:"status_not_finished_count"`
StatusNotFinishedBets int32 `json:"status_not_finished_bets"`
StatusToBeFixedCount int32 `json:"status_to_be_fixed_count"`
StatusToBeFixedBets int32 `json:"status_to_be_fixed_bets"`
StatusPostponedCount int32 `json:"status_postponed_count"`
StatusPostponedBets int32 `json:"status_postponed_bets"`
StatusEndedCount int32 `json:"status_ended_count"`
StatusEndedBets int32 `json:"status_ended_bets"`
StatusRemovedCount int32 `json:"status_removed_count"`
StatusRemovedBets int32 `json:"status_removed_bets"`
RemovedCount int32 `json:"removed_count"`
}
func (q *Queries) CreateResultLog(ctx context.Context, arg CreateResultLogParams) (ResultLog, error) {
row := q.db.QueryRow(ctx, CreateResultLog,
arg.StatusNotFinishedCount,
arg.StatusNotFinishedBets,
arg.StatusToBeFixedCount,
arg.StatusToBeFixedBets,
arg.StatusPostponedCount,
arg.StatusPostponedBets,
arg.StatusEndedCount,
arg.StatusEndedBets,
arg.StatusRemovedCount,
arg.StatusRemovedBets,
arg.RemovedCount,
)
var i ResultLog
err := row.Scan(
&i.ID,
&i.StatusNotFinishedCount,
&i.StatusNotFinishedBets,
&i.StatusToBeFixedCount,
&i.StatusToBeFixedBets,
&i.StatusPostponedCount,
&i.StatusPostponedBets,
&i.StatusEndedCount,
&i.StatusEndedBets,
&i.StatusRemovedCount,
&i.StatusRemovedBets,
&i.RemovedCount,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}
const GetAllResultLog = `-- name: GetAllResultLog :many
SELECT id, status_not_finished_count, status_not_finished_bets, status_to_be_fixed_count, status_to_be_fixed_bets, status_postponed_count, status_postponed_bets, status_ended_count, status_ended_bets, status_removed_count, status_removed_bets, removed_count, created_at, updated_at
FROM result_log
WHERE (
created_at < $1
OR $1 IS NULL
)
AND (
created_at > $2
OR $2 IS NULL
)
ORDER BY created_at DESC
`
type GetAllResultLogParams struct {
CreatedBefore pgtype.Timestamp `json:"created_before"`
CreatedAfter pgtype.Timestamp `json:"created_after"`
}
func (q *Queries) GetAllResultLog(ctx context.Context, arg GetAllResultLogParams) ([]ResultLog, error) {
rows, err := q.db.Query(ctx, GetAllResultLog, arg.CreatedBefore, arg.CreatedAfter)
if err != nil {
return nil, err
}
defer rows.Close()
var items []ResultLog
for rows.Next() {
var i ResultLog
if err := rows.Scan(
&i.ID,
&i.StatusNotFinishedCount,
&i.StatusNotFinishedBets,
&i.StatusToBeFixedCount,
&i.StatusToBeFixedBets,
&i.StatusPostponedCount,
&i.StatusPostponedBets,
&i.StatusEndedCount,
&i.StatusEndedBets,
&i.StatusRemovedCount,
&i.StatusRemovedBets,
&i.RemovedCount,
&i.CreatedAt,
&i.UpdatedAt,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}

View File

@ -2,6 +2,8 @@ package domain
import ( import (
"time" "time"
dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
) )
type MarketConfig struct { type MarketConfig struct {
@ -83,22 +85,78 @@ const (
TIME_STATUS_REMOVED TimeStatus = 99 TIME_STATUS_REMOVED TimeStatus = 99
) )
type ResultStatusCounts struct { type ResultLog struct {
IsNotFinished int `json:"is_not_finished"` ID int64 `json:"id"`
IsNotFinishedBets int `json:"is_not_finished_bets"` StatusNotFinishedCount int `json:"status_not_finished_count"`
IsToBeFixed int `json:"is_to_be_fixed"` StatusNotFinishedBets int `json:"status_not_finished_bets"`
IsToBeFixedBets int `json:"is_to_be_fixed_bets"` StatusToBeFixedCount int `json:"status_to_be_fixed_count"`
IsPostponed int `json:"is_postponed"` StatusToBeFixedBets int `json:"status_to_be_fixed_bets"`
IsPostponedBets int `json:"is_postponed_bets"` StatusPostponedCount int `json:"status_postponed_count"`
IsEnded int `json:"is_ended"` StatusPostponedBets int `json:"status_postponed_bets"`
IsEndedBets int `json:"is_ended_bets"` StatusEndedCount int `json:"status_ended_count"`
IsRemoved int `json:"is_removed"` StatusEndedBets int `json:"status_ended_bets"`
IsRemovedBets int `json:"is_removed_bets"` StatusRemovedCount int `json:"status_removed_count"`
StatusRemovedBets int `json:"status_removed_bets"`
RemovedCount int `json:"removed"`
CreatedAt time.Time `json:"created_at"`
}
type CreateResultLog struct {
StatusNotFinishedCount int `json:"status_not_finished_count"`
StatusNotFinishedBets int `json:"status_not_finished_bets"`
StatusToBeFixedCount int `json:"status_to_be_fixed_count"`
StatusToBeFixedBets int `json:"status_to_be_fixed_bets"`
StatusPostponedCount int `json:"status_postponed_count"`
StatusPostponedBets int `json:"status_postponed_bets"`
StatusEndedCount int `json:"status_ended_count"`
StatusEndedBets int `json:"status_ended_bets"`
StatusRemovedCount int `json:"status_removed_count"`
StatusRemovedBets int `json:"status_removed_bets"`
RemovedCount int `json:"removed"`
}
type ResultFilter struct {
CreatedBefore ValidTime
CreatedAfter ValidTime
} }
type ResultStatusBets struct { type ResultStatusBets struct {
IsNotFinished []int64 `json:"is_not_finished"` StatusNotFinished []int64 `json:"status_not_finished"`
IsToBeFixed []int64 `json:"is_to_be_fixed"` StatusToBeFixed []int64 `json:"status_to_be_fixed"`
IsPostponed []int64 `json:"is_postponed"` StatusPostponed []int64 `json:"status_postponed"`
IsEnded []int64 `json:"is_ended"` StatusEnded []int64 `json:"status_ended"`
IsRemoved []int64 `json:"is_removed"` StatusRemoved []int64 `json:"status_removed"`
}
func ConvertDBResultLog(result dbgen.ResultLog) ResultLog {
return ResultLog{
ID: result.ID,
StatusNotFinishedCount: int(result.StatusNotFinishedCount),
StatusNotFinishedBets: int(result.StatusNotFinishedBets),
StatusToBeFixedCount: int(result.StatusToBeFixedCount),
StatusToBeFixedBets: int(result.StatusToBeFixedBets),
StatusPostponedCount: int(result.StatusPostponedCount),
StatusPostponedBets: int(result.StatusPostponedBets),
StatusEndedCount: int(result.StatusEndedCount),
StatusEndedBets: int(result.StatusEndedBets),
StatusRemovedCount: int(result.StatusRemovedCount),
StatusRemovedBets: int(result.StatusRemovedBets),
RemovedCount: int(result.RemovedCount),
CreatedAt: result.CreatedAt.Time,
}
}
func ConvertCreateResultLog(result CreateResultLog) dbgen.CreateResultLogParams {
return dbgen.CreateResultLogParams{
StatusNotFinishedCount: int32(result.StatusNotFinishedCount),
StatusNotFinishedBets: int32(result.StatusNotFinishedBets),
StatusToBeFixedCount: int32(result.StatusToBeFixedCount),
StatusToBeFixedBets: int32(result.StatusToBeFixedBets),
StatusPostponedCount: int32(result.StatusPostponedCount),
StatusPostponedBets: int32(result.StatusPostponedBets),
StatusEndedCount: int32(result.StatusEndedCount),
StatusEndedBets: int32(result.StatusEndedBets),
StatusRemovedCount: int32(result.StatusRemovedCount),
StatusRemovedBets: int32(result.StatusRemovedBets),
RemovedCount: int32(result.RemovedCount),
}
} }

View File

@ -8,93 +8,34 @@ import (
"github.com/jackc/pgx/v5/pgtype" "github.com/jackc/pgx/v5/pgtype"
) )
func convertDBResult(result dbgen.Result) domain.Result {
scores := make(map[string]domain.Score)
return domain.Result{
ID: result.ID,
BetOutcomeID: result.BetOutcomeID,
EventID: result.EventID,
OddID: result.OddID,
MarketID: result.MarketID,
Status: domain.OutcomeStatus(result.Status),
Score: result.Score.String,
FullTimeScore: result.FullTimeScore.String,
HalfTimeScore: result.HalfTimeScore.String,
SS: result.Ss.String,
Scores: scores,
CreatedAt: result.CreatedAt.Time,
UpdatedAt: result.UpdatedAt.Time,
}
}
func convertCreateResult(result domain.CreateResult) dbgen.CreateResultParams {
return dbgen.CreateResultParams{
BetOutcomeID: result.BetOutcomeID,
EventID: result.EventID,
OddID: result.OddID,
MarketID: result.MarketID,
Status: int32(result.Status),
Score: pgtype.Text{String: result.Score},
}
}
func convertResult(result domain.Result) dbgen.InsertResultParams {
return dbgen.InsertResultParams{
BetOutcomeID: result.BetOutcomeID,
EventID: result.EventID,
OddID: result.OddID,
MarketID: result.MarketID,
Status: int32(result.Status),
Score: pgtype.Text{String: result.Score},
FullTimeScore: pgtype.Text{String: result.FullTimeScore},
HalfTimeScore: pgtype.Text{String: result.HalfTimeScore},
Ss: pgtype.Text{String: result.SS},
}
}
func (s *Store) CreateResult(ctx context.Context, result domain.CreateResult) (domain.Result, error) { func (s *Store) CreateResultLog(ctx context.Context, result domain.CreateResultLog) (domain.ResultLog, error) {
dbResult, err := s.queries.CreateResult(ctx, convertCreateResult(result)) dbResult, err := s.queries.CreateResultLog(ctx, domain.ConvertCreateResultLog(result))
if err != nil { if err != nil {
return domain.Result{}, err return domain.ResultLog{}, err
} }
return convertDBResult(dbResult), nil return domain.ConvertDBResultLog(dbResult), nil
} }
func (s *Store) InsertResult(ctx context.Context, result domain.Result) error { func (s *Store) GetAllResultLog(ctx context.Context, filter domain.ResultFilter) ([]domain.ResultLog, error) {
return s.queries.InsertResult(ctx, convertResult(result)) dbResultLogs, err := s.queries.GetAllResultLog(ctx, dbgen.GetAllResultLogParams{
} CreatedBefore: pgtype.Timestamp{
Time: filter.CreatedBefore.Value,
func (s *Store) GetResultByBetOutcomeID(ctx context.Context, betOutcomeID int64) (domain.Result, error) { Valid: filter.CreatedBefore.Valid,
dbResult, err := s.queries.GetResultByBetOutcomeID(ctx, betOutcomeID) },
if err != nil { CreatedAfter: pgtype.Timestamp{
return domain.Result{}, err Time: filter.CreatedAfter.Value,
} Valid: filter.CreatedAfter.Valid,
return convertDBResult(dbResult), nil },
} })
func (s *Store) GetPendingBetOutcomes(ctx context.Context) ([]domain.BetOutcome, error) {
dbOutcomes, err := s.queries.GetPendingBetOutcomes(ctx)
if err != nil { if err != nil {
return nil, err return nil, err
} }
outcomes := make([]domain.BetOutcome, 0, len(dbOutcomes)) result := make([]domain.ResultLog, 0, len(dbResultLogs))
for _, dbOutcome := range dbOutcomes { for _, dbResultLog := range dbResultLogs {
outcomes = append(outcomes, domain.BetOutcome{ result = append(result, domain.ConvertDBResultLog(dbResultLog))
ID: dbOutcome.ID,
BetID: dbOutcome.BetID,
EventID: dbOutcome.EventID,
OddID: dbOutcome.OddID,
HomeTeamName: dbOutcome.HomeTeamName,
AwayTeamName: dbOutcome.AwayTeamName,
MarketID: dbOutcome.MarketID,
MarketName: dbOutcome.MarketName,
Odd: dbOutcome.Odd,
OddName: dbOutcome.OddName,
OddHeader: dbOutcome.OddHeader,
OddHandicap: dbOutcome.OddHandicap,
Status: domain.OutcomeStatus(dbOutcome.Status),
Expires: dbOutcome.Expires.Time,
})
} }
return outcomes, nil return result, nil
} }

View File

@ -987,6 +987,8 @@ func (s *Service) SendWinningStatusNotification(ctx context.Context, status doma
betNotification := &domain.Notification{ betNotification := &domain.Notification{
RecipientID: userID, RecipientID: userID,
DeliveryStatus: domain.DeliveryStatusPending,
IsRead: false,
Type: domain.NOTIFICATION_TYPE_BET_RESULT, Type: domain.NOTIFICATION_TYPE_BET_RESULT,
Level: domain.NotificationLevelSuccess, Level: domain.NotificationLevelSuccess,
Reciever: domain.NotificationRecieverSideCustomer, Reciever: domain.NotificationRecieverSideCustomer,
@ -1028,6 +1030,8 @@ func (s *Service) SendLosingStatusNotification(ctx context.Context, status domai
betNotification := &domain.Notification{ betNotification := &domain.Notification{
RecipientID: userID, RecipientID: userID,
DeliveryStatus: domain.DeliveryStatusPending,
IsRead: false,
Type: domain.NOTIFICATION_TYPE_BET_RESULT, Type: domain.NOTIFICATION_TYPE_BET_RESULT,
Level: domain.NotificationLevelSuccess, Level: domain.NotificationLevelSuccess,
Reciever: domain.NotificationRecieverSideCustomer, Reciever: domain.NotificationRecieverSideCustomer,
@ -1070,6 +1074,8 @@ func (s *Service) SendErrorStatusNotification(ctx context.Context, status domain
betNotification := &domain.Notification{ betNotification := &domain.Notification{
RecipientID: userID, RecipientID: userID,
DeliveryStatus: domain.DeliveryStatusPending,
IsRead: false,
Type: domain.NOTIFICATION_TYPE_BET_RESULT, Type: domain.NOTIFICATION_TYPE_BET_RESULT,
Level: domain.NotificationLevelSuccess, Level: domain.NotificationLevelSuccess,
Reciever: domain.NotificationRecieverSideCustomer, Reciever: domain.NotificationRecieverSideCustomer,
@ -1104,11 +1110,15 @@ func (s *Service) SendAdminErrorAlertNotification(ctx context.Context, status do
switch status { switch status {
case domain.OUTCOME_STATUS_ERROR, domain.OUTCOME_STATUS_PENDING: case domain.OUTCOME_STATUS_ERROR, domain.OUTCOME_STATUS_PENDING:
headline = "There was an error with your bet" headline = "There was an error processing bet"
message = "We have encounter an error with your bet. We will fix it as soon as we can" message = "We have encounter an error with bet. We will fix it as soon as we can"
} }
errorSeverity := domain.NotificationErrorSeverityHigh
betNotification := &domain.Notification{ betNotification := &domain.Notification{
ErrorSeverity: &errorSeverity,
DeliveryStatus: domain.DeliveryStatusPending,
IsRead: false,
Type: domain.NOTIFICATION_TYPE_BET_RESULT, Type: domain.NOTIFICATION_TYPE_BET_RESULT,
Level: domain.NotificationLevelSuccess, Level: domain.NotificationLevelSuccess,
Reciever: domain.NotificationRecieverSideCustomer, Reciever: domain.NotificationRecieverSideCustomer,

View File

@ -2,9 +2,16 @@ package result
import ( import (
"context" "context"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
) )
type ResultService interface { type ResultService interface {
FetchAndProcessResults(ctx context.Context) error FetchAndProcessResults(ctx context.Context) error
FetchAndStoreResult(ctx context.Context, eventID string) error FetchAndStoreResult(ctx context.Context, eventID string) error
} }
type ResultLogStore interface {
CreateResultLog(ctx context.Context, result domain.CreateResultLog) (domain.ResultLog, error)
GetAllResultLog(ctx context.Context, filter domain.ResultFilter) ([]domain.ResultLog, error)
}

View File

@ -264,9 +264,8 @@ func (s *Service) FetchAndProcessResults(ctx context.Context) error {
return err return err
} }
removed := 0
empty_sport_id := make([]int64, 0) empty_sport_id := make([]int64, 0)
var resultStatusCounts domain.ResultStatusCounts var resultLog domain.CreateResultLog
var resultStatusBets domain.ResultStatusBets var resultStatusBets domain.ResultStatusBets
for _, event := range events { for _, event := range events {
@ -283,7 +282,6 @@ func (s *Service) FetchAndProcessResults(ctx context.Context) error {
result, err := s.fetchResult(ctx, eventID) result, err := s.fetchResult(ctx, eventID)
if err != nil { if err != nil {
if err == ErrEventIsNotActive { if err == ErrEventIsNotActive {
s.logger.Warn("Event is not active", "event_id", eventID, "error", err)
s.mongoLogger.Warn( s.mongoLogger.Warn(
"Event is not active", "Event is not active",
zap.Int64("eventID", eventID), zap.Int64("eventID", eventID),
@ -329,25 +327,42 @@ func (s *Service) FetchAndProcessResults(ctx context.Context) error {
// Admin users will be able to review the events // Admin users will be able to review the events
switch timeStatusParsed { switch timeStatusParsed {
case int64(domain.TIME_STATUS_NOT_STARTED), int64(domain.TIME_STATUS_IN_PLAY): case int64(domain.TIME_STATUS_NOT_STARTED), int64(domain.TIME_STATUS_IN_PLAY):
resultStatusCounts.IsNotFinished += 1 resultLog.StatusNotFinishedCount += 1
bets, err := s.GetTotalBetsForEvents(ctx, eventID) bets, err := s.GetTotalBetsForEvents(ctx, eventID)
if err != nil { if err != nil {
continue continue
} }
resultStatusCounts.IsNotFinishedBets = len(bets) resultLog.StatusNotFinishedBets = len(bets)
for k := range bets { for k := range bets {
resultStatusBets.IsNotFinished = append(resultStatusBets.IsNotFinished, k) resultStatusBets.StatusNotFinished = append(resultStatusBets.StatusNotFinished, k)
} }
case int64(domain.TIME_STATUS_TO_BE_FIXED): case int64(domain.TIME_STATUS_TO_BE_FIXED):
resultStatusCounts.IsToBeFixed += 1 totalBetsRefunded, err := s.RefundAllOutcomes(ctx, eventID)
bets, err := s.GetTotalBetsForEvents(ctx, eventID)
err = s.repo.DeleteEvent(ctx, event.ID)
if err != nil { if err != nil {
s.mongoLogger.Error(
"Failed to remove event",
zap.Int64("eventID", eventID),
zap.Error(err),
)
continue continue
} }
resultStatusCounts.IsToBeFixedBets = len(bets) err = s.repo.DeleteOddsForEvent(ctx, event.ID)
for k := range bets { if err != nil {
resultStatusBets.IsNotFinished = append(resultStatusBets.IsNotFinished, k) s.mongoLogger.Error(
"Failed to remove odds for event",
zap.Int64("eventID", eventID),
zap.Error(err),
)
continue
}
resultLog.RemovedCount += 1
resultLog.StatusToBeFixedCount += 1
resultLog.StatusToBeFixedBets = len(totalBetsRefunded)
for k := range totalBetsRefunded {
resultStatusBets.StatusToBeFixed = append(resultStatusBets.StatusToBeFixed, k)
} }
// s.mongoLogger.Warn( // s.mongoLogger.Warn(
// "Event needs to be rescheduled or corrected", // "Event needs to be rescheduled or corrected",
@ -355,14 +370,16 @@ func (s *Service) FetchAndProcessResults(ctx context.Context) error {
// zap.Error(err), // zap.Error(err),
// ) // )
case int64(domain.TIME_STATUS_POSTPONED), int64(domain.TIME_STATUS_SUSPENDED): case int64(domain.TIME_STATUS_POSTPONED), int64(domain.TIME_STATUS_SUSPENDED):
resultStatusCounts.IsPostponed += 1
bets, err := s.GetTotalBetsForEvents(ctx, eventID) bets, err := s.GetTotalBetsForEvents(ctx, eventID)
if err != nil { if err != nil {
continue continue
} }
resultStatusCounts.IsPostponed = len(bets)
resultLog.StatusPostponedCount += 1
resultLog.StatusPostponedBets = len(bets)
for k := range bets { for k := range bets {
resultStatusBets.IsNotFinished = append(resultStatusBets.IsNotFinished, k) resultStatusBets.StatusPostponed = append(resultStatusBets.StatusPostponed, k)
} }
// s.mongoLogger.Warn( // s.mongoLogger.Warn(
// "Event has been temporarily postponed", // "Event has been temporarily postponed",
@ -409,15 +426,15 @@ func (s *Service) FetchAndProcessResults(ctx context.Context) error {
) )
continue continue
} }
removed += 1 resultLog.RemovedCount += 1
resultStatusCounts.IsEnded += 1 resultLog.StatusEndedCount += 1
bets, err := s.GetTotalBetsForEvents(ctx, eventID) bets, err := s.GetTotalBetsForEvents(ctx, eventID)
if err != nil { if err != nil {
continue continue
} }
resultStatusCounts.IsEndedBets = len(bets) resultLog.StatusEndedBets = len(bets)
for k := range bets { for k := range bets {
resultStatusBets.IsNotFinished = append(resultStatusBets.IsNotFinished, k) resultStatusBets.StatusEnded = append(resultStatusBets.StatusEnded, k)
} }
case int64(domain.TIME_STATUS_ABANDONED), int64(domain.TIME_STATUS_CANCELLED), int64(domain.TIME_STATUS_REMOVED): case int64(domain.TIME_STATUS_ABANDONED), int64(domain.TIME_STATUS_CANCELLED), int64(domain.TIME_STATUS_REMOVED):
// s.SendAdminResultStatusErrorNotification( // s.SendAdminResultStatusErrorNotification(
@ -451,59 +468,126 @@ func (s *Service) FetchAndProcessResults(ctx context.Context) error {
) )
continue continue
} }
removed += 1 resultLog.RemovedCount += 1
resultStatusCounts.IsRemoved += 1 resultLog.StatusRemovedCount += 1
resultStatusCounts.IsRemovedBets = len(totalBetsRefunded) resultLog.StatusRemovedBets = len(totalBetsRefunded)
for k := range totalBetsRefunded { for k := range totalBetsRefunded {
resultStatusBets.IsNotFinished = append(resultStatusBets.IsNotFinished, k) resultStatusBets.StatusRemoved = append(resultStatusBets.StatusRemoved, k)
} }
} }
} }
s.SendAdminResultStatusErrorNotification( // This will be used to send daily notifications, since events will be removed
ctx, _, err = s.repo.CreateResultLog(ctx, resultLog)
resultStatusCounts, if err != nil {
s.mongoLogger.Warn(
"Failed to store result log",
zap.Error(err),
) )
}
var logMessage string var logMessage string
if resultStatusCounts.IsNotFinished != 0 || resultStatusCounts.IsPostponed != 0 || if resultLog.StatusNotFinishedCount != 0 || resultLog.StatusPostponedCount != 0 ||
resultStatusCounts.IsRemoved != 0 || resultStatusCounts.IsToBeFixed != 0 { resultLog.StatusRemovedCount != 0 || resultLog.StatusToBeFixedCount != 0 {
logMessage = "Completed processed results with issues" logMessage = "Completed processing results with issues"
} else { } else {
logMessage = "Successfully processed results with no issues" logMessage = "Successfully processed results with no issues"
} }
s.mongoLogger.Info( s.mongoLogger.Info(
logMessage, logMessage,
zap.Int("number_of_removed_events", removed), zap.Int("number_of_removed_events", resultLog.RemovedCount),
zap.Int("total_expired_events", len(events)), zap.Int("total_expired_events", len(events)),
zap.Any("events_with_empty_sport_id", empty_sport_id), zap.Any("events_with_empty_sport_id", empty_sport_id),
zap.Any("result status counts", resultStatusCounts), zap.Any("result status counts", resultLog),
zap.Any("bets by event status", resultStatusBets), zap.Any("bets by event status", resultStatusBets),
) )
return nil return nil
} }
func buildHeadlineAndMessage(counts domain.ResultStatusCounts) (string, string) { func (s *Service) CheckAndSendResultNotifications(ctx context.Context, createdAfter time.Time) error {
totalIssues := counts.IsNotFinished + counts.IsToBeFixed + counts.IsPostponed + counts.IsRemoved
resultLog, err := s.repo.GetAllResultLog(ctx, domain.ResultFilter{
CreatedAfter: domain.ValidTime{
Value: createdAfter,
Valid: true,
},
})
if err != nil {
s.mongoLogger.Error(
"Failed to get result log",
zap.Time("CreatedAfter", createdAfter),
zap.Error(err),
)
return err
}
if len(resultLog) == 0 {
s.mongoLogger.Info(
"No results found for check and send result notification",
zap.Time("CreatedAfter", createdAfter),
)
return nil
}
totalResultLog := domain.ResultLog{
StatusNotFinishedCount: resultLog[0].StatusNotFinishedCount,
StatusPostponedCount: resultLog[0].StatusPostponedCount,
}
for _, log := range resultLog {
// Add all the bets
totalResultLog.StatusNotFinishedBets += log.StatusNotFinishedBets
totalResultLog.StatusPostponedBets += log.StatusPostponedBets
totalResultLog.StatusToBeFixedBets += log.StatusToBeFixedBets
totalResultLog.StatusRemovedBets += log.StatusRemovedBets
totalResultLog.StatusEndedBets += log.StatusEndedBets
totalResultLog.StatusToBeFixedCount += log.StatusToBeFixedCount
totalResultLog.StatusRemovedCount += log.StatusRemovedCount
totalResultLog.StatusEndedCount += log.StatusEndedCount
totalResultLog.RemovedCount += log.RemovedCount
}
err = s.SendAdminResultStatusErrorNotification(ctx, totalResultLog)
if err != nil {
s.mongoLogger.Error(
"Failed to send admin result status notification",
zap.Time("CreatedAfter", createdAfter),
zap.Error(err),
)
return err
}
return nil
}
func buildHeadlineAndMessage(counts domain.ResultLog) (string, string) {
totalIssues := counts.StatusNotFinishedCount + counts.StatusToBeFixedCount + counts.StatusPostponedCount + counts.StatusRemovedCount
totalBets := counts.StatusEndedBets + counts.StatusNotFinishedBets + counts.StatusPostponedBets + counts.StatusRemovedBets + counts.StatusToBeFixedBets
if totalIssues == 0 { if totalIssues == 0 {
return "✅ Event Results Processed", "All event results were processed successfully. No issues detected." return "✅ Successfully Processed Event Results", fmt.Sprintf(
"%d total ended events with %d total bets. No issues detected", counts.StatusEndedCount, totalBets,
)
} }
parts := []string{} parts := []string{}
if counts.IsNotFinished > 0 { if counts.StatusNotFinishedCount > 0 {
parts = append(parts, fmt.Sprintf("%d unfinished", counts.IsNotFinished)) parts = append(parts, fmt.Sprintf("%d unfinished with %d bets", counts.StatusNotFinishedCount, counts.StatusNotFinishedBets))
} }
if counts.IsToBeFixed > 0 { if counts.StatusToBeFixedCount > 0 {
parts = append(parts, fmt.Sprintf("%d to-fix", counts.IsToBeFixed)) parts = append(parts, fmt.Sprintf("%d to-fix with %d bets", counts.StatusToBeFixedCount, counts.StatusToBeFixedBets))
} }
if counts.IsPostponed > 0 { if counts.StatusPostponedCount > 0 {
parts = append(parts, fmt.Sprintf("%d postponed", counts.IsPostponed)) parts = append(parts, fmt.Sprintf("%d postponed with %d bets", counts.StatusPostponedCount, counts.StatusPostponedBets))
} }
if counts.IsRemoved > 0 { if counts.StatusRemovedCount > 0 {
parts = append(parts, fmt.Sprintf("%d removed", counts.IsRemoved)) parts = append(parts, fmt.Sprintf("%d removed with %d bets", counts.StatusRemovedCount, counts.StatusRemovedBets))
}
if counts.StatusEndedCount > 0 {
parts = append(parts, fmt.Sprintf("%d ended with %d bets", counts.StatusEndedCount, counts.StatusEndedBets))
} }
headline := "⚠️ Issues Found Processing Event Results" headline := "⚠️ Issues Found Processing Event Results"
@ -513,7 +597,7 @@ func buildHeadlineAndMessage(counts domain.ResultStatusCounts) (string, string)
func (s *Service) SendAdminResultStatusErrorNotification( func (s *Service) SendAdminResultStatusErrorNotification(
ctx context.Context, ctx context.Context,
counts domain.ResultStatusCounts, counts domain.ResultLog,
) error { ) error {
superAdmins, _, err := s.userSvc.GetAllUsers(ctx, domain.UserFilter{ superAdmins, _, err := s.userSvc.GetAllUsers(ctx, domain.UserFilter{
@ -558,6 +642,14 @@ func (s *Service) SendAdminResultStatusErrorNotification(
) )
sendErrors = append(sendErrors, err) sendErrors = append(sendErrors, err)
} }
notification.DeliveryChannel = domain.DeliveryChannelEmail
if err := s.notificationSvc.SendNotification(ctx, notification); err != nil {
s.mongoLogger.Error("failed to send admin email notification",
zap.Int64("admin_id", user.ID),
zap.Error(err),
)
sendErrors = append(sendErrors, err)
}
} }
if len(sendErrors) > 0 { if len(sendErrors) > 0 {

View File

@ -51,19 +51,19 @@ func StartDataFetchingCrons(eventService eventsvc.Service, oddsService oddssvc.S
// } // }
// }, // },
// }, // },
// { {
// spec: "0 */5 * * * *", // Every 5 Minutes spec: "0 */5 * * * *", // Every 5 Minutes
// task: func() { task: func() {
// mongoLogger.Info("Began updating all expired events status") mongoLogger.Info("Began updating all expired events status")
// if _, err := resultService.CheckAndUpdateExpiredEvents(context.Background()); err != nil { if _, err := resultService.CheckAndUpdateExpiredEvents(context.Background()); err != nil {
// mongoLogger.Error("Failed to update expired events status", mongoLogger.Error("Failed to update expired events status",
// zap.Error(err), zap.Error(err),
// ) )
// } else { } else {
// mongoLogger.Info("Successfully updated expired events") mongoLogger.Info("Successfully updated expired events")
// } }
// }, },
// }, },
{ {
spec: "0 */15 * * * *", // Every 15 Minutes spec: "0 */15 * * * *", // Every 15 Minutes
task: func() { task: func() {
@ -77,6 +77,19 @@ func StartDataFetchingCrons(eventService eventsvc.Service, oddsService oddssvc.S
} }
}, },
}, },
{
spec: "0 0 * * * *", // Every Day
task: func() {
mongoLogger.Info("Send daily result notification")
if err := resultService.CheckAndSendResultNotifications(context.Background(), time.Now().Add(-24*time.Hour)); err != nil {
mongoLogger.Error("Failed to process result",
zap.Error(err),
)
} else {
mongoLogger.Info("Successfully processed all event result outcomes")
}
},
},
} }
for _, job := range schedule { for _, job := range schedule {