fix: move image moderation to background task + eliminate R2 delete spray (#1266)
* fix: move image moderation to background task + eliminate R2 delete spray
Two perf fixes for track/album image edits, observed via Logfire traces
on a real user session (annamist.com editing track 862):
1. Image moderation scan moved to docket background task (~6s saved).
The moderation service (claude vision on Fly.io) was awaited inline
during PATCH /tracks/{id}, blocking the response for 3-6s + cold
start penalty. The scan only flags and notifies — it never gates the
response — so there's no reason to block on it. New docket task
scan_image_moderation fetches the image from its R2 URL and scans
asynchronously, matching how copyright audio scans already work.
2. R2 image delete now uses the known URL extension (~1.3s saved).
storage.delete() with no file_type falls through to trying every
AudioFormat extension (7-8 HEAD requests) then every ImageFormat
extension (5 HEAD requests) sequentially. New delete_image() method
parses the extension from the image URL and hits the correct key on
the first try. Falls back to the spray only if URL parsing fails.
Combined: a track image edit that was taking ~8.9s should now take
~1s (R2 upload + thumbnail + DB update + ATProto sync).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test: mock delete_image alongside delete in track deletion tests
CI caught that test_track_deletion_deletes_unshared_image only mocked
storage.delete but the code now calls storage.delete_image for images
that have a URL. Add the delete_image mock to all three deletion test
cases so they capture both audio and image delete calls in the same
delete_calls list.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
authored by