be2ec0a4a1
Sharp's bundled libvips has no raw loader, so a DNG can't be thumbnailed directly. This adds a preview-extraction step so RAW/DNG uploads get a proper thumbnail + gallery preview while the original RAW is kept for download. - imageProcessor: isRawFilename() + extractRawPreview() (exiftool extracts the embedded full-res JPEG — JpgFromRaw → PreviewImage → ThumbnailImage, validated with sharp) + withProcessableImage() which is a pass-through for ordinary images and swaps in the extracted JPEG for RAW. Wired into ingest (photoProcessor) and all three on-demand generators (ensureThumbnail/Hero/ Preview). generateHeroImage/generatePreviewImage gained outputBasename so RAW-derived outputs stay named after the source. - Dockerfile: add exiftool (confirmed present in Alpine v3.24 community). - Format maps: dng → image/x-adobe-dng in uploadSettings.js and fileTypes.ts; ALLOWED_MEDIA_TYPES gains a DNG entry (TIFF magic numbers) so it passes the security file-validator. Strictly gated by extension: nothing in this path runs for jpg/png/webp/etc, so existing photos are unaffected. If extraction fails (corrupt RAW, no embedded preview), the photo is marked 'failed' with a clear error — same as any unreadable upload. Verification boundary (please validate on a real DNG after the image rebuilds): the exiftool extraction itself couldn't be exercised in the dev sandbox (exiftool isn't a dev dependency and there's no DNG fixture). Unit tests cover the gating (RAW detection + non-RAW pass-through + clean failure without exiftool); existing processPhoto tests still pass. Known limitation: a DNG is only accepted when the browser reports its MIME as image/x-adobe-dng (Chrome does); browsers that send an empty type reject it client- and server-side — a follow-up can add extension-based acceptance for the RAW set. Companion to the HEIC/dynamic-hint PR; targets main only.