Concepts
Atlas membership is represented by two record types:OrgMember: an active/known member row (memberscollection)OrgMemberApplication: a pending join request (orgMemberApplicationscollection)
OrgMember now supports multi-role assignment through roles[] (with role kept as a synced legacy primary role for compatibility).
Join configuration knobs (Org)
Stored on the org document:Org.requireApprovalForJoin: booleanOrg.memberForm?: ObjectId(aFormused to collect application answers)
Join flow: immediate membership
Condition:requireApprovalForJoin === false
Path: POST /:orgId/apply-to-org (in orgRoutes)
Behavior:
- Creates
OrgMember({ org_id, user_id, role: 'member', roles: ['member'] }) - Responds immediately with success
Join flow: application required
Condition:requireApprovalForJoin === true
Path: POST /:orgId/apply-to-org (in orgRoutes)
Behavior:
1
Check for existing application
2
Handle memberForm (if exists)
3
Create application (no form)
4
Notify admins
Notifies org admins (roles
owner/admin) via NotificationService template org_member_appliedReviewing applications
The “authoritative” member/application read is:GET /org-roles/:orgId/members
members: fromOrgMember.getActiveMembers(orgId)applications:OrgMemberApplication.find({ org_id, status: 'pending' }).populate('user_id formResponse')
Approving an application
POST /org-roles/:orgId/applications/:applicationId/approve- gated by
requireMemberManagement()(org permission:manage_members) - creates a new
OrgMemberfor the applicant - supports role assignment via
roles[]in the request body (default remainsmember) - marks the application
approvedand writes metadata
- gated by
Rejecting an application
There is a rejection concept in the schema (status: 'rejected'), but confirm the existence of a matching API endpoint before relying on it. If missing, implement alongside the approve endpoint and update the UI.
Removing members
DELETE /org-roles/:orgId/members/:userId- gated by
requireMemberManagement() - prevents removing the org owner
- currently hard-deletes the
OrgMemberdocument (commented-out “inactive” soft delete exists)
- gated by
Role assignment is membership creation
The role assignment endpoint:POST /org-roles/:orgId/members/:userId/role
OrgMember row if missing (and assign roles), or update roles if it exists.
Current payload shape:
roles: string[](preferred)role: string(legacy-compatible)
User.clubAssociations if it’s missing.
This means role assignment can effectively “force-join” a user into an org.
Troubleshooting membership issues
- User can’t access
/club-dashboard/:id:- frontend checks
User.clubAssociationsfororg.org_namematch; ensureclubAssociationsis populated and consistent
- frontend checks
- User is “member” but permission middleware denies:
OrgMember.statusmust beactive
- Role exists on member but permissions don’t apply:
Org.positions[].namemust match assigned values inOrgMember.roles[]