f16f9fb5a9
- Added `search` and `q` query parameters to the `SearchForm` for improved notification searching capabilities. - Implemented `ResolvedSearch` method to prioritize search term resolution from the new parameters. - Updated `GetByUserID` repository method to support searching notifications by title and message using regex. - Enhanced logging in the `GetNotifications` service method to include search parameters. - Updated Swagger documentation to reflect the new search parameters for the notification API. This update improves the user experience by allowing more flexible and efficient searching of notifications.
261 lines
7.3 KiB
Go
261 lines
7.3 KiB
Go
package notification
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"regexp"
|
|
"strings"
|
|
"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"
|
|
)
|
|
|
|
var (
|
|
ErrNotificationNotFound = errors.New("notification not found")
|
|
)
|
|
|
|
// Notification represents a stored notification in the database
|
|
type Notification struct {
|
|
orm.Model `bson:",inline"`
|
|
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"`
|
|
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"`
|
|
}
|
|
|
|
// 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
|
|
type Repository interface {
|
|
Create(ctx context.Context, notification *Notification) error
|
|
GetByID(ctx context.Context, id string) (*Notification, error)
|
|
GetByUserID(ctx context.Context, userIDs []string, search, status string, seen *bool, priority, eventType, notificationType string, pagination *response.Pagination) (*orm.PaginatedResult[Notification], 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 with optional user filters
|
|
func (r *notificationRepository) GetByUserID(ctx context.Context, userIDs []string, search, status string, seen *bool, priority, eventType, notificationType string, pagination *response.Pagination) (*orm.PaginatedResult[Notification], error) {
|
|
filter := bson.M{}
|
|
|
|
if q := strings.TrimSpace(search); q != "" {
|
|
pattern := regexp.QuoteMeta(q)
|
|
filter["$or"] = bson.A{
|
|
bson.M{"title": bson.M{"$regex": pattern, "$options": "i"}},
|
|
bson.M{"message": bson.M{"$regex": pattern, "$options": "i"}},
|
|
}
|
|
}
|
|
|
|
if len(userIDs) == 1 {
|
|
filter["user_id"] = userIDs[0]
|
|
} else if len(userIDs) > 1 {
|
|
filter["user_id"] = bson.M{"$in": userIDs}
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
mongoPagination, err := orm.BuildListPagination(
|
|
pagination.Limit,
|
|
pagination.Offset,
|
|
pagination.Cursor,
|
|
pagination.SortBy,
|
|
pagination.SortOrder,
|
|
filter,
|
|
orm.ListPaginationOptions{},
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
result, err := r.ormRepo.FindAll(ctx, filter, mongoPagination)
|
|
if err != nil {
|
|
r.logger.Error("Failed to find notifications", map[string]interface{}{
|
|
"error": err.Error(),
|
|
"user_ids": userIDs,
|
|
})
|
|
return nil, err
|
|
}
|
|
|
|
return result, 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 {
|
|
objectID, err := bson.ObjectIDFromHex(notificationID)
|
|
if err != nil {
|
|
return ErrNotificationNotFound
|
|
}
|
|
|
|
filter := bson.M{
|
|
"_id": objectID,
|
|
"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
|
|
}
|