Yimaru-BackEnd/internal/services/virtualGame/orchestration/service.go

527 lines
16 KiB
Go

package orchestration
import (
"context"
"fmt"
"time"
dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
"github.com/SamuelTariku/FortuneBet-Backend/internal/config"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"github.com/SamuelTariku/FortuneBet-Backend/internal/repository"
virtualgameservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/virtualGame"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/virtualGame/veli"
"github.com/jackc/pgx/v5/pgtype"
)
type Service struct {
virtualGameSvc virtualgameservice.VirtualGameService
veliVirtualGameSvc veli.VeliVirtualGameService
repo repository.VirtualGameRepository
cfg *config.Config
client *veli.Client
}
func New(virtualGameSvc virtualgameservice.VirtualGameService, repo repository.VirtualGameRepository, cfg *config.Config, client *veli.Client) *Service {
return &Service{
virtualGameSvc: virtualGameSvc,
repo: repo,
cfg: cfg,
client: client,
}
}
func (s *Service) AddProviders(ctx context.Context, req domain.ProviderRequest) (*domain.ProviderResponse, error) {
// logger := s.mongoLogger.With(zap.String("service", "AddProviders"), zap.Any("ProviderRequest", req))
// 0. Remove all existing providers first
if err := s.repo.DeleteAllVirtualGameProviders(ctx); err != nil {
// logger.Error("failed to delete all virtual game providers", zap.Error(err))
return nil, fmt.Errorf("failed to clear existing providers: %w", err)
}
// 1. Prepare signature parameters
sigParams := map[string]any{
"brandId": req.BrandID,
}
// Optional fields
sigParams["extraData"] = fmt.Sprintf("%t", req.ExtraData) // false is still included
if req.Size > 0 {
sigParams["size"] = fmt.Sprintf("%d", req.Size)
} else {
sigParams["size"] = ""
}
if req.Page > 0 {
sigParams["page"] = fmt.Sprintf("%d", req.Page)
} else {
sigParams["page"] = ""
}
// 2. Call external API
var res domain.ProviderResponse
if err := s.client.Post(ctx, "/game-lists/public/providers", req, sigParams, &res); err != nil {
return nil, fmt.Errorf("failed to fetch providers: %w", err)
}
// 3. Loop through fetched providers and insert into DB
for _, p := range res.Items {
createParams := dbgen.CreateVirtualGameProviderParams{
ProviderID: p.ProviderID,
ProviderName: p.ProviderName,
LogoDark: pgtype.Text{String: p.LogoForDark, Valid: p.LogoForDark != ""},
LogoLight: pgtype.Text{String: p.LogoForLight, Valid: p.LogoForLight != ""},
Enabled: true,
}
if _, err := s.repo.CreateVirtualGameProvider(ctx, createParams); err != nil {
// logger.Error("failed to add provider", zap.Error(err))
return nil, fmt.Errorf("failed to add provider %s: %w", p.ProviderID, err)
}
}
// 4. Always add "popok" provider manually
popokParams := dbgen.CreateVirtualGameProviderParams{
ProviderID: "popok",
ProviderName: "Popok Gaming",
LogoDark: pgtype.Text{String: fmt.Sprintf("%v/static/logos/popok-dark.png", s.cfg.PopOK.CallbackURL), Valid: true}, // adjust as needed
LogoLight: pgtype.Text{String: fmt.Sprintf("%v/static/logos/popok-light.png", s.cfg.PopOK.CallbackURL), Valid: true}, // adjust as needed
Enabled: true,
}
atlasParams := dbgen.CreateVirtualGameProviderParams{
ProviderID: "atlas",
ProviderName: "Atlas Gaming",
LogoDark: pgtype.Text{String: "/static/logos/atlas-dark.png", Valid: true}, // adjust as needed
LogoLight: pgtype.Text{String: "/static/logos/atlas-light.png", Valid: true}, // adjust as needed
Enabled: true,
}
if _, err := s.repo.CreateVirtualGameProvider(ctx, popokParams); err != nil {
// logger.Error("failed to add popok provider", zap.Any("popokParams", popokParams), zap.Error(err))
return nil, fmt.Errorf("failed to add popok provider: %w", err)
}
if _, err := s.repo.CreateVirtualGameProvider(ctx, atlasParams); err != nil {
return nil, fmt.Errorf("failed to add atlas provider: %w", err)
}
// Optionally also append it to the response for consistency
// res.Items = append(res.Items, domain.VirtualGameProvider{
// ProviderID: uuid.New().String(),
// ProviderName: "Popok Gaming",
// LogoForDark: "/static/logos/popok-dark.png",
// LogoForLight: "/static/logos/popok-light.png",
// })
return &res, nil
}
func (s *Service) GetAllVirtualGames(ctx context.Context, params dbgen.GetAllVirtualGamesParams) ([]domain.UnifiedGame, error) {
// Build params for repo call
// logger := s.mongoLogger.With(zap.String("service", "GetAllVirtualGames"), zap.Any("params", params))
rows, err := s.repo.ListAllVirtualGames(ctx, params)
if err != nil {
// logger.Error("[GetAllVirtualGames] Failed to fetch virtual games", zap.Error(err))
return nil, fmt.Errorf("failed to fetch virtual games: %w", err)
}
var allGames []domain.UnifiedGame
for _, r := range rows {
// --- Convert nullable Rtp to *float64 ---
var rtpPtr *float64
if r.Rtp.Valid {
rtpFloat, err := r.Rtp.Float64Value()
if err == nil {
rtpPtr = new(float64)
*rtpPtr = rtpFloat.Float64
}
}
var betsFloat64 []float64
for _, bet := range r.Bets {
if bet.Valid {
betFloat, err := bet.Float64Value()
if err == nil {
betsFloat64 = append(betsFloat64, betFloat.Float64)
}
}
}
allGames = append(allGames, domain.UnifiedGame{
GameID: r.GameID,
ProviderID: r.ProviderID,
Provider: r.ProviderName,
Name: r.Name,
Category: r.Category.String,
DeviceType: r.DeviceType.String,
Volatility: r.Volatility.String,
RTP: rtpPtr,
HasDemo: r.HasDemo.Bool,
HasFreeBets: r.HasFreeBets.Bool,
Bets: betsFloat64,
Thumbnail: r.Thumbnail.String,
Status: int(r.Status.Int32), // nullable status
})
}
return allGames, nil
}
func (s *Service) FetchAndStoreAllVirtualGames(ctx context.Context, req domain.ProviderRequest, currency string) ([]domain.UnifiedGame, error) {
// logger := s.mongoLogger.With(
// zap.String("service", "FetchAndStoreAllVirtualGames"),
// zap.Any("ProviderRequest", req),
// )
// This is necessary since the provider is a foreign key
_, err := s.AddProviders(ctx, req)
if err != nil {
return nil, fmt.Errorf("failed to add providers to database: %w", err)
}
var allGames []domain.UnifiedGame
// --- 1. Existing providers (Veli Games) ---
providersRes, err := s.veliVirtualGameSvc.GetProviders(ctx, req)
if err != nil {
// logger.Error("Failed to fetch provider", zap.Error(err))
return nil, fmt.Errorf("failed to fetch providers: %w", err)
}
// --- 2. Fetch games for each provider (Veli Games) ---
for _, p := range providersRes.Items {
games, err := s.veliVirtualGameSvc.GetGames(ctx, domain.GameListRequest{
BrandID: s.cfg.VeliGames.BrandID,
ProviderID: p.ProviderID,
Page: req.Page,
Size: req.Size,
})
if err != nil {
// logger.Error("failed to get veli games", zap.String("ProviderID", p.ProviderID), zap.Error(err))
continue // skip failing provider but continue others
}
for _, g := range games {
unified := domain.UnifiedGame{
GameID: g.GameID,
ProviderID: g.ProviderID,
Provider: p.ProviderName,
Name: g.Name,
Category: g.Category,
DeviceType: g.DeviceType,
HasDemo: g.HasDemoMode,
HasFreeBets: g.HasFreeBets,
}
allGames = append(allGames, unified)
// Save to DB
_, err = s.repo.CreateVirtualGame(ctx, dbgen.CreateVirtualGameParams{
GameID: g.GameID,
ProviderID: g.ProviderID,
Name: g.Name,
Category: pgtype.Text{
String: g.Category,
Valid: g.Category != "",
},
DeviceType: pgtype.Text{
String: g.DeviceType,
Valid: g.DeviceType != "",
},
HasDemo: pgtype.Bool{
Bool: g.HasDemoMode,
Valid: true,
},
HasFreeBets: pgtype.Bool{
Bool: g.HasFreeBets,
Valid: true,
},
})
if err != nil {
// logger.Error("failed to create virtual game", zap.Error(err))
}
}
}
// --- 3. Fetch Atlas-V games ---
atlasGames, err := s.veliVirtualGameSvc.GetAtlasVGames(ctx)
if err != nil {
// logger.Error("failed to fetch Atlas-V games", zap.Error(err))
} else {
for _, g := range atlasGames {
unified := domain.UnifiedGame{
GameID: g.GameID,
ProviderID: "atlasv",
Provider: "Atlas-V Gaming", // "Atlas-V"
Name: g.Name,
Category: g.Category, // using Type as Category
Thumbnail: g.Thumbnail,
HasDemo: true,
DemoURL: g.DemoURL,
}
allGames = append(allGames, unified)
// Save to DB
_, err = s.repo.CreateVirtualGame(ctx, dbgen.CreateVirtualGameParams{
GameID: g.GameID,
ProviderID: "atlasv",
Name: g.Name,
Category: pgtype.Text{
String: g.Category,
Valid: g.Category != "",
},
Thumbnail: pgtype.Text{
String: g.Thumbnail,
Valid: g.Thumbnail != "",
},
HasDemo: pgtype.Bool{
Bool: g.HasDemoMode,
Valid: true,
},
})
if err != nil {
// logger.Error("failed to create Atlas-V virtual game", zap.Error(err))
}
}
}
// --- 4. Handle PopOK separately ---
popokGames, err := s.virtualGameSvc.ListGames(ctx, currency)
if err != nil {
// logger.Error("failed to fetch PopOk games", zap.Error(err))
return nil, fmt.Errorf("failed to fetch PopOK games: %w", err)
}
for _, g := range popokGames {
unified := domain.UnifiedGame{
GameID: fmt.Sprintf("%d", g.ID),
ProviderID: "popok",
Provider: "PopOK",
Name: g.GameName,
Category: "Crash",
Bets: g.Bets,
Thumbnail: g.Thumbnail,
Status: g.Status,
}
allGames = append(allGames, unified)
// Convert []float64 to []pgtype.Numeric
var betsNumeric []pgtype.Numeric
for _, bet := range g.Bets {
var num pgtype.Numeric
_ = num.Scan(bet)
betsNumeric = append(betsNumeric, num)
}
// Save to DB
_, err = s.repo.CreateVirtualGame(ctx, dbgen.CreateVirtualGameParams{
GameID: fmt.Sprintf("%d", g.ID),
ProviderID: "popok",
Name: g.GameName,
Bets: betsNumeric,
Thumbnail: pgtype.Text{
String: g.Thumbnail,
Valid: g.Thumbnail != "",
},
Status: pgtype.Int4{
Int32: int32(g.Status),
Valid: true,
},
HasDemo: pgtype.Bool{
Bool: true,
Valid: true,
},
Category: pgtype.Text{
String: "Crash",
Valid: true,
},
})
if err != nil {
// logger.Error("failed to create PopOK virtual game", zap.Error(err))
}
}
return allGames, nil
}
func (s *Service) RemoveProvider(ctx context.Context, providerID string) error {
return s.repo.DeleteVirtualGameProvider(ctx, providerID)
}
// Fetch provider by provider_id
func (s *Service) GetProviderByID(ctx context.Context, providerID string) (dbgen.VirtualGameProvider, error) {
return s.repo.GetVirtualGameProviderByID(ctx, providerID)
}
// List providers with pagination
func (s *Service) ListProviders(ctx context.Context, limit, offset int32) ([]domain.VirtualGameProvider, int64, error) {
providers, err := s.repo.ListVirtualGameProviders(ctx, limit, offset)
if err != nil {
return nil, 0, err
}
total, err := s.repo.CountVirtualGameProviders(ctx)
if err != nil {
return nil, 0, err
}
// Convert []dbgen.VirtualGameProvider to []domain.VirtualGameProvider
domainProviders := make([]domain.VirtualGameProvider, len(providers))
for i, p := range providers {
var logoDark *string
if p.LogoDark.Valid {
logoDark = &p.LogoDark.String
}
var logoLight *string
if p.LogoLight.Valid {
logoLight = &p.LogoLight.String
}
domainProviders[i] = domain.VirtualGameProvider{
ProviderID: p.ProviderID,
ProviderName: p.ProviderName,
Enabled: p.Enabled,
LogoDark: logoDark,
LogoLight: logoLight,
CreatedAt: p.CreatedAt.Time,
UpdatedAt: &p.UpdatedAt.Time,
// Add other fields as needed
}
}
return domainProviders, total, nil
}
// Enable/Disable a provider
func (s *Service) SetProviderEnabled(ctx context.Context, providerID string, enabled bool) (*domain.VirtualGameProvider, error) {
provider, err := s.repo.UpdateVirtualGameProviderEnabled(ctx, providerID, enabled)
if err != nil {
//s.logger.Error("Failed to update provider enabled status", "provider_id", providerID, "enabled", enabled, "error", err)
return nil, err
}
now := time.Now()
provider.UpdatedAt.Time = now
domainProvider := &domain.VirtualGameProvider{
ProviderID: provider.ProviderID,
ProviderName: provider.ProviderName,
Enabled: provider.Enabled,
CreatedAt: provider.CreatedAt.Time,
UpdatedAt: &provider.UpdatedAt.Time,
// Add other fields as needed
}
return domainProvider, nil
}
func (s *Service) CreateVirtualGameProviderReport(ctx context.Context, report domain.CreateVirtualGameProviderReport) (domain.VirtualGameProviderReport, error) {
// Example: logger := s.mongoLogger.With(zap.String("service", "CreateVirtualGameProviderReport"), zap.Any("Report", report))
// Call repository to create the report
created, err := s.repo.CreateVirtualGameProviderReport(ctx, report)
if err != nil {
// logger.Error("failed to create provider report", zap.Error(err), zap.Any("report", report))
return domain.VirtualGameProviderReport{}, fmt.Errorf("failed to create provider report for provider %s: %w", report.ProviderID, err)
}
// Return the created report
return created, nil
}
func (s *Service) CreateVirtualGameReport(ctx context.Context, report domain.CreateVirtualGameReport) (domain.VirtualGameReport, error) {
// Example: logger := s.mongoLogger.With(zap.String("service", "CreateVirtualGameReport"), zap.Any("Report", report))
// Call repository to create the report
created, err := s.repo.CreateVirtualGameReport(ctx, report)
if err != nil {
// logger.Error("failed to create game report", zap.Error(err), zap.Any("report", report))
return domain.VirtualGameReport{}, fmt.Errorf("failed to create game report for game %s: %w", report.GameID, err)
}
// Return the created report
return created, nil
}
func (s *Service) GetVirtualGameProviderReportByProviderAndDate(
ctx context.Context,
providerID string,
reportDate time.Time,
reportType string,
) (domain.VirtualGameProviderReport, error) {
// Example logger if needed
// logger := s.mongoLogger.With(zap.String("service", "GetVirtualGameProviderReportByProviderAndDate"),
// zap.String("provider_id", providerID),
// zap.Time("report_date", reportDate),
// zap.String("report_type", reportType),
// )
report, err := s.repo.GetVirtualGameProviderReportByProviderAndDate(ctx, providerID, reportDate, reportType)
if err != nil {
// logger.Error("failed to retrieve virtual game provider report", zap.Error(err))
return domain.VirtualGameProviderReport{}, fmt.Errorf(
"failed to retrieve provider report for provider %s on %s (%s): %w",
providerID, reportDate.Format("2006-01-02"), reportType, err,
)
}
return report, nil
}
func (s *Service) UpdateVirtualGameProviderReportByDate(
ctx context.Context,
providerID string,
reportDate time.Time,
reportType string,
totalGamesPlayed int64,
totalBets float64,
totalPayouts float64,
totalPlayers int64,
) error {
// Optionally log or trace the update
// Example: s.mongoLogger.Info("Updating virtual game provider report",
// zap.String("provider_id", providerID),
// zap.Time("report_date", reportDate),
// zap.String("report_type", reportType),
// )
err := s.repo.UpdateVirtualGameProviderReportByDate(
ctx,
providerID,
reportDate,
reportType,
totalGamesPlayed,
totalBets,
totalPayouts,
totalPlayers,
)
if err != nil {
return fmt.Errorf("failed to update provider report for provider %s on %s (%s): %w",
providerID,
reportDate.Format("2006-01-02"),
reportType,
err,
)
}
return nil
}
func (s *Service) ListVirtualGameProviderReportsByGamesPlayedAsc(ctx context.Context) ([]domain.VirtualGameProviderReport, error) {
reports, err := s.repo.ListVirtualGameProviderReportsByGamesPlayedAsc(ctx)
if err != nil {
return nil, fmt.Errorf("failed to fetch virtual game provider reports ascending: %w", err)
}
return reports, nil
}
// ListVirtualGameProviderReportsByGamesPlayedDesc fetches all reports sorted by total_games_played descending.
func (s *Service) ListVirtualGameProviderReportsByGamesPlayedDesc(ctx context.Context) ([]domain.VirtualGameProviderReport, error) {
reports, err := s.repo.ListVirtualGameProviderReportsByGamesPlayedDesc(ctx)
if err != nil {
return nil, fmt.Errorf("failed to fetch virtual game provider reports descending: %w", err)
}
return reports, nil
}