# BizMatch Phase 2 — Broker Workflow App Module 1: foundation (Docker Compose, PostgreSQL, schema, migrations, login). Module 2: business scan (NAS -> DB) and the first UI. The UI and all domain constants are English. ## Setup on 192.168.100.99 (Ubuntu 24.04) Prerequisite: Docker + Compose plugin (`sudo apt install docker.io docker-compose-v2`). ```bash # Unpack the project, then: cd bizmatch-app cp .env.example .env # optionally adjust DB_PASSWORD docker compose up -d --build ``` The app applies all migrations on start and then listens on `http://192.168.100.99:8090`. > **Upgrading from Module 1:** `001_init.sql` was rewritten in place (German > constants -> English). Applying it needs a fresh database: > `docker compose down -v && docker compose up -d --build`. The DB held no > production data yet, so there is nothing to migrate. First smoke test: ```bash curl http://localhost:8090/api/health # -> {"ok":true} # Create the three staff members (adjust the names): curl -X POST localhost:8090/api/staff -H 'content-type: application/json' -d '{"name":"Chris"}' curl -X POST localhost:8090/api/staff -H 'content-type: application/json' -d '{"name":"..."}' ``` ## Dev mode (without the app container) ```bash docker compose up -d db # database only npm install set -a; source .env; set +a npm run dev # tsx watch, migrations run on start # second terminal — frontend with hot reload, /api is proxied to :8090 cd web && npm install && npm run dev ``` ## NAS mount Mount it on the host via NFS, e.g. in `/etc/fstab`: ``` :/mnt//bizmatch /mnt/bizmatch-nas nfs ro,soft,timeo=100 0 0 ``` The compose file already passes `NAS_ROOT` (default `/mnt/bizmatch-nas`) into the app container. Write access (NDA filing) comes in module 6 — then replace `ro` with `rw` and limit the permissions to the two write paths. ### Business directories Directly below `NAS_ROOT` there are three status directories; every immediate subdirectory of those is one business. The directory names are configurable (they contain spaces and are treated as opaque strings): | Env var | Default | Business status | | ------------------ | -------------- | --------------- | | `NAS_DIR_ACTIVE` | `AAA = ACTIVE` | ACTIVE | | `NAS_DIR_SOLD` | `AAA = SOLD` | SOLD | | `NAS_DIR_INACTIVE` | `AAA = INACTIVE` | INACTIVE | The scan is idempotent: existing rows are matched by name and only updated when `nas_path` or `status` changed. Businesses that exist in the DB but no longer on disk are kept and only reported as a warning in the log. A missing configured directory aborts the scan with an error naming the path. ## Moving to the AI machine (later) 1. `docker compose down` on .99 2. Take a dump: `docker compose exec db pg_dump -U bizmatch bizmatch > backup.sql` 3. Copy the project folder to the AI machine, run `docker compose up -d --build` 4. Restore the dump: `docker compose exec -T db psql -U bizmatch bizmatch < backup.sql` ## API (as of module 2) | Method | Path | Purpose | Session | | ------ | --------------------------- | ------------------------------------------------ | ------- | | GET | /api/health | liveness incl. DB check | no | | GET | /api/staff | staff list | no | | POST | /api/staff | create staff member `{name}` | no | | POST | /api/login | login via `{staff_id}` → session cookie | no | | GET | /api/me | signed-in staff member | yes | | POST | /api/logout | sign out | yes | | POST | /api/businesses/scan | scan the NAS → `{scanned, inserted, updated, missing}` | yes | | GET | /api/businesses | list `?status=&search=` + counts per status | yes | | GET | /api/businesses/:id | single business incl. `nas_path` | yes | | GET | /api/businesses/:id/files | live directory listing (PDFs first) | yes | Everything except health, staff (GET+POST) and login requires the session cookie; without it the API answers `401`. ## Frontend `web/` is a Vite + React + TypeScript app with Tailwind v4 (no router, no state library). Views: login ("Who is working?"), business list (tabs with counts, search, "Scan NAS now") and business detail (status badge, live file list). In dev, Vite proxies `/api` to `http://localhost:8090`. In production the Fastify app serves `web/dist` via `@fastify/static` with an SPA fallback to `index.html` for all non-`/api` routes; the Dockerfile builds the frontend in its own stage and copies `web/dist` into the runtime image. ## Structure ``` migrations/ numbered SQL migrations (001_init.sql = full schema) src/ config.ts env configuration db.ts pg pool + query helpers migrate.ts migration runner (transactional, advisory lock) business-scan.ts NAS scan + directory listing server.ts Fastify app (health, staff, login, businesses, static) web/ src/api.ts typed API client src/App.tsx session gate + view switch src/views/ Login, Businesses, BusinessDetail ```