Skip to main content

Membership System

Lifecycle

Every actor-to-center relationship follows a strict lifecycle:
INVITED → PENDING → ACTIVE → SUSPENDED → REJECTED → ENDED
StatusDescriptionData Access
INVITEDInvitation sent, not yet acceptedNone
PENDINGAccepted invitation, awaiting approvalLimited
ACTIVEFull memberFull (scoped)
SUSPENDEDTemporarily disabledNone
REJECTEDApplication rejectedNone
ENDEDMembership terminatedNone

Database Schema

Key columns in center_memberships:
ColumnTypeDescription
actor_idUUIDThe member
center_idUUIDThe center
roleENUMADMIN, INSTRUCTOR, PARENT, CHILD
statusENUMLifecycle status
invited_atTIMESTAMPWhen invitation was sent
ended_atTIMESTAMPWhen membership ended
status_reasonTEXTReason for status change

Security Guarantees

  • No backdoor access: All center data access requires an ACTIVE membership
  • No center_id fallback: Direct center access without membership was removed and verified (0 affected rows in staging/production)
  • Audit trail: All membership status changes are logged

Querying Memberships

// Get all active memberships for an actor
const memberships = await basePrisma.centerMembership.findMany({
  where: {
    actorId: actor.id,
    status: 'ACTIVE',
  },
  include: { center: true },
});

// Get all members of a center (scoped)
const members = await scopedPrisma.centerMembership.findMany({
  where: { status: 'ACTIVE' },
  include: { actor: true },
});