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

rides is a runtime-only field populated from the rides subcollection on every GET /user/:id call. 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 status field — lifecycle is implicit: a pending request exists until it is accepted (both docs deleted, buddies docs 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.

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)