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>
5.5 KiB
title, tags, aliases
| title | tags | aliases | ||||
|---|---|---|---|---|---|---|
| Notification |
|
|
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 definitionbackend/src/models/Notification.ts:79— model export
[!warning] String userId
userIdis a String, not an ObjectId, and is not declared withref. Consumers must cast toObjectIdif they want topopulate()it as a User.
[!warning]
categoryenum vs reality The schema enum ispurchase_request/offer/payment/delivery/system, but in practice:
notificationController.createNotificationdefaults 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.tshardcodescategory: 'system'for every realtime-injected notification, so most client-side notifications surface as'system'regardless of their true domain.NotificationService.notifyRequestStatusChangedalways writescategory: '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 }withexpireAfterSeconds: 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_paymentandseller_paidPurchaseRequest statuses have no entry in thestatusMessagestable 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 forpending_payment/seller_paid, they will not arrive as expected.
Relationships
- References: User indirectly through
userId(string); arbitrary entity viarelatedId. - 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.