Yimaru-BackEnd/internal/services/result/notification.go

293 lines
9.6 KiB
Go

package result
import (
"context"
"encoding/json"
"fmt"
"strings"
"time"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"go.uber.org/zap"
)
func (s *Service) CheckAndSendResultNotifications(ctx context.Context, createdAfter time.Time) error {
resultLog, err := s.resultLogStore.GetAllResultLog(ctx, domain.ResultLogFilter{
CreatedAfter: domain.ValidTime{
Value: createdAfter,
Valid: true,
},
})
if err != nil {
s.mongoLogger.Error(
"Failed to get result log",
zap.Time("CreatedAfter", createdAfter),
zap.Error(err),
)
return err
}
if len(resultLog) == 0 {
s.mongoLogger.Info(
"No results found for check and send result notification",
zap.Time("CreatedAfter", createdAfter),
)
return nil
}
totalResultLog := domain.ResultLog{
StatusNotFinishedCount: resultLog[0].StatusNotFinishedCount,
StatusPostponedCount: resultLog[0].StatusPostponedCount,
}
for _, log := range resultLog {
// Add all the bets
totalResultLog.StatusNotFinishedBets += log.StatusNotFinishedBets
totalResultLog.StatusPostponedBets += log.StatusPostponedBets
totalResultLog.StatusToBeFixedBets += log.StatusToBeFixedBets
totalResultLog.StatusRemovedBets += log.StatusRemovedBets
totalResultLog.StatusEndedBets += log.StatusEndedBets
totalResultLog.StatusToBeFixedCount += log.StatusToBeFixedCount
totalResultLog.StatusRemovedCount += log.StatusRemovedCount
totalResultLog.StatusEndedCount += log.StatusEndedCount
totalResultLog.RemovedCount += log.RemovedCount
}
err = s.SendAdminResultStatusErrorNotification(ctx, totalResultLog, createdAfter, time.Now())
if err != nil {
s.mongoLogger.Error(
"Failed to send admin result status notification",
zap.Time("CreatedAfter", createdAfter),
zap.Error(err),
)
return err
}
return nil
}
func buildHeadlineAndMessage(counts domain.ResultLog, createdAfter time.Time, endTime time.Time) (string, string) {
period := fmt.Sprintf("%s - %s", createdAfter.Format("02 Jan 2006"), endTime.Format("02 Jan 2006"))
totalIssues := counts.StatusNotFinishedCount + counts.StatusToBeFixedCount + counts.StatusPostponedCount + counts.StatusRemovedCount
totalBets := counts.StatusEndedBets + counts.StatusNotFinishedBets + counts.StatusPostponedBets + counts.StatusRemovedBets + counts.StatusToBeFixedBets
if totalIssues == 0 {
return "✅ Successfully Processed Event Results", fmt.Sprintf(
"%d total ended events with %d total bets. No issues detected", counts.StatusEndedCount, totalBets,
)
}
parts := []string{}
if counts.StatusNotFinishedCount > 0 {
parts = append(parts, fmt.Sprintf("%d unfinished with %d bets", counts.StatusNotFinishedCount, counts.StatusNotFinishedBets))
}
if counts.StatusToBeFixedCount > 0 {
parts = append(parts, fmt.Sprintf("%d to-fix with %d bets", counts.StatusToBeFixedCount, counts.StatusToBeFixedBets))
}
if counts.StatusPostponedCount > 0 {
parts = append(parts, fmt.Sprintf("%d postponed with %d bets", counts.StatusPostponedCount, counts.StatusPostponedBets))
}
if counts.StatusRemovedCount > 0 {
parts = append(parts, fmt.Sprintf("%d removed with %d bets", counts.StatusRemovedCount, counts.StatusRemovedBets))
}
if counts.StatusEndedCount > 0 {
parts = append(parts, fmt.Sprintf("%d ended with %d bets", counts.StatusEndedCount, counts.StatusEndedBets))
}
headline := "⚠️ Issues Found Processing Event Results"
message := fmt.Sprintf("Processed expired event results (%s): %s. Please review pending entries.",
period, strings.Join(parts, ", "))
return headline, message
}
func buildHeadlineAndMessageEmail(counts domain.ResultLog, user domain.User, createdAfter time.Time, endTime time.Time) (string, string, string) {
period := fmt.Sprintf("%s - %s", createdAfter.Format("02 Jan 2006"), endTime.Format("02 Jan 2006"))
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 := "✅ Weekly Results Report — All Events Processed Successfully"
plain := fmt.Sprintf(`%s
Weekly Results Summary (%s):
- %d Ended Events
- %d Total Bets
All events were processed successfully, and no issues were detected.
Best regards,
The System`, greeting, period, counts.StatusEndedCount, totalBets)
html := fmt.Sprintf(`<p>%s</p>
<h2>Weekly Results Summary</h2>
<p><em>Period: %s</em></p>
<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, period, counts.StatusEndedCount, totalBets)
return headline, plain, html
}
partsPlain := []string{}
partsHTML := []string{}
if counts.StatusNotFinishedCount > 0 {
partsPlain = append(partsPlain,
fmt.Sprintf("- %d Incomplete Events (%d Bets)", counts.StatusNotFinishedCount, counts.StatusNotFinishedBets))
partsHTML = append(partsHTML,
fmt.Sprintf("<li><strong>%d Incomplete 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 := "⚠️ Weekly Results Report — Review Required"
plain := fmt.Sprintf(`%s
Weekly Results Summary (%s):
%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,
period,
strings.Join(partsPlain, "\n"),
totalEvents,
totalBets,
)
html := fmt.Sprintf(`<p>%s</p>
<h2>Weekly Results Summary</h2>
<p><em>Period: %s</em></p>
<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://admin.fortunebets.net">log into the admin dashboard</a> to review pending issues.</p>
<p>Best regards,<br>The System</p>`,
greeting,
period,
strings.Join(partsHTML, "\n"),
totalEvents,
totalBets,
)
return headline, plain, html
}
func (s *Service) SendAdminResultStatusErrorNotification(
ctx context.Context,
counts domain.ResultLog,
createdAfter time.Time,
endTime time.Time,
) error {
superAdmins, _, err := s.userSvc.GetAllUsers(ctx, domain.UserFilter{
Role: string(domain.RoleSuperAdmin),
})
if err != nil {
s.mongoLogger.Error("failed to get super_admin recipients", zap.Error(err))
return err
}
metaBytes, err := json.Marshal(counts)
if err != nil {
s.mongoLogger.Error("failed to marshal metadata", zap.Error(err))
return err
}
headline, message := buildHeadlineAndMessage(counts, createdAfter, endTime)
notification := &domain.Notification{
ErrorSeverity: domain.NotificationErrorSeverityHigh,
DeliveryStatus: domain.DeliveryStatusPending,
IsRead: false,
Type: domain.NOTIFICATION_TYPE_BET_RESULT,
Level: domain.NotificationLevelWarning,
Reciever: domain.NotificationRecieverSideAdmin,
DeliveryChannel: domain.DeliveryChannelInApp,
Payload: domain.NotificationPayload{
Headline: headline,
Message: message,
},
Priority: 2,
Metadata: metaBytes,
}
var sendErrors []error
for _, user := range superAdmins {
notification.RecipientID = user.ID
if err := s.notificationSvc.SendNotification(ctx, notification); err != nil {
s.mongoLogger.Error("failed to send admin notification",
zap.Int64("admin_id", user.ID),
zap.Error(err),
)
sendErrors = append(sendErrors, err)
}
// notification.DeliveryChannel = domain.DeliveryChannelEmail
if user.Email == "" {
continue
}
subject, plain, html := buildHeadlineAndMessageEmail(counts, user, createdAfter, endTime)
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),
)
sendErrors = append(sendErrors, err)
}
}
if len(sendErrors) > 0 {
return fmt.Errorf("sent with partial failure: %d errors", len(sendErrors))
}
return nil
}