Jun 27 Update documents
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# JQC — Janitorial Quality Control System
|
||||
|
||||
A production-grade web application for managing janitorial service contracts, facility inspections, issue tracking, and client reporting.
|
||||
A production-grade, multi-tenant web application for managing janitorial service contracts, facility inspections, issue tracking, and client reporting. Built as a shared-codebase SaaS with database-per-tenant isolation.
|
||||
|
||||
---
|
||||
|
||||
@@ -169,6 +169,7 @@ After=network.target mysql.service
|
||||
User=jqc
|
||||
WorkingDirectory=/home/jqc/lt_janitorial_quality_control
|
||||
EnvironmentFile=/home/jqc/.env
|
||||
EnvironmentFile=/etc/jqc/control.env # control-plane + multi-tenant vars
|
||||
ExecStart=/home/jqc/venv/bin/gunicorn -c gunicorn_config.py wsgi:app
|
||||
Restart=on-failure
|
||||
|
||||
@@ -176,6 +177,8 @@ Restart=on-failure
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
> `/etc/jqc/control.env` must be `chmod 640 / chown root:jqc`. It holds `CONTROL_DATABASE_URL`, `CONTROL_FERNET_KEY`, `PROVISION_DB_URL`, `TENANT_BASE_DOMAIN`, and `MULTI_TENANT_ENABLED`. The same file must be sourced in any shell session that runs provisioning commands (`set -a; . /etc/jqc/control.env; set +a`).
|
||||
|
||||
---
|
||||
|
||||
## Cron Jobs
|
||||
@@ -260,7 +263,7 @@ Access tokens expire after 60 minutes. Refresh tokens are valid for 30 days and
|
||||
## Database Migrations
|
||||
|
||||
```bash
|
||||
# Apply all pending migrations
|
||||
# Apply all pending migrations (existing / single-tenant DB)
|
||||
flask db upgrade
|
||||
|
||||
# Create a new migration after model changes
|
||||
@@ -270,6 +273,21 @@ flask db migrate -m "description of change"
|
||||
flask db downgrade
|
||||
```
|
||||
|
||||
### Multi-tenant migrations
|
||||
|
||||
```bash
|
||||
# Bootstrap a brand-new tenant DB (baseline schema + stamp head)
|
||||
python -m control.tenant_migrate bootstrap --tenant <slug>
|
||||
|
||||
# Incremental upgrade for all active tenants (phase33+ onwards)
|
||||
python -m control.tenant_migrate upgrade --tenant all
|
||||
|
||||
# Check each tenant's current revision vs chain head
|
||||
python -m control.tenant_migrate current --tenant all
|
||||
```
|
||||
|
||||
> **Never run `flask db upgrade` on an empty tenant database.** Fourteen historical phase migrations lack `INFORMATION_SCHEMA` guards and will fail against a DB that already has the baseline schema. Use `bootstrap` instead.
|
||||
|
||||
### MySQL Compatibility Notes
|
||||
|
||||
- **ENUM changes** require three steps: expand → migrate data → contract. Never skip steps.
|
||||
@@ -304,6 +322,64 @@ flask db downgrade
|
||||
| `REDIS_URL` | — | Redis connection URI for shared rate-limit storage; optional but recommended in production |
|
||||
| `GROQ_API_KEY` | — | Groq API key. When absent the AI chatbot is disabled; customers can still submit support tickets. |
|
||||
| `GROQ_MODEL` | `llama-3.3-70b-versatile` | Groq model ID override |
|
||||
| `MULTI_TENANT_ENABLED` | `false` | Set `true` to activate Host→tenant routing. Requires the four control-plane vars below. |
|
||||
| `CONTROL_DATABASE_URL` | — | Control-plane DB URI, e.g. `mysql+pymysql://jqc_control:pw@127.0.0.1/jqc_control` |
|
||||
| `CONTROL_FERNET_KEY` | — | Fernet key for encrypting tenant DB passwords. Generate once and store permanently. |
|
||||
| `PROVISION_DB_URL` | — | MySQL account with `CREATE DATABASE`/`CREATE USER`/`GRANT`. |
|
||||
| `TENANT_BASE_DOMAIN` | — | Subdomain apex, e.g. `jqc.app`. |
|
||||
|
||||
---
|
||||
|
||||
## Multi-Tenant Setup
|
||||
|
||||
JQC runs as a shared-codebase SaaS with database-per-tenant isolation. Each tenant gets its own MySQL database and least-privilege MySQL user. The control plane (`control/`) manages the tenant registry separately from any tenant's data.
|
||||
|
||||
### Prerequisites
|
||||
|
||||
```sql
|
||||
-- Control database
|
||||
CREATE DATABASE jqc_control CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
CREATE USER 'jqc_control'@'localhost' IDENTIFIED BY '<pw>';
|
||||
GRANT ALL PRIVILEGES ON jqc_control.* TO 'jqc_control'@'localhost'; FLUSH PRIVILEGES;
|
||||
|
||||
-- Provisioner account (creates per-tenant DBs + users)
|
||||
CREATE USER 'jqc_provisioner'@'localhost' IDENTIFIED BY '<pw>';
|
||||
GRANT ALL PRIVILEGES ON *.* TO 'jqc_provisioner'@'localhost' WITH GRANT OPTION;
|
||||
GRANT CREATE USER ON *.* TO 'jqc_provisioner'@'localhost'; FLUSH PRIVILEGES;
|
||||
```
|
||||
|
||||
### Bootstrap (run once)
|
||||
|
||||
```bash
|
||||
# Set env (or add to /etc/jqc/control.env)
|
||||
export CONTROL_DATABASE_URL='mysql+pymysql://jqc_control:<pw>@127.0.0.1/jqc_control'
|
||||
export CONTROL_FERNET_KEY="$(python -c 'from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())')"
|
||||
export PROVISION_DB_URL='mysql+pymysql://jqc_provisioner:<pw>@127.0.0.1/'
|
||||
export TENANT_BASE_DOMAIN='jqc.app'
|
||||
|
||||
# Apply control schema + seed plans + first superadmin
|
||||
alembic -c control/migrations/alembic.ini upgrade head
|
||||
python -m control.cli seed
|
||||
python -m control.cli create-superadmin --username admin --email you@example.com
|
||||
|
||||
# Adopt existing LT database as tenant-zero (no data moved)
|
||||
python -m control.provision register-tenant-zero \
|
||||
--slug lts --name "LT Services" --plan enterprise \
|
||||
--db-host 127.0.0.1 --db-name <LT_DB> --db-user <LT_USER> --db-password '<pw>' \
|
||||
--custom-domain jqc.ltservicesinc.com
|
||||
|
||||
# Provision a new tenant
|
||||
python -m control.provision create-tenant \
|
||||
--slug acme --name "Acme Corp" --plan pro --admin-email ops@acme.com
|
||||
```
|
||||
|
||||
### Enable multi-tenancy
|
||||
|
||||
1. Add `MULTI_TENANT_ENABLED=true` to `/etc/jqc/control.env`
|
||||
2. Configure wildcard DNS: `*.jqc.app A <SERVER_IP>`
|
||||
3. Add Nginx wildcard server block (see `CLAUDE.md §19`)
|
||||
4. Obtain wildcard TLS cert via DNS-01 challenge
|
||||
5. `sudo systemctl daemon-reload && sudo systemctl restart jqc`
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user