Discover F3 workout locations near you. Find a free, outdoor, peer-led workout group for men today.
Live site: https://f3near.me
- Frontend: Ionic/Angular 14 PWA
- Backend: Firebase (Firestore, Cloud Functions, Cloud Storage)
- Data source: F3 Nation API — workout events and locations
- Hosting: Firebase Hosting
- Project ID:
f3-workout
- The F3 Nation API is the source of truth for workout events and locations
- Cloud Functions sync data from the API into Firestore (hourly scheduled + webhook-triggered)
- A JSON cache (
all.json) is generated from Firestore and stored in Cloud Storage - The Angular app loads workout data from the JSON cache (with Firestore as fallback)
| Function | Trigger | Description |
|---|---|---|
scheduledSyncAllBeatdowns |
Pub/Sub (hourly) | Full sync from F3 API to Firestore |
scheduledRegenerateJsonCache |
Pub/Sub (hourly) | Regenerate all.json from Firestore |
mapWebhook |
HTTP POST | Processes webhook notifications for real-time updates |
adminSyncAllBeatdowns |
Callable | Manually trigger full sync (supports dryRun) |
localTriggerSync |
HTTP GET | Emulator-only endpoint to trigger sync |
adminRegenerateJsonCache |
Callable | Manually regenerate JSON cache |
adminGetAllLocationIds |
Callable | List all location IDs from API |
adminUpdateSingleLocation |
Callable | Update a single location |
adminRefreshSpecificLocations |
Callable | Refresh a batch of locations |
adminRerunWebhooks |
Callable | Re-process webhooks after a date |
- Node.js 20.x (
node -vto verify) - Java 11+ (required by Firebase emulators —
sudo apt install openjdk-21-jre-headless) - Firebase CLI (
npm install -g firebase-tools) — must be on PATH; the emulator scripts callfirebasedirectly - Ionic CLI (
npm install -g @ionic/cli) - Firebase service account — place at
functions/service-account.json(gitignored)
# Clone and install
git clone <repo-url> && cd f3nearme
npm install
cd functions && npm install && cd .. # functions has its own node_modules
# Configure local environment for Cloud Functions
cp functions/.env.example functions/.env
# Edit functions/.env and fill in F3_API_KEY and other values.
# Note: .env IS deployed to production — do not put secrets you want local-only here.
# Use .env.local for local-only overrides (it is NOT deployed).
# Create .runtimeconfig.json so functions.config() works in local scripts (sync, emulator)
# This is separate from .env — Firebase's functions.config() reads from this file locally.
cat > functions/.runtimeconfig.json << 'EOF'
{
"f3": {
"api_key": "YOUR_ACTUAL_API_KEY",
"client": "f3nearme"
}
}
EOF
# Place your Firebase service account key
# Download from: Firebase Console > Project Settings > Service Accounts > Generate new private key
cp /path/to/your/service-account.json functions/service-account.json# Build functions + start all emulators (Firestore, Functions, Storage, Hosting, Pub/Sub)
npm run emulator
# Emulator UI: http://localhost:4000
# Functions: http://localhost:5001
# Firestore: http://localhost:8080
# Hosting: http://localhost:5000
# Storage: http://localhost:9199The emulator persists data between runs in emulator-data/. To start with an empty database:
npm run emulator:empty # starts without importing previous data (but still exports on exit)Ctrl-C in the emulator terminal is the cleanest way — it exports data before shutting down.
Check if the emulator is still running:
curl -s http://localhost:8080 >/dev/null 2>&1 && echo "Running" || echo "Stopped"If the terminal is gone or the emulator is stuck, kill the processes by port:
lsof -ti :5001 -ti :8080 -ti :8085 -ti :4000 | xargs -r kill -9
rm -f /tmp/hub-f3-workout.json # clean up stale hub fileIn a separate terminal:
npm start # ng serve on http://localhost:4200To connect the Angular app to the emulators, set useEmulators: true in src/environments/environment.ts.
You only need to do this once. After the initial seed, the emulator auto-exports its
data on exit to emulator-data/, and npm run emulator auto-imports it on startup.
# Step 1: Export from production Firestore (one-time, requires service-account.json)
node scripts/export-prod-data.js
# Step 2: Start emulator, seed it, and keep running (one command)
npm run emulator:seedThe emulator:seed command starts a fresh emulator, waits for Firestore to be ready,
imports the seed data, and keeps running. Once it finishes seeding you'll see:
[seed] Done! Emulator is seeded and running. Press Ctrl-C to stop.
From now on, just use npm run emulator — your data will persist automatically.
Scheduled functions (Pub/Sub triggers) can't be invoked from the Emulator UI directly. Instead, use the local-only HTTP endpoints that are automatically available when the emulator is running:
# Trigger full sync (syncAllBeatdowns + generateJsonCache)
curl http://localhost:5001/f3-workout/us-central1/localTriggerSync
# Dry run — see what would change without writing to Firestore
curl "http://localhost:5001/f3-workout/us-central1/localTriggerSync?dryRun=true"
# Trigger only JSON cache regeneration
curl http://localhost:5001/f3-workout/us-central1/localTriggerJsonCacheThese endpoints only exist in the emulator — they are not deployed to production.
You can run your local sync logic against the real production Firestore without deploying.
This requires functions/service-account.json.
# Dry run — preview what would change (safe, read-only)
npm run sync:dry
# Live — actually write changes (prompts for confirmation)
npm run sync:liveExample dry-run output:
============================================================
🔍 DRY RUN — no changes will be written to Firestore
📦 Project: f3-workout
============================================================
[SYNC] Found 7742 existing beatdowns in Firestore
[SYNC] Found 6529 events in API
[SYNC] Found 67 beatdowns to write (0 new, 0 updated, 67 undeleted, 6364 unchanged)
[SYNC] DRY RUN completed in 5867ms
{ newBeatdowns: 0, updatedBeatdowns: 0, undeletedBeatdowns: 67, ... dryRun: true }
You can also trigger syncs from the admin page (/admin) in the deployed app.
-
Start the emulator with debug mode:
npm run emulator:debug
This starts the functions runtime with
--inspecton port 9229. -
In VS Code, open the Run and Debug panel and select "Attach to Functions Emulator".
-
Set breakpoints in
functions/src/index.ts(e.g., insidesyncAllBeatdowns). -
Trigger the function:
curl http://localhost:5001/f3-workout/us-central1/localTriggerSync
-
The debugger will pause at your breakpoints. You can step through the sync logic, inspect variables, etc.
# Deploy only functions (most common)
firebase deploy --only functions
# Deploy everything (hosting + functions)
npm run build:prod && firebase deploy
# Deploy only hosting (also triggered automatically on push to main via GitHub Actions)
npm run build:prod && firebase deploy --only hostingNote: Hosting is also auto-deployed via GitHub Actions on any push to
mainthat changes files undersrc/. Functions must be deployed manually.
API credentials are set via Firebase Functions config (not committed to repo):
firebase functions:config:set f3.api_key="YOUR_API_KEY"
firebase functions:config:set f3.client="f3nearme"├── functions/ # Firebase Cloud Functions
│ ├── src/
│ │ └── index.ts # All function definitions + sync logic
│ ├── .env # Env vars deployed to production Cloud Functions (gitignored)
│ ├── .env.local # Local-only overrides — NOT deployed (gitignored)
│ ├── .env.example # Template for .env / .env.local
│ ├── .runtimeconfig.json # Emulator functions.config() (gitignored)
│ ├── service-account.json # Firebase Admin credentials (gitignored)
│ ├── package.json
│ └── tsconfig.json
├── src/ # Angular/Ionic frontend
│ ├── app/
│ │ ├── pages/
│ │ │ ├── admin/ # Admin page for manual sync controls
│ │ │ ├── nearby/ # Main map/list view
│ │ │ └── workout/ # Individual workout detail
│ │ ├── services/
│ │ │ ├── beatdown.service.ts # Data fetching (JSON cache + Firestore fallback)
│ │ │ └── http.service.ts
│ │ └── pipes/
│ ├── environments/ # Angular environment configs
│ └── assets/
├── scripts/ # Local development utility scripts
│ ├── export-prod-data.js # Export prod Firestore to seed file
│ ├── run-sync.js # Run sync against prod without deploying
│ ├── seed-emulator.js # Import seed data into running emulator
│ └── start-and-seed.js # One-command emulator start + seed
├── .vscode/
│ └── launch.json # VS Code debug configurations
├── firebase.json # Firebase config (hosting, functions, emulators)
├── firestore.rules
└── firestore.indexes.json
