From fbe2dfd5a3d45bfebd420b52cddf75ed0acd6efe Mon Sep 17 00:00:00 2001 From: Samuel Tariku Date: Tue, 10 Jun 2025 06:41:11 +0300 Subject: [PATCH] fix: event and league disabling --- db/query/events.sql | 6 +- db/query/leagues.sql | 45 +++++++------ gen/db/events.sql.go | 6 +- gen/db/leagues.sql.go | 54 ++++++++-------- internal/domain/event.go | 29 ++++----- internal/domain/league.go | 10 +-- internal/repository/event.go | 22 +++++++ internal/repository/league.go | 10 ++- internal/services/event/port.go | 1 + internal/services/event/service.go | 5 +- internal/services/league/port.go | 2 +- internal/services/league/service.go | 4 +- internal/services/result/service.go | 5 ++ internal/web_server/handlers/auth_handler.go | 2 - internal/web_server/handlers/leagues.go | 17 ++++- internal/web_server/handlers/prematch.go | 66 +++++++++++++++----- internal/web_server/routes.go | 13 ++-- 17 files changed, 195 insertions(+), 102 deletions(-) diff --git a/db/query/events.sql b/db/query/events.sql index 3596c56..a02972a 100644 --- a/db/query/events.sql +++ b/db/query/events.sql @@ -144,7 +144,8 @@ SELECT id, source, fetched_at FROM events -WHERE is_live = false +WHERE start_time > now() + AND is_live = false AND status = 'upcoming' ORDER BY start_time ASC; -- name: GetExpiredUpcomingEvents :many @@ -212,7 +213,8 @@ SELECT id, source, fetched_at FROM events -WHERE is_live = false +WHERE start_time > now() + AND is_live = false AND status = 'upcoming' AND ( league_id = sqlc.narg('league_id') diff --git a/db/query/leagues.sql b/db/query/leagues.sql index b9c0e02..5e7bcc7 100644 --- a/db/query/leagues.sql +++ b/db/query/leagues.sql @@ -1,40 +1,39 @@ -- name: InsertLeague :exec INSERT INTO leagues ( - id, - name, - country_code, - bet365_id, - is_active -) VALUES ( - $1, $2, $3, $4, $5 -) -ON CONFLICT (id) DO UPDATE + id, + name, + country_code, + bet365_id, + is_active + ) +VALUES ($1, $2, $3, $4, $5) ON CONFLICT (id) DO +UPDATE SET name = EXCLUDED.name, country_code = EXCLUDED.country_code, bet365_id = EXCLUDED.bet365_id, is_active = EXCLUDED.is_active; -- name: GetSupportedLeagues :many SELECT id, - name, - country_code, - bet365_id, - is_active + name, + country_code, + bet365_id, + is_active FROM leagues WHERE is_active = true; -- name: GetAllLeagues :many SELECT id, - name, - country_code, - bet365_id, - is_active + name, + country_code, + bet365_id, + is_active FROM leagues; -- name: CheckLeagueSupport :one SELECT EXISTS( - SELECT 1 - FROM leagues - WHERE id = $1 - AND is_active = true -); + SELECT 1 + FROM leagues + WHERE id = $1 + AND is_active = true + ); -- name: UpdateLeague :exec UPDATE leagues SET name = $1, @@ -44,5 +43,5 @@ SET name = $1, WHERE id = $5; -- name: SetLeagueActive :exec UPDATE leagues -SET is_active = true +SET is_active = $2 WHERE id = $1; \ No newline at end of file diff --git a/gen/db/events.sql.go b/gen/db/events.sql.go index e5fc357..fac3414 100644 --- a/gen/db/events.sql.go +++ b/gen/db/events.sql.go @@ -40,7 +40,8 @@ SELECT id, source, fetched_at FROM events -WHERE is_live = false +WHERE start_time > now() + AND is_live = false AND status = 'upcoming' ORDER BY start_time ASC ` @@ -207,7 +208,8 @@ SELECT id, source, fetched_at FROM events -WHERE is_live = false +WHERE start_time > now() + AND is_live = false AND status = 'upcoming' AND ( league_id = $1 diff --git a/gen/db/leagues.sql.go b/gen/db/leagues.sql.go index e118d6c..c41c751 100644 --- a/gen/db/leagues.sql.go +++ b/gen/db/leagues.sql.go @@ -13,11 +13,11 @@ import ( const CheckLeagueSupport = `-- name: CheckLeagueSupport :one SELECT EXISTS( - SELECT 1 - FROM leagues - WHERE id = $1 - AND is_active = true -) + SELECT 1 + FROM leagues + WHERE id = $1 + AND is_active = true + ) ` func (q *Queries) CheckLeagueSupport(ctx context.Context, id int64) (bool, error) { @@ -29,10 +29,10 @@ func (q *Queries) CheckLeagueSupport(ctx context.Context, id int64) (bool, error const GetAllLeagues = `-- name: GetAllLeagues :many SELECT id, - name, - country_code, - bet365_id, - is_active + name, + country_code, + bet365_id, + is_active FROM leagues ` @@ -64,10 +64,10 @@ func (q *Queries) GetAllLeagues(ctx context.Context) ([]League, error) { const GetSupportedLeagues = `-- name: GetSupportedLeagues :many SELECT id, - name, - country_code, - bet365_id, - is_active + name, + country_code, + bet365_id, + is_active FROM leagues WHERE is_active = true ` @@ -100,15 +100,14 @@ func (q *Queries) GetSupportedLeagues(ctx context.Context) ([]League, error) { const InsertLeague = `-- name: InsertLeague :exec INSERT INTO leagues ( - id, - name, - country_code, - bet365_id, - is_active -) VALUES ( - $1, $2, $3, $4, $5 -) -ON CONFLICT (id) DO UPDATE + id, + name, + country_code, + bet365_id, + is_active + ) +VALUES ($1, $2, $3, $4, $5) ON CONFLICT (id) DO +UPDATE SET name = EXCLUDED.name, country_code = EXCLUDED.country_code, bet365_id = EXCLUDED.bet365_id, @@ -136,12 +135,17 @@ func (q *Queries) InsertLeague(ctx context.Context, arg InsertLeagueParams) erro const SetLeagueActive = `-- name: SetLeagueActive :exec UPDATE leagues -SET is_active = true +SET is_active = $2 WHERE id = $1 ` -func (q *Queries) SetLeagueActive(ctx context.Context, id int64) error { - _, err := q.db.Exec(ctx, SetLeagueActive, id) +type SetLeagueActiveParams struct { + ID int64 `json:"id"` + IsActive pgtype.Bool `json:"is_active"` +} + +func (q *Queries) SetLeagueActive(ctx context.Context, arg SetLeagueActiveParams) error { + _, err := q.db.Exec(ctx, SetLeagueActive, arg.ID, arg.IsActive) return err } diff --git a/internal/domain/event.go b/internal/domain/event.go index 932fd82..0501505 100644 --- a/internal/domain/event.go +++ b/internal/domain/event.go @@ -86,20 +86,21 @@ type BetResult struct { } type UpcomingEvent struct { - ID string // Event ID - SportID int32 // Sport ID - MatchName string // Match or event name - HomeTeam string // Home team name (if available) - AwayTeam string // Away team name (can be empty/null) - HomeTeamID int32 // Home team ID - AwayTeamID int32 // Away team ID (can be empty/null) - HomeKitImage string // Kit or image for home team (optional) - AwayKitImage string // Kit or image for away team (optional) - LeagueID int32 // League ID - LeagueName string // League name - LeagueCC string // League country code - StartTime time.Time // Converted from "time" field in UNIX format - Source string // bet api provider (bet365, betfair) + ID string `json:"id"` // Event ID + SportID int32 `json:"sport_id"` // Sport ID + MatchName string `json:"match_name"` // Match or event name + HomeTeam string `json:"home_team"` // Home team name (if available) + AwayTeam string `json:"away_team"` // Away team name (can be empty/null) + HomeTeamID int32 `json:"home_team_id"` // Home team ID + AwayTeamID int32 `json:"away_team_id"` // Away team ID (can be empty/null) + HomeKitImage string `json:"home_kit_image"` // Kit or image for home team (optional) + AwayKitImage string `json:"away_kit_image"` // Kit or image for away team (optional) + LeagueID int32 `json:"league_id"` // League ID + LeagueName string `json:"league_name"` // League name + LeagueCC string `json:"league_cc"` // League country code + 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 } type MatchResult struct { EventID string diff --git a/internal/domain/league.go b/internal/domain/league.go index f5ac35e..fff88ca 100644 --- a/internal/domain/league.go +++ b/internal/domain/league.go @@ -1,9 +1,9 @@ package domain type League struct { - ID int64 - Name string - CountryCode string - Bet365ID int32 - IsActive bool + ID int64 `json:"id" example:"1"` + Name string `json:"name" example:"BPL"` + CountryCode string `json:"cc" example:"uk"` + Bet365ID int32 `json:"bet365_id" example:"1121"` + IsActive bool `json:"is_active" example:"false"` } diff --git a/internal/repository/event.go b/internal/repository/event.go index a4b9aef..d466bf7 100644 --- a/internal/repository/event.go +++ b/internal/repository/event.go @@ -88,6 +88,7 @@ func (s *Store) GetAllUpcomingEvents(ctx context.Context) ([]domain.UpcomingEven LeagueCC: e.LeagueCc.String, StartTime: e.StartTime.Time.UTC(), Source: e.Source.String, + Status: domain.EventStatus(e.Status.String), } } return upcomingEvents, nil @@ -119,6 +120,7 @@ func (s *Store) GetExpiredUpcomingEvents(ctx context.Context, filter domain.Even LeagueCC: e.LeagueCc.String, StartTime: e.StartTime.Time.UTC(), Source: e.Source.String, + Status: domain.EventStatus(e.Status.String), } } return upcomingEvents, nil @@ -173,6 +175,7 @@ func (s *Store) GetPaginatedUpcomingEvents(ctx context.Context, filter domain.Ev LeagueCC: e.LeagueCc.String, StartTime: e.StartTime.Time.UTC(), Source: e.Source.String, + Status: domain.EventStatus(e.Status.String), } } totalCount, err := s.queries.GetTotalEvents(ctx, dbgen.GetTotalEventsParams{ @@ -221,6 +224,7 @@ func (s *Store) GetUpcomingEventByID(ctx context.Context, ID string) (domain.Upc LeagueCC: event.LeagueCc.String, StartTime: event.StartTime.Time.UTC(), Source: event.Source.String, + Status: domain.EventStatus(event.Status.String), }, nil } func (s *Store) UpdateFinalScore(ctx context.Context, eventID, fullScore string, status domain.EventStatus) error { @@ -238,6 +242,24 @@ func (s *Store) UpdateFinalScore(ctx context.Context, eventID, fullScore string, return nil } +func (s *Store) UpdateEventStatus(ctx context.Context, eventID string, status domain.EventStatus) error { + params := dbgen.UpdateMatchResultParams{ + Status: pgtype.Text{ + String: string(status), + Valid: true, + }, + ID: eventID, + } + + err := s.queries.UpdateMatchResult(ctx, params) + + if err != nil { + return err + } + return nil + +} + func (s *Store) DeleteEvent(ctx context.Context, eventID string) error { err := s.queries.DeleteEvent(ctx, eventID) if err != nil { diff --git a/internal/repository/league.go b/internal/repository/league.go index 7e5205f..38be3ce 100644 --- a/internal/repository/league.go +++ b/internal/repository/league.go @@ -60,8 +60,14 @@ func (s *Store) CheckLeagueSupport(ctx context.Context, leagueID int64) (bool, e return s.queries.CheckLeagueSupport(ctx, leagueID) } -func (s *Store) SetLeagueActive(ctx context.Context, leagueId int64) error { - return s.queries.SetLeagueActive(ctx, leagueId) +func (s *Store) SetLeagueActive(ctx context.Context, leagueId int64, isActive bool) error { + return s.queries.SetLeagueActive(ctx, dbgen.SetLeagueActiveParams{ + ID: leagueId, + IsActive: pgtype.Bool{ + Bool: isActive, + Valid: true, + }, + }) } // TODO: update based on id, no need for the entire league (same as the set active one) diff --git a/internal/services/event/port.go b/internal/services/event/port.go index 5576d93..c548a32 100644 --- a/internal/services/event/port.go +++ b/internal/services/event/port.go @@ -15,4 +15,5 @@ type Service interface { GetUpcomingEventByID(ctx context.Context, ID string) (domain.UpcomingEvent, error) // 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 } diff --git a/internal/services/event/service.go b/internal/services/event/service.go index f1165dd..c3510ff 100644 --- a/internal/services/event/service.go +++ b/internal/services/event/service.go @@ -339,7 +339,7 @@ func (s *service) GetExpiredUpcomingEvents(ctx context.Context, filter domain.Ev return s.store.GetExpiredUpcomingEvents(ctx, filter) } -func (s *service) GetPaginatedUpcomingEvents(ctx context.Context, filter domain.EventFilter) ([]domain.UpcomingEvent, int64, error){ +func (s *service) GetPaginatedUpcomingEvents(ctx context.Context, filter domain.EventFilter) ([]domain.UpcomingEvent, int64, error) { return s.store.GetPaginatedUpcomingEvents(ctx, filter) } @@ -350,6 +350,9 @@ func (s *service) GetUpcomingEventByID(ctx context.Context, ID string) (domain.U func (s *service) UpdateFinalScore(ctx context.Context, eventID, fullScore string, status domain.EventStatus) error { return s.store.UpdateFinalScore(ctx, eventID, fullScore, status) } +func (s *service) UpdateEventStatus(ctx context.Context, eventID string, status domain.EventStatus) error { + return s.store.UpdateEventStatus(ctx, eventID, status) +} // func (s *service) GetAndStoreMatchResult(ctx context.Context, eventID string) error { // url := fmt.Sprintf("https://api.b365api.com/v1/bet365/result?token=%s&event_id=%s", s.token, eventID) diff --git a/internal/services/league/port.go b/internal/services/league/port.go index 7b71a48..f527c44 100644 --- a/internal/services/league/port.go +++ b/internal/services/league/port.go @@ -8,5 +8,5 @@ import ( type Service interface { GetAllLeagues(ctx context.Context) ([]domain.League, error) - SetLeagueActive(ctx context.Context, leagueId int64) error + SetLeagueActive(ctx context.Context, leagueId int64, isActive bool) error } diff --git a/internal/services/league/service.go b/internal/services/league/service.go index b1f05ed..9de9210 100644 --- a/internal/services/league/service.go +++ b/internal/services/league/service.go @@ -21,6 +21,6 @@ func (s *service) GetAllLeagues(ctx context.Context) ([]domain.League, error) { return s.store.GetAllLeagues(ctx) } -func (s *service) SetLeagueActive(ctx context.Context, leagueId int64) error { - return s.store.SetLeagueActive(ctx, leagueId) +func (s *service) SetLeagueActive(ctx context.Context, leagueId int64, isActive bool) error { + return s.store.SetLeagueActive(ctx, leagueId, isActive) } diff --git a/internal/services/result/service.go b/internal/services/result/service.go index dab02ef..56c0e9c 100644 --- a/internal/services/result/service.go +++ b/internal/services/result/service.go @@ -211,6 +211,11 @@ func (s *Service) CheckAndUpdateExpiredEvents(ctx context.Context) (int64, error s.logger.Error("Failed to parse event id") continue } + + if event.Status == domain.STATUS_REMOVED { + s.logger.Info("Skipping updating removed event") + continue + } result, err := s.fetchResult(ctx, eventID) if err != nil { s.logger.Error("Failed to fetch result", "event_id", eventID, "error", err) diff --git a/internal/web_server/handlers/auth_handler.go b/internal/web_server/handlers/auth_handler.go index e72b59e..1b3cc97 100644 --- a/internal/web_server/handlers/auth_handler.go +++ b/internal/web_server/handlers/auth_handler.go @@ -2,7 +2,6 @@ package handlers import ( "errors" - "fmt" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/authentication" jwtutil "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/jwt" @@ -127,7 +126,6 @@ func (h *Handler) RefreshToken(c *fiber.Ctx) error { user, err := h.userSvc.GetUserByID(c.Context(), refreshToken.UserID) - fmt.Printf("user company id %v", user.CompanyID) // Assuming the refreshed token includes userID and role info; adjust if needed accessToken, err := jwtutil.CreateJwt(user.ID, user.Role, user.CompanyID, h.jwtConfig.JwtAccessKey, h.jwtConfig.JwtAccessExpiry) if err != nil { diff --git a/internal/web_server/handlers/leagues.go b/internal/web_server/handlers/leagues.go index d4f78ee..f4e182b 100644 --- a/internal/web_server/handlers/leagues.go +++ b/internal/web_server/handlers/leagues.go @@ -16,6 +16,10 @@ func (h *Handler) GetAllLeagues(c *fiber.Ctx) error { return response.WriteJSON(c, fiber.StatusOK, "All leagues retrived", leagues, nil) } +type SetLeagueActiveReq struct { + IsActive bool `json:"is_active"` +} + func (h *Handler) SetLeagueActive(c *fiber.Ctx) error { leagueIdStr := c.Params("id") if leagueIdStr == "" { @@ -26,7 +30,18 @@ func (h *Handler) SetLeagueActive(c *fiber.Ctx) error { response.WriteJSON(c, fiber.StatusBadRequest, "invalid league id", nil, nil) } - if err := h.leagueSvc.SetLeagueActive(c.Context(), int64(leagueId)); err != nil { + var req SetLeagueActiveReq + if err := c.BodyParser(&req); err != nil { + h.logger.Error("SetLeagueReq failed", "error", err) + return response.WriteJSON(c, fiber.StatusBadRequest, "Failed to parse request", err, nil) + } + + valErrs, ok := h.validator.Validate(c, req) + if !ok { + return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) + } + + if err := h.leagueSvc.SetLeagueActive(c.Context(), int64(leagueId), req.IsActive); err != nil { response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to update league", err, nil) } diff --git a/internal/web_server/handlers/prematch.go b/internal/web_server/handlers/prematch.go index d325889..1362d94 100644 --- a/internal/web_server/handlers/prematch.go +++ b/internal/web_server/handlers/prematch.go @@ -107,27 +107,35 @@ func (h *Handler) GetRawOddsByMarketID(c *fiber.Ctx) error { func (h *Handler) GetAllUpcomingEvents(c *fiber.Ctx) error { page := c.QueryInt("page", 1) pageSize := c.QueryInt("page_size", 10) - leagueIDQuery, err := strconv.Atoi(c.Query("league_id")) - if err != nil { - h.logger.Error("invalid league id", "error", err) - return response.WriteJSON(c, fiber.StatusBadRequest, "invalid league id", nil, nil) - } + leagueIDQuery := c.Query("league_id") + sportIDQuery := c.Query("sport_id") - sportIDQuery, err := strconv.Atoi(c.Query("sport_id")) - if err != nil { - h.logger.Error("invalid sport id", "error", err) - return response.WriteJSON(c, fiber.StatusBadRequest, "invalid sport id", nil, nil) - } firstStartTimeQuery := c.Query("first_start_time") lastStartTimeQuery := c.Query("last_start_time") - leagueID := domain.ValidInt32{ - Value: int32(leagueIDQuery), - Valid: leagueIDQuery != 0, + var leagueID domain.ValidInt32 + if leagueIDQuery != "" { + leagueIDInt, err := strconv.Atoi(leagueIDQuery) + if err != nil { + h.logger.Error("invalid league id", "error", err) + return response.WriteJSON(c, fiber.StatusBadRequest, "invalid league id", nil, nil) + } + leagueID = domain.ValidInt32{ + Value: int32(leagueIDInt), + Valid: true, + } } - sportID := domain.ValidInt32{ - Value: int32(sportIDQuery), - Valid: sportIDQuery != 0, + var sportID domain.ValidInt32 + if sportIDQuery != "" { + sportIDint, err := strconv.Atoi(sportIDQuery) + if err != nil { + h.logger.Error("invalid sport id", "error", err) + return response.WriteJSON(c, fiber.StatusBadRequest, "invalid sport id", nil, nil) + } + sportID = domain.ValidInt32{ + Value: int32(sportIDint), + Valid: true, + } } var firstStartTime domain.ValidTime @@ -247,3 +255,29 @@ func (h *Handler) GetPrematchOddsByUpcomingID(c *fiber.Ctx) error { return response.WriteJSON(c, fiber.StatusOK, "Prematch odds retrieved successfully", odds, nil) } + +type UpdateEventStatusReq struct { +} + +// SetEventStatusToRemoved godoc +// @Summary Set the event status to removed +// @Description Set the event status to removed +// @Tags event +// @Accept json +// @Produce json +// @Param id path int true "Event ID" +// @Success 200 {object} response.APIResponse +// @Failure 400 {object} response.APIResponse +// @Failure 500 {object} response.APIResponse +// @Router /event/{id}/remove [patch] +func (h *Handler) SetEventStatusToRemoved(c *fiber.Ctx) error { + eventID := c.Params("id") + err := h.eventSvc.UpdateEventStatus(c.Context(), eventID, domain.STATUS_REMOVED) + + if err != nil { + h.logger.Error("Failed to update event status", "eventID", eventID, "error", err) + } + + return response.WriteJSON(c, fiber.StatusOK, "Event updated successfully", nil, nil) + +} diff --git a/internal/web_server/routes.go b/internal/web_server/routes.go index 74f67b5..cdf034c 100644 --- a/internal/web_server/routes.go +++ b/internal/web_server/routes.go @@ -116,17 +116,18 @@ func (a *App) initAppRoutes() { a.fiber.Put("/managers/:id", a.authMiddleware, h.UpdateManagers) a.fiber.Get("/manager/:id/branch", a.authMiddleware, h.GetBranchByManagerID) - a.fiber.Get("/events/odds/:event_id", h.GetPrematchOdds) - a.fiber.Get("/events/odds", h.GetALLPrematchOdds) - a.fiber.Get("/events/odds/upcoming/:upcoming_id/market/:market_id", h.GetRawOddsByMarketID) + a.fiber.Get("/odds/upcoming/:event_id", h.GetPrematchOdds) + a.fiber.Get("/odds", h.GetALLPrematchOdds) + a.fiber.Get("/odds/upcoming/:upcoming_id/market/:market_id", h.GetRawOddsByMarketID) + a.fiber.Get("/odds/upcoming/:upcoming_id", h.GetPrematchOddsByUpcomingID) - a.fiber.Get("/events/:id", h.GetUpcomingEventByID) a.fiber.Get("/events", h.GetAllUpcomingEvents) - a.fiber.Get("/events/odds/upcoming/:upcoming_id", h.GetPrematchOddsByUpcomingID) + a.fiber.Get("/events/:id", h.GetUpcomingEventByID) + a.fiber.Delete("/events/:id", a.authMiddleware, a.SuperAdminOnly, h.SetEventStatusToRemoved) // Leagues a.fiber.Get("/leagues", h.GetAllLeagues) - a.fiber.Get("/leagues/:id/set-active", h.SetLeagueActive) + a.fiber.Put("/leagues/:id/set-active", h.SetLeagueActive) a.fiber.Get("/result/:id", h.GetResultsByEventID)