Schema
Dive Site Data Model
Each dive site is stored as a row with typed, indexed columns for the fields
you filter on most (location, environment, depth, level) and a flexible
tags JSONB field for everything else
(wildlife, conditions, season). This hybrid approach gives you fast queries
without rigid migrations.
Site classification uses two independent dimensions: environment (where the dive happens — ocean, lake, river, etc.) and topologies (terrain features — reef, wall, wreck, etc.). A site has exactly one environment but can have multiple topologies.
All measurements use SI / metric units as the canonical format,
following international diving standards (PADI, SSI). Field names encode the unit:
_m for meters,
_c for Celsius.
Client applications handle imperial conversion for display (meters × 3.28084 = feet).
Fields
id UUID auto-generated Primary key. Generated server-side via gen_random_uuid().
name TEXT required Primary name of the dive site in its most common form.
latitude DOUBLE PRECISION required Decimal degrees, WGS 84. Range: -90 to 90.
longitude DOUBLE PRECISION required Decimal degrees, WGS 84. Range: -180 to 180.
country_code CHAR(2) optional ISO 3166-1 alpha-2 country code. Examples: MX, ID, BZ.
environment TEXT required Where the dive happens. Exactly one value per site. Default: ocean.
topologies TEXT[] required Terrain features of the dive site. One or more values per site. Stored in a separate junction table, returned as a JSON array in the API.
max_depth_m INTEGER optional Maximum depth in meters. The _m suffix is part of the field name by convention.
level TEXT required Minimum recommended certification level. Default: intermediate.
tags JSONB optional Flexible key-value store for additional metadata. See tags reference below.
created_at TIMESTAMPTZ auto Set automatically on insert.
updated_at TIMESTAMPTZ auto Updated automatically on every edit via trigger.
Tags reference
The tags field accepts any valid JSON object.
The following keys are conventionally used and understood by the API and frontend.
wildlife string[] Species identifiers, snake_case. E.g. hammerhead_shark, manta_ray.
best_season string[] Three-letter lowercase months. E.g. ["jul", "aug", "sep"].
current string Typical current strength.
entry string How you enter the water.
visibility_avg_m number Average visibility in meters.
region string Administrative or geographic region. E.g. Baja California Sur.
sea string Body of water. E.g. Sea of Cortez, Caribbean Sea.
name_es / name_en / ...
string Localized names. Use ISO 639-1 suffixes.
GeoJSON output
The API returns dive sites as GeoJSON (RFC 7946). A single site from
GET /sites/:id returns a Feature:
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [longitude, latitude]
},
"properties": {
"id": "uuid",
"name": "string",
"country_code": "XX",
"environment": "ocean | lake | river | ...",
"topologies": ["reef", "wall", ...],
"max_depth_m": 0,
"level": "beginner | intermediate | advanced",
"tags": { ... },
"created_at": "ISO 8601",
"updated_at": "ISO 8601"
}
} GET /sites returns a FeatureCollection wrapping
an array of Features in the same shape. Supports filters:
?country=,
?level=,
?environment=,
?topology=,
?bbox=west,south,east,north.
Example
A real record from the database — El Bajo Seamount, Sea of Cortez:
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [-110.3981, 24.2156]
},
"properties": {
"id": "dccaf1e1-5df6-4058-a10c-00cdc85e87fb",
"name": "El Bajo Seamount",
"country_code": "MX",
"environment": "ocean",
"topologies": ["pinnacle"],
"max_depth_m": 40,
"level": "advanced",
"tags": {
"name_es": "El Bajo",
"wildlife": ["hammerhead_shark", "manta_ray", "whale_shark"],
"best_season": ["jul", "aug", "sep", "oct", "nov"],
"current": "strong",
"entry": "boat",
"visibility_avg_m": 20,
"region": "Baja California Sur",
"sea": "Sea of Cortez"
}
}
} Version history
Every update to a dive site automatically snapshots the previous version into
dive_sites_history via a database trigger.
Retrieve past versions with GET /sites/:id/history,
which returns an array ordered by most recent change first.