Jun 29 - Update CLAUDE.md and create SERVER_DUPLICATION.md

This commit is contained in:
2026-06-29 09:45:30 -04:00
parent a42fc31fc5
commit 42790c499c
2 changed files with 1559 additions and 8 deletions
+161 -8
View File
@@ -2,7 +2,7 @@
> **Audience:** AI assistants and developers working on this codebase.
> **Purpose:** Authoritative reference for architecture, conventions, gotchas, and decisions.
> **Last reviewed:** June 2026 (Phase 19 complete + mobile API gap-fill Phases AE + customer UI refinements + Phase 22 comment visibility + Phase 23 support chat/tickets + inspector performance Excel export + inspection list filters + customer issue logging + AI chatbot + dashboard grouped sections + issues/inspections PDF export + date/ID filters + Reports expansion Phases R1R4 + Phase 24 issue_created notify defaults + Phase 25 inspection GPS + Phase 26 issue vendor fields + Phase 27 facility score alerts + **MT-0 through MT-7 complete; MT-8 flag-gated; MT-9 iOS pending**)
> **Last reviewed:** June 2026 (Phase 19 complete + mobile API gap-fill Phases AE + customer UI refinements + Phase 22 comment visibility + Phase 23 support chat/tickets + inspector performance Excel export + inspection list filters + customer issue logging + AI chatbot + dashboard grouped sections + issues/inspections PDF export + date/ID filters + Reports expansion Phases R1R4 + Phase 24 issue_created notify defaults + Phase 25 inspection GPS + Phase 26 issue vendor fields + Phase 27 facility score alerts + **MT-0 through MT-8 complete; self-service signup; trial enforcement; billing emails; invoice history; superadmin billing controls; per-tenant backup CLI; health dashboard; MT-9 iOS pending**)
---
@@ -29,8 +29,11 @@
19. [Infrastructure](#19-infrastructure)
20. [Known Constraints & Hard Rules](#20-known-constraints--hard-rules)
21. [Change Philosophy](#21-change-philosophy)
22. [Multi-Tenant Architecture (MT-0 → MT-7)](#22-multi-tenant-architecture-mt-0--mt-7)
23. [Coding Rules for AI Assistants](#23-coding-rules-for-ai-assistants)
22. [Multi-Tenant Architecture (MT-0 → MT-8)](#22-multi-tenant-architecture-mt-0--mt-8)
23. [Billing System (MT-8)](#23-billing-system-mt-8)
24. [Backup CLI](#24-backup-cli-controlbackuppy)
25. [Health Dashboard](#25-health-dashboard-health-on-panel)
26. [Coding Rules for AI Assistants](#26-coding-rules-for-ai-assistants)
---
@@ -48,6 +51,8 @@
- **Support chat** — Groq AI chatbot for customers with preset FAQ chips; escalation to admin via ticketing system; customers can view and reply to their own tickets; admins manage tickets at `/support/admin/tickets`
- **Mobile API** — JWT-authenticated REST layer for the iPad native app
- **iPad native app** — SwiftUI + SwiftData offline-first inspection tool (Phase A + B + C complete)
- **Billing** — Stripe-backed subscription system with plan picker, Checkout, Customer Portal, dunning emails (HTML + plain text), invoice history, and trial-period enforcement
- **Self-service signup** — public `/signup` page provisions a new tenant immediately with a 14-day trial (no Stripe required at signup)
The application is actively deployed in production and maintained by a single developer/administrator.
@@ -115,11 +120,23 @@ lt_janitorial_quality_control/
│ │ └── ...
│ ├── routes/
│ │ ├── support.py # /support/* — AI chat, ticket submit/list/detail (Phase 23)
│ │ ├── tenant_settings.py # MT-7: /settings/* — branding, plan view, domain mgmt (@admin_required)
│ │ ├── tenant_settings.py # MT-7: /settings/* — branding, plan view (+ invoice history), domain mgmt (@admin_required)
│ │ ├── signup.py # public /signup — self-service tenant signup (no auth, no tenant context)
│ │ └── ...
│ ├── templates/
│ │ ├── _quota_warning.html # MT-5: reusable quota exceeded banner partial
│ │ ├── tenant_settings/ # MT-7: branding.html, plan.html, domains.html
│ │ ├── billing/
│ │ │ ├── _billing_banner.html # past_due + trial_ending warning banners
│ │ │ ├── plan_picker.html # plan selection page (Starter/Pro/Enterprise cards)
│ │ │ └── email/ # HTML billing lifecycle emails
│ │ │ ├── base.html # shared branded email layout
│ │ │ ├── payment_failed.html
│ │ │ ├── trial_ending.html
│ │ │ └── subscription_cancelled.html
│ │ ├── signup/
│ │ │ ├── index.html # self-service signup form (standalone, no base.html)
│ │ │ └── success.html # post-signup confirmation
│ │ └── ...
│ ├── static/
│ │ └── uploads/ # UPLOAD_FOLDER root
@@ -163,6 +180,7 @@ lt_janitorial_quality_control/
│ ├── models.py # Plan, PlanFeature, Tenant, TenantDomain,
│ │ # Superadmin, ProvisioningJob, TenantAudit
│ ├── provision.py # MT-3 — create_tenant / register_tenant_zero / delete_tenant
│ ├── backup.py # per-tenant mysqldump backup CLI (python -m control.backup)
│ ├── seed.py # idempotent plan seeder
│ ├── tenant_migrate.py # MT-2 — upgrade_tenant / bootstrap_tenant / chain_head CLI
│ ├── time_utils.py # now_eastern() mirror (no app import)
@@ -171,9 +189,10 @@ lt_janitorial_quality_control/
│ │ ├── auth.py # /login, /logout (session-based, no Flask-Login)
│ │ ├── decorators.py # @superadmin_required
│ │ ├── impersonate.py # HMAC-SHA256 signed token generator/validator
│ │ ├── tenants.py # CRUD + plan/suspend/resume/domain/migrate/provision routes
│ │ ├── tenants.py # CRUD + plan/suspend/resume/domain/migrate/provision/billing routes
│ │ ├── health.py # GET /health/ — monitoring dashboard (tenant status, schema, trials)
│ │ ├── wsgi_panel.py # WSGI entry point — Gunicorn on port 8001
│ │ └── templates/panel/ # base.html, login.html, tenants_list.html, tenant_detail.html, provision.html
│ │ └── templates/panel/ # base.html, login.html, tenants_list.html, tenant_detail.html, provision.html, health.html
│ └── migrations/ # control Alembic chain (control{N}_…)
│ └── versions/control0001_init.py ← HEAD
└── ...
@@ -210,6 +229,13 @@ lt_janitorial_quality_control/
| `TENANT_ENGINE_POOL_RECYCLE` | Pool recycle in seconds (default 1800). |
| `PANEL_SECRET_KEY` | Flask secret for the superadmin panel app (separate from `SECRET_KEY`). Generate: `python -c "import secrets; print(secrets.token_hex(32))"` |
| `PANEL_IMPERSONATE_KEY` | HMAC key for impersonation tokens. Must be identical between panel and main app env. Generate same way. |
| `BILLING_ENABLED` | `false` by default. Set `true` to activate Stripe billing UI, gate, and webhooks. |
| `STRIPE_SECRET_KEY` | Stripe secret key (`sk_live_…` or `sk_test_…`). Required when `BILLING_ENABLED=true`. |
| `STRIPE_PUBLISHABLE_KEY` | Stripe publishable key (`pk_…`). Used in Checkout redirect. |
| `STRIPE_WEBHOOK_SECRET` | Stripe webhook signing secret (`whsec_…`). Validates webhook payloads. |
| `STRIPE_PRICE_STARTER` | Stripe Price ID for the Starter plan (`price_…`). Set via `python -m control.cli seed`. |
| `STRIPE_PRICE_PRO` | Stripe Price ID for the Pro plan. |
| `STRIPE_PRICE_ENTERPRISE` | Stripe Price ID for the Enterprise plan. |
### Email SSL Auto-Detection
@@ -439,6 +465,8 @@ api_device_tokens: id, user_id, device_id, apns_token, device_name, app_version
| `api_comments` | `/api/v1` | `GET /issues/<id>/comments`, `POST /issues/<id>/comments` (Phase D) |
| `api_discovery` | `/api/v1` | `GET /discover?subdomain=` or `?email=`, `GET /tenant` — MT-9 server side (public, no auth, exempt from tenant middleware) |
| `tenant_settings` | `/settings` | MT-7: `GET/POST /branding`, `GET /plan`, `GET /domains`, `POST /domains/request`, `POST /domains/<id>/delete` |
| `billing` | `/billing` | `GET /subscribe`, `POST /subscribe`, `GET /portal`, `POST /webhook`, `GET /suspended` |
| `signup` | `/signup` | `GET /`, `POST /` — public self-service signup (exempt from tenant middleware) |
---
@@ -1174,7 +1202,7 @@ set -a; . /etc/jqc/control.env; set +a
---
## 22. Multi-Tenant Architecture (MT-0 → MT-7)
## 22. Multi-Tenant Architecture (MT-0 → MT-8)
See `MULTI_TENANT_PLAN.md` for the full phased roadmap. This section summarises what is built and operational.
@@ -1331,7 +1359,132 @@ curl -sI -H "Host: ztest.jqc.app" http://127.0.0.1:8000/ | head -2
---
## 23. Coding Rules for AI Assistants
## 23. Billing System (MT-8)
### Overview
Stripe-backed subscription billing. Controlled by `BILLING_ENABLED` env var (default `false`). When enabled, a `_billing_gate()` runs in `init_tenancy()` before every request and enforces trial expiry and cancellation state.
### Key files
| File | Purpose |
|---|---|
| `app/billing/__init__.py` | Blueprint registration |
| `app/billing/routes.py` | `subscribe`, `portal`, `webhook`, `suspended` routes |
| `app/billing/webhooks.py` | Stripe event handlers (`payment_failed`, `subscription_updated`, `subscription_deleted`) |
| `app/billing/emails.py` | `send_billing_email()` — HTML + plain text dunning emails in background thread |
| `app/templates/billing/` | Plan picker, billing banner partial, HTML email templates |
| `control/seed.py` | Reads `STRIPE_PRICE_*` from env and writes to `Plan.stripe_price_id` |
### Billing gate (`_billing_gate()` in `app/tenancy/middleware.py`)
| `subscription_status` | Behaviour |
|---|---|
| `None` or `active` | Allow through |
| `trial` (not expired) | Allow; set `g.billing_warning = 'trial_ending'` if ≤ 3 days remain |
| `trial` (expired) | Redirect to `/billing/subscribe` (except `/billing/` and `/settings/`) |
| `past_due` | Allow; set `g.billing_warning = 'past_due'` |
| `cancelled` | Redirect to `/billing/suspended` |
### Trial period
- New tenants provisioned via `create_tenant()` get `subscription_status='trial'`, `trial_ends_at = now + 14 days` by default.
- Self-service signup (`/signup`) provisions a tenant immediately with a 14-day trial.
- Trial enforcement is in `_billing_gate()` — no Stripe required until they subscribe.
### Billing emails (`app/billing/emails.py`)
`send_billing_email(to_addr, event_type, context_dict)` sends multipart HTML + plain text.
| `event_type` | Trigger | Required context keys |
|---|---|---|
| `payment_failed` | `invoice.payment_failed` Stripe webhook | `portal_url` |
| `trial_ending` | Cron (not yet wired — see §11 cron table) | `trial_ends_at`, `subscribe_url` |
| `subscription_cancelled` | `customer.subscription.deleted` Stripe webhook | `portal_url` |
HTML templates live in `app/templates/billing/email/`. `base.html` provides the branded layout; each event type extends it.
### Plan seeding
```bash
# Add to .env:
STRIPE_PRICE_STARTER=price_...
STRIPE_PRICE_PRO=price_...
STRIPE_PRICE_ENTERPRISE=price_...
# Then run:
python -m control.cli seed
python -m control.cli list-plans # verify price IDs
```
### Superadmin billing controls (panel `POST /tenants/<id>/billing`)
| Action | Form field | Effect |
|---|---|---|
| `set_trial` | `trial_days` (int) | Sets `subscription_status=trial`, `trial_ends_at=now+N` |
| `set_status` | `subscription_status` | Overrides status directly |
| `apply_coupon` | `coupon_id` | Calls `stripe.Customer.modify(..., coupon=id)` |
All three actions write a `TenantAudit` row.
### Invoice history
`GET /settings/plan` fetches last 10 Stripe invoices for the tenant's `stripe_customer_id` and passes them to `plan.html`. Shows date, amount, status badge, PDF link, hosted invoice link.
### Self-service signup (`/signup`)
Public route, exempt from tenant middleware. `SignupForm` validates:
- Company name, full name, email
- Subdomain: DNS label regex + uniqueness check against control DB
- Plan picker (Starter / Pro / Enterprise)
- Password + confirm
On submit calls `create_tenant(admin_password=pw, trial_days=14)`. Shows `signup/success.html` with workspace URL and trial end date.
---
## 24. Backup CLI (`control/backup.py`)
Per-tenant MySQL backup using `mysqldump`.
```bash
# Back up all tenants
python -m control.backup --tenant all --output-dir /var/backups/jqc
# Back up one tenant
python -m control.backup --tenant acme --output-dir /var/backups/jqc
# List tenants
python -m control.backup --list
```
Output: `<slug>_YYYYMMDD_HHMMSS.sql.gz` (gzip-compressed SQL dump).
Flags passed to `mysqldump`: `--single-transaction`, `--routines`, `--triggers`, `--set-gtid-purged=OFF`.
**Cron for nightly backup:**
```bash
0 2 * * * set -a; . /etc/jqc/control.env; set +a; \
python -m control.backup --tenant all --output-dir /var/backups/jqc
```
---
## 25. Health Dashboard (`/health/` on panel)
New nav item in the superadmin panel sidebar. Reads control DB only — no per-tenant DB queries.
Shows:
- Summary cards: total tenants, active, suspended, schema behind
- Subscription breakdown (count per status)
- Trial alerts: expired trials, trials expiring within 3 days
- Full tenant table: slug, name, status, plan, subscription status, trial expiry countdown, Stripe presence, schema freshness, created date
Row highlights: yellow = suspended, red = trial expired.
---
## 26. Coding Rules for AI Assistants
These rules apply to every change made to this codebase, without exception.
File diff suppressed because it is too large Load Diff