Skip to content

Invite Tree Module

Location: apps/api/src/modules/invite-tree/

Aggregate root: InviteNode

Purpose: Records who invited whom as a materialized tree, so the platform can show lineage ("who you brought in", ancestors/descendants) and let cross-cutting concerns (contribution, reputation) inherit value along the invite chain. Reading-only domain — nodes are written reactively, never by a direct client mutation.


Model

InviteNode stores an ltree-style path (dotted ancestor ids) plus a denormalized depth, so an entire subtree is a single path startsWith "<userId>." scan and a depth-ordered traversal needs no recursion.

FieldMeaning
userIdThe invited user (one node per user)
inviterUserIdDirect inviter (null for the seed/root)
pathDotted ancestor chain, e.g. root.alice.bob
depthDistance from the root
createdAtWhen the node was created (ISO-serialized in responses)

How nodes are created

The module subscribes to identity.user.registered (UserRegisteredEvent) and inserts the new user's node under their inviter — there is no write endpoint. This keeps the tree consistent with registration without a cross-context service call.

Endpoints (read-only, all /invite-tree/me…)

MethodPathPurpose
GET/invite-tree/meThe caller's own node
GET/invite-tree/me/ancestorsChain up to the root
GET/invite-tree/me/childrenDirect invitees (raw nodes)
GET/invite-tree/me/inviteesDirect invitees enriched with profile data
GET/invite-tree/me/descendantsWhole subtree, depth-then-time ordered, enriched

The enriched queries (invitees, descendants, ancestors) batch-load profile data with a single IN-query and ISO-serialize createdAt so the client contract is a string, consistent with every other serializer.

  • Allocation: the number of invite codes a user gets is reputation-based (see Identity), not a flat cap.
  • Inheritance: Contribution walks the tree so an invitee's activity credits their inviter chain.

See also: /modules/identity, /modules/contribution

Regulus — invite-only social-knowledge platform