Evently is a platform that scrapes event data from multiple Moroccan sources, geocodes each event venue using Nominatim (OpenStreetMap), enriches each event with nearby bus and tramway transport info, indexes everything into Elasticsearch, and serves it through a FastAPI backend with a frontend UI.
Scraping and indexing are automated on a daily scheduled cron job at midnight, with Slack alerts on failure.
geopy| Method | Endpoint | Description |
|---|---|---|
| GET | / |
Frontend homepage |
| GET | /events?page=X&size=Y |
Paginated events |
| GET | /event/{event_id} |
Single event detail |
| GET | /search |
Search with filters (city, category, date) |
| GET | /transport/{event_id} |
Bus and tramway lines near an event |
| GET | /filters |
Available cities and categories |
| GET | /stats |
Total events, upcoming, cities, categories |
| GET | /trending |
Trending upcoming events |
| GET | /recommendations |
Similar events by category and city |
| POST | /favorites/{event_id} |
Add event to favorites |
| DELETE | /favorites/{event_id} |
Remove event from favorites |
| GET | /favorites |
Get favorite events |
| POST | /export |
Export events as JSON or CSV |
| GET | /reindex |
Manually trigger full reindex |
| GET | /health |
Elasticsearch health check |
evently_daily_scrapinghttp://localhost:8080Events_Project/
│
├── elastic/
│ ├── elastic_script.py # indexing + transport enrichment
│ └── elastic_client.py # Elasticsearch client setup
│
├── main.py # FastAPI routes + lifespan startup
│
├── static/
│ ├── script.js
│ └── style.css
│
├── templates/
│ └── index.html # Frontend UI
│
├── requirements.txt # FastAPI app dependencies
├── requirements.airflow.txt # Airflow container dependencies
├── .env # Environment variables (not committed)
│
├── Dockerfile # FastAPI container
├── Dockerfile.airflow # Airflow container with scraping deps
├── docker-compose.yml # All services
│
├── dags/
│ └── evently_scraping.py # Airflow DAG — daily scraping pipeline
│
├── scrape/ # Scrapers (run in parallel)
│ ├── casaevents.py
│ ├── eventbrit.py
│ ├── eventsma.py
│ └── guichet.py
│
├── struct_events/
│ ├── get_coords.py # Nominatim geocoding (venue → coordinates)
│ └── models.py # Events Pydantic model
│
├── transport/
│ ├── bus.py # KDTree transport lookup
│ ├── cleaned_lines/ # Preprocessed transport data (JSON)
│ └── overpass/ # Raw Overpass API data (one-time fetch)
git clone <repo_url>
cd Events_Project
.env fileELASTIC_PASSWORD=your_password_here
SLACK_WEBHOOK_URL=https://hooks.slack.com/services/XXX/YYY/ZZZ
SLACK_WEBHOOK_URLis required for Airflow failure alerts. Get it from https://api.slack.com/apps
docker compose up --build
This will start:
http://localhost:9200http://localhost:8000http://localhost:8080Wait about 60 seconds on first run for all services to initialize.
| Service | URL |
|---|---|
| Frontend | http://localhost:8000 |
| Airflow UI | http://localhost:8080 |
| Elasticsearch | http://localhost:9200 |
http://localhost:8080adminadmin123evently_daily_scraping in the DAGs list| Property | Value |
|---|---|
| DAG ID | evently_daily_scraping |
| Schedule | Daily at midnight (0 0 * * *) |
| Retries | 2 (5 min delay between retries) |
| Slack alert on success | ✅ |
| Slack alert on failure | ✅ |
# Check the UI
http://localhost:8000
# Check events API
http://localhost:8000/events
# Check health
http://localhost:8000/health
# Manually trigger reindex
http://localhost:8000/reindex
# Check Airflow
http://localhost:8080
Scrapers (parallel)
↓
Nominatim API — geocode venue name → coordinates
↓
KDTree spatial index — coordinates → nearest bus/tramway lines
(from precomputed local JSON, originally fetched from Overpass API)
↓
Elasticsearch — event + coordinates + transport lines indexed together
↓
FastAPI — serves events, search, filters, transport, favorites
↓
Frontend UI — search, modal, pagination, favorites, share
At indexing time, each event’s venue is geocoded to coordinates, which are used to query a KDTree built from all bus and tramway stop coordinates. The nearest lines within 0.5km are stored directly on the event document in Elasticsearch.
This means /transport/{event_id} is just a simple document read — no spatial computation or API call happens at request time.
# First run or after changing Dockerfile/requirements
docker compose up --build
# After changing only Python/JS/HTML files
docker compose up
# Full clean restart (wipes data volumes)
docker compose down -v
docker compose up --build
# View logs for a specific service
docker compose logs airflow-scheduler
docker compose logs fastapi