Fix debug upload persistence and UX; exclude prod audit log from DB restore
Uploads and fixes now bust browser cache via file-mtime keys in API responses. Debug upload shows a centered loading overlay and blocks search while uploading. Prod DB restore skips curator_audit_log. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
62096e8210
commit
f78c14f307
+68
-7
@@ -13,6 +13,59 @@ const FETCH_TIMEOUT_MS = 15000;
|
||||
const PORTRAIT_THUMB_WIDTH = 256;
|
||||
const inflight = new Map();
|
||||
|
||||
function imageFileCacheKey(relPath) {
|
||||
if (!relPath || typeof relPath !== 'string') return null;
|
||||
const abs = path.join(IMAGE_DIR, relPath.replace(/^\//, ''));
|
||||
try {
|
||||
if (!fs.existsSync(abs)) return null;
|
||||
return Math.floor(fs.statSync(abs).mtimeMs);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function enrichPaintingRow(row) {
|
||||
if (!row || typeof row !== 'object') return row;
|
||||
return {
|
||||
...row,
|
||||
image_cache_key: imageFileCacheKey(row.image_path),
|
||||
thumbnail_cache_key: imageFileCacheKey(row.thumbnail_path),
|
||||
};
|
||||
}
|
||||
|
||||
function enrichArtistRow(row) {
|
||||
if (!row || typeof row !== 'object') return row;
|
||||
return {
|
||||
...row,
|
||||
portrait_cache_key: imageFileCacheKey(row.portrait_path),
|
||||
portrait_thumb_cache_key: imageFileCacheKey(row.portrait_thumb_path),
|
||||
};
|
||||
}
|
||||
|
||||
function paintingImagePayload(paths, extra = {}) {
|
||||
const imagePath = paths.imagePath ?? paths.image_path ?? null;
|
||||
const thumbnailPath = paths.thumbnailPath ?? paths.thumbnail_path ?? null;
|
||||
return {
|
||||
imagePath,
|
||||
thumbnailPath,
|
||||
image_cache_key: imageFileCacheKey(imagePath),
|
||||
thumbnail_cache_key: imageFileCacheKey(thumbnailPath),
|
||||
...extra,
|
||||
};
|
||||
}
|
||||
|
||||
function artistPortraitPayload(paths, extra = {}) {
|
||||
const portraitPath = paths.portraitPath ?? paths.portrait_path ?? null;
|
||||
const portraitThumbPath = paths.portraitThumbPath ?? paths.portrait_thumb_path ?? null;
|
||||
return {
|
||||
portraitPath,
|
||||
portraitThumbPath,
|
||||
portrait_cache_key: imageFileCacheKey(portraitPath),
|
||||
portrait_thumb_cache_key: imageFileCacheKey(portraitThumbPath),
|
||||
...extra,
|
||||
};
|
||||
}
|
||||
|
||||
function safePaintingBase(artistName, title) {
|
||||
return `${artistName}_${title}`.replace(/[^a-zA-Z0-9_-]/g, '_');
|
||||
}
|
||||
@@ -269,7 +322,7 @@ async function clearPaintingImage(paintingId) {
|
||||
[paintingId]
|
||||
);
|
||||
|
||||
return { imagePath: null, thumbnailPath: null };
|
||||
return paintingImagePayload({ imagePath: null, thumbnailPath: null });
|
||||
}
|
||||
|
||||
async function clearArtistPortrait(artistId) {
|
||||
@@ -290,7 +343,7 @@ async function clearArtistPortrait(artistId) {
|
||||
[artistId]
|
||||
);
|
||||
|
||||
return { portraitPath: null, portraitThumbPath: null };
|
||||
return artistPortraitPayload({ portraitPath: null, portraitThumbPath: null });
|
||||
}
|
||||
|
||||
async function replacePaintingImageFromBuffer(paintingId, buffer, mimeType) {
|
||||
@@ -344,7 +397,7 @@ async function replacePaintingImageFromBuffer(paintingId, buffer, mimeType) {
|
||||
[imagePath, thumbnailPath, paintingId]
|
||||
);
|
||||
|
||||
return { imagePath, thumbnailPath };
|
||||
return paintingImagePayload({ imagePath, thumbnailPath });
|
||||
}
|
||||
|
||||
async function replaceArtistPortraitFromBuffer(artistId, buffer, mimeType) {
|
||||
@@ -387,12 +440,14 @@ async function replaceArtistPortraitFromBuffer(artistId, buffer, mimeType) {
|
||||
fs.writeFileSync(fullDest, buffer);
|
||||
const portraitPath = path.join('portraits', safeBase + fullExt).replace(/\\/g, '/');
|
||||
const portraitThumbPath = (await writePortraitThumb(fullDest, safeBase)) || portraitPath;
|
||||
return updateArtistPortraitPaths(artistId, portraitPath, portraitThumbPath);
|
||||
const updated = await updateArtistPortraitPaths(artistId, portraitPath, portraitThumbPath);
|
||||
return artistPortraitPayload(updated);
|
||||
}
|
||||
|
||||
const portraitPath = path.join('portraits', safeBase + '.jpg').replace(/\\/g, '/');
|
||||
const portraitThumbPath = (await writePortraitThumb(jpgDest, safeBase)) || portraitPath;
|
||||
return updateArtistPortraitPaths(artistId, portraitPath, portraitThumbPath);
|
||||
const updated = await updateArtistPortraitPaths(artistId, portraitPath, portraitThumbPath);
|
||||
return artistPortraitPayload(updated);
|
||||
}
|
||||
|
||||
async function replacePaintingImageFromUrl(paintingId, imageUrl, context = {}) {
|
||||
@@ -436,7 +491,7 @@ async function replacePaintingImageFromUrl(paintingId, imageUrl, context = {}) {
|
||||
[imagePath, thumbnailPath, paintingId]
|
||||
);
|
||||
|
||||
return { imagePath, thumbnailPath };
|
||||
return paintingImagePayload({ imagePath, thumbnailPath });
|
||||
}
|
||||
|
||||
async function replaceArtistPortraitFromUrl(artistId, imageUrl, context = {}) {
|
||||
@@ -480,7 +535,8 @@ async function replaceArtistPortraitFromUrl(artistId, imageUrl, context = {}) {
|
||||
|
||||
const sourceForThumb = fs.existsSync(jpgDest) ? jpgDest : path.join(IMAGE_DIR, portraitPath);
|
||||
const portraitThumbPath = (await writePortraitThumb(sourceForThumb, safeBase)) || portraitPath;
|
||||
return updateArtistPortraitPaths(artistId, portraitPath, portraitThumbPath);
|
||||
const updated = await updateArtistPortraitPaths(artistId, portraitPath, portraitThumbPath);
|
||||
return artistPortraitPayload(updated);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
@@ -493,5 +549,10 @@ module.exports = {
|
||||
clearArtistPortrait,
|
||||
replacePaintingImageFromBuffer,
|
||||
replaceArtistPortraitFromBuffer,
|
||||
enrichPaintingRow,
|
||||
enrichArtistRow,
|
||||
paintingImagePayload,
|
||||
artistPortraitPayload,
|
||||
imageFileCacheKey,
|
||||
IMAGE_DIR,
|
||||
};
|
||||
|
||||
+9
-9
@@ -11,7 +11,7 @@ const { createSessionMiddleware } = require('./middleware/session');
|
||||
const { requireCurator } = require('./middleware/auth');
|
||||
const { logCuratorAction } = require('./audit-log');
|
||||
const authRoutes = require('./routes/auth');
|
||||
const { ensurePaintingImages, preloadArtistImagesLocal, replacePaintingImageFromUrl, replaceArtistPortraitFromUrl, clearPaintingImage, deletePainting, clearArtistPortrait, replacePaintingImageFromBuffer, replaceArtistPortraitFromBuffer, IMAGE_DIR } = require('./image-service');
|
||||
const { ensurePaintingImages, preloadArtistImagesLocal, replacePaintingImageFromUrl, replaceArtistPortraitFromUrl, clearPaintingImage, deletePainting, clearArtistPortrait, replacePaintingImageFromBuffer, replaceArtistPortraitFromBuffer, enrichPaintingRow, enrichArtistRow, IMAGE_DIR } = require('./image-service');
|
||||
const { getVersionInfo } = require('./version-info');
|
||||
const { searchGoogleImagesFirst, searchArtistPortraitFirst, searchPaintingImagesMany, searchArtistPortraitMany, fetchImageBuffer, friendlyImageFetchError, pickExt } = require('../scripts/image-fetcher');
|
||||
|
||||
@@ -32,10 +32,10 @@ app.use('/api/auth', authRoutes);
|
||||
app.use(
|
||||
'/images',
|
||||
express.static(IMAGE_DIR, {
|
||||
maxAge: '365d',
|
||||
immutable: true,
|
||||
etag: true,
|
||||
lastModified: true,
|
||||
setHeaders(res) {
|
||||
res.setHeader('Cache-Control', 'public, max-age=31536000, immutable');
|
||||
res.setHeader('Cache-Control', 'public, max-age=0, must-revalidate');
|
||||
},
|
||||
})
|
||||
);
|
||||
@@ -250,7 +250,7 @@ app.get('/api/movements/:id/gallery', async (req, res) => {
|
||||
|
||||
res.json({
|
||||
movement: movement.rows[0],
|
||||
paintings: paintings.rows,
|
||||
paintings: paintings.rows.map(enrichPaintingRow),
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
@@ -628,9 +628,9 @@ app.get('/api/artists/:id', async (req, res) => {
|
||||
}
|
||||
|
||||
res.json({
|
||||
artist: artist.rows[0],
|
||||
artist: enrichArtistRow(artist.rows[0]),
|
||||
periods: periods.rows,
|
||||
paintings: paintings.rows,
|
||||
paintings: paintings.rows.map(enrichPaintingRow),
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
@@ -786,9 +786,9 @@ app.get('/api/paintings/:id', async (req, res) => {
|
||||
]);
|
||||
|
||||
res.json({
|
||||
painting: painting.rows[0],
|
||||
painting: enrichPaintingRow(painting.rows[0]),
|
||||
influencedBy: influencedBy.rows,
|
||||
influenced: influenced.rows,
|
||||
influenced: influenced.rows.map(enrichPaintingRow),
|
||||
annotations: annotations.rows,
|
||||
});
|
||||
} catch (err) {
|
||||
|
||||
Reference in New Issue
Block a user