Database Schema - esketchandu/TrailAndOutdoorRecreationHub GitHub Wiki

Trail & Outdoor Recreation Hub - Database Schema

Overview

The Trail Hub database is designed to power a community-driven platform where outdoor enthusiasts can discover, share, and explore hiking trails together. Think of it as a social network meets GPS tracker meets trail guidebook - all rolled into one.

Database Schema Diagram

Entity Relationship Diagram showing all tables and their relationships Database Schema Diagram

What This Database Does

The database structure supports six core features that make the Trail Hub special:

  1. Trail Management - Store detailed trail information including GPS routes, difficulty levels, and elevation data
  2. User Reviews - Let hikers share their experiences with ratings, trail conditions, and helpful tips
  3. Points of Interest - Mark important spots like viewpoints, water sources, and campsites along trails
  4. Photo Sharing - Upload and geotag photos to create visual trail guides
  5. Hazard Reporting - Keep the community safe with real-time alerts about trail conditions
  6. Trip Planning - Organize group hikes and connect with other outdoor enthusiasts

Technology Stack

I am using PostgreSQL with the PostGIS extension for spatial data. This means I can store actual GPS trails and perform geographic queries like "find all trails within 10 miles of me" or "which viewpoints are along this trail?"

Design Philosophy

The schema follows a few key principles:

  • Keep it simple - I am focusing on essential features for the MVP
  • Community first - Users can contribute trails, reviews, photos, and updates
  • Safety matters - Hazard reporting and trail conditions are prominently featured
  • Real-world ready - All spatial data uses standard GPS coordinates (WGS84/SRID 4326)

Database Tables

Core Tables

users

The heart of our community - everyone from casual day hikers to hardcore mountaineers

Column Name Data Type Details
id integer Primary key - unique identifier for each user
username varchar(80) Unique username for login (required, indexed for fast lookups)
email varchar(120) Email address (required, unique, indexed)
password_hash varchar(255) Securely hashed password (never store plain text!)
first_name varchar(50) User's first name (optional)
last_name varchar(50) User's last name (optional)
bio text Tell us about your hiking experience (optional)
avatar_url varchar(500) Profile picture stored in AWS S3
hiking_level varchar(20) Self-reported skill: beginner, intermediate, advanced, or expert (defaults to beginner)
is_active boolean Is the account active? (defaults to true)
is_admin boolean Admin privileges for content moderation (defaults to false)
created_at timestamp When they joined our community
updated_at timestamp Last profile update

trails

The stars of the show - from easy nature walks to challenging summit attempts

Column Name Data Type Details
id integer Primary key - unique trail identifier
name varchar(200) Trail name like "Eagle Peak Trail" (required, indexed for search)
description text What makes this trail special? Wildlife, views, history, etc.
difficulty varchar(20) How tough is it? easy, moderate, hard, or expert (required)
length_km float Total distance in kilometers (required)
elevation_gain_m float Total climb in meters - helps gauge difficulty
geometry geometry The actual GPS trail route (PostGIS LINESTRING, required)
region varchar(100) General area like "Yosemite" or "Rocky Mountains"
parking_info text Where to park, fees, and tips
created_by integer User who added this trail (foreign key to users)
avg_rating float Average star rating (calculated from reviews, starts at 0)
total_reviews integer Number of reviews
created_at timestamp When the trail was added (indexed for "newest trails")
updated_at timestamp Last modification date

reviews

Real experiences from real hikers - the good, the muddy, and the breathtaking

Column Name Data Type Details
id integer Primary key
trail_id integer Which trail is being reviewed (foreign key, indexed)
user_id integer Who wrote this review (foreign key, indexed)
rating integer Star rating from 1 (awful) to 5 (amazing) - required
title varchar(200) Review headline like "Perfect Fall Hike!"
content text The full review story (required)
hiked_date date When they actually hiked it
weather_condition varchar(50) What was the weather? sunny, cloudy, rainy, or snowy
trail_condition varchar(50) Trail status: excellent, good, muddy, icy, or poor
crowd_level varchar(20) How busy was it? empty, light, moderate, or crowded
helpful_count integer How many people found this helpful (starts at 0)
is_verified_hike boolean GPS verified they were actually there (defaults to false)
created_at timestamp When review was written (indexed for sorting)
updated_at timestamp Last edit timestamp

points_of_interest

The highlights along the way - because the journey matters as much as the destination

Column Name Data Type Details
id integer Primary key
trail_id integer Which trail this POI belongs to (foreign key, indexed)
name varchar(200) POI name like "Eagle's Nest Viewpoint" (required)
description text What's special about this spot?
type varchar(50) What kind of POI: viewpoint, waterfall, campsite, water_source, parking, warning, or junction (required)
location geometry Exact GPS coordinates (PostGIS POINT, required)
latitude float Latitude for quick access (required)
longitude float Longitude for quick access (required)
elevation_m float How high up is it?
distance_from_start_km float How far from the trailhead?
amenities json For campsites: {"toilets": true, "fire_rings": false, "bear_boxes": true}
seasonal_info text Notes like "Water source dries up in late summer"
is_verified boolean Has this been confirmed? (defaults to false)
created_by integer User who added this POI (foreign key)
created_at timestamp When it was added
updated_at timestamp Last update

photos

A picture's worth a thousand elevation gains - visual inspiration and trail documentation

Column Name Data Type Details
id integer Primary key
uuid uuid Unique identifier for S3 storage (auto-generated)
trail_id integer Which trail this photo belongs to (foreign key, indexed)
user_id integer Who took this amazing shot (foreign key)
poi_id integer Linked to a specific POI (optional foreign key)
original_url varchar(500) Full-resolution image in S3 (required)
thumbnail_url varchar(500) Small preview version
medium_url varchar(500) Web-optimized version
caption text Photo description or story
latitude float GPS latitude from EXIF or manual tagging
longitude float GPS longitude from EXIF or manual tagging
location_on_trail geometry Exact spot on trail (PostGIS POINT, optional)
taken_at timestamp When the photo was taken (from EXIF)
camera_make varchar(100) Camera brand (from EXIF)
camera_model varchar(100) Camera model (from EXIF)
likes_count integer How many likes (starts at 0)
is_featured boolean Admin-selected showcase photos (defaults to false)
uploaded_at timestamp When uploaded to Trail Hub (indexed for recent photos)

hazards

Keeping everyone safe - because conditions change and the community looks out for each other

Column Name Data Type Details
id integer Primary key
trail_id integer Which trail has the hazard (foreign key, indexed)
reported_by integer User who reported it (foreign key, required)
type varchar(50) Type of hazard: fallen_tree, flooding, wildlife, ice, rockfall, trail_damage, or closure (required)
severity varchar(20) How serious: low, medium, high, or extreme (required)
title varchar(200) Quick summary like "Large tree across trail at mile 2" (required)
description text Full details and suggested detours (required)
location geometry Exact hazard location (PostGIS POINT, required)
latitude float Latitude for quick display (required)
longitude float Longitude for quick display (required)
status varchar(20) Current status: active, monitoring, or resolved (defaults to active)
resolved_at timestamp When the hazard was cleared
resolved_by integer User who marked it resolved (foreign key)
resolution_notes text How it was resolved
expires_at timestamp Auto-expire date for temporary hazards
verified_by_admin boolean Admin confirmation for serious hazards (defaults to false)
reported_at timestamp When first reported (indexed for recent hazards)
updated_at timestamp Last status update

trip_plans

Bringing hikers together - because trails are better with friends

Column Name Data Type Details
id integer Primary key
trail_id integer Which trail we're hiking (foreign key)
organizer_id integer Who's organizing this adventure (foreign key)
title varchar(200) Trip name like "Sunrise Hike to Eagle Peak" (required)
description text What to expect, skill level needed, etc.
planned_date date When we're going (required, indexed for upcoming trips)
start_time time Meeting time (required)
estimated_end_time time When we expect to finish
meeting_point varchar(200) Where to meet - might differ from trailhead
meeting_point_location geometry GPS coordinates of meeting spot (PostGIS POINT)
meeting_lat float Meeting point latitude
meeting_lon float Meeting point longitude
max_participants integer Group size limit (defaults to 10)
difficulty_level varchar(20) Expected pace/difficulty
pace varchar(20) How fast we'll go: leisurely, moderate, or fast
requirements json What to bring: ["water", "lunch", "hiking boots", "headlamp"]
notes text Additional info like "Dog-friendly" or "Photography focus"
status varchar(20) Trip status: active, cancelled, or completed (defaults to active)
is_public boolean Open to everyone or private group? (defaults to true)
created_at timestamp When trip was posted
updated_at timestamp Last modification

Relationship Tables

trip_participants

Who's coming on the adventure?

Column Name Data Type Details
trip_plan_id integer Which trip (composite primary key with user_id)
user_id integer Which hiker (composite primary key with trip_plan_id)
status varchar(20) Their RSVP: pending, confirmed, or declined (defaults to pending)
joined_at timestamp When they signed up

favorite_trails

Your personal trail wishlist and hall of fame

Column Name Data Type Details
user_id integer Who favorited it (composite primary key)
trail_id integer Which trail they love (composite primary key)
favorited_at timestamp When they added it to favorites

hazard_photos

Visual documentation of trail hazards

Column Name Data Type Details
hazard_id integer Which hazard (composite primary key)
photo_id integer Which photo shows it (composite primary key)

Key Design Decisions

Why These Relationships?

  • One user can create many trails - We're building a community-contributed database
  • One trail has many reviews - Multiple perspectives make better trail guides
  • One review per user per trail - Prevents spam and ensures authentic experiences
  • Photos can link to POIs - "Here's what the viewpoint actually looks like!"
  • Hazards have reporters and resolvers - Track who's helping keep trails safe
  • Trip plans use many-to-many - Flexible group organization

Performance Considerations

I have added indexes on:

  • Search fields (usernames, trail names) for quick lookups
  • Foreign keys for efficient joins
  • Timestamps for sorting by recent activity
  • Spatial data automatically indexed by PostGIS for "near me" queries

Data Integrity

  • Unique constraints prevent duplicate usernames and emails
  • Foreign keys ensure data consistency (can't review a non-existent trail)
  • Not null constraints on critical fields (every trail needs a name and route)
  • Application-level validation for ratings (1-5) and status values