Identity & Members

Members Service

The Members service manages yearly membership records in the biztechMembers2027 DynamoDB table. All endpoints are admin-only (caller must have a @ubcbiztech.com email). Handlers are in services/members/handler.js.


Endpoints

MethodPathHandlerDescription
POST/memberscreateCreate a yearly member record
GET/members/{id}getGet a member by email
GET/membersgetAllList all members
PATCH/members/{id}updateUpdate a member record
DELETE/members/{id}delDelete a member record
POST/members/grantgrantMembershipGrant membership
GET/members/email/{profileID}getEmailFromProfileLegacy lookup; prefer biztechUsers.profileID-index

All endpoints require Cognito authentication + @ubcbiztech.com email.

Profile Lookup Ownership

Membership rows no longer own profile identity. Use biztechUsers.profileID and profileID-index as the source of truth for profile lookups.


Tables

The members service operates on multiple tables:

TableRole
biztechUsersCreated or updated during grant/payment flow; owns the canonical profileID
biztechMembers2027Primary yearly member records
biztechProfilesCreated or refreshed when membership changes

Year-Suffixed Table

The table name biztechMembers2027 includes the membership year. When the year rolls over, the corresponding table constant and DynamoDB table must be updated.


Member Record Shape

Table: biztechMembers2027

Purpose: yearly membership record. Row existence means active membership for 2027; this row does not own profile identity.

DynamoDB enforces only the id key. Other fields describe the effective write-path shape and may be absent; cardCount defaults to 0 on current create paths.

FieldTypeKey / IndexNotes
idStringPKLowercase email.
profileTypeString-New field. Allowed values are "ATTENDEE", "EXEC", and "PARTNER". Normal paid membership should set "ATTENDEE".
educationString-Membership form value.
firstNameString-Membership form first name.
lastNameString-Membership form last name.
pronounsString-Used to refresh profile.
studentNumberString/Number-Membership form student number.
facultyString-Membership form faculty.
yearString/Number-Used to refresh profile.
majorString-Used to refresh profile.
prevMemberBoolean-Previous member answer.
internationalBoolean-International student answer.
topicsString[]-Interest topics.
heardFromString-Referral source.
heardFromSpecifyString-Referral detail.
dietString-Dietary restrictions.
universityString-School/university.
highSchoolString-High school, if applicable.
adminBoolean-Existing admin snapshot.
cardCountNumber-NFC/card tracking; defaults to 0.
discordIdString-Existing location for now, though eventually user-level is cleaner.
createdAtNumber-Epoch ms.
updatedAtNumber-Epoch ms.

Indexes

IndexKeyPurpose
table primary keyidEmail to yearly membership.

There are no GSIs on the current members table.

Deprecated/removed from docs as source of truth

FieldStatus
profileIDNot part of the current row shape. Read it from biztechUsers.

GET /members/{id}

Fetch a single member by email.

Request

HeadersType
AuthorizationBearer Token
Path ParamDescription
idMember's email

Example Request

GET /members/student@example.com

Response

{
  "id": "student@example.com",
  "profileType": "ATTENDEE",
  "firstName": "Alice",
  "lastName": "Wong",
  "faculty": "Commerce",
  "year": "3",
  "major": "Accounting",
  "international": true,
  "topics": ["Finance", "Tech"],
  "createdAt": 1755702100000,
  "updatedAt": 1755702100000
}

Errors

  • 403 Unauthorized
  • 400 Invalid email
  • 404 Member not found

GET /members/

Fetch all members.

Request

HeadersType
AuthorizationBearer Token

Response

{
  "message": "success",
  "data": [
    {
      "id": "student@example.com",
      "profileType": "ATTENDEE",
      "firstName": "Alice",
      "lastName": "Wong",
      "year": "3",
      "major": "Accounting",
      "createdAt": 1755702100000,
      "updatedAt": 1755702100000
    },
    {
      "id": "bob@example.com",
      "profileType": "ATTENDEE",
      "firstName": "Bob",
      "lastName": "Li",
      "year": "4",
      "major": "Finance"
    }
  ]
}

PATCH /members/{id}

Update an existing member by email.

Request

HeadersType
AuthorizationBearer Token
Path ParamDescription
idMember's email

Body

  • Accepts any subset of the fields from the biztechMembers2027 record shape.
  • Do not write profileID; profile identity belongs on biztechUsers.

Example Request

{
  "major": "Business Technology Management",
  "year": "4",
  "topics": ["Consulting", "AI"]
}

Response

{
  "message": "Updated member with email student@example.com!",
  "response": {
    "Attributes": {
      "major": "Business Technology Management",
      "year": "4",
      "topics": ["Consulting", "AI"],
      "updatedAt": 1755703100000
    }
  }
}

Errors

  • 403 Unauthorized
  • 400 Invalid email
  • 404 Member not found

DELETE /members/{id}

Delete a yearly member record by email.

Request

HeadersType
AuthorizationBearer Token
Path ParamDescription
idMember's email

Example Request

DELETE /members/student@example.com

Response

{
  "message": "Member deleted!",
  "response": {
    "id": "student@example.com"
  }
}

Errors

  • 403 Unauthorized
  • 400 Invalid email
  • 404 Member not found

POST /members/grant

Grant membership for a user. This endpoint creates or updates user, member, and profile records as needed. There is no revocation path - this handler only grants.

Request

HeadersType
AuthorizationBearer Token

The body should contain the member fields needed to create or refresh the yearly membership:

Body PropertyTypeDescription
emailStringUser email (required)
profileTypeStringNew field. Allowed values are "ATTENDEE", "EXEC", and "PARTNER". Normal paid membership should set "ATTENDEE"
firstNameStringFirst name
lastNameStringLast name
educationStringUniversity or institution
studentNumberStringStudent number
pronounsStringPronouns
levelOfStudy / yearStringYear of study (handler checks both fields)
facultyStringFaculty
majorStringMajor
internationalStudent / internationalBooleanInternational student flag
previousMember / prevMemberBooleanWas a member in a previous year
dietaryRestrictions / dietStringDietary restrictions
referral / heardFromStringHow they heard about BizTech
heardFromSpecifyStringReferral detail
topicsString[]Topics of interest

Example Request

{
  "email": "isaacliu@gmail.com",
  "profileType": "ATTENDEE",
  "firstName": "Isaac",
  "lastName": "Liu",
  "education": "University of British Columbia",
  "studentNumber": "12345678",
  "pronouns": "He/Him/His",
  "levelOfStudy": "3",
  "faculty": "Commerce",
  "major": "BUCS",
  "internationalStudent": false,
  "previousMember": true,
  "dietaryRestrictions": "None",
  "referral": "Friend",
  "topics": ["Finance", "Tech"]
}

Response

{
  "message": "Membership granted"
}

Behavior

  1. If the user does not exist in biztechUsers, creates a new user record with the latest basic fields.
  2. If the user exists, updates the user record with the latest basic fields.
  3. Creates a yearly member row in biztechMembers2027 with profileType: "ATTENDEE" if no explicit profile type is provided.
  4. Reads biztechUsers.profileID.
  5. If profileID exists, refreshes the existing biztechProfiles row with membership fields such as pronouns, major, and year.
  6. If profileID is missing, creates a profile via createProfile(email, "ATTENDEE") and writes the generated profile ID to biztechUsers.profileID.

No Revocation

Despite the handler name, grantMembership only grants. Membership is revoked by deleting the yearly member row; DELETE /members/{id} does not delete the user or profile.

Errors

  • 403 Unauthorized if caller is not @ubcbiztech.com
  • 400 Invalid email format

Previous
Users Service