ATTrails is an ATProto app for sharing cycling, running, and hiking routes. It helps you share your collection and discover routes uploaded by others.
- Share your routes as GPX files with the world
- Discover routes shared by others
- Review and curate your personal collection of favorite routes
The ATTrails website is a Next.js based application. It uses modern React features including server actions, suspense, and cache components. It is deployed as a self-hosted Docker container.
The Next.js application is also the "backend" for the website, using server actions, API routes and server-side data fetching.
The ingestion layer for ATTrails is built in Rust, fed by an ATProto Tap instance.
ingest-watcher: Watches relevant collections from an ATProto Tap instance and enqueues any new events for processing.ingest-consumer: Processes events produced byingest-watcherto transform and index routes, reviews, profiles, and GPX files, and labels them for moderation.lifecycle: Shared liveness/readiness probes and graceful shutdown signal handling.amqp: Manages RabbitMQ connection and queues.database: Manages the Postgres connection pool and schema migrations.lexicons: Generated code for lexicons relevant to this app.moderation: Infers moderation labels for records using the Jev decisions API.tracks: Parses GPX files into track summaries, paths, and elevation profiles.
- Postgres + PostGIS for persistence
- RabbitMQ for messaging and job queues
- ATProto Tap for watching the Firehose
- Next.js web app for users
- Jev for moderation
Copy .env.example to .env and run mise install to get the toolchain.
compose.yaml runs Postgres (with PostGIS), RabbitMQ, and Tap. The watcher and consumer profiles add a containerised ingest-watcher or ingest-consumer, so the service being worked on can run with cargo run while compose provides the rest. Run mise run db:migrate once Postgres is up to create the schema.
ingest-consumer labels records with the Jev decisions API, which needs an ATTRAILS_DECISIONS_API_KEY.
docker compose --profile consumer up: Work oningest-watcher(compose runs Postgres, RabbitMQ, Tap, andingest-consumer)docker compose --profile watcher up: Work oningest-consumer(compose runs Postgres, RabbitMQ, Tap, andingest-watcher)docker compose up: Run only Postgres, RabbitMQ, and Tapdocker compose --profile watcher --profile consumer up: Run everything in containersdocker compose --profile web up web: Run the web frontend in a container on port 3000
Add --build to rebuild the service images after code changes.
Migrations live in crates/database/migrations and run with sqlx-cli, installed via mise install. The target database is ATTRAILS_POSTGRES_CONNECTION_URI from .env.
mise run db:migrate:check: List pending migrations without applying themmise run db:migrate: Apply pending migrationssqlx migrate add <name>(fromcrates/database): Create a new migration
Never edit a migration after it has been applied; add a new one instead.
Licensed under either of
- Apache License, Version 2.0 (LICENSE-APACHE or https://www.apache.org/licenses/LICENSE-2.0)
- MIT license (LICENSE-MIT or https://opensource.org/licenses/MIT)
at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this project by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.
