core PK: id 8 required 1 unique

Description

Join table linking users to organizations, carrying the role the user holds within that organization, membership status (including paused state), and ordering metadata for profile switching. A user may belong to up to five organizations simultaneously; one membership is designated primary to drive the default context after login.

18
Attributes
7
Indexes
8
Validation Rules
20
CRUD Operations

Data Structure

Name Type Description Constraints
id uuid Surrogate primary key
PKrequiredunique
user_id uuid Foreign key to users.id — the member
required
organization_id uuid Foreign key to organizations.id — the tenant
required
role enum The user's functional role within this specific organization. Determines access scope on both mobile app and admin portal.
required
status enum Lifecycle state of this membership. 'paused' means the peer mentor is temporarily inactive; 'invited' means onboarding not yet completed.
required
is_primary boolean Marks the organization that serves as the user's default context after login and token issuance. Only one membership per user may be primary at a time.
required
display_order integer Explicit sort position within the profile-switcher UI. Lower values appear first. Defaults to insertion order.
-
invited_by_user_id uuid User ID of the admin or coordinator who issued the invitation. Nullable for system-created or migrated memberships.
-
invited_at datetime Timestamp when the invitation was dispatched. Used for invitation expiry checks.
-
activated_at datetime Timestamp when the member accepted the invitation and the membership became active.
-
paused_at datetime Timestamp when the peer mentor placed the membership in the paused state. Null when status is not paused.
-
paused_until datetime Optional scheduled end of the paused state. When the current time exceeds this value the status auto-transitions back to active via a background job.
-
deactivated_at datetime Timestamp when the membership was deactivated by an admin or coordinator. Retained for audit history.
-
deactivated_by_user_id uuid User ID of the admin who performed the deactivation. Nullable.
-
external_member_id string Identifier for this membership in the organization's external membership system (e.g. HLF portal, NHF registry). Used for sync and deduplication.
-
metadata json Extensible bag for organization-specific membership attributes (e.g. local chapter tag, cost-centre code) without schema migrations.
-
created_at datetime Row creation timestamp (set by database).
required
updated_at datetime Last modification timestamp, updated on every write.
required

Database Indexes

idx_org_memberships_user_id
btree

Columns: user_id

idx_org_memberships_organization_id
btree

Columns: organization_id

idx_org_memberships_user_org_unique
btree unique

Columns: user_id, organization_id

idx_org_memberships_status
btree

Columns: status

idx_org_memberships_user_primary
btree

Columns: user_id, is_primary

idx_org_memberships_org_role_status
btree

Columns: organization_id, role, status

idx_org_memberships_paused_until
btree

Columns: paused_until

Validation Rules

user_id_references_existing_user error

Validation failed

organization_id_references_existing_org error

Validation failed

role_is_valid_enum error

Validation failed

status_transition_valid error

Validation failed

paused_until_after_paused_at error

Validation failed

display_order_non_negative error

Validation failed

external_member_id_max_length error

Validation failed

metadata_valid_json_object error

Validation failed

Business Rules

max_five_memberships_per_user
on_create

A single user may not hold memberships in more than five organizations simultaneously. Mirrors the NHF use-case of members belonging to up to five local chapters. Enforced before insertion.

single_primary_per_user
on_update

Exactly one membership per user must have is_primary = true at any moment. When a new primary is set, the previous primary is demoted atomically in the same transaction.

primary_must_be_active
on_update

The membership flagged as primary must have status = active. If the primary membership is paused or deactivated, the system must auto-promote another active membership (lowest display_order) or clear the primary flag and force re-selection.

pause_notifies_coordinator
on_update

When a peer mentor transitions their membership to status = paused, a notification event is emitted to all coordinators within the same organization. Implemented as an after-update hook, not a synchronous call.

deactivation_cascades_sessions
on_update

When a membership is deactivated, all active sessions scoped to that organization for the user must be revoked. Prevents a deactivated member from continuing to act within the organization.

role_change_audit_logged
on_update

Any change to the role column must produce an entry in audit_logs recording the previous role, new role, and the user who made the change. Supports GDPR accountability requirements.

no_duplicate_membership
on_create

A user may not hold two memberships in the same organization. The unique index on (user_id, organization_id) enforces this at the database level; the application layer should give a clear error message before hitting the constraint.

invitation_expiry_72h
always

Memberships with status = invited that have invited_at older than 72 hours without transition to active are flagged as expired. Expired invitations are excluded from active member counts and the coordinator is notified.

module_toggle_respects_membership
always

Feature module availability is resolved per organization context; the active membership's organization_id is the key used to look up enabled modules. A user switching profiles gets the module set of the newly selected organization.

paused_auto_resume
always

If paused_until is set and the current timestamp exceeds it, status must be transitioned back to active. A scheduled background job checks this condition; the rule is also re-evaluated on any read of the membership by organization-membership-service.

Storage Configuration

Storage Type
primary_table
Location
main_db
Partitioning
No Partitioning
Retention
Permanent Storage