Skip to content

CelestialBrain/wheresthefx

Repository files navigation

WheresTheFX

Metro Manila Event Discovery Platform β€” Find parties, thrift markets, live music, food events, and more on an interactive map.

Production: www.wheresthefx.com | API: api.wheresthefx.com

Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    blead (VPS)                   β”‚
β”‚  Playwright + Gemini AI β†’ SQLite (ig-events.db)  β”‚
β”‚  Scrapes ~157 Instagram accounts daily           β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                  β”‚ ingest script (every 6hrs)
                  β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚              WheresTheFX Server                  β”‚
β”‚  Express.js + Drizzle ORM + PostgreSQL           β”‚
β”‚  REST API: /api/events, /api/venues, etc.        β”‚
β”‚  Image proxy for Instagram CDN fallback          β”‚
β”‚  JWT auth                                        β”‚
β”‚  Port: 3001                                      β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                  β”‚ fetch API
                  β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚              WheresTheFX Client                  β”‚
β”‚  React + Vite + TypeScript + Tailwind            β”‚
β”‚  Leaflet dark-themed map                         β”‚
β”‚  Category filters, search, date/price filters    β”‚
β”‚  Math verification landing page                  β”‚
β”‚  Port: 5173                                      β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Quick Start

Prerequisites

  • Node.js 18+
  • PostgreSQL (OrbStack or local)
  • blead repo at ../blead (for ingest)

Setup

# 1. Create PostgreSQL database
psql -c "CREATE DATABASE wheresthefx;"

# 2. Install server deps + push schema
cd server
npm install
./node_modules/.bin/drizzle-kit push

# 3. Run ingest from blead
BLEAD_DB_PATH=../blead/data/ig-events.db \
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/wheresthefx \
npx tsx src/scripts/ingest-from-blead.ts

# 4. Start server
npx tsx src/index.ts

# 5. Install frontend deps + start
cd ..
npm install
npm run dev

Environment Variables

Copy .env.example to .env and configure:

Variable Description Default
DATABASE_URL PostgreSQL connection string postgresql://postgres:postgres@localhost:5432/wheresthefx
JWT_SECRET Secret for JWT token signing your-secret-key-change-in-production
BLEAD_DB_PATH Path to blead's SQLite database ../blead/data/ig-events.db
BLEAD_IMAGE_DIR Path to blead's cached images ../blead/data/ig-images
PORT Server port 3001
CORS_ORIGIN Allowed CORS origin http://localhost:5173
VITE_API_URL API base URL (frontend) http://localhost:3001

Project Structure

wheresthefx/
β”œβ”€β”€ docs/                       # Documentation
β”‚   β”œβ”€β”€ CONVENTIONS.md           # Code/database/API conventions
β”‚   β”œβ”€β”€ api.md                   # API reference
β”‚   β”œβ”€β”€ schema.md                # Database schema reference
β”‚   └── ingest.md                # Ingest pipeline docs
β”œβ”€β”€ server/                      # Express.js backend
β”‚   β”œβ”€β”€ src/
β”‚   β”‚   β”œβ”€β”€ db/
β”‚   β”‚   β”‚   β”œβ”€β”€ schema.ts        # Drizzle ORM schema (9 tables)
β”‚   β”‚   β”‚   β”œβ”€β”€ connection.ts    # PostgreSQL connection
β”‚   β”‚   β”‚   └── migrate.ts       # Migration runner
β”‚   β”‚   β”œβ”€β”€ middleware/
β”‚   β”‚   β”‚   β”œβ”€β”€ auth.ts          # JWT auth middleware
β”‚   β”‚   β”‚   └── snakeCase.ts     # camelCase β†’ snake_case response transform
β”‚   β”‚   β”œβ”€β”€ routes/
β”‚   β”‚   β”‚   β”œβ”€β”€ auth.ts          # Register, login, me
β”‚   β”‚   β”‚   β”œβ”€β”€ events.ts        # Event CRUD + map/upcoming
β”‚   β”‚   β”‚   β”œβ”€β”€ venues.ts        # Venue directory
β”‚   β”‚   β”‚   β”œβ”€β”€ categories.ts    # Category metadata
β”‚   β”‚   β”‚   └── users.ts         # Saved events, preferences
β”‚   β”‚   β”œβ”€β”€ scripts/
β”‚   β”‚   β”‚   β”œβ”€β”€ ingest-from-blead.ts   # SQLite β†’ PG sync
β”‚   β”‚   β”‚   └── geocode-venues.ts      # Venue geocoding
β”‚   β”‚   └── index.ts             # Express server entry
β”‚   β”œβ”€β”€ drizzle.config.ts
β”‚   └── package.json
β”œβ”€β”€ src/                         # React frontend (Vite)
β”‚   β”œβ”€β”€ api/
β”‚   β”‚   └── client.ts            # API client (snake_case types)
β”‚   β”œβ”€β”€ components/              # React components
β”‚   β”‚   β”œβ”€β”€ EventMap.tsx          # Leaflet map
β”‚   β”‚   β”œβ”€β”€ EventSidebar.tsx      # Sidebar with nearby events
β”‚   β”‚   β”œβ”€β”€ EventPopup.tsx        # Mobile event popup
β”‚   β”‚   β”œβ”€β”€ EventSidePanel.tsx    # Desktop event panel
β”‚   β”‚   β”œβ”€β”€ MapFilters.tsx        # Search, date, price filters
β”‚   β”‚   β”œβ”€β”€ CategoryFilter.tsx    # Category chip filters
β”‚   β”‚   └── ui/                   # shadcn/ui components
β”‚   β”œβ”€β”€ hooks/                    # React hooks
β”‚   β”‚   β”œβ”€β”€ useEventMarkers.ts    # Fetch + cluster events
β”‚   β”‚   β”œβ”€β”€ useSavedEvents.ts     # User's saved events
β”‚   β”‚   └── useUserPreferences.ts
β”‚   β”œβ”€β”€ pages/
β”‚   β”‚   β”œβ”€β”€ Index.tsx             # Landing + map view
β”‚   β”‚   β”œβ”€β”€ Auth.tsx              # Login/register
β”‚   β”‚   └── Admin.tsx             # Admin dashboard
β”‚   └── utils/
β”‚       └── markerUtils.ts        # Map marker clustering
β”œβ”€β”€ .agents/workflows/            # Agent workflow definitions
β”œβ”€β”€ .env.example
└── package.json

Data Pipeline

Source: blead

blead is a separate system that scrapes Instagram daily:

  • 157 accounts tracked (venues, promoters, organizers in Metro Manila)
  • Playwright headless browser extracts posts
  • Gemini AI classifies posts as events and extracts structured data
  • Stores everything in SQLite at data/ig-events.db

Ingest: SQLite β†’ PostgreSQL

The ingest-from-blead.ts script syncs data:

  1. Venues β€” matched by name (UNIQUE)
  2. Accounts β€” matched by username (UNIQUE)
  3. Posts β€” matched by shortcode (UNIQUE)
  4. Events β€” matched by event_hash (SHA256 of title+date+venue)
  5. Sub-events β€” delete + re-insert per parent event

Run every 6 hours via cron on the VPS:

0 */6 * * * cd /path/to/wheresthefx/server && npx tsx src/scripts/ingest-from-blead.ts

Current Data Stats (March 2026)

Table Count
Venues 118
Accounts 157
Posts 1317
Events 230
Sub-events 178
Geocoded 194

API Reference

All endpoints are prefixed with /api. All responses use snake_case keys. See docs/api.md for full reference.

Auth

Method Path Description Auth
POST /auth/register Create account No
POST /auth/login Login, get JWT No
GET /auth/me Current user Yes

Events

Method Path Description Auth
GET /events List events (paginated) Optional
GET /events/upcoming Future events No
GET /events/map Events with geocoding No
GET /events/:id Event detail Optional

Query params: category, search, is_free, date_from, date_to, page, limit

Venues

Method Path Description Auth
GET /venues All venues No
GET /venues/:id Venue detail No
GET /venues/:id/events Events at venue No

Categories

Method Path Description Auth
GET /categories Categories with counts No

User

Method Path Description Auth
GET /users/me/saved Saved events Yes
POST /users/me/saved Toggle save event Yes
GET /users/me/preferences Get preferences Yes
PUT /users/me/preferences Update preferences Yes

Image Proxy

Method Path Description
GET /images/proxy?url=...&shortcode=... Proxy Instagram images

Database Schema

9 tables connected via foreign keys:

  • venue β€” Canonical venue directory with geocoding
  • source_account β€” Instagram accounts being tracked
  • source_post β€” Raw IG posts with AI extraction data
  • event β€” Core event data with event_hash dedup
  • sub_event β€” Multi-act lineups and schedules
  • account β€” Platform user accounts (bcrypt passwords)
  • saved_event β€” User bookmarks (many-to-many)
  • account_preference β€” Preferred categories
  • sync_log β€” Ingest run tracking

Key design decisions:

  • No dependency on blead's internal IDs
  • event_hash (SHA256) for deduplication
  • Denormalized venue_name/source_username for query performance
  • JSONB arrays for artists
  • API responses auto-converted to snake_case by snakeCaseResponse middleware

Deployment

VPS (Express API)

# Sync server code to VPS
rsync -avz --delete --exclude='node_modules' --exclude='.env' --exclude='drizzle' \
  server/ root@217.216.72.28:/root/wheresthefx-api/

# Restart on VPS
ssh root@217.216.72.28 "pm2 restart wheresthefx-api"
  • API: https://api.wheresthefx.com (port 3001 behind Caddy reverse proxy)
  • SSL: Let's Encrypt via Caddy
  • Process manager: PM2

Vercel (Frontend)

# Deploy to production
vercel --prod --yes
  • Frontend: https://www.wheresthefx.com
  • Env: VITE_API_URL=https://api.wheresthefx.com
  • DNS: Namecheap BasicDNS β†’ Vercel

Tech Stack

Layer Technology
Frontend React 18, Vite, TypeScript, Tailwind CSS
UI Components shadcn/ui (Radix primitives)
Map Leaflet with custom markers
Backend Express.js, TypeScript
ORM Drizzle ORM
Database PostgreSQL (OrbStack)
Auth JWT (jsonwebtoken + bcryptjs)
Data Source blead (Playwright + Gemini AI β†’ SQLite)

About

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages