Files
FlockPal/README.md
T
2026-05-02 10:35:11 -04:00

241 lines
10 KiB
Markdown

# FlockPal
FlockPal is a Dockerized TypeScript app for tracking flock health with a clean, modern, and casual UI.
## Current scope
- Passwordless authentication only
- Magic-link email sign-in
- OAuth-ready login flow for Google, Microsoft, and Apple
- Multi-flock model with `standard` household and `rescue` modes
- Shared flock member management for both households and rescues
- Separate per-flock billing plan foundation with `rescue_free`, `household_basic`, `household_plus`, and `household_macaw`
- Stripe-ready per-flock billing identifiers so one account can manage multiple paid flock subscriptions
- Bird profiles with name, tag ID, and species
- Bird DOB and gotcha day fields
- Daily weight recordings
- 30-day weight graph
- Vet visit history with notes
- Postgres-backed storage
- React frontend and Express backend
- Separate worker process for scheduled reminders and background tasks
- Redis-backed background queue for scheduled reminders
- Security-minded defaults like Helmet, CORS allow-listing, rate limiting, and input validation
## Planned next steps
- Medication and care reminders
- Invitation acceptance and onboarding polish for flock members
- Scheduled reminder delivery for birthdays, gotcha days, and care events
- Audit logging for flock access changes and bird transfers
## Development
1. Copy `.env.example` to `.env` if you want custom settings.
2. Start the development stack:
```bash
docker compose up --build
```
3. Open `http://localhost:3000`.
4. The API health check is available at `http://localhost:5000/api/health`.
Full API documentation is available in [docs/API_REFERENCE.md](docs/API_REFERENCE.md).
The default `docker-compose.yml` is development-only. It mounts source files, installs dev dependencies, and runs the backend and frontend in watch mode.
## Operations
### Backups
Create a compressed Postgres backup from the Docker Compose Postgres service:
```bash
./scripts/backup-postgres.sh
```
By default this uses `docker-compose.prod.yml` and writes to `backups/postgres/`. Override with `COMPOSE_FILE` or `BACKUP_DIR`:
```bash
COMPOSE_FILE=docker-compose.yml BACKUP_DIR=/srv/flockpal-backups ./scripts/backup-postgres.sh
```
Test a backup by restoring it into a temporary database in the same Postgres container:
```bash
./scripts/restore-test-postgres.sh backups/postgres/flockpal-YYYYMMDDTHHMMSSZ.dump
```
The restore test creates a temporary database, runs `pg_restore`, verifies it can query `workspaces`, and then drops the temporary database. It does not overwrite the live database.
### Metrics and logs
The backend writes HTTP access logs through Morgan. Production uses the standard combined log format so Docker, Traefik, or a log shipper can collect it from container stdout.
Admin users can fetch lightweight process/request metrics:
```bash
curl -H "Authorization: Bearer <admin-token>" https://your-host/api/metrics
```
## Production
1. Set production values in your environment or `.env`, especially:
- `POSTGRES_PASSWORD`
- `FRONTEND_URL`
- `BACKEND_URL`
- `VITE_API_BASE_URL`
- `REDIS_URL`
- `IMAGE_STORAGE_PROVIDER`
- `S3_ENDPOINT`
- `S3_REGION`
- `S3_BUCKET`
- `S3_ACCESS_KEY_ID`
- `S3_SECRET_ACCESS_KEY`
- `RESCUE_ONBOARDING_WEBHOOK_URL`
2. Build and start the production stack:
```bash
docker compose -f docker-compose.prod.yml up --build -d
```
3. The production backend runs the compiled Node app from `dist/app.js`.
4. The production worker runs `dist/worker.js` and owns scheduled reminder execution.
5. The production frontend is built with Vite and served by Nginx on port `3000`.
## Redis
Compose includes a Redis service at `redis://redis:6379` and passes that value through `REDIS_URL` to the backend and worker. Redis uses append-only persistence under `data/redis/`.
Scheduled milestone reminders are enqueued through Redis with a per-date job id, then processed by the worker. This keeps scheduled work out of API containers and prevents duplicate scheduled jobs when the API is scaled horizontally. Redis can also support later shared rate-limit state and short-lived cache entries.
## Image storage
FlockPal currently keeps bird photos in Postgres as `photo_data_url`. The schema also has S3 object metadata columns so image storage can move to Wasabi/S3 without changing the bird record contract.
Set these when Wasabi image storage is ready:
- `IMAGE_STORAGE_PROVIDER=s3`
- `S3_ENDPOINT=https://s3.<wasabi-region>.wasabisys.com`
- `S3_REGION=<wasabi-region>`
- `S3_BUCKET=<bucket-name>`
- `S3_ACCESS_KEY_ID=<access-key>`
- `S3_SECRET_ACCESS_KEY=<secret-key>`
- `S3_PUBLIC_BASE_URL=<optional CDN or public bucket base URL; leave blank for private signed URLs>`
- `S3_KEY_PREFIX=bird-photos`
Use a dedicated private bucket and access key for FlockPal images. Grant only the S3 permissions the app needs for that bucket. When `S3_PUBLIC_BASE_URL` is blank, FlockPal stores private object keys and returns short-lived signed URLs for bird photos.
Migrate existing Postgres-stored bird photos after deploying S3 image storage:
```bash
docker compose -f docker-compose.prod.yml exec backend npm run migrate:bird-photos-to-s3 -- --apply
```
Run a dry run first by omitting `--apply`. Use `--limit=10` to migrate a small batch, and `--keep-data-url` if you want to leave the original inline image in Postgres during an initial verification pass.
Bucket settings recommendation:
- Enable bucket versioning if you want rollback protection from accidental overwrites or deletes. Add a lifecycle policy once upload volume is known because every object version contributes to stored data.
- Do not enable Object Lock on the primary app image bucket unless there is a strict legal/compliance retention requirement. Object Lock must be enabled when creating the bucket, depends on versioning, and can make user-requested image deletion or replacement harder.
## Worker process
The API container does not run scheduled reminder loops. Background reminders run in the `worker` service so the API can be scaled horizontally without multiple API containers sending duplicate scheduled emails.
Run the worker locally through Compose:
```bash
docker compose up worker
```
Run the compiled worker directly from the backend package:
```bash
npm run build
npm run worker
```
## Auth and flock notes
- One user can belong to multiple flocks.
- A rescue member can also keep their own household flock separate from the rescue flock.
- Billing should attach to the household flock, not the user account.
- Rescue flocks stay on the free plan.
- Shared access is controlled by flock roles like `owner`, `assistant`, `caregiver`, and `viewer`.
- FlockPal no longer stores local passwords.
- Authentication now happens through magic links or external identity providers.
## OAuth environment
Set these in Docker or your `.env` file if you want provider login enabled:
- `FRONTEND_URL`
- `BACKEND_URL`
- `GOOGLE_CLIENT_ID`
- `GOOGLE_CLIENT_SECRET`
- `MICROSOFT_CLIENT_ID`
- `MICROSOFT_CLIENT_SECRET`
- `APPLE_CLIENT_ID`
- `APPLE_CLIENT_SECRET`
## Magic-link email environment
Set these if you want magic links delivered by email instead of logged as a preview URL during local development:
- `SMTP_HOST`
- `SMTP_PORT`
- `SMTP_SECURE`
- `SMTP_USER`
- `SMTP_PASS`
- `SMTP_FROM_EMAIL`
- `SMTP_FROM_NAME`
## Stripe billing environment
Set these when the Stripe Checkout, Customer Portal, and webhook flow is enabled:
- `STRIPE_SECRET_KEY`
- `STRIPE_WEBHOOK_SECRET`
- `STRIPE_PRICE_HOUSEHOLD_CONURE_MONTHLY` or `STRIPE_PRICE_HOUSEHOLD_CONURE`
- `STRIPE_PRICE_HOUSEHOLD_CONURE_YEARLY`
- `STRIPE_PRICE_HOUSEHOLD_INDIANRINGNECK_MONTHLY` or `STRIPE_PRICE_HOUSEHOLD_INDIANRINGNECK`
- `STRIPE_PRICE_HOUSEHOLD_INDIANRINGNECK_YEARLY`
- `STRIPE_PRICE_HOUSEHOLD_MACAW_MONTHLY` or `STRIPE_PRICE_HOUSEHOLD_MACAW`
- `STRIPE_PRICE_HOUSEHOLD_MACAW_YEARLY`
- `STRIPE_CHECKOUT_SUCCESS_URL`
- `STRIPE_CHECKOUT_CANCEL_URL`
- `STRIPE_PORTAL_RETURN_URL`
Recommended defaults:
- `STRIPE_CHECKOUT_SUCCESS_URL=https://your-frontend-host/?billing=success`
- `STRIPE_CHECKOUT_CANCEL_URL=https://your-frontend-host/?billing=cancelled`
- `STRIPE_PORTAL_RETURN_URL=https://your-frontend-host/?billing=portal`
## Stripe operations
- Configure the Stripe Customer Portal to allow subscription plan changes for the household products.
- Enable the proration behavior you want in the Customer Portal configuration. FlockPal treats Stripe as the source of truth for upgrade timing and proration outcomes.
- Point the Stripe webhook endpoint at `https://your-backend-host/api/billing/stripe/webhook`.
- Subscribe the webhook endpoint to at least `checkout.session.completed`, `customer.subscription.created`, `customer.subscription.updated`, and `customer.subscription.deleted`.
- Use one Stripe Price per plan and billing interval. FlockPal maps Stripe price IDs back to `household_basic`, `household_plus`, and `household_macaw`.
- After Stripe redirects back to the app, FlockPal now performs a direct billing sync against Stripe and then refreshes the active session. Webhooks are still required so asynchronous subscription changes stay in sync later.
For local development with the Stripe CLI:
```bash
stripe listen --forward-to http://localhost:5000/api/billing/stripe/webhook
```
Copy the signing secret printed by `stripe listen` into `STRIPE_WEBHOOK_SECRET`.
## Notes for monetization and security
This starter now includes the account and flock foundation for monetization, plus Stripe checkout, Customer Portal, and webhook synchronization. It still needs production-grade session hardening, invitation verification, audit logging, and background reminder delivery before launch.
Stripe billing should be attached to `workspaces`, not `users`. Each flock has its own billing plan, subscription status, Stripe customer ID, and Stripe subscription ID, which lets one person own multiple household flocks with separate subscriptions while rescue flocks can stay on the free rescue path.
For account design, `standard` vs `rescue` is best treated as a flock type, not as a user role. If paid plans are added later, a separate `admin account mode` is usually less flexible than flock roles such as `owner`, `assistant`, `caregiver`, and `viewer`. That lets the same underlying account system work for both households and rescues without splitting product logic into unrelated account classes.