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
+10 -10
View File
@@ -1,6 +1,6 @@
const express = require('express');
const pool = require('../db');
const { requireCurator } = require('../middleware/auth');
const { requirePermission } = require('../middleware/auth');
const { logCuratorAction } = require('../audit-log');
const {
COLUMN_ROLES,
@@ -24,7 +24,7 @@ function parseId(value) {
return Number.isFinite(n) && n > 0 ? Math.floor(n) : null;
}
router.get('/presets', requireCurator, (_req, res) => {
router.get('/presets', requirePermission('influences'), (_req, res) => {
res.json({
roles: COLUMN_ROLES,
presets: Object.values(PRESETS).map((p) => ({
@@ -35,7 +35,7 @@ router.get('/presets', requireCurator, (_req, res) => {
});
});
router.get('/', requireCurator, async (req, res) => {
router.get('/', requirePermission('influences'), async (req, res) => {
try {
const artistId = parseId(req.query.artistId);
const paintingId = parseId(req.query.paintingId);
@@ -138,7 +138,7 @@ router.get('/', requireCurator, async (req, res) => {
}
});
router.get('/graph', requireCurator, async (req, res) => {
router.get('/graph', requirePermission('influences'), async (req, res) => {
try {
const artistId = parseId(req.query.artistId);
const paintingId = parseId(req.query.paintingId);
@@ -261,7 +261,7 @@ router.get('/graph', requireCurator, async (req, res) => {
}
});
router.post('/', requireCurator, async (req, res) => {
router.post('/', requirePermission('influences'), async (req, res) => {
try {
const {
paintingId,
@@ -361,7 +361,7 @@ router.post('/', requireCurator, async (req, res) => {
}
});
router.patch('/:id', requireCurator, async (req, res) => {
router.patch('/:id', requirePermission('influences'), async (req, res) => {
try {
const id = parseId(req.params.id);
if (!id) return res.status(400).json({ error: 'Invalid id' });
@@ -450,7 +450,7 @@ router.patch('/:id', requireCurator, async (req, res) => {
}
});
router.delete('/:id', requireCurator, async (req, res) => {
router.delete('/:id', requirePermission('influences'), async (req, res) => {
try {
const id = parseId(req.params.id);
if (!id) return res.status(400).json({ error: 'Invalid id' });
@@ -491,7 +491,7 @@ router.delete('/:id', requireCurator, async (req, res) => {
}
});
router.post('/import/parse', requireCurator, async (req, res) => {
router.post('/import/parse', requirePermission('influences'), async (req, res) => {
try {
const { filename, sheet, contentBase64, content } = req.body || {};
let buffer;
@@ -552,7 +552,7 @@ router.post('/import/parse', requireCurator, async (req, res) => {
}
});
router.post('/import/preview', requireCurator, async (req, res) => {
router.post('/import/preview', requirePermission('influences'), async (req, res) => {
try {
const { rows, mapping, sourceLabel, contentHash, payloadHash } = req.body || {};
if (!Array.isArray(rows) || !rows.length) {
@@ -585,7 +585,7 @@ router.post('/import/preview', requireCurator, async (req, res) => {
}
});
router.post('/import/commit', requireCurator, async (req, res) => {
router.post('/import/commit', requirePermission('influences'), async (req, res) => {
try {
const { proposals, fileName, contentHash, payloadHash, force } = req.body || {};
if (!Array.isArray(proposals)) {