.github/workflows/backup.yml runs nightly (03:00 UTC) and on-demand
(workflow_dispatch). It dumps the Postgres database with
backend/scripts/backup_db.sh and uploads the gzipped .sql.gz file as a
workflow artifact (30-day retention).
- In Render, open
catmap-db→ Connect and copy the External Connection String (the internal one isn't reachable from GitHub Actions). - In the GitHub repo, go to Settings → Secrets and variables → Actions
and add a secret named
BACKUP_DATABASE_URLwith that connection string. - (Optional) For longer-than-30-day retention, also copy backups to an
S3-compatible bucket by adding these secrets:
BACKUP_S3_BUCKET— bucket nameAWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEYAWS_DEFAULT_REGION(andAWS_ENDPOINT_URLfor non-AWS providers like Backblaze B2, Cloudflare R2, etc.)
If BACKUP_DATABASE_URL isn't set, the workflow runs but skips the backup
step (visible as a warning in the run log) — it won't fail CI or alert
anyone.
Download the artifact (or S3 object), then:
gunzip -c catmap-20260101T030000Z.sql.gz | psql "$DATABASE_URL"Use a connection string for the target database (a fresh Render Postgres instance, or a local Postgres for testing). Restoring into a database that already has data will likely produce conflicts — restore into an empty database, or drop and recreate it first.
After restoring, run alembic upgrade head (or let the app's startup
migration runner do it) if the backup predates the running app's migrations.
You can also run the backup script directly against any database:
DATABASE_URL=postgresql://user:pass@host:5432/catmap ./backend/scripts/backup_db.sh ./backupsRequires pg_dump (the postgresql-client package).
Owners of active missing-cat posts get a "Still missing?" inbox/push nudge
every MISSING_REMINDER_DAYS days (default 14) of inactivity. The job is the
admin-only POST /api/admin/jobs/missing-reminders (header X-Admin-Token),
run daily by .github/workflows/reminders.yml. To enable it, add the
CATMAP_API_URL (e.g. https://catmap-backend.onrender.com) and
CATMAP_ADMIN_TOKEN repository secrets. Reminders are throttled per post, so
running the job more often is harmless.
Users can watch up to 5 areas (centre + radius) from the Watching screen. New
cats posted inside an area create an inbox notification (and a push, if the
device is subscribed) — no push subscription required. Signed-in users with a
verified email can also opt in to a weekly digest of new cats in their areas
(Account → email preferences). The digest is sent by
POST /api/admin/jobs/weekly-digest, run Mondays by
.github/workflows/digest.yml with the same CATMAP_API_URL /
CATMAP_ADMIN_TOKEN secrets as the reminders job. It needs email configured
(Resend, below), skips users with nothing new, and won't send to the same user
twice within 6 days. Area watches are matched in Python against all watches on
each new post, which is fine into the thousands of watches; move the match into
a bounding-box SQL query if that grows.
CatMap supports three notification channels:
- In-app inbox — always available; polled via
/api/notifications. - Web Push (VAPID) — browser PWA; requires VAPID keys on the backend.
- FCM (Android) — native app via Capacitor; requires Firebase.
Generate a keypair:
npx web-push generate-vapid-keysSet on the backend (Render → catmap-backend → Environment):
VAPID_PUBLIC_KEY— the public keyVAPID_PRIVATE_KEY— the private keyVAPID_SUBJECT— e.g.mailto:you@example.com
The frontend service worker registers push subscriptions against
/api/push/subscribe.
- Create a Firebase project and add an Android app (
com.catmap.appor your application id). - Download
google-services.jsonintofrontend/android/app/. - Create a Firebase service account with Firebase Cloud Messaging API
Admin and paste the JSON into
FCM_SERVICE_ACCOUNT_JSONon the backend (single-line JSON string).
Without Firebase configured, the app still works — inbox notifications remain; native push is skipped gracefully.
Users opt in via Settings → Alert me about missing cats nearby. The
backend stores alert_lat, alert_lng, and alert_radius_km on each push
subscription and notifies matching devices when a new kind=missing post is
created.
Accounts are optional. Anonymous device-token usage continues to work. Signing in links the current device token to the account so ownership, hearts, and inbox sync across devices.
- Create a Resend account and API key.
- Verify your sending domain (or use Resend's onboarding domain for tests).
- Set on the backend:
RESEND_API_KEYEMAIL_FROM— e.g.CatMap <noreply@yourdomain.com>- optional
EMAIL_REPLY_TO
- With the key unset, verification / reset / notification emails are no-ops (useful for local Docker).
- In Google Cloud Console, create OAuth client IDs for Web, Android, and iOS (same project as Firebase if you already use FCM).
- Set
GOOGLE_CLIENT_IDSon the backend to the comma-separated list of all client IDs. - Set
VITE_GOOGLE_CLIENT_IDon the frontend to the web client ID. - Android: add the signing SHA-1 fingerprint to the Android OAuth client and
keep
google-services.jsonin place. - iOS: add the reversed client ID URL scheme to the iOS app / Info.plist.
- Native apps use
@capacitor-firebase/authenticationwhen installed (npm i @capacitor-firebase/authenticationthennpx cap sync). The plugin returns a Google ID token that is posted to/api/auth/google. Email/password remains available so App Store guideline 4.8 is satisfied without Sign in with Apple.