ChurchMap is a location based church discovery app for searching churches by city/state, viewing them on a map, and reading or leaving reviews
翻譯準備中
正在載入完整 README。
檢視完整 README ↗Find your church, rated on what actually matters.
ChurchMap is a church discovery app for searching churches by city/state, viewing them on a map, and reading or leaving reviews across six fit dimensions: worship energy, community warmth, sermon depth, children's programs, theological openness, and facilities.
The repo is named church_map (formerly holyhub); the product UI is branded as ChurchMap.
| Area | Used in this repo |
|---|---|
| Backend | FastAPI (async), Uvicorn, psycopg 3 + AsyncConnectionPool, Pydantic, HTTPX |
| Database | Supabase Postgres + pgvector, transaction pooler on port 6543 |
| Frontend | React 18, Vite, React Router, Leaflet, React Leaflet |
| Styling | Plain CSS in frontend/src/index.css, Fraunces (serif) + Plus Jakarta Sans |
| Auth | Google Identity Services in the browser, Google tokeninfo verification in FastAPI |
| Enrichment | Google Places API (lazy, capped at 10,800 calls/month) |
| Location | ipapi.co for first-visit city/state detection |
| Hosting | Vercel (frontend) + Render Web Service Starter (backend) |
| Migrations | Numbered migrations/*.sql + backend/db/migrate.py runner |
| Tests | pytest (17 active, parity tests gated on DATABASE_URL) |
city + state, or by zip_code through the API/church/:idGOOGLE_PLACES_KEY is configuredPrerequisites:
git clone https://github.com/zhou100/church_map.git
cd church_map
# Backend
python -m venv venv && source venv/bin/activate
pip install -r requirements.txt
# Frontend
cd frontend && npm install && cd ..
Set environment variables (see next section), then apply schema migrations:
export DATABASE_URL='postgresql://postgres.xxx:PWD@aws-0-...:6543/postgres'
python -m backend.db.migrate
# applies migrations/0001_initial.sql, 0002_pgvector.sql, 0003_indexes.sql
The data load is a one-shot from a SQLite snapshot (used during the Phase A cutover; for fresh local dev you start with an empty schema and seed via the API).
Backend:
DATABASE_URL=postgresql://postgres.xxx:[email protected]:6543/postgres
GOOGLE_CLIENT_ID=your-google-oauth-client-id
GOOGLE_PLACES_KEY=your-google-places-api-key
ENV=development # set to "production" on Render
READ_ONLY=0 # flip to 1 during cutover windows
DB_POOL_MAX=10 # psycopg pool max size
Frontend (.env.local or Vercel project env):
VITE_API_URL=http://localhost:8000
VITE_GOOGLE_CLIENT_ID=your-google-oauth-client-id
Notes:
DATABASE_URL must use the transaction pooler port (6543), not the direct port (5432). Direct exhausts the 60-conn limit under FastAPI workers.GOOGLE_CLIENT_ID is optional on the backend, but when set it verifies that Google ID tokens were issued for the expected client.VITE_GOOGLE_CLIENT_ID is required for the review form to show Google Sign-In.GOOGLE_PLACES_KEY is optional. Without it, enrichment endpoints return cached data if present or skip safely.# Terminal 1: backend at http://localhost:8000
uvicorn backend.main:app --reload
# Terminal 2: frontend at http://localhost:5173
cd frontend && npm run dev
Open http://localhost:5173. Try Brooklyn, NY if location detection does not find local results.
pytest -q # unit + static checks (17 active)
DATABASE_URL=... pytest tests/test_parity.py # endpoint parity against Postgres
tests/test_no_sqlite_in_routes.py is a CI grep that fails if anything reintroduces sqlite3.connect, ? placeholders, or .lastrowid into runtime route code.
GET /api/health
GET /api/stats
GET /api/churches?city=Brooklyn&state=NY&limit=50&offset=0
GET /api/churches?city=Brooklyn&state=NY&language=Spanish&worship_style=liturgical&stance=traditional
GET /api/churches?zip_code=11201&limit=50&offset=0
GET /api/churches/{church_id}
GET /api/churches/{church_id}/similar
POST /api/churches/{church_id}/enrich
GET /api/reviews/{church_id}
POST /api/reviews
POST /api/auth/verify
GET /api/stats reports corpus and crawl-pipeline health — how many churches
have a website, how many are extracted, which prompt version and model they
were extracted under, why extraction attempts ended, and how recently each
crawl stage did any work. Aggregates only, so it needs no token, and cached
in-process for 5 minutes.
Read crawl.pipeline_ok and crawl.stages[*].stale, not crawl.runs.error.
A stage that dies before it can write its crawl_runs row — a database it
can't reach, say — contributes no error at all, so an error count of zero is
not evidence of health. Age since the last progress is the signal an absent
row can't fake. A stage that has never made progress reports null and counts
as stale rather than being omitted; the crawl going quiet is the failure this
is meant to make visible (it was auto-disabled for 8 days in July 2026 and
nothing said so).
以上為 README 的開頭部分。其餘內容請閱讀原文。 檢視完整 README ↗