changes
This commit is contained in:
68
README.md
68
README.md
@@ -2,6 +2,8 @@
|
||||
|
||||
Module 1: foundation (Docker Compose, PostgreSQL, schema, migrations, login).
|
||||
Module 2: business scan (NAS -> DB) and the first UI.
|
||||
Module 3: recursive file listing, PDF streaming from the NAS and the ported
|
||||
pdf.js viewer.
|
||||
|
||||
The UI and all domain constants are English.
|
||||
|
||||
@@ -83,7 +85,7 @@ directory aborts the scan with an error naming the path.
|
||||
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)
|
||||
## API (as of module 3)
|
||||
|
||||
| Method | Path | Purpose | Session |
|
||||
| ------ | --------------------------- | ------------------------------------------------ | ------- |
|
||||
@@ -96,22 +98,74 @@ directory aborts the scan with an error naming the path.
|
||||
| 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 |
|
||||
| GET | /api/businesses/:id/files | recursive listing, max depth 3 (PDFs first) | yes |
|
||||
| GET | /api/businesses/:id/file | stream one file, `?path=<relative>` | yes |
|
||||
|
||||
Everything except health, staff (GET+POST) and login requires the session
|
||||
cookie; without it the API answers `401`.
|
||||
|
||||
### File listing and streaming
|
||||
|
||||
`/files` walks the business directory recursively (max depth 3), skipping
|
||||
dotfiles, dot-directories and symlinks, and returns
|
||||
`{ path, size, mtime }` with `path` relative to the business directory and
|
||||
always posix-separated. PDFs come first, then everything else, each group
|
||||
alphabetical.
|
||||
|
||||
`/file?path=…` streams one of those files straight from disk
|
||||
(`createReadStream`, never buffered):
|
||||
|
||||
* the path is resolved against `nas_path` and then `realpath`-validated to be
|
||||
inside `realpath(business dir)`. Absolute paths, `..`, leading dots, empty
|
||||
paths and symlinks pointing out of the tree get `400`; a missing file `404`.
|
||||
* single-range HTTP `Range` requests answer `206` with `Content-Range`,
|
||||
unsatisfiable ones `416`.
|
||||
* `ETag` is derived from mtime + size, `If-None-Match` answers `304`.
|
||||
* `.pdf` is served as `application/pdf` (inline), anything else as
|
||||
`application/octet-stream` with `Content-Disposition: attachment`.
|
||||
|
||||
## 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).
|
||||
search, "Scan NAS now") and business detail — a master-detail split filling the
|
||||
viewport: file table left, PDF viewer right.
|
||||
|
||||
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.
|
||||
|
||||
### PDF viewer
|
||||
|
||||
The viewer is the proven one from the phase-1 Deno desktop app (see
|
||||
`viewer-phase1/`), ported nearly byte-identical. It lives in
|
||||
`web/public/viewer/` as plain, unbundled ES modules — Vite serves `public/`
|
||||
as-is, so the same files work in dev and prod. The React app embeds it in an
|
||||
`<iframe>`:
|
||||
|
||||
```
|
||||
/viewer/index.html?file=<urlencoded /api/businesses/:id/file?path=...>
|
||||
```
|
||||
|
||||
The page refuses any `file` value that is not a root-relative `/api/` path, and
|
||||
the iframe is same-origin, so the normal session cookie authenticates it.
|
||||
|
||||
`web/public/pdfjs/` holds the pdf.js runtime, copied out of
|
||||
`node_modules/pdfjs-dist` (pinned to exactly 6.1.200) by
|
||||
`web/scripts/copy-pdfjs.mjs`, which runs on `predev` and `prebuild` — also
|
||||
inside the Docker web stage. The directory is generated and git-ignored:
|
||||
|
||||
```
|
||||
web/public/pdfjs/legacy/ pdf.min.mjs + pdf.worker.min.mjs
|
||||
web/public/pdfjs/wasm/ CCITT-G4/JBIG2, JPEG2000 and ICC decoders
|
||||
web/public/pdfjs/standard_fonts/ standardFontDataUrl
|
||||
web/public/pdfjs/iccs/ iccUrl
|
||||
```
|
||||
|
||||
The `wasm/` directory is what makes scanned B/W pages render at all; without it
|
||||
pdf.js fails the decoders silently and shows blank white canvases.
|
||||
|
||||
## Structure
|
||||
|
||||
```
|
||||
@@ -120,10 +174,14 @@ 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)
|
||||
business-scan.ts NAS scan, recursive listing, safe file path resolution
|
||||
server.ts Fastify app (health, staff, login, businesses, file, static)
|
||||
web/
|
||||
scripts/copy-pdfjs.mjs pdfjs-dist -> public/pdfjs/ (predev + prebuild)
|
||||
public/viewer/ standalone, unbundled pdf.js viewer page
|
||||
public/pdfjs/ generated, git-ignored pdf.js runtime
|
||||
src/api.ts typed API client
|
||||
src/App.tsx session gate + view switch
|
||||
src/views/ Login, Businesses, BusinessDetail
|
||||
viewer-phase1/ reference copy of the phase-1 desktop viewer
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user