269 lines
8.5 KiB
Go
269 lines
8.5 KiB
Go
package ticket
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/event"
|
|
notificationservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/notfication"
|
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
var (
|
|
// ErrGenerateRandomOutcome = errors.New("Failed to generate any random outcome for events")
|
|
// ErrOutcomesNotCompleted = errors.New("Some bet outcomes are still pending")
|
|
ErrEventHasNotEnded = errors.New("Event has not ended yet")
|
|
ErrNoEventsAvailable = errors.New("Not enough events available with the given filters")
|
|
ErrEventHasBeenRemoved = errors.New("Event has been removed")
|
|
ErrTooManyOutcomesForTicket = errors.New("Too many odds/outcomes for a single ticket")
|
|
ErrTicketAmountTooHigh = errors.New("Cannot create a ticket with an amount above limit")
|
|
ErrTicketLimitForSingleUser = errors.New("Number of Ticket Limit reached")
|
|
ErrTicketWinningTooHigh = errors.New("Total Winnings over set limit")
|
|
|
|
ErrRawOddInvalid = errors.New("Prematch Raw Odd is Invalid")
|
|
)
|
|
|
|
type Service struct {
|
|
ticketStore TicketStore
|
|
eventSvc event.Service
|
|
prematchSvc odds.ServiceImpl
|
|
mongoLogger *zap.Logger
|
|
notificationSvc *notificationservice.Service
|
|
}
|
|
|
|
func NewService(
|
|
ticketStore TicketStore,
|
|
eventSvc event.Service,
|
|
prematchSvc odds.ServiceImpl,
|
|
mongoLogger *zap.Logger,
|
|
notificationSvc *notificationservice.Service,
|
|
) *Service {
|
|
return &Service{
|
|
ticketStore: ticketStore,
|
|
eventSvc: eventSvc,
|
|
prematchSvc: prematchSvc,
|
|
mongoLogger: mongoLogger,
|
|
notificationSvc: notificationSvc,
|
|
}
|
|
}
|
|
|
|
func (s *Service) GenerateTicketOutcome(ctx context.Context, eventID int64, marketID int64, oddID int64) (domain.CreateTicketOutcome, error) {
|
|
eventIDStr := strconv.FormatInt(eventID, 10)
|
|
marketIDStr := strconv.FormatInt(marketID, 10)
|
|
oddIDStr := strconv.FormatInt(oddID, 10)
|
|
event, err := s.eventSvc.GetUpcomingEventByID(ctx, eventIDStr)
|
|
if err != nil {
|
|
s.mongoLogger.Error("failed to fetch upcoming event by ID",
|
|
zap.Int64("event_id", eventID),
|
|
zap.Error(err),
|
|
)
|
|
return domain.CreateTicketOutcome{}, ErrEventHasBeenRemoved
|
|
}
|
|
|
|
// Checking to make sure the event hasn't already started
|
|
currentTime := time.Now()
|
|
if event.StartTime.Before(currentTime) {
|
|
s.mongoLogger.Error("event has already started",
|
|
zap.Int64("event_id", eventID),
|
|
zap.Time("event_start_time", event.StartTime),
|
|
zap.Time("current_time", currentTime),
|
|
)
|
|
return domain.CreateTicketOutcome{}, ErrEventHasNotEnded
|
|
}
|
|
|
|
odds, err := s.prematchSvc.GetRawOddsByMarketID(ctx, marketIDStr, eventIDStr)
|
|
|
|
if err != nil {
|
|
s.mongoLogger.Error("failed to get raw odds by market ID",
|
|
zap.Int64("event_id", eventID),
|
|
zap.Int64("market_id", marketID),
|
|
zap.Error(err),
|
|
)
|
|
// return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid market id", err, nil)
|
|
|
|
}
|
|
type rawOddType struct {
|
|
ID string
|
|
Name string
|
|
Odds string
|
|
Header string
|
|
Handicap string
|
|
}
|
|
var selectedOdd rawOddType
|
|
var isOddFound bool = false
|
|
for _, raw := range odds.RawOdds {
|
|
var rawOdd rawOddType
|
|
rawBytes, err := json.Marshal(raw)
|
|
err = json.Unmarshal(rawBytes, &rawOdd)
|
|
if err != nil {
|
|
s.mongoLogger.Error("failed to unmarshal raw ods",
|
|
zap.Int64("event_id", eventID),
|
|
zap.String("rawOddID", rawOdd.ID),
|
|
zap.Error(err),
|
|
)
|
|
continue
|
|
}
|
|
|
|
if rawOdd.ID == oddIDStr {
|
|
selectedOdd = rawOdd
|
|
isOddFound = true
|
|
}
|
|
}
|
|
|
|
if !isOddFound {
|
|
// return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid odd id", nil, nil)
|
|
s.mongoLogger.Error("Invalid Odd ID",
|
|
zap.Int64("event_id", eventID),
|
|
zap.String("oddIDStr", oddIDStr),
|
|
)
|
|
return domain.CreateTicketOutcome{}, ErrRawOddInvalid
|
|
}
|
|
|
|
parsedOdd, err := strconv.ParseFloat(selectedOdd.Odds, 32)
|
|
if err != nil {
|
|
s.mongoLogger.Error("failed to parse selected odd value",
|
|
zap.String("odd", selectedOdd.Odds),
|
|
zap.Int64("odd_id", oddID),
|
|
zap.Error(err),
|
|
)
|
|
return domain.CreateTicketOutcome{}, err
|
|
}
|
|
|
|
newOutcome := domain.CreateTicketOutcome{
|
|
EventID: eventID,
|
|
OddID: oddID,
|
|
MarketID: marketID,
|
|
HomeTeamName: event.HomeTeam,
|
|
AwayTeamName: event.AwayTeam,
|
|
MarketName: odds.MarketName,
|
|
Odd: float32(parsedOdd),
|
|
OddName: selectedOdd.Name,
|
|
OddHeader: selectedOdd.Header,
|
|
OddHandicap: selectedOdd.Handicap,
|
|
Expires: event.StartTime,
|
|
}
|
|
|
|
// outcomes = append(outcomes, )
|
|
|
|
return newOutcome, nil
|
|
|
|
}
|
|
|
|
func (s *Service) CreateTicket(ctx context.Context, req domain.CreateTicketReq, clientIP string) (domain.Ticket, int64, error) {
|
|
|
|
// s.mongoLogger.Info("Creating ticket")
|
|
// TODO Validate Outcomes Here and make sure they didn't expire
|
|
// Validation for creating tickets
|
|
if len(req.Outcomes) > 30 {
|
|
// return response.WriteJSON(c, fiber.StatusBadRequest, "Too many odds/outcomes selected", nil, nil)
|
|
return domain.Ticket{}, 0, ErrTooManyOutcomesForTicket
|
|
|
|
}
|
|
|
|
if req.Amount > 100000 {
|
|
// return response.WriteJSON(c, fiber.StatusBadRequest, "Cannot create a ticket with an amount above 100,000 birr", nil, nil)
|
|
return domain.Ticket{}, 0, ErrTicketAmountTooHigh
|
|
}
|
|
|
|
count, err := s.CountTicketByIP(ctx, clientIP)
|
|
|
|
if err != nil {
|
|
// return response.WriteJSON(c, fiber.StatusInternalServerError, "Error fetching user info", nil, nil)
|
|
return domain.Ticket{}, 0, err
|
|
}
|
|
|
|
if count > 50 {
|
|
// return response.WriteJSON(c, fiber.StatusBadRequest, "Ticket Limit reached", nil, nil)
|
|
return domain.Ticket{}, 0, ErrTicketLimitForSingleUser
|
|
}
|
|
var outcomes []domain.CreateTicketOutcome = make([]domain.CreateTicketOutcome, 0, len(req.Outcomes))
|
|
var totalOdds float32 = 1
|
|
for _, outcomeReq := range req.Outcomes {
|
|
newOutcome, err := s.GenerateTicketOutcome(ctx, outcomeReq.EventID, outcomeReq.MarketID, outcomeReq.OddID)
|
|
if err != nil {
|
|
s.mongoLogger.Error("failed to generate outcome",
|
|
zap.Int64("event_id", outcomeReq.EventID),
|
|
zap.Int64("market_id", outcomeReq.MarketID),
|
|
zap.Int64("odd_id", outcomeReq.OddID),
|
|
zap.Error(err),
|
|
)
|
|
return domain.Ticket{}, 0, err
|
|
}
|
|
totalOdds *= float32(newOutcome.Odd)
|
|
outcomes = append(outcomes, newOutcome)
|
|
}
|
|
totalWinnings := req.Amount * totalOdds
|
|
if totalWinnings > 1000000 {
|
|
s.mongoLogger.Error("Total Winnings over limit", zap.Float32("Total Odds", totalOdds), zap.Float32("amount", req.Amount))
|
|
// return response.WriteJSON(c, fiber.StatusBadRequest, "Cannot create a ticket with 1,000,000 winnings", nil, nil)
|
|
return domain.Ticket{}, 0, ErrTicketWinningTooHigh
|
|
}
|
|
|
|
ticket, err := s.ticketStore.CreateTicket(ctx, domain.CreateTicket{
|
|
Amount: domain.ToCurrency(req.Amount),
|
|
TotalOdds: totalOdds,
|
|
IP: clientIP,
|
|
})
|
|
if err != nil {
|
|
s.mongoLogger.Error("Error Creating Ticket", zap.Float32("Total Odds", totalOdds), zap.Float32("amount", req.Amount))
|
|
return domain.Ticket{}, 0, err
|
|
}
|
|
|
|
// Add the ticket id now that it has fetched from the database
|
|
for index := range outcomes {
|
|
outcomes[index].TicketID = ticket.ID
|
|
}
|
|
|
|
rows, err := s.CreateTicketOutcome(ctx, outcomes)
|
|
|
|
if err != nil {
|
|
s.mongoLogger.Error("Error Creating Ticket Outcomes", zap.Any("outcomes", outcomes))
|
|
return domain.Ticket{}, rows, err
|
|
}
|
|
|
|
// updates := domain.MetricUpdates{
|
|
// TotalLiveTicketsDelta: domain.PtrInt64(1),
|
|
// }
|
|
|
|
// if err := s.notificationSvc.UpdateLiveMetrics(ctx, updates); err != nil {
|
|
// // handle error
|
|
// }
|
|
|
|
return ticket, rows, nil
|
|
}
|
|
|
|
// func (s *Service) CreateTicket(ctx context.Context, ticket domain.CreateTicket) (domain.Ticket, error) {
|
|
// return s.ticketStore.CreateTicket(ctx, ticket)
|
|
// }
|
|
|
|
func (s *Service) CreateTicketOutcome(ctx context.Context, outcomes []domain.CreateTicketOutcome) (int64, error) {
|
|
return s.ticketStore.CreateTicketOutcome(ctx, outcomes)
|
|
}
|
|
|
|
func (s *Service) GetTicketByID(ctx context.Context, id int64) (domain.GetTicket, error) {
|
|
return s.ticketStore.GetTicketByID(ctx, id)
|
|
}
|
|
func (s *Service) GetAllTickets(ctx context.Context) ([]domain.GetTicket, error) {
|
|
return s.ticketStore.GetAllTickets(ctx)
|
|
}
|
|
|
|
func (s *Service) CountTicketByIP(ctx context.Context, IP string) (int64, error) {
|
|
return s.ticketStore.CountTicketByIP(ctx, IP)
|
|
}
|
|
|
|
func (s *Service) UpdateTicketOutcomeStatus(ctx context.Context, id int64, status domain.OutcomeStatus) error {
|
|
return s.ticketStore.UpdateTicketOutcomeStatus(ctx, id, status)
|
|
}
|
|
func (s *Service) DeleteTicket(ctx context.Context, id int64) error {
|
|
return s.ticketStore.DeleteTicket(ctx, id)
|
|
}
|
|
|
|
func (s *Service) DeleteOldTickets(ctx context.Context) error {
|
|
return s.ticketStore.DeleteOldTickets(ctx)
|
|
}
|