From 7fd2438c36702ceef57192551a3e62a509785c32 Mon Sep 17 00:00:00 2001 From: Justin Reiners Date: Thu, 26 Mar 2026 10:36:38 -0500 Subject: [PATCH] =?UTF-8?q?Initial=20paw=E2=9D=A4=EF=B8=8Fprint=20repo=20w?= =?UTF-8?q?ith=20SaaS=20architecture=20docs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- README.md | 69 +++++++++ docs/ONBOARDING.md | 274 ++++++++++++++++++++++++++++++++++++ docs/SAAS.md | 211 +++++++++++++++++++++++++++ docs/TEMPLATE_EXTRACTION.md | 136 ++++++++++++++++++ 4 files changed, 690 insertions(+) create mode 100644 README.md create mode 100644 docs/ONBOARDING.md create mode 100644 docs/SAAS.md create mode 100644 docs/TEMPLATE_EXTRACTION.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..645a0bb --- /dev/null +++ b/README.md @@ -0,0 +1,69 @@ +# 🐾 paw❀️print + +**Open-source rescue management platform β€” self-host free or use PawPrint Cloud.** + +Modern, fast, mobile-first website and management system built for animal rescues. Everything a foster-based rescue needs, nothing it doesn't. + +--- + +## What It Does + +- πŸ• **Pet Management** β€” Profiles, photos, medical records, foster assignments, status tracking +- πŸ“‹ **Applications** β€” Adoption, foster, volunteer, surrender forms with configurable questions +- 🏠 **Foster Portal** β€” Dedicated dashboard for fosters to submit updates, photos, supply requests +- πŸ’° **Donations & Expenses** β€” Stripe payments, multi-source tracking, financial reports +- πŸ“… **Events** β€” Listings with optional Facebook auto-posting +- ✏️ **Content CMS** β€” Editable page sections, newsletter, happy tails / success stories +- πŸ“Š **Analytics** β€” Human traffic, bot detection, honeypot, AI job tracking +- πŸ” **Role-Based Access** β€” 9 roles from sysadmin to foster with granular permissions +- πŸ€– **Bot Protection** β€” Dynamic robots.txt, honeypot tarpit, managed blocklist/whitelist +- 🧠 **AI Features** β€” Pet name generator (Ollama), expandable job queue + +## Tech Stack + +- **SvelteKit 2** + **Svelte 5** (runes) +- **Tailwind CSS 4** +- **Drizzle ORM** + **MariaDB/MySQL** +- **Node.js** adapter for production +- **Stripe** for payments +- **Nodemailer** for email +- **Ollama** for local AI (optional) + +## SaaS Architecture + +PawPrint supports multi-tenant deployment: + +- **Self-hosted** β€” One rescue, one server, free forever +- **PawPrint Cloud** β€” Automated onboarding, subdomain provisioning, managed hosting + +See [docs/SAAS.md](docs/SAAS.md) for the multi-tenant architecture. +See [docs/ONBOARDING.md](docs/ONBOARDING.md) for automated provisioning. + +## Quick Start (Self-Hosted) + +```bash +git clone https://github.com/yourusername/pawprint +cd pawprint +cp .env.example .env +# Edit .env with your database URL, SMTP, etc. +npm install +npx drizzle-kit push +npm run seed +npm run dev +``` + +## PawPrint Cloud Pricing + +| Plan | Price | What You Get | +|------|-------|-------------| +| **Self-Hosted** | Free | Full source, you manage everything | +| **PawPrint Cloud** | $19/mo | Managed hosting, subdomain, SSL, backups, updates | +| **Setup Assist** | $50 one-time | We migrate your data from your old site | + +## License + +MIT β€” use it, fork it, save some dogs. + +--- + +Built with ❀️ by [JRei](https://reiners.dev) β€” started as Almost Home Canine Rescue's website, now open for every rescue. diff --git a/docs/ONBOARDING.md b/docs/ONBOARDING.md new file mode 100644 index 0000000..1dce9bc --- /dev/null +++ b/docs/ONBOARDING.md @@ -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 + +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=$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 | diff --git a/docs/SAAS.md b/docs/SAAS.md new file mode 100644 index 0000000..ba27484 --- /dev/null +++ b/docs/SAAS.md @@ -0,0 +1,211 @@ +# PawPrint SaaS Architecture + +## Multi-Tenant Strategy + +### Phase 1: Database-per-Tenant (MVP) + +Each rescue gets their own database on shared infrastructure. The app code is identical across tenants β€” only the database connection and subdomain differ. + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Caddy Reverse Proxy β”‚ +β”‚ *.pawprint.app β†’ route by subdomain β”‚ +β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ almosthomeβ”‚ happypawsβ”‚ furever β”‚ ... β”‚ +β”‚ :3001 β”‚ :3002 β”‚ :3003 β”‚ β”‚ +β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ DB: ahcr β”‚ DB: hpawsβ”‚ DB: furevβ”‚ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +β”‚ MariaDB Server β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +**Why this approach:** +- Zero code changes to the rescue app itself +- Complete data isolation between tenants (no org_id bugs) +- Each tenant can be backed up, migrated, or deleted independently +- Can scale horizontally by adding more VPS nodes +- Simple to reason about β€” each rescue is just "another install" + +**How it works:** +1. Tenant signs up at `pawprint.app` +2. Provisioning script creates: database, .env file, systemd service, Caddy route +3. App boots on a unique port, connects to its own DB +4. Caddy routes `{slug}.pawprint.app` β†’ `localhost:{port}` + +### Phase 2: Shared Database (Scale) + +When we hit 500+ tenants and per-DB overhead matters: +- Add `orgId` column to every table +- Single app instance, single DB +- Row-level security via middleware +- **Only do this when Phase 1 becomes painful** + +## Infrastructure + +### Single Server (0-100 tenants) + +One beefy VPS handles everything: +- **Hetzner CX31** ($15/mo): 4 vCPU, 8GB RAM, 80GB SSD +- MariaDB: all tenant databases +- Node.js: one process per tenant (low memory with Node adapter) +- Caddy: wildcard SSL via Let's Encrypt +- Each tenant uses ~50MB RAM idle, ~200MB under load + +100 tenants Γ— 50MB = 5GB RAM β€” fits on an 8GB server with headroom. + +### Multi Server (100+ tenants) + +- Separate DB server (managed MariaDB or dedicated VPS) +- Multiple app servers behind a load balancer +- Shared file storage (Cloudflare R2 or mounted volume) +- Caddy on each app server or a dedicated proxy + +### Image Storage + +| Scale | Strategy | Cost | +|-------|----------|------| +| 0-50 tenants | Local disk | $0 (included in VPS) | +| 50-200 tenants | Cloudflare R2 | ~$1-5/mo (zero egress) | +| 200+ tenants | R2 + CDN | ~$5-20/mo | + +Average rescue: ~300MB of images. 100 rescues = 30GB = $0.45/mo on R2. + +## Tenant Management + +### Admin Dashboard (`admin.pawprint.app`) + +The PawPrint operator (you) gets a meta-dashboard: + +- **Tenants list** β€” name, subdomain, plan, status, created date, last active +- **Provisioning** β€” create new tenant (runs automation) +- **Billing** β€” Stripe subscription status per tenant +- **Health** β€” systemd service status, DB size, disk usage +- **Maintenance** β€” run migrations across all tenants, restart services + +### Database Schema (Meta) + +The admin dashboard has its own database (`pawprint_admin`): + +```sql +CREATE TABLE tenants ( + id INT PRIMARY KEY AUTO_INCREMENT, + name VARCHAR(255) NOT NULL, -- "Almost Home Canine Rescue" + slug VARCHAR(100) NOT NULL UNIQUE, -- "almosthome" + subdomain VARCHAR(100) NOT NULL UNIQUE, -- "almosthome.pawprint.app" + custom_domain VARCHAR(255), -- "almosthomecaninerescue.com" + db_name VARCHAR(100) NOT NULL, -- "pp_almosthome" + port INT NOT NULL, -- 3001 + admin_email VARCHAR(255) NOT NULL, + admin_name VARCHAR(255) NOT NULL, + plan ENUM('free_trial', 'cloud', 'self_hosted') DEFAULT 'free_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 +); + +CREATE TABLE tenant_events ( + id INT PRIMARY KEY AUTO_INCREMENT, + tenant_id INT NOT NULL REFERENCES tenants(id), + event VARCHAR(50) NOT NULL, -- "provisioned", "migrated", "suspended", etc. + details JSON, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); +``` + +## Billing + +### Stripe Integration + +- **Product:** PawPrint 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` β†’ send warning email, suspend after 3 failures +- **Dunning:** Stripe handles retry logic + +### Lifecycle + +``` +Signup β†’ Trial (14 days) β†’ Active ($19/mo) β†’ ... + β†’ Expired β†’ Suspended (7 days) β†’ Deleted +``` + +Suspended tenants: +- App still running but shows "account suspended" page +- Data preserved for 30 days +- Reactivate by paying + +## Custom Domains + +Tenants can optionally use their own domain: + +1. Tenant sets custom domain in their settings +2. They add a CNAME record: `www.theirrescue.com β†’ almosthome.pawprint.app` +3. Caddy auto-provisions SSL via ACME +4. We add the domain to the Caddy config + +``` +theirrescue.com { + reverse_proxy localhost:3001 +} +``` + +## Security Considerations + +- Each tenant's DB user should only have access to their own database +- .env files are per-tenant with unique SESSION_SECRET +- Admin dashboard requires separate auth (not tenant auth) +- Tenant data is never mixed β€” complete isolation +- Backups are per-tenant (can restore one without affecting others) +- Rate limiting is per-tenant (one rescue getting hammered doesn't affect others) + +## Migration Path + +### From WordPress +1. Export pets from WP (CSV or WP REST API) +2. Map fields to PawPrint schema +3. Import via PawPrint's CSV import tool +4. Download and re-upload photos + +### From Shelterluv / RescueGroups +1. Export data as CSV +2. Map to PawPrint schema +3. Import tool handles the rest + +### From Petfinder +1. If they have API access: use REST API to pull pets +2. If not: manual CSV export or widget scraping +3. Note: Petfinder killed their API in 2025, so most rescues can't use it + +## Development Workflow + +### Updating the App + +When we push updates to the PawPrint app: + +```bash +# On the server +for tenant in $(cat /etc/pawprint/tenants.list); do + cd /var/www/pawprint/$tenant + git pull + npm install + npm run build + systemctl restart pawprint-$tenant +done +``` + +Or better: blue-green deploys per tenant. Build once, symlink to each tenant. + +### Running Migrations + +```bash +for tenant in $(cat /etc/pawprint/tenants.list); do + DB_NAME=$(grep DATABASE_URL /var/www/pawprint/$tenant/.env | cut -d/ -f4) + mysql $DB_NAME < migrations/0009_new_feature.sql +done +``` diff --git a/docs/TEMPLATE_EXTRACTION.md b/docs/TEMPLATE_EXTRACTION.md new file mode 100644 index 0000000..0bbcc43 --- /dev/null +++ b/docs/TEMPLATE_EXTRACTION.md @@ -0,0 +1,136 @@ +# Template Extraction Plan + +## What to Pull from AHCR + +The AHCR codebase (`almosthomecaninerescue.com-2026/ahcr`) is the source. We extract and genericize. + +### Copy As-Is (Core Framework) +- SvelteKit config, vite config, tsconfig +- Drizzle ORM setup and schema (strip AHCR-specific data) +- Auth system (sessions, Argon2, roles) +- Rate limiting +- Audit logging +- File upload handler +- Email system (Nodemailer) +- Stripe integration +- Bot protection (tracking, robots.txt, honeypot) + +### Genericize (Replace AHCR-Specific) + +| AHCR-Specific | PawPrint Generic | +|---|---| +| "Almost Home Canine Rescue" | `ORG_NAME` env var | +| Sioux Falls, SD references | `ORG_LOCATION` env var | +| SD17 Petfinder org ID | `PETFINDER_ORG_ID` env var (optional) | +| Teal color scheme (#0d9488) | Configurable via setup wizard, stored in site_settings | +| AHCR logo / images | Placeholder logo, upload your own | +| 16 adoption agreement items | Configurable JSON in site_settings | +| Foster application agreements | Configurable JSON | +| info@almosthomecaninerescue.com | `SMTP_FROM` env var | +| GiveButter donation link | Generic donate page with configurable URL | +| Facebook app ID / secrets | Per-tenant config | +| Specific intake sources | Seed with common defaults, editable in admin | +| Content blocks (home, about, etc.) | Seed with placeholder content | + +### Remove (AHCR Data) +- database_dump.sql +- decoded_lewodi.php (malware artifact) +- CSV import data and scripts (AHCR-specific) +- Petfinder scraper (API is dead) +- docs/csv-import-data-issues.md +- docs/deleted-junk-pets.md +- All pet data, user data, etc. + +### Add (New for Template) +- Setup wizard (first-login onboarding) +- `org_config` table or expand site_settings for org-level config +- Configurable color theme (CSS custom properties) +- Configurable application form questions +- Seed script with placeholder data (example pets, example events) +- Docker Compose file +- Landing page / marketing site (separate from rescue site) +- Health check endpoint (`/api/health`) +- Tenant provisioning scripts + +## File-by-File Extraction Checklist + +### Config Files +- [ ] package.json β€” update name, description, remove AHCR references +- [ ] svelte.config.js β€” keep as-is +- [ ] vite.config.ts β€” keep as-is +- [ ] tsconfig.json β€” keep as-is +- [ ] drizzle.config.ts β€” keep as-is +- [ ] .env.example β€” genericize all values +- [ ] .gitignore β€” keep, add uploads/ + +### Schema +- [ ] src/lib/server/schema.ts β€” keep all tables, add org_config if needed +- [ ] drizzle/ migrations β€” regenerate clean from schema + +### Auth & Roles +- [ ] src/lib/roles.ts β€” keep all roles +- [ ] src/lib/server/auth.ts β€” keep as-is +- [ ] src/lib/server/auth-utils.ts β€” keep as-is +- [ ] src/hooks.server.ts β€” keep, genericize any hardcoded strings + +### Server Utilities +- [ ] src/lib/server/db.ts β€” keep as-is +- [ ] src/lib/server/email.ts β€” genericize from address, org name in templates +- [ ] src/lib/server/stripe.ts β€” keep as-is +- [ ] src/lib/server/upload.ts β€” keep as-is +- [ ] src/lib/server/audit.ts β€” keep as-is +- [ ] src/lib/server/track.ts β€” keep as-is +- [ ] src/lib/server/rate-limit.ts β€” keep as-is +- [ ] src/lib/server/content.ts β€” keep as-is +- [ ] src/lib/server/facebook.ts β€” keep as-is +- [ ] src/lib/server/contract.ts β€” genericize org name in contract template +- [ ] src/lib/server/slugify.ts β€” keep as-is + +### Routes β€” Keep & Genericize +- [ ] All admin routes β€” keep, genericize any AHCR text +- [ ] All public routes β€” keep, replace hardcoded content with CMS blocks +- [ ] Foster portal β€” keep as-is +- [ ] API routes β€” keep as-is +- [ ] robots.txt β€” keep dynamic version +- [ ] sitemap.xml β€” keep, genericize + +### Routes β€” Remove +- [ ] src/lib/server/csv-import.ts β€” AHCR-specific +- [ ] src/lib/server/run-import.ts β€” AHCR-specific +- [ ] src/routes/admin/import/ β€” replace with generic CSV import + +### Components +- [ ] All UI components β€” keep as-is +- [ ] Header/Footer β€” genericize org name, links +- [ ] Replace any hardcoded "Almost Home" text + +### Static Assets +- [ ] Remove AHCR logos and images +- [ ] Add placeholder logo +- [ ] Keep favicon structure (replace with paw icon) + +### New Files to Create +- [ ] scripts/provision-tenant.sh +- [ ] scripts/deprovision-tenant.sh +- [ ] scripts/seed-tenant.js +- [ ] scripts/send-welcome.js +- [ ] scripts/migrate-all.sh +- [ ] docker-compose.yml +- [ ] Dockerfile +- [ ] Setup wizard route (/setup) +- [ ] Health check endpoint (/api/health) +- [ ] Landing/marketing page (separate or at /) + +## Estimated Effort + +| Task | Time | +|------|------| +| Extract and genericize core app | 4-6 hours | +| Setup wizard | 2-3 hours | +| Docker Compose | 1 hour | +| Provisioning scripts | 2-3 hours | +| Admin meta-dashboard | 4-6 hours | +| Landing page | 2-3 hours | +| Stripe billing integration | 2-3 hours | +| Testing & polish | 3-4 hours | +| **Total** | **~20-30 hours** |