package veli import ( "context" "fmt" dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "github.com/jackc/pgx/v5/pgtype" "go.uber.org/zap" ) 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, } 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) } // 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. Get providers from external API --- providersRes, err := s.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 --- for _, p := range providersRes.Items { // Violates foreign key if the provider isn't added games, err := s.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, // Volatility: g.Volatility, // RTP: g.RTP, 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 != "", }, // Volatility: g.Volatility, // RTP: g.RTP, HasDemo: pgtype.Bool{ Bool: g.HasDemoMode, Valid: true, }, HasFreeBets: pgtype.Bool{ Bool: g.HasFreeBets, Valid: true, }, // Bets: g.Bets, // Thumbnail: g.Thumbnail, // Status: g.Status, }) if err != nil { logger.Error("failed to create virtual game", zap.Error(err)) } } } // --- 3. 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), //The id here needs to be clean for me to access 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 virtual game", zap.Error(err)) } } return allGames, nil }