Add public curator notes and U-shaped hall wall hang.
Paintings get editable curator notes with brass plates in the 3D hall, and visit order now uses the far/end wall between left and right. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
5ddc3fd7f0
commit
bc8369e373
@@ -836,6 +836,45 @@ 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) => {
|
||||
try {
|
||||
const paintingId = parseInt(req.params.id, 10);
|
||||
if (!Number.isFinite(paintingId)) {
|
||||
return res.status(400).json({ error: 'Invalid painting id' });
|
||||
}
|
||||
|
||||
if (typeof req.body?.curatorNotes !== 'string') {
|
||||
return res.status(400).json({ error: 'curatorNotes must be a string' });
|
||||
}
|
||||
const curatorNotes = req.body.curatorNotes.trim();
|
||||
|
||||
const result = await pool.query(
|
||||
`UPDATE paintings SET curator_notes = $2
|
||||
WHERE id = $1
|
||||
RETURNING curator_notes`,
|
||||
[paintingId, curatorNotes]
|
||||
);
|
||||
if (result.rows.length === 0) {
|
||||
return res.status(404).json({ error: 'Painting not found' });
|
||||
}
|
||||
|
||||
res.json({ curatorNotes: result.rows[0].curator_notes ?? '' });
|
||||
|
||||
await logCuratorAction({
|
||||
userId: req.curatorUser.id,
|
||||
action: 'painting.update_curator_notes',
|
||||
resourceType: 'painting',
|
||||
resourceId: paintingId,
|
||||
details: { length: curatorNotes.length },
|
||||
req,
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('Curator notes update error:', err.message);
|
||||
res.status(500).json({ error: 'Failed to update curator notes' });
|
||||
}
|
||||
});
|
||||
|
||||
// Painting detail with influences
|
||||
app.get('/api/paintings/:id', async (req, res) => {
|
||||
try {
|
||||
|
||||
@@ -4,6 +4,17 @@ const pool = require('../db');
|
||||
|
||||
const SEVEN_DAYS_MS = 7 * 24 * 60 * 60 * 1000;
|
||||
|
||||
function resolveCookieSecure() {
|
||||
const flag = process.env.SESSION_COOKIE_SECURE;
|
||||
if (flag === '1' || flag === 'true') return true;
|
||||
if (flag === '0' || flag === 'false') return false;
|
||||
// Behind Keenetic/HTTPS termination: match the browser scheme via X-Forwarded-Proto.
|
||||
if (process.env.TRUST_PROXY === '1' || process.env.TRUST_PROXY === 'true') {
|
||||
return 'auto';
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function createSessionMiddleware() {
|
||||
const secret = process.env.SESSION_SECRET;
|
||||
if (!secret) {
|
||||
@@ -12,10 +23,6 @@ function createSessionMiddleware() {
|
||||
);
|
||||
}
|
||||
|
||||
const secureCookie =
|
||||
process.env.SESSION_COOKIE_SECURE === '1' ||
|
||||
process.env.SESSION_COOKIE_SECURE === 'true';
|
||||
|
||||
return session({
|
||||
store: new pgSession({
|
||||
pool,
|
||||
@@ -28,7 +35,7 @@ function createSessionMiddleware() {
|
||||
saveUninitialized: false,
|
||||
cookie: {
|
||||
httpOnly: true,
|
||||
secure: secureCookie,
|
||||
secure: resolveCookieSecure(),
|
||||
sameSite: 'lax',
|
||||
maxAge: SEVEN_DAYS_MS,
|
||||
},
|
||||
|
||||
@@ -16,6 +16,7 @@ const INCREMENTAL_MIGRATIONS = [
|
||||
'migrate-sync-timestamps.sql',
|
||||
'migrate-i18n.sql',
|
||||
'migrate-tours.sql',
|
||||
'migrate-curator-notes.sql',
|
||||
];
|
||||
|
||||
async function bootstrapCurator() {
|
||||
|
||||
+11
-4
@@ -38,7 +38,7 @@ router.post('/login', async (req, res) => {
|
||||
|
||||
try {
|
||||
const { rows } = await pool.query(
|
||||
`SELECT id, username, password_hash FROM users WHERE username = $1`,
|
||||
`SELECT id, username, password_hash FROM users WHERE LOWER(username) = LOWER($1)`,
|
||||
[username.trim()]
|
||||
);
|
||||
if (rows.length === 0) {
|
||||
@@ -56,9 +56,16 @@ router.post('/login', async (req, res) => {
|
||||
req.session.userId = user.id;
|
||||
req.session.username = user.username;
|
||||
|
||||
res.json({
|
||||
role: 'curator',
|
||||
username: user.username,
|
||||
// Ensure the store writes before the response finishes (proxy / HTTPS).
|
||||
req.session.save((err) => {
|
||||
if (err) {
|
||||
console.error('Auth session save error:', err.message);
|
||||
return res.status(500).json({ error: 'Login failed' });
|
||||
}
|
||||
res.json({
|
||||
role: 'curator',
|
||||
username: user.username,
|
||||
});
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('Auth login error:', err.message);
|
||||
|
||||
Reference in New Issue
Block a user