From b31f5f03dae384455cedf50e7c8fc4a391450f3e Mon Sep 17 00:00:00 2001 From: NguyenND Date: Tue, 25 Aug 2026 10:01:20 -0400 Subject: [PATCH] Aug 25 - Fix inspection photo lost --- app/api/photos.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/app/api/photos.py b/app/api/photos.py index 0afbe8d..dd69349 100644 --- a/app/api/photos.py +++ b/app/api/photos.py @@ -76,16 +76,27 @@ def upload_photo(): if user.role not in _ALLOWED_ROLES: return api_error('Access denied', 403) + # Every rejection below is logged at WARNING with the user. Only SUCCESSES + # were logged before, so when an inspector's photos failed repeatedly there + # was nothing server-side to explain why — and a photo that exhausts its + # upload attempts costs the inspection its evidence (see the iPad's + # PendingPhoto.lastUploadError for the device half of this). if 'file' not in request.files: + logger.warning('API PHOTOS | rejected | reason=no_file_part | user=%s', + user.username) return api_error('No file provided', 400) file_obj = request.files['file'] entity_type = request.form.get('entity_type', 'inspection') if not file_obj or not file_obj.filename: + logger.warning('API PHOTOS | rejected | reason=empty_file | user=%s', + user.username) return api_error('Empty file', 400) if not _allowed_file(file_obj.filename): + logger.warning('API PHOTOS | rejected | reason=bad_extension | file=%r | user=%s', + file_obj.filename, user.username) return api_error( f'File type not allowed. Accepted: {", ".join(sorted(_ALLOWED_EXTENSIONS))}', 400 @@ -115,7 +126,16 @@ def upload_photo(): # Write via the active storage backend (local disk or R2). Key format # 'uploads//.' is unchanged across backends. from app.utils import storage - server_path = storage.save(file_obj, subfolder) + try: + server_path = storage.save(file_obj, subfolder) + except Exception as exc: + # A storage failure is the most likely cause of a REPEATED upload + # failure (disk full, R2 credentials/quota). Name it explicitly — + # otherwise it surfaces only as a generic 500 with no link to the + # inspector who is losing evidence photos. + logger.error('API PHOTOS | STORAGE WRITE FAILED | user=%s | entity_type=%s | ' + 'subfolder=%s | error=%s', user.username, entity_type, subfolder, exc) + return api_error('Could not store the photo. Please retry.', 500) logger.info( 'API PHOTOS | uploaded | entity_type=%s | path=%s | user=%s | '