Contribution Module
Location: apps/api/src/modules/contribution/
Aggregate roots: ContributionEvent, ContributionSnapshot
The Contribution context tracks a holistic activity score per user. It uses an append-only ledger with a materialised snapshot. After each event, it propagates a partial score up the invite tree (50%/25%/10% to direct parent/grandparent/great-grandparent).
Score Deltas
| Kind | Delta | Trigger Event |
|---|---|---|
post_created | +10 | posts.created |
reply_created | +3 | posts.replied |
reaction_given | +1 | posts.reacted |
bookmark_given | +1 | posts.bookmarked |
emoji_pay_sent | +2 | economy.payment.sent |
Prisma Models
| Model | Table | Notes |
|---|---|---|
| ContributionEvent | contribution_events | Append-only; delta ≥ 0 for positive, negative for reversal |
| ContributionSnapshot | contribution_snapshots | Materialised total per user |
ContributionEvent Fields
| Field | Type | Notes |
|---|---|---|
| id | UUID PK | |
| userId | UUID | |
| kind | String (40) | See deltas table |
| delta | Int | Always positive; reversals use negative delta |
| refId | UUID? | Audit reference (postId, replyId, etc.) |
| createdAt | DateTime |
Indexes: (userId, createdAt), createdAt
ContributionSnapshot Fields
| Field | Type | Notes |
|---|---|---|
| userId | UUID PK | |
| total | Int | Rolled-up sum |
| updatedAt | DateTime |
Key Methods
| Method | Description |
|---|---|
record(userId, kind, refId?) | Append event + increment snapshot; fire invite-tree inheritance |
getScore(userId) | Return snapshot total |
getLeaderboard(limit) | Top users by total score |
recomputeSnapshot(userId) | Admin: recompute from ledger (fixes drift) |
Invite-Tree Inheritance
After each record(), the service propagates score up the invite tree using INHERIT_RATIOS = [0.5, 0.25, 0.1]:
| Ancestor Level | Fraction of delta |
|---|---|
| Direct inviter (depth 1) | 50% |
| Inviter's inviter (depth 2) | 25% |
| Depth 3 | 10% |
Example: User creates a post (+10). Their inviter receives +5, their inviter's inviter receives +2.
This means the quality of who you invite into the platform affects your own contribution score.
Nightly Reconciliation
A @Cron(EVERY_DAY_AT_2AM) job recomputes all snapshots from the ledger SUM(delta) grouped by userId. This corrects any incremental drift from race conditions.
HTTP Endpoints
All under /api/v1/contribution/. Require JwtAuthGuard.
| Method | Path | Description |
|---|---|---|
| GET | /contribution/me | My score + rank |
| GET | /contribution/leaderboard | Top contributors ?limit=20 |
| GET | /contribution/users/:id | Another user's score (public) |