fix(uploads): RAW derivative key collision, watermark skip, dev exiftool (codex review of #833 round 2)
- Derivative key collision: processUploadedPhotos/replacePhoto passed the client-supplied original filename as the RAW output basename, but thumbnails/ heroes/previews are global keys — two galleries uploading IMG_0001.dng would overwrite each other's derivative. Use the unique stored newFilename instead. (processPhoto already used the unique photo.filename.) - Watermark: the watermark path opens the original with sharp, which can't decode RAW, so it fell back to the original bytes and recorded the copy as watermarked. Skip RAW in generateForPhoto (like videos) so the watermark state stays honest until RAW watermarking is properly supported. - exiftool added to Dockerfile.dev so dev/native runtimes don't accept a DNG then fail it with ENOENT.
This commit is contained in:
@@ -8,7 +8,10 @@ RUN apk upgrade --no-cache
|
||||
# Install dumb-init for proper signal handling and ffmpeg for video uploads.
|
||||
# Alpine's ffmpeg ships both ffmpeg + ffprobe built natively against musl;
|
||||
# the npm-bundled binary doesn't run reliably on Alpine. Match production.
|
||||
RUN apk add --no-cache dumb-init ffmpeg
|
||||
# exiftool: extract embedded JPEG previews from RAW/DNG uploads (#821) — kept in
|
||||
# sync with the production Dockerfile so dev/native runtimes don't accept a DNG
|
||||
# and then fail it with ENOENT.
|
||||
RUN apk add --no-cache dumb-init ffmpeg exiftool
|
||||
|
||||
# Copy package files
|
||||
COPY package*.json ./
|
||||
|
||||
@@ -148,7 +148,9 @@ async function processUploadedPhotos(files, eventId, uploadedBy = 'admin', categ
|
||||
// RAW/DNG can't be fed to sharp directly (no raw loader), so extract the
|
||||
// embedded JPEG preview first and thumbnail/measure THAT. Pass-through
|
||||
// for ordinary images. The stored original stays the RAW (download).
|
||||
const proc = await withProcessableImage(tempPath, file.originalname);
|
||||
// Use the unique stored filename (not the client-supplied original) so
|
||||
// the RAW-derived thumbnail's global key can't collide across galleries.
|
||||
const proc = await withProcessableImage(tempPath, newFilename);
|
||||
try {
|
||||
thumbnailPath = await generateThumbnail(proc.path, { outputBasename: proc.outputBasename });
|
||||
try {
|
||||
|
||||
@@ -69,7 +69,9 @@ async function replacePhoto(existingPhoto, newFileTempPath, { originalFilename,
|
||||
let width = null;
|
||||
let height = null;
|
||||
let thumbnailPath = null;
|
||||
const proc = await withProcessableImage(newFileTempPath, originalFilename);
|
||||
// Detect/name by the unique stored filename (newFilename), not the
|
||||
// client-supplied original, so RAW derivative keys can't collide.
|
||||
const proc = await withProcessableImage(newFileTempPath, newFilename);
|
||||
try {
|
||||
try {
|
||||
const metadata = await sharp(proc.path).metadata();
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
const { db } = require('../database/db');
|
||||
const watermarkService = require('./watermarkService');
|
||||
const { resolvePhotoStorageKey, resolvePhotoFilePath } = require('./photoResolver');
|
||||
const { withLocalCopy } = require('./imageProcessor');
|
||||
const { withLocalCopy, isRawFilename } = require('./imageProcessor');
|
||||
const logger = require('../utils/logger');
|
||||
|
||||
class WatermarkGeneratorService {
|
||||
@@ -52,6 +52,14 @@ class WatermarkGeneratorService {
|
||||
return { success: false, error: 'Videos do not support watermarks' };
|
||||
}
|
||||
|
||||
// Skip RAW/DNG (experimental, #821). The watermark path opens the original
|
||||
// with sharp, which can't decode RAW — proceeding would fall back to the
|
||||
// original bytes and falsely record the copy as watermarked. Skipping keeps
|
||||
// the watermark state honest until RAW watermarking is properly supported.
|
||||
if (isRawFilename(photo.filename)) {
|
||||
return { success: false, error: 'RAW/DNG files are not watermarked yet' };
|
||||
}
|
||||
|
||||
// Get watermark settings
|
||||
const settings = await watermarkService.getWatermarkSettings();
|
||||
if (!settings || !settings.enabled) {
|
||||
|
||||
Reference in New Issue
Block a user