Flows
Profile Sync
How profiles are created, what they store, and how they connect to members, connections, and the companion app.
Profile Data Model
Profiles live in biztechProfiles. This table uses a composite key structure to store multiple record types:
| Record Type | compositeID (PK) | type (SK) | Purpose |
|---|---|---|---|
| Profile | PROFILE#<profileID> | PROFILE | The user's public profile |
| Connection | PROFILE#<profileID> | CONNECTION#<connProfileID> | A connection to another user |
This means a single partition key holds both the profile and all of its connections. Querying compositeID = PROFILE#abc returns the profile plus every connection that user has made.
Profile Fields
| Field | Type | Notes |
|---|---|---|
compositeID | String | PROFILE#<profileID> (partition key) |
type | String | PROFILE for the profile row (sort key) |
profileID | String | Human-readable ID (generated by humanId()) |
profileType | String | ATTENDEE, PARTNER, or EXEC |
firstName, lastName | String | From member data |
pronouns, major, year | String/Number | From member data |
hobby1, hobby2 | String | User-editable fun facts |
funQuestion1, funQuestion2 | String | User-editable ice-breaker answers |
linkedIn, additionalLink | String | Social links |
profilePictureURL | String | S3 URL for profile picture |
description | String | Bio |
viewableMap | Object | Privacy toggles ({ "linkedIn": true, "hobby1": false, ... }) |
Connection Row Fields
Each connection is stored as two rows (one for each side):
| Field | Type | Notes |
|---|---|---|
compositeID | String | PROFILE#<userProfileID> (partition key) |
type | String | CONNECTION#<otherProfileID> (sort key) |
connectionName | String | Denormalized name of the connected person |
connectionMajor, connectionYear | String/Number | Denormalized data |
createdAt | Number | Timestamp |
Profile Creation
Profiles are created via createProfile() in services/profiles/helpers.js. This function is called from three places:
| Caller | When |
|---|---|
services/payments/handler.js → OAuthMemberSignup() | After membership payment webhook |
services/members/handler.js → grantMembership() | After admin grants membership |
services/profiles/handler.js → create() | Direct API call POST /profiles |
createProfile() Steps
- Fetches the member record from
biztechMembers2026by email - Returns 404 if no member exists; returns error if
profileIDis already set (duplicate) - Generates a human-readable
profileIDusing thehuman-idlibrary - Builds the profile object with composite key
PROFILE#<profileID>, typePROFILE - Copies
firstName,lastName,pronouns,major,yearfrom the member record - Sets a default
viewableMapwith all fields visible - Executes a DynamoDB TransactWrite that atomically:
- Put the new profile into
biztechProfiles - Update the member in
biztechMembers2026to set itsprofileIDfield
- Put the new profile into
The transaction ensures the member's profileID always points to a valid profile.
Profile Types
| Type | Who | Created by |
|---|---|---|
ATTENDEE | Regular members | createProfile() during membership flow |
PARTNER | Sponsor/partner representatives | createPartialPartnerProfile() — creates a partner profile + QR entry |
EXEC | BizTech executive team | createProfile() with type override |
Partner profiles have additional fields like company, role, and companyProfileID.
Profile Picture Upload
Members upload profile pictures via POST /profiles/profile-pic-upload-url:
- Validates
fileTypestarts withimage/ - Generates an S3 key:
profile-pictures/<profileId>/optimized/<timestamp>.<ext> - Returns a pre-signed PUT URL for the
biztech-profile-picturesS3 bucket (60-second expiry) - The frontend uploads directly to S3, then updates the profile with the URL
Public vs Private Profile Views
Public view (GET /profiles/profile/{profileID}):
- Filters through the
viewableMap— only returns fields where the toggle istrue - Used by anyone viewing another person's profile (e.g., after an NFC tap)
Private view (GET /profiles/user/):
- Returns the full profile with all fields, regardless of
viewableMap - Used by the profile owner to edit their own profile
- Looks up the caller's
profileIDvia theirbiztechMembers2026record
Profile Updates
PATCH /profiles/user/ only allows updating fields in MUTABLE_PROFILE_ATTRIBUTES:
hobby1,hobby2,funQuestion1,funQuestion2linkedIn,profilePictureURL,additionalLink,description
The viewableMap can also be updated (toggling field visibility). Core identity fields (firstName, lastName, major, etc.) are set at creation time from the member record and cannot be changed through this endpoint.
Company Profiles
Company profiles represent organizations (sponsors, partner companies). They are created via POST /profiles/company:
- Derives a
companyIdfrom the company name (lowercase alphanumeric) - Creates a profile record in
biztechProfiles - Creates a QR entry in
biztechQRs
Partner profiles can be linked to company profiles via POST /profiles/company/link-partner, which:
- Sets
companyProfileIDandcompanyProfilePictureURLon the partner profile - Appends the partner's
profileIDto the company'sdelegateProfileIDslist
Connections (How They're Made)
Connections are created by the interactions service when two users tap NFC cards or scan QR codes:
POST /interactions/witheventType: "CONNECTION"andeventParam(scanned profile data)- The handler resolves both users'
profileIDvalues frombiztechMembers2026 - Fetches both profiles from
biztechProfiles - Checks for duplicate (same connection already exists)
- Writes two
CONNECTIONrows intobiztechProfiles— one for each user, with denormalized names - Broadcasts the new connection to the Live Wall via WebSocket
- Logs the connection to
bizLiveConnectionsfor wall replay
The GET /interactions/journal/ endpoint returns all of a user's connections by querying biztechProfiles where type begins_with CONNECTION#.
Profiles Service Endpoints
| Method | Path | Auth | Description |
|---|---|---|---|
POST | /profiles | Cognito | Create profile (calls createProfile()) |
POST | /profiles/partner/partial | Public | Create partner profile (deprecated) |
POST | /profiles/company | Public | Create company profile |
POST | /profiles/company/link-partner | Public | Link partner to company |
POST | /profiles/sync-partner-data | Public | Sync partner profiles with registrations |
GET | /profiles/profile/{profileID} | Public | Get public profile (filtered by viewableMap) |
GET | /profiles/user/ | Cognito | Get own profile (full, unfiltered) |
PATCH | /profiles/user/ | Cognito | Update own profile |
POST | /profiles/profile-pic-upload-url | Cognito | Get S3 presigned URL for profile picture |
Key Files
| File | Purpose |
|---|---|
services/profiles/handler.js | All profile endpoint handlers |
services/profiles/helpers.js | createProfile() and profile helper functions |
services/profiles/constants.js | Profile types, mutable attributes |
services/interactions/handler.js | Connection creation via NFC/QR |
services/interactions/helpers.js | handleConnection() — writes connection rows |
services/members/handler.js | grantMembership() calls createProfile() |
Related Pages
- User, Member & Profile Relationships — how profiles relate to users and members
- Membership Flow — when profiles get created
- Profiles Service — full profiles endpoint reference
- Connections & Testing — NFC-based connection details
- Companion System — where profiles are displayed