fix(13): CR-03 wrap post-upload DB block in try/except to handle provider-success/DB-fail divergence

This commit is contained in:
curo1305
2026-06-23 00:28:34 +02:00
parent a1d1c3ba2e
commit 405c7a6610
+63 -38
View File
@@ -1043,47 +1043,72 @@ async def upload_cloud_file(
content_type=content_type, content_type=content_type,
size=resolved_size, size=resolved_size,
) )
# Upsert item — establishes stable row identity for navigation
await upsert_cloud_item(session, user_id=str(current_user.id), resource=resource)
# Invalidate parent folder so next browse triggers a provider re-list. # DB mutation block: upsert + folder-state invalidation + audit + commit.
# We do NOT call apply_listing_and_finalize — that requires a full provider # CR-03: Wrapped in try/except so that a DB failure after a successful
# listing which we don't have. Instead we mark the folder stale with a # provider upload is reported distinctly rather than propagating as an
# controlled reason so the browse endpoint knows to re-fetch. # unhandled 500. The file IS on the provider; the metadata recording
invalidate_parent = resolved_parent_ref if resolved_parent_ref is not None else "" # failed. The frontend receives a typed error so it can surface the
await update_folder_state( # discrepancy rather than silently losing the upload record.
session, try:
user_id=str(current_user.id), # Upsert item — establishes stable row identity for navigation
connection_id=str(connection_id), await upsert_cloud_item(session, user_id=str(current_user.id), resource=resource)
parent_ref=invalidate_parent,
refresh_state="warning",
error_code="upload_mutated",
error_message="Folder contents changed by upload — re-listing required.",
)
# Write metadata-only audit row in the same transaction (Plan 06, Task 2: T-13-20). # Invalidate parent folder so next browse triggers a provider re-list.
# Only authoritative upload success reaches this path. Non-success outcomes # We do NOT call apply_listing_and_finalize — that requires a full provider
# (conflict, offline, reauth_required) are handled in separate branches below # listing which we don't have. Instead we mark the folder stale with a
# and never call write_audit_log — preserving T-13-21 (no false success events). # controlled reason so the browse endpoint knows to re-fetch.
# The audit payload is metadata-only: no provider URLs, tokens, bytes, or invalidate_parent = resolved_parent_ref if resolved_parent_ref is not None else ""
# document text are included (T-13-02). await update_folder_state(
await write_audit_log( session,
session, user_id=str(current_user.id),
event_type="cloud.file_uploaded", connection_id=str(connection_id),
user_id=current_user.id, parent_ref=invalidate_parent,
actor_id=current_user.id, refresh_state="warning",
resource_id=None, error_code="upload_mutated",
ip_address=get_client_ip(request), error_message="Folder contents changed by upload — re-listing required.",
metadata_={ )
"connection_id": str(connection_id),
"provider_item_id": provider_item_id,
"filename": resolved_name,
"size_bytes": resolved_size,
"parent_ref": resolved_parent_ref,
},
)
await session.commit() # Write metadata-only audit row in the same transaction (Plan 06, Task 2: T-13-20).
# Only authoritative upload success reaches this path. Non-success outcomes
# (conflict, offline, reauth_required) are handled in separate branches below
# and never call write_audit_log — preserving T-13-21 (no false success events).
# The audit payload is metadata-only: no provider URLs, tokens, bytes, or
# document text are included (T-13-02).
await write_audit_log(
session,
event_type="cloud.file_uploaded",
user_id=current_user.id,
actor_id=current_user.id,
resource_id=None,
ip_address=get_client_ip(request),
metadata_={
"connection_id": str(connection_id),
"provider_item_id": provider_item_id,
"filename": resolved_name,
"size_bytes": resolved_size,
"parent_ref": resolved_parent_ref,
},
)
await session.commit()
except Exception:
# Provider upload succeeded but DB recording failed.
# Roll back any partial writes and return a typed error so the frontend
# knows the file is on the provider but DocuVault has no metadata record.
await session.rollback()
return JSONResponse(
status_code=status.HTTP_207_MULTI_STATUS,
content={
"kind": "provider_success_db_error",
"reason": "metadata_record_failed",
"provider_item_id": provider_item_id,
"message": (
"File uploaded to provider but DocuVault metadata recording failed. "
"Refresh the folder to re-sync."
),
},
)
return { return {
"kind": "uploaded", "kind": "uploaded",