diff --git a/internal/config/config.go b/internal/config/config.go index 15b9421..d96e816 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -380,6 +380,13 @@ func (c *Config) loadEnv() error { c.ADRO_SMS_HOST_URL = "https://api.afrosms.com" } + //Atlas + c.Atlas.BaseURL = os.Getenv("ATLAS_BASE_URL") + c.Atlas.CasinoID = os.Getenv("ATLAS_CASINO_ID") + c.Atlas.OperatorID = os.Getenv("ATLAS_OPERATOR_ID") + c.Atlas.PartnerID = os.Getenv("ATLAS_PARTNER_ID") + c.Atlas.SecretKey = os.Getenv("ATLAS_SECRET_KEY") + popOKClientID := os.Getenv("POPOK_CLIENT_ID") popOKPlatform := os.Getenv("POPOK_PLATFORM") diff --git a/internal/domain/veli_games.go b/internal/domain/veli_games.go index 2fad79a..db6ad6e 100644 --- a/internal/domain/veli_games.go +++ b/internal/domain/veli_games.go @@ -31,6 +31,20 @@ type GameEntity struct { Category string `json:"category"` HasDemoMode bool `json:"hasDemoMode"` HasFreeBets bool `json:"hasFreeBets"` + // Thumbnail string `json:"thumbnail"` // ✅ new field + // DemoURL string `json:"demoUrl"` // ✅ new field +} + +type AtlasGameEntity struct { + GameID string `json:"game_id"` + ProviderID string `json:"providerId"` + Name string `json:"name"` + DeviceType string `json:"deviceType"` + Category string `json:"type"` + HasDemoMode bool `json:"has_demo"` + HasFreeBets bool `json:"hasFreeBets"` + Thumbnail string `json:"thumbnail_img_url"` // ✅ new field + DemoURL string `json:"demo_url"` // ✅ new field } type GameStartRequest struct { diff --git a/internal/domain/virtual_game.go b/internal/domain/virtual_game.go index fe92d4c..9929c5d 100644 --- a/internal/domain/virtual_game.go +++ b/internal/domain/virtual_game.go @@ -283,35 +283,36 @@ type PopokLaunchResponse struct { type VirtualGameProvider struct { // ID int64 `json:"id" db:"id"` - ProviderID string `json:"provider_id" db:"provider_id"` - ProviderName string `json:"provider_name" db:"provider_name"` - LogoDark *string `json:"logo_dark,omitempty" db:"logo_dark"` - LogoLight *string `json:"logo_light,omitempty" db:"logo_light"` - Enabled bool `json:"enabled" db:"enabled"` - CreatedAt time.Time `json:"created_at" db:"created_at"` + ProviderID string `json:"provider_id" db:"provider_id"` + ProviderName string `json:"provider_name" db:"provider_name"` + LogoDark *string `json:"logo_dark,omitempty" db:"logo_dark"` + LogoLight *string `json:"logo_light,omitempty" db:"logo_light"` + Enabled bool `json:"enabled" db:"enabled"` + CreatedAt time.Time `json:"created_at" db:"created_at"` UpdatedAt *time.Time `json:"updated_at,omitempty" db:"updated_at"` } // VirtualGameProviderPagination is used when returning paginated results type VirtualGameProviderPagination struct { - Providers []VirtualGameProvider `json:"providers"` - TotalCount int64 `json:"total_count"` - Limit int32 `json:"limit"` - Offset int32 `json:"offset"` + Providers []VirtualGameProvider `json:"providers"` + TotalCount int64 `json:"total_count"` + Limit int32 `json:"limit"` + Offset int32 `json:"offset"` } type UnifiedGame struct { - GameID string `json:"gameId"` - ProviderID string `json:"providerId"` - Provider string `json:"provider"` - Name string `json:"name"` - Category string `json:"category,omitempty"` - DeviceType string `json:"deviceType,omitempty"` - Volatility string `json:"volatility,omitempty"` - RTP *float64 `json:"rtp,omitempty"` - HasDemo bool `json:"hasDemo"` - HasFreeBets bool `json:"hasFreeBets"` - Bets []float64 `json:"bets,omitempty"` - Thumbnail string `json:"thumbnail,omitempty"` - Status int `json:"status,omitempty"` + GameID string `json:"gameId"` + ProviderID string `json:"providerId"` + Provider string `json:"provider"` + Name string `json:"name"` + Category string `json:"category,omitempty"` + DeviceType string `json:"deviceType,omitempty"` + Volatility string `json:"volatility,omitempty"` + RTP *float64 `json:"rtp,omitempty"` + HasDemo bool `json:"hasDemo"` + HasFreeBets bool `json:"hasFreeBets"` + Bets []float64 `json:"bets,omitempty"` + Thumbnail string `json:"thumbnail,omitempty"` + Status int `json:"status,omitempty"` + DemoURL string `json:"demoUrl"` } diff --git a/internal/services/virtualGame/veli/game_orchestration.go b/internal/services/virtualGame/veli/game_orchestration.go index a371296..37ef8be 100644 --- a/internal/services/virtualGame/veli/game_orchestration.go +++ b/internal/services/virtualGame/veli/game_orchestration.go @@ -149,29 +149,28 @@ func (s *Service) GetAllVirtualGames(ctx context.Context, params dbgen.GetAllVir } 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 + // 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 --- + + // --- 1. Existing providers (Veli Games) --- 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 --- + // --- 2. Fetch games for each provider (Veli Games) --- 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, @@ -185,20 +184,18 @@ func (s *Service) FetchAndStoreAllVirtualGames(ctx context.Context, req domain.P 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, + 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 --- + // Save to DB _, err = s.repo.CreateVirtualGame(ctx, dbgen.CreateVirtualGameParams{ GameID: g.GameID, ProviderID: g.ProviderID, @@ -211,8 +208,6 @@ func (s *Service) FetchAndStoreAllVirtualGames(ctx context.Context, req domain.P String: g.DeviceType, Valid: g.DeviceType != "", }, - // Volatility: g.Volatility, - // RTP: g.RTP, HasDemo: pgtype.Bool{ Bool: g.HasDemoMode, Valid: true, @@ -221,18 +216,56 @@ func (s *Service) FetchAndStoreAllVirtualGames(ctx context.Context, req domain.P 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 --- + // --- 3. Fetch Atlas-V games --- + atlasGames, err := s.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)) @@ -252,7 +285,7 @@ func (s *Service) FetchAndStoreAllVirtualGames(ctx context.Context, req domain.P } allGames = append(allGames, unified) - // --- Convert []float64 to []pgtype.Numeric --- + // Convert []float64 to []pgtype.Numeric var betsNumeric []pgtype.Numeric for _, bet := range g.Bets { var num pgtype.Numeric @@ -260,9 +293,9 @@ func (s *Service) FetchAndStoreAllVirtualGames(ctx context.Context, req domain.P betsNumeric = append(betsNumeric, num) } - // --- Save to DB --- + // 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 + GameID: fmt.Sprintf("%d", g.ID), ProviderID: "popok", Name: g.GameName, Bets: betsNumeric, @@ -275,9 +308,8 @@ func (s *Service) FetchAndStoreAllVirtualGames(ctx context.Context, req domain.P Valid: true, }, }) - if err != nil { - logger.Error("failed to create virtual game", zap.Error(err)) + logger.Error("failed to create PopOK virtual game", zap.Error(err)) } } diff --git a/internal/services/virtualGame/veli/port.go b/internal/services/virtualGame/veli/port.go index edbffe2..5e50cba 100644 --- a/internal/services/virtualGame/veli/port.go +++ b/internal/services/virtualGame/veli/port.go @@ -9,6 +9,7 @@ import ( ) type VeliVirtualGameService interface { + GetAtlasVGames(ctx context.Context) ([]domain.AtlasGameEntity, error) FetchAndStoreAllVirtualGames(ctx context.Context, req domain.ProviderRequest, currency string) ([]domain.UnifiedGame, error) GetAllVirtualGames(ctx context.Context, params dbgen.GetAllVirtualGamesParams) ([]domain.UnifiedGame, error) AddProviders(ctx context.Context, req domain.ProviderRequest) (*domain.ProviderResponse, error) diff --git a/internal/services/virtualGame/veli/service.go b/internal/services/virtualGame/veli/service.go index 3f41633..cea093e 100644 --- a/internal/services/virtualGame/veli/service.go +++ b/internal/services/virtualGame/veli/service.go @@ -2,9 +2,13 @@ package veli import ( "context" + "encoding/json" "errors" "fmt" + "io" + "net/http" "strconv" + "time" "github.com/SamuelTariku/FortuneBet-Backend/internal/config" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" @@ -51,6 +55,42 @@ func New( } } +func (s *Service) GetAtlasVGames(ctx context.Context) ([]domain.AtlasGameEntity, error) { + // 1. Compose URL (could be configurable) + url := "https://atlas-v.com/partner/35fr5784dbgr4dfw234wsdsw" + + "?hash=b3596faa6185180e9b2ca01cb5a052d316511872×tamp=1700244963080" + + // 2. Create a dedicated HTTP client with timeout + client := &http.Client{Timeout: 15 * time.Second} + + // 3. Prepare request with context + req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) + if err != nil { + return nil, fmt.Errorf("creating request: %w", err) + } + + // 4. Execute request + resp, err := client.Do(req) + if err != nil { + return nil, fmt.Errorf("calling Atlas-V API: %w", err) + } + defer resp.Body.Close() + + // 5. Check response status + if resp.StatusCode != http.StatusOK { + body, _ := io.ReadAll(resp.Body) + return nil, fmt.Errorf("Atlas-V API error: status %d, body: %s", resp.StatusCode, body) + } + + // 6. Decode response into slice of GameEntity + var games []domain.AtlasGameEntity + if err := json.NewDecoder(resp.Body).Decode(&games); err != nil { + return nil, fmt.Errorf("decoding Atlas-V games: %w", err) + } + + return games, nil +} + func (s *Service) GetProviders(ctx context.Context, req domain.ProviderRequest) (*domain.ProviderResponse, error) { // Always mirror request body fields into sigParams sigParams := map[string]any{