fix: sending daily result report instead of hourly

This commit is contained in:
Samuel Tariku 2025-09-03 22:26:12 +03:00
parent c77355ad4c
commit 144cb0a42c
6 changed files with 145 additions and 19 deletions

View File

@ -3,10 +3,9 @@ package messenger
import ( import (
"context" "context"
"github.com/resend/resend-go/v2" "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 apiKey := s.config.ResendApiKey
client := resend.NewClient(apiKey) client := resend.NewClient(apiKey)
formattedSenderEmail := "FortuneBets <" + s.config.ResendSenderEmail + ">" formattedSenderEmail := "FortuneBets <" + s.config.ResendSenderEmail + ">"
@ -15,6 +14,7 @@ func (s *Service) SendEmail(ctx context.Context, receiverEmail, message string,
To: []string{receiverEmail}, To: []string{receiverEmail},
Subject: subject, Subject: subject,
Text: message, Text: message,
Html: messageHTML,
} }
_, err := client.Emails.Send(params) _, err := client.Emails.Send(params)

View File

@ -300,7 +300,7 @@ func (s *Service) handleNotification(notification *domain.Notification) {
} }
case domain.DeliveryChannelEmail: 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 { if err != nil {
notification.DeliveryStatus = domain.DeliveryStatusFailed notification.DeliveryStatus = domain.DeliveryStatusFailed
} else { } else {
@ -371,7 +371,7 @@ func (s *Service) SendNotificationEmail(ctx context.Context, recipientID int64,
if user.Email == "" { if user.Email == "" {
return fmt.Errorf("email is invalid") 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 { if err != nil {
s.mongoLogger.Error("[NotificationSvc.HandleNotification] Failed to send notification SMS", s.mongoLogger.Error("[NotificationSvc.HandleNotification] Failed to send notification SMS",
zap.Int64("recipient_id", recipientID), zap.Int64("recipient_id", recipientID),
@ -440,7 +440,7 @@ func (s *Service) retryFailedNotifications() {
return return
} }
case domain.DeliveryChannelEmail: 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 notification.DeliveryStatus = domain.DeliveryStatusSent
if _, err := s.repo.UpdateNotificationStatus(ctx, notification.ID, string(notification.DeliveryStatus), notification.IsRead, notification.Metadata); err != nil { 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", s.mongoLogger.Error("[NotificationSvc.RetryFailedNotifications] Failed to update after retry",

View File

@ -16,6 +16,7 @@ import (
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/bet" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/bet"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/event" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/event"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/league" "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" notificationservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/notification"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/user" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/user"
@ -33,6 +34,7 @@ type Service struct {
eventSvc event.Service eventSvc event.Service
leagueSvc league.Service leagueSvc league.Service
notificationSvc *notificationservice.Service notificationSvc *notificationservice.Service
messengerSvc *messenger.Service
userSvc user.Service userSvc user.Service
} }
@ -46,6 +48,7 @@ func NewService(
eventSvc event.Service, eventSvc event.Service,
leagueSvc league.Service, leagueSvc league.Service,
notificationSvc *notificationservice.Service, notificationSvc *notificationservice.Service,
messengerSvc *messenger.Service,
userSvc user.Service, userSvc user.Service,
) *Service { ) *Service {
return &Service{ return &Service{
@ -59,6 +62,7 @@ func NewService(
eventSvc: eventSvc, eventSvc: eventSvc,
leagueSvc: leagueSvc, leagueSvc: leagueSvc,
notificationSvc: notificationSvc, notificationSvc: notificationSvc,
messengerSvc: messengerSvc,
userSvc: userSvc, userSvc: userSvc,
} }
} }
@ -491,6 +495,7 @@ func (s *Service) CheckAndSendResultNotifications(ctx context.Context, createdAf
} }
func buildHeadlineAndMessage(counts domain.ResultLog) (string, string) { func buildHeadlineAndMessage(counts domain.ResultLog) (string, string) {
totalIssues := counts.StatusNotFinishedCount + counts.StatusToBeFixedCount + counts.StatusPostponedCount + counts.StatusRemovedCount totalIssues := counts.StatusNotFinishedCount + counts.StatusToBeFixedCount + counts.StatusPostponedCount + counts.StatusRemovedCount
totalBets := counts.StatusEndedBets + counts.StatusNotFinishedBets + counts.StatusPostponedBets + counts.StatusRemovedBets + counts.StatusToBeFixedBets totalBets := counts.StatusEndedBets + counts.StatusNotFinishedBets + counts.StatusPostponedBets + counts.StatusRemovedBets + counts.StatusToBeFixedBets
if totalIssues == 0 { if totalIssues == 0 {
@ -517,10 +522,124 @@ func buildHeadlineAndMessage(counts domain.ResultLog) (string, string) {
} }
headline := "⚠️ Issues Found Processing Event Results" 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 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(`<p>%s</p>
<h2>Daily Results Summary</h2>
<ul>
<li><strong>%d Ended Events</strong></li>
<li><strong>%d Total Bets</strong></li>
</ul>
<p>All events were processed successfully, and no issues were detected.</p>
<p>Best regards,<br>The System</p>`,
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("<li><strong>%d Unresolved Events</strong> (%d Bets)</li>", 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("<li><strong>%d Requires Review</strong> (%d Bets)</li>", 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("<li><strong>%d Postponed Events</strong> (%d Bets)</li>", 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("<li><strong>%d Discarded Events</strong> (%d Bets)</li>", 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("<li><strong>%d Successfully Ended Events</strong> (%d Bets)</li>", 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(`<p>%s</p>
<h2>Daily Results Summary</h2>
<ul>
%s
</ul>
<h3>Totals</h3>
<ul>
<li><strong>%d Events Processed</strong></li>
<li><strong>%d Total Bets</strong></li>
</ul>
<p><strong>Next Steps:</strong><br>Some events require your attention. Please <a href="https://your-dashboard-url.example">log into the admin dashboard</a> to review pending issues.</p>
<p>Best regards,<br>The System</p>`,
greeting,
strings.Join(partsHTML, "\n"),
totalEvents,
totalBets,
)
return headline, plain, html
}
func (s *Service) SendAdminResultStatusErrorNotification( func (s *Service) SendAdminResultStatusErrorNotification(
ctx context.Context, ctx context.Context,
counts domain.ResultLog, counts domain.ResultLog,
@ -541,6 +660,8 @@ func (s *Service) SendAdminResultStatusErrorNotification(
} }
headline, message := buildHeadlineAndMessage(counts) headline, message := buildHeadlineAndMessage(counts)
notification := &domain.Notification{ notification := &domain.Notification{
ErrorSeverity: domain.NotificationErrorSeverityHigh, ErrorSeverity: domain.NotificationErrorSeverityHigh,
DeliveryStatus: domain.DeliveryStatusPending, DeliveryStatus: domain.DeliveryStatusPending,
@ -567,9 +688,14 @@ func (s *Service) SendAdminResultStatusErrorNotification(
) )
sendErrors = append(sendErrors, err) sendErrors = append(sendErrors, err)
} }
notification.DeliveryChannel = domain.DeliveryChannelEmail // notification.DeliveryChannel = domain.DeliveryChannelEmail
if err := s.notificationSvc.SendNotification(ctx, notification); err != nil { if user.Email == "" {
s.mongoLogger.Error("failed to send admin email notification", 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.Int64("admin_id", user.ID),
zap.Error(err), zap.Error(err),
) )

View File

@ -31,7 +31,7 @@ func (s *Service) SendOtp(ctx context.Context, sentTo string, otpFor domain.OtpF
return fmt.Errorf("invalid sms provider: %s", provider) return fmt.Errorf("invalid sms provider: %s", provider)
} }
case domain.OtpMediumEmail: 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 return err
} }
} }

View File

@ -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) { func (s *Service) StartDemoGame(ctx context.Context, req domain.DemoGameRequest) (*domain.GameStartResponse, error) {
// 1. Check if provider is enabled in DB // 1. Check if provider is enabled in DB
// provider, err := s.repo.GetVirtualGameProviderByID(ctx, req.ProviderID) provider, err := s.repo.GetVirtualGameProviderByID(ctx, req.ProviderID)
// if err != nil { if err != nil {
// return nil, fmt.Errorf("failed to check provider %s: %w", req.ProviderID, err) return nil, fmt.Errorf("failed to check provider %s: %w", req.ProviderID, err)
// } }
// if !provider.Enabled { if !provider.Enabled {
// // Provider exists but is disabled → return error // Provider exists but is disabled → return error
// return nil, fmt.Errorf("provider %s is disabled", req.ProviderID) return nil, fmt.Errorf("provider %s is disabled", req.ProviderID)
// } }
// 2. Prepare signature params // 2. Prepare signature params
sigParams := map[string]any{ sigParams := map[string]any{

View File

@ -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() { task: func() {
mongoLogger.Info("Began Send daily result notification cron task") mongoLogger.Info("Began Send daily result notification cron task")
if err := resultService.CheckAndSendResultNotifications(context.Background(), time.Now().Add(-24*time.Hour)); err != nil { if err := resultService.CheckAndSendResultNotifications(context.Background(), time.Now().Add(-24*time.Hour)); err != nil {