Files
nick-doc/02 - Data Models/Data Model Overview.md
Siavash Sameni d072238fe8 docs: update PG migration status, data models, architecture + add Telegram Mini App flow (v2.8.59)
- Postgres Runtime Cutover Status: 17 migrations (0000–0017), dual-write repo matrix
- Backend Architecture: dual-DB architecture, repo factory, MONGO_CONNECT_MODE modes
- Data Model Overview: 23-model index with PG table names and migration status
- User, PurchaseRequest, SellerOffer, Chat, Dispute: Drizzle schema + cutover status added
- 04 - Flows/Telegram Mini App.md: new doc covering Mini App architecture and flows
- mongo-to-pg-migration-prd.md: status block prepended with 2026-06-03 milestone tracking

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 10:30:51 +04:00

259 lines
23 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
title: Data Model Overview
tags: [data-model, mongoose, postgres, drizzle, overview]
aliases: [Models Index, Schema Overview]
---
# Data Model Overview
This section documents every Mongoose model that backs the marketplace and the parallel Drizzle/Postgres schema that is progressively replacing it. On backend `integrate-main-into-development@cab0719`, Mongoose models are still the live read path for most domains. The Drizzle layer has 17 applied migrations (00000017) and active dual-write repos for the majority of tables.
> [!note] Scope
> Twenty-two models are present in `backend/src/models/`. The "File" concept exists only at the service layer (`backend/src/services/file/`) and is not persisted as its own Mongoose collection, so it is not listed below.
>
> [!note] Documentation freshness
> As of 2026-06-03 the Postgres migration inventory reflects migrations 00000017. The dual-write summary table at the bottom of this page is the authoritative migration-status reference. Individual model pages should be updated to note their PG table name and dual-write repo when they are deepened.
> [!warning] Mongo vs Postgres runtime status
> Dual-write repos exist for the majority of domain tables, but **reads are still served from Mongo** for all dual-write tables. Postgres is the sole store only for infra/bridge tables (`id_map`, `pg_dualwrite_gaps`), oracle quote rows (`payment_quotes`), and `config_setting_history`. Full read cutover is human-gated. See [[Postgres Runtime Cutover Status]].
## Index of Models
### Mongo Models (still live read path)
- [[User]] — Core identity. Stores credentials, profile, preferences, referral data, points, and WebAuthn passkeys. Every other model that records "who did what" points back at a `User._id`. Buyers, sellers, admins, resolvers, and guards all live in this collection, differentiated by a `role` enum. PG table: `users` (dual-write active).
- [[PurchaseRequest]] — The buyer-side document at the heart of the marketplace. Captures what a buyer wants, the budget, urgency, delivery preferences, and the full lifecycle status (`pending_payment``seller_paid`). Aggregates [[SellerOffer]] references and tracks delivery codes. PG table: `purchase_requests` + 6 child tables (dual-write active).
- [[SellerOffer]] — A seller's bid against a [[PurchaseRequest]]. Holds price, delivery ETA, attachments, and a small status machine (`pending` / `accepted` / `rejected` / `withdrawn`). PG table: `seller_offers` (dual-write active).
- [[Payment]] — Records monetary movement intent and state: buyer pay-in, seller release, and refund. The current primary provider path is Request Network plus in-house checkout, derived destinations, funds ledger entries, and Transaction Safety Provider metadata. PG table: `payments` (dual-write active).
- [[Chat]] — Conversation container with embedded messages, participants, unread counters, and reactions. Used for direct buyer-seller chats, group chats, and support tickets. Can be linked to a [[PurchaseRequest]] or [[SellerOffer]]. PG table: `chats` (conservative JSONB shim; Chat normalization is an open blocker).
- [[Notification]] — Per-user notification with category, type, and 90-day TTL for automatic cleanup. References any related entity by stringified id. PG table: `notifications` (dual-write active; `user_id` stored as `text`, no hard FK).
- [[RequestTemplate]] — A seller-authored, sharable template that pre-fills a [[PurchaseRequest]]. Carries a public shareable link, usage counter, and an optional default proposal. PG table: `request_templates` (dual-write active).
- [[Dispute]] — Buyer-raised complaint tied to a [[PurchaseRequest]]. Captures evidence uploads, a timeline of admin actions, deadlines, and a structured resolution. PG table: `disputes` (dual-write active; all IDs as `text` for ObjectId/UUID coexistence).
- [[BlogPost]] — Editorial content: title, slug, rich content, media, SEO metadata, view/like counters, and a draft/published/archived workflow. PG table: `blog_posts` (dual-write active).
- [[Address]] — User shipping address book entry. Enforces a single primary address per user via a pre-save hook. PG table: `addresses` (schema scaffolded, migration 0016; `addressStore.ts` reads PG directly).
- [[Category]] — Hierarchical product/service taxonomy referenced by [[PurchaseRequest]] and [[RequestTemplate]]. Supports parent/child via `parentId` and bilingual `name` / `nameEn`. PG table: `categories` (dual-write active).
- [[Review]] — Polymorphic 1-5 star review against either a seller or a [[RequestTemplate]] (`subjectType` discriminator). One review per reviewer per subject (compound unique index). PG table: `reviews` (schema scaffolded, no dual-write repo yet).
- [[PointTransaction]] — Ledger of point grants and spends per user. Sources include purchase, referral, bonus, admin grant, and redemption. PG table: `point_transactions` (dual-write active).
- [[LevelConfig]] — Static configuration of loyalty tiers (level number, point thresholds, benefits, icon, color). Driven by admins; consumed by the [[User]].points.level field. No PG table (read-only config; not yet migrated).
- [[ShopSettings]] — One-to-one storefront branding for a seller: name, description, avatar, cover image, review toggles, and social links. PG table: `shop_settings` (schema scaffolded, no dual-write repo yet).
- [[TempVerification]] — Short-lived signup record that holds candidate user data and a verification code. Auto-purges via TTL when `emailVerificationCodeExpires` passes. No PG table (TTL-only; not yet migrated).
- [[TelegramLink]] — Permanent auditable association between a Telegram user ID and an Amanat [[User]]. Stores Telegram profile metadata, link source (`miniapp` / `bot` / `login_widget`), status (`active` / `blocked`), and last-seen timestamp. One per Telegram user (unique on both `userId` and `telegramUserId`). PG table: `telegram_links` (schema scaffolded, no dual-write repo yet).
- [[TelegramSession]] — Short-lived Telegram Mini App session token issued when `initData` is verified. Carries the `initDataFingerprint` for replay protection and auto-expires via a MongoDB TTL index on `expiresAt`. PG table: `telegram_sessions` (schema scaffolded, no dual-write repo yet).
- [[ConfigSetting]] — Runtime configuration persisted in MongoDB for operational knobs that need an admin surface rather than a deploy. PG table: `config_settings` (schema scaffolded, no dual-write repo yet).
- [[DerivedDestination]] — Per-payment derived wallet destination records used to reduce address reuse and reconcile on-chain pay-ins. PG table: `derived_destinations` + `derived_destination_sweeps` (dual-write active).
- [[FundsLedgerEntry]] — Immutable accounting ledger rows for pay-in, hold, release, refund, fee, adjustment, and reversal events. PG table: `funds_ledger_entries` (dual-write active; immutability enforced by DB trigger since migration 0015).
- [[TrezorAccount]] — Hardware-wallet/safekeeping account metadata for custody operations and staged signer hardening. PG table: `trezor_accounts` + `trezor_derived_addresses` (dual-write active).
- [[ConfigSettingHistory]] — Immutable audit trail of numeric runtime-config changes. Currently used for per-chain confirmation threshold change events, keyed as `confirmation_threshold:<chainId>`. Added in commit `27fb15a`. PG table: `config_setting_history` (PG-only; no Mongo equivalent).
### PG-Only Tables (no Mongo equivalent)
- `id_map` — ObjectId → UUID bridge. Every migrated table upserts here during backfill/dual-write. Composite PK on `(collection, legacy_object_id)`, unique on `new_id`.
- `pg_dualwrite_gaps` — Append-only reconciliation gap log for failed PG dual-writes. Tracks collection, op, payload, severity, and resolution metadata.
- `payment_quotes` — Oracle pricing quotes per payment (oracle depeg-protection feature). Stores `fx_rate`, `token_price_usd`, `depeg_adjustment_bps`, `settle_amount`, chain/token, and expiry. Requires `ORACLE_QUOTING_ENABLED=true`. 1:1 to `payments`.
- `user_passkeys` — WebAuthn credential store (child of `users`). Columns: credential id (text PK), `user_id FK→users CASCADE`, `public_key`, `counter`, `device_type`, `device_name`.
- `user_refresh_tokens` — Refresh token store (child of `users`). Columns: `token text PK`, `user_id FK→users CASCADE`.
## Relationship Diagram
```mermaid
erDiagram
USER ||--o{ PURCHASE_REQUEST : "creates as buyer"
USER ||--o{ SELLER_OFFER : "submits as seller"
USER ||--o{ ADDRESS : "owns"
USER ||--o{ NOTIFICATION : "receives"
USER ||--o{ POINT_TRANSACTION : "earns/spends"
USER ||--o{ REQUEST_TEMPLATE : "authors as seller"
USER ||--o| SHOP_SETTINGS : "configures"
USER ||--o{ BLOG_POST : "publishes"
USER ||--o{ REVIEW : "writes as reviewer"
USER ||--o{ DISPUTE : "raises as buyer"
USER ||--o{ USER : "referred by"
USER ||--o{ TREZOR_ACCOUNT : "controls custody account"
USER ||--o{ USER_PASSKEY : "authenticates with"
USER ||--o{ USER_REFRESH_TOKEN : "sessions via"
USER ||--o| TELEGRAM_LINK : "links identity"
USER ||--o{ TELEGRAM_SESSION : "session for"
PURCHASE_REQUEST }o--|| CATEGORY : "belongs to"
PURCHASE_REQUEST ||--o{ SELLER_OFFER : "receives"
PURCHASE_REQUEST ||--o{ PAYMENT : "settled by"
PURCHASE_REQUEST ||--o| CHAT : "discussed in"
PURCHASE_REQUEST ||--o{ DISPUTE : "may trigger"
PURCHASE_REQUEST ||--o| REVIEW : "rated by buyer"
SELLER_OFFER ||--o| PAYMENT : "funds"
SELLER_OFFER }o--|| PURCHASE_REQUEST : "responds to"
PAYMENT }o--|| USER : "buyer"
PAYMENT }o--|| USER : "seller"
PAYMENT ||--o{ FUNDS_LEDGER_ENTRY : "accounted by"
PAYMENT ||--o| DERIVED_DESTINATION : "collects into"
PAYMENT ||--o| PAYMENT_QUOTE : "oracle-priced by"
CHAT }o--o{ USER : "participants"
CHAT ||--o{ DISPUTE : "support channel"
REQUEST_TEMPLATE }o--|| CATEGORY : "belongs to"
REQUEST_TEMPLATE ||--o{ REVIEW : "rated as subject"
CATEGORY ||--o{ CATEGORY : "parent of"
POINT_TRANSACTION }o--|| USER : "owner"
LEVEL_CONFIG ||..|| USER : "level lookup"
TEMP_VERIFICATION ||..|| USER : "promoted to"
TELEGRAM_LINK }o--|| USER : "links identity"
TELEGRAM_SESSION }o--o| USER : "session for"
TELEGRAM_SESSION }o--|| TELEGRAM_LINK : "matches"
TREZOR_ACCOUNT ||--o{ TREZOR_DERIVED_ADDRESS : "issues"
DERIVED_DESTINATION ||--o{ DERIVED_DESTINATION_SWEEP : "swept by"
ID_MAP ||..|| USER : "bridges ObjectId"
```
## Conventions Across All Models
### Mongoose Conventions
> [!note] Shared schema patterns
> - **Timestamps**: every model declares `{ timestamps: true }`, so `createdAt` and `updatedAt` are always present.
> - **ObjectId references**: foreign keys use `Schema.Types.ObjectId` with an explicit `ref` (e.g. `ref: 'User'`). The two exceptions are [[Notification]] and [[Payment]] which use string-typed or `Mixed` identifiers in places to support template-flow payments.
> - **Soft delete**: deletion is modelled as a `status` flag (e.g. `User.status = 'deleted'`, `BlogPost.status = 'archived'`) rather than physical removal.
> - **TTL indexes**: short-lived collections ([[Notification]], [[TempVerification]]) use `{ expireAfterSeconds: ... }` so MongoDB does the cleanup.
> - **toJSON sanitisation**: [[User]] overrides `toJSON` to strip credentials, refresh tokens, and verification codes before serialisation.
> [!warning] Index discipline
> Several schemas leave a comment noting that `unique: true` already creates an index — adding `schema.index({ field: 1 })` on top would produce a duplicate-index warning at startup. When introducing new indexes, search for `unique: true` first.
### Drizzle/Postgres Conventions
> [!note] PG schema patterns
> - **Legacy bridge**: every migrated table carries `legacy_object_id text` with a partial-unique index `WHERE legacy_object_id IS NOT NULL` for idempotent backfill upserts. The `id_map` table records the ObjectId → UUID mapping centrally.
> - **Money columns**: `numeric(38,18)` for fiat/crypto amounts throughout, except `seller_offers` which uses `numeric(18,8)` per the Migration Guide. Blockchain balance columns use `numeric(78,0)` to hold uint256 without overflow.
> - **Polymorphic triples**: the `ref_kind` enum (`entity` | `template`) discriminator is expanded into three columns (`_ref_kind`, `_id`, `_external_ref`) with a CHECK constraint to enforce discriminator integrity. Used by `payments`, `funds_ledger_entries`, and `derived_destinations`.
> - **Soft delete**: `addresses` uses `deleted_at timestamptz` (nullable) with partial-unique indexes scoped to `WHERE deleted_at IS NULL`. Most other tables retain the Mongo `status` flag approach.
> - **Timestamps**: all timestamp columns declare `withTimezone: true`.
> - **Immutability**: `funds_ledger_entries` has both an UPDATE-blocking and a DELETE-blocking trigger installed at the DB level (migrations 0004, 0015). A TRUNCATE trigger was added in migration 0013.
> - **user_role enum**: values are `admin`, `buyer`, `seller`, `resolver`, `guard`. The `guard` value was added in migration 0017.
## Postgres Migration Inventory
Schema entry point: `backend/src/db/schema/index.ts`
| Migration | File | Summary |
|---|---|---|
| 0000 | `0000_slimy_veda.sql` | Initial: core enums + `id_map` + `categories` |
| 0001 | `0001_wild_cargill.sql` | `trezor_accounts` + `trezor_derived_addresses` (later reset) |
| 0002 | `0002_motionless_grey_gargoyle.sql` | Schema reset: drops 0000/0001 tables to be rebuilt in 0003; adds `categories.parent_id` self-FK |
| 0003 | `0003_remarkable_retro_girl.sql` | Comprehensive rebuild: all enums + full core domain (`users`, `payments`, `funds_ledger_entries`, `derived_destinations`, `purchase_requests` + 6 children, `seller_offers`, `point_transactions`, `trezor_*`) |
| 0004a | `0004_funds_ledger_entries.sql` | UPDATE-blocking immutability trigger on `funds_ledger_entries` |
| 0004b | `0004_seller_offer.sql` | Physical FKs on `seller_offers``users` and `purchase_requests` (CASCADE) |
| 0005 | `0005_simple_champions.sql` | `pg_dualwrite_gaps`; FKs on `payments`; `legacy_object_id` unique indexes; refined pending-RN payment unique index |
| 0006 | `0006_normal_madame_hydra.sql` | CHECK: `purchase_requests.budget_currency` restricted to crypto (USDT, USDC) |
| 0007 | `0007_woozy_shaman.sql` | Drops 0006 constraint; sets `budget_currency` default to `'USDT'` |
| 0008 | `0008_giant_winter_soldier.sql` | Adds `'TRY'` to `offer_currency` enum; creates `payment_quotes` table |
| 0009 | `0009_unique_active_categories.sql` | Category deduplication; partial unique index on normalized active category name |
| 0010 | `0010_request_templates.sql` | Creates `request_templates`; deduplicates `purchase_request_specifications`; adds unique key constraint |
| 0011 | `0011_chats.sql` | Creates `chats` with JSONB participant/message storage + chat-related enums |
| 0012 | `0012_disputes.sql` | Creates `disputes` (text IDs, JSONB evidence/timeline/resolution) |
| 0013 | `0013_money_constraints.sql` | Money-integrity CHECKs on `payments`, `payment_quotes`, `point_transactions`, `users`; TRUNCATE trigger on `funds_ledger_entries`; composite PK + unique on `id_map` |
| 0014 | `0014_physical_fks.sql` | NOT VALID FKs across all major tables (validated immediately); composite indexes on `payments`, `purchase_requests`, `seller_offers` |
| 0015 | `0015_funds_ledger_immutable_trigger.sql` | Replaces/extends ledger triggers: UPDATE-block + new DELETE-block on `funds_ledger_entries` |
| 0016 | `0016_addresses_table.sql` | `address_type` enum + `addresses` table; partial-unique primary-address-per-user index |
| 0017 | `0017_user_role_guard.sql` | Adds `'guard'` to `user_role` enum (idempotent `ADD VALUE IF NOT EXISTS`) |
## Drizzle Table Inventory and Migration Status
### Infrastructure / Bridge
| PG Table | Schema File | Status | Notes |
|---|---|---|---|
| `id_map` | `idMap.ts` | PG-only | ObjectId → UUID bridge; composite PK + unique on `new_id` |
| `pg_dualwrite_gaps` | `pgDualwriteGaps.ts` | PG-only | Append-only reconciliation gap log for failed dual-writes |
### Core Domain
| PG Table | Schema File | Status | Dual-Write Repo |
|---|---|---|---|
| `users` | `users.ts` | Dual-write active | `DualWriteUserRepo` + `DrizzleUserRepo` + `MongoUserRepo` |
| `user_passkeys` | `users.ts` | Dual-write active (child of users) | — |
| `user_refresh_tokens` | `users.ts` | Dual-write active (child of users) | — |
| `categories` | `category.ts` | Dual-write active | `DualWriteMarketplaceRepo` |
| `purchase_requests` | `purchaseRequest.ts` | Dual-write active | `DualWriteMarketplaceRepo` |
| `purchase_request_delivery_info` | `purchaseRequest.ts` | Dual-write active (1:1 child) | — |
| `purchase_request_delivery_address` | `purchaseRequest.ts` | Dual-write active (1:1 child) | — |
| `purchase_request_seller_delivery_info` | `purchaseRequest.ts` | Dual-write active (1:1 child) | — |
| `delivery_attempts` | `purchaseRequest.ts` | Dual-write active (1:N child) | — |
| `purchase_request_service_info` | `purchaseRequest.ts` | Dual-write active (1:1 child) | — |
| `purchase_request_specifications` | `purchaseRequest.ts` | Dual-write active (1:N child) | — |
| `purchase_request_preferred_sellers` | `purchaseRequest.ts` | Dual-write active (N:M junction) | — |
| `seller_offers` | `sellerOffer.ts` | Dual-write active | `DualWriteMarketplaceRepo` |
| `payments` | `payment.ts` | Dual-write active | `DualWritePaymentRepo` + `DrizzlePaymentRepo` + `MongoPaymentRepo` |
| `payment_quotes` | `paymentQuote.ts` | PG-only | No Mongo equivalent; oracle depeg-protection feature |
| `funds_ledger_entries` | `fundsLedgerEntry.ts` | Dual-write active | `DrizzlePaymentRepo` / `DualWritePaymentRepo` |
| `derived_destinations` | `derivedDestination.ts` | Dual-write active | `DualWriteDerivedDestinationRepo` + `DrizzleDerivedDestinationRepo` |
| `derived_destination_sweeps` | `derivedDestination.ts` | Dual-write active (append-only child) | — |
| `trezor_accounts` | `trezorAccount.ts` | Dual-write active | `DualWriteTrezorAccountRepo` + `DrizzleTrezorAccountRepo` |
| `trezor_derived_addresses` | `trezorAccount.ts` | Dual-write active (child of trezor_accounts) | — |
| `point_transactions` | `pointTransaction.ts` | Dual-write active | `DualWritePointsRepo` + `DrizzlePointsRepo` |
| `request_templates` | `requestTemplate.ts` | Dual-write active | `DualWriteMarketplaceRepo` |
| `chats` | `chat.ts` | Dual-write active | `DrizzleChatRepo` |
| `blog_posts` | `blogPost.ts` | Dual-write active | `DualWriteBlogRepo` + `DrizzleBlogRepo` |
| `notifications` | `notification.ts` | Dual-write active | `DualWriteNotificationRepo` + `DrizzleNotificationRepo` |
| `disputes` | `dispute.ts` | Dual-write active | `DualWriteDisputeRepo` + `DrizzleDisputeRepo` |
| `addresses` | `address.ts` | Schema scaffolded | No dual-write repo; `addressStore.ts` reads PG directly (migration 0016) |
| `shop_settings` | `shopSettings.ts` | Schema scaffolded | No dual-write repo |
| `config_settings` | `configSetting.ts` | Schema scaffolded | No dual-write repo |
| `config_setting_history` | `configSetting.ts` | PG-only | No Mongo equivalent; child of `config_settings` |
| `telegram_links` | `telegramLink.ts` | Schema scaffolded | No dual-write repo |
| `telegram_sessions` | `telegramSession.ts` | Schema scaffolded | No dual-write repo |
| `reviews` | `review.ts` | Schema scaffolded | No dual-write repo |
> [!note] Read cutover status
> **Dual-write active** means writes go to both Mongo and PG; reads still come from Mongo (per MEMORY.md as of 2026-06-03). **Schema scaffolded** means the Drizzle table exists but no DualWriteRepo plumbs it. **PG-only** means there is no Mongo model for that data.
## Shared Enum Reference
Enums live in `backend/src/db/schema/_enums.ts` (shared) and individual schema files. Key enums:
| Enum | Values |
|---|---|
| `user_role` | admin, buyer, seller, resolver, guard |
| `auth_provider` | email, google, telegram |
| `user_status` | active, suspended, deleted |
| `purchase_request_status` | pending_payment, pending, received_offers, in_negotiation, payment_pending, payment_confirmed, in_progress, delivery, delivered, completed, disputed, refunded, seller_paid |
| `offer_status` | pending, accepted, rejected, withdrawn, active |
| `offer_currency` | USD, EUR, IRR, USDT, USDC, TRY |
| `payment_provider` | request.network, amn.scanner, shkeeper, other |
| `payment_status` | pending, processing, confirmed, completed, failed, cancelled, refunded |
| `escrow_state` | funded, releasable, released, refunded, releasing, failed, cancelled, partial |
| `funds_ledger_entry_type` | payment_detected, provider_fee, platform_fee, hold, release, refund, dispute_hold, adjustment |
| `derived_destination_status` | active, swept, sweeping, quarantined |
| `ref_kind` | entity, template |
| `chat_type` | direct, group, support |
| `review_subject_kind` | seller, template |
| `address_type` | Home, Office, Other |
| `telegram_link_source` | miniapp, bot, login_widget |
| `telegram_link_status` | active, blocked |
## Lifecycle View
The dominant happy-path flow exercises five collections in order:
1. A buyer (`User`) creates a `PurchaseRequest` with `status: 'pending'`.
2. Sellers (other `User`s) attach `SellerOffer` documents; the request transitions through `received_offers``in_negotiation` as the parties chat in a `Chat`.
3. The buyer accepts an offer; a `Payment` is opened against the Request Network provider and, once verified by webhook/reconciliation and safety checks, advances to a funded escrow state. If `ORACLE_QUOTING_ENABLED=true`, a `payment_quote` row is written to PG at this point.
4. The seller marks the request `delivery``delivered`; the buyer confirms with the 6-digit `deliveryCode` and the request becomes `completed`.
5. The escrow `Payment` flips to `released` after a ledger-gated custody transfer instruction. Each ledger event appends an immutable `FundsLedgerEntry` row (Mongo + PG). Optionally the buyer writes a `Review` and earns a `PointTransaction`.
If anything goes sideways, the buyer can open a `Dispute`, which freezes release until an admin resolves it (refund, replacement, compensation, or no-action).
## How to Navigate
Each model has its own note in this folder. Cross-references use `[[wikilinks]]` so backlinks work in Obsidian's graph view. Schemas are documented at field-level granularity — every field is listed with its type, default, validation, and indexing decisions. Where a model carries a meaningful state machine, a Mermaid `stateDiagram-v2` accompanies the schema table.
> [!note] Source of truth
> The information below is mirrored from the TypeScript schema definitions. If a field listed here disagrees with the code, the code wins — please update the note. All source citations use the form `backend/src/models/<File>.ts:<line>` for Mongo and `backend/src/db/schema/<File>.ts:<line>` for Drizzle/PG.