Add curator authentication with audit logging and fix empty 3D gallery sessions.
Introduce session-based curator login, gate debug/checkup routes, log mutations to curator_audit_log, and keep guest hall preload public. Fix gallery view mounting so WebGL halls render reliably after navigation.
This commit is contained in:
+69
-2
@@ -26,6 +26,66 @@ curl.exe -sk https://devgallery.mysuperlab.netcraze.pro/api/bounds
|
||||
|
||||
---
|
||||
|
||||
## Authentication
|
||||
|
||||
Anonymous visitors have implicit role **`user`** (browse only). **Curator** accounts unlock debug mode, the Checkup page, and all mutating audit routes.
|
||||
|
||||
Sessions use an HTTP-only cookie (`gallery.sid`). The client sends `credentials: 'include'` on API requests.
|
||||
|
||||
### `GET /api/auth/me`
|
||||
|
||||
**Response (anonymous)**
|
||||
|
||||
```json
|
||||
{ "role": "user" }
|
||||
```
|
||||
|
||||
**Response (curator session)**
|
||||
|
||||
```json
|
||||
{ "role": "curator", "username": "curator" }
|
||||
```
|
||||
|
||||
### `POST /api/auth/login`
|
||||
|
||||
**Body:** `{ "username": "curator", "password": "…" }`
|
||||
|
||||
**Response:** `{ "role": "curator", "username": "curator" }`
|
||||
|
||||
**Errors:** `401` invalid credentials, `400` missing fields.
|
||||
|
||||
### `POST /api/auth/logout`
|
||||
|
||||
Destroys the session cookie.
|
||||
|
||||
**Response:** `{ "ok": true }`
|
||||
|
||||
### Curator-only routes
|
||||
|
||||
These return **`401`** with `{ "error": "Curator login required" }` without a valid curator session:
|
||||
|
||||
| Route | Audit action (mutations only) |
|
||||
|-------|-------------------------------|
|
||||
| `GET /api/paintings/checkup` | — (read) |
|
||||
| `GET /api/paintings/:id/debug-image-search` (+ `/more`) | — |
|
||||
| `GET /api/artists/:id/debug-portrait-search` (+ `/more`) | — |
|
||||
| `GET /api/debug/image-proxy` | — |
|
||||
| `PATCH /api/paintings/:id/checkup-flags` | `painting.checkup_flags` |
|
||||
| `PATCH /api/artists/:id/checkup-flags` | `artist.checkup_flags` |
|
||||
| `POST /api/paintings/:id/fix-image` | `painting.fix_image` |
|
||||
| `POST /api/paintings/:id/clear-image` | `painting.clear_image` |
|
||||
| `POST /api/paintings/:id/upload-image` | `painting.upload_image` |
|
||||
| `DELETE /api/paintings/:id` | `painting.delete` |
|
||||
| `POST /api/artists/:id/fix-portrait` | `artist.fix_portrait` |
|
||||
| `POST /api/artists/:id/clear-portrait` | `artist.clear_portrait` |
|
||||
| `POST /api/artists/:id/upload-portrait` | `artist.upload_portrait` |
|
||||
|
||||
**Public** (no login): all catalog `GET` routes, `POST /api/artists/:id/preload-images` (local file linking for 3D halls), `/images`, SPA static.
|
||||
|
||||
Curator mutations are recorded in `curator_audit_log` (see [DB_structure.md](DB_structure.md)).
|
||||
|
||||
---
|
||||
|
||||
## `GET /api/bounds`
|
||||
|
||||
Returns the overall timeline year range used to initialise the zoomable timeline.
|
||||
@@ -316,7 +376,9 @@ Both lists are grouped by art movement and exclude the current artist. Each arti
|
||||
|
||||
## `POST /api/artists/:id/preload-images`
|
||||
|
||||
Fast local scan: links paintings to files already on disk. Does **not** download from the internet (safe to call before opening the 3D gallery).
|
||||
**Public** — no curator login required.
|
||||
|
||||
Fast local scan: links paintings to files already on disk. Does **not** download from the internet. The 3D client calls this automatically when entering an **artist** hall.
|
||||
|
||||
**Response**
|
||||
|
||||
@@ -398,6 +460,8 @@ Returns the image bytes with `Cache-Control: public, max-age=86400`, or `404` if
|
||||
|
||||
## Developer image audit
|
||||
|
||||
**Curator login required** for every route in this section. See [Authentication](#authentication) above.
|
||||
|
||||
Routes for the **Checkup** page and **Debug mode** on painting detail and artist bio. Register `GET /api/paintings/checkup` **before** `GET /api/paintings/:id` so `"checkup"` is not parsed as a painting id.
|
||||
|
||||
### `GET /api/paintings/checkup`
|
||||
@@ -590,10 +654,13 @@ Returns image bytes with appropriate `Content-Type`.
|
||||
|
||||
## Frontend helpers
|
||||
|
||||
The React client wraps these endpoints in `client/src/api/client.ts`:
|
||||
The React client wraps these endpoints in `client/src/api/client.ts`. All requests send `credentials: 'include'` for session cookies.
|
||||
|
||||
| Function | Maps to |
|
||||
|----------|---------|
|
||||
| `getAuthMe()` | `GET /api/auth/me` |
|
||||
| `loginCurator(user, pass)` | `POST /api/auth/login` |
|
||||
| `logoutCurator()` | `POST /api/auth/logout` |
|
||||
| `api.getBounds()` | `GET /api/bounds` |
|
||||
| `api.getTimeline(start, end)` | `GET /api/timeline` |
|
||||
| `api.getArtists(...)` | `GET /api/artists` |
|
||||
|
||||
@@ -184,6 +184,51 @@ Unique index on `(painting_id, source_type, source_painting_id, source_artist_id
|
||||
|
||||
**Canonical influence store.** Used by all API influence queries: painting detail (`influencedBy`, `influenced`), `has_influence_links`, and artist hall navigation (predecessors / successors). Legacy `painting_influences` rows are backfilled here on migration; new curated painting edges are written to both tables by `update-influences`.
|
||||
|
||||
### `users`
|
||||
|
||||
Curator accounts (named logins). Anonymous site visitors do not have rows here.
|
||||
|
||||
| Column | Type | Notes |
|
||||
|--------|------|-------|
|
||||
| `id` | SERIAL PK | |
|
||||
| `username` | VARCHAR(64) UNIQUE | Login name |
|
||||
| `password_hash` | VARCHAR(255) | bcrypt hash |
|
||||
| `created_at` | TIMESTAMPTZ | |
|
||||
| `last_login_at` | TIMESTAMPTZ | Updated on successful login |
|
||||
|
||||
First curator is bootstrapped on `npm run migrate` when `users` is empty and `CURATOR_USERNAME` / `CURATOR_PASSWORD` are set in env.
|
||||
|
||||
### `curator_audit_log`
|
||||
|
||||
Append-only log of curator debug mutations (fix/clear/upload/delete, checkup flag changes).
|
||||
|
||||
| Column | Type | Notes |
|
||||
|--------|------|-------|
|
||||
| `id` | BIGSERIAL PK | |
|
||||
| `user_id` | FK → `users` | Who performed the action |
|
||||
| `action` | VARCHAR(64) | e.g. `painting.fix_image`, `artist.upload_portrait` |
|
||||
| `resource_type` | VARCHAR(32) | `painting` or `artist` |
|
||||
| `resource_id` | INTEGER | Target row id |
|
||||
| `details` | JSONB | Optional metadata (URL, mime type, flag values) |
|
||||
| `ip_address` | VARCHAR(45) | Client IP (respects `TRUST_PROXY`) |
|
||||
| `created_at` | TIMESTAMPTZ | |
|
||||
|
||||
**Logged `action` values:** `painting.fix_image`, `painting.clear_image`, `painting.upload_image`, `painting.delete`, `painting.checkup_flags`, `artist.fix_portrait`, `artist.clear_portrait`, `artist.upload_portrait`, `artist.checkup_flags`.
|
||||
|
||||
Example query in pgAdmin:
|
||||
|
||||
```sql
|
||||
SELECT l.created_at, u.username, l.action, l.resource_type, l.resource_id, l.details
|
||||
FROM curator_audit_log l
|
||||
JOIN users u ON u.id = l.user_id
|
||||
ORDER BY l.created_at DESC
|
||||
LIMIT 50;
|
||||
```
|
||||
|
||||
### `session`
|
||||
|
||||
PostgreSQL session store for `express-session` (`connect-pg-simple`). Not application data.
|
||||
|
||||
## Indexes
|
||||
|
||||
- `artists(movement_id)`, `artists(century)`
|
||||
|
||||
+42
-2
@@ -78,11 +78,47 @@ copy .env.example .env # edit DB credentials, PUBLIC_URL
|
||||
npm install
|
||||
cd client; npm install; cd ..
|
||||
|
||||
npm run migrate # schema + incremental SQL
|
||||
npm run migrate # schema + incremental SQL (+ auth tables, bootstrap curator)
|
||||
npm run setup # migrate + seed (fresh empty DB only)
|
||||
```
|
||||
|
||||
After clone with existing data/images, skip `setup` if DB already split — use post-seed steps below.
|
||||
**Curator auth (after migrate):** set in `.env` before first `npm run migrate` if the DB has no curator yet:
|
||||
|
||||
```env
|
||||
SESSION_SECRET=your-long-random-secret
|
||||
CURATOR_USERNAME=curator
|
||||
CURATOR_PASSWORD=your-secure-password
|
||||
```
|
||||
|
||||
Then open the gallery → **Curator login** (top-right) → use debug mode / Checkup. Mutations are logged in `curator_audit_log` (view in pgAdmin).
|
||||
|
||||
**Roles:**
|
||||
|
||||
| Role | Access |
|
||||
|------|--------|
|
||||
| Guest (`user`) | Timeline, movement flow, 3D halls, painting detail, bios |
|
||||
| Curator | Above + debug mode, Checkup, image fix/upload/delete APIs |
|
||||
|
||||
**Audit log (pgAdmin on `gallery_dev` or `gallery_prod`):**
|
||||
|
||||
```sql
|
||||
SELECT l.created_at, u.username, l.action, l.resource_type, l.resource_id
|
||||
FROM curator_audit_log l
|
||||
JOIN users u ON u.id = l.user_id
|
||||
ORDER BY l.created_at DESC
|
||||
LIMIT 30;
|
||||
```
|
||||
|
||||
**Prod auth env** (TrueNAS app or `infra/docker/.env.prod`):
|
||||
|
||||
```env
|
||||
SESSION_SECRET=long-random-secret
|
||||
SESSION_COOKIE_SECURE=true
|
||||
CURATOR_USERNAME=curator
|
||||
CURATOR_PASSWORD=your-secure-password
|
||||
```
|
||||
|
||||
Run `npm run migrate` against prod DB after first deploy with auth vars set (creates tables + bootstrap curator if `users` is empty).
|
||||
|
||||
---
|
||||
|
||||
@@ -107,6 +143,10 @@ After clone with existing data/images, skip `setup` if DB already split — use
|
||||
DB_NAME=gallery_dev
|
||||
PORT=3451
|
||||
PUBLIC_URL=https://devgallery.mysuperlab.netcraze.pro
|
||||
SESSION_SECRET=your-long-random-secret
|
||||
SESSION_COOKIE_SECURE=false
|
||||
CURATOR_USERNAME=curator
|
||||
CURATOR_PASSWORD=your-secure-password
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
+27
-9
@@ -300,7 +300,7 @@ Movement galleries do **not** use the predecessor/successor influence picker —
|
||||
|
||||
### Shared 3D behaviour
|
||||
|
||||
**3D images** use locally cached files only (`galleryImageUrl` in `client/src/api/client.ts`). Remote fetches are too slow for realtime WebGL textures; call `POST /api/artists/:id/preload-images` before entering an **artist** hall to link disk files. Movement galleries load painting lists from the API without a separate preload step. While a texture is loading, the frame shows the canvas cover instead of a white placeholder.
|
||||
**3D images** use locally cached files only (`galleryImageUrl` in `client/src/api/client.ts`). Remote fetches are too slow for realtime WebGL textures; the client calls `POST /api/artists/:id/preload-images` automatically when entering an **artist** hall (public route — links disk files only). Movement galleries load painting lists from the API without a separate preload step. While a texture is loading, the frame shows the canvas cover instead of a white placeholder. The 3D hall stays mounted while painting detail or bio overlays are open; returning remounts the canvas when the hall becomes active again.
|
||||
|
||||
## Painting detail view
|
||||
|
||||
@@ -333,7 +333,9 @@ Opened from the 3D hall (artist or movement wing — click a frame) or from infl
|
||||
|
||||
### Debug mode (developer)
|
||||
|
||||
When **Debug mode** is enabled from the home header, painting detail and artist biography show a bottom-left panel with image search preview and action buttons. See [Developer tools (image audit)](#developer-tools-image-audit).
|
||||
**Curator login required.** Debug tools are hidden until you sign in from the home header (**Curator login**). After login, enable **Debug mode** from the same header area.
|
||||
|
||||
When debug mode is on, painting detail and artist biography show a bottom-left panel with image search preview and action buttons. See [Developer tools (image audit)](#developer-tools-image-audit).
|
||||
|
||||
Next to the toggle, **Show more** (checkbox, persisted in `localStorage`) opens the **More** search-results modal automatically whenever you open a painting or artist bio while debug mode is on.
|
||||
|
||||
@@ -347,18 +349,32 @@ Next to the toggle, **Show more** (checkbox, persisted in `localStorage`) opens
|
||||
- **One hall per artist** keeps navigation predictable: enter from the timeline or bio, leave via the single exit or back button.
|
||||
- **Movement galleries** complement artist halls: full movement corpus in period-themed wings, entered from the flow diagram.
|
||||
- **Influence-based hall links** connect artists through documented painting relationships, grouped by movement at the exit.
|
||||
- **3D gallery images** use locally cached files only; slow remote fetches would break realtime rendering.
|
||||
- **3D gallery images** use locally cached files only; slow remote fetches would break realtime rendering. The client calls `POST /api/artists/:id/preload-images` automatically when entering an **artist** hall (public route — links disk files only).
|
||||
- **3D gallery session** stays mounted while painting detail or bio overlays are open; returning to the hall remounts the WebGL canvas when it becomes active again.
|
||||
- **Influence data** is stored in **`painting_influence_sources`** (directed links from paintings to source paintings, artists, or movements), with optional period fields and citation metadata. Sources include curated scholarship (`art-influences-data.js`) and **PainterPalette** (`discovered_via = painter-palette`). Legacy `painting_influences` mirrors painting-to-painting edges for scripts only.
|
||||
|
||||
## User roles and access
|
||||
|
||||
| Role | Who | Can do |
|
||||
|------|-----|--------|
|
||||
| **`user`** | Anonymous visitor (default) | Browse timeline, movement flow, 3D artist/movement halls, painting detail, artist bios, images |
|
||||
| **`curator`** | Named account (`users` table) | Everything above + **Debug mode**, **Checkup**, debug API mutations |
|
||||
|
||||
Curators sign in via **Curator login** in the site header. Sessions use an HTTP-only cookie (`gallery.sid`). The UI hides debug controls from guests; the server enforces the same rules on debug/checkup API routes (`401` without a valid session).
|
||||
|
||||
Mutating debug actions (fix/clear/upload/delete, checkup flag changes) are appended to **`curator_audit_log`** with username, action, target id, optional JSON details, and client IP. Query in pgAdmin — see [DB_structure.md](DB_structure.md#curator_audit_log).
|
||||
|
||||
## Developer tools (image audit)
|
||||
|
||||
Optional workflow for curating local image files — not part of the public visitor experience.
|
||||
Curator-only workflow for reviewing and fixing local image files — not part of the public visitor experience.
|
||||
|
||||
| Feature | Where | Purpose |
|
||||
|---------|--------|---------|
|
||||
| **Debug mode** | Home header toggle (`client/src/utils/debugMode.ts`) | Persists in `localStorage`; enables debug panel on painting detail and artist bio |
|
||||
| **Show more** | Home header checkbox (same util) | When debug mode is on, auto-opens the **More** modal on each painting / bio page load |
|
||||
| **Checkup page** | Home header → **Checkup** (`CheckupPage.tsx`) | Full-catalog table: gallery vs detail thumbnails, search, fix, review flags |
|
||||
| **Curator login** | Home header (guests) | Username + password modal; unlocks debug tools |
|
||||
| **Debug mode** | Home header toggle (curators only) | Persists in `localStorage`; enables debug panel on painting detail and artist bio |
|
||||
| **Show more** | Home header checkbox (curators, when debug on) | Auto-opens the **More** modal on each painting / bio page load |
|
||||
| **Checkup page** | Home header → **Checkup** (curators only) | Full-catalog table: gallery vs detail thumbnails, search, fix, review flags |
|
||||
| **Logout** | Home header (curators) | Ends session; hides debug tools |
|
||||
| **Debug panel** | Painting detail or artist bio (bottom-left, when debug mode on) | Search preview + action buttons (six on painting detail, five on artist bio) |
|
||||
|
||||
### Debug panel (painting detail and artist bio)
|
||||
@@ -386,9 +402,11 @@ Influence side-panel thumbnails use **letterboxing** (`object-fit: contain`) so
|
||||
|
||||
**Search visible** runs image search only for rows currently shown after text/filter — not automatically on page load. Fixing an image sets **Fixed** and **Reviewed**.
|
||||
|
||||
Run `npm run migrate:checkup-flags`, `npm run migrate:artist-checkup-flags`, and `npm run migrate:painting-annotations` once on existing databases. Load notes with `npm run update-painting-annotations` (add `--wikipedia` for overview lines from Wikipedia intro text). After server code changes, restart `npm run start` (or `npm run dev:server`) so new routes (e.g. clear, upload, delete painting, portrait debug, annotations) are registered. JSON body limit for uploads is **20 MB** (`express.json` in `server/index.js`); individual files are capped at **15 MB** after decode.
|
||||
Run `npm run migrate:checkup-flags`, `npm run migrate:artist-checkup-flags`, and `npm run migrate:painting-annotations` once on existing databases. Load notes with `npm run update-painting-annotations` (add `--wikipedia` for overview lines from Wikipedia intro text). After server code changes, restart `npm run start` (or `npm run dev:server`) so new routes are registered. JSON body limit for uploads is **20 MB** (`express.json` in `server/index.js`); individual files are capped at **15 MB** after decode.
|
||||
|
||||
See [API.md](API.md#developer-image-audit) and [data-and-images.md](data-and-images.md#duplicate-paintings).
|
||||
Server-side auth lives in `server/middleware/session.js`, `server/middleware/auth.js`, `server/routes/auth.js`, and `server/audit-log.js`. Client auth context: `client/src/context/AuthContext.tsx`.
|
||||
|
||||
See [API.md](API.md#authentication) and [data-and-images.md](data-and-images.md#duplicate-paintings).
|
||||
|
||||
## Related docs
|
||||
|
||||
|
||||
@@ -352,7 +352,7 @@ Requests are deduplicated (`inflight` map) and timeout after 15 seconds. On-dema
|
||||
|
||||
## Preload before 3D gallery
|
||||
|
||||
`POST /api/artists/:id/preload-images` runs **local-only** linking — no network. Call this when entering an **artist’s** 3D hall so textures use files already on disk.
|
||||
`POST /api/artists/:id/preload-images` is a **public** route (no curator login). It runs **local-only** linking — no network. The React client calls it automatically when entering an **artist’s** 3D hall so textures use files already on disk.
|
||||
|
||||
**Movement galleries** (`GET /api/movements/:id/gallery`) do not use preload — they load the full painting list from the API and resolve local paths the same way as artist halls. Works without files still show the canvas cover in the frame.
|
||||
|
||||
@@ -496,4 +496,6 @@ The client passes `searchUrl`, `source`, and `thumbUrl` from search results to i
|
||||
|
||||
Checkup **Search visible** queues search for filtered rows only (3 concurrent); it does not search the full catalog on load.
|
||||
|
||||
**Curator login required** for all debug/checkup UI and mutating API routes. Guests can browse and enter 3D halls normally; preload remains public. See [API.md — Authentication](API.md#authentication).
|
||||
|
||||
See [API.md](API.md#developer-image-audit) and [basics.md](basics.md#developer-tools-image-audit).
|
||||
|
||||
@@ -90,8 +90,14 @@ npm run db:split-databases
|
||||
DB_NAME=gallery_dev
|
||||
PORT=3451
|
||||
PUBLIC_URL=https://devgallery.mysuperlab.netcraze.pro
|
||||
SESSION_SECRET=your-long-random-secret
|
||||
SESSION_COOKIE_SECURE=false
|
||||
CURATOR_USERNAME=curator
|
||||
CURATOR_PASSWORD=your-secure-password
|
||||
```
|
||||
|
||||
`npm run migrate` creates auth tables and bootstraps the first curator when `users` is empty.
|
||||
|
||||
2. Run:
|
||||
|
||||
```powershell
|
||||
@@ -159,8 +165,10 @@ Use normal PowerShell with `-SkipHosts` if you already added `192.168.10.122 git
|
||||
1. **Apps** → **Discover Apps** → **Custom App** → **Install via Docker Compose**
|
||||
2. Paste contents of `infra/docker/compose.truenas.yaml` from the repo
|
||||
3. Replace `YOUR_POSTGRES_PASSWORD` with the `gallery` user password
|
||||
4. **Apps** → **Settings** → register Gitea registry (`gitea.mysuperlab.netcraze.pro`, token with `read:package`)
|
||||
5. Deploy → wait for **gallery-web** to show **Running**
|
||||
4. Replace `REPLACE_WITH_LONG_RANDOM_SECRET` and `REPLACE_WITH_SECURE_PASSWORD` for `SESSION_SECRET` and `CURATOR_PASSWORD`
|
||||
5. **Apps** → **Settings** → register Gitea registry (`gitea.mysuperlab.netcraze.pro`, token with `read:package`)
|
||||
6. Deploy → wait for **gallery-web** to show **Running**
|
||||
7. Run `npm run migrate` against `gallery_prod` if auth tables are not yet applied (or migrate from dev PC with prod env)
|
||||
|
||||
### Step H — Verify production
|
||||
|
||||
@@ -171,7 +179,7 @@ curl.exe -sk https://gallery.mysuperlab.netcraze.pro/api/bounds
|
||||
curl.exe -s http://192.168.10.122:5173/api/bounds
|
||||
```
|
||||
|
||||
Open **https://gallery.mysuperlab.netcraze.pro/** — timeline and sample painting images should load.
|
||||
Open **https://gallery.mysuperlab.netcraze.pro/** — timeline and sample painting images should load. Click an artist portrait or movement label to enter a 3D hall. **Curator login** (top-right) unlocks debug mode and Checkup.
|
||||
|
||||
---
|
||||
|
||||
@@ -223,8 +231,12 @@ Expect **HTTP 200** (not 502).
|
||||
```env
|
||||
PUBLIC_URL=https://devgallery.mysuperlab.netcraze.pro
|
||||
TRUST_PROXY=true
|
||||
SESSION_SECRET=your-long-random-secret
|
||||
SESSION_COOKIE_SECURE=false
|
||||
```
|
||||
|
||||
Prod (`infra/docker/.env.prod`): set `SESSION_COOKIE_SECURE=true` and the same `SESSION_SECRET` / `CURATOR_*` vars on the TrueNAS app environment.
|
||||
|
||||
Restart `npm run dev:web` after changing `PUBLIC_URL`.
|
||||
|
||||
---
|
||||
|
||||
+24
-6
@@ -26,6 +26,12 @@ cp .env.example .env
|
||||
| `PUBLIC_URL` | Public URL (dev: `https://devgallery.mysuperlab.netcraze.pro`; prod: `https://gallery.mysuperlab.netcraze.pro`) |
|
||||
| `TRUST_PROXY` | Set to `true` when behind nginx/reverse proxy (honours `X-Forwarded-*`) |
|
||||
| `IMAGE_DIR` | Root for cached images (default `./data/images`) |
|
||||
| `SESSION_SECRET` | Random string for signed session cookies (required for curator login) |
|
||||
| `SESSION_COOKIE_SECURE` | `false` for local HTTP dev; `true` in prod behind HTTPS |
|
||||
| `CURATOR_USERNAME` | Bootstrap only — first curator account name (default `curator`) |
|
||||
| `CURATOR_PASSWORD` | Bootstrap only — password for first curator when `users` table is empty |
|
||||
|
||||
`.env` is git-ignored; never commit passwords.
|
||||
|
||||
Optional script tuning:
|
||||
|
||||
@@ -34,8 +40,6 @@ Optional script tuning:
|
||||
| `MIN_PAINTINGS` | `expand-catalog` | Minimum paintings per artist (default `6`) |
|
||||
| `FETCH_MAX_WAIT_SEC` | `fetch-images` | Max seconds per painting in batch runs (default `10`) |
|
||||
|
||||
`.env` is git-ignored; never commit passwords.
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
@@ -62,6 +66,14 @@ npm run seed # eras, movements, artists, flagship paintings (one per artis
|
||||
|
||||
If migration fails with permission errors, grant schema rights to the app user first (see [DB_structure.md](DB_structure.md)).
|
||||
|
||||
### Curator accounts (auth migration)
|
||||
|
||||
`npm run migrate` applies `db/migrate-auth.sql` (`users`, `curator_audit_log`, `session` tables). When the `users` table is empty and `CURATOR_USERNAME` / `CURATOR_PASSWORD` are set in `.env`, the first curator account is created automatically.
|
||||
|
||||
After migrate, sign in from the site header (**Curator login**). Debug mode, Checkup, and all mutating debug APIs require an active curator session. Anonymous visitors browse the timeline and 3D halls without logging in.
|
||||
|
||||
See [API.md — Authentication](API.md#authentication) and [basics.md — Developer tools](basics.md#developer-tools-image-audit).
|
||||
|
||||
### Recommended post-seed steps
|
||||
|
||||
After a fresh seed, run these to match a fully populated local install:
|
||||
@@ -129,10 +141,11 @@ Production runs as **`gallery-web`** on TrueNAS at **https://gallery.mysuperlab.
|
||||
Quick deploy checklist:
|
||||
|
||||
1. One-time DB split in **pgAdmin** on dev PC: [`db/split-dev-prod-pgadmin.sql`](../db/split-dev-prod-pgadmin.sql)
|
||||
2. Dev `.env` → `DB_NAME=gallery_dev`, `PORT=3451`, `PUBLIC_URL=https://devgallery.mysuperlab.netcraze.pro`
|
||||
3. `net use \\192.168.10.122\Gallery` → `npm run images:sync-to-prod`
|
||||
4. `npm run docker:publish` → TrueNAS Custom App from `infra/docker/compose.truenas.yaml`
|
||||
5. Keenetic: both domains → `:5173`, protocol to device **`http`**, correct IP per environment
|
||||
2. Dev `.env` → `DB_NAME=gallery_dev`, `PORT=3451`, `PUBLIC_URL=https://devgallery.mysuperlab.netcraze.pro`, plus auth vars (`SESSION_SECRET`, `CURATOR_*`)
|
||||
3. `npm run migrate` on dev and prod DBs (includes auth tables + bootstrap curator)
|
||||
4. `net use \\192.168.10.122\Gallery` → `npm run images:sync-to-prod`
|
||||
5. `npm run docker:publish` → TrueNAS Custom App from `infra/docker/compose.truenas.yaml` (set auth env in compose)
|
||||
6. Keenetic: both domains → `:5173`, protocol to device **`http`**, correct IP per environment
|
||||
|
||||
### Legacy deployment (optional)
|
||||
|
||||
@@ -254,3 +267,8 @@ After clone: copy `.env.example` → `.env`, install dependencies, run [one-time
|
||||
| Movement gallery shows generic cream walls | Stale client build | `cd client && npm run build`; hard-refresh browser |
|
||||
| Windows overlap paintings in movement wing | Stale client | Rebuild client — windows are placed only on side walls in gaps between frames |
|
||||
| Influence thumbnails cropped on painting detail | Stale client build | `npm run build` — panels use `object-fit: contain` for full image |
|
||||
| **Curator login** fails / always guest | Auth tables missing or wrong password | Set `SESSION_SECRET` + `CURATOR_PASSWORD` in `.env`, run `npm run migrate`, restart server |
|
||||
| Debug / Checkup returns **401** | Not signed in as curator | **Curator login** (top-right); session cookie `gallery.sid` must be sent (`credentials: include`) |
|
||||
| Debug works in UI but API rejects | Stale server without auth middleware | Restart `npm run dev:web` or `npm run dev:server` after pulling auth changes |
|
||||
| **Empty screen** entering 3D hall (header missing) | Stale client before gallery-session fix | Hard-refresh; pull latest client — hall renders from `view` state, not only `gallerySession` |
|
||||
| 3D hall black after returning from painting detail | WebGL context lost while hall was hidden | Hard-refresh; latest client remounts canvas when hall becomes active again |
|
||||
|
||||
Reference in New Issue
Block a user