Files
nick-doc/02 - Data Models/Notification.md
Siavash Sameni 7a616744f4 docs: complete code-reality alignment for remaining docs + reconcile issue set
Remaining docs updated to match code (the docs that the first pass had not covered):
- Flows: Chat, Referral, Rating, Registration, Google OAuth, Negotiation, Payout,
  Trezor Safekeeping — corrected endpoints, socket events, status enums, auth gaps
- API Reference: User API, Trezor API — admin route prefix/verb/status corrections,
  added undocumented endpoints (ton-proof challenge, profile email verify,
  GET /trezor/account, POST /trezor/verify-operation)
- Data Models: Chat, Notification, Payment, PointTransaction, User — corrected
  enums (PaymentProvider, escrowState, PointTransaction.type, User.status),
  90-day notification TTL, soft-delete semantics, wallet fields

Trezor "zero frontend" finding (audit C31/C32) corrected as STALE:
- Verified current code HAS a full frontend Trezor implementation (admin/trezor
  page, TrezorSettingsView, trezorConnector via @trezor/connect-web,
  TrezorSignDialog, actions/trezor.ts building the {message,signature} object)
- Fixed Trezor Safekeeping Flow doc (removed false "no frontend" warnings)
- Reclassified ISSUE-012 as invalid/superseded with explanation

Issue set reconciled to a single canonical numbering (ISSUE-001..054):
- Adopted the comprehensive 51-issue set (long-slug, fully indexed)
- Removed 35 superseded short-slug duplicates from the first pass
- Removed a duplicate ISSUE-046 file
- Added 3 issues the 51-set lacked: ISSUE-052 (completed-not-counted-in-stats),
  ISSUE-053 (axios 401-only interceptor), ISSUE-054 (rate limiter counts all attempts)
- Regenerated Issues Index: 53 open (14 critical, 39 major) + 1 invalid

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-29 15:15:02 +04:00

5.5 KiB

title, tags, aliases
title tags aliases
Notification
data-model
mongoose
User Notification
INotification

Notification

Last updated: 2026-05-29 — aligned with code (see Doc vs Code Audit Report)

Per-user notification entry. Each row binds to one userId (stored as a string rather than ObjectId), carries a typed severity (info / success / warning / error) and a domain category, optionally references another entity via relatedId, and supports an actionUrl for deep-linking. Old notifications are auto-purged by a 90-day TTL index (createdAt with expireAfterSeconds = 7,776,000).

[!note] Source backend/src/models/Notification.ts:18 — schema definition backend/src/models/Notification.ts:79 — model export

[!warning] String userId userId is a String, not an ObjectId, and is not declared with ref. Consumers must cast to ObjectId if they want to populate() it as a User.

[!warning] category enum vs reality The schema enum is purchase_request / offer / payment / delivery / system, but in practice:

  • notificationController.createNotification defaults the category to 'general' (category = 'general') when the caller omits it. 'general' is not in the schema enum — Mongoose enum validation will reject it on a strict save, so callers must supply a valid value or the write fails. Treat 'general' as a value you may encounter in payloads even though it is not an enum member.
  • The frontend socket hook use-notifications.ts hardcodes category: 'system' for every realtime-injected notification, so most client-side notifications surface as 'system' regardless of their true domain.
  • NotificationService.notifyRequestStatusChanged always writes category: 'system' for purchase-request status changes.

Schema

Field Type Required Default Validation Index Description
userId String yes yes (single + compound) Owner of the notification.
title String yes maxlength 200 Headline.
message String yes maxlength 1000 Body.
type String yes info enum: info / success / warning / error Severity.
category String yes enum: purchase_request / offer / payment / delivery / system yes (compound) Domain bucket. ⚠️ notificationController defaults to 'general' (not in the enum) and the realtime socket hook + notifyRequestStatusChanged hardcode 'system'. See warning above.
relatedId String no yes Id of the related entity (e.g. PurchaseRequest).
metadata Mixed no Arbitrary payload.
actionUrl String no maxlength 500 Deep link.
isRead Boolean no false yes (compound) Read flag.
readAt Date no When read.
createdAt Date auto yes (compound + TTL) Mongoose timestamp. Auto-deleted after 90 days by TTL index.
updatedAt Date auto Mongoose timestamp.

The collection name is overridden to notifications via collection: 'notifications'.

Virtuals

None defined.

Indexes

Defined at backend/src/models/Notification.ts:71-77:

  • { userId: 1, createdAt: -1 } — user feed.
  • { userId: 1, isRead: 1 } — unread badge.
  • { userId: 1, category: 1 } — category filter.
  • { relatedId: 1 } — lookup by linked entity.
  • { createdAt: 1 } with expireAfterSeconds: 60 * 60 * 24 * 90 (7,776,000 s) — MongoDB TTL index; the database hard-deletes documents automatically after 90 days.

Plus the implicit index from userId having index: true at the field level.

Pre/Post Hooks

None declared.

Instance Methods

None defined.

Static Methods

None defined.

Status-change notification coverage

NotificationService.notifyRequestStatusChanged maps a PurchaseRequest status to a human label via an internal statusMessages table. That table covers pending, active, received_offers, in_negotiation, payment, processing, delivery, delivered, confirming, completed, and cancelled.

[!warning] Missing status templates The pending_payment and seller_paid PurchaseRequest statuses have no entry in the statusMessages table and no dedicated notification template. Transitions into these states do not produce a meaningful status-change notification (the label falls back to the raw status string, and several flows skip notification entirely). If you rely on notifications for pending_payment / seller_paid, they will not arrive as expected.

Relationships

  • References: User indirectly through userId (string); arbitrary entity via relatedId.
  • Referenced by: none.

State Transitions

stateDiagram-v2
    [*] --> unread
    unread --> read : user opens
    read --> [*] : TTL purge (90d)
    unread --> [*] : TTL purge (90d)

Common Queries

// User feed
Notification.find({ userId }).sort({ createdAt: -1 }).limit(50);

// Unread badge count
Notification.countDocuments({ userId, isRead: false });

// Mark all read
Notification.updateMany(
  { userId, isRead: false },
  { $set: { isRead: true, readAt: new Date() } }
);

// All notifications about a request
Notification.find({ relatedId: purchaseRequestId.toString() });

Related: User, PurchaseRequest, SellerOffer, Payment.