Organization Membership
Data Entity
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.
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
Columns: user_id
idx_org_memberships_organization_id
Columns: organization_id
idx_org_memberships_user_org_unique
Columns: user_id, organization_id
idx_org_memberships_status
Columns: status
idx_org_memberships_user_primary
Columns: user_id, is_primary
idx_org_memberships_org_role_status
Columns: organization_id, role, status
idx_org_memberships_paused_until
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
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
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
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
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
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
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
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
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
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
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.