notification bug fix and refactor
This commit is contained in:
@@ -0,0 +1,251 @@
|
||||
package notification
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"tm/pkg/logger"
|
||||
orm "tm/pkg/mongo"
|
||||
"tm/pkg/response"
|
||||
|
||||
"go.mongodb.org/mongo-driver/v2/bson"
|
||||
mongopkg "go.mongodb.org/mongo-driver/v2/mongo"
|
||||
"go.mongodb.org/mongo-driver/v2/mongo/options"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNotificationNotFound = errors.New("notification not found")
|
||||
)
|
||||
|
||||
// Notification represents a stored notification in the database
|
||||
type Notification struct {
|
||||
ID string `bson:"_id,omitempty" json:"id"`
|
||||
UserID string `bson:"user_id" json:"user_id"`
|
||||
Title string `bson:"title" json:"title"`
|
||||
Message string `bson:"message" json:"message"`
|
||||
Link string `bson:"link,omitempty" json:"link,omitempty"`
|
||||
Image string `bson:"image,omitempty" json:"image,omitempty"`
|
||||
Priority string `bson:"priority" json:"priority"`
|
||||
Type string `bson:"type" json:"type"`
|
||||
EventType string `bson:"event_type,omitempty" json:"event_type,omitempty"`
|
||||
Status string `bson:"status" json:"status"`
|
||||
Methods map[string]string `bson:"methods,omitempty" json:"methods,omitempty"`
|
||||
Metadata map[string]any `bson:"metadata,omitempty" json:"metadata,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"`
|
||||
SeenAt int64 `bson:"seen_at,omitempty" json:"seen_at,omitempty"`
|
||||
IsScheduled bool `bson:"is_scheduled" json:"is_scheduled"`
|
||||
ScheduledAt int64 `bson:"scheduled_at,omitempty" json:"scheduled_at,omitempty"`
|
||||
}
|
||||
|
||||
// Repository defines methods for notification data access
|
||||
type Repository interface {
|
||||
Create(ctx context.Context, notification *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) ([]Notification, int64, error)
|
||||
Update(ctx context.Context, notification *Notification) error
|
||||
MarkAsSeen(ctx context.Context, notificationID, userID string) error
|
||||
MarkAllAsSeen(ctx context.Context, userID string) error
|
||||
}
|
||||
|
||||
type notificationRepository struct {
|
||||
ormRepo orm.Repository[Notification]
|
||||
collection *mongopkg.Collection
|
||||
logger logger.Logger
|
||||
}
|
||||
|
||||
// NewRepository creates a new notification repository
|
||||
func NewRepository(mongoManager *orm.ConnectionManager, logger logger.Logger) Repository {
|
||||
// Create indexes
|
||||
indexes := []orm.Index{
|
||||
*orm.NewIndex("user_id_idx", bson.D{{Key: "user_id", Value: 1}}),
|
||||
*orm.NewIndex("status_idx", bson.D{{Key: "status", Value: 1}}),
|
||||
*orm.NewIndex("seen_idx", bson.D{{Key: "seen", Value: 1}}),
|
||||
*orm.NewIndex("created_at_idx", bson.D{{Key: "created_at", Value: -1}}),
|
||||
}
|
||||
|
||||
err := mongoManager.CreateIndexes("notifications", indexes)
|
||||
if err != nil {
|
||||
logger.Warn("Failed to create notification indexes", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
collection := mongoManager.GetCollection("notifications")
|
||||
ormRepo := orm.NewRepository[Notification](collection, logger)
|
||||
|
||||
return ¬ificationRepository{
|
||||
ormRepo: ormRepo,
|
||||
collection: collection,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// Create creates a new notification
|
||||
func (r *notificationRepository) Create(ctx context.Context, notification *Notification) error {
|
||||
now := time.Now().Unix()
|
||||
notification.CreatedAt = now
|
||||
notification.UpdatedAt = now
|
||||
|
||||
err := r.ormRepo.Create(ctx, notification)
|
||||
if err != nil {
|
||||
r.logger.Error("Failed to create notification", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"user_id": notification.UserID,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetByID retrieves a notification by ID
|
||||
func (r *notificationRepository) GetByID(ctx context.Context, id string) (*Notification, error) {
|
||||
return r.ormRepo.FindByID(ctx, id)
|
||||
}
|
||||
|
||||
// GetByUserID retrieves notifications for a specific user with filters
|
||||
func (r *notificationRepository) GetByUserID(ctx context.Context, userID string, status string, seen *bool, priority, eventType, notificationType string, pagination *response.Pagination) ([]Notification, int64, error) {
|
||||
filter := bson.M{"user_id": userID}
|
||||
|
||||
if status != "" {
|
||||
filter["status"] = status
|
||||
}
|
||||
|
||||
if seen != nil {
|
||||
filter["seen"] = *seen
|
||||
}
|
||||
|
||||
if priority != "" {
|
||||
filter["priority"] = priority
|
||||
}
|
||||
|
||||
if eventType != "" {
|
||||
filter["event_type"] = eventType
|
||||
}
|
||||
|
||||
if notificationType != "" {
|
||||
filter["type"] = notificationType
|
||||
}
|
||||
|
||||
// Get total count
|
||||
total, err := r.collection.CountDocuments(ctx, filter)
|
||||
if err != nil {
|
||||
r.logger.Error("Failed to count notifications", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"user_id": userID,
|
||||
})
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// Build find options
|
||||
findOptions := options.Find()
|
||||
if pagination.Limit > 0 {
|
||||
findOptions.SetLimit(int64(pagination.Limit))
|
||||
}
|
||||
if pagination.Offset > 0 {
|
||||
findOptions.SetSkip(int64(pagination.Offset))
|
||||
}
|
||||
|
||||
// Sort by created_at descending
|
||||
findOptions.SetSort(bson.D{{Key: "created_at", Value: -1}})
|
||||
|
||||
cursor, err := r.collection.Find(ctx, filter, findOptions)
|
||||
if err != nil {
|
||||
r.logger.Error("Failed to find notifications", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"user_id": userID,
|
||||
})
|
||||
return nil, 0, err
|
||||
}
|
||||
defer cursor.Close(ctx)
|
||||
|
||||
var notifications []Notification
|
||||
if err := cursor.All(ctx, ¬ifications); err != nil {
|
||||
r.logger.Error("Failed to decode notifications", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"user_id": userID,
|
||||
})
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return notifications, total, nil
|
||||
}
|
||||
|
||||
// Update updates a notification
|
||||
func (r *notificationRepository) Update(ctx context.Context, notification *Notification) error {
|
||||
notification.UpdatedAt = time.Now().Unix()
|
||||
|
||||
err := r.ormRepo.Update(ctx, notification)
|
||||
if err != nil {
|
||||
r.logger.Error("Failed to update notification", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"user_id": notification.UserID,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarkAsSeen marks a notification as seen
|
||||
func (r *notificationRepository) MarkAsSeen(ctx context.Context, notificationID, userID string) error {
|
||||
filter := bson.M{
|
||||
"_id": notificationID,
|
||||
"user_id": userID,
|
||||
}
|
||||
|
||||
update := bson.M{
|
||||
"$set": bson.M{
|
||||
"seen": true,
|
||||
"seen_at": time.Now().Unix(),
|
||||
"updated_at": time.Now().Unix(),
|
||||
},
|
||||
}
|
||||
|
||||
result, err := r.collection.UpdateOne(ctx, filter, update)
|
||||
if err != nil {
|
||||
r.logger.Error("Failed to mark notification as seen", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"notification_id": notificationID,
|
||||
"user_id": userID,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
if result.MatchedCount == 0 {
|
||||
return ErrNotificationNotFound
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarkAllAsSeen marks all notifications for a user as seen
|
||||
func (r *notificationRepository) MarkAllAsSeen(ctx context.Context, userID string) error {
|
||||
filter := bson.M{
|
||||
"user_id": userID,
|
||||
"seen": false,
|
||||
}
|
||||
|
||||
update := bson.M{
|
||||
"$set": bson.M{
|
||||
"seen": true,
|
||||
"seen_at": time.Now().Unix(),
|
||||
"updated_at": time.Now().Unix(),
|
||||
},
|
||||
}
|
||||
|
||||
_, err := r.collection.UpdateMany(ctx, filter, update)
|
||||
if err != nil {
|
||||
r.logger.Error("Failed to mark all notifications as seen", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"user_id": userID,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"errors"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"tm/internal/customer"
|
||||
"tm/internal/user"
|
||||
@@ -28,6 +27,7 @@ type Service interface {
|
||||
// notificationService implements NotificationService interface
|
||||
type notificationService struct {
|
||||
sdk notification.SDK
|
||||
repository Repository
|
||||
userService user.Service
|
||||
customerService customer.Service
|
||||
logger logger.Logger
|
||||
@@ -36,12 +36,14 @@ type notificationService struct {
|
||||
// NewService creates a new notification service
|
||||
func NewService(
|
||||
sdk notification.SDK,
|
||||
repository Repository,
|
||||
userService user.Service,
|
||||
customerService customer.Service,
|
||||
logger logger.Logger,
|
||||
) Service {
|
||||
return ¬ificationService{
|
||||
sdk: sdk,
|
||||
repository: repository,
|
||||
userService: userService,
|
||||
customerService: customerService,
|
||||
logger: logger,
|
||||
@@ -192,17 +194,7 @@ func (s *notificationService) GetNotifications(ctx context.Context, req *SearchF
|
||||
"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) + 1, // Convert 0-based offset to 1-based page number
|
||||
})
|
||||
notifications, total, err := s.repository.GetByUserID(ctx, req.Recipient, req.Status, req.Seen, req.Priority, req.EventType, req.Type, pagination)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to get notifications", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
@@ -211,18 +203,7 @@ func (s *notificationService) GetNotifications(ctx context.Context, req *SearchF
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
for _, notification := range notifications {
|
||||
usr, _ := s.userService.GetUserByID(ctx, notification.UserID)
|
||||
cust, _ := s.customerService.GetByID(ctx, notification.UserID)
|
||||
|
||||
@@ -258,25 +239,31 @@ func (s *notificationService) GetNotifications(ctx context.Context, req *SearchF
|
||||
Status: notification.Status,
|
||||
Methods: notification.Methods,
|
||||
Metadata: notification.Metadata,
|
||||
ScheduleAt: notification.ScheduledAt,
|
||||
ScheduleAt: notification.ScheduleAt,
|
||||
IsScheduled: notification.IsScheduled,
|
||||
Image: imagePtr,
|
||||
Seen: notification.Seen,
|
||||
SeenAt: notification.SeenAt,
|
||||
ScheduledAt: notification.ScheduledAt,
|
||||
CreatedAt: createdAtUnix,
|
||||
UpdatedAt: updatedAtUnix,
|
||||
CreatedAt: notification.CreatedAt,
|
||||
UpdatedAt: notification.UpdatedAt,
|
||||
})
|
||||
}
|
||||
|
||||
// Calculate total pages
|
||||
totalPages := int(total) / pagination.Limit
|
||||
if int(total)%pagination.Limit > 0 {
|
||||
totalPages++
|
||||
}
|
||||
|
||||
return &NotificationListResponse{
|
||||
Notifications: notificationsResponse,
|
||||
Meta: &response.Meta{
|
||||
Total: notifications.Pagination.Total,
|
||||
Limit: notifications.Pagination.PerPage,
|
||||
Total: int(total),
|
||||
Limit: pagination.Limit,
|
||||
Offset: pagination.Offset,
|
||||
Page: notifications.Pagination.CurrentPage,
|
||||
Pages: notifications.Pagination.TotalPages,
|
||||
Page: (pagination.Offset / pagination.Limit) + 1,
|
||||
Pages: totalPages,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
@@ -286,7 +273,7 @@ func (s *notificationService) GetNotification(ctx context.Context, notificationI
|
||||
"notification_id": notificationID,
|
||||
})
|
||||
|
||||
notification, err := s.sdk.GetNotification(ctx, notificationID)
|
||||
notification, err := s.repository.GetByID(ctx, notificationID)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to get notification", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
@@ -297,17 +284,17 @@ func (s *notificationService) GetNotification(ctx context.Context, notificationI
|
||||
|
||||
// Convert to detailed response format
|
||||
var linkPtr *string
|
||||
if notification.Data.Link != "" {
|
||||
linkPtr = ¬ification.Data.Link
|
||||
if notification.Link != "" {
|
||||
linkPtr = ¬ification.Link
|
||||
}
|
||||
|
||||
var imagePtr *string
|
||||
if notification.Data.Image != "" {
|
||||
imagePtr = ¬ification.Data.Image
|
||||
if notification.Image != "" {
|
||||
imagePtr = ¬ification.Image
|
||||
}
|
||||
|
||||
usr, _ := s.userService.GetUserByID(ctx, notification.Data.UserID)
|
||||
cust, _ := s.customerService.GetByID(ctx, notification.Data.UserID)
|
||||
usr, _ := s.userService.GetUserByID(ctx, notification.UserID)
|
||||
cust, _ := s.customerService.GetByID(ctx, notification.UserID)
|
||||
|
||||
var recipient any
|
||||
if usr != nil {
|
||||
@@ -316,35 +303,26 @@ func (s *notificationService) GetNotification(ctx context.Context, notificationI
|
||||
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,
|
||||
ID: notification.ID,
|
||||
UserID: notification.UserID,
|
||||
Recipient: recipient,
|
||||
Title: notification.Data.Title,
|
||||
Message: notification.Data.Message,
|
||||
Title: notification.Title,
|
||||
Message: notification.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,
|
||||
Type: notification.Type,
|
||||
Priority: notification.Priority,
|
||||
EventType: notification.EventType,
|
||||
CreatedAt: notification.CreatedAt,
|
||||
UpdatedAt: notification.UpdatedAt,
|
||||
Methods: notification.Methods,
|
||||
Metadata: notification.Metadata,
|
||||
Status: notification.Status,
|
||||
Seen: notification.Seen,
|
||||
SeenAt: notification.SeenAt,
|
||||
ScheduledAt: notification.ScheduledAt,
|
||||
IsScheduled: notification.IsScheduled,
|
||||
}
|
||||
|
||||
return detailedResponse, nil
|
||||
@@ -355,23 +333,14 @@ func (s *notificationService) MarkSeen(ctx context.Context, notificationID, user
|
||||
"notification_id": notificationID,
|
||||
})
|
||||
|
||||
notif, err := s.sdk.GetNotification(ctx, notificationID)
|
||||
err := s.repository.MarkAsSeen(ctx, notificationID, userID)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to get notification", map[string]interface{}{
|
||||
s.logger.Error("Failed to mark notification as seen", 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
|
||||
}
|
||||
|
||||
@@ -380,8 +349,12 @@ func (s *notificationService) AllMarkSeen(ctx context.Context, userID string) er
|
||||
"user_id": userID,
|
||||
})
|
||||
|
||||
_, err := s.sdk.AllMarkSeen(ctx, userID)
|
||||
err := s.repository.MarkAllAsSeen(ctx, userID)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to mark all notifications as seen", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"user_id": userID,
|
||||
})
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
||||
Reference in New Issue
Block a user