adding upcoming data

This commit is contained in:
OneTap Technologies 2025-04-12 15:55:42 +03:00
parent 1726cfd63b
commit 6c478df2b4
13 changed files with 679 additions and 13 deletions

View File

@ -67,7 +67,7 @@ func main() {
app := httpserver.NewApp(cfg.Port, v, authSvc, logger, jwtutil.JwtConfig{
JwtAccessKey: cfg.JwtKey,
JwtAccessExpiry: cfg.AccessExpiry,
}, userSvc, oddsSvc)
}, userSvc, oddsSvc, eventSvc)
logger.Info("Starting server", "port", cfg.Port)
if err := app.Run(); err != nil {

View File

@ -64,4 +64,50 @@ ON CONFLICT (id) DO UPDATE SET
-- name: ListLiveEvents :many
SELECT id FROM events WHERE is_live = true;
SELECT id FROM events WHERE is_live = true;
-- 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,
is_live,
status,
fetched_at
FROM events
WHERE is_live = false
AND status = 'upcoming'
ORDER BY start_time ASC;
-- 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,
is_live,
status,
fetched_at
FROM events
WHERE id = $1
AND is_live = false
AND status = 'upcoming'
LIMIT 1;

View File

@ -180,6 +180,82 @@ const docTemplate = `{
}
}
},
"/prematch/events": {
"get": {
"description": "Retrieve all upcoming events from the database",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"prematch"
],
"summary": "Retrieve all upcoming events",
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/domain.UpcomingEvent"
}
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
}
}
}
},
"/prematch/events/{id}": {
"get": {
"description": "Retrieve an upcoming event by ID",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"prematch"
],
"summary": "Retrieve an upcoming by ID",
"parameters": [
{
"type": "string",
"description": "ID",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/domain.UpcomingEvent"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
}
}
}
},
"/prematch/odds": {
"get": {
"description": "Retrieve all prematch odds from the database",
@ -666,6 +742,63 @@ const docTemplate = `{
"RoleCashier"
]
},
"domain.UpcomingEvent": {
"type": "object",
"properties": {
"awayKitImage": {
"description": "Kit or image for away team (optional)",
"type": "string"
},
"awayTeam": {
"description": "Away team name (can be empty/null)",
"type": "string"
},
"awayTeamID": {
"description": "Away team ID (can be empty/null)",
"type": "string"
},
"homeKitImage": {
"description": "Kit or image for home team (optional)",
"type": "string"
},
"homeTeam": {
"description": "Home team name (if available)",
"type": "string"
},
"homeTeamID": {
"description": "Home team ID",
"type": "string"
},
"id": {
"description": "Event ID",
"type": "string"
},
"leagueCC": {
"description": "League country code",
"type": "string"
},
"leagueID": {
"description": "League ID",
"type": "string"
},
"leagueName": {
"description": "League name",
"type": "string"
},
"matchName": {
"description": "Match or event name",
"type": "string"
},
"sportID": {
"description": "Sport ID",
"type": "string"
},
"startTime": {
"description": "Converted from \"time\" field in UNIX format",
"type": "string"
}
}
},
"handlers.CheckPhoneEmailExistReq": {
"type": "object",
"properties": {

View File

@ -172,6 +172,82 @@
}
}
},
"/prematch/events": {
"get": {
"description": "Retrieve all upcoming events from the database",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"prematch"
],
"summary": "Retrieve all upcoming events",
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/domain.UpcomingEvent"
}
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
}
}
}
},
"/prematch/events/{id}": {
"get": {
"description": "Retrieve an upcoming event by ID",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"prematch"
],
"summary": "Retrieve an upcoming by ID",
"parameters": [
{
"type": "string",
"description": "ID",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/domain.UpcomingEvent"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
}
}
}
},
"/prematch/odds": {
"get": {
"description": "Retrieve all prematch odds from the database",
@ -658,6 +734,63 @@
"RoleCashier"
]
},
"domain.UpcomingEvent": {
"type": "object",
"properties": {
"awayKitImage": {
"description": "Kit or image for away team (optional)",
"type": "string"
},
"awayTeam": {
"description": "Away team name (can be empty/null)",
"type": "string"
},
"awayTeamID": {
"description": "Away team ID (can be empty/null)",
"type": "string"
},
"homeKitImage": {
"description": "Kit or image for home team (optional)",
"type": "string"
},
"homeTeam": {
"description": "Home team name (if available)",
"type": "string"
},
"homeTeamID": {
"description": "Home team ID",
"type": "string"
},
"id": {
"description": "Event ID",
"type": "string"
},
"leagueCC": {
"description": "League country code",
"type": "string"
},
"leagueID": {
"description": "League ID",
"type": "string"
},
"leagueName": {
"description": "League name",
"type": "string"
},
"matchName": {
"description": "Match or event name",
"type": "string"
},
"sportID": {
"description": "Sport ID",
"type": "string"
},
"startTime": {
"description": "Converted from \"time\" field in UNIX format",
"type": "string"
}
}
},
"handlers.CheckPhoneEmailExistReq": {
"type": "object",
"properties": {

View File

@ -63,6 +63,48 @@ definitions:
- RoleBranchManager
- RoleCustomer
- RoleCashier
domain.UpcomingEvent:
properties:
awayKitImage:
description: Kit or image for away team (optional)
type: string
awayTeam:
description: Away team name (can be empty/null)
type: string
awayTeamID:
description: Away team ID (can be empty/null)
type: string
homeKitImage:
description: Kit or image for home team (optional)
type: string
homeTeam:
description: Home team name (if available)
type: string
homeTeamID:
description: Home team ID
type: string
id:
description: Event ID
type: string
leagueCC:
description: League country code
type: string
leagueID:
description: League ID
type: string
leagueName:
description: League name
type: string
matchName:
description: Match or event name
type: string
sportID:
description: Sport ID
type: string
startTime:
description: Converted from "time" field in UNIX format
type: string
type: object
handlers.CheckPhoneEmailExistReq:
properties:
email:
@ -325,6 +367,56 @@ paths:
summary: Refresh token
tags:
- auth
/prematch/events:
get:
consumes:
- application/json
description: Retrieve all upcoming events from the database
produces:
- application/json
responses:
"200":
description: OK
schema:
items:
$ref: '#/definitions/domain.UpcomingEvent'
type: array
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.APIResponse'
summary: Retrieve all upcoming events
tags:
- prematch
/prematch/events/{id}:
get:
consumes:
- application/json
description: Retrieve an upcoming event by ID
parameters:
- description: ID
in: path
name: id
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/domain.UpcomingEvent'
"400":
description: Bad Request
schema:
$ref: '#/definitions/response.APIResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.APIResponse'
summary: Retrieve an upcoming by ID
tags:
- prematch
/prematch/odds:
get:
consumes:

View File

@ -11,6 +11,154 @@ import (
"github.com/jackc/pgx/v5/pgtype"
)
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,
is_live,
status,
fetched_at
FROM events
WHERE is_live = false
AND status = 'upcoming'
ORDER BY start_time ASC
`
type GetAllUpcomingEventsRow struct {
ID string
SportID pgtype.Text
MatchName pgtype.Text
HomeTeam pgtype.Text
AwayTeam pgtype.Text
HomeTeamID pgtype.Text
AwayTeamID pgtype.Text
HomeKitImage pgtype.Text
AwayKitImage pgtype.Text
LeagueID pgtype.Text
LeagueName pgtype.Text
LeagueCc pgtype.Text
StartTime pgtype.Timestamp
IsLive pgtype.Bool
Status pgtype.Text
FetchedAt pgtype.Timestamp
}
func (q *Queries) GetAllUpcomingEvents(ctx context.Context) ([]GetAllUpcomingEventsRow, error) {
rows, err := q.db.Query(ctx, GetAllUpcomingEvents)
if err != nil {
return nil, err
}
defer rows.Close()
var items []GetAllUpcomingEventsRow
for rows.Next() {
var i GetAllUpcomingEventsRow
if err := rows.Scan(
&i.ID,
&i.SportID,
&i.MatchName,
&i.HomeTeam,
&i.AwayTeam,
&i.HomeTeamID,
&i.AwayTeamID,
&i.HomeKitImage,
&i.AwayKitImage,
&i.LeagueID,
&i.LeagueName,
&i.LeagueCc,
&i.StartTime,
&i.IsLive,
&i.Status,
&i.FetchedAt,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
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,
is_live,
status,
fetched_at
FROM events
WHERE id = $1
AND is_live = false
AND status = 'upcoming'
LIMIT 1
`
type GetUpcomingByIDRow struct {
ID string
SportID pgtype.Text
MatchName pgtype.Text
HomeTeam pgtype.Text
AwayTeam pgtype.Text
HomeTeamID pgtype.Text
AwayTeamID pgtype.Text
HomeKitImage pgtype.Text
AwayKitImage pgtype.Text
LeagueID pgtype.Text
LeagueName pgtype.Text
LeagueCc pgtype.Text
StartTime pgtype.Timestamp
IsLive pgtype.Bool
Status pgtype.Text
FetchedAt pgtype.Timestamp
}
func (q *Queries) GetUpcomingByID(ctx context.Context, id string) (GetUpcomingByIDRow, error) {
row := q.db.QueryRow(ctx, GetUpcomingByID, id)
var i GetUpcomingByIDRow
err := row.Scan(
&i.ID,
&i.SportID,
&i.MatchName,
&i.HomeTeam,
&i.AwayTeam,
&i.HomeTeamID,
&i.AwayTeamID,
&i.HomeKitImage,
&i.AwayKitImage,
&i.LeagueID,
&i.LeagueName,
&i.LeagueCc,
&i.StartTime,
&i.IsLive,
&i.Status,
&i.FetchedAt,
)
return i, err
}
const InsertEvent = `-- name: InsertEvent :exec
INSERT INTO events (
id, sport_id, match_name, home_team, away_team,

View File

@ -6,6 +6,7 @@ import (
dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
// "github.com/SamuelTariku/FortuneBet-Backend/internal/services/event"
"github.com/jackc/pgx/v5/pgtype"
)
@ -59,3 +60,52 @@ func (s *Store) SaveUpcomingEvent(ctx context.Context, e domain.UpcomingEvent) e
func (s *Store) GetLiveEventIDs(ctx context.Context) ([]string, error) {
return s.queries.ListLiveEvents(ctx)
}
func (s *Store) GetAllUpcomingEvents(ctx context.Context) ([]domain.UpcomingEvent, error) {
events, err := s.queries.GetAllUpcomingEvents(ctx)
if err != nil {
return nil, err
}
upcomingEvents := make([]domain.UpcomingEvent, len(events))
for i, e := range events {
upcomingEvents[i] = domain.UpcomingEvent{
ID: e.ID,
SportID: e.SportID.String,
MatchName: e.MatchName.String,
HomeTeam: e.HomeTeam.String,
AwayTeam: e.AwayTeam.String,
HomeTeamID: e.HomeTeamID.String,
AwayTeamID: e.AwayTeamID.String,
HomeKitImage: e.HomeKitImage.String,
AwayKitImage: e.AwayKitImage.String,
LeagueID: e.LeagueID.String,
LeagueName: e.LeagueName.String,
LeagueCC: e.LeagueCc.String,
StartTime: e.StartTime.Time.UTC(),
}
}
return upcomingEvents, nil
}
func (s *Store) GetUpcomingEventByID(ctx context.Context, ID string) (domain.UpcomingEvent, error) {
event, err := s.queries.GetUpcomingByID(ctx, ID)
if err != nil {
return domain.UpcomingEvent{}, err
}
return domain.UpcomingEvent{
ID: event.ID,
SportID: event.SportID.String,
MatchName: event.MatchName.String,
HomeTeam: event.HomeTeam.String,
AwayTeam: event.AwayTeam.String,
HomeTeamID: event.HomeTeamID.String,
AwayTeamID: event.AwayTeamID.String,
HomeKitImage: event.HomeKitImage.String,
AwayKitImage: event.AwayKitImage.String,
LeagueID: event.LeagueID.String,
LeagueName: event.LeagueName.String,
LeagueCC: event.LeagueCc.String,
StartTime: event.StartTime.Time.UTC(),
}, nil
}

View File

@ -1,8 +1,14 @@
package event
import "context"
import (
"context"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
)
type Service interface {
FetchLiveEvents(ctx context.Context) error
FetchUpcomingEvents(ctx context.Context) error
GetAllUpcomingEvents(ctx context.Context) ([]domain.UpcomingEvent, error)
GetUpcomingEventByID(ctx context.Context, ID string) (domain.UpcomingEvent, error)
}

View File

@ -12,6 +12,7 @@ import (
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"github.com/SamuelTariku/FortuneBet-Backend/internal/repository"
// "github.com/SamuelTariku/FortuneBet-Backend/internal/services/event"
)
type service struct {
@ -172,4 +173,11 @@ func getInt(v interface{}) int {
return int(f)
}
return 0
}
}
func (s *service) GetAllUpcomingEvents(ctx context.Context) ([]domain.UpcomingEvent, error) {
return s.store.GetAllUpcomingEvents(ctx)
}
func (s *service) GetUpcomingEventByID(ctx context.Context, ID string) (domain.UpcomingEvent, error) {
return s.store.GetUpcomingEventByID(ctx, ID)
}

View File

@ -5,7 +5,8 @@ import (
"log/slog"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/authentication"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/event"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/user"
jwtutil "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/jwt"
customvalidator "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/validator"
@ -22,7 +23,8 @@ type App struct {
userSvc *user.Service
validator *customvalidator.CustomValidator
JwtConfig jwtutil.JwtConfig
prematchSvc *odds.ServiceImpl
prematchSvc *odds.ServiceImpl
eventSvc event.Service
}
func NewApp(
@ -32,6 +34,7 @@ func NewApp(
JwtConfig jwtutil.JwtConfig,
userSvc *user.Service,
prematchSvc *odds.ServiceImpl,
eventSvc event.Service,
) *App {
app := fiber.New(fiber.Config{
CaseSensitive: true,
@ -47,7 +50,8 @@ func NewApp(
logger: logger,
JwtConfig: JwtConfig,
userSvc: userSvc,
prematchSvc: prematchSvc,
prematchSvc: prematchSvc,
eventSvc: eventSvc,
}
s.initAppRoutes()

View File

@ -18,7 +18,7 @@ func StartDataFetchingCrons(eventService eventsvc.Service, oddsService oddssvc.S
}{
{
spec: "*/5 * * * * *", // Every 5 seconds
spec: "0 0 * * * *", // Every hour at minute 0 and second 0
task: func() {
if err := eventService.FetchUpcomingEvents(context.Background()); err != nil {
log.Printf("FetchUpcomingEvents error: %v", err)
@ -26,8 +26,6 @@ func StartDataFetchingCrons(eventService eventsvc.Service, oddsService oddssvc.S
},
},
{
spec: "*/5 * * * * *", // Every 5 seconds
task: func() {
@ -38,8 +36,8 @@ func StartDataFetchingCrons(eventService eventsvc.Service, oddsService oddssvc.S
},
{
spec: "0 * * * * *", // Every 1 minute
{
spec: "0 0 * * * *", // Every hour at minute 0 and second 0
task: func() {
if err := oddsService.FetchNonLiveOdds(context.Background()); err != nil {
log.Printf("FetchNonLiveOdds error: %v", err)

View File

@ -3,6 +3,7 @@ package handlers
import (
"github.com/gofiber/fiber/v2"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/event"
"github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response"
"log/slog"
)
@ -77,4 +78,48 @@ func GetRawOddsByID(logger *slog.Logger, prematchSvc *odds.ServiceImpl) fiber.Ha
return response.WriteJSON(c, fiber.StatusOK, "Raw odds retrieved successfully", rawOdds, nil)
}
}
}
// @Summary Retrieve all upcoming events
// @Description Retrieve all upcoming events from the database
// @Tags prematch
// @Accept json
// @Produce json
// @Success 200 {array} domain.UpcomingEvent
// @Failure 500 {object} response.APIResponse
// @Router /prematch/events [get]
func GetAllUpcomingEvents(logger *slog.Logger, eventSvc event.Service) fiber.Handler {
return func(c *fiber.Ctx) error {
events, err := eventSvc.GetAllUpcomingEvents(c.Context())
if err != nil {
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve all upcoming events", nil, nil)
}
return response.WriteJSON(c, fiber.StatusOK, "All upcoming events retrieved successfully", events, nil)
}
}
// @Summary Retrieve an upcoming by ID
// @Description Retrieve an upcoming event by ID
// @Tags prematch
// @Accept json
// @Produce json
// @Param id path string true "ID"
// @Success 200 {object} domain.UpcomingEvent
// @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /prematch/events/{id} [get]
func GetUpcomingEventByID(logger *slog.Logger, eventSvc event.Service) fiber.Handler {
return func(c *fiber.Ctx) error {
id := c.Params("id")
if id == "" {
return response.WriteJSON(c, fiber.StatusBadRequest, "Missing id", nil, nil)
}
event, err := eventSvc.GetUpcomingEventByID(c.Context(), id)
if err != nil {
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve upcoming event", nil, nil)
}
return response.WriteJSON(c, fiber.StatusOK, "Upcoming event retrieved successfully", event, nil)
}
}

View File

@ -30,6 +30,9 @@ func (a *App) initAppRoutes() {
a.fiber.Get("/prematch/odds/:event_id", handlers.GetPrematchOdds(a.logger, a.prematchSvc))
a.fiber.Get("/prematch/odds", handlers.GetALLPrematchOdds(a.logger, a.prematchSvc))
a.fiber.Get("/prematch/odds/raw/:raw_odds_id", handlers.GetRawOddsByID(a.logger, a.prematchSvc))
a.fiber.Get("/prematch/events/:id", handlers.GetUpcomingEventByID(a.logger, a.eventSvc))
a.fiber.Get("/prematch/events", handlers.GetAllUpcomingEvents(a.logger, a.eventSvc))
// Swagger
a.fiber.Get("/swagger/*", fiberSwagger.WrapHandler)
}