Add curator Influences tool with import wizard, CRUD, and graph.
CSV/JSON/XLSX mapping wizard expands artist-level rows to all paintings, blocks duplicate file/data imports via content and payload hashes, and documents the workflow in influence-import.md. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
62d7ebbe6a
commit
48bd17e985
@@ -195,6 +195,23 @@ Requires curator session. Base path: `/api/translations`.
|
||||
| `PUT` | `/api/translations/:entityType/:id` | Upsert fields `{ locale, fields, status }` |
|
||||
| `POST` | `/api/translations/:entityType/:id/publish` | Publish all draft/reviewed rows for locale |
|
||||
|
||||
## Influences (curator)
|
||||
|
||||
Requires curator session. Base path: `/api/influences`. See [influence-import.md](influence-import.md).
|
||||
|
||||
| Method | Path | Purpose |
|
||||
|--------|------|---------|
|
||||
| `GET` | `/api/influences` | List edges (`artistId`, `paintingId`, `q`, pagination) |
|
||||
| `GET` | `/api/influences/graph?artistId=` | Neighborhood nodes/edges for visualization |
|
||||
| `POST` | `/api/influences` | Create edge `{ paintingId, sourceType, sourceArtistId\|sourcePaintingId\|sourceMovementId, … }` |
|
||||
| `PATCH` | `/api/influences/:id` | Update notes / remap source |
|
||||
| `DELETE` | `/api/influences/:id` | Delete edge |
|
||||
| `POST` | `/api/influences/import/parse` | Parse upload `{ filename, contentBase64, sheet? }` — returns `contentHash` / `payloadHash` / `alreadyImported` |
|
||||
| `POST` | `/api/influences/import/preview` | Validate `{ rows, mapping, contentHash?, payloadHash? }` |
|
||||
| `POST` | `/api/influences/import/commit` | Insert `{ proposals, contentHash?, payloadHash?, force? }` — `409` if duplicate unless `force` |
|
||||
|
||||
Public painting detail still exposes read-only `influencedBy` / `influenced` (unchanged).
|
||||
|
||||
---
|
||||
|
||||
## `GET /api/search`
|
||||
|
||||
@@ -213,7 +213,7 @@ Append-only log of curator debug mutations (fix/clear/upload/delete, checkup fla
|
||||
| `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`.
|
||||
**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`, `translation.upsert`, `translation.publish`, `influence.create`, `influence.update`, `influence.delete`, `influence.import`.
|
||||
|
||||
Example query in pgAdmin:
|
||||
|
||||
|
||||
@@ -94,14 +94,14 @@ 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).
|
||||
Then open the gallery → **Curator login** (top-right) → use debug mode / Checkup / Translations / **Influences**. 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 |
|
||||
| Curator | Above + debug mode, Checkup, Translations, Influences (import/CRUD/graph), image fix/upload/delete APIs |
|
||||
|
||||
**Audit log (pgAdmin on `gallery_dev` or `gallery_prod`):**
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
this file contains draft for future releases and features
|
||||
|
||||
1. ~~Multi language support, russian version at least~~ — done: UI i18n (EN/RU) + `entity_translations` DB + curator Translations tool — [i18n-russian.md](i18n-russian.md)
|
||||
2. tool to manage links (influence/influenced by ) import csv's ( define format), edit ,add, delete, visualize, map to pictures/ entities
|
||||
2. ~~tool to manage links (influence/influenced by ) import csv's ( define format), edit ,add, delete, visualize, map to pictures/ entities~~ — done: curator Influences page (list CRUD + import wizard CSV/JSON/XLSX + neighborhood graph) — [influence-import.md](influence-import.md)
|
||||
3. tool to monitor/manage (plan actions) of curator actions, markers to check painting/text ?
|
||||
4. ~~tool to sync prod /env resources (both ways), db structure, db data, images, users etc~~ — done for catalog DB + images: `npm run harmonize` (schema dev→prod only; users/audit excluded) — [harmonize-dev-prod.md](harmonize-dev-prod.md)
|
||||
5. curator_audit_log should contain log of actions like fixit, checked, upload etc with details for which entity it was made and details what was the action and outcome
|
||||
5. ~~curator_audit_log should contain log of actions like fixit, checked, upload etc with details for which entity it was made and details what was the action and outcome~~ — done: table + `logCuratorAction` on fix/clear/upload/delete/checkup flags (and translation upsert/publish); see [DB_structure.md](DB_structure.md#curator_audit_log). (UI to browse logs is still item 3.)
|
||||
6. ~~create search by entity (painting, artist, movement)~~ — done: timeline header + `GET /api/search`
|
||||
7. create guided tours (with text/extra infor, set of entities)
|
||||
|
||||
|
||||
@@ -50,6 +50,10 @@ Gallery/
|
||||
│ │ ├── components/CatalogSearchBar.tsx # Timeline header catalog search
|
||||
│ │ ├── components/PaintingAnnotations.tsx # Art-history notes on painting detail
|
||||
│ │ ├── pages/CheckupPage.tsx # Image audit table
|
||||
│ │ ├── pages/TranslationsPage.tsx # Russian translation review
|
||||
│ │ ├── pages/InfluencesPage.tsx # Influence links CRUD + import wizard
|
||||
│ │ ├── i18n/ # react-i18next bootstrap
|
||||
│ │ └── locales/{en,ru}/ # UI chrome strings
|
||||
│ │ ├── data/historical-events.ts # Timeline event markers (UI)
|
||||
│ │ ├── data/movement-lineage.ts # Curated movement predecessor links (UI)
|
||||
│ │ ├── utils/parquetFloorTexture.ts # Procedural parquet floor
|
||||
@@ -412,7 +416,7 @@ Next to the toggle, **Show more** (checkbox, persisted in `localStorage`) opens
|
||||
| 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 |
|
||||
| **`curator`** | Named account (`users` table) | Everything above + **Debug mode**, **Checkup**, **Translations**, **Influences**, 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).
|
||||
|
||||
@@ -429,6 +433,8 @@ Curator-only workflow for reviewing and fixing local image files — not part of
|
||||
| **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 |
|
||||
| **Translations** | Home header → **Translations** (curators only) | Review/publish Russian `entity_translations` |
|
||||
| **Influences** | Home header → **Influences** (curators only) | List/CRUD influence edges, CSV/JSON/XLSX import wizard, neighborhood graph — [influence-import.md](influence-import.md) |
|
||||
| **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) |
|
||||
|
||||
@@ -474,4 +480,6 @@ See [API.md](API.md#authentication) and [data-and-images.md](data-and-images.md#
|
||||
| [harmonize-dev-prod.md](harmonize-dev-prod.md) | Incremental dev ↔ prod merge (catalog DB + images) |
|
||||
| [DB_structure.md](DB_structure.md) | Tables and relationships |
|
||||
| [API.md](API.md) | REST endpoints |
|
||||
| [influence-import.md](influence-import.md) | Curator Influences tool — import wizard, CRUD, graph |
|
||||
| [i18n-russian.md](i18n-russian.md) | Russian UI + entity_translations |
|
||||
| [data-and-images.md](data-and-images.md) | Image pipeline and seeding |
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
# Influence links import & curator tool
|
||||
|
||||
Curator tool to **list / add / delete** influence edges, **visualize** an artist neighborhood, and **import** CSV / JSON / XLSX files through a mapping wizard.
|
||||
|
||||
Entry: header **Influences** (curator session). Data lives in `painting_influence_sources` (legacy `painting_influences` mirrored for painting→painting).
|
||||
|
||||
---
|
||||
|
||||
## Expansion rule (artist-level files)
|
||||
|
||||
Input workbooks such as [`Inputs/artist_influences_web_sources.xlsx`](../Inputs/artist_influences_web_sources.xlsx) are **artist-centric**. On import:
|
||||
|
||||
- **Influenced by** tokens → attach as sources on **all paintings** of the subject artist
|
||||
- **Influenced** tokens that resolve to an artist → reverse link: subject artist becomes a source on **all paintings** of the influenced artist (same as PainterPalette `Influencedon`)
|
||||
|
||||
Unresolved names (artists / movements / paintings not in the DB) are **skipped** with warnings — no auto-create stubs.
|
||||
|
||||
---
|
||||
|
||||
## Wizard column roles
|
||||
|
||||
| Role | Meaning |
|
||||
|------|---------|
|
||||
| `subject_artist` | Artist the row is about (required) |
|
||||
| `subject_painting` | Optional work hint (contextual; expansion still uses all works) |
|
||||
| `influenced_by` | Who/what influenced the subject (`;` / `,` separated) |
|
||||
| `influenced` | Who the subject influenced |
|
||||
| `notes` / `reference` / `source_url` | Citation metadata (URLs scraped from reference text) |
|
||||
| `ignore` | Skip column |
|
||||
|
||||
### Presets
|
||||
|
||||
| Preset | Typical headers |
|
||||
|--------|-----------------|
|
||||
| Web sources | `Artist`, `Painting`, `Influenced by`, `Influenced`, `Reference (source + link)` |
|
||||
| Story of Art | Same Title Case (+ chapter reference column) |
|
||||
| Art influences | `artist`, `painting`, `influenced_by`, `influenced`, `reference` |
|
||||
| Custom | Map any columns manually |
|
||||
|
||||
Token classification order: **artist → movement → painting title** (under subject artist, then global).
|
||||
|
||||
Committed edges use `confidence=curated`, `discovered_via=import-wizard`.
|
||||
|
||||
### Duplicate file / data guard
|
||||
|
||||
Each successful commit stores SHA-256 fingerprints in `curator_audit_log` (`influence.import` details):
|
||||
|
||||
- `contentHash` — raw file bytes
|
||||
- `payloadHash` — normalized mapped rows (same data under another filename still matches)
|
||||
|
||||
On parse/preview, if either hash matches a prior import, the UI warns and **blocks commit** unless the curator checks **Import anyway (force)**. Individual edges remain unique via DB `ON CONFLICT` either way.
|
||||
|
||||
---
|
||||
|
||||
## API (curator)
|
||||
|
||||
| Method | Path | Purpose |
|
||||
|--------|------|---------|
|
||||
| `GET` | `/api/influences` | List (`artistId`, `paintingId`, `q`, `limit`, `offset`) |
|
||||
| `GET` | `/api/influences/graph?artistId=` | Nodes + edges for SVG neighborhood |
|
||||
| `POST` | `/api/influences` | Create one edge |
|
||||
| `PATCH` | `/api/influences/:id` | Update metadata / remap source |
|
||||
| `DELETE` | `/api/influences/:id` | Delete (+ legacy mirror) |
|
||||
| `POST` | `/api/influences/import/parse` | `{ filename, contentBase64, sheet? }` → columns, hashes, `alreadyImported` |
|
||||
| `POST` | `/api/influences/import/preview` | `{ rows, mapping, contentHash?, payloadHash? }` → proposals + duplicate check |
|
||||
| `POST` | `/api/influences/import/commit` | `{ proposals, fileName?, contentHash?, payloadHash?, force? }` — `409 ALREADY_IMPORTED` unless `force` |
|
||||
|
||||
Audit: `influence.create` / `update` / `delete` / `import` in `curator_audit_log`.
|
||||
|
||||
---
|
||||
|
||||
## CLI still available
|
||||
|
||||
- `npm run dev:update-influences` — curated [`scripts/art-influences-data.js`](../scripts/art-influences-data.js)
|
||||
- `npm run dev:import-painter-palette` — PainterPalette CSV
|
||||
|
||||
The wizard is the interactive path for ad-hoc spreadsheets under `Inputs/`.
|
||||
|
||||
See also [API.md](API.md), [DB_structure.md](DB_structure.md), [Plans.md](Plans.md).
|
||||
@@ -72,7 +72,7 @@ If migration fails with permission errors, grant schema rights to the app user f
|
||||
|
||||
`npm run dev: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.
|
||||
After migrate, sign in from the site header (**Curator login**). Debug mode, Checkup, Translations, Influences, 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).
|
||||
|
||||
@@ -120,7 +120,7 @@ Image fetch can take hours if you run it for the entire catalog. The first line
|
||||
| `npm run dev:server` | API with nodemon reload (local HMR workflow) |
|
||||
| `npm run dev:client` | Vite dev server on :5173 |
|
||||
|
||||
See [environments.md](environments.md) for dev/prod URLs, database split, Docker deploy, and sync commands. For Russian UI + catalog text, see [i18n-russian.md](i18n-russian.md). Quick reference: [FAC.md](FAC.md).
|
||||
See [environments.md](environments.md) for dev/prod URLs, database split, Docker deploy, and sync commands. For Russian UI + catalog text, see [i18n-russian.md](i18n-russian.md). Influence link import/CRUD: [influence-import.md](influence-import.md). Quick reference: [FAC.md](FAC.md).
|
||||
|
||||
**Production frontend:** build the client, then start the server:
|
||||
|
||||
@@ -274,6 +274,9 @@ After clone: copy `.env.example` → `.env`, install dependencies, run [one-time
|
||||
| Catalog search returns empty / 500 | Search indexes missing | Run `npm run dev:migrate` (includes `migrate-search.sql`) or `npm run dev:migrate:search`; restart API |
|
||||
| `fetch-artist-bios-ru` fails: `index row size … exceeds btree maximum` | Old `entity_translations` index on all `value` text | `npm run dev:migrate` (partial search index on name/title only), then re-run `npm run dev:fetch-artist-bios-ru` |
|
||||
| Russian UI shows English catalog names | Translations not published | Curator → Translations → Publish; public API serves only `status = published` |
|
||||
| Influence import leaves many unresolved tokens | Names not in catalog DB | Curator → Influences → Import warnings; fix spelling or add artists first; free-text traditions stay unresolved |
|
||||
| Influence import created too many edges | Artist-level rows expand to all paintings | Expected (PainterPalette-style); delete unwanted edges in Influences list |
|
||||
| Influence import blocked: already imported | Same file bytes or mapped data imported before | Expected; use **Import anyway** only if intentional, or skip |
|
||||
| Frame still black after **Checked** | Gallery session not synced | Re-enter hall or toggle debug **Checked** from detail with gallery open behind overlay |
|
||||
| Duplicate works in gallery / timeline | Double import or variant Wikipedia titles | `npm run dev:find-duplicates`; merge or delete spare rows manually |
|
||||
| **Failed to load movement gallery** / `Cannot GET /api/movements/:id/gallery` | Stale server process missing route | Restart `npm run dev:web` or `npm run dev:server` after pulling API changes |
|
||||
|
||||
Reference in New Issue
Block a user