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).
# 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.sqlwas 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:
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)
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:
<truenas-ip>:/mnt/<pool>/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)
docker compose downon .99- Take a dump:
docker compose exec db pg_dump -U bizmatch bizmatch > backup.sql - Copy the project folder to the AI machine, run
docker compose up -d --build - 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