feat: migrate account bootstrap to dedicated api#1648
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughThe Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Suggested reviewers
Poem
🚥 Pre-merge checks | ❌ 1❌ Failed checks (1 warning)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a92d2b1a5f
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| const data: FetchOrCreateAccountResponse = await response.json(); | ||
| return data.data; |
There was a problem hiding this comment.
Parse /api/accounts response using the API envelope
This helper assumes POST /api/accounts returns { data: ... }, but other consumers of the same accounts API use a { status, ... } envelope (for example, hooks/useOrgSettings.ts expects data.account and lib/chat/validateHeaders.ts checks result.status). If the bootstrap endpoint follows that contract, return data.data becomes undefined, so useUser initializes userData and profile fields from undefined and downstream flows that rely on account_id stop working. Parse the documented envelope (status + account) and treat non-success payloads as errors.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@hooks/useUser.tsx`:
- Around line 121-141: The async init() inside the useEffect should be made
safe: wrap its body in try-catch to handle rejections from getAccessToken() and
fetchOrCreateAccount(), call the function with void init() to avoid an
unhandled-promise lint warning, and add a cancellation guard (e.g. let cancelled
= false; cleanup sets cancelled = true) so before each setState (setUserData,
setImage, setInstruction, setName, setOrganization, setJobTitle, setRoleType,
setCompanyName) you check !cancelled to avoid stale writes; also log or
propagate caught errors as appropriate.
In `@lib/accounts/fetchOrCreateAccount.ts`:
- Around line 20-37: The fetchOrCreateAccount function's POST to
`${getClientApiBaseUrl()}/api/accounts` needs timeout protection and richer
error context: wrap the fetch in an AbortController with a short timeout (e.g.,
5–10s) and abort on timeout, and on non-ok responses throw an Error that
includes response.status plus the response text/body and any relevant headers
(or JSON error payload) to aid debugging; also catch fetch-level exceptions and
include the original error message and the request context (email, wallet,
whether accessToken was sent) before rethrowing. Use the same pattern as in
lib/serpapi/searchImages.ts and lib/tasks/updateTask.ts when adding the
AbortController and enhanced error construction so callers of
fetchOrCreateAccount / the FetchOrCreateAccountResponse handling receive
detailed failure info.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: aac60865-fa9b-4ef3-8e71-6c7d7f6a4ce0
📒 Files selected for processing (3)
app/api/account/route.tsxhooks/useUser.tsxlib/accounts/fetchOrCreateAccount.ts
💤 Files with no reviewable changes (1)
- app/api/account/route.tsx
| useEffect(() => { | ||
| const init = async () => { | ||
| const config = { | ||
| method: "POST", | ||
| body: JSON.stringify({ | ||
| email, | ||
| wallet: address, | ||
| }), | ||
| headers: { | ||
| "Content-Type": "application/json", | ||
| }, | ||
| }; | ||
| const response = await fetch("/api/account", config); | ||
|
|
||
| if (!response.ok) { | ||
| throw new Error( | ||
| `Email API request failed with status: ${response.status}` | ||
| ); | ||
| } | ||
| const accessToken = await getAccessToken(); | ||
| const data = await fetchOrCreateAccount({ | ||
| email, | ||
| wallet: address, | ||
| accessToken, | ||
| }); | ||
|
|
||
| const data = await response.json(); | ||
| setUserData(data.data); | ||
| setImage(data.data?.image || ""); | ||
| setInstruction(data.data?.instruction || ""); | ||
| setName(data?.data?.name || ""); | ||
| setOrganization(data?.data?.organization || ""); | ||
| setJobTitle(data?.data?.job_title || ""); | ||
| setRoleType(data?.data?.role_type || ""); | ||
| setCompanyName(data?.data?.company_name || ""); | ||
| setUserData(data); | ||
| setImage(data.image || ""); | ||
| setInstruction(data.instruction || ""); | ||
| setName(data.name || ""); | ||
| setOrganization(data.organization || ""); | ||
| setJobTitle(data.job_title || ""); | ||
| setRoleType(data.role_type || ""); | ||
| setCompanyName(data.company_name || ""); | ||
| }; | ||
| if (!email && !address) return; | ||
| init(); | ||
| }, [email, address]); | ||
| }, [email, address, getAccessToken]); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
find . -type f -name "useUser.tsx" -o -name "useUser.ts"Repository: recoupable/chat
Length of output: 78
🏁 Script executed:
cat -n ./hooks/useUser.tsxRepository: recoupable/chat
Length of output: 5693
🏁 Script executed:
# Check for similar async patterns in useEffect hooks
rg -A 10 "useEffect.*\(\)" hooks/ --type ts --type tsx | head -100Repository: recoupable/chat
Length of output: 86
🏁 Script executed:
rg -A 10 "useEffect" hooks/ --type typescript | head -150Repository: recoupable/chat
Length of output: 93
🏁 Script executed:
rg -A 10 "useEffect" hooks/ | head -150Repository: recoupable/chat
Length of output: 8832
🏁 Script executed:
# Look at useVercelChat.ts more closely to see the isCancelled pattern
sed -n '1,150p' hooks/useVercelChat.tsRepository: recoupable/chat
Length of output: 6282
🏁 Script executed:
# Check how errors are handled in other async operations in the codebase
rg "catch\s*\{" hooks/ -A 2 | head -80Repository: recoupable/chat
Length of output: 1171
Add error handling and cancellation guard to prevent stale state writes and unhandled rejections.
Lines 123–137: init() is called as a fire-and-forget function. If getAccessToken() or fetchOrCreateAccount() rejects, the promise rejection goes unhandled. Additionally, if either awaited call completes after the component unmounts or dependencies change, the subsequent setState calls (lines 130–137) will write stale data.
Wrap the async logic in try-catch, add a cleanup function with a cancellation flag to guard against stale writes, and explicitly mark the unhandled promise with void init():
Proposed fix
useEffect(() => {
+ let isCancelled = false;
+
const init = async () => {
- const accessToken = await getAccessToken();
- const data = await fetchOrCreateAccount({
- email,
- wallet: address,
- accessToken,
- });
-
- setUserData(data);
- setImage(data.image || "");
- setInstruction(data.instruction || "");
- setName(data.name || "");
- setOrganization(data.organization || "");
- setJobTitle(data.job_title || "");
- setRoleType(data.role_type || "");
- setCompanyName(data.company_name || "");
+ try {
+ const accessToken = await getAccessToken();
+ const data = await fetchOrCreateAccount({
+ email,
+ wallet: address,
+ accessToken,
+ });
+
+ if (isCancelled) return;
+ setUserData(data);
+ setImage(data.image || "");
+ setInstruction(data.instruction || "");
+ setName(data.name || "");
+ setOrganization(data.organization || "");
+ setJobTitle(data.job_title || "");
+ setRoleType(data.role_type || "");
+ setCompanyName(data.company_name || "");
+ } catch {
+ if (!isCancelled) {
+ toast.error("Failed to initialize account.");
+ }
+ }
};
if (!email && !address) return;
- init();
+ void init();
+ return () => {
+ isCancelled = true;
+ };
}, [email, address, getAccessToken]);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@hooks/useUser.tsx` around lines 121 - 141, The async init() inside the
useEffect should be made safe: wrap its body in try-catch to handle rejections
from getAccessToken() and fetchOrCreateAccount(), call the function with void
init() to avoid an unhandled-promise lint warning, and add a cancellation guard
(e.g. let cancelled = false; cleanup sets cancelled = true) so before each
setState (setUserData, setImage, setInstruction, setName, setOrganization,
setJobTitle, setRoleType, setCompanyName) you check !cancelled to avoid stale
writes; also log or propagate caught errors as appropriate.
| const response = await fetch(`${getClientApiBaseUrl()}/api/accounts`, { | ||
| method: "POST", | ||
| headers: { | ||
| "Content-Type": "application/json", | ||
| ...(accessToken ? { Authorization: `Bearer ${accessToken}` } : {}), | ||
| }, | ||
| body: JSON.stringify({ | ||
| email, | ||
| wallet, | ||
| }), | ||
| }); | ||
|
|
||
| if (!response.ok) { | ||
| throw new Error(`Account API request failed with status: ${response.status}`); | ||
| } | ||
|
|
||
| const data: FetchOrCreateAccountResponse = await response.json(); | ||
| return data.data; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
fd -t f "fetchOrCreateAccount.ts"Repository: recoupable/chat
Length of output: 95
🏁 Script executed:
cat -n lib/accounts/fetchOrCreateAccount.tsRepository: recoupable/chat
Length of output: 1318
🏁 Script executed:
rg "AbortController|setTimeout.*fetch" lib/ --type ts --type tsx -B 2 -A 2Repository: recoupable/chat
Length of output: 86
🏁 Script executed:
rg "AbortController|setTimeout.*fetch" lib/ -A 2 -B 2Repository: recoupable/chat
Length of output: 453
🏁 Script executed:
fd -t f ".*\.ts$" lib/ | rg "fetch" | head -20Repository: recoupable/chat
Length of output: 483
🏁 Script executed:
cat -n lib/serpapi/searchImages.ts | head -40Repository: recoupable/chat
Length of output: 1364
🏁 Script executed:
cat -n lib/serpapi/searchImages.ts | sed -n '40,70p'Repository: recoupable/chat
Length of output: 1102
🏁 Script executed:
cat -n lib/recoup/fetchPosts.tsRepository: recoupable/chat
Length of output: 2021
🏁 Script executed:
cat -n lib/perplexity/fetchPerplexityApi.tsRepository: recoupable/chat
Length of output: 1023
🏁 Script executed:
rg "fetch.*signal.*controller" lib/ -B 5 -A 5Repository: recoupable/chat
Length of output: 41
🏁 Script executed:
cat -n lib/agent-templates/fetchAgentTemplates.tsRepository: recoupable/chat
Length of output: 631
🏁 Script executed:
rg "response\.text\(\)" lib/ -B 3 -A 3Repository: recoupable/chat
Length of output: 4499
Add timeout and rich error context to this bootstrap API call.
This utility lacks timeout protection and detailed error reporting. The fetch at line 20 can hang indefinitely if the API stalls, blocking bootstrap. The error at line 33 only reports HTTP status, making production debugging difficult. The codebase already establishes this pattern elsewhere (e.g., lib/serpapi/searchImages.ts, lib/tasks/updateTask.ts).
💡 Proposed fix
export async function fetchOrCreateAccount({
@@
}): Promise<AccountWithDetails> {
+ const controller = new AbortController();
+ const timeoutId = setTimeout(() => controller.abort(), 10_000);
+
+ let response: Response;
+ try {
- const response = await fetch(`${getClientApiBaseUrl()}/api/accounts`, {
+ response = await fetch(`${getClientApiBaseUrl()}/api/accounts`, {
method: "POST",
headers: {
"Content-Type": "application/json",
...(accessToken ? { Authorization: `Bearer ${accessToken}` } : {}),
},
body: JSON.stringify({
email,
wallet,
}),
+ signal: controller.signal,
});
+ } finally {
+ clearTimeout(timeoutId);
+ }
if (!response.ok) {
- throw new Error(`Account API request failed with status: ${response.status}`);
+ const errorText = await response.text().catch(() => "");
+ throw new Error(
+ `Account API request failed with status: ${response.status}${
+ errorText ? ` - ${errorText}` : ""
+ }`
+ );
}Per coding guidelines: "For utility functions, ensure proper error handling."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@lib/accounts/fetchOrCreateAccount.ts` around lines 20 - 37, The
fetchOrCreateAccount function's POST to `${getClientApiBaseUrl()}/api/accounts`
needs timeout protection and richer error context: wrap the fetch in an
AbortController with a short timeout (e.g., 5–10s) and abort on timeout, and on
non-ok responses throw an Error that includes response.status plus the response
text/body and any relevant headers (or JSON error payload) to aid debugging;
also catch fetch-level exceptions and include the original error message and the
request context (email, wallet, whether accessToken was sent) before rethrowing.
Use the same pattern as in lib/serpapi/searchImages.ts and
lib/tasks/updateTask.ts when adding the AbortController and enhanced error
construction so callers of fetchOrCreateAccount / the
FetchOrCreateAccountResponse handling receive detailed failure info.
There was a problem hiding this comment.
2 issues found across 3 files
Confidence score: 2/5
- There is a high-confidence regression risk in
lib/accounts/fetchOrCreateAccount.ts: the account bootstrap behavior changed to a new API flow, but the new behavior is not exercised by tests, so breakage could slip through merge. - A concrete functional issue is called out in
lib/accounts/fetchOrCreateAccount.ts: readingdata.datainstead of the shared{ status, account }envelope can returnundefined, which may leave users unauthenticated when/api/accountsfollows the common contract. - Pay close attention to
lib/accounts/fetchOrCreateAccount.ts- API response parsing and bootstrap-path test coverage need validation before merge.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="lib/accounts/fetchOrCreateAccount.ts">
<violation number="1" location="lib/accounts/fetchOrCreateAccount.ts:1">
P1: Custom agent: **Flag AI Slop and Fabricated Changes**
Rule 2 ("behavior-change PRs where the changed behavior is not exercised by tests") is violated: this change migrates account bootstrap to a new API flow, but verification states tests were not run and the helper test was removed, leaving a critical auth/account path unvalidated.</violation>
<violation number="2" location="lib/accounts/fetchOrCreateAccount.ts:37">
P2: Parse the `/api/accounts` response using the shared `{ status, account }` envelope (and treat non-success as errors). Returning `data.data` will yield undefined when the API follows the common contract, which leaves userData uninitialized and breaks downstream account_id usage.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review, or fix all with cubic.
| @@ -0,0 +1,38 @@ | |||
| import { getClientApiBaseUrl } from "@/lib/api/getClientApiBaseUrl"; | |||
There was a problem hiding this comment.
P1: Custom agent: Flag AI Slop and Fabricated Changes
Rule 2 ("behavior-change PRs where the changed behavior is not exercised by tests") is violated: this change migrates account bootstrap to a new API flow, but verification states tests were not run and the helper test was removed, leaving a critical auth/account path unvalidated.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At lib/accounts/fetchOrCreateAccount.ts, line 1:
<comment>Rule 2 ("behavior-change PRs where the changed behavior is not exercised by tests") is violated: this change migrates account bootstrap to a new API flow, but verification states tests were not run and the helper test was removed, leaving a critical auth/account path unvalidated.</comment>
<file context>
@@ -0,0 +1,38 @@
+import { getClientApiBaseUrl } from "@/lib/api/getClientApiBaseUrl";
+import type { AccountWithDetails } from "@/lib/supabase/accounts/getAccountWithDetails";
+
</file context>
| } | ||
|
|
||
| const data: FetchOrCreateAccountResponse = await response.json(); | ||
| return data.data; |
There was a problem hiding this comment.
P2: Parse the /api/accounts response using the shared { status, account } envelope (and treat non-success as errors). Returning data.data will yield undefined when the API follows the common contract, which leaves userData uninitialized and breaks downstream account_id usage.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At lib/accounts/fetchOrCreateAccount.ts, line 37:
<comment>Parse the `/api/accounts` response using the shared `{ status, account }` envelope (and treat non-success as errors). Returning `data.data` will yield undefined when the API follows the common contract, which leaves userData uninitialized and breaks downstream account_id usage.</comment>
<file context>
@@ -0,0 +1,38 @@
+ }
+
+ const data: FetchOrCreateAccountResponse = await response.json();
+ return data.data;
+}
</file context>
|
Successful live verification on the deployed preview:
|
Summary
Verification
Summary by CodeRabbit