From 8670fba6a40daf6e070867417e8816f15040600a Mon Sep 17 00:00:00 2001 From: dawitel Date: Sat, 12 Apr 2025 04:12:22 +0300 Subject: [PATCH] feat: referal completed --- cmd/main.go | 7 +- db/query/referal.sql | 7 + gen/db/referal.sql.go | 18 +++ internal/domain/user.go | 5 + internal/repository/referal.go | 13 ++ internal/services/notfication/service.go | 2 +- internal/services/referal/port.go | 2 +- internal/services/referal/service.go | 149 ++++++++++++------ .../web_server/handlers/referal_handlers.go | 13 +- internal/web_server/routes.go | 2 +- 10 files changed, 155 insertions(+), 63 deletions(-) diff --git a/cmd/main.go b/cmd/main.go index 7ac39e6..88a3b8e 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -13,6 +13,7 @@ import ( "github.com/SamuelTariku/FortuneBet-Backend/internal/services/authentication" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/bet" notificationservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/notfication" + referralservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/referal" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/ticket" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/transaction" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/user" @@ -63,13 +64,15 @@ func main() { transactionSvc := transaction.NewService(store) notificationRepo := repository.NewNotificationRepository(store) + referalRepo := repository.NewReferralRepository(store) + notificationSvc := notificationservice.New(notificationRepo, logger, cfg) + referalSvc := referralservice.New(referalRepo, *walletSvc, store, cfg, logger) app := httpserver.NewApp(cfg.Port, v, authSvc, logger, jwtutil.JwtConfig{ JwtAccessKey: cfg.JwtKey, JwtAccessExpiry: cfg.AccessExpiry, - }, userSvc, ticketSvc, betSvc, walletSvc, transactionSvc, notificationSvc, - ) + }, userSvc, ticketSvc, betSvc, walletSvc, transactionSvc, notificationSvc, referalSvc) logger.Info("Starting server", "port", cfg.Port) if err := app.Run(); err != nil { diff --git a/db/query/referal.sql b/db/query/referal.sql index 8f0adaa..a10b274 100644 --- a/db/query/referal.sql +++ b/db/query/referal.sql @@ -22,6 +22,13 @@ SET WHERE id = $1 RETURNING *; +-- name: UpdateReferralCode :exec +UPDATE users +SET + referral_code = $2, + updated_at = CURRENT_TIMESTAMP +WHERE id = $1; + -- name: GetReferralStats :one SELECT COUNT(*) as total_referrals, diff --git a/gen/db/referal.sql.go b/gen/db/referal.sql.go index a2e6306..9fcc1f1 100644 --- a/gen/db/referal.sql.go +++ b/gen/db/referal.sql.go @@ -234,6 +234,24 @@ func (q *Queries) UpdateReferral(ctx context.Context, arg UpdateReferralParams) return i, err } +const UpdateReferralCode = `-- name: UpdateReferralCode :exec +UPDATE users +SET + referral_code = $2, + updated_at = CURRENT_TIMESTAMP +WHERE id = $1 +` + +type UpdateReferralCodeParams struct { + ID int64 + ReferralCode pgtype.Text +} + +func (q *Queries) UpdateReferralCode(ctx context.Context, arg UpdateReferralCodeParams) error { + _, err := q.db.Exec(ctx, UpdateReferralCode, arg.ID, arg.ReferralCode) + return err +} + const UpdateReferralSettings = `-- name: UpdateReferralSettings :one UPDATE referral_settings SET diff --git a/internal/domain/user.go b/internal/domain/user.go index b47928a..89c0d95 100644 --- a/internal/domain/user.go +++ b/internal/domain/user.go @@ -50,3 +50,8 @@ type UpdateUserReq struct { LastName ValidString Suspended ValidBool } + +type UpdateUserReferalCode struct { + UserID int64 + Code string +} diff --git a/internal/repository/referal.go b/internal/repository/referal.go index 022b827..4320f0d 100644 --- a/internal/repository/referal.go +++ b/internal/repository/referal.go @@ -19,6 +19,7 @@ type ReferralRepository interface { UpdateSettings(ctx context.Context, settings *domain.ReferralSettings) error CreateSettings(ctx context.Context, settings *domain.ReferralSettings) error GetReferralByReferredID(ctx context.Context, referredID string) (*domain.Referral, error) // New method + UpdateUserReferalCode(ctx context.Context, codedata domain.UpdateUserReferalCode) error } type ReferralRepo struct { @@ -29,6 +30,18 @@ func NewReferralRepository(store *Store) ReferralRepository { return &ReferralRepo{store: store} } +func (r *ReferralRepo) UpdateUserReferalCode(ctx context.Context, codedata domain.UpdateUserReferalCode) error { + params := dbgen.UpdateReferralCodeParams{ + ID: codedata.UserID, + ReferralCode: pgtype.Text{ + String: codedata.Code, + Valid: true, + }, + } + + return r.store.queries.UpdateReferralCode(ctx, params) +} + func (r *ReferralRepo) CreateReferral(ctx context.Context, referral *domain.Referral) error { rewardAmount := pgtype.Numeric{} if err := rewardAmount.Scan(referral.RewardAmount); err != nil { diff --git a/internal/services/notfication/service.go b/internal/services/notfication/service.go index 1ca03a3..e21f7da 100644 --- a/internal/services/notfication/service.go +++ b/internal/services/notfication/service.go @@ -17,11 +17,11 @@ import ( type Service struct { repo repository.NotificationRepository - logger *slog.Logger connections sync.Map notificationCh chan *domain.Notification stopCh chan struct{} config *config.Config + logger *slog.Logger } func New(repo repository.NotificationRepository, logger *slog.Logger, cfg *config.Config) NotificationStore { diff --git a/internal/services/referal/port.go b/internal/services/referal/port.go index a51c4b5..5fb867b 100644 --- a/internal/services/referal/port.go +++ b/internal/services/referal/port.go @@ -8,7 +8,7 @@ import ( type ReferralStore interface { GenerateReferralCode() (string, error) - CreateReferral(ctx context.Context, userID string) (*domain.Referral, error) + CreateReferral(ctx context.Context, userID int64) error ProcessReferral(ctx context.Context, referredID, referralCode string) error ProcessDepositBonus(ctx context.Context, userID string, amount float64) error GetReferralStats(ctx context.Context, userID string) (*domain.ReferralStats, error) diff --git a/internal/services/referal/service.go b/internal/services/referal/service.go index c821b18..6650c1f 100644 --- a/internal/services/referal/service.go +++ b/internal/services/referal/service.go @@ -5,9 +5,11 @@ import ( "crypto/rand" "encoding/base32" "errors" + "log/slog" "strconv" "time" + "github.com/SamuelTariku/FortuneBet-Backend/internal/config" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "github.com/SamuelTariku/FortuneBet-Backend/internal/repository" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/wallet" @@ -17,13 +19,17 @@ type Service struct { repo repository.ReferralRepository walletSvc wallet.Service store *repository.Store + config *config.Config + logger *slog.Logger } -func NewService(repo repository.ReferralRepository, walletSvc wallet.Service, store *repository.Store) *Service { +func New(repo repository.ReferralRepository, walletSvc wallet.Service, store *repository.Store, cfg *config.Config, logger *slog.Logger) *Service { return &Service{ repo: repo, walletSvc: walletSvc, store: store, + config: cfg, + logger: logger, } } @@ -37,77 +43,57 @@ var ( func (s *Service) GenerateReferralCode() (string, error) { b := make([]byte, 8) if _, err := rand.Read(b); err != nil { + s.logger.Error("Failed to generate random bytes for referral code", "error", err) return "", err } - return base32.StdEncoding.EncodeToString(b)[:10], nil + code := base32.StdEncoding.EncodeToString(b)[:10] + s.logger.Debug("Generated referral code", "code", code) + return code, nil } -func (s *Service) CreateReferral(ctx context.Context, userPhone string) (*domain.Referral, error) { - settings, err := s.repo.GetSettings(ctx) - if err != nil { - return nil, err - } - +func (s *Service) CreateReferral(ctx context.Context, userID int64) error { + s.logger.Info("Creating referral code for user", "userID", userID) code, err := s.GenerateReferralCode() if err != nil { - return nil, err + s.logger.Error("Failed to generate referral code", "error", err) + return err } - userID, err := strconv.ParseInt(userPhone, 10, 64) - if err != nil { - return nil, errors.New("invalid phone number format") + if err := s.repo.UpdateUserReferalCode(ctx, domain.UpdateUserReferalCode{ + UserID: userID, + Code: code, + }); err != nil { + return err } - wallets, err := s.walletSvc.GetWalletsByUser(ctx, userID) - if err != nil { - return nil, err - } - if len(wallets) == 0 { - _, err = s.walletSvc.CreateWallet(ctx, domain.CreateWallet{ - IsWithdraw: true, - IsBettable: true, - UserID: userID, - }) - if err != nil { - return nil, err - } - } - - referral := &domain.Referral{ - ReferrerID: userPhone, - ReferralCode: code, - Status: domain.ReferralPending, - RewardAmount: settings.ReferralRewardAmount, - ExpiresAt: time.Now().Add(time.Duration(settings.ExpiresAfterDays) * 24 * time.Hour), - CreatedAt: time.Now(), - UpdatedAt: time.Now(), - } - - if err := s.repo.CreateReferral(ctx, referral); err != nil { - return nil, err - } - - return referral, nil + return nil } func (s *Service) ProcessReferral(ctx context.Context, referredPhone, referralCode string) error { + s.logger.Info("Processing referral", "referredPhone", referredPhone, "referralCode", referralCode) + referral, err := s.repo.GetReferralByCode(ctx, referralCode) if err != nil { + s.logger.Error("Failed to get referral by code", "referralCode", referralCode, "error", err) return err } if referral == nil || referral.Status != domain.ReferralPending || referral.ExpiresAt.Before(time.Now()) { + s.logger.Warn("Invalid or expired referral", "referralCode", referralCode, "status", referral.Status) return ErrInvalidReferral } user, err := s.store.GetUserByPhone(ctx, referredPhone) if err != nil { if errors.Is(err, domain.ErrUserNotFound) { + s.logger.Warn("User not found for referral", "referredPhone", referredPhone) return ErrUserNotFound } + s.logger.Error("Failed to get user by phone", "referredPhone", referredPhone, "error", err) return err } if !user.PhoneVerified { + s.logger.Warn("Phone not verified for referral", "referredPhone", referredPhone) return ErrInvalidReferralSignup } @@ -116,99 +102,166 @@ func (s *Service) ProcessReferral(ctx context.Context, referredPhone, referralCo referral.UpdatedAt = time.Now() if err := s.repo.UpdateReferral(ctx, referral); err != nil { + s.logger.Error("Failed to update referral", "referralCode", referralCode, "error", err) return err } referrerID, err := strconv.ParseInt(referral.ReferrerID, 10, 64) if err != nil { + s.logger.Error("Invalid referrer phone number format", "referrerID", referral.ReferrerID, "error", err) return errors.New("invalid referrer phone number format") } wallets, err := s.walletSvc.GetWalletsByUser(ctx, referrerID) if err != nil { + s.logger.Error("Failed to get wallets for referrer", "referrerID", referrerID, "error", err) return err } if len(wallets) == 0 { + s.logger.Error("Referrer has no wallet", "referrerID", referrerID) return errors.New("referrer has no wallet") } walletID := wallets[0].ID currentBonus := float64(wallets[0].Balance) - return s.walletSvc.Add(ctx, walletID, domain.Currency(int64((currentBonus+referral.RewardAmount)*100))) + err = s.walletSvc.Add(ctx, walletID, domain.Currency(int64((currentBonus+referral.RewardAmount)*100))) + if err != nil { + s.logger.Error("Failed to add referral reward to wallet", "walletID", walletID, "referrerID", referrerID, "error", err) + return err + } + + s.logger.Info("Referral processed successfully", "referredPhone", referredPhone, "referralCode", referralCode, "rewardAmount", referral.RewardAmount) + return nil } func (s *Service) ProcessDepositBonus(ctx context.Context, userPhone string, amount float64) error { + s.logger.Info("Processing deposit bonus", "userPhone", userPhone, "amount", amount) + settings, err := s.repo.GetSettings(ctx) if err != nil { + s.logger.Error("Failed to get referral settings", "error", err) return err } userID, err := strconv.ParseInt(userPhone, 10, 64) if err != nil { + s.logger.Error("Invalid phone number format", "userPhone", userPhone, "error", err) return errors.New("invalid phone number format") } wallets, err := s.walletSvc.GetWalletsByUser(ctx, userID) if err != nil { + s.logger.Error("Failed to get wallets for user", "userID", userID, "error", err) return err } if len(wallets) == 0 { + s.logger.Error("User has no wallet", "userID", userID) return errors.New("user has no wallet") } walletID := wallets[0].ID bonus := amount * (settings.CashbackPercentage / 100) currentBonus := float64(wallets[0].Balance) - return s.walletSvc.Add(ctx, walletID, domain.Currency(int64((currentBonus+bonus)*100))) + err = s.walletSvc.Add(ctx, walletID, domain.Currency(int64((currentBonus+bonus)*100))) + if err != nil { + s.logger.Error("Failed to add deposit bonus to wallet", "walletID", walletID, "userID", userID, "bonus", bonus, "error", err) + return err + } + + s.logger.Info("Deposit bonus processed successfully", "userPhone", userPhone, "bonus", bonus) + return nil } func (s *Service) ProcessBetReferral(ctx context.Context, userPhone string, betAmount float64) error { + s.logger.Info("Processing bet referral", "userPhone", userPhone, "betAmount", betAmount) + settings, err := s.repo.GetSettings(ctx) if err != nil { + s.logger.Error("Failed to get referral settings", "error", err) return err } referral, err := s.repo.GetReferralByReferredID(ctx, userPhone) if err != nil { + s.logger.Error("Failed to get referral by referred ID", "userPhone", userPhone, "error", err) return err } if referral == nil || referral.Status != domain.ReferralCompleted { + s.logger.Warn("No valid referral found", "userPhone", userPhone, "status", referral.Status) return ErrNoReferralFound } referrerID, err := strconv.ParseInt(referral.ReferrerID, 10, 64) if err != nil { + s.logger.Error("Invalid referrer phone number format", "referrerID", referral.ReferrerID, "error", err) return errors.New("invalid referrer phone number format") } wallets, err := s.walletSvc.GetWalletsByUser(ctx, referrerID) if err != nil { + s.logger.Error("Failed to get wallets for referrer", "referrerID", referrerID, "error", err) return err } if len(wallets) == 0 { + s.logger.Error("Referrer has no wallet", "referrerID", referrerID) return errors.New("referrer has no wallet") } bonusPercentage := settings.BetReferralBonusPercentage if bonusPercentage == 0 { bonusPercentage = 5.0 + s.logger.Debug("Using default bet referral bonus percentage", "percentage", bonusPercentage) } bonus := betAmount * (bonusPercentage / 100) walletID := wallets[0].ID currentBalance := float64(wallets[0].Balance) - return s.walletSvc.Add(ctx, walletID, domain.Currency(int64((currentBalance+bonus)*100))) + err = s.walletSvc.Add(ctx, walletID, domain.Currency(int64((currentBalance+bonus)*100))) + if err != nil { + s.logger.Error("Failed to add bet referral bonus to wallet", "walletID", walletID, "referrerID", referrerID, "bonus", bonus, "error", err) + return err + } + + s.logger.Info("Bet referral processed successfully", "userPhone", userPhone, "referrerID", referrerID, "bonus", bonus) + return nil } func (s *Service) GetReferralStats(ctx context.Context, userPhone string) (*domain.ReferralStats, error) { - return s.repo.GetReferralStats(ctx, userPhone) + s.logger.Info("Fetching referral stats", "userPhone", userPhone) + + stats, err := s.repo.GetReferralStats(ctx, userPhone) + if err != nil { + s.logger.Error("Failed to get referral stats", "userPhone", userPhone, "error", err) + return nil, err + } + + s.logger.Info("Referral stats retrieved successfully", "userPhone", userPhone, "totalReferrals", stats.TotalReferrals) + return stats, nil } func (s *Service) UpdateReferralSettings(ctx context.Context, settings *domain.ReferralSettings) error { + s.logger.Info("Updating referral settings", "settingsID", settings.ID) + settings.UpdatedAt = time.Now() - return s.repo.UpdateSettings(ctx, settings) + err := s.repo.UpdateSettings(ctx, settings) + if err != nil { + s.logger.Error("Failed to update referral settings", "settingsID", settings.ID, "error", err) + return err + } + + s.logger.Info("Referral settings updated successfully", "settingsID", settings.ID) + return nil } func (s *Service) GetReferralSettings(ctx context.Context) (*domain.ReferralSettings, error) { - return s.repo.GetSettings(ctx) + s.logger.Info("Fetching referral settings") + + settings, err := s.repo.GetSettings(ctx) + if err != nil { + s.logger.Error("Failed to get referral settings", "error", err) + return nil, err + } + + s.logger.Info("Referral settings retrieved successfully", "settingsID", settings.ID) + return settings, nil } diff --git a/internal/web_server/handlers/referal_handlers.go b/internal/web_server/handlers/referal_handlers.go index 91fdf64..d978e0b 100644 --- a/internal/web_server/handlers/referal_handlers.go +++ b/internal/web_server/handlers/referal_handlers.go @@ -6,26 +6,19 @@ import ( "github.com/gofiber/fiber/v2" ) -func (h *Handler) CreateReferral(c *fiber.Ctx) error { +func (h *Handler) CreateReferralCode(c *fiber.Ctx) error { userID, ok := c.Locals("user_id").(int64) if !ok || userID == 0 { h.logger.Error("Invalid user ID in context") return fiber.NewError(fiber.StatusUnauthorized, "Invalid user identification") } - user, err := h.userSvc.GetUserByID(c.Context(), userID) - if err != nil { - h.logger.Error("Failed to get user", "userID", userID, "error", err) - return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve user") - } - - referral, err := h.referralSvc.CreateReferral(c.Context(), user.PhoneNumber) - if err != nil { + if err := h.referralSvc.CreateReferral(c.Context(), userID); err != nil { h.logger.Error("Failed to create referral", "userID", userID, "error", err) return fiber.NewError(fiber.StatusInternalServerError, "Failed to create referral") } - return response.WriteJSON(c, fiber.StatusOK, "Referral created successfully", referral, nil) + return response.WriteJSON(c, fiber.StatusOK, "Referral created successfully", nil, nil) } // GetReferralStats godoc diff --git a/internal/web_server/routes.go b/internal/web_server/routes.go index b393abd..e333f1f 100644 --- a/internal/web_server/routes.go +++ b/internal/web_server/routes.go @@ -71,7 +71,7 @@ func (a *App) initAppRoutes() { a.fiber.Patch("/wallet/:id", h.UpdateWalletActive) // Referral Routes - a.fiber.Post("/referral/create", a.authMiddleware, h.CreateReferral) + a.fiber.Post("/referral/create", a.authMiddleware, h.CreateReferralCode) a.fiber.Get("/referral/stats", a.authMiddleware, h.GetReferralStats) a.fiber.Get("/referral/settings", h.GetReferralSettings) a.fiber.Patch("/referral/settings", a.authMiddleware, h.UpdateReferralSettings)