Files
tm_back/internal/notification/repository.go
T
Mazyar f16f9fb5a9 Enhance notification search functionality and update API documentation
- 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.
2026-06-11 01:33:47 +03:30

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 &notificationRepository{
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
}