# Performance West — Development Guidelines ## Deployment Rules - **NEVER** edit files in `/tmp/` — always edit in this project directory - **NEVER** scp individual files to dev/prod — always commit and deploy via git - After editing any file, commit it: `git add && 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 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 ``` ### Run migrations ```bash # 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 - **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**: 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) - `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, research ## Key Patterns ### 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/.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 `