Improve error logging for photo uploads

This commit is contained in:
Sam Becker 2025-01-20 14:40:33 -06:00
parent ebbf01c698
commit 33a950f73c
2 changed files with 62 additions and 41 deletions

View File

@ -8,6 +8,7 @@ import {
AI_TEXT_GENERATION_ENABLED, AI_TEXT_GENERATION_ENABLED,
BLUR_ENABLED, BLUR_ENABLED,
} from '@/site/config'; } from '@/site/config';
import ErrorNote from '@/components/ErrorNote';
export const maxDuration = 60; export const maxDuration = 60;
@ -23,16 +24,19 @@ export default async function UploadPage({ params }: Params) {
photoFormExif, photoFormExif,
imageResizedBase64: imageThumbnailBase64, imageResizedBase64: imageThumbnailBase64,
shouldStripGpsData, shouldStripGpsData,
error,
} = await extractImageDataFromBlobPath(uploadPath, { } = await extractImageDataFromBlobPath(uploadPath, {
includeInitialPhotoFields: true, includeInitialPhotoFields: true,
generateBlurData: BLUR_ENABLED, generateBlurData: BLUR_ENABLED,
generateResizedImage: AI_TEXT_GENERATION_ENABLED, generateResizedImage: AI_TEXT_GENERATION_ENABLED,
}); });
if ( const isDataMissing =
!photoFormExif || !photoFormExif ||
(AI_TEXT_GENERATION_ENABLED && !imageThumbnailBase64) (AI_TEXT_GENERATION_ENABLED && !imageThumbnailBase64);
) {
if (isDataMissing && !error) {
// Only redirect if there's no error to report
redirect(PATH_ADMIN); redirect(PATH_ADMIN);
} }
@ -43,14 +47,18 @@ export default async function UploadPage({ params }: Params) {
const textFieldsToAutoGenerate = AI_TEXT_AUTO_GENERATED_FIELDS; const textFieldsToAutoGenerate = AI_TEXT_AUTO_GENERATED_FIELDS;
return ( return (
<UploadPageClient {...{ !isDataMissing
blobId, ? <UploadPageClient {...{
photoFormExif, blobId,
uniqueTags, photoFormExif,
hasAiTextGeneration, uniqueTags,
textFieldsToAutoGenerate, hasAiTextGeneration,
imageThumbnailBase64, textFieldsToAutoGenerate,
shouldStripGpsData, imageThumbnailBase64,
}} /> shouldStripGpsData,
}} />
: <ErrorNote>
{error ?? 'Unknown error'}
</ErrorNote>
); );
}; };

View File

@ -29,6 +29,7 @@ export const extractImageDataFromBlobPath = async (
imageResizedBase64?: string imageResizedBase64?: string
shouldStripGpsData?: boolean shouldStripGpsData?: boolean
fileBytes?: ArrayBuffer fileBytes?: ArrayBuffer
error?: string
}> => { }> => {
const { const {
includeInitialPhotoFields, includeInitialPhotoFields,
@ -42,49 +43,60 @@ export const extractImageDataFromBlobPath = async (
const extension = getExtensionFromStorageUrl(url); const extension = getExtensionFromStorageUrl(url);
const fileBytes = blobPath
? await fetch(url, { cache: 'no-store' }).then(res => res.arrayBuffer())
: undefined;
let exifData: ExifData | undefined; let exifData: ExifData | undefined;
let filmSimulation: FilmSimulation | undefined; let filmSimulation: FilmSimulation | undefined;
let blurData: string | undefined; let blurData: string | undefined;
let imageResizedBase64: string | undefined; let imageResizedBase64: string | undefined;
let shouldStripGpsData = false; let shouldStripGpsData = false;
let error: string | undefined;
if (fileBytes) { const fileBytes = blobPath
const parser = ExifParserFactory.create(Buffer.from(fileBytes)); ? await fetch(url, { cache: 'no-store' }).then(res => res.arrayBuffer())
.catch(e => {
error = `Error fetching image from ${url}: "${e.message}"`;
return undefined;
})
: undefined;
// Data for form try {
parser.enableBinaryFields(false); if (fileBytes) {
exifData = parser.parse(); const parser = ExifParserFactory.create(Buffer.from(fileBytes));
// Capture film simulation for Fujifilm cameras // Data for form
if (isExifForFujifilm(exifData)) { parser.enableBinaryFields(false);
// Parse exif data again with binary fields exifData = parser.parse();
// in order to access MakerNote tag
parser.enableBinaryFields(true); // Capture film simulation for Fujifilm cameras
const exifDataBinary = parser.parse(); if (isExifForFujifilm(exifData)) {
const makerNote = exifDataBinary.tags?.MakerNote; // Parse exif data again with binary fields
if (Buffer.isBuffer(makerNote)) { // in order to access MakerNote tag
filmSimulation = getFujifilmSimulationFromMakerNote(makerNote); parser.enableBinaryFields(true);
const exifDataBinary = parser.parse();
const makerNote = exifDataBinary.tags?.MakerNote;
if (Buffer.isBuffer(makerNote)) {
filmSimulation = getFujifilmSimulationFromMakerNote(makerNote);
}
} }
}
if (generateBlurData) { if (generateBlurData) {
blurData = await blurImage(fileBytes); blurData = await blurImage(fileBytes);
} }
if (generateResizedImage) { if (generateResizedImage) {
imageResizedBase64 = await resizeImage(fileBytes); imageResizedBase64 = await resizeImage(fileBytes);
} }
shouldStripGpsData = GEO_PRIVACY_ENABLED && ( shouldStripGpsData = GEO_PRIVACY_ENABLED && (
Boolean(exifData.tags?.GPSLatitude) || Boolean(exifData.tags?.GPSLatitude) ||
Boolean(exifData.tags?.GPSLongitude) Boolean(exifData.tags?.GPSLongitude)
); );
}
} catch (e) {
error = `Error extracting image data from ${url}: "${e}"`;
} }
if (error) { console.log(error); }
return { return {
blobId, blobId,
...exifData && { ...exifData && {
@ -102,6 +114,7 @@ export const extractImageDataFromBlobPath = async (
imageResizedBase64, imageResizedBase64,
shouldStripGpsData, shouldStripGpsData,
fileBytes, fileBytes,
error,
}; };
}; };