Skip to content

looorent/broulette

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

250 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

BiteRoulette

A location-based React application that helps users find the perfect dining spot based on their current location, time preference, and distance range.

Overview

This application acts as a randomization engine for restaurants. It aggregates restaurant data from various external sources to provide a unified profile. Users can search for dining options based on specific criteria (Dinner, Lunch, "Right Now") and interact with the results.

Tech Stack

  • Framework: React Router 7 (framework mode)
  • Runtime: Cloudflare Workers
  • Database: Cloudflare D1 (SQLite)
  • ORM: Drizzle
  • Styling: Tailwind CSS 4
  • Language: TypeScript 5.9 (strict mode)

Prerequisites

  • Node.js >= 22.0.0
  • npm
  • Cloudflare account (for deployment)

Getting Started

1. Install Dependencies

npm install

2. Set Up Environment Variables

Create a .dev.vars file with the required secrets:

BROULETTE_SESSION_SECRET=<generate with: openssl rand -hex 32>
BROULETTE_GOOGLE_PLACE_API_KEY=<your-google-api-key>
BROULETTE_TRIPADVISOR_API_KEY=<your-tripadvisor-api-key>

3. Initialize the Database

npm run db:migrate:local

4. Run the Application

npm run dev

The application will be available at http://localhost:5173.

Testing

Run All Tests

npm test

Run Tests in Watch Mode

npm run test:watch

Run Tests with Coverage

npm run test:coverage

Coverage reports are generated in the coverage/ directory.

Run Specific Test Files

npm test -- --run src/features/session.server/

Test Structure

Tests are co-located with their source files using the .test.ts suffix:

src/features/
  session.server/
    csrf.ts
    csrf.test.ts      # Unit tests for CSRF
    session.ts
    session.test.ts   # Unit tests for session
src/routes/
  _.api.address-searches.ts
  _.api.address-searches.test.ts  # Route handler tests

Deployment

Deploy to Cloudflare Workers

npm run deploy

Production Database Migration

npm run db:migrate:prod

Setting Production Secrets

npx wrangler secret put BROULETTE_SESSION_SECRET
npx wrangler secret put BROULETTE_GOOGLE_PLACE_API_KEY
npx wrangler secret put BROULETTE_TRIPADVISOR_API_KEY

Preview Build Locally

npm run build
npm run preview

Database Management

This project uses Drizzle for database management.

Update Schema

If you modify schema.ts, generate migrations:

npm run db:gen

Apply Migrations

# Local
npm run db:migrate:local

# Production
npm run db:migrate:prod

Database Studio

# Local database UI
npm run db:studio:local

# Production database UI
npm run db:studio:prod

Manual Database Backup

Export from D1:

npx wrangler d1 export broulette-eu --remote --no-data --output=./schema.sql
npx wrangler d1 export broulette-eu --remote --no-schema --output=./data.sql

Import to D1:

npx wrangler d1 execute broulette-eu --remote --file=./schema.sql
npx wrangler d1 execute broulette-eu --remote --file=./data.sql

Note: Data exports may have foreign key ordering issues. Consider disabling foreign key checks during import.

Environment Variables

Required Variables

Variable Description Required
BROULETTE_SESSION_SECRET Secret for signing session cookies (generate with openssl rand -hex 32) Yes

Service Enable Flags

Variable Default Description
BROULETTE_NOMINATIM_ENABLED false Enable Nominatim for address search
BROULETTE_PHOTON_ENABLED false Enable Photon for address search
BROULETTE_OVERPASS_ENABLED false Enable Overpass for restaurant discovery
BROULETTE_GOOGLE_PLACE_ENABLED false Enable Google Places for restaurant details
BROULETTE_TRIPADVISOR_ENABLED false Enable TripAdvisor for restaurant details

API Keys (Secrets)

Variable Description
BROULETTE_GOOGLE_PLACE_API_KEY Google Places API key
BROULETTE_TRIPADVISOR_API_KEY TripAdvisor API key

Nominatim Configuration

Variable Default Description
BROULETTE_NOMINATIM_INSTANCE_URLS https://nominatim.openstreetmap.org/search Comma-separated list of instance URLs
BROULETTE_NOMINATIM_USER_AGENT BiteRoulette/<version> User agent for API requests
BROULETTE_NOMINATIM_NUMBER_OF_ADDRESSES 5 Maximum addresses to return
BROULETTE_NOMINATIM_API_TIMEOUT 5000 Request timeout in milliseconds
BROULETTE_NOMINATIM_API_RETRIES 3 Number of retries on failure

Photon Configuration

Variable Default Description
BROULETTE_PHOTON_INSTANCE_URLS https://photon.komoot.io/api/ Comma-separated list of instance URLs
BROULETTE_PHOTON_NUMBER_OF_ADDRESSES 5 Maximum addresses to return
BROULETTE_PHOTON_API_TIMEOUT 5000 Request timeout in milliseconds
BROULETTE_PHOTON_API_RETRIES 3 Number of retries on failure

Google Places Configuration

Variable Default Description
BROULETTE_GOOGLE_PLACE_BASE_URL https://places.googleapis.com API base URL
BROULETTE_GOOGLE_PLACE_API_SEARCH_RADIUS_IN_METERS 100 Search radius for matching
BROULETTE_GOOGLE_PLACE_API_PHOTO_MAX_WIDTH_IN_PX 800 Maximum photo width
BROULETTE_GOOGLE_PLACE_API_PHOTO_MAX_HEIGHT_IN_PX 600 Maximum photo height
BROULETTE_GOOGLE_PLACE_API_MAX_NUMBER_OF_ATTEMPTS_PER_MONTH 10000 Rate limiting threshold

TripAdvisor Configuration

Variable Default Description
BROULETTE_TRIPADVISOR_INSTANCE_URL https://api.content.tripadvisor.com/api/v1 API base URL
BROULETTE_TRIPADVISOR_API_SEARCH_RADIUS_IN_METERS 100 Search radius for matching
BROULETTE_TRIPADVISOR_API_PHOTO_SIZE medium Photo size (small, medium, large)
BROULETTE_TRIPADVISOR_API_MAX_NUMBER_OF_ATTEMPTS_PER_MONTH 5000 Rate limiting threshold

Overpass Configuration

Variable Default Description
BROULETTE_OVERPASS_API_INSTANCE_URLS Multiple public instances Comma-separated list of instance URLs
BROULETTE_OVERPASS_API_TIMEOUT 5000 Request timeout in milliseconds
BROULETTE_OVERPASS_API_RETRIES 3 Number of retries on failure

Search Engine Configuration

Variable Default Description
BROULETTE_SEARCH_ENGINE_DISCOVERY_RANGE_INCREASE_METERS 500 Range increase per iteration
BROULETTE_SEARCH_ENGINE_MAX_DISCOVERY_ITERATIONS 10 Maximum discovery iterations
BROULETTE_SEARCH_ENGINE_CLOSE_RANGE_IN_METERS 1500 "Close" distance range
BROULETTE_SEARCH_ENGINE_CLOSE_TIMEOUT_IN_MS 30000 Timeout for close searches
BROULETTE_SEARCH_ENGINE_MID_RANGE_IN_METERS 5000 "Mid-range" distance
BROULETTE_SEARCH_ENGINE_MID_TIMEOUT_IN_MS 45000 Timeout for mid-range searches
BROULETTE_SEARCH_ENGINE_FAR_RANGE_IN_METERS 15000 "Far" distance range
BROULETTE_SEARCH_ENGINE_FAR_TIMEOUT_IN_MS 60000 Timeout for far searches

Tag Configuration

Variable Default Description
BROULETTE_TAGS_TO_EXCLUDE (empty) Comma-separated tags to hide
BROULETTE_TAGS_TO_PRIORITIZE (empty) Comma-separated tags to show first
BROULETTE_TAGS_MAXIMUM 5 Maximum tags to display

Data Sources

Address Search

  • Nominatim - OpenStreetMap's geocoding service
  • Photon - Komoot's geocoding service (faster, location-biased)

Restaurant Discovery

  • Overpass - OpenStreetMap query API for finding nearby restaurants

Restaurant Details

  • Google Places - Rich details, photos, ratings
  • TripAdvisor - Reviews, photos, cuisine details

Architecture

Resilience Patterns

The application implements several resilience patterns:

  • Circuit Breaker: Prevents cascading failures when services are unavailable
  • Load Balancer: Round-robin distribution across multiple service instances
  • Retry with Exponential Backoff: Automatic retries with increasing delays
  • Timeout: Configurable request timeouts per service

Server/Client Separation

Files are organized with clear boundaries:

  • .server.ts - Server-only code (API calls, database, secrets)
  • .client.ts - Client-only code (DOM, geolocation, haptics)

Troubleshooting

Common Issues

"Location access denied" on mobile

Ensure the site is served over HTTPS. Geolocation API requires a secure context.

Address suggestions not appearing

  1. Verify BROULETTE_NOMINATIM_ENABLED or BROULETTE_PHOTON_ENABLED is true
  2. Check browser console for network errors
  3. Verify the service instances are accessible

"Unable to fetch addresses" error

This indicates a service failure. Check:

  1. API service availability
  2. Network connectivity
  3. Rate limiting (check API quotas)

Search returns no restaurants

  1. Verify BROULETTE_OVERPASS_ENABLED is true
  2. Try a different location (some areas have sparse OSM data)
  3. Increase the search range in preferences

Restaurant details missing photos

  1. Verify BROULETTE_GOOGLE_PLACE_ENABLED or BROULETTE_TRIPADVISOR_ENABLED is true
  2. Check API key validity
  3. Check rate limiting quotas

Database migration fails

For production:

# Check current migration status
npx wrangler d1 execute broulette-eu --remote --command="SELECT * FROM __drizzle_migrations"

# Manually apply migrations if needed
npx wrangler d1 execute broulette-eu --remote --file=./drizzle/migrations/XXXX_migration.sql

Build fails with TypeScript errors

# Check types
npx tsc --noEmit

# Clear build cache
rm -rf node_modules/.vite
npm run build

Debug Logging

Enable detailed logging by checking the browser console or Cloudflare Workers logs:

# Stream production logs
npx wrangler tail

Next steps?

  • Haptics do not work on Android?
  • Move all the code about "cache" to a dedicated module?

About

A location-based React application that helps users find the perfect dining spot based on their current location, time preference, and distance range.

Resources

Stars

Watchers

Forks

Contributors

Languages