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) <noreply@anthropic.com>
513 lines
20 KiB
Markdown
513 lines
20 KiB
Markdown
# 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
|
|
<h1>{t('pets.title')}</h1>
|
|
<p>{t('applications.adoption.intro')}</p>
|
|
```
|
|
|
|
`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)
|