Repository navigation
Duplicate-upload guard: same file, same item returns the existing row (#7) - #75
Merged
Merged
Conversation
…blygger#7) POST /api/media with an item_id hashes the bytes (SHA-256) before the R2 write. A row on that item with the same hash, MIME type and placement is returned as 200 { id, url, mime, duplicate: true } and nothing is stored. Migration 0028 adds a nullable media.sha256 (no backfill) with an (item_id, sha256) index, and recreates change_media_update in the remote-safe shape with the new column watched. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The duplicate-upload guard from #7, as its own PR, in the shape you described there. The other half of #7, attachments per version, is left for a separate PR.
What it does. When
POST /api/mediacarries anitem_id, the server takes a SHA-256 of the file's bytes before anything goes to R2. If that item already has a row with the same hash, the same MIME type and the sameinlineplacement, the server stores nothing. It returns the existing row as200 { id, url, mime, duplicate: true }. Otherwise it behaves as before and returns201.altis kept. A second upload with different alt text does not overwrite it.item_id(the avatar, a composer with no draft yet) are never deduplicated.Migration
0028_media_sha256.sql(the number 0029's header reserves for this):media.sha256, with no backfill;(item_id, sha256);change_media_updatein 0027's remote-safe shape:SELECT RAISE … WHEREand noCASE … END;, withsha256added to the watched columns so the polling-cache oracle still covers every column.Contract.
uploadMediagains a200response withduplicate: true, alongside the existing201.openapi.jsonandsdk/generated/are regenerated. The Studio's composer insertsmedia.urlwhatever the status, so a re-dropped image lands in the text with no UI change.docs/api.mdnotes the behaviour.Scope. Returning an existing row writes nothing, so a draft-only token that re-sends a published item's file gets that row back and changes nothing. A new test asserts this: no R2 object, no row, and the item document unchanged.
Tests.
test/media.test.ts, two new cases:200, same id, and onemedia[]entry with the first alt.201and a new row.test/mcp-media.oracle.test.ts:test/media-visibility-security.oracle.test.ts: the "later upload stays private" case now uploads different bytes. The same file again would correctly return the already-published row.Results. Typecheck clean, and
sdk:generateis stable. Worker suite 1748 passed, 5 skipped, 0 failed (142 files). UI 97/97. There is no e2e change, since the Studio needs none.Refs #7 (the guard half; per-version attachments stay open there).
🤖 Generated with Claude Code