Fix: skip username uniqueness check on partial profile updates#443
Conversation
Greptile SummaryThis PR fixes a 500 crash on
Confidence Score: 3/5The crash fix is correct, but the update handler spreads Zod defaults into every DB write, meaning a partial PATCH silently zeros out skills, tools, portfolio URLs, wallet addresses, and resets availability — real data loss for any user who sends a minimal request. Making username optional and guarding the uniqueness check properly addresses the 500 crash. However, the spread of validationResult.data into the update payload means fields with Zod defaults (skills, ai_tools, portfolio_urls, wallet_addresses, is_available) are unconditionally written to the database when absent from the request body. This turns every partial PATCH into a destructive overwrite of the user's existing data, which is a regression introduced by unlocking partial update behaviour without adjusting the payload construction logic. src/app/api/profile/route.ts — the updateData construction (lines 112–116) needs to exclude keys absent from the original request body before the Supabase update is safe for partial requests. Important Files Changed
Sequence DiagramsequenceDiagram
participant Client
participant PATCH/PUT Handler
participant profileSchema (Zod)
participant Supabase
Client->>PATCH/PUT Handler: PATCH /api/profile {username?: "...", ...fields}
PATCH/PUT Handler->>profileSchema (Zod): safeParse(body)
Note over profileSchema (Zod): username now .optional()<br/>array fields fill .default([])
profileSchema (Zod)-->>PATCH/PUT Handler: validationResult.data<br/>(skills:[], ai_tools:[], is_available:true, ...)
alt username present
PATCH/PUT Handler->>Supabase: SELECT id WHERE username=? AND id!=user.id (.maybeSingle())
Supabase-->>PATCH/PUT Handler: existingUser or null
alt username taken
PATCH/PUT Handler-->>Client: 400 Username taken
end
end
PATCH/PUT Handler->>Supabase: UPDATE profiles SET {...validationResult.data, profile_completed, updated_at}
Note over Supabase: Zod defaults (skills:[], etc.)<br/>overwrite existing DB values
Supabase-->>PATCH/PUT Handler: updated profile
PATCH/PUT Handler-->>Client: 200 {profile}
|
| if (validationResult.data.username) { | ||
| const { data: existingUser } = await supabase | ||
| .from("profiles") | ||
| .select("id") | ||
| .eq("username", validationResult.data.username) | ||
| .neq("id", user.id) | ||
| .maybeSingle(); | ||
|
|
||
| if (existingUser) { | ||
| return NextResponse.json( | ||
| { error: "Username is already taken" }, | ||
| { status: 400 } | ||
| ); | ||
| } | ||
| } |
There was a problem hiding this comment.
Guard is unreachable — schema still requires
username
profileSchema defines username without .optional() (see src/lib/validations.ts lines 76-83), so when a PATCH request omits username, safeParse returns success: false and the handler already returns a 400 at lines 57-62 — the execution never reaches the new if (validationResult.data.username) guard. The partial-update behaviour described in the PR is therefore not actually unlocked: requests without username still fail with a validation error, not a successful partial update.
To make the guard meaningful, username in profileSchema (or a dedicated partial schema used only by PATCH) needs to be marked .optional().
|
Updated: Also fixed profileSchema in validations.ts - username is now .optional(), so PATCH requests without a username field pass Zod validation and reach the route handler. |
The PATCH /api/profile endpoint crashed with a 500 error when the request body didn't include a
usernamefield. The username uniqueness check used.single()which throws on zero matches. Wrapping the check inif (validationResult.data.username)and using.maybeSingle()allows partial profile updates to work correctly.