From d68ec1bd11c5552a28837f2d31c58b20a74f3138 Mon Sep 17 00:00:00 2001 From: Justin Reiners Date: Thu, 26 Mar 2026 11:26:58 -0500 Subject: [PATCH] Add FosterFlow SaaS platform design spec Comprehensive design for the multi-tenant rescue management SaaS, covering architecture, data model, tenant lifecycle, roles, features, tech details, testing, and deployment. Clean room rebuild from AHCR. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../2026-03-26-fosterflow-saas-design.md | 512 ++++++++++++++++++ 1 file changed, 512 insertions(+) create mode 100644 docs/superpowers/specs/2026-03-26-fosterflow-saas-design.md diff --git a/docs/superpowers/specs/2026-03-26-fosterflow-saas-design.md b/docs/superpowers/specs/2026-03-26-fosterflow-saas-design.md new file mode 100644 index 0000000..cc31def --- /dev/null +++ b/docs/superpowers/specs/2026-03-26-fosterflow-saas-design.md @@ -0,0 +1,512 @@ +# FosterFlow SaaS Platform — Design Spec + +**Date:** 2026-03-26 +**Domain:** fosterflow.app +**Status:** Approved + +--- + +## Overview + +FosterFlow is a multi-tenant SaaS platform for animal rescues. It provides a complete website + management system — everything a foster-based rescue needs, nothing it doesn't. Clean room rebuild using the AHCR (Almost Home Canine Rescue) codebase as reference. + +**Two deployment modes:** +- **Self-hosted** — free, open source (MIT), rescue runs the tenant app themselves via Docker +- **FosterFlow Cloud** — $19/mo managed hosting with automated provisioning, subdomain, SSL, backups, updates + +**Tech stack:** SvelteKit 2, Svelte 5 (runes), Tailwind CSS 4, Drizzle ORM, MariaDB, Stripe, Nodemailer, Caddy, Ollama (AI features) + +--- + +## 1. Overall Architecture + +``` +┌─────────────────────────────────────────────────────┐ +│ Caddy Proxy │ +│ fosterflow.app → platform app (marketing, │ +│ signup, operator dashboard) │ +│ *.fosterflow.app → tenant app (by subdomain) │ +│ custom domains → tenant app (by domain) │ +└──────────────┬──────────────────────┬───────────────┘ + │ │ + ┌──────────▼──────────┐ ┌────────▼────────────┐ + │ Platform App │ │ Tenant Instance(s) │ + │ (SvelteKit) │ │ (SvelteKit) │ + │ │ │ │ + │ / landing│ │ / public site │ + │ /signup form │ │ /admin dashboard │ + │ /operator mgmt │ │ /foster portal │ + │ /api hooks │ │ /api endpoints │ + └──────────┬──────────┘ └────────┬────────────┘ + │ │ + ┌──────────▼──────────┐ ┌────────▼────────────┐ + │ fosterflow_platform│ │ ff_{slug} (per DB) │ + │ (tenants, billing, │ │ (pets, users, apps, │ + │ signups, events) │ │ donations, etc.) │ + └─────────────────────┘ └─────────────────────┘ + │ │ + └──────────┬───────────┘ + MariaDB Server +``` + +**Two separate apps, two separate repos:** + +- **`fosterflow`** (public, GitHub) — the tenant app. Full rescue site + admin + foster portal. This is what self-hosted users clone. MIT licensed. +- **`fosterflow-cloud`** (private) — the platform app. Operator dashboard, signup/approval, provisioning engine, billing. Only runs on FosterFlow infrastructure. + +The tenant app has zero knowledge of multi-tenancy. It's just "a rescue site." The platform app orchestrates instances of it. + +**Shared Ollama instance** — platform-level AI service all tenants call for the dog name generator (free perk). + +--- + +## 2. Data Model + +### Platform Database (`fosterflow_platform`) + +```sql +-- Signup applications (pre-approval) +CREATE TABLE signups ( + id INT PRIMARY KEY AUTO_INCREMENT, + org_name VARCHAR(255) NOT NULL, + admin_name VARCHAR(255) NOT NULL, + admin_email VARCHAR(255) NOT NULL, + password_hash VARCHAR(255) NOT NULL, + slug VARCHAR(100) NOT NULL UNIQUE, + website_url VARCHAR(255), + ein VARCHAR(20), + description TEXT, + city VARCHAR(100), + state VARCHAR(100), + status ENUM('pending', 'approved', 'rejected') DEFAULT 'pending', + reviewed_by INT, + reviewed_at TIMESTAMP, + rejection_reason TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +-- Active tenants (post-approval) +CREATE TABLE tenants ( + id INT PRIMARY KEY AUTO_INCREMENT, + name VARCHAR(255) NOT NULL, + slug VARCHAR(100) NOT NULL UNIQUE, + subdomain VARCHAR(100) NOT NULL UNIQUE, + custom_domain VARCHAR(255), + db_name VARCHAR(100) NOT NULL, + port INT NOT NULL, + admin_email VARCHAR(255) NOT NULL, + admin_name VARCHAR(255) NOT NULL, + plan ENUM('trial', 'cloud', 'self_hosted') DEFAULT 'trial', + stripe_customer_id VARCHAR(255), + stripe_subscription_id VARCHAR(255), + status ENUM('provisioning', 'active', 'suspended', 'deleted') DEFAULT 'provisioning', + trial_ends_at TIMESTAMP, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +); + +-- Audit trail for tenant lifecycle +CREATE TABLE tenant_events ( + id INT PRIMARY KEY AUTO_INCREMENT, + tenant_id INT NOT NULL REFERENCES tenants(id), + event VARCHAR(50) NOT NULL, + details JSON, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +-- Operator users +CREATE TABLE operators ( + id INT PRIMARY KEY AUTO_INCREMENT, + email VARCHAR(255) NOT NULL UNIQUE, + name VARCHAR(255) NOT NULL, + password_hash VARCHAR(255) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); +``` + +### Tenant Database (`ff_{slug}`) + +| Group | Tables | +|-------|--------| +| **Auth** | `users`, `sessions` | +| **Pets** | `pets`, `pet_photos`, `pet_medical`, `foster_assignments`, `pet_mentors` | +| **Applications** | `applications`, `application_homevisit_notes` | +| **People** | `vets`, `mentors`, `sponsors`, `intake_sources` | +| **Finance** | `donations`, `expenses` | +| **Content** | `events`, `happy_tails`, `content_blocks`, `newsletter_subscribers`, `contact_messages` | +| **Foster** | `foster_submissions`, `supply_requests` | +| **Config** | `site_settings`, `org_config` | +| **System** | `audit_log`, `page_views`, `ai_jobs` | + +**`org_config` table** — key/value store with JSON values: + +```sql +CREATE TABLE org_config ( + id INT PRIMARY KEY AUTO_INCREMENT, + `key` VARCHAR(100) NOT NULL UNIQUE, + value JSON NOT NULL, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +); + +-- Example rows: +-- ('branding', '{"primaryColor":"#0d9488","accentColor":"#f59e0b","logoUrl":"/uploads/logo.png"}') +-- ('org_info', '{"name":"Almost Home","city":"Sioux Falls","state":"SD","phone":"..."}') +-- ('locale', '{"language":"en","currency":"USD","dateFormat":"MM/DD/YYYY"}') +-- ('applications', '{"adoptionEnabled":true,"fosterEnabled":true,"adoptionFee":250}') +-- ('agreements', '{"adoption":[...],"foster":[...]}') +``` + +**Changes from AHCR:** +- Added `org_config` table — drives theming, org details, locale, form settings +- All user-facing strings from config, not hardcoded +- Dropped `sync_jobs` (PetFinder API is dead) +- Dropped AHCR-specific import tables +- Generic CSV import replaces hardcoded importers + +--- + +## 3. Tenant Lifecycle + +``` +Rescue finds fosterflow.app + │ + ▼ +┌─────────────────────┐ +│ Signup Form │ +│ org name, admin │ +│ name/email, pass, │ +│ subdomain, website │ +│ EIN (opt), desc, │ +│ city/state │ +└─────────┬───────────┘ + │ + ▼ +┌─────────────────────┐ +│ Pending Review │ → Operator gets email notification +│ (stored in │ +│ platform DB) │ +└─────────┬───────────┘ + │ + ┌─────▼─────┐ + │ Operator │ + │ reviews │ + ├────┬───────┤ + │ │ │ + Approve Reject + │ │ + │ └→ Rejection email with reason + ▼ +┌─────────────────────┐ +│ Auto-Provision │ (~60 seconds) +│ 1. Create DB │ +│ 2. Run migrations │ +│ 3. Seed admin user │ +│ 4. Generate .env │ +│ 5. Clone tenant app│ +│ 6. Build & start │ +│ 7. Add Caddy route │ +└─────────┬───────────┘ + │ + ▼ +┌─────────────────────┐ +│ Welcome Email │ +│ login URL, temp │ +│ password, guide │ +└─────────┬───────────┘ + │ + ▼ +┌─────────────────────┐ +│ Setup Wizard │ +│ branding, config, │ +│ application forms, │ +│ optional CSV import│ +└─────────┬───────────┘ + │ + ▼ + Live at {slug}.fosterflow.app + 14-day free trial (no card required) + │ + ▼ + Trial expires → Pay $19/mo → Active + → Don't pay → Suspended (7 day grace) + → Deleted (30 days, data archived) +``` + +**Signup validation:** subdomain availability, unique email, spam checks. + +**Suspension:** App still runs but shows "account suspended" page. Data preserved. Reactivate by paying. + +**Custom domains:** Tenant sets domain in settings, adds CNAME to `{slug}.fosterflow.app`, Caddy auto-provisions SSL. + +**Deprovisioning:** Stop service, remove Caddy route, archive DB dump + uploads, mark deleted. Hard delete after 30 days. + +--- + +## 4. Roles & Permissions + +### Operator Level (Platform App) + +| Role | Access | +|------|--------| +| `operator` | Full access — approve/reject signups, manage tenants, billing, health, migrations | + +Separate auth system from tenant auth. Email/password login. + +### Tenant Level (Tenant App) + +| Role | Permissions | +|------|-------------| +| `sysadmin` | All 14 permissions (full control of rescue instance) | +| `director` | All except `system` | +| `foster_coordinator` | dashboard, pets, applications | +| `volunteer_manager` | dashboard, volunteers, events | +| `vet_liaison` | dashboard, pets, vets, medical, expenses | +| `content_editor` | dashboard, sponsors, content, events | +| `applications_manager` | dashboard, applications, pets_view | +| `viewer` | dashboard only | +| `foster` | Foster portal only | + +**14 permissions:** pets, applications, donations, expenses, vets, medical, events, content, volunteers, users, shop, import, system, dashboard + +Tenant `sysadmin` has zero visibility into the platform. They see their rescue site and nothing else. + +--- + +## 5. Feature Set + +### Core Features (Rebuilt from AHCR Reference) + +| Feature | Description | +|---------|-------------| +| **Pet Management** | Profiles, photos, medical records, foster assignments, status tracking, mentors | +| **Applications** | Adoption, foster, volunteer, surrender — configurable questions per form | +| **Foster Portal** | Dedicated dashboard — submit updates, photos, supply requests | +| **Donations & Expenses** | Stripe payments, multi-source tracking (PayPal, Venmo, cash, etc.), financial reports | +| **Events** | Listings with optional Facebook auto-posting | +| **Content CMS** | Editable page sections via `content_blocks`, happy tails / success stories | +| **Newsletter** | Subscriber management, compose and send | +| **Contact Forms** | Public contact form with message management in admin | +| **Sponsors** | Three tiers (top dog, wagging tails, wet noses), public sponsor page | +| **Analytics** | Page views, traffic tracking, bot detection | +| **Bot Protection** | Dynamic robots.txt, honeypot, managed blocklist/whitelist | +| **AI Name Generator** | Shared Ollama instance, free for all tenants | + +### New for FosterFlow + +| Feature | Description | +|---------|-------------| +| **Setup Wizard** | First-login onboarding — branding, org info, forms config, optional CSV import | +| **Configurable Theming** | Primary + accent color, logo upload via CSS custom properties | +| **`org_config` System** | All org-level settings in one place, drives the whole app | +| **Generic CSV Import** | Upload any CSV, map columns to FosterFlow fields, preview before commit | +| **Health Endpoint** | `/api/health` for platform monitoring | +| **i18n Architecture** | Translation key system, English strings in locale files, ready for more languages | + +### Dropped from AHCR + +| Dropped | Why | +|---------|-----| +| PetFinder sync | API is dead | +| AHCR-specific CSV import scripts | Replaced with generic column-mapping CSV import | +| Hardcoded content | Replaced with CMS content blocks + org_config | + +--- + +## 6. Tech Architecture Details + +### Tenant App Structure + +``` +fosterflow/ +├── src/ +│ ├── lib/ +│ │ ├── server/ +│ │ │ ├── db.ts # Drizzle + MariaDB connection +│ │ │ ├── schema.ts # All tenant tables +│ │ │ ├── auth.ts # Session management, Argon2 +│ │ │ ├── auth-utils.ts # Permission checks +│ │ │ ├── email.ts # Nodemailer (from address via config) +│ │ │ ├── stripe.ts # Payment processing +│ │ │ ├── upload.ts # File uploads +│ │ │ ├── audit.ts # Audit logging +│ │ │ ├── rate-limit.ts # Rate limiting +│ │ │ ├── content.ts # CMS content blocks +│ │ │ ├── contract.ts # Adoption contract generation +│ │ │ ├── ai.ts # Ollama client (name generator) +│ │ │ └── config.ts # Reads org_config, caches it +│ │ ├── components/ +│ │ │ ├── ui/ # Shared UI primitives +│ │ │ ├── admin/ # Admin dashboard components +│ │ │ └── public/ # Public site components +│ │ ├── i18n/ +│ │ │ ├── index.ts # Translation loader +│ │ │ └── locales/ +│ │ │ └── en.json # English strings +│ │ ├── roles.ts # Role + permission definitions +│ │ └── slugify.ts +│ ├── routes/ +│ │ ├── (public)/ # Public-facing pages +│ │ ├── admin/ # Admin dashboard +│ │ ├── foster/ # Foster portal +│ │ ├── api/ # API endpoints +│ │ ├── application/ # Public application forms +│ │ ├── setup/ # First-login setup wizard +│ │ ├── login/ +│ │ ├── logout/ +│ │ ├── register/ +│ │ ├── change-password/ +│ │ ├── pay/ # Stripe payment pages +│ │ ├── robots.txt/ +│ │ └── sitemap.xml/ +│ ├── hooks.server.ts # Auth, security headers, tracking +│ └── app.html +├── drizzle/ # Migration files +├── scripts/ +│ ├── seed.ts # Seed placeholder data +│ └── seed-tenant.ts # Seed admin user (used by provisioner) +├── static/ +├── .env.example +├── package.json +├── svelte.config.js +├── drizzle.config.ts +├── vite.config.ts +├── docker-compose.yml +├── Dockerfile +└── Caddyfile +``` + +### Config-Driven Architecture + +Everything flows from two sources: + +- **`.env`** — infrastructure config (DB URL, ports, secrets, Stripe keys, SMTP) +- **`org_config` table** — org-level config (branding, org info, locale, form settings, agreements) + +``` +Request comes in + → hooks.server.ts loads org_config (cached, refreshes every 5 min) + → event.locals.orgConfig available in all routes + → Components read orgConfig for branding, org name, colors + → CSS custom properties set from orgConfig.branding + → i18n loads locale from orgConfig.locale +``` + +### Theming + +```css +/* Generated from org_config.branding */ +:root { + --color-primary: #0d9488; + --color-accent: #f59e0b; +} +``` + +Tailwind CSS 4 picks these up via CSS custom properties. Components use `text-primary`, `bg-accent`, etc. No hardcoded colors. + +### i18n Approach + +Every user-facing string goes through a `t()` function: + +```svelte +

{t('pets.title')}

+

{t('applications.adoption.intro')}

+``` + +`en.json` ships with the app. Adding a language = adding a locale file + tenant picks locale in settings. No code changes required. + +### Generic CSV Import + +- Upload any CSV file +- App auto-detects columns, shows preview +- User maps their columns to FosterFlow fields (name, breed, age, status, etc.) +- Preview mapped data before committing +- Handle mismatches gracefully (skip bad rows, show errors) +- Available in setup wizard and in admin anytime for bulk imports + +--- + +## 7. Testing Strategy + +| Layer | Tool | What | +|-------|------|------| +| **Unit** | Vitest | Schema validation, permission checks, config loading, i18n, utility functions | +| **Integration** | Vitest | DB operations, auth flows, API endpoints (real DB, not mocks) | +| **E2E** | Playwright | Full user flows — setup wizard, pet CRUD, application submission, foster portal | + +**Key test scenarios:** +- Setup wizard completes and populates org_config +- Role permissions block/allow correctly +- Theming applies from org_config +- CSV import with various column formats +- Application forms render based on config +- Public site reflects CMS content blocks +- Health endpoint returns correct status + +--- + +## 8. Deployment & Infrastructure + +### Self-Hosted (Tenant App Only) + +```bash +git clone https://github.com/jrei/fosterflow +cd fosterflow +cp .env.example .env # edit with your details +docker compose up -d # MariaDB + app + Caddy +``` + +Three containers: +- `fosterflow-app` — Node.js SvelteKit +- `fosterflow-db` — MariaDB +- `fosterflow-proxy` — Caddy (auto SSL) + +No operator dashboard. No multi-tenant machinery. Just a rescue site. + +### FosterFlow Cloud + +Caddy proxy, one app process per tenant, database-per-tenant on shared MariaDB. Platform app runs alongside. + +Provisioning script automates: create DB, run migrations, seed admin user, clone tenant app, generate `.env`, build, start systemd service, add Caddy route. + +**Scaling path:** + +| Tenants | Setup | +|---------|-------| +| 0-100 | Single VPS ($15-20/mo) | +| 100-200 | Separate DB server, shared app build (symlink not copy), automated health checks | +| 200+ | Multiple app servers, Cloudflare R2 for images, monitoring (Grafana), consider containers | + +**Revenue at scale:** + +| Tenants | Monthly Revenue | Server Costs | Margin | +|---------|----------------|--------------|--------| +| 10 | $190 | $15 | $175 | +| 50 | $950 | $30 | $920 | +| 100 | $1,900 | $60 | $1,840 | +| 500 | $9,500 | $200 | $9,300 | + +### Backups + +- Per-tenant `mysqldump` on cron +- Uploads archived separately +- Platform DB backed up independently +- 30-day retention after tenant deletion + +### Billing (Stripe) + +- **Product:** FosterFlow Cloud ($19/mo) +- **Trial:** 14 days free (no card required) +- **Webhook events:** + - `customer.subscription.created` → activate tenant + - `customer.subscription.deleted` → suspend tenant (grace period) + - `invoice.payment_failed` → warning email, suspend after 3 failures +- **Dunning:** Stripe handles retry logic + +--- + +## Reference + +- **AHCR source:** `../almosthomecaninerescue.com-2026/ahcr/` +- **Market:** ~10,000 rescue organizations in the US, ~14,000 total animal welfare orgs +- **Differentiator:** Full website + management in one (competitors are mostly backend-only tools) +- **Competition:** Shelterluv ($2/adoption), RescueGroups (free), Petstablished (free), BARRK ($20/mo)