adding prematchodd

This commit is contained in:
OneTap Technologies 2025-04-11 17:04:25 +03:00
parent a282080133
commit b90fd84aba
15 changed files with 566 additions and 103 deletions

View File

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

View File

@ -35,6 +35,25 @@ ON CONFLICT (event_id, market_id, header, name, handicap) DO UPDATE SET
raw_event_id = EXCLUDED.raw_event_id; raw_event_id = EXCLUDED.raw_event_id;
-- name: GetUpcomingEventIDs :many -- name: GetPrematchOdds :many
SELECT id FROM events SELECT
WHERE is_live = false; id,
event_id,
fi,
raw_event_id,
market_type,
market_name,
market_category,
market_id,
header,
name,
handicap,
odds_value,
section,
category,
raw_odds,
fetched_at,
source,
is_active
FROM odds
WHERE event_id = $1 AND is_active = true AND source = 'b365api';

View File

@ -180,6 +180,53 @@ const docTemplate = `{
} }
} }
}, },
"/prematch/odds/{event_id}": {
"get": {
"description": "Retrieve prematch odds for a specific event by event ID",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"prematch"
],
"summary": "Retrieve prematch odds for an event",
"parameters": [
{
"type": "string",
"description": "Event ID",
"name": "event_id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/domain.Odd"
}
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
}
}
}
},
"/user/checkPhoneEmailExist": { "/user/checkPhoneEmailExist": {
"post": { "post": {
"description": "Check if phone number or email exist", "description": "Check if phone number or email exist",
@ -452,20 +499,80 @@ const docTemplate = `{
} }
}, },
"definitions": { "definitions": {
"domain.Odd": {
"type": "object",
"properties": {
"category": {
"type": "string"
},
"event_id": {
"type": "string"
},
"fetched_at": {
"type": "string"
},
"fi": {
"type": "string"
},
"handicap": {
"type": "string"
},
"header": {
"type": "string"
},
"id": {
"type": "integer"
},
"is_active": {
"type": "boolean"
},
"market_category": {
"type": "string"
},
"market_id": {
"type": "string"
},
"market_name": {
"type": "string"
},
"market_type": {
"type": "string"
},
"name": {
"type": "string"
},
"odds_value": {
"type": "number"
},
"raw_event_id": {
"type": "string"
},
"raw_odds": {
"type": "array",
"items": {}
},
"section": {
"type": "string"
},
"source": {
"type": "string"
}
}
},
"domain.Role": { "domain.Role": {
"type": "string", "type": "string",
"enum": [ "enum": [
"admin",
"customer",
"super_admin", "super_admin",
"admin",
"branch_manager", "branch_manager",
"customer",
"cashier" "cashier"
], ],
"x-enum-varnames": [ "x-enum-varnames": [
"RoleAdmin",
"RoleCustomer",
"RoleSuperAdmin", "RoleSuperAdmin",
"RoleAdmin",
"RoleBranchManager", "RoleBranchManager",
"RoleCustomer",
"RoleCashier" "RoleCashier"
] ]
}, },

View File

@ -172,6 +172,53 @@
} }
} }
}, },
"/prematch/odds/{event_id}": {
"get": {
"description": "Retrieve prematch odds for a specific event by event ID",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"prematch"
],
"summary": "Retrieve prematch odds for an event",
"parameters": [
{
"type": "string",
"description": "Event ID",
"name": "event_id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/domain.Odd"
}
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
}
}
}
},
"/user/checkPhoneEmailExist": { "/user/checkPhoneEmailExist": {
"post": { "post": {
"description": "Check if phone number or email exist", "description": "Check if phone number or email exist",
@ -444,20 +491,80 @@
} }
}, },
"definitions": { "definitions": {
"domain.Odd": {
"type": "object",
"properties": {
"category": {
"type": "string"
},
"event_id": {
"type": "string"
},
"fetched_at": {
"type": "string"
},
"fi": {
"type": "string"
},
"handicap": {
"type": "string"
},
"header": {
"type": "string"
},
"id": {
"type": "integer"
},
"is_active": {
"type": "boolean"
},
"market_category": {
"type": "string"
},
"market_id": {
"type": "string"
},
"market_name": {
"type": "string"
},
"market_type": {
"type": "string"
},
"name": {
"type": "string"
},
"odds_value": {
"type": "number"
},
"raw_event_id": {
"type": "string"
},
"raw_odds": {
"type": "array",
"items": {}
},
"section": {
"type": "string"
},
"source": {
"type": "string"
}
}
},
"domain.Role": { "domain.Role": {
"type": "string", "type": "string",
"enum": [ "enum": [
"admin",
"customer",
"super_admin", "super_admin",
"admin",
"branch_manager", "branch_manager",
"customer",
"cashier" "cashier"
], ],
"x-enum-varnames": [ "x-enum-varnames": [
"RoleAdmin",
"RoleCustomer",
"RoleSuperAdmin", "RoleSuperAdmin",
"RoleAdmin",
"RoleBranchManager", "RoleBranchManager",
"RoleCustomer",
"RoleCashier" "RoleCashier"
] ]
}, },

View File

@ -1,17 +1,57 @@
definitions: definitions:
domain.Odd:
properties:
category:
type: string
event_id:
type: string
fetched_at:
type: string
fi:
type: string
handicap:
type: string
header:
type: string
id:
type: integer
is_active:
type: boolean
market_category:
type: string
market_id:
type: string
market_name:
type: string
market_type:
type: string
name:
type: string
odds_value:
type: number
raw_event_id:
type: string
raw_odds:
items: {}
type: array
section:
type: string
source:
type: string
type: object
domain.Role: domain.Role:
enum: enum:
- admin
- customer
- super_admin - super_admin
- admin
- branch_manager - branch_manager
- customer
- cashier - cashier
type: string type: string
x-enum-varnames: x-enum-varnames:
- RoleAdmin
- RoleCustomer
- RoleSuperAdmin - RoleSuperAdmin
- RoleAdmin
- RoleBranchManager - RoleBranchManager
- RoleCustomer
- RoleCashier - RoleCashier
handlers.CheckPhoneEmailExistReq: handlers.CheckPhoneEmailExistReq:
properties: properties:
@ -275,6 +315,37 @@ paths:
summary: Refresh token summary: Refresh token
tags: tags:
- auth - auth
/prematch/odds/{event_id}:
get:
consumes:
- application/json
description: Retrieve prematch odds for a specific event by event ID
parameters:
- description: Event ID
in: path
name: event_id
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
items:
$ref: '#/definitions/domain.Odd'
type: array
"400":
description: Bad Request
schema:
$ref: '#/definitions/response.APIResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.APIResponse'
summary: Retrieve prematch odds for an event
tags:
- prematch
/user/checkPhoneEmailExist: /user/checkPhoneEmailExist:
post: post:
consumes: consumes:

View File

@ -11,24 +11,62 @@ import (
"github.com/jackc/pgx/v5/pgtype" "github.com/jackc/pgx/v5/pgtype"
) )
const GetUpcomingEventIDs = `-- name: GetUpcomingEventIDs :many const GetPrematchOdds = `-- name: GetPrematchOdds :many
SELECT id FROM events SELECT
WHERE is_live = false id,
event_id,
fi,
raw_event_id,
market_type,
market_name,
market_category,
market_id,
header,
name,
handicap,
odds_value,
section,
category,
raw_odds,
fetched_at,
source,
is_active
FROM odds
WHERE event_id = $1 AND is_active = true AND source = 'b365api'
` `
func (q *Queries) GetUpcomingEventIDs(ctx context.Context) ([]string, error) { func (q *Queries) GetPrematchOdds(ctx context.Context, eventID pgtype.Text) ([]Odd, error) {
rows, err := q.db.Query(ctx, GetUpcomingEventIDs) rows, err := q.db.Query(ctx, GetPrematchOdds, eventID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer rows.Close() defer rows.Close()
var items []string var items []Odd
for rows.Next() { for rows.Next() {
var id string var i Odd
if err := rows.Scan(&id); err != nil { if err := rows.Scan(
&i.ID,
&i.EventID,
&i.Fi,
&i.RawEventID,
&i.MarketType,
&i.MarketName,
&i.MarketCategory,
&i.MarketID,
&i.Header,
&i.Name,
&i.Handicap,
&i.OddsValue,
&i.Section,
&i.Category,
&i.RawOdds,
&i.FetchedAt,
&i.Source,
&i.IsActive,
); err != nil {
return nil, err return nil, err
} }
items = append(items, id) items = append(items, i)
} }
if err := rows.Err(); err != nil { if err := rows.Err(); err != nil {
return nil, err return nil, err

View File

@ -3,7 +3,9 @@ package domain
import ( import (
"encoding/json" "encoding/json"
"time" "time"
) )
type RawMessage interface{} // Change from json.RawMessage to interface{}
type Market struct { type Market struct {
EventID string EventID string
@ -15,9 +17,29 @@ type Market struct {
UpdatedAt time.Time UpdatedAt time.Time
Odds []json.RawMessage Odds []json.RawMessage
// Optional breakdown (extracted from odds) Header string
Header string // only if processing one odd at a time
Name string Name string
Handicap string Handicap string
OddsVal float64 OddsVal float64
} }
type Odd struct {
ID int64 `json:"id"`
EventID string `json:"event_id"`
Fi string `json:"fi"`
RawEventID string `json:"raw_event_id"`
MarketType string `json:"market_type"`
MarketName string `json:"market_name"`
MarketCategory string `json:"market_category"`
MarketID string `json:"market_id"`
Header string `json:"header"`
Name string `json:"name"`
Handicap string `json:"handicap"`
OddsValue float64 `json:"odds_value"`
Section string `json:"section"`
Category string `json:"category"`
RawOdds []RawMessage `json:"raw_odds"`
FetchedAt time.Time `json:"fetched_at"`
Source string `json:"source"`
IsActive bool `json:"is_active"`
}

View File

@ -15,14 +15,14 @@ import (
func (s *Store) SaveNonLiveMarket(ctx context.Context, m domain.Market) error { func (s *Store) SaveNonLiveMarket(ctx context.Context, m domain.Market) error {
if len(m.Odds) == 0 { if len(m.Odds) == 0 {
fmt.Printf("⚠️ Market has no odds: %s (%s)\n", m.MarketType, m.EventID) fmt.Printf(" Market has no odds: %s (%s)\n", m.MarketType, m.EventID)
return nil return nil
} }
for _, raw := range m.Odds { for _, raw := range m.Odds {
var item map[string]interface{} var item map[string]interface{}
if err := json.Unmarshal(raw, &item); err != nil { if err := json.Unmarshal(raw, &item); err != nil {
fmt.Printf(" Invalid odd JSON for %s (%s): %v\n", m.MarketType, m.EventID, err) fmt.Printf(" Invalid odd JSON for %s (%s): %v\n", m.MarketType, m.EventID, err)
continue continue
} }
@ -31,7 +31,6 @@ func (s *Store) SaveNonLiveMarket(ctx context.Context, m domain.Market) error {
handicap := getString(item["handicap"]) handicap := getString(item["handicap"])
oddsVal := getFloat(item["odds"]) oddsVal := getFloat(item["odds"])
// Marshal the full list of odds for reference (if needed)
rawOddsBytes, _ := json.Marshal(m.Odds) rawOddsBytes, _ := json.Marshal(m.Odds)
params := dbgen.InsertNonLiveOddParams{ params := dbgen.InsertNonLiveOddParams{
@ -53,12 +52,12 @@ func (s *Store) SaveNonLiveMarket(ctx context.Context, m domain.Market) error {
err := s.queries.InsertNonLiveOdd(ctx, params) err := s.queries.InsertNonLiveOdd(ctx, params)
if err != nil { if err != nil {
fmt.Printf(" Failed to insert odd for market %s (%s): %v\n", m.MarketType, m.EventID, err) fmt.Printf(" Failed to insert odd for market %s (%s): %v\n", m.MarketType, m.EventID, err)
_ = writeFailedMarketLog(m, err) _ = writeFailedMarketLog(m, err)
continue continue
} }
fmt.Printf("Inserted odd: %s | type=%s | header=%s | name=%s\n", m.EventID, m.MarketType, header, name) fmt.Printf("Inserted odd: %s | type=%s | header=%s | name=%s\n", m.EventID, m.MarketType, header, name)
} }
return nil return nil
} }
@ -110,6 +109,43 @@ func getFloat(v interface{}) float64 {
return 0 return 0
} }
func (s *Store) GetUpcomingEventIDs(ctx context.Context) ([]string, error) { func (s *Store) GetPrematchOdds(ctx context.Context, eventID string) ([]domain.Odd, error) {
return s.queries.GetUpcomingEventIDs(ctx) eventIDParam := pgtype.Text{String: eventID, Valid: eventID != ""}
}
odds, err := s.queries.GetPrematchOdds(ctx, eventIDParam)
if err != nil {
return nil, err
}
domainOdds := make([]domain.Odd, len(odds))
for i, odd := range odds {
domainOdds[i] = domain.Odd{
ID: int64(odd.ID), // Cast int32 to int64
EventID: odd.EventID.String, // Extract the String value
Fi: odd.Fi.String, // Extract the String value
RawEventID: odd.RawEventID.String, // Extract the String value
MarketType: odd.MarketType, // Direct assignment
MarketName: odd.MarketName.String, // Extract the String value
MarketCategory: odd.MarketCategory.String, // Extract the String value
MarketID: odd.MarketID.String, // Extract the String value
Header: odd.Header.String, // Extract the String value
Name: odd.Name.String, // Extract the String value
Handicap: odd.Handicap.String, // Extract the String value
OddsValue: odd.OddsValue.Float64, // Extract the Float64 value
Section: odd.Section, // Direct assignment
Category: odd.Category.String, // Extract the String value
RawOdds: func() []domain.RawMessage {
var rawOdds []domain.RawMessage
if err := json.Unmarshal(odd.RawOdds, &rawOdds); err != nil {
rawOdds = nil
}
return rawOdds
}(),
FetchedAt: odd.FetchedAt.Time, // Extract the Time value
Source: odd.Source.String, // Extract the String value
IsActive: odd.IsActive.Bool, // Extract the Bool value
}
}
return domainOdds, nil
}

View File

@ -1,8 +1,14 @@
package odds package odds
import "context" import (
"context"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
)
type Service interface { type Service interface {
FetchNonLiveOdds(ctx context.Context) error FetchNonLiveOdds(ctx context.Context) error
GetPrematchOdds(ctx context.Context, eventID string) ([]domain.Odd, error)
} }

View File

@ -23,13 +23,13 @@ func New(token string, store *repository.Store) *ServiceImpl {
} }
func (s *ServiceImpl) FetchNonLiveOdds(ctx context.Context) error { func (s *ServiceImpl) FetchNonLiveOdds(ctx context.Context) error {
fmt.Println("🔄 Starting FetchNonLiveOdds...") fmt.Println("Starting FetchNonLiveOdds...")
sportID := 1 sportID := 1
upcomingURL := fmt.Sprintf("https://api.b365api.com/v1/bet365/upcoming?sport_id=%d&token=%s", sportID, s.token) upcomingURL := fmt.Sprintf("https://api.b365api.com/v1/bet365/upcoming?sport_id=%d&token=%s", sportID, s.token)
resp, err := http.Get(upcomingURL) resp, err := http.Get(upcomingURL)
if err != nil { if err != nil {
fmt.Printf("Failed to fetch upcoming: %v\n", err) fmt.Printf("Failed to fetch upcoming: %v\n", err)
return err return err
} }
defer resp.Body.Close() defer resp.Body.Close()
@ -42,23 +42,23 @@ func (s *ServiceImpl) FetchNonLiveOdds(ctx context.Context) error {
} `json:"results"` } `json:"results"`
} }
if err := json.Unmarshal(body, &upcomingData); err != nil || upcomingData.Success != 1 { if err := json.Unmarshal(body, &upcomingData); err != nil || upcomingData.Success != 1 {
fmt.Printf("Failed to decode upcoming response\nRaw: %s\n", string(body)) fmt.Printf("Failed to decode upcoming response\nRaw: %s\n", string(body))
return err return err
} }
for _, ev := range upcomingData.Results { for _, ev := range upcomingData.Results {
eventID := ev.ID eventID := ev.ID
fmt.Printf("📦 Fetching prematch odds for event_id=%s\n", eventID) fmt.Printf("Fetching prematch odds for event_id=%s\n", eventID)
prematchURL := fmt.Sprintf("https://api.b365api.com/v3/bet365/prematch?token=%s&FI=%s", s.token, eventID) prematchURL := fmt.Sprintf("https://api.b365api.com/v3/bet365/prematch?token=%s&FI=%s", s.token, eventID)
oddsResp, err := http.Get(prematchURL) oddsResp, err := http.Get(prematchURL)
if err != nil { if err != nil {
fmt.Printf(" Odds fetch failed for event_id=%s: %v\n", eventID, err) fmt.Printf(" Odds fetch failed for event_id=%s: %v\n", eventID, err)
continue continue
} }
defer oddsResp.Body.Close() defer oddsResp.Body.Close()
oddsBody, _ := io.ReadAll(oddsResp.Body) oddsBody, _ := io.ReadAll(oddsResp.Body)
fmt.Printf("📩 Raw odds response for event_id=%s: %.300s...\n", eventID, string(oddsBody)) fmt.Printf(" Raw odds response for event_id=%s: %.300s...\n", eventID, string(oddsBody))
var oddsData struct { var oddsData struct {
Success int `json:"success"` Success int `json:"success"`
@ -69,7 +69,7 @@ func (s *ServiceImpl) FetchNonLiveOdds(ctx context.Context) error {
} `json:"results"` } `json:"results"`
} }
if err := json.Unmarshal(oddsBody, &oddsData); err != nil || oddsData.Success != 1 || len(oddsData.Results) == 0 { if err := json.Unmarshal(oddsBody, &oddsData); err != nil || oddsData.Success != 1 || len(oddsData.Results) == 0 {
fmt.Printf(" Failed odds decode for event_id=%s\nRaw: %s\n", eventID, string(oddsBody)) fmt.Printf(" Failed odds decode for event_id=%s\nRaw: %s\n", eventID, string(oddsBody))
continue continue
} }
@ -79,23 +79,23 @@ func (s *ServiceImpl) FetchNonLiveOdds(ctx context.Context) error {
finalID = result.FI finalID = result.FI
} }
if finalID == "" { if finalID == "" {
fmt.Println("⚠️ Skipping event with missing final ID.") fmt.Println(" Skipping event with missing final ID.")
continue continue
} }
fmt.Printf("🗂 Saving prematch odds for event_id=%s\n", finalID) fmt.Printf("🗂 Saving prematch odds for event_id=%s\n", finalID)
s.storeSection(ctx, finalID, result.FI, "main", result.Main) s.storeSection(ctx, finalID, result.FI, "main", result.Main)
fmt.Printf(" Finished storing prematch odds for event_id=%s\n", finalID) fmt.Printf(" Finished storing prematch odds for event_id=%s\n", finalID)
} }
fmt.Println(" All prematch odds fetched and stored.") fmt.Println(" All prematch odds fetched and stored.")
return nil return nil
} }
func (s *ServiceImpl) storeSection(ctx context.Context, eventID, fi, sectionName string, section OddsSection) { func (s *ServiceImpl) storeSection(ctx context.Context, eventID, fi, sectionName string, section OddsSection) {
fmt.Printf("📂 Processing section '%s' for event_id=%s\n", sectionName, eventID) fmt.Printf(" Processing section '%s' for event_id=%s\n", sectionName, eventID)
if len(section.Sp) == 0 { if len(section.Sp) == 0 {
fmt.Printf("⚠️ No odds in section '%s' for event_id=%s\n", sectionName, eventID) fmt.Printf(" No odds in section '%s' for event_id=%s\n", sectionName, eventID)
return return
} }
@ -103,9 +103,9 @@ func (s *ServiceImpl) storeSection(ctx context.Context, eventID, fi, sectionName
updatedAt := time.Unix(updatedAtUnix, 0) updatedAt := time.Unix(updatedAtUnix, 0)
for marketType, market := range section.Sp { for marketType, market := range section.Sp {
fmt.Printf("🔍 Processing market: %s (%s)\n", marketType, market.ID) fmt.Printf(" Processing market: %s (%s)\n", marketType, market.ID)
if len(market.Odds) == 0 { if len(market.Odds) == 0 {
fmt.Printf("⚠️ Empty odds for marketType=%s in section=%s\n", marketType, sectionName) fmt.Printf(" Empty odds for marketType=%s in section=%s\n", marketType, sectionName)
continue continue
} }
@ -120,17 +120,16 @@ func (s *ServiceImpl) storeSection(ctx context.Context, eventID, fi, sectionName
Odds: market.Odds, Odds: market.Odds,
} }
fmt.Printf("📦 Saving market to DB: %s (%s)\n", marketType, market.ID) fmt.Printf(" Saving market to DB: %s (%s)\n", marketType, market.ID)
err := s.store.SaveNonLiveMarket(ctx, marketRecord) err := s.store.SaveNonLiveMarket(ctx, marketRecord)
if err != nil { if err != nil {
fmt.Printf(" Save failed for market %s (%s): %v\n", marketType, eventID, err) fmt.Printf(" Save failed for market %s (%s): %v\n", marketType, eventID, err)
} else { } else {
fmt.Printf(" Successfully stored market: %s (%s)\n", marketType, eventID) fmt.Printf(" Successfully stored market: %s (%s)\n", marketType, eventID)
} }
} }
} }
// Odds structures
type OddsMarket struct { type OddsMarket struct {
ID string `json:"id"` ID string `json:"id"`
@ -145,10 +144,22 @@ type OddsSection struct {
Sp map[string]OddsMarket `json:"sp"` Sp map[string]OddsMarket `json:"sp"`
} }
// Utility
func getString(v interface{}) string { func getString(v interface{}) string {
if str, ok := v.(string); ok { if str, ok := v.(string); ok {
return str return str
} }
return "" return ""
} }
func (s *ServiceImpl) GetPrematchOdds(ctx context.Context, eventID string) ([]domain.Odd, error) {
fmt.Printf("Retrieving prematch odds for event_id=%s\n", eventID)
odds, err := s.store.GetPrematchOdds(ctx, eventID)
if err != nil {
fmt.Printf(" Failed to retrieve odds for event_id=%s: %v\n", eventID, err)
return nil, err
}
fmt.Printf(" Retrieved %d odds entries for event_id=%s\n", len(odds), eventID)
return odds, nil
}

View File

@ -13,6 +13,7 @@ type Service struct {
otpStore OtpStore otpStore OtpStore
smsGateway SmsGateway smsGateway SmsGateway
emailGateway EmailGateway emailGateway EmailGateway
} }
func NewService( func NewService(

View File

@ -1,55 +1,60 @@
package httpserver package httpserver
import ( import (
"fmt" "fmt"
"log/slog" "log/slog"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/authentication" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/authentication"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/user" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds"
jwtutil "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/jwt" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/user"
customvalidator "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/validator" jwtutil "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/jwt"
"github.com/bytedance/sonic" customvalidator "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/validator"
"github.com/gofiber/fiber/v2"
"github.com/bytedance/sonic"
"github.com/gofiber/fiber/v2"
) )
type App struct { type App struct {
fiber *fiber.App fiber *fiber.App
logger *slog.Logger logger *slog.Logger
port int port int
authSvc *authentication.Service authSvc *authentication.Service
userSvc *user.Service userSvc *user.Service
validator *customvalidator.CustomValidator validator *customvalidator.CustomValidator
JwtConfig jwtutil.JwtConfig JwtConfig jwtutil.JwtConfig
prematchSvc *odds.ServiceImpl
} }
func NewApp( func NewApp(
port int, validator *customvalidator.CustomValidator, port int, validator *customvalidator.CustomValidator,
authSvc *authentication.Service, authSvc *authentication.Service,
logger *slog.Logger, logger *slog.Logger,
JwtConfig jwtutil.JwtConfig, JwtConfig jwtutil.JwtConfig,
userSvc *user.Service, userSvc *user.Service,
prematchSvc *odds.ServiceImpl,
) *App { ) *App {
app := fiber.New(fiber.Config{ app := fiber.New(fiber.Config{
CaseSensitive: true, CaseSensitive: true,
DisableHeaderNormalizing: true, DisableHeaderNormalizing: true,
JSONEncoder: sonic.Marshal, JSONEncoder: sonic.Marshal,
JSONDecoder: sonic.Unmarshal, JSONDecoder: sonic.Unmarshal,
}) })
s := &App{ s := &App{
fiber: app, fiber: app,
port: port, port: port,
authSvc: authSvc, authSvc: authSvc,
validator: validator, validator: validator,
logger: logger, logger: logger,
JwtConfig: JwtConfig, JwtConfig: JwtConfig,
userSvc: userSvc, userSvc: userSvc,
} prematchSvc: prematchSvc,
}
s.initAppRoutes() s.initAppRoutes()
return s return s
} }
func (a *App) Run() error { func (a *App) Run() error {
return a.fiber.Listen(fmt.Sprintf(":%d", a.port)) return a.fiber.Listen(fmt.Sprintf(":%d", a.port))
} }

View File

@ -24,14 +24,14 @@ func StartDataFetchingCrons(eventService eventsvc.Service, oddsService oddssvc.S
} }
}, },
}, },
// { {
// spec: "*/5 * * * * *", // Every 5 seconds spec: "*/5 * * * * *", // Every 5 seconds
// task: func() { task: func() {
// if err := eventService.FetchLiveEvents(context.Background()); err != nil { if err := eventService.FetchLiveEvents(context.Background()); err != nil {
// log.Printf(" FetchLiveEvents error: %v", err) log.Printf(" FetchLiveEvents error: %v", err)
// } }
// }, },
// }, },
{ {
spec: "*/5 * * * * *", // Every 5 seconds spec: "*/5 * * * * *", // Every 5 seconds
task: func() { task: func() {

View File

@ -0,0 +1,37 @@
package handlers
import (
"github.com/gofiber/fiber/v2"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds"
"github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response"
"log/slog"
)
// GetPrematchOdds godoc
// @Summary Retrieve prematch odds for an event
// @Description Retrieve prematch odds for a specific event by event ID
// @Tags prematch
// @Accept json
// @Produce json
// @Param event_id path string true "Event ID"
// @Success 200 {array} domain.Odd
// @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /prematch/odds/{event_id} [get]
func GetPrematchOdds(logger *slog.Logger, prematchSvc *odds.ServiceImpl) fiber.Handler {
return func(c *fiber.Ctx) error {
eventID := c.Params("event_id")
if eventID == "" {
logger.Error("GetPrematchOdds failed: missing event_id")
return response.WriteJSON(c, fiber.StatusBadRequest, "Missing event_id", nil, nil)
}
odds, err := prematchSvc.GetPrematchOdds(c.Context(), eventID)
if err != nil {
logger.Error("GetPrematchOdds failed", "error", err)
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve odds", nil, nil)
}
return response.WriteJSON(c, fiber.StatusOK, "Prematch odds retrieved successfully", odds, nil)
}
}

View File

@ -26,6 +26,9 @@ func (a *App) initAppRoutes() {
a.fiber.Post("/user/sendRegisterCode", handlers.SendRegisterCode(a.logger, a.userSvc, a.validator)) a.fiber.Post("/user/sendRegisterCode", handlers.SendRegisterCode(a.logger, a.userSvc, a.validator))
a.fiber.Post("/user/checkPhoneEmailExist", handlers.CheckPhoneEmailExist(a.logger, a.userSvc, a.validator)) a.fiber.Post("/user/checkPhoneEmailExist", handlers.CheckPhoneEmailExist(a.logger, a.userSvc, a.validator))
a.fiber.Get("/user/profile", a.authMiddleware, handlers.UserProfile(a.logger, a.userSvc)) a.fiber.Get("/user/profile", a.authMiddleware, handlers.UserProfile(a.logger, a.userSvc))
a.fiber.Get("/prematch/odds/:event_id", handlers.GetPrematchOdds(a.logger, a.prematchSvc))
// Swagger // Swagger
a.fiber.Get("/swagger/*", fiberSwagger.WrapHandler) a.fiber.Get("/swagger/*", fiberSwagger.WrapHandler)
} }