個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證A Simple map showing cats around the world
翻譯準備中
取自儲存庫 README
正在載入完整 README。 以下為已儲存的摘錄。
An anonymous-first world map for geotagging cat sightings. Anyone can drop a
cat pin with a photo + description and confirm other people's sightings — no
account required (device-token identity). Optionally, people can create an
account (email/password or Google) to sync cats and hearts across devices and
receive email notifications.檢視完整 README ↗An anonymous-first world map for geotagging cat sightings. Anyone can drop a cat pin with a photo + description and confirm other people's sightings — no account required (device-token identity). Optionally, people can create an account (email/password or Google) to sync cats and hearts across devices and receive email notifications.
Anonymity: each device generates a random token stored in localStorage. It's
sent as X-Device-Token and used only to prevent double-confirming and to
attribute (not identify) a post. No personal data is collected. Uploaded photos
have their EXIF metadata stripped before storage.
cp .env.example .env # values are fine as-is for local dev
docker compose up --build
The frontend image bakes
VITE_API_BASE=http://localhost:8000at build time (seedocker-compose.yml). If you change the backend port, rebuild the frontend image.
cd backend
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
# point DATABASE_URL at any Postgres instance
export DATABASE_URL=postgresql+psycopg://catmap:catmap@localhost:5432/catmap
uvicorn app.main:app --reload
cd frontend
npm install
npm run fetch-model # downloads YOLOv10n for client cat pre-check (~9 MB)
# talk to a backend on :8000
echo "VITE_API_BASE=http://localhost:8000" > .env.local
npm run dev
All endpoints are available under both /api/v1/... (canonical, versioned)
and /api/... (unversioned alias, kept for backward compatibility). New
clients should prefer /api/v1.
| Method | Path | Purpose |
|---|---|---|
| GET | /api/v1/sightings?min_lat&max_lat&min_lng&max_lng | Sightings in a bounding box — id, lat, lng, description, created_at, thumbnail_url, confirmations_count, stale (supports since, until, color, is_ear_tipped, is_stray, min_confidence, limit, offset) |
| GET | /api/v1/sightings/clusters?min_lat&max_lat&min_lng&max_lng&zoom | Grid-aggregated counts for zoomed-out views (no cap) |
| GET | /api/v1/sightings/recent?limit&offset&sort=recent|confirmed | Browse feed of active sightings |
| GET | /api/v1/sightings/mine | Your identity's sightings (device token; signed-in accounts include all linked devices) |
| POST | /api/v1/sightings | Create (multipart: images (1+ files, or legacy image), lat, lng, description, color, is_ear_tipped, is_stray, kind, cat_name, contact) |
| GET | /api/v1/sightings/{id}/comments | List tips/comments on a sighting |
| POST | /api/v1/sightings/{id}/comments | Add a comment/tip (optional lat/lng) |
| GET | /api/v1/notifications | In-app notification inbox (needs token) |
| POST | /api/v1/push/subscribe | Register Web Push or FCM token (needs token) |
| GET | /api/v1/hearts | List hearted sightings/cats for this identity |
| POST | /api/v1/hearts | Heart a sighting or cat (target_type, target_id) |
| DELETE |
POST/PATCH/confirm/report/gone/DELETE/mine require the
X-Device-Token header. Create, confirm, and report are rate-limited (see
RATE_LIMIT_* env vars). A sighting can have up to 6 photos. When
CAT_DETECTION_STRICT is enabled, a sighting whose photos don't score above
CAT_DETECTION_THRESHOLD (or that couldn't be scored at all) is created with
status="pending" instead of being rejected — the response's pending field
reflects this — and it stays off the public map until an admin approves it
(see Moderation below). report accepts a reason of not_a_cat, spam,
wrong_location, duplicate, or other. Sightings not confirmed within
STALE_AFTER_DAYS (default 30) are flagged stale and dimmed on the map.
Set ADMIN_TOKEN to enable token-gated moderation (sent as X-Admin-Token):
| Method | Path | Purpose |
|---|---|---|
| GET | /api/v1/admin/reports?sort&limit&offset | List reported sightings, paginated (sort=reports|date) |
| GET | /api/v1/admin/pending?limit&offset | List sightings queued for review (failed cat detection) |
| POST | /api/v1/admin/sightings/{id}/approve | Approve a pending sighting (sets status="active") |
| POST | /api/v1/admin/sightings/{id}/hide | Hide a sighting |
| POST | /api/v1/admin/sightings/{id}/unhide | Restore a sighting |
| DELETE | /api/v1/admin/sightings/{id} | Delete a sighting |
| GET | /api/v1/admin/actions?limit&offset | Moderation audit log (newest first) |
Visiting /admin on the frontend serves a small token-gated web UI for the
same operations: sign in with the admin token (stored in sessionStorage),
then review sightings pending cat-detection approval, view/sort/paginate
reported sightings, hide/unhide/delete them, and review the recent
moderation actions log.
Every hide/unhide/delete is recorded in the admin_actions table (action,
sighting ID, timestamp) — this audit trail persists even after a sighting is
deleted.
Sightings reach status="hidden" automatically once AUTO_HIDE_THRESHOLD
distinct devices report them; hidden sightings vanish from the public map.
The backend logs each request as a single-line JSON object (timestamp,
level, method, path, status code, duration, and a per-request
request_id) to stdout, via the catmap logger. The same request_id is
echoed back as the X-Request-ID response header (or honored if the client
sends one). Set LOG_LEVEL to adjust verbosity (default INFO).
Set SENTRY_DSN (backend) and/or VITE_SENTRY_DSN (frontend) to enable
Sentry error reporting. Both are opt-in — leave empty to
disable. The frontend DSN is read from window.__CATMAP_ENV__.sentryDsn
(written to /env-config.js at container startup, like
VITE_GA_MEASUREMENT_ID) or the build-time VITE_SENTRY_DSN, and is skipped
in dev builds. SENTRY_TRACES_SAMPLE_RATE and SENTRY_ENVIRONMENT tune the
backend's performance-tracing sample rate and reported environment name.
.github/workflows/backup.yml runs a nightly Postgres dump via
backend/scripts/backup_db.sh, uploaded as a workflow artifact. It's a
no-op until the BACKUP_DATABASE_URL repo secret is set — see
docs/operations.md for setup and restore steps.
cd backend && pip install -r requirements-dev.txt && pytest (runs
against SQLite; covers create/EXIF/confirm/report-auto-hide/delete-ownership/
rate-limit/upload-hardening).cd frontend && npm test (Vitest + React
Testing Library; covers api.js, deviceToken.js, filter/favorite/theme
helpers, and the Modal/Toast/SegmentedControl components).cd frontend && npm run e2e (Playwright; starts the
dev server itself and runs the add-sighting wizard validation flow, plus
@axe-core/playwright accessibility checks on the map page and the
add-sighting modal). First run npx playwright install chromium.CI (.github/workflows/ci.yml) runs backend lint + tests, frontend unit
tests + build, the Playwright e2e job, and Docker image builds.
render.yaml and provisions:
catmap-db — managed PostgreSQLcatmap-backend — Docker web service (DATABASE_URL auto-wired)catmap-frontend — Docker web service (VITE_API_BASE baked at build)render.yaml
(catmap-backend.onrender.com / catmap-frontend.onrender.com). If Render
assigned different names, update BACKEND_URL / BACKEND_HOST (frontend) and
CORS_ORIGINS (backend) accordingly and redeploy. The frontend nginx proxies
/api to the backend so the browser stays same-origin; add any custom frontend
domain to CORS_ORIGINS only if you call the backend URL directly.The backend normalizes Render's postgresql:// connection string to the
postgresql+psycopg:// driver form automatically (app/database.py).
render-staging.yaml is a second Blueprint that provisions a fully separate
staging stack (catmap-db-staging, catmap-backend-staging,
catmap-frontend-staging) — its own database, services, and URLs, isolated
from production.
render-staging.yaml as the blueprint file (Render lets you pick a
non-default blueprint path when creating the Blueprint).BACKEND_URL / BACKEND_HOST / CORS_ORIGINS / PUBLIC_SITE_URL in
render-staging.yaml and redeploy.LOG_LEVEL=DEBUG, no analytics, and admin/Sentry
disabled until you set ADMIN_TOKEN / SENTRY_DSN / VITE_SENTRY_DSN in
the Render dashboard for that service (use a separate Sentry project or
environment from production).The frontend is an installable PWA: open it on a phone and Add to Home
Screen for a full-screen, app-like experience with camera capture and
geolocation. The same React app is wrapped with
Capacitor for native Android and iOS
store builds. Tag a release (vMAJOR.MINOR.PATCH) to produce APK/AAB/IPA
artifacts — see frontend/android/RELEASE.md
and frontend/ios/RELEASE.md.
Schema changes are managed with Alembic. On startup the backend runs
alembic upgrade head (app/database.py).
cd backend
# apply migrations manually (same as startup)
alembic upgrade head
# create a new revision after editing models
alembic revision -m "describe change" --autogenerate
If you deployed before Alembic was added and tables already exist, the app
auto-stamps the 0001 baseline on first boot, then applies newer revisions
(e.g. 0002 adds cat_confidence). Fresh databases run all revisions from
scratch.
Uploads are checked server-side with a YOLOv10 COCO object detector (cat class).
Tune via env vars (CAT_DETECTION_ENABLED, CAT_DETECTION_THRESHOLD,
CAT_DETECTION_ANIMAL_THRESHOLD, CAT_DETECTION_STRICT). The browser runs a
matching YOLOv10n pre-check; the server is authoritative.
Download models for local dev:
cd backend && python scripts/fetch_model.py
cd ../frontend && npm run fetch-model
/api/v1/hearts |
Un-heart (target_type, target_id query params) |
| POST | /api/v1/auth/signup | Create account (email + password); returns session |
| POST | /api/v1/auth/login | Email/password login |
| POST | /api/v1/auth/google | Google ID token login |
| GET | /api/v1/auth/me | Current account (Bearer session) |
| POST | /api/v1/auth/logout | Revoke current session |
| GET | /api/v1/sightings/{id} | Full detail, including a photos list |
| PATCH | /api/v1/sightings/{id} | Edit your own sighting's description/attributes/location |
| GET | /api/v1/sightings/{id}/photo | Primary image bytes |
| GET | /api/v1/sightings/{id}/thumbnail | Primary thumbnail bytes |
| GET | /api/v1/sightings/{id}/photos/{photo_id} | Additional photo bytes |
| GET | /api/v1/sightings/{id}/photos/{photo_id}/thumbnail | Additional photo thumbnail |
| POST | /api/v1/sightings/{id}/confirm | Confirm once per device (idempotent; refreshes "last seen") |
| POST | /api/v1/sightings/{id}/report | Report once per device; auto-hides at threshold |
| POST | /api/v1/sightings/{id}/gone | Mark your own cat as gone (off the map) |
| DELETE | /api/v1/sightings/{id} | Delete your own (device must be creator) |
| GET | /healthz | Liveness + DB connectivity |