Initial paw❤️print repo with SaaS architecture docs

README, multi-tenant SaaS architecture (database-per-tenant),
automated onboarding flow (signup → 60 seconds → live site),
and template extraction plan from AHCR codebase.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-26 10:36:38 -05:00
co-authored by Claude Opus 4.6
commit 7fd2438c36
4 changed files with 690 additions and 0 deletions
+274
View File
@@ -0,0 +1,274 @@
# Automated Onboarding
## Flow
```
Rescue signs up at pawprint.app
│
▼
┌─────────────────────┐
│ Signup Form │
│ - Org name │
│ - Admin email │
│ - Admin name │
│ - Subdomain pick │
│ - Password │
└─────────┬───────────┘
│
▼
┌─────────────────────┐
│ Validate │
│ - Subdomain avail? │
│ - Email unique? │
│ - Spam check │
└─────────┬───────────┘
│
▼
┌─────────────────────┐
│ Provision │ ← Automated script
│ 1. Create DB │
│ 2. Run migrations │
│ 3. Seed admin user │
│ 4. Generate .env │
│ 5. Create service │
│ 6. Add Caddy route │
│ 7. Start app │
└─────────┬───────────┘
│
▼
┌─────────────────────┐
│ Welcome Email │
│ - Login URL │
│ - Temp password │
│ - Getting started │
└─────────┬───────────┘
│
▼
Rescue is live at
{slug}.pawprint.app
(~60 seconds total)
```
## Provisioning Script
```bash
#!/bin/bash
# provision-tenant.sh <slug> <org_name> <admin_email> <admin_name>
set -e
SLUG=$1
ORG_NAME=$2
ADMIN_EMAIL=$3
ADMIN_NAME=$4
DB_NAME="pp_${SLUG}"
PORT=$(next_available_port) # Function to find next open port
APP_DIR="/var/www/pawprint/${SLUG}"
SESSION_SECRET=$(openssl rand -hex 32)
TEMP_PASSWORD=$(openssl rand -hex 4)
echo "=== Provisioning ${SLUG} ==="
# 1. Create database
echo "Creating database ${DB_NAME}..."
mysql -e "CREATE DATABASE IF NOT EXISTS ${DB_NAME} CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
mysql -e "CREATE USER IF NOT EXISTS 'pp_${SLUG}'@'localhost' IDENTIFIED BY '$(openssl rand -hex 16)';"
mysql -e "GRANT ALL PRIVILEGES ON ${DB_NAME}.* TO 'pp_${SLUG}'@'localhost';"
mysql -e "FLUSH PRIVILEGES;"
# 2. Clone app (or symlink to shared build)
echo "Setting up app directory..."
mkdir -p ${APP_DIR}
cp -r /var/www/pawprint/_template/* ${APP_DIR}/
# Or: ln -s /var/www/pawprint/_shared_build/build ${APP_DIR}/build
# 3. Generate .env
echo "Generating .env..."
cat > ${APP_DIR}/.env << EOF
DATABASE_URL=mysql://pp_${SLUG}:${DB_PASS}@localhost:3306/${DB_NAME}
SESSION_SECRET=${SESSION_SECRET}
UPLOAD_DIR=${APP_DIR}/uploads
PUBLIC_SITE_URL=https://${SLUG}.pawprint.app
ORG_NAME=${ORG_NAME}
PORT=${PORT}
EOF
# 4. Run migrations
echo "Running migrations..."
cd ${APP_DIR}
npx drizzle-kit push
# 5. Seed admin user
echo "Seeding admin user..."
node scripts/seed-tenant.js \
--email "${ADMIN_EMAIL}" \
--name "${ADMIN_NAME}" \
--password "${TEMP_PASSWORD}" \
--orgName "${ORG_NAME}"
# 6. Create systemd service
echo "Creating systemd service..."
cat > /etc/systemd/system/pawprint-${SLUG}.service << EOF
[Unit]
Description=PawPrint - ${ORG_NAME}
After=network.target
[Service]
Type=simple
User=www-data
WorkingDirectory=${APP_DIR}
ExecStart=/usr/bin/node build
Restart=on-failure
RestartSec=5
Environment=NODE_ENV=production
EnvironmentFile=${APP_DIR}/.env
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable pawprint-${SLUG}
systemctl start pawprint-${SLUG}
# 7. Add Caddy route
echo "Configuring Caddy..."
cat >> /etc/caddy/sites/pawprint-tenants.caddy << EOF
${SLUG}.pawprint.app {
reverse_proxy localhost:${PORT}
file_server /uploads/* {
root ${APP_DIR}
}
}
EOF
systemctl reload caddy
# 8. Register in admin DB
mysql pawprint_admin -e "INSERT INTO tenants (name, slug, subdomain, db_name, port, admin_email, admin_name, status, trial_ends_at) VALUES ('${ORG_NAME}', '${SLUG}', '${SLUG}.pawprint.app', '${DB_NAME}', ${PORT}, '${ADMIN_EMAIL}', '${ADMIN_NAME}', 'active', DATE_ADD(NOW(), INTERVAL 14 DAY));"
# 9. Send welcome email
echo "Sending welcome email..."
node scripts/send-welcome.js \
--email "${ADMIN_EMAIL}" \
--name "${ADMIN_NAME}" \
--orgName "${ORG_NAME}" \
--url "https://${SLUG}.pawprint.app" \
--password "${TEMP_PASSWORD}"
echo "=== ${SLUG}.pawprint.app is LIVE ==="
```
## What the Rescue Gets
After ~60 seconds of provisioning:
1. **Live site** at `{slug}.pawprint.app` with SSL
2. **Admin login** with temp password (forced change on first login)
3. **Welcome email** with login URL, getting started guide
4. **Empty but functional** — ready to add pets, customize content, upload photos
5. **14-day free trial** — full features, no credit card required
## Setup Wizard (First Login)
When the admin first logs in, they see a setup wizard instead of the dashboard:
### Step 1: Organization Info
- Logo upload
- Organization name (pre-filled from signup)
- Tagline / mission statement
- Location (city, state)
- Contact email, phone
- Social links (Facebook, Instagram)
### Step 2: Branding
- Primary color (default: teal)
- Accent color
- Font preference (2-3 options)
- Preview of how the site looks
### Step 3: Application Forms
- Toggle which application types to enable (adoption, foster, volunteer, surrender)
- Customize adoption fee default
- Edit agreement items (pre-populated with sensible defaults)
### Step 4: Integrations (Optional)
- Stripe keys (for accepting payments)
- Email SMTP (or use PawPrint's shared sender)
- Facebook page connection
- Petfinder widget (org ID)
### Step 5: Import Data (Optional)
- Upload CSV of existing pets
- Map columns to PawPrint fields
- Preview and confirm import
### Done!
- Redirect to dashboard
- Checklist of "next steps" (add first pet, upload logo, share your site)
## Deprovisioning
When a tenant cancels or trial expires:
```bash
#!/bin/bash
# deprovision-tenant.sh <slug>
SLUG=$1
# 1. Stop service
systemctl stop pawprint-${SLUG}
systemctl disable pawprint-${SLUG}
rm /etc/systemd/system/pawprint-${SLUG}.service
# 2. Remove Caddy route
# (sed out the block from tenants.caddy)
systemctl reload caddy
# 3. Archive data (keep for 30 days)
mysqldump pp_${SLUG} > /backups/tenants/${SLUG}-$(date +%Y%m%d).sql
tar -czf /backups/tenants/${SLUG}-uploads-$(date +%Y%m%d).tar.gz /var/www/pawprint/${SLUG}/uploads/
# 4. Mark as deleted in admin DB
mysql pawprint_admin -e "UPDATE tenants SET status = 'deleted' WHERE slug = '${SLUG}';"
# 5. Schedule hard delete after 30 days
at now + 30 days << EOF
mysql -e "DROP DATABASE IF EXISTS pp_${SLUG};"
mysql -e "DROP USER IF EXISTS 'pp_${SLUG}'@'localhost';"
rm -rf /var/www/pawprint/${SLUG}
rm /backups/tenants/${SLUG}-*.sql
rm /backups/tenants/${SLUG}-*.tar.gz
EOF
echo "=== ${SLUG} deprovisioned, data retained for 30 days ==="
```
## Scaling Considerations
### 0-50 Tenants
- Single VPS ($15-20/mo)
- Everything on one box
- Manual monitoring
### 50-200 Tenants
- Separate DB server
- Shared build (symlink, not copy)
- Automated health checks
- Consider container-per-tenant (Docker)
### 200+ Tenants
- Multiple app servers
- Managed database (PlanetScale, Aiven)
- Cloudflare R2 for images
- Kubernetes or Docker Swarm
- Dedicated ops/monitoring (Grafana, alerts)
### 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 |