252 lines
7.3 KiB
Go
252 lines
7.3 KiB
Go
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
|
|
}
|