package notification import ( "context" "errors" "slices" "strings" "time" "tm/internal/customer" "tm/internal/user" "tm/pkg/logger" "tm/pkg/notification" "tm/pkg/response" ) // NotificationService defines the interface for notification business logic type Service interface { Send(ctx context.Context, req *NotificationRequest) error GetNotifications(ctx context.Context, req *SearchForm, pagination *response.Pagination) (*NotificationListResponse, error) GetNotification(ctx context.Context, notificationID string) (*NotificationResponse, error) MarkSeen(ctx context.Context, notificationID, userID string) error AllMarkSeen(ctx context.Context, userID string) error getUsers(ctx context.Context, userIDs []string) ([]notificationRecipient, error) getCustomers(ctx context.Context, values []string, target string) ([]notificationRecipient, error) } // notificationService implements NotificationService interface type notificationService struct { sdk notification.SDK userService user.Service customerService customer.Service logger logger.Logger } // NewService creates a new notification service func NewService( sdk notification.SDK, userService user.Service, customerService customer.Service, logger logger.Logger, ) Service { return ¬ificationService{ sdk: sdk, userService: userService, customerService: customerService, logger: logger, } } // Send sends a multiple notification func (s *notificationService) Send(ctx context.Context, req *NotificationRequest) error { s.logger.Info("Sending notification", map[string]interface{}{ "recipient": req.Recipient, "type": req.Type, "priority": req.Priority, }) if req.Target == TargetAudienceAllUsers || req.Target == TargetAudienceSpecificUsers { recipients, err := s.getUsers(ctx, req.Recipient) if err != nil { return err } for _, recipient := range recipients { if slices.Contains(req.Channels, DeliveryChannelPush) { for _, v := range recipient.DeviceTokens { s.sdk.SendNotification(ctx, ¬ification.NotificationRequest{ UserID: recipient.UserID, Title: req.Title, Message: req.Description, Type: string(req.Type), Priority: string(req.Priority), EventType: notification.EventTypePush, Metadata: map[string]any{ "tender": strings.TrimSpace(req.Tender), }, Methods: notification.NotificationMethods{ Push: v, }, ScheduledAt: req.ScheduleAt, }) } } if slices.Contains(req.Channels, DeliveryChannelEmail) { if recipient.Email != "" { s.sdk.SendNotification(ctx, ¬ification.NotificationRequest{ UserID: recipient.UserID, Title: req.Title, Message: req.Description, Type: string(req.Type), Priority: string(req.Priority), EventType: notification.EventTypeEmail, Metadata: map[string]any{ "tender": strings.TrimSpace(req.Tender), }, Methods: notification.NotificationMethods{ Email: recipient.Email, }, ScheduledAt: req.ScheduleAt, }) } } } } else { recipients, err := s.getCustomers(ctx, req.Recipient, string(req.Target)) if err != nil { return err } for _, recipient := range recipients { if slices.Contains(req.Channels, DeliveryChannelPush) { for _, v := range recipient.DeviceTokens { s.sdk.SendNotification(ctx, ¬ification.NotificationRequest{ UserID: recipient.UserID, Title: req.Title, Message: req.Description, Type: string(req.Type), Priority: string(req.Priority), EventType: notification.EventTypePush, Methods: notification.NotificationMethods{ Push: v, }, ScheduledAt: req.ScheduleAt, Metadata: map[string]any{ "tender": strings.TrimSpace(req.Tender), }, }) } } if slices.Contains(req.Channels, DeliveryChannelEmail) { if recipient.Email != "" { s.sdk.SendNotification(ctx, ¬ification.NotificationRequest{ UserID: recipient.UserID, Title: req.Title, Message: req.Description, Type: string(req.Type), Priority: string(req.Priority), EventType: notification.EventTypeEmail, Methods: notification.NotificationMethods{ Email: recipient.Email, }, ScheduledAt: req.ScheduleAt, Metadata: map[string]any{ "tender": strings.TrimSpace(req.Tender), }, }) } } } s.logger.Info("Notification sent", map[string]interface{}{ "recipient": req.Recipient, "channels": req.Channels, }) return nil } return nil } func (s *notificationService) GetNotifications(ctx context.Context, req *SearchForm, pagination *response.Pagination) (*NotificationListResponse, error) { s.logger.Info("Getting notifications", map[string]interface{}{ "status": req.Status, "method": req.Method, "event_type": req.EventType, "type": req.Type, "recipient": req.Recipient, "seen": req.Seen, "priority": req.Priority, "limit": pagination.Limit, "offset": pagination.Offset, }) notifications, err := s.sdk.GetNotifications(ctx, ¬ification.GetNotificationsRequest{ Status: req.Status, Method: req.Method, EventType: req.EventType, Type: req.Type, UserID: req.Recipient, Seen: req.Seen, Priority: req.Priority, PerPage: pagination.Limit, Page: pagination.Offset / pagination.Limit, }) if err != nil { s.logger.Error("Failed to get notifications", map[string]interface{}{ "error": err.Error(), }) return nil, err } notificationsResponse := make([]*NotificationResponse, 0) for _, notification := range notifications.Data { createdAt, _ := time.Parse(time.RFC3339, notification.CreatedAt) createdAtUnix := createdAt.Unix() updatedAt, _ := time.Parse(time.RFC3339, notification.UpdatedAt) updatedAtUnix := updatedAt.Unix() if createdAtUnix < 0 { createdAtUnix = 0 } if updatedAtUnix < 0 { updatedAtUnix = 0 } usr, _ := s.userService.GetUserByID(ctx, notification.UserID) cust, _ := s.customerService.GetByID(ctx, notification.UserID) var recipient any if usr != nil { recipient = usr } else if cust != nil { recipient = cust } notificationsResponse = append(notificationsResponse, &NotificationResponse{ ID: notification.ID, UserID: notification.UserID, Recipient: recipient, Title: notification.Title, Message: notification.Message, Link: ¬ification.Link, Priority: notification.Priority, Type: notification.Type, EventType: notification.EventType, Status: notification.Status, Methods: notification.Methods, Metadata: notification.Metadata, ScheduleAt: notification.ScheduledAt, IsScheduled: notification.IsScheduled, Image: ¬ification.Image, Seen: notification.Seen, SeenAt: notification.SeenAt, ScheduledAt: notification.ScheduledAt, CreatedAt: createdAtUnix, UpdatedAt: updatedAtUnix, }) } return &NotificationListResponse{ Notifications: notificationsResponse, Meta: &response.Meta{ Total: notifications.Pagination.Total, Limit: notifications.Pagination.PerPage, Offset: pagination.Offset, Page: notifications.Pagination.CurrentPage, Pages: notifications.Pagination.TotalPages, }, }, nil } func (s *notificationService) GetNotification(ctx context.Context, notificationID string) (*NotificationResponse, error) { s.logger.Info("Getting notification", map[string]interface{}{ "notification_id": notificationID, }) notification, err := s.sdk.GetNotification(ctx, notificationID) if err != nil { s.logger.Error("Failed to get notification", map[string]interface{}{ "error": err.Error(), "notification_id": notificationID, }) return nil, err } // Convert to detailed response format var linkPtr *string if notification.Data.Link != "" { linkPtr = ¬ification.Data.Link } var imagePtr *string if notification.Data.Image != "" { imagePtr = ¬ification.Data.Image } usr, _ := s.userService.GetUserByID(ctx, notification.Data.UserID) cust, _ := s.customerService.GetByID(ctx, notification.Data.UserID) var recipient any if usr != nil { recipient = usr } else if cust != nil { recipient = cust } createdAt := notification.Data.CreatedAt.Unix() updatedAt := notification.Data.UpdatedAt.Unix() if createdAt < 0 { createdAt = 0 } if updatedAt < 0 { updatedAt = 0 } detailedResponse := &NotificationResponse{ ID: notification.Data.ID, UserID: notification.Data.UserID, Recipient: recipient, Title: notification.Data.Title, Message: notification.Data.Message, Link: linkPtr, Image: imagePtr, Type: notification.Data.Type, Priority: notification.Data.Priority, EventType: notification.Data.EventType, CreatedAt: createdAt, UpdatedAt: updatedAt, Methods: notification.Data.Methods, Metadata: notification.Data.Metadata, Status: notification.Data.Status, Seen: notification.Data.Seen, SeenAt: notification.Data.SeenAt, ScheduledAt: notification.Data.ScheduledAt, IsScheduled: notification.Data.IsScheduled, } return detailedResponse, nil } func (s *notificationService) MarkSeen(ctx context.Context, notificationID, userID string) error { s.logger.Info("Marking notification as seen", map[string]interface{}{ "notification_id": notificationID, }) notif, err := s.sdk.GetNotification(ctx, notificationID) if err != nil { s.logger.Error("Failed to get notification", map[string]interface{}{ "error": err.Error(), "notification_id": notificationID, }) return err } if notif.Data.UserID != userID { return errors.New("notification not found") } _, err = s.sdk.MarkSeen(ctx, notificationID) if err != nil { return err } return nil } func (s *notificationService) AllMarkSeen(ctx context.Context, userID string) error { s.logger.Info("Marking all notifications as seen", map[string]interface{}{ "user_id": userID, }) _, err := s.sdk.AllMarkSeen(ctx, userID) if err != nil { return err } return nil } func (s *notificationService) getUsers(ctx context.Context, userIDs []string) ([]notificationRecipient, error) { recipients := make([]notificationRecipient, 0) offset := 0 for { var users *user.UserListResponse var err error if len(userIDs) > 0 { users, err = s.userService.GetUsersByIDs(ctx, userIDs) if err != nil { return nil, err } } else { users, err = s.userService.Search(ctx, &user.SearchUsersForm{}, &response.Pagination{Limit: 100, Offset: offset}) if err != nil { return nil, err } offset += 100 if len(users.Users) == 0 { break } } for _, user := range users.Users { recipients = append(recipients, notificationRecipient{ UserID: user.ID, Email: user.Email, DeviceTokens: user.DeviceToken, }) } if len(recipients) == 0 { return nil, errors.New("no users found") } } return recipients, nil } func (s *notificationService) getCustomers(ctx context.Context, values []string, target string) ([]notificationRecipient, error) { recipients := make([]notificationRecipient, 0) if target == string(TargetAudienceSpecificRole) { if len(values) == 0 { return nil, errors.New("no role provided") } customers, err := s.customerService.GetCustomersByRole(ctx, values[0]) if err != nil { return nil, err } for _, customer := range customers { recipients = append(recipients, notificationRecipient{ UserID: customer.ID, Email: customer.Email, DeviceTokens: customer.DeviceToken, }) } } else if target == string(TargetAudienceSpecificCompany) { customers, err := s.customerService.GetCustomersByCompanies(ctx, values) if err != nil { return nil, err } for _, customer := range customers { recipients = append(recipients, notificationRecipient{ UserID: customer.ID, Email: customer.Email, DeviceTokens: customer.DeviceToken, }) } } else if target == string(TargetAudienceSpecificCustomer) { customers, err := s.customerService.GetCustomersByIDs(ctx, values) if err != nil { return nil, err } for _, customer := range customers { recipients = append(recipients, notificationRecipient{ UserID: customer.ID, Email: customer.Email, DeviceTokens: customer.DeviceToken, }) } } else { offset := 0 for { customers, err := s.customerService.SearchNotification(ctx, &customer.SearchCustomersForm{}, &response.Pagination{Limit: 100, Offset: offset}) if err != nil { return nil, err } for _, customer := range customers { recipients = append(recipients, notificationRecipient{ UserID: customer.ID, Email: customer.Email, DeviceTokens: customer.DeviceToken, }) } offset += 100 if len(customers) == 0 { break } } } if len(recipients) == 0 { return nil, errors.New("no customers found") } return recipients, nil }