feat(api): migrate GET /api/ai/models#491
Conversation
…annel-info) Adds api parity for chat's /api/email, /api/ai/models, /api/youtube/channel-info ahead of the chat-side cutover. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Important Review skippedReview was skipped due to path filters ⛔ Files ignored due to path filters (2)
CodeRabbit blocks several paths by default. You can override this behavior by explicitly including those paths in the path filters. For example, including ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 WalkthroughWalkthroughAdds two new API endpoints (AI models and YouTube channel-info) and supporting libraries for retrieving available models, validating and refreshing YouTube OAuth tokens, persisting tokens to Supabase, and request validation with CORS-enabled responses. Changes
Sequence DiagramssequenceDiagram
participant Client
participant API as GET /api/ai/models
participant Handler as getAvailableModelsHandler
participant Models as getAvailableModels
Client->>API: GET /api/ai/models
API->>Handler: handle request
Handler->>Models: fetch available models
Models-->>Handler: { models } or error
Handler-->>API: JSON { models } / { message } + CORS
API-->>Client: HTTP response
sequenceDiagram
participant Client
participant API as GET /api/youtube/channel-info
participant Validate as validateYouTubeChannelInfoRequest
participant TokenVal as validateYouTubeTokens
participant DB as YouTube Tokens DB
participant Refresh as refreshStoredYouTubeToken
participant Handler as getYouTubeChannelHandler
participant YouTube as YouTube Data API
Client->>API: GET /api/youtube/channel-info?artist_account_id=...
API->>Validate: validate auth & query
Validate->>TokenVal: request token validation for artist_account_id
TokenVal->>DB: select stored tokens
DB-->>TokenVal: stored token row / none
alt token expired & has refresh_token
TokenVal->>Refresh: refreshStoredYouTubeToken
Refresh->>YouTube: OAuth refresh
YouTube-->>Refresh: new credentials
Refresh->>DB: upsert updated tokens
DB-->>Refresh: confirmation
Refresh-->>TokenVal: refreshed tokens
end
TokenVal-->>Validate: resolved tokens
Validate-->>API: validated params + tokens
API->>Handler: fetch channel info with tokens
Handler->>YouTube: fetch channel data
YouTube-->>Handler: channel data / error
Handler-->>API: JSON { status, channels } / error + CORS
API-->>Client: HTTP response
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 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.
9 issues found across 19 files
Confidence score: 3/5
- There is concrete user-facing risk in several handlers (
lib/ai/getAvailableModelsHandler.ts,lib/email/trackEmailHandler.ts,lib/youtube/getYouTubeChannelHandler.ts) that return raw exception text in 500 responses, which can leak internal system details. - Related tests (
lib/ai/__tests__/getAvailableModelsHandler.test.ts,lib/youtube/__tests__/getYouTubeChannelHandler.test.ts) currently enforce that leakage behavior, so the unsafe response pattern is likely to persist unless both code and assertions are updated together. - This is not necessarily a merge blocker, but the combination of multiple high-severity/high-confidence findings (plus the GET-vs-POST mutation issue in
lib/email/trackEmailHandler.ts) raises meaningful regression and API-contract risk. - Pay close attention to
lib/ai/getAvailableModelsHandler.ts,lib/email/trackEmailHandler.ts,lib/youtube/getYouTubeChannelHandler.ts,lib/ai/__tests__/getAvailableModelsHandler.test.ts, andlib/youtube/__tests__/getYouTubeChannelHandler.test.ts- align error handling to generic 500 messages and ensure tests no longer codify exception leakage.
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/email/trackEmailHandler.ts">
<violation number="1" location="lib/email/trackEmailHandler.ts:13">
P2: Custom agent: **API Design Consistency and Maintainability**
State-changing contact tracking is exposed via GET instead of POST, violating the API method requirement for mutations.</violation>
<violation number="2" location="lib/email/trackEmailHandler.ts:42">
P1: Do not return raw exception messages in API responses; return a generic server-error message instead.
(Based on your team's feedback about preventing internal exception leakage in error responses.) [FEEDBACK_USED]</violation>
</file>
<file name="lib/youtube/youtubeErrors.ts">
<violation number="1" location="lib/youtube/youtubeErrors.ts:44">
P3: Custom agent: **Module should export a single primary function whose name matches the filename**
Primary exported function name does not match the file basename (`youtubeErrors` vs `buildYouTubeUtilityError`).</violation>
</file>
<file name="lib/email/loopsClient.ts">
<violation number="1" location="lib/email/loopsClient.ts:7">
P2: Custom agent: **Module should export a single primary function whose name matches the filename**
Module exports a singleton `loopsClient` instance instead of a single primary function matching the filename, which conflicts with the module-export rule.</violation>
</file>
<file name="lib/youtube/__tests__/getYouTubeChannelHandler.test.ts">
<violation number="1" location="lib/youtube/__tests__/getYouTubeChannelHandler.test.ts:1">
P2: Custom agent: **Enforce Clear Code Style and Maintainability Practices**
Test file exceeds the repository’s 100-line file-length limit.</violation>
<violation number="2" location="lib/youtube/__tests__/getYouTubeChannelHandler.test.ts:44">
P1: This test currently codifies leaking raw exception text (`details: "db dead"`) in API responses. Expect a generic internal error message instead and assert exception text is not exposed.
(Based on your team's feedback about never returning raw exception messages in API 500 responses.) [FEEDBACK_USED]</violation>
</file>
<file name="lib/ai/__tests__/getAvailableModelsHandler.test.ts">
<violation number="1" location="lib/ai/__tests__/getAvailableModelsHandler.test.ts:40">
P2: This test locks in returning raw exception text in a 500 response. Use a fixed generic message so internal error details are not exposed to API clients.
(Based on your team's feedback about not exposing raw exception messages in 500 responses.) [FEEDBACK_USED]</violation>
</file>
<file name="lib/ai/getAvailableModelsHandler.ts">
<violation number="1" location="lib/ai/getAvailableModelsHandler.ts:23">
P1: Do not return raw exception messages in 500 responses; this leaks internal error details to clients. Return a fixed generic message instead.
(Based on your team's feedback about not exposing exception text in 500 API responses.) [FEEDBACK_USED]</violation>
</file>
<file name="lib/youtube/getYouTubeChannelHandler.ts">
<violation number="1" location="lib/youtube/getYouTubeChannelHandler.ts:77">
P1: Do not expose raw exception messages in API responses; return a fixed generic error string instead.
(Based on your team's feedback about not exposing raw exception details in API responses.) [FEEDBACK_USED]</violation>
</file>
Architecture diagram
sequenceDiagram
participant Client as Client (Chat UI)
participant API as API Route Handler
participant Loops as Loops.so API
participant AI as AI Model Gateway
participant DB as Supabase (youtube_tokens)
participant Google as Google/YouTube API
Note over Client, Google: NEW: Chat Parity API Migration
%% Flow 1: Email Tracking
rect rgb(240, 240, 240)
Note right of Client: GET /api/email
Client->>API: trackEmail(email)
API->>API: NEW: validateTrackEmailQuery()
API->>Loops: NEW: loopsClient.updateContact()
Loops-->>API: { success, id, message }
API-->>Client: { success, message, id }
end
%% Flow 2: AI Models Catalog
rect rgb(230, 230, 230)
Note right of Client: GET /api/ai/models
Client->>API: getAvailableModels()
API->>AI: NEW: getAvailableModels()
AI-->>API: Model list
API->>API: Filter embed models
API-->>Client: { models }
end
%% Flow 3: YouTube Channel Info
rect rgb(220, 220, 220)
Note right of Client: GET /api/youtube/channel-info
Client->>API: getYouTubeChannelInfo(artist_id)
API->>API: NEW: validateYouTubeChannelQuery()
API->>DB: NEW: selectYouTubeTokens(artist_id)
DB-->>API: Token record
alt Token Expired
API->>Google: NEW: refreshAccessToken(refresh_token)
Google-->>API: New access_token
API->>DB: NEW: upsertYouTubeTokens(new_tokens)
else Invalid/Missing Tokens
API-->>Client: 200 { success: false, tokenStatus: "invalid" }
end
opt Token is Valid (or refreshed)
API->>Google: NEW: fetchYouTubeChannelInfo(access_token)
alt API Success
Google-->>API: Channel metadata
API-->>Client: { success: true, channels, tokenStatus: "valid" }
else API Error
Google-->>API: Error response
API-->>Client: { success: false, error, tokenStatus: "api_error" }
end
end
end
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
| * @param message - The human-readable error message. | ||
| * @returns A YouTubeUtilityError object. | ||
| */ | ||
| export function buildYouTubeUtilityError( |
There was a problem hiding this comment.
P3: Custom agent: Module should export a single primary function whose name matches the filename
Primary exported function name does not match the file basename (youtubeErrors vs buildYouTubeUtilityError).
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At lib/youtube/youtubeErrors.ts, line 44:
<comment>Primary exported function name does not match the file basename (`youtubeErrors` vs `buildYouTubeUtilityError`).</comment>
<file context>
@@ -0,0 +1,52 @@
+ * @param message - The human-readable error message.
+ * @returns A YouTubeUtilityError object.
+ */
+export function buildYouTubeUtilityError(
+ code: YouTubeUtilityErrorCode,
+ message: string,
</file context>
- updateContact: switch to loops v6 object-arg signature (fixes Vercel build) - handlers: stop leaking raw exception text in 500 responses - loopsClient: convert singleton to factory function matching filename - youtubeErrors: align export name with filename - getYouTubeChannelHandler: explicit boolean discriminator narrowing for build Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Email tracking upserts a Loops contact — a write — so it should not be
exposed as GET with email in query params (PII in URLs, cacheable
responses). Switches to POST with `{ email }` JSON body.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
1 issue found across 6 files (changes from recent commits).
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="app/api/email/route.ts">
<violation number="1" location="app/api/email/route.ts:27">
P1: This route no longer handles GET requests, which breaks the documented `/api/email` GET parity and will return 405 for existing callers.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
Loops is no longer used; the email tracking endpoint is removed from this migration entirely. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Fold token validation into validateYouTubeChannelInfoRequest so the
handler is a thin fetch+respond orchestrator
- Rename validateYouTubeChannelQuery → validateYouTubeChannelInfoRequest
(matches validateArtistRequest / validateChatRequest convention)
- Drop buildYouTubeUtilityError + error code catalog: every token-level
failure collapses to the same {success:false, tokenStatus:"invalid"}
response, so the typed codes carried no information out
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
1 issue found across 8 files (changes from recent commits).
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/youtube/__tests__/validateYouTubeChannelInfoRequest.test.ts">
<violation number="1" location="lib/youtube/__tests__/validateYouTubeChannelInfoRequest.test.ts:1">
P3: Custom agent: **Enforce Clear Code Style and Maintainability Practices**
File exceeds the repository’s 100-line limit.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
- refreshStoredYouTubeToken now throws on every failure (no more catch-log-return-null); the deep invalid_grant type guard is gone since the general console.error already logs the full error - validateYouTubeTokens lets errors propagate; null is reserved for the legitimate "no tokens row / no refresh_token" cases - validateYouTubeChannelInfoRequest is the single catch boundary; thrown errors and null both collapse to the same tokenStatus:invalid response, matching chat parity Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
TS inference already produces the same types for these helpers; explicit annotations were noise. Kept annotations on discriminated-union returns (validators, route handlers) and anywhere inference would widen. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Continues 7f64027 — TS inference produces the same types for these helpers; explicit annotations were noise. Kept the discriminated-union annotation on validateYouTubeChannelInfoRequest where it documents an intentional contract. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- drop response fields that no chat consumer reads - validateYouTubeTokens throws on every unusable case; the request validator's catch is the single failure boundary (no more let/null) - drop now-unnecessary explicit return types Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
4 issues found across 6 files (changes from recent commits).
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/youtube/__tests__/getYouTubeChannelHandler.test.ts">
<violation number="1" location="lib/youtube/__tests__/getYouTubeChannelHandler.test.ts:61">
P2: These assertions removed `tokenStatus` from the expected response, so the tests no longer enforce the endpoint response contract.</violation>
</file>
<file name="lib/youtube/getYouTubeChannelHandler.ts">
<violation number="1" location="lib/youtube/getYouTubeChannelHandler.ts:35">
P1: The handler removed `tokenStatus` from its JSON payloads, which breaks the documented `/api/youtube/channel-info` response contract and removes state needed by clients to branch on token validity vs API failure.</violation>
</file>
<file name="lib/youtube/validateYouTubeChannelInfoRequest.ts">
<violation number="1" location="lib/youtube/validateYouTubeChannelInfoRequest.ts:33">
P2: Missing-param responses no longer include `tokenStatus`, breaking the documented `/api/youtube/channel-info` response contract.</violation>
<violation number="2" location="lib/youtube/validateYouTubeChannelInfoRequest.ts:50">
P1: Token validation failures now omit `tokenStatus`, making auth failures indistinguishable from generic channel fetch failures.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
- Drop `success` from /api/youtube/channel-info; conform to the
`{ status: "success", channels }` envelope used across the rest of
the api (lib/networking/successResponse.ts, songs, posts, accounts,
chats, artists). Clients infer "needs re-auth" from
`channels === null`.
- Keep only validator + handler tests; cover no-token, expired-no-refresh,
refresh-failure, and db-update-failure cases at the validator boundary
via it.each on the thrown-error contract.
- Trim jsdocs to information-bearing lines only; drop stale
"mirrors chat" notes and self-evident @param/@returns lines on lib
helpers.
- Dedupe YouTubeTokensRow: keep the export in validateYouTubeTokens.ts
(the contract owner) and import it in refreshStoredYouTubeToken.ts.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
You're iterating quickly on this pull request. To help protect your rate limits, cubic has paused automatic reviews on new pushes for now—when you're ready for another review, comment |
The SDK's getAvailableModels() rejects valid responses with a Zod error
("expected error.object"), which silenced 194 models behind an empty
array. Match chat's direct fetch to /v1/ai/config — same approach,
same payload, same parity.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The 3.x SDK requires specificationVersion:"v3" via a strict Zod literal, but the live /v1/ai/config endpoint still emits "v2" descriptors. Pin to 2.0.83 (latest 2.x, expects "v2") so gateway.getAvailableModels() works as designed. This is a catalog-read-only consumer; api never uses gateway() as a model factory, so 2.x's LanguageModelV2 surface doesn't collide with the rest of the codebase on @ai-sdk/provider@3. Restore the SDK call site (no more direct fetch hack); a comment flags when to bump back to 3.x. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Layers in validateAuthContext + checkAccountArtistAccess so anonymous callers can no longer probe stored YouTube tokens by guessing UUIDs. Returns 401 on missing/invalid auth, 403 when the authed caller has no access to the requested artist. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (1)
lib/youtube/getYouTubeChannelHandler.ts (1)
17-48: ⚡ Quick winRefactor the handler into smaller helpers to meet function-size and SRP guidelines.
This function currently handles validation orchestration, upstream invocation, and response mapping in one >20-line block. Splitting error/response mapping into helpers will make this easier to maintain and test.
As per coding guidelines, "Flag functions longer than 20 lines" and "Apply Single Responsibility Principle (SRP): one exported function per file; each file should do one thing well".🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/youtube/getYouTubeChannelHandler.ts` around lines 17 - 48, Split getYouTubeChannelHandler into a thin orchestration that calls small non-exported helpers: move response mapping into a helper like mapChannelSuccessResponse(channelData) that returns the NextResponse with getCorsHeaders(), move error mapping into mapChannelErrorResponse(statusCode?) or handleChannelError(error) that logs and returns the 502 JSON response, and move any token parsing into a small helper (e.g., extractTokens(validated)) so the handler only validates, invokes fetchYouTubeChannelInfo, then delegates to these helpers; keep validateYouTubeChannelInfoRequest and fetchYouTubeChannelInfo calls unchanged and ensure only getYouTubeChannelHandler remains exported from this file.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@lib/ai/getAvailableModels.ts`:
- Around line 12-15: The catch block in getAvailableModels (the function
handling the gateway fetch) currently logs errors and returns an empty array,
which masks upstream failures; change it to preserve failure semantics by either
removing the try/catch so the error bubbles to the caller, or re-throw the
caught error after logging (e.g., log via console.error and then throw error) so
the handler can perform proper HTTP mapping instead of silently returning [].
In `@lib/youtube/getYouTubeChannelHandler.ts`:
- Line 42: Replace the raw error logging in getYouTubeChannelHandler that
currently does console.error("Error in YouTube channel info API:", error) with a
sanitized log containing only non-sensitive fields (e.g., error.message,
error.name and any safe error.code or status) to avoid leaking OAuth tokens or
request metadata; locate the console.error call in getYouTubeChannelHandler and
update it to construct a safe message (include a short context string plus the
selected sanitized fields) and log that instead (use the existing logger if
available, otherwise console.error) so no raw error object is emitted.
- Line 26: The code sets refreshToken to an empty string which can be
interpreted as an invalid credential; change the assignment in
getYouTubeChannelHandler (where refreshToken: validated.tokens.refresh_token ||
"") to pass undefined when there is no refresh token so fetchYouTubeChannelInfo
receives an absent optional value (e.g., set refreshToken to
validated.tokens.refresh_token ?? undefined or conditionally include the
property) — update the refreshToken property at the call site in
getYouTubeChannelHandler.ts accordingly.
In `@lib/youtube/validateYouTubeTokens.ts`:
- Around line 13-17: The code treats any null from selectYouTubeTokens as
“tokens not found”, but selectYouTubeTokens currently returns null for both
missing rows and DB/query errors; update selectYouTubeTokens to throw on actual
query failures (or return a distinct error result) and return null only when no
row exists, then update validateYouTubeTokens (the branch using storedTokens for
artist_account_id) to catch/re-throw DB errors as 5xx (or let the thrown error
bubble) and only throw the 401-style "youtube tokens not found" when
selectYouTubeTokens legitimately returns null; use the function names
selectYouTubeTokens and validateYouTubeTokens and the storedTokens variable to
locate the changes.
---
Nitpick comments:
In `@lib/youtube/getYouTubeChannelHandler.ts`:
- Around line 17-48: Split getYouTubeChannelHandler into a thin orchestration
that calls small non-exported helpers: move response mapping into a helper like
mapChannelSuccessResponse(channelData) that returns the NextResponse with
getCorsHeaders(), move error mapping into mapChannelErrorResponse(statusCode?)
or handleChannelError(error) that logs and returns the 502 JSON response, and
move any token parsing into a small helper (e.g., extractTokens(validated)) so
the handler only validates, invokes fetchYouTubeChannelInfo, then delegates to
these helpers; keep validateYouTubeChannelInfoRequest and
fetchYouTubeChannelInfo calls unchanged and ensure only getYouTubeChannelHandler
remains exported from this file.
🪄 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: 3625b627-e8a0-49b2-b018-49841d6c7e7e
⛔ Files ignored due to path filters (5)
lib/ai/__tests__/getAvailableModels.test.tsis excluded by!**/*.test.*,!**/__tests__/**and included bylib/**lib/youtube/__tests__/getYouTubeChannelHandler.test.tsis excluded by!**/*.test.*,!**/__tests__/**and included bylib/**lib/youtube/__tests__/validateYouTubeChannelInfoRequest.test.tsis excluded by!**/*.test.*,!**/__tests__/**and included bylib/**package.jsonis excluded by none and included by nonepnpm-lock.yamlis excluded by!**/pnpm-lock.yamland included by none
📒 Files selected for processing (5)
lib/ai/getAvailableModels.tslib/youtube/getYouTubeChannelHandler.tslib/youtube/refreshStoredYouTubeToken.tslib/youtube/validateYouTubeChannelInfoRequest.tslib/youtube/validateYouTubeTokens.ts
🚧 Files skipped from review as they are similar to previous changes (2)
- lib/youtube/validateYouTubeChannelInfoRequest.ts
- lib/youtube/refreshStoredYouTubeToken.ts
| } catch (error) { | ||
| console.error("[getAvailableModels] gateway fetch failed:", error); | ||
| return []; | ||
| } |
There was a problem hiding this comment.
Preserve failure semantics instead of returning an empty catalog on gateway errors.
At Line 14, return [] converts upstream failures into a false-success response path, which can hide outages and bypass intended handler-level error mapping. Re-throw after logging (or remove this catch and let the handler own HTTP translation).
Suggested fix
export const getAvailableModels = async () => {
try {
const { models } = await gateway.getAvailableModels();
return models.filter(m => !isEmbedModel(m));
} catch (error) {
console.error("[getAvailableModels] gateway fetch failed:", error);
- return [];
+ throw error;
}
};As per coding guidelines, "For domain functions, ensure: ... Proper error handling".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@lib/ai/getAvailableModels.ts` around lines 12 - 15, The catch block in
getAvailableModels (the function handling the gateway fetch) currently logs
errors and returns an empty array, which masks upstream failures; change it to
preserve failure semantics by either removing the try/catch so the error bubbles
to the caller, or re-throw the caught error after logging (e.g., log via
console.error and then throw error) so the handler can perform proper HTTP
mapping instead of silently returning [].
| const storedTokens = await selectYouTubeTokens(artist_account_id); | ||
|
|
||
| if (!storedTokens) { | ||
| throw new Error("youtube tokens not found"); | ||
| } |
There was a problem hiding this comment.
Differentiate missing token rows from database read failures.
selectYouTubeTokens returns null for both “row not found” and query errors (see lib/supabase/youtube_tokens/selectYouTubeTokens.ts:10-23), but this branch throws a single “not found” error. Downstream, that collapses operational DB failures into 401 re-auth responses instead of a 5xx path.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@lib/youtube/validateYouTubeTokens.ts` around lines 13 - 17, The code treats
any null from selectYouTubeTokens as “tokens not found”, but selectYouTubeTokens
currently returns null for both missing rows and DB/query errors; update
selectYouTubeTokens to throw on actual query failures (or return a distinct
error result) and return null only when no row exists, then update
validateYouTubeTokens (the branch using storedTokens for artist_account_id) to
catch/re-throw DB errors as 5xx (or let the thrown error bubble) and only throw
the 401-style "youtube tokens not found" when selectYouTubeTokens legitimately
returns null; use the function names selectYouTubeTokens and
validateYouTubeTokens and the storedTokens variable to locate the changes.
YouTube channel-info will migrate via Composio in a separate PR. Reduces Group 1 scope to just /api/ai/models. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Browser-verified smoke (chat preview → api preview)Chat preview at
CLI smoke for the validation/error paths is also clean:
|
Manual verification on preview deploymentTested Test cases
Schema gap (not blocking)Chat prod payload is Root cause:
The 3.x client returns a Functional impact on the chat-side cutover: none. Chat's Optional follow-up if strict byte-parity is desired: bump api's Sample dataFirst model in api response: { "id": "alibaba/qwen-3-14b", "name": "Qwen3-14B" }Same id appears at the same position in chat prod (sorted by gateway), confirming consistent ordering. You can re-run the comparison yourself: PREVIEW="https://api-git-migrate-misc-reads-group1-recoupable-ad724970.vercel.app"
diff <(curl -sS "$PREVIEW/api/ai/models" | jq -r '.models[].id' | sort) \
<(curl -sS "https://chat.recoupable.com/api/ai/models" | jq -r '.models[].id' | sort)
# expected: empty diff |
* Migrate stripe subscription checkout sessions (#493) * chore: update Prettier configuration and add new dependencies - Added "endOfLine": "lf" to the Prettier configuration for consistent line endings. - Introduced "stripe" and "uuid" packages in package.json for enhanced functionality. - Updated pnpm-lock.yaml to reflect the addition of new dependencies. * refactor(stripe): rename mapToSubscriptionSessionErrorResponse to mapToSubscriptionSessionError - Updated the function name from `mapToSubscriptionSessionErrorResponse` to `mapToSubscriptionSessionError` for consistency and clarity. - Adjusted import statements and references in related files to reflect the new function name. * fix(tests): update ACCOUNT UUID in route.test.ts for consistency - Changed the ACCOUNT constant in route.test.ts from "123e4567-e89b-12d3-a456-426614174000" to "123e4567-e89b-12d3-a456-426614174001" to ensure consistency in test cases. * feat(stripe): add STRIPE_SK environment variable and improve error handling - Introduced the STRIPE_SK environment variable in .env.example to ensure proper configuration for Stripe integration. - Updated the Stripe client initialization to throw an error if STRIPE_SK is not set, enhancing robustness. - Modified error handling in createSubscriptionSessionHandler to return a 500 status with a generic error message for internal server errors. - Adjusted validation logic in validateCreateSubscriptionSessionRequest to remove the optional accountId field, simplifying the request structure. - Enhanced mapToSubscriptionSessionError to mask upstream error messages for 5xx responses, improving security. - Updated tests to reflect changes in error handling and validation logic. * fix(connectors): unify action catalog and execution paths for consistent access - Merged shared and artist toolkits into the actions catalog to ensure all executable actions are listed, reflecting the full access available to the chat agent. - Updated the executeConnectorAction function to also utilize the merged toolkit, ensuring that all actions listed in the catalog are executable. - Extracted ConnectorActionNotFoundError into a separate file for cleaner error handling and improved test organization. - Rewritten tests to mock the new toolkit structure, ensuring comprehensive coverage of the updated functionality. * feat(stripe): refactor createStripeSession to use configuration constants - Updated createStripeSession to utilize STRIPE_SUBSCRIPTION_PRICE_ID and STRIPE_SUBSCRIPTION_TRIAL_PERIOD_DAYS constants for better maintainability and configuration management. - Enhanced tests for createStripeSession to reflect these changes, ensuring consistency in the subscription parameters. - Improved error handling in client tests by refining the management of the STRIPE_SK environment variable. * chore: remove .gitattributes and update Prettier configuration - Deleted the .gitattributes file to simplify project configuration. - Updated .prettierrc by removing the "endOfLine" setting for cleaner formatting consistency. * chore(stripe): drop uuid dep, use accountId as client_reference_id YAGNI: client_reference_id is a soft-convenience pointer string with no readers in either api or chat — never round-trips, never queried via the sessions.list API (it isn't a filterable param). The migration was generating a throwaway random uuid that mapped to nothing internally. Setting it to accountId instead: - removes the uuid dep entirely (only callsite in this repo) - makes the Stripe dashboard's "Client reference" column actually useful for support/debugging - matches metadata.accountId so reconciliation is consistent Drop the uuid mock from createStripeSession.test.ts and flip the not.toBe("acc-1") assertion to toBe("acc-1") — encoding the new contract instead of the old "value must be a random UUID" stance. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Sweets Sweetman <sweetmantech@gmail.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(api): migrate GET /api/ai/models (#491) * feat(api): migrate misc-reads endpoints (email, ai/models, youtube/channel-info) Adds api parity for chat's /api/email, /api/ai/models, /api/youtube/channel-info ahead of the chat-side cutover. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(api): address AI review on misc-reads PR - updateContact: switch to loops v6 object-arg signature (fixes Vercel build) - handlers: stop leaking raw exception text in 500 responses - loopsClient: convert singleton to factory function matching filename - youtubeErrors: align export name with filename - getYouTubeChannelHandler: explicit boolean discriminator narrowing for build Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(api): change /api/email from GET to POST Email tracking upserts a Loops contact — a write — so it should not be exposed as GET with email in query params (PII in URLs, cacheable responses). Switches to POST with `{ email }` JSON body. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * revert(api): drop /api/email endpoint Loops is no longer used; the email tracking endpoint is removed from this migration entirely. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor(api): simplify youtube channel-info flow - Fold token validation into validateYouTubeChannelInfoRequest so the handler is a thin fetch+respond orchestrator - Rename validateYouTubeChannelQuery → validateYouTubeChannelInfoRequest (matches validateArtistRequest / validateChatRequest convention) - Drop buildYouTubeUtilityError + error code catalog: every token-level failure collapses to the same {success:false, tokenStatus:"invalid"} response, so the typed codes carried no information out Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor(api): throw from youtube libs, single catch at validator - refreshStoredYouTubeToken now throws on every failure (no more catch-log-return-null); the deep invalid_grant type guard is gone since the general console.error already logs the full error - validateYouTubeTokens lets errors propagate; null is reserved for the legitimate "no tokens row / no refresh_token" cases - validateYouTubeChannelInfoRequest is the single catch boundary; thrown errors and null both collapse to the same tokenStatus:invalid response, matching chat parity Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor(api): drop redundant explicit return types from misc-reads libs TS inference already produces the same types for these helpers; explicit annotations were noise. Kept annotations on discriminated-union returns (validators, route handlers) and anywhere inference would widen. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor(api): drop more redundant return types from misc-reads libs Continues 7f64027 — TS inference produces the same types for these helpers; explicit annotations were noise. Kept the discriminated-union annotation on validateYouTubeChannelInfoRequest where it documents an intentional contract. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor(api): slim youtube channel-info response and validator - drop response fields that no chat consumer reads - validateYouTubeTokens throws on every unusable case; the request validator's catch is the single failure boundary (no more let/null) - drop now-unnecessary explicit return types Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor(api): trim youtube tests, jsdocs, type duplication, response - Drop `success` from /api/youtube/channel-info; conform to the `{ status: "success", channels }` envelope used across the rest of the api (lib/networking/successResponse.ts, songs, posts, accounts, chats, artists). Clients infer "needs re-auth" from `channels === null`. - Keep only validator + handler tests; cover no-token, expired-no-refresh, refresh-failure, and db-update-failure cases at the validator boundary via it.each on the thrown-error contract. - Trim jsdocs to information-bearing lines only; drop stale "mirrors chat" notes and self-evident @param/@returns lines on lib helpers. - Dedupe YouTubeTokensRow: keep the export in validateYouTubeTokens.ts (the contract owner) and import it in refreshStoredYouTubeToken.ts. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(api): return proper status codes for youtube channel-info errors - 401 + {status:"error",message} when stored YouTube tokens can't be validated/refreshed (re-auth needed) - 502 + {status:"error",message} for upstream YouTube API failures - Drops the inconsistent "200 status:success channels:null" shape Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor(api): drop YouTubeTokensRow alias and return type validateYouTubeTokens lets TS infer the return type; the alias is gone (only refreshStoredYouTubeToken used it, now inlined as Tables<>). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(api): bypass @ai-sdk/gateway SDK for model catalog The SDK's getAvailableModels() rejects valid responses with a Zod error ("expected error.object"), which silenced 194 models behind an empty array. Match chat's direct fetch to /v1/ai/config — same approach, same payload, same parity. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(api): pin @ai-sdk/gateway to 2.x for catalog reads The 3.x SDK requires specificationVersion:"v3" via a strict Zod literal, but the live /v1/ai/config endpoint still emits "v2" descriptors. Pin to 2.0.83 (latest 2.x, expects "v2") so gateway.getAvailableModels() works as designed. This is a catalog-read-only consumer; api never uses gateway() as a model factory, so 2.x's LanguageModelV2 surface doesn't collide with the rest of the codebase on @ai-sdk/provider@3. Restore the SDK call site (no more direct fetch hack); a comment flags when to bump back to 3.x. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor(api): drop redundant jsdoc on getAvailableModels Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor(api): restore original jsdoc on getAvailableModels Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(api): gate /api/youtube/channel-info behind auth + artist access Layers in validateAuthContext + checkAccountArtistAccess so anonymous callers can no longer probe stored YouTube tokens by guessing UUIDs. Returns 401 on missing/invalid auth, 403 when the authed caller has no access to the requested artist. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * revert(api): drop /api/youtube/channel-info from this migration YouTube channel-info will migrate via Composio in a separate PR. Reduces Group 1 scope to just /api/ai/models. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: Sweets Sweetman <sweetmantech@gmail.com> --------- Co-authored-by: ahmednahima0-beep <ahmednahima0@gmail.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: Arpit Gupta <arpitgupta1214@gmail.com>
Summary
Adds api parity for chat's
/api/ai/modelsso the chat-side cutover can land.Test plan
GET /api/ai/modelsreturns the same model catalog as chat production on the Vercel preview.pnpm lintandpnpm testare green.