Namecard photo resize backend#125
Open
AndrewHUNGNguyen wants to merge 3 commits into
Open
Conversation
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.
Summary
Moves nametag profile photo uploads from direct client-to-Supabase storage to a server-side upload API that resizes and normalizes images before saving them.
This PR adds a backend processing step using Sharp so uploaded avatars are:
Frontend upload flows on
/setupand/whoisnow call a shareduploadAvatar()helper instead of uploading directly to Supabase Storage.New files
lib/avatarImageConfig.tsCentralizes avatar image constants (max dimension, max input size, allowed formats, WebP quality) so validation and processing stay in sync.
lib/processAvatarImage.tsCore Sharp processing logic. Validates input, rotates based on EXIF, resizes with
fit: "inside"andwithoutEnlargement: true, and outputs WebP. Throws typedAvatarImageErrors for API responses.lib/supabaseServer.tsServer-side Supabase helpers for API routes. Creates a user-scoped Supabase client from the request bearer token and verifies the user is authenticated before allowing uploads.
lib/uploadAvatar.tsClient-side helper used by setup/profile pages. Sends the selected file to the new API route with the user's session token and returns the processed image's public URL.
app/api/avatars/upload/route.tsNew authenticated upload endpoint. Accepts multipart form data, runs validation + Sharp processing, uploads the processed
.webpfile to theavatarsbucket, and returns the public URL.Modified files
package.jsonAdds
sharpas a dependency for server-side image processing.app/setup/page.tsxReplaces direct Supabase Storage upload logic with
uploadAvatar(file)so onboarding uses the new resize pipeline.app/whois/ProfileDisplay.tsxSame refactor as setup page — profile photo edits now go through the shared upload helper/API.
app/components/Nametag.tsxChanges nametag avatar display from
object-fit: covertoobject-fit: fillso the full processed image is shown in the square photo frame without cropping (with the tradeoff that non-square images may stretch).Resize behavior
Sharp uses:
fit: "inside"— scales image down to fit within 512×512 without croppingwithoutEnlargement: true— does not upscale images smaller than 512px.webp({ quality: 85 })— converts output to WebPExample:
1200×1482PNG415×512WebPTest plan
supabase startand point.env.localat local Supabasebun installandbun run dev/setup/whoisavatarsis<uuid>.webpResize verification results from terminal
% bun -e 'import sharp from "sharp"; import { processAvatarImage } from "./lib/processAvatarImage.ts";
const p="{some_image}";
const input = await sharp(p).toBuffer();
const before = await sharp(input).metadata();
const output = await processAvatarImage(input);
const after = await sharp(output).metadata();
console.log({ before:
${before.width}x${before.height} ${before.format}, after:${after.width}x${after.height} ${after.format}, outputBytes: output.length });'{
before: "600x500 jpeg",
after: "512x427 webp",
outputBytes: 31204,
}