Merge pull request 'Refactor notification service to support multiple delivery channels' (#48) from notif-channel into develop

Reviewed-on: https://repo.ravanertebat.com/TM/tm_back/pulls/48
This commit is contained in:
m.nazemi
2026-07-11 21:12:11 +03:30
2 changed files with 53 additions and 10 deletions
+20 -2
View File
@@ -28,8 +28,26 @@ const (
DeliveryChannelInApp DeliveryChannel = "in_app"
)
// EventTypeInApp identifies notifications stored for the in-app notification center.
const EventTypeInApp = "in_app"
// Persisted event types for stored notifications (admin list / notification center).
const (
EventTypeInApp = "in_app"
EventTypeEmail = "email"
EventTypePush = "push"
)
// persistedEventType maps a delivery channel to the event_type stored in MongoDB.
func persistedEventType(channel DeliveryChannel) (string, bool) {
switch channel {
case DeliveryChannelEmail:
return EventTypeEmail, true
case DeliveryChannelPush:
return EventTypePush, true
case DeliveryChannelInApp:
return EventTypeInApp, true
default:
return "", false
}
}
// DeliveryStatus represents the delivery status of notification
type NotificationStatus string
+33 -8
View File
@@ -99,13 +99,27 @@ func (s *notificationService) Send(ctx context.Context, req *NotificationRequest
}
func (s *notificationService) sendToRecipient(ctx context.Context, recipient notificationRecipient, req *NotificationRequest, link, image string) {
if err := s.persistNotification(ctx, recipient, req, link, image); err != nil {
// Always persist an in-app record so every send appears in the notification center.
if err := s.persistNotification(ctx, recipient, req, link, image, DeliveryChannelInApp); err != nil {
s.logger.Error("Failed to persist in-app notification", map[string]interface{}{
"error": err.Error(),
"user_id": recipient.UserID,
})
}
for _, channel := range req.Channels {
if channel == DeliveryChannelInApp {
continue
}
if err := s.persistNotification(ctx, recipient, req, link, image, channel); err != nil {
s.logger.Error("Failed to persist notification", map[string]interface{}{
"error": err.Error(),
"user_id": recipient.UserID,
"channel": channel,
})
}
}
metadata := map[string]any{
"tender": strings.TrimSpace(req.Tender),
}
@@ -149,15 +163,26 @@ func (s *notificationService) sendToRecipient(ctx context.Context, recipient not
}
}
func (s *notificationService) persistNotification(ctx context.Context, recipient notificationRecipient, req *NotificationRequest, link, image string) error {
methods := map[string]string{
"in_app": "true",
func (s *notificationService) persistNotification(ctx context.Context, recipient notificationRecipient, req *NotificationRequest, link, image string, channel DeliveryChannel) error {
eventType, ok := persistedEventType(channel)
if !ok {
return nil
}
if slices.Contains(req.Channels, DeliveryChannelEmail) && recipient.Email != "" {
methods := map[string]string{}
switch channel {
case DeliveryChannelEmail:
if recipient.Email == "" {
return nil
}
methods["email"] = recipient.Email
}
if slices.Contains(req.Channels, DeliveryChannelPush) && len(recipient.DeviceTokens) > 0 {
case DeliveryChannelPush:
if len(recipient.DeviceTokens) == 0 {
return nil
}
methods["push"] = recipient.DeviceTokens[0]
case DeliveryChannelInApp:
methods["in_app"] = "true"
}
status := string(DeliveryStatusSent)
@@ -173,7 +198,7 @@ func (s *notificationService) persistNotification(ctx context.Context, recipient
Image: image,
Priority: string(req.Priority),
Type: string(req.Type),
EventType: EventTypeInApp,
EventType: eventType,
Status: status,
Methods: methods,
Metadata: map[string]any{