Skip to content

πŸ—„οΈ Database Schema & PostGIS Reference ​

This document maps out the PostGIS-enabled PostgreSQL database schema managed via Drizzle ORM for the BikeTrack application. All schema code resides in [apps/api/src/db/schema.ts](file:///Users/ulad/Documents/Pets/BikeTrackV2/bike-maps/apps/api/src/db/schema.ts).


πŸ—ΊοΈ Database ER Diagram (High Level) ​


πŸ› οΈ Custom Spatial Geometry Types ​

PostGIS geometry columns are integrated into Drizzle using custom types defined at the top of the schema file:

1. lineStringGeometry ​

  • Postgres Column Type: geometry(LineString, 4326)
  • Purpose: Holds ordered arrays of coordinate points [longitude, latitude] defining a route path.
  • Projection: SRID 4326 (WGS 84 - Standard World Geodetic System).

2. polygonGeometry ​

  • Postgres Column Type: geometry(Polygon, 4326)
  • Purpose: Defines bounded area segments for map overlays and route metadata.

πŸ“‹ Table Definitions ​

1. users ​

Represents application user accounts synced with Firebase Auth.

ColumnTypeConstraintsDescription
idtextprimaryKeyFirebase Authentication Unique ID (UID)
emailtext-Email address
stravaAthleteIdtextuniqueLinked Strava Account Athlete ID
stravaAccessTokentext-Strava OAuth Access Token
stravaRefreshTokentext-Strava OAuth Refresh Token
stravaTokenExpiresAttimestamp-Strava Token Expiration Date
stravaClientIdtext-Strava Client ID override (if configured)
stravaClientSecrettext-Strava Client Secret override
lastStravaSyncDatetimestamp-Date of last successful activity sync
createdAttimestampdefaultNow()Account Creation Date

2. tracks ​

Saves user-planned route coordinates, telemetry stats, cue sheets, and waypoints.

ColumnTypeConstraintsDescription
iduuidprimaryKey, defaultRandom()Unique ID
userIdtextnotNull, references(users.id)Owner ID
titletextnotNullTrack Title
descriptiontext-Short Description
routeGeometrylineStringGeometrynotNullPostGIS LineString geometry containing points
distanceMetersdoublePrecisionnotNullTotal route length in meters
statsjsonbdefault('{}')Elevation gain/loss and surface breakdown stats
waypointsjsonbdefault('[]')Core plan waypoints [lng, lat] used in editing
cuesjsonb-Array of cue directions (turn sheets)
colorHextext-Route display color on map
avoidanceZonesjsonbdefault('[]')Spatial bounding boxes to bypass during routing
isFavoritebooleannotNull, default(false)Favorite toggle
deletedAttimestamp-Soft delete timestamp
createdAttimestampdefaultNow()Track creation date
updatedAttimestampdefaultNow()Track last modification date

NOTE

Indices:

  • idx_tracks_user_id: Index on userId to load user library quickly.
  • idx_tracks_user_folder_state: Composite index on userId, deletedAt, and isFavorite to optimize dashboard layout queries.

3. collections ​

Represents folder organizations to group tracks (e.g. "Gravel Projects", "Italy Trip 2026").

ColumnTypeConstraintsDescription
iduuidprimaryKey, defaultRandom()Unique ID
userIdtextnotNull, references(users.id, CASCADE)Owner ID
nametextnotNullCollection folder name
colorHextext-Cover color
imageUrltext-Custom cover image URL
isProjectbooleannotNull, default(false)True if this record represents a planning project
projectMetadatajsonb-Project-level metadata
createdAttimestampdefaultNow()Creation timestamp

NOTE

Unique Constraints:

  • collections_user_id_name_key: Unique index on (userId, name) preventing duplicate folder names per user.

4. track_collections ​

Many-to-many junction table to map tracks to their folders (collections).

ColumnTypeConstraintsDescription
trackIduuidnotNull, references(tracks.id, CASCADE)Track Link
collectionIduuidnotNull, references(collections.id, CASCADE)Collection Link
sortOrderintegernotNull, default(0)Sorting layout precedence in the folder

5. pois ​

User-defined Points of Interest rendered as emoji markers on the map canvas.

ColumnTypeConstraintsDescription
iduuidprimaryKey, defaultRandom()Unique ID
userIdtextnotNull, references(users.id, CASCADE)Owner ID
nametextnotNullPOI display label
typepoi_type enumnotNullEnums: WATER, FOOD, HAZARD, VIEWPOINT, CAMPING, GENERAL
coordinatesjsonbnotNullGeographical array [lng, lat]
descriptiontext-Detailed description popup content
scopetextnotNullScope classification ("track", "global")
visibilitytextnotNullVisibility permissions ("private", "public")
routeIduuidreferences(tracks.id, CASCADE)Route link (if this POI is attached to a specific route)
createdAttimestampdefaultNow()Creation date

6. bikes ​

Virtual representation of a user's bicycle in the virtual Garage.

ColumnTypeConstraintsDescription
iduuidprimaryKey, defaultRandom()Unique ID
userIdtextnotNull, references(users.id, CASCADE)Owner ID
nametextnotNullBike Label (e.g. "Canyon Grizl")
brandtextnotNullBrand
modeltextnotNullModel
typetextnotNull, default('Other')Category (Road, Gravel, MTB, E-Bike)
stravaGearIdtext-Strava Gear Identifier for automatic component wear updates
totalKmdoublePrecisionnotNull, default(0)Total distance ridden
isIgnoredbooleannotNull, default(false)Flag to ignore from sync calculations

7. components ​

Virtual bike components that track dual-wear limits (distance and operating hours).

ColumnTypeConstraintsDescription
iduuidprimaryKey, defaultRandom()Unique ID
userIdtextnotNull, references(users.id, CASCADE)Owner ID
bikeIduuidreferences(bikes.id, SET NULL)Currently mounted bicycle
nametextnotNullComponent Name (e.g. "Chain CN-M8100")
brandtext-Brand
modeltext-Model
pricedoublePrecision-Purchase price
categorytextnotNull, default('DRIVETRAIN')drivetrain, brakes, wheels, suspension, cockpit, accessories
statustextnotNull, default('INVENTORY')inventory, mounted, archived
compatibleBikeIdsjsonbdefault('[]')Array of compatible bike IDs
tagsjsonbdefault('[]')Custom text tags
lastServiceDatetimestamp-Timestamp of last recorded maintenance action
maxLifespanKmdoublePrecisionnotNull, default(0)Maximum wear limit (kilometers)
currentLifespanKmdoublePrecisionnotNull, default(0)Ridden kilometers since brand new
maxLifespanHoursdoublePrecisionnotNull, default(0)Maximum wear limit (hours)
currentLifespanHoursdoublePrecisionnotNull, default(0)Ridden hours since brand new
serviceIntervalKmdoublePrecisionnotNull, default(0)Kilometers between required maintenance
currentServiceKmdoublePrecisionnotNull, default(0)Kilometers since last service event
serviceIntervalHoursdoublePrecisionnotNull, default(0)Hours between required maintenance
currentServiceHoursdoublePrecisionnotNull, default(0)Hours since last service event
typetextnotNullDrivetrain subclass (retained for backward compatibility)
maxKmdoublePrecisionnotNullWear limit kilometer override (legacy)
currentKmdoublePrecisionnotNull, default(0)Current kilometers (legacy)
kmSinceLastServicedoublePrecisionnotNull, default(0)Kilometers since service (legacy)

8. maintenance_logs ​

Historical records of services performed on virtual components.

ColumnTypeConstraintsDescription
iduuidprimaryKey, defaultRandom()Unique ID
componentIduuidnotNull, references(components.id, CASCADE)Serviced component ID
bikeIduuidreferences(bikes.id, SET NULL)Bicycle reference during service
actionTypetextnotNullType of service (Clean, Replace, Lubricate)
notestext-Custom description of work
kmAtServicedoublePrecisionnotNullComponent kilometer read when service was performed
performedAttimestampnotNull, defaultNow()Log timestamp

9. ride_wear_logs ​

Accumulates wear coefficients based on route types to model component degradation.

ColumnTypeConstraintsDescription
iduuidprimaryKey, defaultRandom()Unique ID
bikeIduuidnotNull, references(bikes.id, CASCADE)Battered bicycle
trackIdtextnotNullAssociated ride activity or path
distancedoublePrecisionnotNullRidden distance
multiplierdoublePrecisionnotNullEnvironmental multiplier (e.g. Gravel=1.2x, Dirt=1.5x)
appliedAttimestampnotNull, defaultNow()Execution date

10. activities ​

Rides imported from Strava to track mileage and calculate virtual component wear.

ColumnTypeConstraintsDescription
iduuidprimaryKey, defaultRandom()Unique ID
userIdtextnotNull, references(users.id, CASCADE)Athlete ID
externalIdtextnotNull, uniqueStrava API Activity ID
nametextnotNullStrava ride title
distancedoublePrecisionnotNullRode distance in meters
movingTimeintegernotNullRide duration in seconds
startDatetimestampnotNullDate ride began
gearIdtext-Strava Gear/Bike ID used on the ride
summaryPolylinetext-Strava simplified map polyline string
isIgnoredbooleannotNull, default(false)Skip processing
trackIduuidreferences(tracks.id, SET NULL)Correlated saved route
statustextnotNull, default('PENDING')Import status (PENDING, PROCESSED)
localBikeIduuidreferences(bikes.id, SET NULL)Matched garage bike
timezonetext-Timezone string
commutebooleandefault(false)True if ride was marked as commute
sportTypetextnotNull, default('Ride')Ride sport type

11. routes ​

Geographic route caches computed from activities or custom trim events.

ColumnTypeConstraintsDescription
iduuidprimaryKey, defaultRandom()Unique ID
userIdtextnotNull, references(users.id, CASCADE)Owner ID
sourceActivityIduuidreferences(activities.id, SET NULL)Original imported Strava Activity
nametextnotNullRoute label
distancedoublePrecisionnotNullPrecision distance (km)
polylinetextnotNullCompressed encoded path polyline
isEbikebooleannotNull, default(false)Flag if ridden on electric bike
createdAttimestampnotNull, defaultNow()Cache creation date

πŸ”’ Transaction Safeguards ​

Drizzle transaction layers are used during route updates (e.g. in /api/routes/:id/crop) to protect geometry integrity:

  1. Coordinates are loaded and verified in memory.
  2. A single nested SQL BEGIN...COMMIT block performs:
    • Database line update (routes.polyline).
    • Telemetry distance normalization (routes.distance).
    • Triggering component wear recalibration in a downstream queue step.
  3. If any math check or coordinate slice operation fails, the transaction is immediately rolled back using transaction.rollback(), ensuring no partial/broken coordinates are ever committed to Postgres.