package store import ( "context" "fmt" "time" "go.mongodb.org/mongo-driver/v2/bson" "go.mongodb.org/mongo-driver/v2/mongo/options" "bountyboard/internal/ulid" ) // Notification is an in-app notification (ยง4.8). type Notification struct { ID string `bson:"_id" json:"id"` UserID string `bson:"userId" json:"userId"` Kind string `bson:"kind" json:"kind"` Title string `bson:"title" json:"title"` Body string `bson:"body" json:"body"` Link string `bson:"link" json:"link"` ReadAt *time.Time `bson:"readAt" json:"readAt"` CreatedAt time.Time `bson:"createdAt" json:"createdAt"` } func (s *Store) InsertNotification(ctx context.Context, n *Notification) error { n.ID = ulid.New() n.CreatedAt = time.Now().UTC() if _, err := s.DB.Collection("notifications").InsertOne(ctx, n); err != nil { return fmt.Errorf("insert notification: %w", err) } return nil } func (s *Store) ListNotifications(ctx context.Context, userID string, unreadOnly bool, cursor string, limit int) ([]Notification, error) { if limit <= 0 || limit > 100 { limit = 30 } q := bson.M{"userId": userID} if unreadOnly { q["readAt"] = nil } if cursor != "" { q["_id"] = bson.M{"$lt": cursor} } cur, err := s.DB.Collection("notifications").Find(ctx, q, options.Find().SetSort(bson.D{{Key: "_id", Value: -1}}).SetLimit(int64(limit))) if err != nil { return nil, fmt.Errorf("list notifications: %w", err) } out := []Notification{} if err := cur.All(ctx, &out); err != nil { return nil, fmt.Errorf("decode notifications: %w", err) } return out, nil } func (s *Store) CountUnreadNotifications(ctx context.Context, userID string) (int64, error) { n, err := s.DB.Collection("notifications").CountDocuments(ctx, bson.M{"userId": userID, "readAt": nil}) if err != nil { return 0, fmt.Errorf("count unread: %w", err) } return n, nil } // MarkNotificationsRead marks the given ids (or all when ids is empty) read. func (s *Store) MarkNotificationsRead(ctx context.Context, userID string, ids []string) (int64, error) { q := bson.M{"userId": userID, "readAt": nil} if len(ids) > 0 { q["_id"] = bson.M{"$in": ids} } res, err := s.DB.Collection("notifications").UpdateMany(ctx, q, bson.M{"$set": bson.M{"readAt": time.Now().UTC()}}) if err != nil { return 0, fmt.Errorf("mark read: %w", err) } return res.ModifiedCount, nil }