Add checkup review flags, duplicate detection, and corrected painting images.

Introduce checkup_checked/checkup_fixed on paintings with Checkup page filters and API. Fixed paintings auto-mark as reviewed. Add find-duplicates tooling and document debug/checkup workflow. Include Botticelli and Michelangelo image fixes from checkup.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Danila Khodjaef
2026-06-20 20:45:55 +03:00
co-authored by Cursor
parent 4683089495
commit 4c6acd5a3a
55 changed files with 750 additions and 39 deletions
+70 -2
View File
@@ -279,7 +279,9 @@ app.get('/api/artists/:id', async (req, res) => {
app.get('/api/paintings/checkup', async (_req, res) => {
try {
const { rows } = await pool.query(
`SELECT p.id, p.title, p.year, p.image_path, p.thumbnail_path, a.name AS artist_name
`SELECT p.id, p.title, p.year, p.image_path, p.thumbnail_path,
p.checkup_checked AS checked, p.checkup_fixed AS fixed,
a.name AS artist_name
FROM paintings p
JOIN artists a ON a.id = p.artist_id
ORDER BY a.name, p.year NULLS LAST, p.title`
@@ -309,6 +311,8 @@ app.get('/api/paintings/checkup', async (_req, res) => {
detail_on_demand: detailOnDemand,
gallery_file_exists: galleryFileExists,
detail_file_exists: detailOnDemand ? null : detailFileExists,
checked: !!row.checked,
fixed: !!row.fixed,
};
});
@@ -319,6 +323,66 @@ app.get('/api/paintings/checkup', async (_req, res) => {
}
});
// Update checkup workflow flags (checked / fixed)
app.patch('/api/paintings/:id/checkup-flags', async (req, res) => {
try {
const paintingId = parseInt(req.params.id, 10);
const { checked, fixed } = req.body ?? {};
if (checked === undefined && fixed === undefined) {
return res.status(400).json({ error: 'Provide checked and/or fixed boolean' });
}
if (checked !== undefined && typeof checked !== 'boolean') {
return res.status(400).json({ error: 'checked must be a boolean' });
}
if (fixed !== undefined && typeof fixed !== 'boolean') {
return res.status(400).json({ error: 'fixed must be a boolean' });
}
const current = await pool.query(
`SELECT checkup_checked, checkup_fixed FROM paintings WHERE id = $1`,
[paintingId]
);
if (current.rows.length === 0) {
return res.status(404).json({ error: 'Painting not found' });
}
const willBeFixed =
fixed !== undefined ? fixed : !!current.rows[0].checkup_fixed;
let nextChecked = checked;
if (willBeFixed) {
nextChecked = true;
}
const sets = [];
const params = [];
if (fixed !== undefined) {
params.push(fixed);
sets.push(`checkup_fixed = $${params.length}`);
}
if (nextChecked !== undefined) {
params.push(nextChecked);
sets.push(`checkup_checked = $${params.length}`);
}
params.push(paintingId);
const result = await pool.query(
`UPDATE paintings SET ${sets.join(', ')}
WHERE id = $${params.length}
RETURNING checkup_checked AS checked, checkup_fixed AS fixed`,
params
);
res.json({
checked: !!result.rows[0].checked,
fixed: !!result.rows[0].fixed,
});
} catch (err) {
console.error('Checkup flags error:', err.message);
res.status(500).json({ error: 'Failed to update checkup flags' });
}
});
// Painting detail with influences
app.get('/api/paintings/:id', async (req, res) => {
try {
@@ -419,7 +483,11 @@ app.post('/api/paintings/:id/fix-image', async (req, res) => {
}
const updated = await replacePaintingImageFromUrl(paintingId, imageUrl);
res.json(updated);
await pool.query(
`UPDATE paintings SET checkup_fixed = true, checkup_checked = true WHERE id = $1`,
[paintingId]
);
res.json({ ...updated, fixed: true, checked: true });
} catch (err) {
console.error('Fix image error:', err.message);
res.status(500).json({ error: err.message || 'Fix image failed' });