recommendation service
This commit is contained in:
parent
169e22e3a7
commit
5dcefa377f
|
|
@ -23,6 +23,7 @@ import (
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/event"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/event"
|
||||||
notificationservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/notfication"
|
notificationservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/notfication"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds"
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/recommendation"
|
||||||
referralservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/referal"
|
referralservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/referal"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/result"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/result"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/ticket"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/ticket"
|
||||||
|
|
@ -88,6 +89,7 @@ func main() {
|
||||||
notificationRepo := repository.NewNotificationRepository(store)
|
notificationRepo := repository.NewNotificationRepository(store)
|
||||||
referalRepo := repository.NewReferralRepository(store)
|
referalRepo := repository.NewReferralRepository(store)
|
||||||
vitualGameRepo := repository.NewVirtualGameRepository(store)
|
vitualGameRepo := repository.NewVirtualGameRepository(store)
|
||||||
|
recommendationRepo := repository.NewRecommendationRepository(store)
|
||||||
|
|
||||||
notificationSvc := notificationservice.New(notificationRepo, logger, cfg)
|
notificationSvc := notificationservice.New(notificationRepo, logger, cfg)
|
||||||
referalSvc := referralservice.New(referalRepo, *walletSvc, store, cfg, logger)
|
referalSvc := referralservice.New(referalRepo, *walletSvc, store, cfg, logger)
|
||||||
|
|
@ -98,13 +100,13 @@ func main() {
|
||||||
cfg,
|
cfg,
|
||||||
logger,
|
logger,
|
||||||
)
|
)
|
||||||
|
|
||||||
veliService := veli.NewVeliPlayService(
|
veliService := veli.NewVeliPlayService(
|
||||||
vitualGameRepo,
|
vitualGameRepo,
|
||||||
*walletSvc,
|
*walletSvc,
|
||||||
cfg,
|
cfg,
|
||||||
logger,
|
logger,
|
||||||
)
|
)
|
||||||
|
recommendationSvc := recommendation.NewService(recommendationRepo)
|
||||||
|
|
||||||
httpserver.StartDataFetchingCrons(eventSvc, oddsSvc, resultSvc)
|
httpserver.StartDataFetchingCrons(eventSvc, oddsSvc, resultSvc)
|
||||||
httpserver.StartTicketCrons(*ticketSvc)
|
httpserver.StartTicketCrons(*ticketSvc)
|
||||||
|
|
@ -113,7 +115,7 @@ func main() {
|
||||||
JwtAccessKey: cfg.JwtKey,
|
JwtAccessKey: cfg.JwtKey,
|
||||||
JwtAccessExpiry: cfg.AccessExpiry,
|
JwtAccessExpiry: cfg.AccessExpiry,
|
||||||
}, userSvc,
|
}, userSvc,
|
||||||
ticketSvc, betSvc, walletSvc, transactionSvc, branchSvc, companySvc, notificationSvc, oddsSvc, eventSvc, referalSvc, virtualGameSvc, aleaService, veliService, resultSvc, cfg)
|
ticketSvc, betSvc, walletSvc, transactionSvc, branchSvc, companySvc, notificationSvc, oddsSvc, eventSvc, referalSvc, virtualGameSvc, aleaService, veliService, recommendationSvc, resultSvc, cfg)
|
||||||
logger.Info("Starting server", "port", cfg.Port)
|
logger.Info("Starting server", "port", cfg.Port)
|
||||||
|
|
||||||
if err := app.Run(); err != nil {
|
if err := app.Run(); err != nil {
|
||||||
|
|
|
||||||
15
db/migrations/000005_result_checker.up copy.sql
Normal file
15
db/migrations/000005_result_checker.up copy.sql
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
CREATE TABLE IF NOT EXISTS results (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
bet_outcome_id BIGINT NOT NULL,
|
||||||
|
event_id BIGINT NOT NULL,
|
||||||
|
odd_id BIGINT NOT NULL,
|
||||||
|
market_id BIGINT NOT NULL,
|
||||||
|
status INT NOT NULL,
|
||||||
|
score VARCHAR(255),
|
||||||
|
full_time_score VARCHAR(255),
|
||||||
|
half_time_score VARCHAR(255),
|
||||||
|
ss VARCHAR(255),
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (bet_outcome_id) REFERENCES bet_outcomes (id)
|
||||||
|
);
|
||||||
28
db/migrations/000006_recommendation.up.sql
Normal file
28
db/migrations/000006_recommendation.up.sql
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
CREATE TABLE virtual_games (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
name VARCHAR(255) NOT NULL,
|
||||||
|
provider VARCHAR(100) NOT NULL,
|
||||||
|
category VARCHAR(100) NOT NULL,
|
||||||
|
min_bet DECIMAL(15,2) NOT NULL,
|
||||||
|
max_bet DECIMAL(15,2) NOT NULL,
|
||||||
|
volatility VARCHAR(50) NOT NULL,
|
||||||
|
rtp DECIMAL(5,2) NOT NULL,
|
||||||
|
is_featured BOOLEAN DEFAULT false,
|
||||||
|
popularity_score INTEGER DEFAULT 0,
|
||||||
|
thumbnail_url TEXT,
|
||||||
|
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE user_game_interactions (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
user_id BIGINT NOT NULL REFERENCES users(id),
|
||||||
|
game_id BIGINT NOT NULL REFERENCES virtual_games(id),
|
||||||
|
interaction_type VARCHAR(50) NOT NULL, -- 'view', 'play', 'bet', 'favorite'
|
||||||
|
amount DECIMAL(15,2),
|
||||||
|
duration_seconds INTEGER,
|
||||||
|
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX idx_user_game_interactions_user ON user_game_interactions(user_id);
|
||||||
|
CREATE INDEX idx_user_game_interactions_game ON user_game_interactions(game_id);
|
||||||
47
docs/docs.go
47
docs/docs.go
|
|
@ -493,6 +493,45 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/api/v1/virtual-games/recommendations/{userID}": {
|
||||||
|
"get": {
|
||||||
|
"description": "Returns a list of recommended virtual games for a specific user",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Recommendations"
|
||||||
|
],
|
||||||
|
"summary": "Get virtual game recommendations",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "User ID",
|
||||||
|
"name": "userID",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Recommended games fetched successfully",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Failed to fetch recommendations",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/domain.RecommendationErrorResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/api/v1/webhooks/alea": {
|
"/api/v1/webhooks/alea": {
|
||||||
"post": {
|
"post": {
|
||||||
"description": "Handles webhook callbacks from Alea Play virtual games for bet settlement",
|
"description": "Handles webhook callbacks from Alea Play virtual games for bet settlement",
|
||||||
|
|
@ -4678,6 +4717,14 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"domain.RecommendationErrorResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"message": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"domain.ReferralSettings": {
|
"domain.ReferralSettings": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|
|
||||||
|
|
@ -485,6 +485,45 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/api/v1/virtual-games/recommendations/{userID}": {
|
||||||
|
"get": {
|
||||||
|
"description": "Returns a list of recommended virtual games for a specific user",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Recommendations"
|
||||||
|
],
|
||||||
|
"summary": "Get virtual game recommendations",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "User ID",
|
||||||
|
"name": "userID",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Recommended games fetched successfully",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Failed to fetch recommendations",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/domain.RecommendationErrorResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/api/v1/webhooks/alea": {
|
"/api/v1/webhooks/alea": {
|
||||||
"post": {
|
"post": {
|
||||||
"description": "Handles webhook callbacks from Alea Play virtual games for bet settlement",
|
"description": "Handles webhook callbacks from Alea Play virtual games for bet settlement",
|
||||||
|
|
@ -4670,6 +4709,14 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"domain.RecommendationErrorResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"message": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"domain.ReferralSettings": {
|
"domain.ReferralSettings": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|
|
||||||
|
|
@ -357,6 +357,11 @@ definitions:
|
||||||
items: {}
|
items: {}
|
||||||
type: array
|
type: array
|
||||||
type: object
|
type: object
|
||||||
|
domain.RecommendationErrorResponse:
|
||||||
|
properties:
|
||||||
|
message:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
domain.ReferralSettings:
|
domain.ReferralSettings:
|
||||||
properties:
|
properties:
|
||||||
betReferralBonusPercentage:
|
betReferralBonusPercentage:
|
||||||
|
|
@ -1761,6 +1766,32 @@ paths:
|
||||||
summary: Verify a transfer
|
summary: Verify a transfer
|
||||||
tags:
|
tags:
|
||||||
- Chapa
|
- Chapa
|
||||||
|
/api/v1/virtual-games/recommendations/{userID}:
|
||||||
|
get:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Returns a list of recommended virtual games for a specific user
|
||||||
|
parameters:
|
||||||
|
- description: User ID
|
||||||
|
in: path
|
||||||
|
name: userID
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: Recommended games fetched successfully
|
||||||
|
schema:
|
||||||
|
additionalProperties: true
|
||||||
|
type: object
|
||||||
|
"500":
|
||||||
|
description: Failed to fetch recommendations
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/domain.RecommendationErrorResponse'
|
||||||
|
summary: Get virtual game recommendations
|
||||||
|
tags:
|
||||||
|
- Recommendations
|
||||||
/api/v1/webhooks/alea:
|
/api/v1/webhooks/alea:
|
||||||
post:
|
post:
|
||||||
consumes:
|
consumes:
|
||||||
|
|
|
||||||
5
internal/domain/recommendation.go
Normal file
5
internal/domain/recommendation.go
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
package domain
|
||||||
|
|
||||||
|
type RecommendationErrorResponse struct {
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
|
@ -147,7 +147,7 @@ type BaseballResultResponse struct {
|
||||||
EighthInning Score `json:"8"`
|
EighthInning Score `json:"8"`
|
||||||
NinthInning Score `json:"9"`
|
NinthInning Score `json:"9"`
|
||||||
ExtraInnings Score `json:"10"`
|
ExtraInnings Score `json:"10"`
|
||||||
TotalScore Score `json:"7"`
|
TotalScore Score `json:"11"`
|
||||||
} `json:"scores"`
|
} `json:"scores"`
|
||||||
Stats struct {
|
Stats struct {
|
||||||
Hits []string `json:"hits"`
|
Hits []string `json:"hits"`
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ import (
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/authentication"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/authentication"
|
||||||
"github.com/jackc/pgx/v5/pgtype"
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *Store) GetUserByEmailPhone(ctx context.Context, email, phone string) (domain.User, error) {
|
func (s *Store) GetUserByEmailPhone(ctx context.Context, email, phone string) (domain.User, error) {
|
||||||
user, err := s.queries.GetUserByEmailPhone(ctx, dbgen.GetUserByEmailPhoneParams{
|
user, err := s.queries.GetUserByEmailPhone(ctx, dbgen.GetUserByEmailPhoneParams{
|
||||||
Email: pgtype.Text{
|
Email: pgtype.Text{
|
||||||
|
|
|
||||||
48
internal/repository/recommendation.go
Normal file
48
internal/repository/recommendation.go
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RecommendationRepository interface {
|
||||||
|
GetUserVirtualGameInteractions(ctx context.Context, userID string) ([]UserGameInteraction, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type recommendationRepo struct {
|
||||||
|
store *Store
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRecommendationRepository(store *Store) RecommendationRepository {
|
||||||
|
return &recommendationRepo{store: store}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *recommendationRepo) GetUserVirtualGameInteractions(ctx context.Context, userID string) ([]UserGameInteraction, error) {
|
||||||
|
var interactions []UserGameInteraction
|
||||||
|
query := `SELECT game_id, interaction_type, timestamp FROM virtual_game_interactions WHERE user_id = $1 ORDER BY timestamp DESC LIMIT 100`
|
||||||
|
|
||||||
|
rows, err := r.store.conn.Query(ctx, query, userID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
for rows.Next() {
|
||||||
|
var u UserGameInteraction
|
||||||
|
if err := rows.Scan(&u.GameID, &u.InteractionType, &u.Timestamp); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
interactions = append(interactions, u)
|
||||||
|
}
|
||||||
|
|
||||||
|
if rows.Err() != nil {
|
||||||
|
return nil, rows.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
return interactions, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserGameInteraction struct {
|
||||||
|
GameID string
|
||||||
|
InteractionType string // e.g., "view", "bet", "like"
|
||||||
|
Timestamp string
|
||||||
|
}
|
||||||
|
|
@ -14,7 +14,6 @@ type VirtualGameRepository interface {
|
||||||
CreateVirtualGameSession(ctx context.Context, session *domain.VirtualGameSession) error
|
CreateVirtualGameSession(ctx context.Context, session *domain.VirtualGameSession) error
|
||||||
GetVirtualGameSessionByToken(ctx context.Context, token string) (*domain.VirtualGameSession, error)
|
GetVirtualGameSessionByToken(ctx context.Context, token string) (*domain.VirtualGameSession, error)
|
||||||
UpdateVirtualGameSessionStatus(ctx context.Context, id int64, status string) error
|
UpdateVirtualGameSessionStatus(ctx context.Context, id int64, status string) error
|
||||||
// UpdateVirtualGameSessionStatus(ctx context.Context, id int64, status string) error
|
|
||||||
CreateVirtualGameTransaction(ctx context.Context, tx *domain.VirtualGameTransaction) error
|
CreateVirtualGameTransaction(ctx context.Context, tx *domain.VirtualGameTransaction) error
|
||||||
GetVirtualGameTransactionByExternalID(ctx context.Context, externalID string) (*domain.VirtualGameTransaction, error)
|
GetVirtualGameTransactionByExternalID(ctx context.Context, externalID string) (*domain.VirtualGameTransaction, error)
|
||||||
UpdateVirtualGameTransactionStatus(ctx context.Context, id int64, status string) error
|
UpdateVirtualGameTransactionStatus(ctx context.Context, id int64, status string) error
|
||||||
|
|
|
||||||
7
internal/services/recommendation/port.go
Normal file
7
internal/services/recommendation/port.go
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
package recommendation
|
||||||
|
|
||||||
|
import "context"
|
||||||
|
|
||||||
|
type RecommendationService interface {
|
||||||
|
GetRecommendations(ctx context.Context, userID string) ([]string, error)
|
||||||
|
}
|
||||||
65
internal/services/recommendation/service.go
Normal file
65
internal/services/recommendation/service.go
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
package recommendation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/repository"
|
||||||
|
)
|
||||||
|
|
||||||
|
// type RecommendationService interface {
|
||||||
|
// GetRecommendations(ctx context.Context, userID string) ([]string, error)
|
||||||
|
// }
|
||||||
|
|
||||||
|
type service struct {
|
||||||
|
repo repository.RecommendationRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewService(repo repository.RecommendationRepository) RecommendationService {
|
||||||
|
return &service{repo: repo}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *service) GetRecommendations(ctx context.Context, userID string) ([]string, error) {
|
||||||
|
interactions, err := s.repo.GetUserVirtualGameInteractions(ctx, userID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
recommendationMap := map[string]int{}
|
||||||
|
for _, interaction := range interactions {
|
||||||
|
score := 1
|
||||||
|
switch interaction.InteractionType {
|
||||||
|
case "bet":
|
||||||
|
score = 5
|
||||||
|
case "view":
|
||||||
|
score = 1
|
||||||
|
}
|
||||||
|
recommendationMap[interaction.GameID] += score
|
||||||
|
}
|
||||||
|
|
||||||
|
// Example: return top 3 games based on score
|
||||||
|
topGames := sortGamesByScore(recommendationMap)
|
||||||
|
return topGames, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func sortGamesByScore(scoreMap map[string]int) []string {
|
||||||
|
type kv struct {
|
||||||
|
Key string
|
||||||
|
Value int
|
||||||
|
}
|
||||||
|
|
||||||
|
var sorted []kv
|
||||||
|
for k, v := range scoreMap {
|
||||||
|
sorted = append(sorted, kv{k, v})
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Slice(sorted, func(i, j int) bool {
|
||||||
|
return sorted[i].Value > sorted[j].Value
|
||||||
|
})
|
||||||
|
|
||||||
|
var result []string
|
||||||
|
for i := 0; i < len(sorted) && i < 3; i++ {
|
||||||
|
result = append(result, sorted[i].Key)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
@ -38,7 +38,6 @@ func NewVeliPlayService(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GenerateGameLaunchURL mirrors Alea's pattern but uses Veli's auth requirements
|
|
||||||
func (s *VeliPlayService) GenerateGameLaunchURL(ctx context.Context, userID int64, gameID, currency, mode string) (string, error) {
|
func (s *VeliPlayService) GenerateGameLaunchURL(ctx context.Context, userID int64, gameID, currency, mode string) (string, error) {
|
||||||
session := &domain.VirtualGameSession{
|
session := &domain.VirtualGameSession{
|
||||||
UserID: userID,
|
UserID: userID,
|
||||||
|
|
@ -71,7 +70,6 @@ func (s *VeliPlayService) GenerateGameLaunchURL(ctx context.Context, userID int6
|
||||||
return fmt.Sprintf("%s/launch?%s", s.config.APIURL, params.Encode()), nil
|
return fmt.Sprintf("%s/launch?%s", s.config.APIURL, params.Encode()), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandleCallback processes Veli's webhooks (similar structure to Alea)
|
|
||||||
func (s *VeliPlayService) HandleCallback(ctx context.Context, callback *domain.VeliCallback) error {
|
func (s *VeliPlayService) HandleCallback(ctx context.Context, callback *domain.VeliCallback) error {
|
||||||
if !s.verifyCallbackSignature(callback) {
|
if !s.verifyCallbackSignature(callback) {
|
||||||
return errors.New("invalid callback signature")
|
return errors.New("invalid callback signature")
|
||||||
|
|
@ -114,7 +112,6 @@ func (s *VeliPlayService) HandleCallback(ctx context.Context, callback *domain.V
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shared helper methods (same pattern as Alea)
|
|
||||||
func (s *VeliPlayService) generateSignature(data string) string {
|
func (s *VeliPlayService) generateSignature(data string) string {
|
||||||
h := hmac.New(sha256.New, []byte(s.config.SecretKey))
|
h := hmac.New(sha256.New, []byte(s.config.SecretKey))
|
||||||
h.Write([]byte(data))
|
h.Write([]byte(data))
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/company"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/company"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/event"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/event"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds"
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/recommendation"
|
||||||
referralservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/referal"
|
referralservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/referal"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/result"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/result"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/ticket"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/ticket"
|
||||||
|
|
@ -33,6 +34,7 @@ type App struct {
|
||||||
fiber *fiber.App
|
fiber *fiber.App
|
||||||
aleaVirtualGameService alea.AleaVirtualGameService
|
aleaVirtualGameService alea.AleaVirtualGameService
|
||||||
veliVirtualGameService veli.VeliVirtualGameService
|
veliVirtualGameService veli.VeliVirtualGameService
|
||||||
|
recommendationSvc recommendation.RecommendationService
|
||||||
cfg *config.Config
|
cfg *config.Config
|
||||||
logger *slog.Logger
|
logger *slog.Logger
|
||||||
NotidicationStore *notificationservice.Service
|
NotidicationStore *notificationservice.Service
|
||||||
|
|
@ -74,6 +76,7 @@ func NewApp(
|
||||||
virtualGameSvc virtualgameservice.VirtualGameService,
|
virtualGameSvc virtualgameservice.VirtualGameService,
|
||||||
aleaVirtualGameService alea.AleaVirtualGameService,
|
aleaVirtualGameService alea.AleaVirtualGameService,
|
||||||
veliVirtualGameService veli.VeliVirtualGameService,
|
veliVirtualGameService veli.VeliVirtualGameService,
|
||||||
|
recommendationSvc recommendation.RecommendationService,
|
||||||
resultSvc *result.Service,
|
resultSvc *result.Service,
|
||||||
cfg *config.Config,
|
cfg *config.Config,
|
||||||
) *App {
|
) *App {
|
||||||
|
|
@ -113,6 +116,7 @@ func NewApp(
|
||||||
virtualGameSvc: virtualGameSvc,
|
virtualGameSvc: virtualGameSvc,
|
||||||
aleaVirtualGameService: aleaVirtualGameService,
|
aleaVirtualGameService: aleaVirtualGameService,
|
||||||
veliVirtualGameService: veliVirtualGameService,
|
veliVirtualGameService: veliVirtualGameService,
|
||||||
|
recommendationSvc: recommendationSvc,
|
||||||
resultSvc: resultSvc,
|
resultSvc: resultSvc,
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/event"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/event"
|
||||||
notificationservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/notfication"
|
notificationservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/notfication"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds"
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/recommendation"
|
||||||
referralservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/referal"
|
referralservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/referal"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/ticket"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/ticket"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/transaction"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/transaction"
|
||||||
|
|
@ -39,6 +40,7 @@ type Handler struct {
|
||||||
virtualGameSvc virtualgameservice.VirtualGameService
|
virtualGameSvc virtualgameservice.VirtualGameService
|
||||||
aleaVirtualGameSvc alea.AleaVirtualGameService
|
aleaVirtualGameSvc alea.AleaVirtualGameService
|
||||||
veliVirtualGameSvc veli.VeliVirtualGameService
|
veliVirtualGameSvc veli.VeliVirtualGameService
|
||||||
|
recommendationSvc recommendation.RecommendationService
|
||||||
authSvc *authentication.Service
|
authSvc *authentication.Service
|
||||||
jwtConfig jwtutil.JwtConfig
|
jwtConfig jwtutil.JwtConfig
|
||||||
validator *customvalidator.CustomValidator
|
validator *customvalidator.CustomValidator
|
||||||
|
|
@ -54,6 +56,7 @@ func New(
|
||||||
virtualGameSvc virtualgameservice.VirtualGameService,
|
virtualGameSvc virtualgameservice.VirtualGameService,
|
||||||
aleaVirtualGameSvc alea.AleaVirtualGameService,
|
aleaVirtualGameSvc alea.AleaVirtualGameService,
|
||||||
veliVirtualGameSvc veli.VeliVirtualGameService,
|
veliVirtualGameSvc veli.VeliVirtualGameService,
|
||||||
|
recommendationSvc recommendation.RecommendationService,
|
||||||
userSvc *user.Service,
|
userSvc *user.Service,
|
||||||
transactionSvc *transaction.Service,
|
transactionSvc *transaction.Service,
|
||||||
ticketSvc *ticket.Service,
|
ticketSvc *ticket.Service,
|
||||||
|
|
@ -83,6 +86,7 @@ func New(
|
||||||
virtualGameSvc: virtualGameSvc,
|
virtualGameSvc: virtualGameSvc,
|
||||||
aleaVirtualGameSvc: aleaVirtualGameSvc,
|
aleaVirtualGameSvc: aleaVirtualGameSvc,
|
||||||
veliVirtualGameSvc: veliVirtualGameSvc,
|
veliVirtualGameSvc: veliVirtualGameSvc,
|
||||||
|
recommendationSvc: recommendationSvc,
|
||||||
authSvc: authSvc,
|
authSvc: authSvc,
|
||||||
jwtConfig: jwtConfig,
|
jwtConfig: jwtConfig,
|
||||||
Cfg: cfg,
|
Cfg: cfg,
|
||||||
|
|
|
||||||
26
internal/web_server/handlers/recommendation.go
Normal file
26
internal/web_server/handlers/recommendation.go
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// @Summary Get virtual game recommendations
|
||||||
|
// @Description Returns a list of recommended virtual games for a specific user
|
||||||
|
// @Tags Recommendations
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param userID path string true "User ID"
|
||||||
|
// @Success 200 {object} map[string]interface{} "Recommended games fetched successfully"
|
||||||
|
// @Failure 500 {object} domain.RecommendationErrorResponse "Failed to fetch recommendations"
|
||||||
|
// @Router /api/v1/virtual-games/recommendations/{userID} [get]
|
||||||
|
func (h *Handler) GetRecommendations(c *fiber.Ctx) error {
|
||||||
|
userID := c.Params("userID") // or from JWT
|
||||||
|
recommendations, err := h.recommendationSvc.GetRecommendations(c.Context(), userID)
|
||||||
|
if err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch recommendations")
|
||||||
|
}
|
||||||
|
return c.JSON(fiber.Map{
|
||||||
|
"message": "Recommended games fetched successfully",
|
||||||
|
"recommended_games": recommendations,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -23,6 +23,7 @@ func (a *App) initAppRoutes() {
|
||||||
a.virtualGameSvc,
|
a.virtualGameSvc,
|
||||||
a.aleaVirtualGameService,
|
a.aleaVirtualGameService,
|
||||||
a.veliVirtualGameService,
|
a.veliVirtualGameService,
|
||||||
|
a.recommendationSvc,
|
||||||
a.userSvc,
|
a.userSvc,
|
||||||
a.transactionSvc,
|
a.transactionSvc,
|
||||||
a.ticketSvc,
|
a.ticketSvc,
|
||||||
|
|
@ -36,6 +37,8 @@ func (a *App) initAppRoutes() {
|
||||||
a.cfg,
|
a.cfg,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
group := a.fiber.Group("/api/v1")
|
||||||
|
|
||||||
a.fiber.Get("/", func(c *fiber.Ctx) error {
|
a.fiber.Get("/", func(c *fiber.Ctx) error {
|
||||||
return c.JSON(fiber.Map{
|
return c.JSON(fiber.Map{
|
||||||
"message": "Welcome to the FortuneBet API",
|
"message": "Welcome to the FortuneBet API",
|
||||||
|
|
@ -177,7 +180,6 @@ func (a *App) initAppRoutes() {
|
||||||
a.fiber.Post("/transfer/refill/:id", a.authMiddleware, h.RefillWallet)
|
a.fiber.Post("/transfer/refill/:id", a.authMiddleware, h.RefillWallet)
|
||||||
|
|
||||||
//Chapa Routes
|
//Chapa Routes
|
||||||
group := a.fiber.Group("/api/v1")
|
|
||||||
|
|
||||||
group.Post("/chapa/payments/initialize", h.InitializePayment)
|
group.Post("/chapa/payments/initialize", h.InitializePayment)
|
||||||
group.Get("/chapa/payments/verify/:tx_ref", h.VerifyTransaction)
|
group.Get("/chapa/payments/verify/:tx_ref", h.VerifyTransaction)
|
||||||
|
|
@ -194,6 +196,9 @@ func (a *App) initAppRoutes() {
|
||||||
group.Get("/veli-games/launch", a.authMiddleware, h.LaunchVeliGame)
|
group.Get("/veli-games/launch", a.authMiddleware, h.LaunchVeliGame)
|
||||||
group.Post("/webhooks/veli-games", a.authMiddleware, h.HandleVeliCallback)
|
group.Post("/webhooks/veli-games", a.authMiddleware, h.HandleVeliCallback)
|
||||||
|
|
||||||
|
// Recommendation Routes
|
||||||
|
group.Get("/virtual-games/recommendations/:userID", a.authMiddleware, h.GetRecommendations)
|
||||||
|
|
||||||
// Transactions /transactions
|
// Transactions /transactions
|
||||||
a.fiber.Post("/transaction", a.authMiddleware, h.CreateTransaction)
|
a.fiber.Post("/transaction", a.authMiddleware, h.CreateTransaction)
|
||||||
a.fiber.Get("/transaction", a.authMiddleware, h.GetAllTransactions)
|
a.fiber.Get("/transaction", a.authMiddleware, h.GetAllTransactions)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user