From 144cb0a42c894d039d94e8fce6b4acb6adfdf65a Mon Sep 17 00:00:00 2001 From: Samuel Tariku Date: Wed, 3 Sep 2025 22:26:12 +0300 Subject: [PATCH] fix: sending daily result report instead of hourly --- internal/services/messenger/email.go | 4 +- internal/services/notification/service.go | 6 +- internal/services/result/service.go | 134 +++++++++++++++++- internal/services/user/common.go | 2 +- internal/services/virtualGame/veli/service.go | 16 +-- internal/web_server/cron.go | 2 +- 6 files changed, 145 insertions(+), 19 deletions(-) diff --git a/internal/services/messenger/email.go b/internal/services/messenger/email.go index ddb3542..a99d2fe 100644 --- a/internal/services/messenger/email.go +++ b/internal/services/messenger/email.go @@ -3,10 +3,9 @@ package messenger import ( "context" "github.com/resend/resend-go/v2" - ) -func (s *Service) SendEmail(ctx context.Context, receiverEmail, message string, subject string) error { +func (s *Service) SendEmail(ctx context.Context, receiverEmail, message string, messageHTML string, subject string) error { apiKey := s.config.ResendApiKey client := resend.NewClient(apiKey) formattedSenderEmail := "FortuneBets <" + s.config.ResendSenderEmail + ">" @@ -15,6 +14,7 @@ func (s *Service) SendEmail(ctx context.Context, receiverEmail, message string, To: []string{receiverEmail}, Subject: subject, Text: message, + Html: messageHTML, } _, err := client.Emails.Send(params) diff --git a/internal/services/notification/service.go b/internal/services/notification/service.go index 76531db..6ba4044 100644 --- a/internal/services/notification/service.go +++ b/internal/services/notification/service.go @@ -300,7 +300,7 @@ func (s *Service) handleNotification(notification *domain.Notification) { } case domain.DeliveryChannelEmail: - err := s.SendNotificationEmail(ctx, notification.RecipientID, notification.Payload.Headline, notification.Payload.Message) + err := s.SendNotificationEmail(ctx, notification.RecipientID, notification.Payload.Message, notification.Payload.Headline) if err != nil { notification.DeliveryStatus = domain.DeliveryStatusFailed } else { @@ -371,7 +371,7 @@ func (s *Service) SendNotificationEmail(ctx context.Context, recipientID int64, if user.Email == "" { return fmt.Errorf("email is invalid") } - err = s.messengerSvc.SendEmail(ctx, user.Email, message, subject) + err = s.messengerSvc.SendEmail(ctx, user.Email, message, message, subject) if err != nil { s.mongoLogger.Error("[NotificationSvc.HandleNotification] Failed to send notification SMS", zap.Int64("recipient_id", recipientID), @@ -440,7 +440,7 @@ func (s *Service) retryFailedNotifications() { return } case domain.DeliveryChannelEmail: - if err := s.SendNotificationEmail(ctx, notification.RecipientID, notification.Payload.Headline, notification.Payload.Message); err == nil { + if err := s.SendNotificationEmail(ctx, notification.RecipientID, notification.Payload.Message, notification.Payload.Headline); err == nil { notification.DeliveryStatus = domain.DeliveryStatusSent if _, err := s.repo.UpdateNotificationStatus(ctx, notification.ID, string(notification.DeliveryStatus), notification.IsRead, notification.Metadata); err != nil { s.mongoLogger.Error("[NotificationSvc.RetryFailedNotifications] Failed to update after retry", diff --git a/internal/services/result/service.go b/internal/services/result/service.go index a872924..c8e69b9 100644 --- a/internal/services/result/service.go +++ b/internal/services/result/service.go @@ -16,6 +16,7 @@ import ( "github.com/SamuelTariku/FortuneBet-Backend/internal/services/bet" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/event" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/league" + "github.com/SamuelTariku/FortuneBet-Backend/internal/services/messenger" notificationservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/notification" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/user" @@ -33,6 +34,7 @@ type Service struct { eventSvc event.Service leagueSvc league.Service notificationSvc *notificationservice.Service + messengerSvc *messenger.Service userSvc user.Service } @@ -46,6 +48,7 @@ func NewService( eventSvc event.Service, leagueSvc league.Service, notificationSvc *notificationservice.Service, + messengerSvc *messenger.Service, userSvc user.Service, ) *Service { return &Service{ @@ -59,6 +62,7 @@ func NewService( eventSvc: eventSvc, leagueSvc: leagueSvc, notificationSvc: notificationSvc, + messengerSvc: messengerSvc, userSvc: userSvc, } } @@ -491,6 +495,7 @@ func (s *Service) CheckAndSendResultNotifications(ctx context.Context, createdAf } func buildHeadlineAndMessage(counts domain.ResultLog) (string, string) { + totalIssues := counts.StatusNotFinishedCount + counts.StatusToBeFixedCount + counts.StatusPostponedCount + counts.StatusRemovedCount totalBets := counts.StatusEndedBets + counts.StatusNotFinishedBets + counts.StatusPostponedBets + counts.StatusRemovedBets + counts.StatusToBeFixedBets if totalIssues == 0 { @@ -517,10 +522,124 @@ func buildHeadlineAndMessage(counts domain.ResultLog) (string, string) { } headline := "⚠️ Issues Found Processing Event Results" - message := fmt.Sprintf("Processed expired event results: %s. Please review pending entries.", strings.Join(parts, ", ")) + message := fmt.Sprintf("Processed expired event results: %s. Please review pending entries.", strings.Join(parts, ", ")) return headline, message } +func buildHeadlineAndMessageEmail(counts domain.ResultLog, user domain.User) (string, string, string) { + totalIssues := counts.StatusNotFinishedCount + counts.StatusToBeFixedCount + + counts.StatusPostponedCount + counts.StatusRemovedCount + totalEvents := counts.StatusEndedCount + counts.StatusNotFinishedCount + + counts.StatusToBeFixedCount + counts.StatusPostponedCount + counts.StatusRemovedCount + totalBets := counts.StatusEndedBets + counts.StatusNotFinishedBets + + counts.StatusPostponedBets + counts.StatusRemovedBets + counts.StatusToBeFixedBets + + greeting := fmt.Sprintf("Hi %s %s,", user.FirstName, user.LastName) + + if totalIssues == 0 { + headline := "✅ Daily Results Report — All Events Processed Successfully" + plain := fmt.Sprintf(`%s + +Daily Results Summary: +- %d Ended Events +- %d Total Bets + +All events were processed successfully, and no issues were detected. + +Best regards, +The System`, greeting, counts.StatusEndedCount, totalBets) + + html := fmt.Sprintf(`

%s

+

Daily Results Summary

+ +

All events were processed successfully, and no issues were detected.

+

Best regards,
The System

`, + greeting, counts.StatusEndedCount, totalBets) + + return headline, plain, html + } + + partsPlain := []string{} + partsHTML := []string{} + + if counts.StatusNotFinishedCount > 0 { + partsPlain = append(partsPlain, + fmt.Sprintf("- %d Unresolved Events (%d Bets)", counts.StatusNotFinishedCount, counts.StatusNotFinishedBets)) + partsHTML = append(partsHTML, + fmt.Sprintf("
  • %d Unresolved Events (%d Bets)
  • ", counts.StatusNotFinishedCount, counts.StatusNotFinishedBets)) + } + if counts.StatusToBeFixedCount > 0 { + partsPlain = append(partsPlain, + fmt.Sprintf("- %d Requires Review (%d Bets)", counts.StatusToBeFixedCount, counts.StatusToBeFixedBets)) + partsHTML = append(partsHTML, + fmt.Sprintf("
  • %d Requires Review (%d Bets)
  • ", counts.StatusToBeFixedCount, counts.StatusToBeFixedBets)) + } + if counts.StatusPostponedCount > 0 { + partsPlain = append(partsPlain, + fmt.Sprintf("- %d Postponed Events (%d Bets)", counts.StatusPostponedCount, counts.StatusPostponedBets)) + partsHTML = append(partsHTML, + fmt.Sprintf("
  • %d Postponed Events (%d Bets)
  • ", counts.StatusPostponedCount, counts.StatusPostponedBets)) + } + if counts.StatusRemovedCount > 0 { + partsPlain = append(partsPlain, + fmt.Sprintf("- %d Discarded Events (%d Bets)", counts.StatusRemovedCount, counts.StatusRemovedBets)) + partsHTML = append(partsHTML, + fmt.Sprintf("
  • %d Discarded Events (%d Bets)
  • ", counts.StatusRemovedCount, counts.StatusRemovedBets)) + } + if counts.StatusEndedCount > 0 { + partsPlain = append(partsPlain, + fmt.Sprintf("- %d Successfully Ended Events (%d Bets)", counts.StatusEndedCount, counts.StatusEndedBets)) + partsHTML = append(partsHTML, + fmt.Sprintf("
  • %d Successfully Ended Events (%d Bets)
  • ", counts.StatusEndedCount, counts.StatusEndedBets)) + } + + headline := "⚠️ Daily Results Report — Review Required" + + plain := fmt.Sprintf(`%s + +Daily Results Summary: +%s + +Totals: +- %d Events Processed +- %d Total Bets + +Next Steps: +Some events require your attention. Please log into the admin dashboard to review pending issues. + +Best regards, +The System`, + greeting, + strings.Join(partsPlain, "\n"), + totalEvents, + totalBets, + ) + + html := fmt.Sprintf(`

    %s

    +

    Daily Results Summary

    + +

    Totals

    + +

    Next Steps:
    Some events require your attention. Please log into the admin dashboard to review pending issues.

    +

    Best regards,
    The System

    `, + greeting, + strings.Join(partsHTML, "\n"), + totalEvents, + totalBets, + ) + + return headline, plain, html +} + + func (s *Service) SendAdminResultStatusErrorNotification( ctx context.Context, counts domain.ResultLog, @@ -541,6 +660,8 @@ func (s *Service) SendAdminResultStatusErrorNotification( } headline, message := buildHeadlineAndMessage(counts) + + notification := &domain.Notification{ ErrorSeverity: domain.NotificationErrorSeverityHigh, DeliveryStatus: domain.DeliveryStatusPending, @@ -567,9 +688,14 @@ func (s *Service) SendAdminResultStatusErrorNotification( ) sendErrors = append(sendErrors, err) } - notification.DeliveryChannel = domain.DeliveryChannelEmail - if err := s.notificationSvc.SendNotification(ctx, notification); err != nil { - s.mongoLogger.Error("failed to send admin email notification", + // notification.DeliveryChannel = domain.DeliveryChannelEmail + if user.Email == "" { + continue + } + + subject, plain, html := buildHeadlineAndMessageEmail(counts, user) + if err := s.messengerSvc.SendEmail(ctx, user.Email, plain, html, subject); err != nil { + s.mongoLogger.Error("failed to send admin result report email", zap.Int64("admin_id", user.ID), zap.Error(err), ) diff --git a/internal/services/user/common.go b/internal/services/user/common.go index c14403c..40b1814 100644 --- a/internal/services/user/common.go +++ b/internal/services/user/common.go @@ -31,7 +31,7 @@ func (s *Service) SendOtp(ctx context.Context, sentTo string, otpFor domain.OtpF return fmt.Errorf("invalid sms provider: %s", provider) } case domain.OtpMediumEmail: - if err := s.messengerSvc.SendEmail(ctx, sentTo, message, "FortuneBets - One Time Password"); err != nil { + if err := s.messengerSvc.SendEmail(ctx, sentTo, message, message, "FortuneBets - One Time Password"); err != nil { return err } } diff --git a/internal/services/virtualGame/veli/service.go b/internal/services/virtualGame/veli/service.go index d7dc327..9104da4 100644 --- a/internal/services/virtualGame/veli/service.go +++ b/internal/services/virtualGame/veli/service.go @@ -131,15 +131,15 @@ func (s *Service) StartGame(ctx context.Context, req domain.GameStartRequest) (* func (s *Service) StartDemoGame(ctx context.Context, req domain.DemoGameRequest) (*domain.GameStartResponse, error) { // 1. Check if provider is enabled in DB - // provider, err := s.repo.GetVirtualGameProviderByID(ctx, req.ProviderID) - // if err != nil { - // return nil, fmt.Errorf("failed to check provider %s: %w", req.ProviderID, err) - // } + provider, err := s.repo.GetVirtualGameProviderByID(ctx, req.ProviderID) + if err != nil { + return nil, fmt.Errorf("failed to check provider %s: %w", req.ProviderID, err) + } - // if !provider.Enabled { - // // Provider exists but is disabled → return error - // return nil, fmt.Errorf("provider %s is disabled", req.ProviderID) - // } + if !provider.Enabled { + // Provider exists but is disabled → return error + return nil, fmt.Errorf("provider %s is disabled", req.ProviderID) + } // 2. Prepare signature params sigParams := map[string]any{ diff --git a/internal/web_server/cron.go b/internal/web_server/cron.go index 53acbb1..1e0b967 100644 --- a/internal/web_server/cron.go +++ b/internal/web_server/cron.go @@ -79,7 +79,7 @@ func StartDataFetchingCrons(eventService eventsvc.Service, oddsService oddssvc.S }, }, { - spec: "0 0 * * * *", // Every Day + spec: "0 0 0 * * *", // Every Day task: func() { mongoLogger.Info("Began Send daily result notification cron task") if err := resultService.CheckAndSendResultNotifications(context.Background(), time.Now().Add(-24*time.Hour)); err != nil {