User Schema
Firestore Document (users/{userId})
| Field | Type | Required | Constraints | Description |
|---|---|---|---|---|
id |
string |
yes | 6–100 chars | Firebase Auth UID; matches document ID |
authUser.id |
string |
yes | 6–100 chars | Firebase Auth UID |
authUser.email |
string \| null |
no | valid email | Email from Firebase Auth |
authUser.isDisabled |
boolean |
no | default false |
Whether the Firebase Auth account is disabled |
authUser.isEmailVerified |
boolean |
no | default false |
Whether the Firebase Auth email is verified |
authUser.createdAt |
Timestamp |
yes | — | Firebase Auth account creation time |
authUser.lastSignInAt |
Timestamp |
yes | — | Most recent Firebase Auth sign-in time |
authUser.name |
string \| null |
no | — | Display name from Firebase Auth |
authUser.phoneNumber |
string \| null |
no | E.164 format | Phone number from Firebase Auth |
authUser.photoURL |
string \| null |
no | valid URL | Profile photo URL from Firebase Auth |
authUser.provider |
string[] |
no | default [] |
Sign-in provider IDs (e.g. "google.com", "apple.com") |
name |
string |
no | 5–100 chars, default "Rider" |
Display name; takes precedence over authUser.name when non-null |
email |
string \| null |
no | valid email | App-level email; only settable when authUser.email is null |
isEmailVerified |
boolean |
no | default false |
Whether the app-level email field has been verified; distinct from authUser.isEmailVerified |
phoneNumber |
string \| null |
no | E.164 format | Phone number; takes precedence over authUser.phoneNumber |
photoURL |
string \| null |
no | valid URL | Profile photo; takes precedence over authUser.photoURL |
isAnonymous |
boolean |
no | default false |
Whether the account is anonymous |
notificationToken |
string \| null |
no | default null |
FCM push notification token |
settings.homeLocation |
{lat: number, lng: number} \| null |
no | lat −90–90, lng −180–180 | User's saved home location |
settings.notifications |
boolean |
no | default true |
Whether push notifications are enabled |
settings.shareLocation |
boolean |
no | default true |
Whether location sharing is enabled during rides |
type |
"free" \| "trial" \| "subscriber" \| "beta" |
no | default "free" |
Subscription tier; controls feature access |
status |
"active" \| "inactive" \| "banned" \| "deleted" |
no | default "active" |
Account status |
subscriptionExpiryAt |
Timestamp \| null |
no | default null |
When the current subscription expires |
deletedAt |
Timestamp |
no | — | Set when status becomes "deleted"; not returned in API responses |
createdAt |
Timestamp |
no | server-set | Account creation time; falls back to authUser.createdAt |
updatedAt |
Timestamp |
no | server-set | Last modification time |
ridesis a runtime-only field populated from theridessubcollection on everyGET /user/:idcall. It is not stored in the main document.
{
"id": "uid_abc123",
"authUser": {
"id": "uid_abc123",
"email": "rider@example.com",
"isDisabled": false,
"isEmailVerified": true,
"createdAt": "2025-01-01T00:00:00.000Z",
"lastSignInAt": "2025-06-01T08:00:00.000Z",
"name": "Arjun Mehta",
"phoneNumber": "+919876543210",
"photoURL": "https://example.com/photo.jpg",
"provider": ["google.com"],
},
"name": "Arjun Mehta", // overrides authUser.name when non-null
"email": null, // only set when authUser.email is null
"isEmailVerified": false, // true after user clicks the verification link
"phoneNumber": "+919876543210",
"photoURL": "https://example.com/photo.jpg",
"isAnonymous": false,
"notificationToken": "fcm-token-xyz",
"settings": {
"homeLocation": { "lat": 12.9716, "lng": 77.5946 }, // null if not set
"notifications": true,
"shareLocation": true,
},
"type": "subscriber", // "free" | "trial" | "subscriber" | "beta"
"status": "active", // "active" | "inactive" | "banned" | "deleted"
"subscriptionExpiryAt": "2026-06-01T00:00:00.000Z",
"createdAt": "2025-01-01T00:00:00.000Z",
"updatedAt": "2025-06-01T08:00:00.000Z",
}
Sub-collections
users/{userId}/rides/{rideId}
Tracks rides the user has RSVPed to. Used to populate the rides field in
GET /user/:id responses.
| Field | Type | Required | Constraints | Description |
|---|---|---|---|---|
id |
string |
yes | non-empty | Ride ID (matches document ID) |
updatedAt |
Timestamp |
yes | server-set | Last update time for this membership record |
users/{userId}/favorites/{favoriteId}
User's saved locations for quick selection during ride creation.
| Field | Type | Required | Constraints | Description |
|---|---|---|---|---|
id |
string |
yes | 6–100 chars | Favorite location ID (nanoId) |
title |
string |
yes | 3–100 chars | Human-readable location name |
type |
string |
yes | enum | One of origin, destination, meetingPoint, haltPoint, restaurant, fuelStation, other, home |
latitude |
number |
yes | −90 to 90 | WGS-84 latitude |
longitude |
number |
yes | −180 to 180 | WGS-84 longitude |
placeId |
string \| null |
yes | min 6 chars or null | Google Places API place ID |
createdAt |
Timestamp |
yes | server-set | Creation time |
updatedAt |
Timestamp |
yes | server-set | Last modification time |
users/{userId}/buddyRequests/{requestId}
Planned — not yet live.
Pending buddy requests. Mirrored on both the sender and recipient with a type
field indicating perspective. Firestore TTL policy is enabled on expiresAt —
requests are automatically deleted 90 days after creation.
| Field | Type | Required | Constraints | Description |
|---|---|---|---|---|
id |
string |
yes | 6–100 chars (nanoId) | Request ID; matches document ID |
type |
"sent" \| "received" |
yes | — | Perspective of the owning user |
otherUserId |
string |
yes | non-empty | The other party's UID |
senderId |
string |
yes | non-empty | Who initiated the request |
recipientId |
string |
yes | non-empty | Who should respond |
createdAt |
Timestamp |
yes | server-set | Creation time |
expiresAt |
Timestamp |
yes | computed | createdAt + 90d; Firestore TTL field |
Requests have no
statusfield — lifecycle is implicit: a pending request exists until it is accepted (both docs deleted,buddiesdocs written), cancelled or declined (both docs deleted), or expired (Firestore TTL deletes both docs after 90 days).
users/{userId}/buddies/{buddyId}
Planned — not yet live.
Confirmed buddy connections. Written to both users on request acceptance.
| Field | Type | Required | Constraints | Description |
|---|---|---|---|---|
id |
string |
yes | non-empty | The buddy's Firebase Auth UID. Used as the document ID — enables O(1) existence checks and enforces one document per buddy. |
createdAt |
Timestamp |
yes | server-set | When the connection was established |
users/{userId}/blockedUsers/{blockedUserId}
Planned — not yet live.
Users blocked by the owning user. The block is directional — each user maintains
their own list and there is no mirrored document on the blocked user (unlike
buddyRequests). Existence of a doc answers "does this user block that user?"
in O(1); listing the subcollection returns everyone the owner has blocked.
| Field | Type | Required | Constraints | Description |
|---|---|---|---|---|
id |
string |
yes | non-empty | The blocked user's Firebase Auth UID. Used as the document ID — enables O(1) by-UID lookups and enforces one document per blocked user. |
createdAt |
Timestamp |
yes | server-set | When the block was created |
Planned:
users/{userId}/groups/{groupId}— user's group memberships.Planned:
users/{userId}/subscriptions/{subscriptionId}— subscription and ride pack records. See Subscription Schema.
Related Collections
verificationTokens/{token}
Top-level collection for one-time verification tokens. The token is the document
ID — a 64-char nanoid. Used for email verification today; type makes it
extensible to phone and other verification flows. No PII appears in URLs.
Firestore TTL policy is enabled on expiresAt.
| Field | Type | Required | Constraints | Description |
|---|---|---|---|---|
userId |
string |
yes | non-empty | Owner's UID |
type |
"email" \| "phone" |
yes | — | Verification kind; extensible enum |
value |
string |
yes | non-empty | The thing being verified (email, phone number) |
createdAt |
Timestamp |
yes | server-set | Creation time |
expiresAt |
Timestamp |
yes | computed | Token expiry (72h); Firestore TTL field |
usedAt |
Timestamp \| null |
yes | — | Set on first use; subsequent attempts rejected |
Indexes
| Fields | Query Type | Reason |
|---|---|---|
status asc |
Single-field | Filter banned or deleted users |
type asc |
Single-field | Tier-based access checks across users |
users/{userId}/buddyRequests: type asc, expiresAt asc |
Composite | Filter sent/received requests and exclude expired ones |
verificationTokens: userId asc, usedAt asc |
Composite | Look up pending tokens by user (resend check) |