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:
co-authored by
Cursor
parent
bfa21989c9
commit
0466b77328
+19
-17
@@ -8,9 +8,10 @@ require('dotenv').config();
|
||||
|
||||
const pool = require('./db');
|
||||
const { createSessionMiddleware } = require('./middleware/session');
|
||||
const { requireCurator } = require('./middleware/auth');
|
||||
const { requirePermission } = require('./middleware/auth');
|
||||
const { logCuratorAction } = require('./audit-log');
|
||||
const authRoutes = require('./routes/auth');
|
||||
const usersRoutes = require('./routes/users');
|
||||
const { ensurePaintingImages, preloadArtistImagesLocal, replacePaintingImageFromUrl, replaceArtistPortraitFromUrl, clearPaintingImage, deletePainting, clearArtistPortrait, replacePaintingImageFromBuffer, replaceArtistPortraitFromBuffer, enrichPaintingRow, enrichArtistRow, IMAGE_DIR } = require('./image-service');
|
||||
const { getVersionInfo } = require('./version-info');
|
||||
const { searchCatalog } = require('./search-service');
|
||||
@@ -44,6 +45,7 @@ app.use(compression());
|
||||
app.use(express.json({ limit: '20mb' }));
|
||||
app.use(createSessionMiddleware());
|
||||
app.use('/api/auth', authRoutes);
|
||||
app.use('/api/users', usersRoutes);
|
||||
app.use('/api/translations', translationRoutes);
|
||||
app.use('/api/influences', influenceRoutes);
|
||||
app.use('/api/tours', tourRoutes);
|
||||
@@ -469,7 +471,7 @@ app.get('/api/artists/:id/navigation', async (req, res) => {
|
||||
});
|
||||
|
||||
// Update artist portrait checkup flags (checked / fixed)
|
||||
app.patch('/api/artists/:id/checkup-flags', requireCurator, async (req, res) => {
|
||||
app.patch('/api/artists/:id/checkup-flags', requirePermission('checkup'), async (req, res) => {
|
||||
try {
|
||||
const artistId = parseInt(req.params.id, 10);
|
||||
const { checked, fixed } = req.body ?? {};
|
||||
@@ -537,7 +539,7 @@ app.patch('/api/artists/:id/checkup-flags', requireCurator, async (req, res) =>
|
||||
});
|
||||
|
||||
// Developer debug: portrait image search for artist bio
|
||||
app.get('/api/artists/:id/debug-portrait-search/more', requireCurator, async (req, res) => {
|
||||
app.get('/api/artists/:id/debug-portrait-search/more', requirePermission('images'), async (req, res) => {
|
||||
try {
|
||||
const artistId = parseInt(req.params.id, 10);
|
||||
const limit = Math.min(20, Math.max(1, parseInt(req.query.limit, 10) || 20));
|
||||
@@ -555,7 +557,7 @@ app.get('/api/artists/:id/debug-portrait-search/more', requireCurator, async (re
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/api/artists/:id/debug-portrait-search', requireCurator, async (req, res) => {
|
||||
app.get('/api/artists/:id/debug-portrait-search', requirePermission('images'), async (req, res) => {
|
||||
try {
|
||||
const artistId = parseInt(req.params.id, 10);
|
||||
const result = await pool.query(`SELECT name FROM artists WHERE id = $1`, [artistId]);
|
||||
@@ -573,7 +575,7 @@ app.get('/api/artists/:id/debug-portrait-search', requireCurator, async (req, re
|
||||
});
|
||||
|
||||
// Developer debug: replace artist portrait with a search result URL
|
||||
app.post('/api/artists/:id/fix-portrait', requireCurator, async (req, res) => {
|
||||
app.post('/api/artists/:id/fix-portrait', requirePermission('images'), async (req, res) => {
|
||||
try {
|
||||
const artistId = parseInt(req.params.id, 10);
|
||||
const { imageUrl, searchUrl, source, pageUrl, thumbUrl } = req.body ?? {};
|
||||
@@ -607,7 +609,7 @@ app.post('/api/artists/:id/fix-portrait', requireCurator, async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/api/artists/:id/clear-portrait', requireCurator, async (req, res) => {
|
||||
app.post('/api/artists/:id/clear-portrait', requirePermission('images'), async (req, res) => {
|
||||
try {
|
||||
const artistId = parseInt(req.params.id, 10);
|
||||
const updated = await clearArtistPortrait(artistId);
|
||||
@@ -630,7 +632,7 @@ app.post('/api/artists/:id/clear-portrait', requireCurator, async (req, res) =>
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/api/artists/:id/upload-portrait', requireCurator, async (req, res) => {
|
||||
app.post('/api/artists/:id/upload-portrait', requirePermission('images'), async (req, res) => {
|
||||
try {
|
||||
const artistId = parseInt(req.params.id, 10);
|
||||
const { imageData, mimeType } = req.body ?? {};
|
||||
@@ -720,7 +722,7 @@ app.get('/api/artists/:id', async (req, res) => {
|
||||
});
|
||||
|
||||
// Painting image checkup (developer audit table) — must be before /api/paintings/:id
|
||||
app.get('/api/paintings/checkup', requireCurator, async (_req, res) => {
|
||||
app.get('/api/paintings/checkup', requirePermission('checkup'), async (_req, res) => {
|
||||
try {
|
||||
const { rows } = await pool.query(
|
||||
`SELECT p.id, p.title, p.year, p.image_path, p.thumbnail_path,
|
||||
@@ -768,7 +770,7 @@ app.get('/api/paintings/checkup', requireCurator, async (_req, res) => {
|
||||
});
|
||||
|
||||
// Update checkup workflow flags (checked / fixed)
|
||||
app.patch('/api/paintings/:id/checkup-flags', requireCurator, async (req, res) => {
|
||||
app.patch('/api/paintings/:id/checkup-flags', requirePermission('checkup'), async (req, res) => {
|
||||
try {
|
||||
const paintingId = parseInt(req.params.id, 10);
|
||||
const { checked, fixed } = req.body ?? {};
|
||||
@@ -837,7 +839,7 @@ app.patch('/api/paintings/:id/checkup-flags', requireCurator, async (req, res) =
|
||||
});
|
||||
|
||||
// Update public curator notes on a painting
|
||||
app.patch('/api/paintings/:id/curator-notes', requireCurator, async (req, res) => {
|
||||
app.patch('/api/paintings/:id/curator-notes', requirePermission('curator_notes'), async (req, res) => {
|
||||
try {
|
||||
const paintingId = parseInt(req.params.id, 10);
|
||||
if (!Number.isFinite(paintingId)) {
|
||||
@@ -941,7 +943,7 @@ app.post('/api/artists/:id/preload-images', async (req, res) => {
|
||||
});
|
||||
|
||||
// Developer debug: Google Images first result for image audit
|
||||
app.get('/api/paintings/:id/debug-image-search/more', requireCurator, async (req, res) => {
|
||||
app.get('/api/paintings/:id/debug-image-search/more', requirePermission('images'), async (req, res) => {
|
||||
try {
|
||||
const paintingId = parseInt(req.params.id, 10);
|
||||
const limit = Math.min(20, Math.max(1, parseInt(req.query.limit, 10) || 20));
|
||||
@@ -965,7 +967,7 @@ app.get('/api/paintings/:id/debug-image-search/more', requireCurator, async (req
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/api/paintings/:id/debug-image-search', requireCurator, async (req, res) => {
|
||||
app.get('/api/paintings/:id/debug-image-search', requirePermission('images'), async (req, res) => {
|
||||
try {
|
||||
const paintingId = parseInt(req.params.id, 10);
|
||||
const result = await pool.query(
|
||||
@@ -989,7 +991,7 @@ app.get('/api/paintings/:id/debug-image-search', requireCurator, async (req, res
|
||||
});
|
||||
|
||||
// Developer debug: replace painting image with a search result URL
|
||||
app.post('/api/paintings/:id/fix-image', requireCurator, async (req, res) => {
|
||||
app.post('/api/paintings/:id/fix-image', requirePermission('images'), async (req, res) => {
|
||||
try {
|
||||
const paintingId = parseInt(req.params.id, 10);
|
||||
const { imageUrl, searchUrl, source, pageUrl, thumbUrl } = req.body ?? {};
|
||||
@@ -1023,7 +1025,7 @@ app.post('/api/paintings/:id/fix-image', requireCurator, async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
app.delete('/api/paintings/:id', requireCurator, async (req, res) => {
|
||||
app.delete('/api/paintings/:id', requirePermission('images'), async (req, res) => {
|
||||
try {
|
||||
const paintingId = parseInt(req.params.id, 10);
|
||||
if (!Number.isFinite(paintingId)) {
|
||||
@@ -1047,7 +1049,7 @@ app.delete('/api/paintings/:id', requireCurator, async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/api/paintings/:id/clear-image', requireCurator, async (req, res) => {
|
||||
app.post('/api/paintings/:id/clear-image', requirePermission('images'), async (req, res) => {
|
||||
try {
|
||||
const paintingId = parseInt(req.params.id, 10);
|
||||
const updated = await clearPaintingImage(paintingId);
|
||||
@@ -1070,7 +1072,7 @@ app.post('/api/paintings/:id/clear-image', requireCurator, async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/api/paintings/:id/upload-image', requireCurator, async (req, res) => {
|
||||
app.post('/api/paintings/:id/upload-image', requirePermission('images'), async (req, res) => {
|
||||
try {
|
||||
const paintingId = parseInt(req.params.id, 10);
|
||||
const { imageData, mimeType } = req.body ?? {};
|
||||
@@ -1111,7 +1113,7 @@ app.post('/api/paintings/:id/upload-image', requireCurator, async (req, res) =>
|
||||
});
|
||||
|
||||
// Proxy remote image for debug preview (avoids hotlink / CORS blocks)
|
||||
app.get('/api/debug/image-proxy', requireCurator, async (req, res) => {
|
||||
app.get('/api/debug/image-proxy', requirePermission('images'), async (req, res) => {
|
||||
try {
|
||||
const imageUrl = req.query.url;
|
||||
const searchUrl = req.query.searchUrl;
|
||||
|
||||
Reference in New Issue
Block a user