Skip to main content

Collections and schemas

All Atlas data is stored in MongoDB via Mongoose schemas and is model-bound per request through getModels(req, ...).

Core collections (Atlas)

ModelSchema FileCollection
OrgMeridian/backend/schemas/org.jsorgs
OrgMemberMeridian/backend/schemas/orgMember.jsmembers
OrgMemberApplicationMeridian/backend/schemas/orgMemberApplication.jsorgMemberApplications
OrgFollowerMeridian/backend/schemas/orgFollower.jsfollowers
OrgVerificationMeridian/backend/schemas/orgVerification.jsorgVerifications
OrgManagementConfigMeridian/backend/schemas/orgManagementConfig.jsorgManagementConfigs
OrgMessageMeridian/backend/schemas/orgMessage.jsorgMessages

User linkage

The user schema has:
clubAssociations: [{
  type: mongoose.Schema.Types.ObjectId,
  ref: 'Org'
}]
This is used in the frontend to gate access to /club-dashboard/:id.

Org (organization) schema

File: Meridian/backend/schemas/org.js

Identity

{
  org_name: { type: String, required: true }, // Uniqueness enforced at route level
  org_profile_image: { type: String, required: true, default: '/Logo.svg' },
  org_description: { type: String, required: true },
  owner: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true }
}

Membership config

{
  requireApprovalForJoin: { type: Boolean, default: false },
  memberForm: { type: mongoose.Schema.Types.ObjectId, ref: 'Form', default: null }
}
If requireApprovalForJoin is true, join requests create OrgMemberApplication instead of immediate membership.

Roles (positions[])

Roles are stored on the org document in positions[]:
positions: [{
  name: String,              // Role key: 'owner', 'admin', 'officer', 'member'
  displayName: String,
  permissions: [String],      // From constants/permissions.js
  canManageMembers: Boolean, // Legacy boolean (duplicated from permissions)
  canManageRoles: Boolean,
  canManageEvents: Boolean,
  canViewAnalytics: Boolean,
  order: Number
}]
Invariant: Role names are referenced by OrgMember.role. If you rename role names, you must migrate members.

Verification fields

Atlas “verification” is stored directly on the org:
{
  verified: { type: Boolean, default: false },
  verifiedAt: Date,
  verifiedBy: { type: mongoose.Schema.Types.ObjectId, ref: 'User' },
  verificationType: { 
    type: String, 
    enum: ['basic', 'premium', 'gold', 'platinum'] 
  },
  verificationStatus: { 
    type: String, 
    enum: ['pending', 'approved', 'rejected'] 
  }
}
verificationType is validated via config in OrgManagementConfig for requests; on Org it is an enum.

Messaging settings (messageSettings)

Per-org constraints:
messageSettings: {
  enabled: { type: Boolean, default: true },
  visibility: { 
    type: String, 
    enum: ['members_only', 'members_and_followers', 'public'],
    default: 'members_only'
  },
  postingPermissions: [String], // Role names allowed to post
  allowReplies: { type: Boolean, default: true },
  allowLikes: { type: Boolean, default: true },
  requireApproval: { type: Boolean, default: false },
  characterLimit: { type: Number, min: 100, max: 2000, default: 500 }
}

Org-level helper methods

Org defines helpers used by permission checks and role management:
// Get role by name
org.getRoleByName('admin')

// Check if role has permission
org.hasPermission('admin', 'manage_members')

// Role management
org.addCustomRole({ name: 'treasurer', ... })
org.updateRole('treasurer', { permissions: [...] })
org.removeRole('treasurer')

OrgMember schema

File: Meridian/backend/schemas/orgMember.js

Fields

{
  org_id: { type: mongoose.Schema.Types.ObjectId, ref: 'Org', required: true },
  user_id: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
  role: { type: String, default: 'member' }, // Must match Org.positions[].name
  status: { 
    type: String, 
    enum: ['active', 'inactive', 'pending', 'suspended'],
    default: 'active'
  },
  joinedAt: Date,
  assignedAt: Date,
  assignedBy: { type: mongoose.Schema.Types.ObjectId, ref: 'User' },
  roleHistory: [{
    role: String,
    changedAt: Date,
    changedBy: { type: mongoose.Schema.Types.ObjectId, ref: 'User' }
  }],
  customPermissions: [String],  // Force-allow permissions
  deniedPermissions: [String]   // Force-deny permissions
}

Indexes

// Unique compound index prevents multiple membership rows
{ org_id: 1, user_id: 1 }, { unique: true }

// Role queries
{ org_id: 1, role: 1 }

// User membership queries
{ user_id: 1, status: 1 }

Permission evaluation

member.hasPermissionWithOrg(permission, org)
1

Check denied permissions

If deniedPermissions.includes(permission)deny
2

Check custom permissions

Else if customPermissions.includes(permission)allow
3

Check org role

Else → org.hasPermission(this.role, permission)

OrgMemberApplication schema

File: Meridian/backend/schemas/orgMemberApplication.js Represents a join application when Org.requireApprovalForJoin === true.

Fields

{
  org_id: { type: mongoose.Schema.Types.ObjectId, ref: 'Org', required: true },
  user_id: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
  status: { 
    type: String, 
    enum: ['pending', 'approved', 'rejected'],
    default: 'pending'
  },
  reason: String, // Used for approval/rejection decisions
  formResponse: { 
    type: mongoose.Schema.Types.ObjectId, 
    ref: 'FormResponse' 
  }, // Created when org has memberForm
  approvedBy: { type: mongoose.Schema.Types.ObjectId, ref: 'User' },
  approvedAt: Date
}

OrgManagementConfig (global system config)

File: Meridian/backend/schemas/orgManagementConfig.js
This is a singleton document (read via findOne()).

Key sections

{
  verification: {
    verificationEnabled: Boolean,
    verificationRequired: Boolean,
    allowedRequestTypes: [String], // Feature flags
    verificationTiers: {
      // Keyed by tier id
      'basic': { requirements: [...], benefits: [...] },
      'premium': { requirements: [...], benefits: [...] }
    }
  },
  featureAccess: {
    eventCreation: Boolean,
    memberManagement: Boolean,
    fundingRequests: Boolean,
    spaceReservation: Boolean
  },
  reviewWorkflow: {
    requireMultipleApprovers: Boolean,
    minApprovers: Number,
    autoEscalateAfterDays: Number
  },
  reporting: {
    analyticsEnabled: Boolean,
    retentionDays: Number
  },
  messaging: {
    maxCharacterLimit: Number,
    minCharacterLimit: Number,
    requireProfanityFilter: Boolean,
    notificationSettings: {...}
  }
}

OrgVerification (verification request)

File: Meridian/backend/schemas/orgVerification.js

Fields

{
  orgId: { type: mongoose.Schema.Types.ObjectId, ref: 'Org', required: true },
  requestedBy: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
  requestType: { 
    type: String, 
    enum: ['verification', 'funding', 'feature_access'] 
  },
  verificationType: String, // Tier key; validated against config
  status: { 
    type: String, 
    enum: ['pending', 'under_review', 'approved', 'rejected'],
    default: 'pending'
  },
  requestData: mongoose.Schema.Types.Mixed, // Freeform payload
  reviewedBy: { type: mongoose.Schema.Types.ObjectId, ref: 'User' },
  reviewedAt: Date,
  reviewNotes: String,
  attachments: [{
    url: String,
    filename: String,
    uploadedAt: Date
  }]
}

OrgMessage (announcements/messages)

File: Meridian/backend/schemas/orgMessage.js

Fields

{
  orgId: { type: mongoose.Schema.Types.ObjectId, ref: 'Org', required: true },
  authorId: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
  content: { type: String, required: true, maxlength: 2000 },
  visibility: { 
    type: String, 
    enum: ['members_only', 'members_and_followers', 'public'],
    default: 'members_only'
  },
  mentionedEvents: [{ 
    type: mongoose.Schema.Types.ObjectId, 
    ref: 'Event' 
  }], // Derived from content parsing
  links: [String],
  likes: [{ type: mongoose.Schema.Types.ObjectId, ref: 'User' }],
  likeCount: { type: Number, default: 0 },
  parentMessageId: { 
    type: mongoose.Schema.Types.ObjectId, 
    ref: 'OrgMessage' 
  },
  replyCount: { type: Number, default: 0 },
  isDeleted: { type: Boolean, default: false },
  deletedAt: Date
}
Indexes are tuned for timeline queries (orgId + createdAt).

OrgFollower (followers)

File: Meridian/backend/schemas/orgFollower.js

Fields

{
  org_id: { type: mongoose.Schema.Types.ObjectId, ref: 'Club' },
  user_id: { type: mongoose.Schema.Types.ObjectId, ref: 'User' }
}
Important: This schema currently defines org_id: ref 'Club', but the model is named OrgFollower and used as “org followers” throughout routes. Treat this as a likely ref mismatch (it should probably be ref: 'Org'). If you rely on population behavior here, verify the reference is correct.