Add curator roles/permissions with Users admin, and fix lineage branch joins.

Staff accounts use admin/curator roles and fine-grained flags; transitions connect source-to-target with color gradients and stream cutout masks so overlaps stay seamless.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Danila Khodjaef
2026-07-27 18:04:17 +03:00
co-authored by Cursor
parent bfa21989c9
commit 0466b77328
31 changed files with 1492 additions and 269 deletions
+13 -6
View File
@@ -215,34 +215,41 @@ Unique index on `(painting_id, source_type, source_painting_id, source_artist_id
### `users`
Curator accounts (named logins). Anonymous site visitors do not have rows here.
Staff accounts (named logins). Anonymous site visitors do not have rows here. Migration: `db/migrate-auth.sql` + `db/migrate-user-roles.sql`.
| Column | Type | Notes |
|--------|------|-------|
| `id` | SERIAL PK | |
| `username` | VARCHAR(64) UNIQUE | Login name |
| `password_hash` | VARCHAR(255) | bcrypt hash |
| `role` | VARCHAR(32) | `admin` or `curator` (`users_role_check`) |
| `permissions` | TEXT[] | Fine-grained flags for `curator` accounts; admins are treated as having all |
| `is_active` | BOOLEAN | Soft-disable; inactive users cannot log in |
| `created_at` | TIMESTAMPTZ | |
| `last_login_at` | TIMESTAMPTZ | Updated on successful login |
First curator is bootstrapped on `npm run dev:migrate` when `users` is empty and `CURATOR_USERNAME` / `CURATOR_PASSWORD` are set in env.
**Permission keys:** `images`, `checkup`, `curator_notes`, `translations`, `influences`, `tours`, `users`.
- **`admin`** — all curator tools + **Users** management (role bypasses permission checks).
- **`curator`** — only assigned permission flags.
- First account is bootstrapped as **admin** on `npm run dev:migrate` when `users` is empty and `CURATOR_USERNAME` / `CURATOR_PASSWORD` are set. Manage additional accounts via the in-app **Users** page or `/api/users`.
### `curator_audit_log`
Append-only log of curator debug mutations (fix/clear/upload/delete, checkup flag changes).
Append-only log of staff mutations (fix/clear/upload/delete, checkup flags, translations, influences, tours, user management).
| 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` |
| `action` | VARCHAR(64) | e.g. `painting.fix_image`, `user.create` |
| `resource_type` | VARCHAR(32) | `painting`, `artist`, `tour`, `user`, etc. |
| `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`, `translation.upsert`, `translation.publish`, `influence.create`, `influence.update`, `influence.delete`, `influence.import`.
**Logged `action` values:** `painting.fix_image`, `painting.clear_image`, `painting.upload_image`, `painting.delete`, `painting.checkup_flags`, `painting.update_curator_notes`, `artist.fix_portrait`, `artist.clear_portrait`, `artist.upload_portrait`, `artist.checkup_flags`, `translation.upsert`, `translation.publish`, `influence.create`, `influence.update`, `influence.delete`, `influence.import`, `tour.create`, `tour.update`, `tour.delete`, `tour.stops`, `user.create`, `user.update`, `user.reset_password`.
Example query in pgAdmin: