Identity & Members

User, Member & Profile Relationships

How the three identity tables - biztechUsers, biztechMembers2027, and biztechProfiles - relate to each other and when each record is created.


Ownership Rules

AreaSource of truthNotes
Account identitybiztechUsersStable user row keyed by lowercase email. This is the source of truth for profileID.
Yearly membershipbiztechMembers2027Row existence means the user is a current 2027 member. This table does not own profile identity.
Networking profilebiztechProfilesPublic/private profile data plus connection rows keyed by profile ID.

Important rule for docs and implementation:

  • User owns profile identity.
  • Membership owns yearly membership data.
  • Profile owns networking and public profile data.

The Three Records

Every fully-registered BizTech member has records across three tables:

biztechUsers          biztechMembers2027        biztechProfiles
---------------       ------------------        ----------------------
| id (email)  | <---> | id (email)      |        | compositeID        |
| profileID --|-------------------------------> | PROFILE#<profileID>|
| fname       |       | firstName       |        | type: PROFILE      |
| lname       |       | lastName        |        | profileID          |
| admin       |       | profileType     |        | profileType        |
| education   |       | cardCount       |        | fname, lname       |
| faculty     |       | pronouns        |        | pronouns           |
| year        |       | major, year     |        | major, year        |
| diet        |       | yearly answers  |        | viewableMap        |
| ...         |       | ...             |        | linkedIn, ...      |
---------------       ------------------        ----------------------
     PK: id (email)        PK: id (email)        PK: compositeID
     GSI: profileID-index                         SK: type

Key Relationships

  • User -> Member: Linked by id (lowercase email). The existence of a biztechMembers2027 row means the user is a current member.
  • User -> Profile: Linked by biztechUsers.profileID. This is the canonical profile identity link.
  • Member -> Profile: Membership data can refresh profile fields like pronouns, major, and year, but membership rows do not own profileID.

Table Schemas

DynamoDB enforces the key attributes. Other fields below describe the effective write-path shape and may be absent unless noted otherwise.

biztechUsers

Purpose: stable account identity. This table is now the source of truth for profileID.

FieldTypeKey / IndexNotes
idStringPKLowercase email.
profileIDStringGSI profileID-indexStable profile ID. Used to find the user's profile.
fnameString-First name.
lnameString-Last name.
educationString-School/education status.
studentIdString/Number-Student number.
facultyString-Faculty.
majorString-Major/program.
yearString/Number-Year of study.
genderString-Existing user write paths store pronouns/gender under gender; profiles use pronouns.
dietString-Dietary restrictions.
adminBoolean-Auto-set for ubcbiztech.com; immutable via user API.
favedEventsID;yearStringSet-Favorite events.
createdAtNumber-Epoch ms.
updatedAtNumber-Epoch ms.

Indexes

IndexKeyPurpose
table primary keyidEmail to user.
profileID-indexprofileIDKEYS_ONLY projection; profile ID to user/email.

Owner service: services/users/handler.js

isMember is not part of the current user write/read contract. Older rows may still contain it, but membership checks use the yearly members table and ignore the legacy attribute.

biztechMembers2027

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

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.

Deprecated/removed from docs as source of truth

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

Owner service: services/members/handler.js

Year-Suffixed Table

The table name includes the membership year. The active yearly members table is now biztechMembers2027; when the membership year rolls over, update the table constant and create the corresponding DynamoDB table.

biztechProfiles

Purpose: public/private profile data and connection rows.

Profile Row

FieldTypeKey / IndexNotes
compositeIDStringPKPROFILE#{profileID}.
typeStringSK"PROFILE" for profile row.
profileIDString-Stored explicitly now for convenience.
profileTypeString-"ATTENDEE", "EXEC", or "PARTNER".
fnameString-Public profile first name.
lnameString-Public profile last name.
pronounsString-Refreshed from membership purchase.
majorString-Refreshed from membership purchase.
yearString/Number-Refreshed from membership purchase.
hobby1String-Editable profile field.
hobby2String-Editable profile field.
funQuestion1String-Editable profile field.
funQuestion2String-Editable profile field.
linkedInString-Editable.
profilePictureURLString-Editable/uploaded.
additionalLinkString-Editable.
resumeURLString-Resume link.
descriptionString-Bio.
companyString-Company name, when applicable.
positionString-Position/title, when applicable.
viewableMapObject-Visibility flags.
createdAtNumber-Epoch ms.
updatedAtNumber-Epoch ms.

Connection Row

FieldTypeKey / IndexNotes
compositeIDStringPKPROFILE#{sourceProfileID}.
typeStringSKCONNECTION#{targetProfileID}.
connectionIDString-Target profileID.
connectionTypeString-Target profile type.
createdAtNumber-First connection timestamp.
fname, lname, pronouns, major, year, company, titleMixed-Denormalized target snapshot.

A connection normally writes two rows, one under each profile, so the relationship is bidirectional.

Indexes

IndexKeyPurpose
table primary keycompositeID, typeProfile row and profile-scoped connection rows.

Owner service: services/profiles/handler.js


Lookup Patterns

I have...I need...How
EmailUser recordRead biztechUsers by id.
EmailYearly member recordRead biztechMembers2027 by id.
EmailProfileRead biztechUsers.profileID, then read biztechProfiles with compositeID: "PROFILE#{profileID}" and type: "PROFILE".
Profile IDUser/emailQuery biztechUsers.profileID-index by profileID.
Profile IDPublic profileGET /profiles/profile/{profileID} filtered by viewableMap.

Deprecated Profile Lookup

Do not read profileID from biztechMembers2027. Membership rows no longer own profile identity.


Previous
Admin Detection