# Gallery — command reference (FAC) Quick cheat sheet for daily operations. All `npm` commands run from the **repository root** unless noted. **Default target: dev.** Day-to-day commands use `gallery_dev`, repo `data/images/`, and https://devgallery.mysuperlab.netcraze.pro. Prod (`gallery_prod`, Docker, `prod:docker:publish`) is for **scheduled releases** (~weekly) — see [Promote dev → prod](#promote-dev--prod-scheduled-release) below. **Script prefixes:** `dev:` → `gallery_dev` / dev PC · `prod:` → production / TrueNAS · `devtoprod:` → promote dev → prod · `prodto:dev:` → refresh dev from prod · `infra:` → one-time setup **Environments:** | | Dev | Prod | |---|-----|------| | URL | https://devgallery.mysuperlab.netcraze.pro | https://gallery.mysuperlab.netcraze.pro | | Host | Dev PC `192.168.10.70:5173` | TrueNAS `192.168.10.122:5173` | | Database | `gallery_dev` | `gallery_prod` | Details: [environments.md](environments.md) · Deploy: [../infra/docker/DEPLOY-truenas.md](../infra/docker/DEPLOY-truenas.md) --- ## Start and stop servers ### Start — public dev (Keenetic URL) ```powershell cd T:\Repo\Gallery npm run dev:web ``` Vite on **:5173**, API on **:3451**. Open https://devgallery.mysuperlab.netcraze.pro or http://localhost:5173. ### Start — local HMR (no Keenetic) Two terminals: ```powershell npm run dev:server # API — PORT from .env (default 3451) npm run dev:client # Vite on :5173 ``` ### Start — production-style (single Node process, built SPA) ```powershell npm run prod:start # build client + serve on PORT from .env # or: npm run prod:build npm run dev:start ``` ### Stop dev servers | Method | When | |--------|------| | **Ctrl+C** in the terminal running `dev:web` / `dev:server` / `dev:client` | Normal stop | | Close the terminal tab | Same effect | Free ports **5173**, **3451**, **3520** before DB maintenance or if “port in use” errors appear. **Find what holds a port (PowerShell):** ```powershell netstat -ano | findstr ":5173 :3451" # Stop by PID: Stop-Process -Id -Force ``` ### Stop / restart production (TrueNAS) | Action | Where | |--------|-------| | **Stop** | TrueNAS Web UI → Apps → **gallery-web** → Stop | | **Restart** | Same → Restart (after `docker:publish` or config change) | | **Update image** | Dev PC: `npm run prod:docker:publish` → restart app on TrueNAS | --- ## First-time install ```powershell cd T:\Repo\Gallery copy .env.example .env # edit DB credentials, PUBLIC_URL npm install cd client; npm install; cd .. npm run dev:migrate # schema + incremental SQL (+ auth tables, bootstrap curator) npm run dev:reset-curator # upsert curator password from .env CURATOR_* (clears sessions) npm run dev:setup # migrate + seed (fresh empty DB only) ``` **Curator auth (after migrate):** set in `.env` before first `npm run dev: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 tools allowed by your role/permissions (debug, Checkup, Translations, Influences, Tour editor, **Users**, **Activity**). Mutations are logged in `curator_audit_log` per user. If login fails after changing `CURATOR_PASSWORD` in `.env`, run `npm run dev:reset-curator` (bootstrap only runs when `users` is empty; reset upserts the env account as **admin**). **Roles:** | Role | Access | |------|--------| | Guest (`user`) | Timeline, movement flow, 3D halls, painting detail, bios | | Curator | Public browse + assigned permission flags (`images`, `checkup`, `curator_notes`, `translations`, `influences`, `tours`, `users`) | | Admin | All curator tools + **Users** + **Activity** audit reports | **Users page:** after admin login, header → **Users** — create/edit staff, reset passwords, disable accounts. Prod and dev keep **separate** `users` tables: `devtoprod:db:restore` and harmonize never copy staff accounts. If create fails with a confusing “already exists” after a restore, serial sequences may be lagging — current restore syncs them to `MAX(id)`, and user create re-syncs `users_id_seq` before insert. **Activity page:** after admin login, header → **Activity** — filterable curator action log (date/time, curator, action, resource, details, IP) plus summary charts. Reads the DB for that environment (`gallery_dev` on devgallery / `npm run dev:web`, `gallery_prod` on prod). **Audit log (SQL / 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 dev:migrate` against prod DB after first deploy with auth vars set (creates tables + bootstrap curator if `users` is empty). --- ## Database | Command | Description | |---------|-------------| | `npm run dev:migrate` | Apply `db/schema.sql` + incremental migrations (safe to re-run) | | `npm run dev:setup` | `migrate` + `seed` — fresh catalog from Wikipedia data | | `npm run infra:db:split-dev-prod` | One-time: legacy `Gallery` → `gallery_prod` + `gallery_dev` (needs `PGUSER=postgres`) | | `npm run prodto:dev:db` | Clone `gallery_prod` → `gallery_dev` (TEMPLATE); also runs image sync from prod | | `npm run dev:db:backup` | Dev data-only backup → `db/DataBackup/*.txt` + `.zip` | | `npm run prod:db:backup` | Prod backup (reads `infra/docker/.env.prod`) | | `npm run dev:db:restore -- --file ` | Restore backup into **dev** (truncates tables first; prompts `yes`) | | `npm run devtoprod:db:restore -- --file ` | Restore catalog into **prod** (skips `users` / `session` / `curator_audit_log`; syncs serial sequences; requires confirmation) | | `npm run harmonize` | Bidirectional catalog DB + image merge by `updated_at` / file mtime — [harmonize-dev-prod.md](harmonize-dev-prod.md) | | `npm run harmonize:schema` | Apply dev migrations to prod schema only (dev → prod) | | `npm run harmonize:db` / `harmonize:images` | DB or image merge only (`harmonize:images` also merges artists/paintings checkup flags + image paths, then regenerates thumbs on both sides) | | `npm run dev:backfill-updated-at` | Backfill catalog `updated_at` from image mtimes (dev) | **One-time split (recommended):** pgAdmin on dev PC → open [`db/split-dev-prod-pgadmin.sql`](../db/split-dev-prod-pgadmin.sql) → run each STEP on database `postgres`, then verify on `gallery_dev`. **Dev `.env` essentials:** ```env DB_NAME=gallery_dev PORT=3451 PUBLIC_URL=https://devgallery.mysuperlab.netcraze.pro SESSION_SECRET=your-long-random-secret # Omit SESSION_COOKIE_SECURE for auto (HTTPS via Keenetic → Secure cookie) CURATOR_USERNAME=curator CURATOR_PASSWORD=your-secure-password ``` --- ## Import and enrich catalog data Run in this order on a **new** or **re-seeded** database: | # | Command | What it does | |---|---------|----------------| | 1 | `npm run dev:seed` | Eras, movements, artists, one flagship painting per artist | | 2 | `npm run dev:sync-image-paths` | Import paintings from `data/images/paintings/`; link paths in DB | | 3 | `npm run dev:fetch-artist-images` | Portraits → `data/images/portraits/`, set `portrait_path` | | 4 | `npm run dev:fetch-artist-bios` | Wikipedia bios → `bio_short` / `bio_full` | | 5 | `npm run dev:expand-catalog` | Add famous works per artist (below `MIN_PAINTINGS`) | | 6 | `npm run dev:update-influences` | Influence graph (detail panels, 3D hall lamps) | | 7 | `npm run dev:fetch-images -- --limit=50` | Download missing painting files (batch) | **One-shot bootstrap:** `npm run dev:setup` = steps 1 + migrate only; still run 2–7 for a full catalog. ### Useful flags ```powershell npm run dev:sync-image-paths -- --dry-run npm run dev:fetch-artist-images -- --force npm run dev:fetch-artist-bios -- --force npm run dev:expand-catalog -- --fetch-images npm run dev:fetch-images -- --artist="Claude Monet" npm run dev:fetch-images -- --limit=50 --max-wait=120 npm run dev:update-influences -- --discover npm run dev:discover-influences # discovery only, no curated insert ``` ### Optional migrations / imports | Command | Description | |---------|-------------| | `npm run dev:migrate:thumbnails` | Add thumbnail columns | | `npm run dev:migrate:influence-sources` | `painting_influence_sources` table + backfill | | `npm run dev:migrate:checkup-flags` | Review flags on `paintings` | | `npm run dev:migrate:artist-checkup-flags` | Review flags on `artists` | | `npm run dev:migrate:painting-annotations` | Art-history notes table | | `npm run dev:migrate:artist-palette` | `palette_metadata` JSONB on artists | | `npm run dev:import-painter-palette` | Enrich from `Inputs/PainterPalette.csv` | | `npm run dev:update-painting-annotations` | Load curated notes | | `npm run dev:update-painting-annotations -- --wikipedia` | Add Wikipedia intro sentences | ### Audit / export | Command | Description | |---------|-------------| | `npm run dev:audit-painting-images` | Thumb vs full aspect-ratio mismatches | | `npm run dev:find-duplicates` | Duplicate / near-duplicate painting rows | | `npm run dev:audit-influence-duplicates` | Duplicate influence edges | | `npm run dev:analyze-painter-palette` | CSV ↔ artist name match report | | `npm run dev:export-paintings` | Write `Output/paintings.csv` (gitignored folder) | **Influence workbooks** (curator import): see [influence-import.md](influence-import.md) — e.g. `Inputs/gariff_influential_painters_influences.xlsx`, `Inputs/story_of_art_influences.xlsx`. --- ## Images (local files) | Command | Description | |---------|-------------| | `npm run dev:fetch-images` | Search/download missing painting files (alias: `search-missing-paintings`) | | `npm run dev:fetch-artist-images` | Download or link artist portraits | | `npm run dev:sync-image-paths` | Align DB paths with files on disk; import new rows | | `npm run dev:regenerate-thumbnails` | Rebuild painting thumbs from full images | | `npm run dev:regenerate-portrait-thumbs` | Rebuild timeline portrait thumbs (~256px) | | `npm run devtoprod:thumbnails` | Both of the above — run on dev before promote backup/sync | | `npm run devtoprod:release` | Config-driven full promote (see [deploy-dev-to-prod.md](deploy-dev-to-prod.md#one-command-release-automated)) | **Local paths:** `data/images/portraits/`, `data/images/paintings/`, `data/images/paintings/thumbs/` --- ## Dev ↔ prod image sync (SMB) SMB share **`Gallery`** → `/mnt/BasePool/Applications/Gallery` on TrueNAS. ```powershell net use \\192.168.10.122\Gallery /user:YOUR_TRUENAS_USER npm run devtoprod:release # full promote from infra/deploy/devtoprod.config.json npm run devtoprod:thumbnails # rebuild thumb files + DB paths on dev (before backup) npm run devtoprod:images # dev repo → TrueNAS (promote / first deploy) npm run prodto:dev:images # TrueNAS → dev repo (all images) npm run harmonize # bidirectional merge (newer wins) — see harmonize-dev-prod.md npm run harmonize:dry-run # preview DB + image changes only ``` **One artist only (prod → dev):** after `net use`, robocopy the artist file prefix (example: Duccio): ```powershell $src = "\\192.168.10.122\Gallery\data\images\paintings" $dst = "T:\Repo\Gallery\data\images\paintings" robocopy $src $dst "Duccio*" /XO /R:2 /W:3 robocopy "$src\thumbs" "$dst\thumbs" "Duccio*" /XO /R:2 /W:3 ``` Type `yes` when prompted (or set `autoConfirm: true` in release config). Robocopy exit codes **0–7** = success. Deploy scripts print a final **`===== SUCCESS =====`** or **`===== FAILED =====`** banner. --- ## Docker and production deploy | Command | Where | Description | |---------|-------|-------------| | `npm run prod:docker:publish` | Dev PC, **Admin** PowerShell, Docker Desktop running | Build + push `gallery-web:latest` to Gitea | | `npm run prod:docker:push-only` | Same | Push only (skip rebuild) | | `.\infra\docker\save-for-truenas.ps1` | Dev PC | Save image to `gallery-web.tar` (offline deploy) | **Before first deploy:** `npm run prod:docker:publish` → TrueNAS Custom App from `infra/docker/compose.truenas.yaml`. **After code changes:** `npm run prod:docker:publish` → restart **gallery-web** on TrueNAS. ```powershell docker login gitea.mysuperlab.netcraze.pro ``` --- ## Build frontend ```powershell npm run prod:build # client → client/dist/ cd client && npm run build && cd .. ``` Prod container serves `client/dist/` from the Docker image (rebuild image after UI changes). --- ## Verify (health checks) ```powershell # Dev (servers running) curl.exe -sk https://devgallery.mysuperlab.netcraze.pro/api/bounds curl.exe -s http://192.168.10.70:5173/api/bounds # Prod (bypass Keenetic) curl.exe -s http://192.168.10.122:5173/api/bounds # Prod (public) curl.exe -sk https://gallery.mysuperlab.netcraze.pro/api/bounds ``` Expect JSON with `min_year` / `max_year`. HTML shell only from `curl` on `/` is normal (Vite dev). **PowerShell note:** use `curl.exe`, not `curl` — PowerShell aliases `curl` to `Invoke-WebRequest` (no `-k` flag). --- ## Promote dev → prod (scheduled release) ~Weekly (or when explicitly releasing to prod). Not part of daily dev. Full runbook with per-change decision matrix and rollback: [deploy-dev-to-prod.md](deploy-dev-to-prod.md). **One command (recommended):** copy `infra/deploy/devtoprod.config.example.json` → `infra/deploy/devtoprod.config.json`, edit SMB/git settings, then: ```powershell npm run devtoprod:release # or: deploy-dev-to-prod.cmd ``` Dry-run: `npm run devtoprod:release -- -DryRun`. The script pauses for a manual **gallery-web** restart on TrueNAS before verify. **Manual steps** (partial releases): 1. Test on https://devgallery.mysuperlab.netcraze.pro 2. `npm run devtoprod:thumbnails` (if paintings/portraits changed — rebuild thumb files + DB paths on dev) 3. `npm run dev:db:backup` 4. `npm run devtoprod:db:restore -- --file db/DataBackup/gallery_dev_data_....txt` (if DB/catalog changed) 5. `npm run devtoprod:images` (if images changed) 6. `npm run prod:docker:publish` (if code changed) 7. Restart **gallery-web** on TrueNAS 8. Verify https://gallery.mysuperlab.netcraze.pro --- ## Keenetic (router) Both rules: **protocol to device = `http`**, **Preserve Host = ON**. | Domain | Upstream | |--------|----------| | `devgallery.mysuperlab.netcraze.pro` | `192.168.10.70:5173` | | `gallery.mysuperlab.netcraze.pro` | `192.168.10.122:5173` | Wrong IP or `https` to device → **502 / 504** (`Server: Web server`). --- ## Git (Gitea) ```powershell git status git add . git commit -m "Your message" git push origin main ``` Remote: https://gitea.mysuperlab.netcraze.pro/Danilka/Art-gallery --- ## Related docs | Document | Contents | |----------|----------| | [deploy-dev-to-prod.md](deploy-dev-to-prod.md) | Step-by-step release runbook (code, DB, data, images) | | [environments.md](environments.md) | Full dev/prod walkthrough | | [setup.md](setup.md) | Install, env vars, troubleshooting | | [data-and-images.md](data-and-images.md) | Catalog and image pipeline | | [API.md](API.md) | REST endpoints | | [tours.md](tours.md) | Guided tours | | [DB_structure.md](DB_structure.md) | PostgreSQL schema |