feat(user-profile): enforce upload policy on photo selection - #1122
Merged
Conversation
bmc08gt
force-pushed
the
feat/flipcash-upload-policy-enforcement
branch
from
July 23, 2026 01:39
e54e14c to
baa42fa
Compare
Gate profile photo picks against the server UploadPolicy before caching and upload: - Reject MIME types the policy doesn't accept (checked against the re-encoded upload type, not the source, so HEIC/WebP that normalize into an accepted format still pass). - Derive the downscale target from the policy's dimension + pixel caps (min of maxWidth, maxHeight, and sqrt(maxPixels)) instead of a hardcoded 500px; falls back to 500 when no constraints are named. - Enforce maxSizeBytes on the re-encoded output before upload, adding ContentReader.size() for the measurement. Fails open when the policy hasn't loaded; the server remains authoritative. Rejections clear the preview and surface an alert.
… errors Migrate BlobState to a sealed Ready/Rejected model and update all consumers, and surface per-category moderation feedback in the name and photo flows. BlobState (sealed): - toBlobState() now yields BlobState? — READY -> Ready(metadata), REJECTED -> Rejected(reason), and non-terminal PENDING/PROCESSING/ UNKNOWN -> null (callers keep polling). A REJECTED blob missing its reason falls back to UNKNOWN rather than being dropped. - getBlobs uses mapNotNull so a still-processing id resolves to an empty list; awaitReady switches on the sealed type. - BlobRejectedException takes a non-null BlobRejection. Name/photo error handling: - Photo: map BlobRejectedException by RejectionReason, and MODERATION by FlaggedCategory, to specific copy; other terminal reasons show a generic failure. - Name: rely on SetDisplayNameError.FailedModerated and branch on FlaggedCategory for specific copy. - Add per-category name/photo strings; fix subtitle typo. Photo selection also resizes to fit the policy: shrink the longest edge until the re-encoded bytes fit maxSizeBytes rather than rejecting, with a last-resort reject only if the smallest re-encode still overflows.
bmc08gt
force-pushed
the
feat/flipcash-upload-policy-enforcement
branch
from
July 23, 2026 18:37
baa42fa to
615802c
Compare
bmc08gt
added a commit
that referenced
this pull request
Jul 24, 2026
* feat(user-profile): enforce upload policy on photo selection Gate profile photo picks against the server UploadPolicy before caching and upload: - Reject MIME types the policy doesn't accept (checked against the re-encoded upload type, not the source, so HEIC/WebP that normalize into an accepted format still pass). - Derive the downscale target from the policy's dimension + pixel caps (min of maxWidth, maxHeight, and sqrt(maxPixels)) instead of a hardcoded 500px; falls back to 500 when no constraints are named. - Enforce maxSizeBytes on the re-encoded output before upload, adding ContentReader.size() for the measurement. Fails open when the policy hasn't loaded; the server remains authoritative. Rejections clear the preview and surface an alert. * feat(user-profile): sealed BlobState + granular name/photo moderation errors Migrate BlobState to a sealed Ready/Rejected model and update all consumers, and surface per-category moderation feedback in the name and photo flows. BlobState (sealed): - toBlobState() now yields BlobState? — READY -> Ready(metadata), REJECTED -> Rejected(reason), and non-terminal PENDING/PROCESSING/ UNKNOWN -> null (callers keep polling). A REJECTED blob missing its reason falls back to UNKNOWN rather than being dropped. - getBlobs uses mapNotNull so a still-processing id resolves to an empty list; awaitReady switches on the sealed type. - BlobRejectedException takes a non-null BlobRejection. Name/photo error handling: - Photo: map BlobRejectedException by RejectionReason, and MODERATION by FlaggedCategory, to specific copy; other terminal reasons show a generic failure. - Name: rely on SetDisplayNameError.FailedModerated and branch on FlaggedCategory for specific copy. - Add per-category name/photo strings; fix subtitle typo. Photo selection also resizes to fit the policy: shrink the longest edge until the re-encoded bytes fit maxSizeBytes rather than rejecting, with a last-resort reject only if the smallest re-encode still overflows.
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.
What
Enforces the server-provided
UploadPolicyon profile photo selection inPhotoSelectionViewModel. The policy was already loaded into state but never actually consulted — picks were cached, re-encoded, and uploaded regardless of what the server accepts.Changes
uploadMimeFor(...)), not the raw source, so formats that normalize into an accepted type (e.g. HEIC/WebP → PNG) still pass.500. SincecopyToCachebounds the longest edge,maxEdgeFor()takesmin(maxWidth, maxHeight, √maxPixels), which satisfies both dimension caps and the total-area cap. Falls back to500when the policy names no image constraints.maxSizeBytesbefore upload; oversized picks are rejected. AddsContentReader.size(uri)for the measurement.imageTooLargestrings reuse the existing alert pattern).Behavior notes
initiateExternalUpload+ moderation remain authoritative server-side.maxSizeBytes— it rejects instead. For downscaled profile images that ceiling is very unlikely to be hit, so a transcode-retry loop felt like over-engineering. Easy follow-up if desired.Testing
:ui:resourcesand:apps:flipcash:features:user-profilecompile clean.PhotoSelectionViewModel/ContentReader(and no test fakes implement the interface), so this is compile-verified.