From 027c9d2f029e9431d577b4ee5d2e2099092ea988 Mon Sep 17 00:00:00 2001 From: jross Date: Mon, 8 Jun 2026 10:53:56 -0600 Subject: [PATCH] fix: validate thing_id before GCS upload in asset upload endpoint Move session.get(Thing, thing_id) ahead of gcs_upload in upload_and_record_asset so requests with a nonexistent thing_id short-circuit with 409 before any GCS object is written. Previously the file was uploaded first, then the 409 raised, leaving an orphaned blob with no Asset row. Co-Authored-By: Claude Opus 4.7 --- api/asset.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/api/asset.py b/api/asset.py index 125fdb51..6de8e132 100644 --- a/api/asset.py +++ b/api/asset.py @@ -239,15 +239,7 @@ async def upload_and_record_asset( ], ) - # ── 3. Upload file to GCS (blocking I/O — run in thread pool) ──────────── - uri, blob_name = await run_in_threadpool(gcs_upload, file, bucket) - - # ── 4. Return existing record for duplicate file + thing combinations ───── - existing = check_asset_exists(session, blob_name, thing_id=thing_id) - if existing: - return existing - - # ── 5. Validate the Thing exists ───────────────────────────────────────── + # ── 3. Validate the Thing exists (before upload to avoid orphaned blobs) ─ thing = session.get(Thing, thing_id) if thing is None: raise PydanticStyleException( @@ -262,6 +254,14 @@ async def upload_and_record_asset( ], ) + # ── 4. Upload file to GCS (blocking I/O — run in thread pool) ──────────── + uri, blob_name = await run_in_threadpool(gcs_upload, file, bucket) + + # ── 5. Return existing record for duplicate file + thing combinations ───── + existing = check_asset_exists(session, blob_name, thing_id=thing_id) + if existing: + return existing + # ── 6. Persist the Asset record ─────────────────────────────────────────── asset = Asset( name=name or file.filename,