Add debug More/Clear/Upload tools for paintings and artist portraits.
Extends the debug panel on painting detail and artist bio with a 20-result search picker, local image upload, and clear-to-empty-frame workflow, plus API routes, artist checkup migration, and documentation. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
b4425445bb
commit
0972b5df99
@@ -16,6 +16,10 @@ function safePaintingBase(artistName, title) {
|
||||
return `${artistName}_${title}`.replace(/[^a-zA-Z0-9_-]/g, '_');
|
||||
}
|
||||
|
||||
function safeArtistPortraitBase(artistName) {
|
||||
return artistName.replace(/[^a-zA-Z0-9_-]/g, '_');
|
||||
}
|
||||
|
||||
function unlinkIfExists(absPath) {
|
||||
if (absPath && fs.existsSync(absPath)) {
|
||||
try {
|
||||
@@ -154,6 +158,183 @@ async function ensurePaintingImages(paintingId, size = 'thumb') {
|
||||
return promise;
|
||||
}
|
||||
|
||||
function pickExtFromMime(mimeType) {
|
||||
const map = {
|
||||
'image/jpeg': '.jpg',
|
||||
'image/jpg': '.jpg',
|
||||
'image/png': '.png',
|
||||
'image/webp': '.webp',
|
||||
'image/gif': '.gif',
|
||||
};
|
||||
return map[String(mimeType || '').toLowerCase()] || '.jpg';
|
||||
}
|
||||
|
||||
function unlinkPaintingFiles(row, safeBase) {
|
||||
unlinkIfExists(row.image_path ? path.join(IMAGE_DIR, row.image_path) : null);
|
||||
unlinkIfExists(row.thumbnail_path ? path.join(IMAGE_DIR, row.thumbnail_path) : null);
|
||||
const paintingsDir = path.join(IMAGE_DIR, 'paintings');
|
||||
const thumbsDir = path.join(IMAGE_DIR, 'paintings', 'thumbs');
|
||||
for (const ext of ['.jpg', '.jpeg', '.png', '.webp', '.gif', '.JPG']) {
|
||||
unlinkIfExists(path.join(paintingsDir, safeBase + ext));
|
||||
unlinkIfExists(path.join(thumbsDir, safeBase + '_thumb' + ext));
|
||||
unlinkIfExists(path.join(thumbsDir, safeBase + '_thumb.jpg'));
|
||||
}
|
||||
}
|
||||
|
||||
function unlinkPortraitFiles(row, safeBase) {
|
||||
unlinkIfExists(row.portrait_path ? path.join(IMAGE_DIR, row.portrait_path) : null);
|
||||
const portraitsDir = path.join(IMAGE_DIR, 'portraits');
|
||||
for (const ext of ['.jpg', '.jpeg', '.png', '.webp', '.gif', '.JPG']) {
|
||||
unlinkIfExists(path.join(portraitsDir, safeBase + ext));
|
||||
}
|
||||
}
|
||||
|
||||
async function clearPaintingImage(paintingId) {
|
||||
const result = await pool.query(
|
||||
`SELECT p.id, p.title, p.image_path, p.thumbnail_path, a.name AS artist_name
|
||||
FROM paintings p
|
||||
JOIN artists a ON a.id = p.artist_id
|
||||
WHERE p.id = $1`,
|
||||
[paintingId]
|
||||
);
|
||||
if (result.rows.length === 0) {
|
||||
throw new Error('Painting not found');
|
||||
}
|
||||
|
||||
const row = result.rows[0];
|
||||
const safeBase = safePaintingBase(row.artist_name, row.title);
|
||||
unlinkPaintingFiles(row, safeBase);
|
||||
|
||||
await pool.query(
|
||||
`UPDATE paintings SET image_path = NULL, thumbnail_path = NULL WHERE id = $1`,
|
||||
[paintingId]
|
||||
);
|
||||
|
||||
return { imagePath: null, thumbnailPath: null };
|
||||
}
|
||||
|
||||
async function clearArtistPortrait(artistId) {
|
||||
const result = await pool.query(
|
||||
`SELECT id, name, portrait_path FROM artists WHERE id = $1`,
|
||||
[artistId]
|
||||
);
|
||||
if (result.rows.length === 0) {
|
||||
throw new Error('Artist not found');
|
||||
}
|
||||
|
||||
const row = result.rows[0];
|
||||
const safeBase = safeArtistPortraitBase(row.name);
|
||||
unlinkPortraitFiles(row, safeBase);
|
||||
|
||||
await pool.query(`UPDATE artists SET portrait_path = NULL WHERE id = $1`, [artistId]);
|
||||
|
||||
return { portraitPath: null };
|
||||
}
|
||||
|
||||
async function replacePaintingImageFromBuffer(paintingId, buffer, mimeType) {
|
||||
if (!buffer?.length) {
|
||||
throw new Error('Empty image data');
|
||||
}
|
||||
|
||||
const sharp = require('sharp');
|
||||
try {
|
||||
await sharp(buffer).metadata();
|
||||
} catch {
|
||||
throw new Error('Invalid image file');
|
||||
}
|
||||
|
||||
const result = await pool.query(
|
||||
`SELECT p.id, p.title, p.image_path, p.thumbnail_path, a.name AS artist_name
|
||||
FROM paintings p
|
||||
JOIN artists a ON a.id = p.artist_id
|
||||
WHERE p.id = $1`,
|
||||
[paintingId]
|
||||
);
|
||||
if (result.rows.length === 0) {
|
||||
throw new Error('Painting not found');
|
||||
}
|
||||
|
||||
const row = result.rows[0];
|
||||
const safeBase = safePaintingBase(row.artist_name, row.title);
|
||||
const paintingsDir = path.join(IMAGE_DIR, 'paintings');
|
||||
const thumbsDir = path.join(IMAGE_DIR, 'paintings', 'thumbs');
|
||||
if (!fs.existsSync(paintingsDir)) fs.mkdirSync(paintingsDir, { recursive: true });
|
||||
if (!fs.existsSync(thumbsDir)) fs.mkdirSync(thumbsDir, { recursive: true });
|
||||
|
||||
const fullExt = pickExtFromMime(mimeType);
|
||||
const fullDest = path.join(paintingsDir, safeBase + fullExt);
|
||||
const thumbDest = path.join(thumbsDir, safeBase + '_thumb.jpg');
|
||||
|
||||
unlinkPaintingFiles(row, safeBase);
|
||||
fs.writeFileSync(fullDest, buffer);
|
||||
|
||||
let thumbnailPath = null;
|
||||
try {
|
||||
await generateThumbnailFromFull(fullDest, thumbDest);
|
||||
thumbnailPath = path.join('paintings', 'thumbs', safeBase + '_thumb.jpg').replace(/\\/g, '/');
|
||||
} catch {
|
||||
thumbnailPath = path.join('paintings', safeBase + fullExt).replace(/\\/g, '/');
|
||||
}
|
||||
|
||||
const imagePath = path.join('paintings', safeBase + fullExt).replace(/\\/g, '/');
|
||||
|
||||
await pool.query(
|
||||
`UPDATE paintings SET image_path = $1, thumbnail_path = $2 WHERE id = $3`,
|
||||
[imagePath, thumbnailPath, paintingId]
|
||||
);
|
||||
|
||||
return { imagePath, thumbnailPath };
|
||||
}
|
||||
|
||||
async function replaceArtistPortraitFromBuffer(artistId, buffer, mimeType) {
|
||||
if (!buffer?.length) {
|
||||
throw new Error('Empty image data');
|
||||
}
|
||||
|
||||
const sharp = require('sharp');
|
||||
try {
|
||||
await sharp(buffer).metadata();
|
||||
} catch {
|
||||
throw new Error('Invalid image file');
|
||||
}
|
||||
|
||||
const result = await pool.query(
|
||||
`SELECT id, name, portrait_path FROM artists WHERE id = $1`,
|
||||
[artistId]
|
||||
);
|
||||
if (result.rows.length === 0) {
|
||||
throw new Error('Artist not found');
|
||||
}
|
||||
|
||||
const row = result.rows[0];
|
||||
const safeBase = safeArtistPortraitBase(row.name);
|
||||
const portraitsDir = path.join(IMAGE_DIR, 'portraits');
|
||||
if (!fs.existsSync(portraitsDir)) fs.mkdirSync(portraitsDir, { recursive: true });
|
||||
|
||||
unlinkPortraitFiles(row, safeBase);
|
||||
|
||||
const jpgDest = path.join(portraitsDir, safeBase + '.jpg');
|
||||
try {
|
||||
await sharp(buffer)
|
||||
.rotate()
|
||||
.resize({ width: 900, height: 1100, fit: 'inside', withoutEnlargement: true })
|
||||
.jpeg({ quality: 88 })
|
||||
.toFile(jpgDest);
|
||||
} catch {
|
||||
const fullExt = pickExtFromMime(mimeType);
|
||||
const fullDest = path.join(portraitsDir, safeBase + fullExt);
|
||||
fs.writeFileSync(fullDest, buffer);
|
||||
const portraitPath = path.join('portraits', safeBase + fullExt).replace(/\\/g, '/');
|
||||
await pool.query(`UPDATE artists SET portrait_path = $1 WHERE id = $2`, [portraitPath, artistId]);
|
||||
return { portraitPath };
|
||||
}
|
||||
|
||||
const portraitPath = path.join('portraits', safeBase + '.jpg').replace(/\\/g, '/');
|
||||
await pool.query(`UPDATE artists SET portrait_path = $1 WHERE id = $2`, [portraitPath, artistId]);
|
||||
|
||||
return { portraitPath };
|
||||
}
|
||||
|
||||
async function replacePaintingImageFromUrl(paintingId, imageUrl, context = {}) {
|
||||
const result = await pool.query(
|
||||
`SELECT p.id, p.title, p.image_path, p.thumbnail_path, a.name AS artist_name
|
||||
@@ -202,9 +383,61 @@ async function replacePaintingImageFromUrl(paintingId, imageUrl, context = {}) {
|
||||
return { imagePath, thumbnailPath };
|
||||
}
|
||||
|
||||
async function replaceArtistPortraitFromUrl(artistId, imageUrl, context = {}) {
|
||||
const result = await pool.query(
|
||||
`SELECT id, name, portrait_path FROM artists WHERE id = $1`,
|
||||
[artistId]
|
||||
);
|
||||
if (result.rows.length === 0) {
|
||||
throw new Error('Artist not found');
|
||||
}
|
||||
|
||||
const row = result.rows[0];
|
||||
const safeBase = safeArtistPortraitBase(row.name);
|
||||
const portraitsDir = path.join(IMAGE_DIR, 'portraits');
|
||||
if (!fs.existsSync(portraitsDir)) fs.mkdirSync(portraitsDir, { recursive: true });
|
||||
|
||||
const fullExt = pickExt(imageUrl);
|
||||
const fullDest = path.join(portraitsDir, safeBase + fullExt);
|
||||
|
||||
unlinkIfExists(row.portrait_path ? path.join(IMAGE_DIR, row.portrait_path) : null);
|
||||
for (const ext of ['.jpg', '.jpeg', '.png', '.webp', '.JPG']) {
|
||||
unlinkIfExists(path.join(portraitsDir, safeBase + ext));
|
||||
}
|
||||
|
||||
await downloadImageForFix(imageUrl, fullDest, context);
|
||||
|
||||
let portraitPath = path.join('portraits', safeBase + fullExt).replace(/\\/g, '/');
|
||||
const jpgDest = path.join(portraitsDir, safeBase + '.jpg');
|
||||
|
||||
try {
|
||||
const sharp = require('sharp');
|
||||
await sharp(fullDest)
|
||||
.rotate()
|
||||
.resize({ width: 900, height: 1100, fit: 'inside', withoutEnlargement: true })
|
||||
.jpeg({ quality: 88 })
|
||||
.toFile(jpgDest);
|
||||
if (fullDest !== jpgDest && fs.existsSync(fullDest)) {
|
||||
fs.unlinkSync(fullDest);
|
||||
}
|
||||
portraitPath = path.join('portraits', safeBase + '.jpg').replace(/\\/g, '/');
|
||||
} catch {
|
||||
// keep downloaded file as-is
|
||||
}
|
||||
|
||||
await pool.query(`UPDATE artists SET portrait_path = $1 WHERE id = $2`, [portraitPath, artistId]);
|
||||
|
||||
return { portraitPath };
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
ensurePaintingImages,
|
||||
preloadArtistImagesLocal,
|
||||
replacePaintingImageFromUrl,
|
||||
replaceArtistPortraitFromUrl,
|
||||
clearPaintingImage,
|
||||
clearArtistPortrait,
|
||||
replacePaintingImageFromBuffer,
|
||||
replaceArtistPortraitFromBuffer,
|
||||
IMAGE_DIR,
|
||||
};
|
||||
|
||||
+239
-2
@@ -5,8 +5,8 @@ const fs = require('fs');
|
||||
require('dotenv').config();
|
||||
|
||||
const pool = require('./db');
|
||||
const { ensurePaintingImages, preloadArtistImagesLocal, replacePaintingImageFromUrl, IMAGE_DIR } = require('./image-service');
|
||||
const { searchGoogleImagesFirst, fetchImageBuffer, friendlyImageFetchError, pickExt } = require('../scripts/image-fetcher');
|
||||
const { ensurePaintingImages, preloadArtistImagesLocal, replacePaintingImageFromUrl, replaceArtistPortraitFromUrl, clearPaintingImage, clearArtistPortrait, replacePaintingImageFromBuffer, replaceArtistPortraitFromBuffer, IMAGE_DIR } = require('./image-service');
|
||||
const { searchGoogleImagesFirst, searchArtistPortraitFirst, searchPaintingImagesMany, searchArtistPortraitMany, fetchImageBuffer, friendlyImageFetchError, pickExt } = require('../scripts/image-fetcher');
|
||||
|
||||
const app = express();
|
||||
const PORT = process.env.PORT || 3001;
|
||||
@@ -234,6 +234,173 @@ app.get('/api/artists/:id/navigation', async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// Update artist portrait checkup flags (checked / fixed)
|
||||
app.patch('/api/artists/:id/checkup-flags', async (req, res) => {
|
||||
try {
|
||||
const artistId = 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 artists WHERE id = $1`,
|
||||
[artistId]
|
||||
);
|
||||
if (current.rows.length === 0) {
|
||||
return res.status(404).json({ error: 'Artist 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(artistId);
|
||||
|
||||
const result = await pool.query(
|
||||
`UPDATE artists 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('Artist checkup flags error:', err.message);
|
||||
res.status(500).json({ error: 'Failed to update checkup flags' });
|
||||
}
|
||||
});
|
||||
|
||||
// Developer debug: portrait image search for artist bio
|
||||
app.get('/api/artists/:id/debug-portrait-search/more', 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));
|
||||
const result = await pool.query(`SELECT name FROM artists WHERE id = $1`, [artistId]);
|
||||
if (result.rows.length === 0) {
|
||||
return res.status(404).json({ error: 'Artist not found' });
|
||||
}
|
||||
|
||||
const { name } = result.rows[0];
|
||||
const search = await searchArtistPortraitMany(name, limit);
|
||||
res.json(search);
|
||||
} catch (err) {
|
||||
console.error('Debug portrait search (more) error:', err.message);
|
||||
res.status(500).json({ error: 'Portrait search failed' });
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/api/artists/:id/debug-portrait-search', async (req, res) => {
|
||||
try {
|
||||
const artistId = parseInt(req.params.id, 10);
|
||||
const result = await pool.query(`SELECT name FROM artists WHERE id = $1`, [artistId]);
|
||||
if (result.rows.length === 0) {
|
||||
return res.status(404).json({ error: 'Artist not found' });
|
||||
}
|
||||
|
||||
const { name } = result.rows[0];
|
||||
const search = await searchArtistPortraitFirst(name);
|
||||
res.json(search);
|
||||
} catch (err) {
|
||||
console.error('Debug portrait search error:', err.message);
|
||||
res.status(500).json({ error: 'Portrait search failed' });
|
||||
}
|
||||
});
|
||||
|
||||
// Developer debug: replace artist portrait with a search result URL
|
||||
app.post('/api/artists/:id/fix-portrait', async (req, res) => {
|
||||
try {
|
||||
const artistId = parseInt(req.params.id, 10);
|
||||
const { imageUrl, searchUrl, source, pageUrl, thumbUrl } = req.body ?? {};
|
||||
if (!imageUrl || typeof imageUrl !== 'string' || !/^https?:\/\//i.test(imageUrl)) {
|
||||
return res.status(400).json({ error: 'Valid imageUrl required' });
|
||||
}
|
||||
|
||||
const updated = await replaceArtistPortraitFromUrl(artistId, imageUrl, {
|
||||
searchUrl: typeof searchUrl === 'string' ? searchUrl : undefined,
|
||||
source: typeof source === 'string' ? source : undefined,
|
||||
pageUrl: typeof pageUrl === 'string' ? pageUrl : undefined,
|
||||
thumbUrl: typeof thumbUrl === 'string' ? thumbUrl : undefined,
|
||||
});
|
||||
await pool.query(
|
||||
`UPDATE artists SET checkup_fixed = true, checkup_checked = true WHERE id = $1`,
|
||||
[artistId]
|
||||
);
|
||||
res.json({ ...updated, fixed: true, checked: true });
|
||||
} catch (err) {
|
||||
console.error('Fix portrait error:', err.message);
|
||||
res.status(500).json({ error: friendlyImageFetchError(err) });
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/api/artists/:id/clear-portrait', async (req, res) => {
|
||||
try {
|
||||
const artistId = parseInt(req.params.id, 10);
|
||||
const updated = await clearArtistPortrait(artistId);
|
||||
await pool.query(
|
||||
`UPDATE artists SET checkup_fixed = true, checkup_checked = true WHERE id = $1`,
|
||||
[artistId]
|
||||
);
|
||||
res.json({ ...updated, fixed: true, checked: true });
|
||||
} catch (err) {
|
||||
console.error('Clear portrait error:', err.message);
|
||||
res.status(500).json({ error: err.message || 'Could not clear portrait' });
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/api/artists/:id/upload-portrait', async (req, res) => {
|
||||
try {
|
||||
const artistId = parseInt(req.params.id, 10);
|
||||
const { imageData, mimeType } = req.body ?? {};
|
||||
if (!imageData || typeof imageData !== 'string') {
|
||||
return res.status(400).json({ error: 'imageData required' });
|
||||
}
|
||||
const buffer = Buffer.from(imageData, 'base64');
|
||||
if (!buffer.length) {
|
||||
return res.status(400).json({ error: 'Empty image data' });
|
||||
}
|
||||
if (buffer.length > 15 * 1024 * 1024) {
|
||||
return res.status(400).json({ error: 'Image too large (max 15 MB)' });
|
||||
}
|
||||
|
||||
const updated = await replaceArtistPortraitFromBuffer(
|
||||
artistId,
|
||||
buffer,
|
||||
typeof mimeType === 'string' ? mimeType : 'image/jpeg'
|
||||
);
|
||||
await pool.query(
|
||||
`UPDATE artists SET checkup_fixed = true, checkup_checked = true WHERE id = $1`,
|
||||
[artistId]
|
||||
);
|
||||
res.json({ ...updated, fixed: true, checked: true });
|
||||
} catch (err) {
|
||||
console.error('Upload portrait error:', err.message);
|
||||
res.status(500).json({ error: err.message || 'Could not upload portrait' });
|
||||
}
|
||||
});
|
||||
|
||||
// Artist detail with periods and paintings
|
||||
app.get('/api/artists/:id', async (req, res) => {
|
||||
try {
|
||||
@@ -452,6 +619,30 @@ 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', 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));
|
||||
const result = await pool.query(
|
||||
`SELECT p.title, a.name AS artist_name
|
||||
FROM paintings p
|
||||
JOIN artists a ON a.id = p.artist_id
|
||||
WHERE p.id = $1`,
|
||||
[paintingId]
|
||||
);
|
||||
if (result.rows.length === 0) {
|
||||
return res.status(404).json({ error: 'Painting not found' });
|
||||
}
|
||||
|
||||
const { title, artist_name: artistName } = result.rows[0];
|
||||
const search = await searchPaintingImagesMany(artistName, title, limit);
|
||||
res.json(search);
|
||||
} catch (err) {
|
||||
console.error('Debug image search (more) error:', err.message);
|
||||
res.status(500).json({ error: 'Image search failed' });
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/api/paintings/:id/debug-image-search', async (req, res) => {
|
||||
try {
|
||||
const paintingId = parseInt(req.params.id, 10);
|
||||
@@ -501,6 +692,52 @@ app.post('/api/paintings/:id/fix-image', async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/api/paintings/:id/clear-image', async (req, res) => {
|
||||
try {
|
||||
const paintingId = parseInt(req.params.id, 10);
|
||||
const updated = await clearPaintingImage(paintingId);
|
||||
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('Clear image error:', err.message);
|
||||
res.status(500).json({ error: err.message || 'Could not clear image' });
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/api/paintings/:id/upload-image', async (req, res) => {
|
||||
try {
|
||||
const paintingId = parseInt(req.params.id, 10);
|
||||
const { imageData, mimeType } = req.body ?? {};
|
||||
if (!imageData || typeof imageData !== 'string') {
|
||||
return res.status(400).json({ error: 'imageData required' });
|
||||
}
|
||||
const buffer = Buffer.from(imageData, 'base64');
|
||||
if (!buffer.length) {
|
||||
return res.status(400).json({ error: 'Empty image data' });
|
||||
}
|
||||
if (buffer.length > 15 * 1024 * 1024) {
|
||||
return res.status(400).json({ error: 'Image too large (max 15 MB)' });
|
||||
}
|
||||
|
||||
const updated = await replacePaintingImageFromBuffer(
|
||||
paintingId,
|
||||
buffer,
|
||||
typeof mimeType === 'string' ? mimeType : 'image/jpeg'
|
||||
);
|
||||
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('Upload image error:', err.message);
|
||||
res.status(500).json({ error: err.message || 'Could not upload image' });
|
||||
}
|
||||
});
|
||||
|
||||
// Proxy remote image for debug preview (avoids hotlink / CORS blocks)
|
||||
app.get('/api/debug/image-proxy', async (req, res) => {
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user