Update CLAUDE.md with complete deployment guide, infrastructure map, and key patterns

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
justin 2026-04-28 02:54:44 -05:00
parent 134e84177b
commit 6b569b52fe

113
CLAUDE.md
View file

@ -6,43 +6,124 @@
- **NEVER** scp individual files to dev/prod — always commit and deploy via git
- After editing any file, commit it: `git add <file> && git commit -m "description"`
- All source code lives in this repo and is deployed via `git pull` on the server
- The server has NO `.env` in git — secrets are in `/opt/performancewest/.env` and `/opt/performancewest-dev/.env` on the server
### Deploy workflow
```bash
# 1. Make changes locally
# 2. Commit and push
git add -A && git commit -m "description" && git push origin main
# 3. SSH to server and deploy
ssh -p 22022 deploy@207.174.124.71
cd /opt/performancewest && ./deploy.sh # rebuild all (site, api, workers)
cd /opt/performancewest && ./deploy.sh site # rebuild just site
cd /opt/performancewest && ./deploy.sh api # rebuild just api
```
### Deploy to dev
```bash
./scripts/deploy.sh dev
ssh -p 22022 deploy@207.174.124.71
cd /opt/performancewest-dev
git pull origin main
docker compose build site api workers
docker compose up -d site api workers
```
### Deploy to prod
### Run migrations
```bash
./scripts/deploy.sh prod
# Copy SQL into container and run via node
docker cp api/migrations/074_xxx.sql performancewest-api-1:/tmp/
docker exec performancewest-api-1 node -e "const fs=require('fs');const{Pool}=require('pg');const p=new Pool({connectionString:process.env.DATABASE_URL});p.query(fs.readFileSync('/tmp/074_xxx.sql','utf8')).then(()=>{console.log('OK');p.end()}).catch(e=>{console.log(e.message);p.end()})"
```
## Git Server
- **URL**: https://git.performancewest.net
- **Repo**: performancewest/new-site
- **SSH clone**: `git clone ssh://git@git.performancewest.net:2222/performancewest/new-site.git`
- **Remote**: `ssh://git@git.performancewest.net:2222/justin/new-site.git`
- **Branch**: `main`
- **Prod**: `/opt/performancewest/` tracks `origin/main`
- **Dev**: `/opt/performancewest-dev/` tracks `origin/main`
## Infrastructure
- **Prod server**: `deploy@207.174.124.71:22022``/opt/performancewest/`
- **Dev server**: same host → `/opt/performancewest-dev/`
- **HestiaCP**: `root@cp.carrierone.com:22022` (DNS, email provisioning)
- **Docker Compose**: all services run in containers (API, site, workers, postgres, etc.)
- **Docker Compose**: prod has full stack (api, site, workers, postgres, erpnext, minio, listmonk, umami, ollama); dev has api/site/workers/postgres + connects to prod network for shared services
### Prod containers
- `performancewest-api-1` — Express API (port 3001)
- `performancewest-site-1` — Astro static site (port 4322)
- `performancewest-workers-1` — Python job server (port 8090)
- `performancewest-api-postgres-1` — PostgreSQL (port 5432)
- `performancewest-erpnext-1` — ERPNext CRM (port 8080)
- `performancewest-minio-1` — MinIO object storage (port 9000)
- `performancewest-listmonk-1` — Email marketing (port 9100)
- `performancewest-ollama-1` — LLM (port 11434)
- `performancewest-umami-1` — Analytics (port 3100)
### Dev containers
- `performancewest-dev-api-1` — port 3002
- `performancewest-dev-site-1` — port 4323
- `performancewest-dev-workers-1` — port 8090 (internal)
- `performancewest-dev-api-postgres-1` — port 5433
- Dev connects to `performancewest_default` network for ERPNext, MinIO, Listmonk, Ollama
### Dev uses Stripe test mode
- `NODE_ENV=development` → API auto-selects `STRIPE_TEST_SECRET_KEY`
- Test card: `4242 4242 4242 4242`, any future expiry, any CVC
- PayPal sandbox: `api-m.sandbox.paypal.com`
## Project Structure
- `api/` — Express.js API (TypeScript)
- `site/` — Astro static site (pages, components, layouts)
- `api/migrations/` — SQL migration files (run manually, numbered 001-074+)
- `api/src/routes/` — API route handlers
- `site/` — Astro static site
- `site/src/pages/` — Astro pages (compile to HTML)
- `site/src/components/` — Astro components
- `site/public/` — Static files served as-is (tools, order pages)
- `site/public/_astro/` — Astro build artifacts (hoisted JS) — do NOT edit these directly
- `scripts/` — Python workers, document generators, scrapers
- `scripts/workers/services/` — Compliance service handlers (one per service)
- `scripts/workers/job_server.py` — Main worker job dispatch
- `scripts/document_gen/templates/` — DOCX/PDF generators
- `scripts/formation/` — State formation adapters, entity cache
- `infra/` — Ansible playbooks, nginx configs
- `docs/` — Product documentation
- `docs/` — Product documentation, research
## Site Pages
## Key Patterns
Site source files live in `site/src/pages/`. The site uses:
- Layout: `site/src/layouts/Base.astro`
- Components: `site/src/components/`
- Tailwind CSS with `pw-` custom color palette
- Inline `<script>` for interactivity (no React/Vue)
- API base URL: `(window as any).__PW_API`
### Service catalog
Services are defined in `api/src/routes/compliance-orders.ts``COMPLIANCE_SERVICES` dict. Each service needs:
- Entry in `COMPLIANCE_SERVICES` (slug, name, price, erpnext_item)
- Handler class in `scripts/workers/services/` registered in `__init__.py``SERVICE_HANDLERS`
- ERPNext Item created in the CRM
### Intake pages
- Astro pages at `site/src/pages/order/<slug>.astro` — these are POST-PAYMENT intake forms
- Do NOT show prices on intake pages — the client has already paid
- Steps defined in `site/src/lib/intake_manifest.ts`
- The "payment" step in the manifest is for standalone orders only; batch orders skip it
### Static tool pages
- `site/public/tools/fcc-compliance-check/index.html` — static HTML, NOT an Astro page
- Uses `/_astro/hoisted.aBLqmOPy.js` which is in `site/public/_astro/` (passed through by Astro build)
- Enhancement script in the inline `<script>` at the bottom of the HTML
- Tailwind classes from `public/` files may not be in the compiled CSS — use inline styles for dynamic elements
### Order pages
- `site/public/order/fcc-compliance/index.html` — batch compliance checkout (static HTML)
- `site/public/order/state-puc/index.html` — State PUC order page
- `site/public/order/neca-ocn/index.html` — NECA OCN order page
- These use the 2-step checkout: POST `/api/v1/compliance-orders` or `/batch` → POST `/api/v1/checkout/create-session`
### Cron jobs
- Defined in `infra/ansible/roles/worker-crons/defaults/main.yml`
- Deployed as systemd timers via `infra/ansible/playbooks/deploy-crons.yml`
- Run inside the workers container: `docker compose exec -T workers python -m <module>`
### Emails
- Batch orders: `sendComplianceIntakeEmail` in checkout.ts (DO NOT also send `sendOrderConfirmationEmail` for batches)
- Intake emails from handlers: deduped per batch — only first order (lowest order_number) sends
- RMD review email: sent by `rmd_filing.py` handler after filing is assembled