in-app notification bug fix
This commit is contained in:
@@ -25,7 +25,7 @@ type SearchForm struct {
|
|||||||
Method string `query:"method" valid:"optional"`
|
Method string `query:"method" valid:"optional"`
|
||||||
EventType string `query:"event_type" valid:"optional"`
|
EventType string `query:"event_type" valid:"optional"`
|
||||||
Type string `query:"type" valid:"optional"`
|
Type string `query:"type" valid:"optional"`
|
||||||
Recipient string `query:"recipient" valid:"optional"`
|
Recipient []string `query:"recipient" json:"recipient" valid:"optional"`
|
||||||
Seen *bool `query:"seen" valid:"optional"`
|
Seen *bool `query:"seen" valid:"optional"`
|
||||||
Priority string `query:"priority" valid:"optional"`
|
Priority string `query:"priority" valid:"optional"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -143,7 +143,7 @@ func (h *NotificationHandler) AdminNotifications(c echo.Context) error {
|
|||||||
return response.ValidationError(c, err.Error(), "")
|
return response.ValidationError(c, err.Error(), "")
|
||||||
}
|
}
|
||||||
|
|
||||||
form.Recipient = userID
|
form.Recipient = []string{userID}
|
||||||
form.Status = "sent"
|
form.Status = "sent"
|
||||||
|
|
||||||
pagination, err := response.NewPagination(c)
|
pagination, err := response.NewPagination(c)
|
||||||
@@ -261,7 +261,7 @@ func (h *NotificationHandler) CustomerNotifications(c echo.Context) error {
|
|||||||
return response.ValidationError(c, err.Error(), "")
|
return response.ValidationError(c, err.Error(), "")
|
||||||
}
|
}
|
||||||
|
|
||||||
form.Recipient = userID
|
form.Recipient = []string{userID}
|
||||||
form.Status = "sent"
|
form.Status = "sent"
|
||||||
|
|
||||||
pagination, err := response.NewPagination(c)
|
pagination, err := response.NewPagination(c)
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ var (
|
|||||||
|
|
||||||
// Notification represents a stored notification in the database
|
// Notification represents a stored notification in the database
|
||||||
type Notification struct {
|
type Notification struct {
|
||||||
ID string `bson:"_id,omitempty" json:"id"`
|
orm.Model `bson:",inline"`
|
||||||
UserID string `bson:"user_id" json:"user_id"`
|
UserID string `bson:"user_id" json:"user_id"`
|
||||||
Title string `bson:"title" json:"title"`
|
Title string `bson:"title" json:"title"`
|
||||||
Message string `bson:"message" json:"message"`
|
Message string `bson:"message" json:"message"`
|
||||||
@@ -32,19 +32,27 @@ type Notification struct {
|
|||||||
Methods map[string]string `bson:"methods,omitempty" json:"methods,omitempty"`
|
Methods map[string]string `bson:"methods,omitempty" json:"methods,omitempty"`
|
||||||
Metadata map[string]any `bson:"metadata,omitempty" json:"metadata,omitempty"`
|
Metadata map[string]any `bson:"metadata,omitempty" json:"metadata,omitempty"`
|
||||||
ScheduleAt int64 `bson:"schedule_at,omitempty" json:"schedule_at,omitempty"`
|
ScheduleAt int64 `bson:"schedule_at,omitempty" json:"schedule_at,omitempty"`
|
||||||
CreatedAt int64 `bson:"created_at" json:"created_at"`
|
|
||||||
UpdatedAt int64 `bson:"updated_at" json:"updated_at"`
|
|
||||||
Seen bool `bson:"seen" json:"seen"`
|
Seen bool `bson:"seen" json:"seen"`
|
||||||
SeenAt int64 `bson:"seen_at,omitempty" json:"seen_at,omitempty"`
|
SeenAt int64 `bson:"seen_at,omitempty" json:"seen_at,omitempty"`
|
||||||
IsScheduled bool `bson:"is_scheduled" json:"is_scheduled"`
|
IsScheduled bool `bson:"is_scheduled" json:"is_scheduled"`
|
||||||
ScheduledAt int64 `bson:"scheduled_at,omitempty" json:"scheduled_at,omitempty"`
|
ScheduledAt int64 `bson:"scheduled_at,omitempty" json:"scheduled_at,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetID sets the notification ID (implements IDSetter interface)
|
||||||
|
func (n *Notification) SetID(id string) {
|
||||||
|
n.Model.SetID(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetID returns the notification ID (implements IDGetter interface)
|
||||||
|
func (n *Notification) GetID() string {
|
||||||
|
return n.Model.GetID()
|
||||||
|
}
|
||||||
|
|
||||||
// Repository defines methods for notification data access
|
// Repository defines methods for notification data access
|
||||||
type Repository interface {
|
type Repository interface {
|
||||||
Create(ctx context.Context, notification *Notification) error
|
Create(ctx context.Context, notification *Notification) error
|
||||||
GetByID(ctx context.Context, id string) (*Notification, error)
|
GetByID(ctx context.Context, id string) (*Notification, error)
|
||||||
GetByUserID(ctx context.Context, userID string, status string, seen *bool, priority, eventType, notificationType string, pagination *response.Pagination) (*orm.PaginatedResult[Notification], error)
|
GetByUserID(ctx context.Context, userIDs []string, status string, seen *bool, priority, eventType, notificationType string, pagination *response.Pagination) (*orm.PaginatedResult[Notification], error)
|
||||||
Update(ctx context.Context, notification *Notification) error
|
Update(ctx context.Context, notification *Notification) error
|
||||||
MarkAsSeen(ctx context.Context, notificationID, userID string) error
|
MarkAsSeen(ctx context.Context, notificationID, userID string) error
|
||||||
MarkAllAsSeen(ctx context.Context, userID string) error
|
MarkAllAsSeen(ctx context.Context, userID string) error
|
||||||
@@ -106,9 +114,15 @@ func (r *notificationRepository) GetByID(ctx context.Context, id string) (*Notif
|
|||||||
return r.ormRepo.FindByID(ctx, id)
|
return r.ormRepo.FindByID(ctx, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetByUserID retrieves notifications for a specific user with filters
|
// GetByUserID retrieves notifications with optional user filters
|
||||||
func (r *notificationRepository) GetByUserID(ctx context.Context, userID string, status string, seen *bool, priority, eventType, notificationType string, pagination *response.Pagination) (*orm.PaginatedResult[Notification], error) {
|
func (r *notificationRepository) GetByUserID(ctx context.Context, userIDs []string, status string, seen *bool, priority, eventType, notificationType string, pagination *response.Pagination) (*orm.PaginatedResult[Notification], error) {
|
||||||
filter := bson.M{"user_id": userID}
|
filter := bson.M{}
|
||||||
|
|
||||||
|
if len(userIDs) == 1 {
|
||||||
|
filter["user_id"] = userIDs[0]
|
||||||
|
} else if len(userIDs) > 1 {
|
||||||
|
filter["user_id"] = bson.M{"$in": userIDs}
|
||||||
|
}
|
||||||
|
|
||||||
if status != "" {
|
if status != "" {
|
||||||
filter["status"] = status
|
filter["status"] = status
|
||||||
@@ -146,8 +160,8 @@ func (r *notificationRepository) GetByUserID(ctx context.Context, userID string,
|
|||||||
result, err := r.ormRepo.FindAll(ctx, filter, mongoPagination)
|
result, err := r.ormRepo.FindAll(ctx, filter, mongoPagination)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
r.logger.Error("Failed to find notifications", map[string]interface{}{
|
r.logger.Error("Failed to find notifications", map[string]interface{}{
|
||||||
"error": err.Error(),
|
"error": err.Error(),
|
||||||
"user_id": userID,
|
"user_ids": userIDs,
|
||||||
})
|
})
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -173,8 +187,13 @@ func (r *notificationRepository) Update(ctx context.Context, notification *Notif
|
|||||||
|
|
||||||
// MarkAsSeen marks a notification as seen
|
// MarkAsSeen marks a notification as seen
|
||||||
func (r *notificationRepository) MarkAsSeen(ctx context.Context, notificationID, userID string) error {
|
func (r *notificationRepository) MarkAsSeen(ctx context.Context, notificationID, userID string) error {
|
||||||
|
objectID, err := bson.ObjectIDFromHex(notificationID)
|
||||||
|
if err != nil {
|
||||||
|
return ErrNotificationNotFound
|
||||||
|
}
|
||||||
|
|
||||||
filter := bson.M{
|
filter := bson.M{
|
||||||
"_id": notificationID,
|
"_id": objectID,
|
||||||
"user_id": userID,
|
"user_id": userID,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -76,50 +76,7 @@ func (s *notificationService) Send(ctx context.Context, req *NotificationRequest
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for _, recipient := range recipients {
|
for _, recipient := range recipients {
|
||||||
if slices.Contains(req.Channels, DeliveryChannelPush) {
|
s.sendToRecipient(ctx, recipient, req, link, image)
|
||||||
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,
|
|
||||||
Link: link,
|
|
||||||
Image: image,
|
|
||||||
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,
|
|
||||||
Link: link,
|
|
||||||
Image: image,
|
|
||||||
Metadata: map[string]any{
|
|
||||||
"tender": strings.TrimSpace(req.Tender),
|
|
||||||
},
|
|
||||||
Methods: notification.NotificationMethods{
|
|
||||||
Email: recipient.Email,
|
|
||||||
},
|
|
||||||
ScheduledAt: req.ScheduleAt,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
recipients, err := s.getCustomers(ctx, req.Recipient, string(req.Target))
|
recipients, err := s.getCustomers(ctx, req.Recipient, string(req.Target))
|
||||||
@@ -127,60 +84,104 @@ func (s *notificationService) Send(ctx context.Context, req *NotificationRequest
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for _, recipient := range recipients {
|
for _, recipient := range recipients {
|
||||||
if slices.Contains(req.Channels, DeliveryChannelPush) {
|
s.sendToRecipient(ctx, recipient, req, link, image)
|
||||||
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,
|
|
||||||
Link: link,
|
|
||||||
Image: image,
|
|
||||||
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,
|
|
||||||
Link: link,
|
|
||||||
Image: image,
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
s.logger.Info("Notification sent", map[string]interface{}{
|
||||||
|
"recipient": req.Recipient,
|
||||||
|
"channels": req.Channels,
|
||||||
|
"target": req.Target,
|
||||||
|
})
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
s.logger.Error("Failed to persist in-app notification", map[string]interface{}{
|
||||||
|
"error": err.Error(),
|
||||||
|
"user_id": recipient.UserID,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
metadata := map[string]any{
|
||||||
|
"tender": strings.TrimSpace(req.Tender),
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
Link: link,
|
||||||
|
Image: image,
|
||||||
|
Metadata: metadata,
|
||||||
|
Methods: notification.NotificationMethods{
|
||||||
|
Push: v,
|
||||||
|
},
|
||||||
|
ScheduledAt: req.ScheduleAt,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if slices.Contains(req.Channels, DeliveryChannelEmail) && 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,
|
||||||
|
Link: link,
|
||||||
|
Image: image,
|
||||||
|
Metadata: metadata,
|
||||||
|
Methods: notification.NotificationMethods{
|
||||||
|
Email: recipient.Email,
|
||||||
|
},
|
||||||
|
ScheduledAt: req.ScheduleAt,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *notificationService) persistNotification(ctx context.Context, recipient notificationRecipient, req *NotificationRequest, link, image string) error {
|
||||||
|
methods := make(map[string]string)
|
||||||
|
if slices.Contains(req.Channels, DeliveryChannelEmail) && recipient.Email != "" {
|
||||||
|
methods["email"] = recipient.Email
|
||||||
|
}
|
||||||
|
if slices.Contains(req.Channels, DeliveryChannelPush) && len(recipient.DeviceTokens) > 0 {
|
||||||
|
methods["push"] = recipient.DeviceTokens[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
status := string(DeliveryStatusSent)
|
||||||
|
if req.ScheduleAt > 0 {
|
||||||
|
status = string(DeliveryStatusPending)
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.repository.Create(ctx, &Notification{
|
||||||
|
UserID: recipient.UserID,
|
||||||
|
Title: req.Title,
|
||||||
|
Message: req.Description,
|
||||||
|
Link: link,
|
||||||
|
Image: image,
|
||||||
|
Priority: string(req.Priority),
|
||||||
|
Type: string(req.Type),
|
||||||
|
Status: status,
|
||||||
|
Methods: methods,
|
||||||
|
Metadata: map[string]any{
|
||||||
|
"tender": strings.TrimSpace(req.Tender),
|
||||||
|
},
|
||||||
|
ScheduleAt: req.ScheduleAt,
|
||||||
|
ScheduledAt: req.ScheduleAt,
|
||||||
|
IsScheduled: req.ScheduleAt > 0,
|
||||||
|
Seen: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func (s *notificationService) GetNotifications(ctx context.Context, req *SearchForm, pagination *response.Pagination) (*NotificationListResponse, error) {
|
func (s *notificationService) GetNotifications(ctx context.Context, req *SearchForm, pagination *response.Pagination) (*NotificationListResponse, error) {
|
||||||
s.logger.Info("Getting notifications", map[string]interface{}{
|
s.logger.Info("Getting notifications", map[string]interface{}{
|
||||||
"status": req.Status,
|
"status": req.Status,
|
||||||
@@ -227,7 +228,7 @@ func (s *notificationService) GetNotifications(ctx context.Context, req *SearchF
|
|||||||
}
|
}
|
||||||
|
|
||||||
notificationsResponse = append(notificationsResponse, &NotificationResponse{
|
notificationsResponse = append(notificationsResponse, &NotificationResponse{
|
||||||
ID: notification.ID,
|
ID: notification.GetID(),
|
||||||
UserID: notification.UserID,
|
UserID: notification.UserID,
|
||||||
Recipient: recipient,
|
Recipient: recipient,
|
||||||
Title: notification.Title,
|
Title: notification.Title,
|
||||||
@@ -292,7 +293,7 @@ func (s *notificationService) GetNotification(ctx context.Context, notificationI
|
|||||||
}
|
}
|
||||||
|
|
||||||
detailedResponse := &NotificationResponse{
|
detailedResponse := &NotificationResponse{
|
||||||
ID: notification.ID,
|
ID: notification.GetID(),
|
||||||
UserID: notification.UserID,
|
UserID: notification.UserID,
|
||||||
Recipient: recipient,
|
Recipient: recipient,
|
||||||
Title: notification.Title,
|
Title: notification.Title,
|
||||||
@@ -378,11 +379,15 @@ func (s *notificationService) getUsers(ctx context.Context, userIDs []string) ([
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(recipients) == 0 {
|
if len(userIDs) > 0 || len(users.Users) == 0 {
|
||||||
return nil, errors.New("no users found")
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(recipients) == 0 {
|
||||||
|
return nil, errors.New("no users found")
|
||||||
|
}
|
||||||
|
|
||||||
return recipients, nil
|
return recipients, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user