WayFy’s backend persists all application data in a relational database managed through SQLAlchemy and Flask-Migrate (Alembic). PostgreSQL is the recommended engine for both local development and production, but the stack also supports SQLite for quick local testing. The connection is configured entirely through theDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/jhonyes04/new-wayfy/llms.txt
Use this file to discover all available pages before exploring further.
DATABASE_URL environment variable, and schema changes are versioned as Alembic migration files in backend/migrations/versions/ — there are currently eleven migration revisions tracking the full history of the schema.
Supported Database Engines
- PostgreSQL (Recommended)
- SQLite
PostgreSQL is the engine used in production on Render and is the best-tested option for WayFy. Use the standard
postgres:// format — WayFy’s app.py automatically converts it to postgresql:// for SQLAlchemy compatibility:SQLAlchemy dropped support for the
postgres:// scheme in version 1.4, but many hosting providers (including Render and older Heroku-style configs) still emit it. WayFy’s app.py detects the prefix and rewrites it to postgresql:// at startup so you never need to manually edit connection strings from your host.Running Migrations
WayFy uses Flask-Migrate, which wraps Alembic and ties migration commands into the Flask CLI. All migration operations are run from thebackend/ directory with Pipenv using the scripts defined in backend/Pipfile.
Apply all pending migrations
Run the Internally this runs
upgrade script to bring your database schema up to the latest revision. This is the command you run after cloning the repo for the first time, and after pulling new migration files from teammates:flask db upgrade, which applies any unapplied migration files in backend/migrations/versions/ in order.Generate a new migration after model changes
After modifying a SQLAlchemy model in Alembic compares the current model definitions against the last known schema state and writes a new versioned file in
backend/src/api/models/, auto-generate a new migration file by running:backend/migrations/versions/. Always review the generated file before committing — Alembic cannot detect all changes (e.g. column renames) automatically.Roll back the last migration
If you need to undo the most recent migration (e.g. to revise a model change before it is merged), run:This runs
flask db downgrade, reverting one revision. Run it repeatedly to step back through multiple revisions.Seeding Test Data
WayFy provides two Flask CLI commands for populating a development database with realistic data:Insert Test Users
Creates a specified number of test user accounts with hashed passwords:Pass any integer to control how many users are created. Useful for testing pagination, admin dashboards, and user management flows.
Insert Test Data
Runs the full seed script from Use this to get a fully populated local environment with representative data across all eight tables.
backend/src/commands.py, which populates places, trips, accessibility reviews, and related records:Data Model Reference
WayFy’s schema spans eight tables. The diagram below shows the relationships, followed by a detailed field reference for each model.users
users
Stores WayFy account records. Passwords are stored as bcrypt hashes — never plain text.
| Column | Type | Notes |
|---|---|---|
id | integer | Primary key |
firstname | string(50) | User’s first name |
lastname | string(100) | User’s last name |
email | string(120) | Unique; used as login credential |
password | string(255) | bcrypt hash |
selected_mobility | text (JSON) | User’s mobility profile stored as a JSON array string (e.g. '["wheelchair", "walker"]'); parsed to a list on serialize |
avatar | string(255) | Filename served from /api/users/avatar/<filename>; defaults to default_avatar.png |
is_active | boolean | Whether the account is active |
is_admin | boolean | Grants access to admin endpoints and AdminRoute pages |
user_favorites
user_favorites
Saved places for a user, sourced from OpenStreetMap. The combination of
user_id + osm_id is enforced as unique via a UniqueConstraint, preventing duplicate saves of the same place by the same user.| Column | Type | Notes |
|---|---|---|
id | integer | Primary key |
user_id | integer | FK → users.id |
osm_id | string(50) | OpenStreetMap element identifier; always stored as a string |
place_name | string(200) | Display name of the place |
place_label | string(100) | Full formatted address label |
longitude | float | WGS 84 longitude |
latitude | float | WGS 84 latitude |
wheelchair | string(20) | OSM wheelchair tag value (e.g. yes, limited, no) |
osm_type | string(20) | OSM element type (node, way, relation) |
sub_type | string(100) | Place category (e.g. restaurant, museum) |
all_tags | text (JSON) | Full OSM tags dictionary as a JSON string |
created_at | datetime | Timestamp when the favorite was saved |
accessibility_reviews
accessibility_reviews
Community-submitted accessibility assessments for a place identified by
osm_id. The osm_id column has a unique index — one review record per OSM element.| Column | Type | Notes |
|---|---|---|
id | integer | Primary key |
osm_id | string(50) | Unique OSM identifier for the reviewed place |
osm_type | string(10) | OSM element type; defaults to 'node' |
place_name | string(200) | Display name at time of review |
last_modified_by_id | integer | FK → users.id — most recent editor |
wheelchair | string(10) | Overall wheelchair accessibility rating (yes, limited, no) |
has_ramp | boolean | Entrance ramp present |
has_elevator | boolean | Elevator available |
has_accessible_toilet | boolean | Accessible toilet on premises |
has_accessible_parking | boolean | Designated accessible parking available |
automatic_door | boolean | Automatic or power-assisted door at entrance |
description | text | Free-text notes from the reviewer |
created_at | datetime | First submission timestamp |
updated_at | datetime | Last update timestamp |
accessibility_photos
accessibility_photos
Photos attached to an accessibility review, uploaded via Cloudinary.
| Column | Type | Notes |
|---|---|---|
id | integer | Primary key |
review_id | integer | FK → accessibility_reviews.id |
user_id | integer | FK → users.id — uploader |
filename | string(255) | Cloudinary public ID or filename; served from /api/accessibility/photos/<filename> |
caption | string(200) | Optional descriptive caption |
created_at | datetime | Upload timestamp |
trips
trips
User-created itineraries. Trips can be public or private, and a trip can be forked from another via
original_trip_id.| Column | Type | Notes |
|---|---|---|
id | integer | Primary key |
user_id | integer | FK → users.id — owner |
title | string(200) | Trip name |
description | text | Optional overview text |
is_public | boolean | Whether the trip is visible to other users; defaults to false |
original_trip_id | integer | Self-FK → trips.id; set when a trip is forked/cloned; nullable |
cover_image | string(200) | Filename served from /api/trips/cover/<filename>; nullable |
created_at | datetime | Creation timestamp |
updated_at | datetime | Last modification timestamp |
trip_days
trip_days
Individual days within a trip, each representing one day of the itinerary. Days are ordered by
day_number.| Column | Type | Notes |
|---|---|---|
id | integer | Primary key |
trip_id | integer | FK → trips.id |
day_number | integer | Sequential day index within the trip |
date | date | Calendar date for this day (optional) |
title | string(200) | Optional day label (e.g. “Arrival Day”) |
notes | text | Free-text notes for the day |
trip_day_places
trip_day_places
Ordered places visited on a specific trip day. Each entry links a day to either a saved favorite or a raw place coordinate. Places within a day are ordered by the
order column.| Column | Type | Notes |
|---|---|---|
id | integer | Primary key |
trip_day_id | integer | FK → trip_days.id |
favorite_id | integer | FK → user_favorites.id; nullable — not set when a place is added without saving it as a favorite |
place_name | string(200) | Display name of the place |
latitude | float | WGS 84 latitude |
longitude | float | WGS 84 longitude |
osm_id | string(50) | OSM identifier; may be null for manually added places |
sub_type | string(100) | Place category |
order | integer | Position within the day’s itinerary |
notes | text | Visit-specific notes |
visit_time | time | Planned arrival time |
visit_time_end | time | Planned departure time |
places
places
User-submitted places not yet in OpenStreetMap, subject to admin moderation. New submissions start as
pending; admins approve them via the Flask-Admin interface, after which they appear on the map as the community-approved layer.| Column | Type | Notes |
|---|---|---|
id | integer | Primary key |
name | string(200) | Place name |
longitude | float | WGS 84 longitude |
latitude | float | WGS 84 latitude |
sub_type | string(100) | Place category |
place_label | string(100) | Full formatted address label |
created_by | integer | FK → users.id — submitter |
status | string(20) | 'pending' (awaiting review) or 'approved' (visible to all users) |
created_at | datetime | Submission timestamp |
Alembic Migration Workflow
The full migration lifecycle for a typical model change looks like this:db commands directly: