atlas orchestration + .env fixes

This commit is contained in:
Yared Yemane 2025-09-25 16:37:53 +03:00
parent f5f2ab995f
commit 423ae69fb0
6 changed files with 144 additions and 49 deletions

View File

@ -380,6 +380,13 @@ func (c *Config) loadEnv() error {
c.ADRO_SMS_HOST_URL = "https://api.afrosms.com" 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") popOKClientID := os.Getenv("POPOK_CLIENT_ID")
popOKPlatform := os.Getenv("POPOK_PLATFORM") popOKPlatform := os.Getenv("POPOK_PLATFORM")

View File

@ -31,6 +31,20 @@ type GameEntity struct {
Category string `json:"category"` Category string `json:"category"`
HasDemoMode bool `json:"hasDemoMode"` HasDemoMode bool `json:"hasDemoMode"`
HasFreeBets bool `json:"hasFreeBets"` 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 { type GameStartRequest struct {

View File

@ -283,35 +283,36 @@ type PopokLaunchResponse struct {
type VirtualGameProvider struct { type VirtualGameProvider struct {
// ID int64 `json:"id" db:"id"` // ID int64 `json:"id" db:"id"`
ProviderID string `json:"provider_id" db:"provider_id"` ProviderID string `json:"provider_id" db:"provider_id"`
ProviderName string `json:"provider_name" db:"provider_name"` ProviderName string `json:"provider_name" db:"provider_name"`
LogoDark *string `json:"logo_dark,omitempty" db:"logo_dark"` LogoDark *string `json:"logo_dark,omitempty" db:"logo_dark"`
LogoLight *string `json:"logo_light,omitempty" db:"logo_light"` LogoLight *string `json:"logo_light,omitempty" db:"logo_light"`
Enabled bool `json:"enabled" db:"enabled"` Enabled bool `json:"enabled" db:"enabled"`
CreatedAt time.Time `json:"created_at" db:"created_at"` CreatedAt time.Time `json:"created_at" db:"created_at"`
UpdatedAt *time.Time `json:"updated_at,omitempty" db:"updated_at"` UpdatedAt *time.Time `json:"updated_at,omitempty" db:"updated_at"`
} }
// VirtualGameProviderPagination is used when returning paginated results // VirtualGameProviderPagination is used when returning paginated results
type VirtualGameProviderPagination struct { type VirtualGameProviderPagination struct {
Providers []VirtualGameProvider `json:"providers"` Providers []VirtualGameProvider `json:"providers"`
TotalCount int64 `json:"total_count"` TotalCount int64 `json:"total_count"`
Limit int32 `json:"limit"` Limit int32 `json:"limit"`
Offset int32 `json:"offset"` Offset int32 `json:"offset"`
} }
type UnifiedGame struct { type UnifiedGame struct {
GameID string `json:"gameId"` GameID string `json:"gameId"`
ProviderID string `json:"providerId"` ProviderID string `json:"providerId"`
Provider string `json:"provider"` Provider string `json:"provider"`
Name string `json:"name"` Name string `json:"name"`
Category string `json:"category,omitempty"` Category string `json:"category,omitempty"`
DeviceType string `json:"deviceType,omitempty"` DeviceType string `json:"deviceType,omitempty"`
Volatility string `json:"volatility,omitempty"` Volatility string `json:"volatility,omitempty"`
RTP *float64 `json:"rtp,omitempty"` RTP *float64 `json:"rtp,omitempty"`
HasDemo bool `json:"hasDemo"` HasDemo bool `json:"hasDemo"`
HasFreeBets bool `json:"hasFreeBets"` HasFreeBets bool `json:"hasFreeBets"`
Bets []float64 `json:"bets,omitempty"` Bets []float64 `json:"bets,omitempty"`
Thumbnail string `json:"thumbnail,omitempty"` Thumbnail string `json:"thumbnail,omitempty"`
Status int `json:"status,omitempty"` Status int `json:"status,omitempty"`
DemoURL string `json:"demoUrl"`
} }

View File

@ -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) { func (s *Service) FetchAndStoreAllVirtualGames(ctx context.Context, req domain.ProviderRequest, currency string) ([]domain.UnifiedGame, error) {
logger := s.mongoLogger.With( logger := s.mongoLogger.With(
zap.String("service", "FetchAndStoreAllVirtualGames"), zap.String("service", "FetchAndStoreAllVirtualGames"),
zap.Any("ProviderRequest", req), 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) _, err := s.AddProviders(ctx, req)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to add providers to database: %w", err) return nil, fmt.Errorf("failed to add providers to database: %w", err)
} }
var allGames []domain.UnifiedGame var allGames []domain.UnifiedGame
// --- 1. Get providers from external API ---
// --- 1. Existing providers (Veli Games) ---
providersRes, err := s.GetProviders(ctx, req) providersRes, err := s.GetProviders(ctx, req)
if err != nil { if err != nil {
logger.Error("Failed to fetch provider", zap.Error(err)) logger.Error("Failed to fetch provider", zap.Error(err))
return nil, fmt.Errorf("failed to fetch providers: %w", 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 { for _, p := range providersRes.Items {
// Violates foreign key if the provider isn't added
games, err := s.GetGames(ctx, domain.GameListRequest{ games, err := s.GetGames(ctx, domain.GameListRequest{
BrandID: s.cfg.VeliGames.BrandID, BrandID: s.cfg.VeliGames.BrandID,
ProviderID: p.ProviderID, ProviderID: p.ProviderID,
@ -185,20 +184,18 @@ func (s *Service) FetchAndStoreAllVirtualGames(ctx context.Context, req domain.P
for _, g := range games { for _, g := range games {
unified := domain.UnifiedGame{ unified := domain.UnifiedGame{
GameID: g.GameID, GameID: g.GameID,
ProviderID: g.ProviderID, ProviderID: g.ProviderID,
Provider: p.ProviderName, Provider: p.ProviderName,
Name: g.Name, Name: g.Name,
Category: g.Category, Category: g.Category,
DeviceType: g.DeviceType, DeviceType: g.DeviceType,
// Volatility: g.Volatility,
// RTP: g.RTP,
HasDemo: g.HasDemoMode, HasDemo: g.HasDemoMode,
HasFreeBets: g.HasFreeBets, HasFreeBets: g.HasFreeBets,
} }
allGames = append(allGames, unified) allGames = append(allGames, unified)
// --- Save to DB --- // Save to DB
_, err = s.repo.CreateVirtualGame(ctx, dbgen.CreateVirtualGameParams{ _, err = s.repo.CreateVirtualGame(ctx, dbgen.CreateVirtualGameParams{
GameID: g.GameID, GameID: g.GameID,
ProviderID: g.ProviderID, ProviderID: g.ProviderID,
@ -211,8 +208,6 @@ func (s *Service) FetchAndStoreAllVirtualGames(ctx context.Context, req domain.P
String: g.DeviceType, String: g.DeviceType,
Valid: g.DeviceType != "", Valid: g.DeviceType != "",
}, },
// Volatility: g.Volatility,
// RTP: g.RTP,
HasDemo: pgtype.Bool{ HasDemo: pgtype.Bool{
Bool: g.HasDemoMode, Bool: g.HasDemoMode,
Valid: true, Valid: true,
@ -221,18 +216,56 @@ func (s *Service) FetchAndStoreAllVirtualGames(ctx context.Context, req domain.P
Bool: g.HasFreeBets, Bool: g.HasFreeBets,
Valid: true, Valid: true,
}, },
// Bets: g.Bets,
// Thumbnail: g.Thumbnail,
// Status: g.Status,
}) })
if err != nil { if err != nil {
logger.Error("failed to create virtual game", zap.Error(err)) 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) popokGames, err := s.virtualGameSvc.ListGames(ctx, currency)
if err != nil { if err != nil {
logger.Error("failed to fetch PopOk games", zap.Error(err)) 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) allGames = append(allGames, unified)
// --- Convert []float64 to []pgtype.Numeric --- // Convert []float64 to []pgtype.Numeric
var betsNumeric []pgtype.Numeric var betsNumeric []pgtype.Numeric
for _, bet := range g.Bets { for _, bet := range g.Bets {
var num pgtype.Numeric var num pgtype.Numeric
@ -260,9 +293,9 @@ func (s *Service) FetchAndStoreAllVirtualGames(ctx context.Context, req domain.P
betsNumeric = append(betsNumeric, num) betsNumeric = append(betsNumeric, num)
} }
// --- Save to DB --- // Save to DB
_, err = s.repo.CreateVirtualGame(ctx, dbgen.CreateVirtualGameParams{ _, 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", ProviderID: "popok",
Name: g.GameName, Name: g.GameName,
Bets: betsNumeric, Bets: betsNumeric,
@ -275,9 +308,8 @@ func (s *Service) FetchAndStoreAllVirtualGames(ctx context.Context, req domain.P
Valid: true, Valid: true,
}, },
}) })
if err != nil { if err != nil {
logger.Error("failed to create virtual game", zap.Error(err)) logger.Error("failed to create PopOK virtual game", zap.Error(err))
} }
} }

View File

@ -9,6 +9,7 @@ import (
) )
type VeliVirtualGameService interface { type VeliVirtualGameService interface {
GetAtlasVGames(ctx context.Context) ([]domain.AtlasGameEntity, error)
FetchAndStoreAllVirtualGames(ctx context.Context, req domain.ProviderRequest, currency string) ([]domain.UnifiedGame, error) FetchAndStoreAllVirtualGames(ctx context.Context, req domain.ProviderRequest, currency string) ([]domain.UnifiedGame, error)
GetAllVirtualGames(ctx context.Context, params dbgen.GetAllVirtualGamesParams) ([]domain.UnifiedGame, error) GetAllVirtualGames(ctx context.Context, params dbgen.GetAllVirtualGamesParams) ([]domain.UnifiedGame, error)
AddProviders(ctx context.Context, req domain.ProviderRequest) (*domain.ProviderResponse, error) AddProviders(ctx context.Context, req domain.ProviderRequest) (*domain.ProviderResponse, error)

View File

@ -2,9 +2,13 @@ package veli
import ( import (
"context" "context"
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"io"
"net/http"
"strconv" "strconv"
"time"
"github.com/SamuelTariku/FortuneBet-Backend/internal/config" "github.com/SamuelTariku/FortuneBet-Backend/internal/config"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "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&timestamp=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) { func (s *Service) GetProviders(ctx context.Context, req domain.ProviderRequest) (*domain.ProviderResponse, error) {
// Always mirror request body fields into sigParams // Always mirror request body fields into sigParams
sigParams := map[string]any{ sigParams := map[string]any{