Skip to content

feat(chat): migrate GET /api/artists/{id}/posts#1695

Merged
sweetmantech merged 3 commits into
testfrom
feat/chat-artist-posts-migration
Apr 23, 2026
Merged

feat(chat): migrate GET /api/artists/{id}/posts#1695
sweetmantech merged 3 commits into
testfrom
feat/chat-artist-posts-migration

Conversation

@arpitgupta1214

@arpitgupta1214 arpitgupta1214 commented Apr 22, 2026

Copy link
Copy Markdown
Collaborator

Summary

Migrates fetchPosts.ts and useArtistPosts to the new /api/artists/{id}/posts endpoint.

Test plan

  • Artist Posts surface loads posts via the new endpoint on a Vercel preview
  • Pagination continues to work

Summary by CodeRabbit

  • New Features

    • Posts now require user authentication to load.
  • Refactor

    • Updated the API request structure for posts retrieval.
    • Consolidated type definitions for improved maintainability.

Swap hardcoded api.recoupable.com/api/posts for getClientApiBaseUrl()
with the artist id on the path, send Privy Bearer auth, and gate
useArtistPosts on Privy authenticated so the queryFn does not fire
before the session hydrates. Extract the Post type to types/Post.ts
so consumers import directly (no re-export indirection) and update
the inlined docs samples to the new path.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@vercel

vercel Bot commented Apr 22, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
recoup-chat Ready Ready Preview Apr 22, 2026 8:19pm

Request Review

@coderabbitai

coderabbitai Bot commented Apr 22, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Review was skipped due to path filters

⛔ Files ignored due to path filters (1)
  • types/Post.ts is excluded by none and included by none

CodeRabbit blocks several paths by default. You can override this behavior by explicitly including those paths in the path filters. For example, including **/dist/** will override the default block on the dist directory, by removing the pattern from both the lists.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: b7e63644-9610-4c07-ae40-d7a88f031534

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This pull request consolidates the Post type definition by moving it from lib/recoup/fetchPosts.ts to a centralized @/types/Post module, updates import statements across multiple components, and integrates Privy authentication into the useArtistPosts hook and fetchPosts function to require access tokens for API requests.

Changes

Cohort / File(s) Summary
Type Consolidation
components/Posts/PostCard.tsx, components/Posts/Posts.tsx, components/Posts/PostsWrapper.tsx
Updated Post type imports from @/lib/recoup/fetchPosts to @/types/Post, consolidating type definitions in a centralized location.
Authentication Integration
hooks/useArtistPosts.ts
Integrated Privy authentication via usePrivy hook; modified queryFn to retrieve and pass access tokens to fetchPosts, and updated the enabled condition to include an authenticated check before executing queries.
API Endpoint & Authentication
lib/recoup/fetchPosts.ts
Added accessToken parameter to FetchPostsParams; removed local Post interface in favor of centralized import; restructured API endpoint from fixed URL with artist_account_id query param to dynamic URL pattern using getClientApiBaseUrl() and route params; added Authorization: Bearer header to authenticated requests.

Sequence Diagram(s)

sequenceDiagram
    participant Client as React Component
    participant Hook as useArtistPosts
    participant Privy as Privy Auth
    participant Fetch as fetchPosts
    participant API as API Server

    Client->>Hook: Call with artistAccountId
    Hook->>Privy: Check authenticated status
    alt Not Authenticated
        Privy-->>Hook: authenticated = false
        Hook-->>Client: Query disabled
    else Authenticated
        Privy-->>Hook: authenticated = true
        Hook->>Privy: getAccessToken()
        Privy-->>Hook: accessToken
        Hook->>Fetch: fetchPosts({ artistAccountId, page, limit, accessToken })
        Fetch->>API: GET /api/artists/{artistAccountId}/posts<br/>Header: Authorization: Bearer {accessToken}
        API-->>Fetch: Posts data
        Fetch-->>Hook: PostsResponse
        Hook-->>Client: InfiniteData<PostsResponse>
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • sweetmantech
  • cubic-dev-ai

Poem

🔐 Token in hand, types now align,
Privy guards the API line,
From scattered posts to central home,
Auth flows free where'er we roam! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Solid & Clean Code ⚠️ Warning Code violates SOLID principles: missing URL encoding in fetchPosts.ts, improper cache key scoping in useArtistPosts.ts lacks user context, and enabled condition incomplete. Apply review fixes: use encodeURIComponent() for artistAccountId, implement URL.searchParams API, import useUserProvider, add userData?.account_id to queryKey and enabled condition.
✅ Passed checks (2 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/chat-artist-posts-migration

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 64f83190ec

ℹ️ 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".

Comment thread app/docs/posts/constants.ts Outdated
Comment on lines 2 to 3
curl: `curl -X GET "https://recoup-api.vercel.app/api/artists/YOUR_ARTIST_ACCOUNT_ID/posts?page=1&limit=20" \\
-H "Content-Type: application/json"`,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Add bearer auth to posts API examples

After switching the docs to GET /api/artists/{id}/posts, the request snippets still show only Content-Type headers, but the client implementation now always sends Authorization: Bearer ... in lib/recoup/fetchPosts.ts. Anyone copying these examples as-is will hit 401/unauthorized against the new endpoint, so the migration leaves the documentation examples functionally incorrect.

Useful? React with 👍 / 👎.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 8 files

Confidence score: 4/5

  • This PR looks safe to merge with minimal runtime risk, since the reported issues are documentation-focused rather than behavior changes in production code.
  • The most significant issue is in app/docs/posts/constants.ts: /api/artists/{id}/posts examples missing the Bearer auth header can mislead integrators and cause unauthorized request failures when using Privy access tokens.
  • app/docs/posts/page.tsx has inconsistent parameter naming (artistAccountId vs artist_account_id), which can create avoidable client confusion but is low severity.
  • Pay close attention to app/docs/posts/constants.ts and app/docs/posts/page.tsx - align auth and parameter docs to prevent integration mistakes.
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/docs/posts/page.tsx">

<violation number="1" location="app/docs/posts/page.tsx:32">
P3: Parameter naming is inconsistent in the docs: the endpoint path uses `artistAccountId` while the documented parameter is `artist_account_id`. Use one canonical name to avoid client confusion.</violation>
</file>

<file name="app/docs/posts/constants.ts">

<violation number="1" location="app/docs/posts/constants.ts:15">
P2: The updated `/api/artists/{id}/posts` examples omit the Bearer auth header. Since this route is now called with a Privy access token, these docs examples can lead users to unauthorized request failures.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread app/docs/posts/constants.ts Outdated
}

response = requests.get("https://api.recoupable.com/api/posts", headers=headers, params=params)
response = requests.get("https://recoup-api.vercel.app/api/artists/YOUR_ARTIST_ACCOUNT_ID/posts", headers=headers, params=params)

@cubic-dev-ai cubic-dev-ai Bot Apr 22, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The updated /api/artists/{id}/posts examples omit the Bearer auth header. Since this route is now called with a Privy access token, these docs examples can lead users to unauthorized request failures.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/docs/posts/constants.ts, line 15:

<comment>The updated `/api/artists/{id}/posts` examples omit the Bearer auth header. Since this route is now called with a Privy access token, these docs examples can lead users to unauthorized request failures.</comment>

<file context>
@@ -8,14 +8,13 @@ headers = {
 }
 
-response = requests.get("https://api.recoupable.com/api/posts", headers=headers, params=params)
+response = requests.get("https://recoup-api.vercel.app/api/artists/YOUR_ARTIST_ACCOUNT_ID/posts", headers=headers, params=params)
 data = response.json()`,
-  javascript: `fetch("https://api.recoupable.com/api/posts?artist_account_id=YOUR_ARTIST_ACCOUNT_ID&page=1&limit=20", {
</file context>
Fix with Cubic

Comment thread app/docs/posts/page.tsx Outdated
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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/useArtistPosts.ts`:
- Around line 24-45: The query cache key for useInfiniteQuery in
useArtistPosts.ts is missing the authenticated user's identifier, causing cached
posts to leak between users; update the queryKey from ["posts", artistAccountId,
limit] to include the current user's account id (the same user identifier used
elsewhere, e.g. userData?.account_id) so it becomes ["posts",
userData?.account_id, artistAccountId, limit]; obtain userData/account_id from
the same auth/context hook used in
useProStatus/useCredits/useAccountOrganizations (or from usePrivy if it exposes
the user id) and add the same change to useArtistFans.ts at the analogous
queryKey to keep cache boundaries per-user.

In `@lib/recoup/fetchPosts.ts`:
- Around line 34-36: The URL construction in fetchPosts.ts currently
interpolates artistAccountId and query params directly; update the const url
creation to encode the path segment and set queries via URL.searchParams: use
encodeURIComponent(artistAccountId) when building the path from
getClientApiBaseUrl(), then create new URL(...) for the base path and call
url.searchParams.set('page', String(page)) and url.searchParams.set('limit',
String(limit)) so reserved characters in artistAccountId and query values are
safely encoded (identify and change the const url, getClientApiBaseUrl(),
artistAccountId, page, and limit usages).
🪄 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: 45c42598-1d1e-48bf-bd20-96a1b1f56101

📥 Commits

Reviewing files that changed from the base of the PR and between d31c6da and 8a74465.

⛔ Files ignored due to path filters (1)
  • types/Post.ts is excluded by none and included by none
📒 Files selected for processing (5)
  • components/Posts/PostCard.tsx
  • components/Posts/Posts.tsx
  • components/Posts/PostsWrapper.tsx
  • hooks/useArtistPosts.ts
  • lib/recoup/fetchPosts.ts

Comment thread hooks/useArtistPosts.ts
Comment on lines +24 to +45
const { getAccessToken, authenticated } = usePrivy();

return useInfiniteQuery<PostsResponse, PostsError>({
queryKey: ["posts", artistAccountId, limit],
queryFn: ({ pageParam }) =>
fetchPosts({
queryFn: async ({ pageParam }) => {
const accessToken = await getAccessToken();
if (!accessToken) {
throw { message: "No access token" } as PostsError;
}
return fetchPosts({
artistAccountId: artistAccountId!,
accessToken,
page: pageParam as number,
limit,
}),
});
},
getNextPageParam: (lastPage: PostsResponse) => {
const { page, total_pages } = lastPage.pagination;
return page < total_pages ? page + 1 : undefined;
},
initialPageParam: 1,
enabled: !!artistAccountId,
enabled: !!artistAccountId && authenticated,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Inspect how auth-dependent React Query keys are scoped elsewhere.

rg -n -C3 --type=ts --type=tsx 'queryKey:\s*\['
rg -n -C3 --type=ts --type=tsx 'const\s*\{[^}]*user[^}]*\}\s*=\s*usePrivy\('
rg -n -C3 --type=ts --type=tsx 'removeQueries|invalidateQueries|clear\('

Repository: recoupable/chat

Length of output: 142


🏁 Script executed:

#!/bin/bash
# Re-run with corrected rg type flags (ts only, not tsx)
# and examine the actual file

echo "=== Examining hooks/useArtistPosts.ts ==="
cat -n hooks/useArtistPosts.ts

echo -e "\n=== Searching for queryKey patterns in .ts/.tsx files ==="
rg -n 'queryKey:\s*\[' --type ts

echo -e "\n=== Searching for usePrivy usage ==="
rg -n 'usePrivy' --type ts -A 3

echo -e "\n=== Searching for query invalidation/clearing ==="
rg -n 'invalidateQueries|removeQueries|resetQueries' --type ts -A 2

Repository: recoupable/chat

Length of output: 28612


Scope query cache keys with authenticated user context.

The query cache key ["posts", artistAccountId, limit] doesn't account for the authenticated user. When a user logs out and another logs in, the same artist's cached posts can be served to a different user. Add a user identifier to the cache boundary.

The codebase already uses this pattern elsewhere (e.g., useProStatus, useCredits, useAccountOrganizations all include userData?.account_id). Apply the same approach here, and note that useArtistFans.ts has an identical issue at line 24.

Suggested fix
-  const { getAccessToken, authenticated } = usePrivy();
+  const { getAccessToken, authenticated } = usePrivy();
+  const { userData } = useUserProvider();

   return useInfiniteQuery<PostsResponse, PostsError>({
-    queryKey: ["posts", artistAccountId, limit],
+    queryKey: ["posts", userData?.account_id, artistAccountId, limit],
...
-    enabled: !!artistAccountId && authenticated,
+    enabled: !!artistAccountId && authenticated && !!userData?.account_id,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@hooks/useArtistPosts.ts` around lines 24 - 45, The query cache key for
useInfiniteQuery in useArtistPosts.ts is missing the authenticated user's
identifier, causing cached posts to leak between users; update the queryKey from
["posts", artistAccountId, limit] to include the current user's account id (the
same user identifier used elsewhere, e.g. userData?.account_id) so it becomes
["posts", userData?.account_id, artistAccountId, limit]; obtain
userData/account_id from the same auth/context hook used in
useProStatus/useCredits/useAccountOrganizations (or from usePrivy if it exposes
the user id) and add the same change to useArtistFans.ts at the analogous
queryKey to keep cache boundaries per-user.

Comment thread lib/recoup/fetchPosts.ts
Comment on lines +34 to +36
const url = new URL(
`${getClientApiBaseUrl()}/api/artists/${artistAccountId}/posts?page=${page}&limit=${limit}`,
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Encode the artist path segment and query params.

artistAccountId is now part of the path, so reserved URL characters like /, ?, #, or % can alter the route. Build the path with encodeURIComponent() and set query params through URL.searchParams.

Proposed fix
-    const url = new URL(
-      `${getClientApiBaseUrl()}/api/artists/${artistAccountId}/posts?page=${page}&limit=${limit}`,
-    );
+    const url = new URL(
+      `${getClientApiBaseUrl()}/api/artists/${encodeURIComponent(
+        artistAccountId,
+      )}/posts`,
+    );
+    url.searchParams.set("page", String(page));
+    url.searchParams.set("limit", String(limit));

As per coding guidelines, “Implement built-in security practices for authentication and data handling.”

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/recoup/fetchPosts.ts` around lines 34 - 36, The URL construction in
fetchPosts.ts currently interpolates artistAccountId and query params directly;
update the const url creation to encode the path segment and set queries via
URL.searchParams: use encodeURIComponent(artistAccountId) when building the path
from getClientApiBaseUrl(), then create new URL(...) for the base path and call
url.searchParams.set('page', String(page)) and url.searchParams.set('limit',
String(limit)) so reserved characters in artistAccountId and query values are
safely encoded (identify and change the const url, getClientApiBaseUrl(),
artistAccountId, page, and limit usages).

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 2 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/docs/posts/constants.ts">

<violation number="1">
P2: Custom agent: **Flag AI Slop and Fabricated Changes**

PR-stated migration target and updated API examples are inconsistent: code now documents `/api/posts?artist_account_id=...` instead of `/api/artists/{id}/posts`, creating a fabricated/misaligned change signal.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

@arpitgupta1214 arpitgupta1214 changed the title Migrate fetchPosts to mono api /api/artists/{id}/posts feat(chat): migrate GET /api/artists/{id}/posts Apr 22, 2026
@arpitgupta1214

Copy link
Copy Markdown
Collaborator Author

E2E integration verified against api PR #469 preview

Loaded this chat preview with ?api=<api-preview-url> and exercised the posts view end-to-end via chrome-devtools-mcp.

Flow:

  • ?api=...sessionStorage.recoup_api_override
  • /posts view → Privy Bearer auth → api preview ✅
  • GET https://api-git-feat-api-artist-posts-migration-recoupable-ad724970.vercel.app/api/artists/ab1c64cd-4364-415e-abb4-04abbbda368e/posts?page=1&limit=20200
  • Response shape matches spec:
    ```json
    {"status":"success","posts":[],"pagination":{"total_count":0,"page":1,"limit":20,"total_pages":1}}
    ```

Kanye West account has 0 posts in the preview DB, but the plumbing — override, auth, routing, response shape — all work.

platform is derived client-side from post_url via getPlatform() in
PostCard; it's never read off the API response. The new
/api/artists/{id}/posts response doesn't include it either, so the
optional field is dead weight.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0 issues found across 1 file (changes from recent commits).

Requires human review: Auto-approval blocked by 1 unresolved issue from previous reviews.

@sweetmantech

Copy link
Copy Markdown
Collaborator

Browser smoke test

Against preview https://recoup-chat-git-feat-chat-artist-pos-689e25-recoupable-ad724970.vercel.app at commit 33d3578f, tested via Chrome DevTools MCP after logging in as sweetmantech@gmail.com.

Flow: Logged in → navigated to /posts with Justin Bieber selected → switched selected artist to PinkPantheress → returned to /posts → captured both requests.

Request 1 — empty case (Justin Bieber, 0 posts)

GET https://test-recoup-api.vercel.app/api/artists/2ca72ee5-fff9-41cc-b76b-844e5d6e2ab5/posts?page=1&limit=20 → 200

Response body:

{ "status": "success", "posts": [], "pagination": { "total_count": 0, "page": 1, "limit": 20, "total_pages": 1 } }

Request 2 — populated case (PinkPantheress, 16 posts)

GET https://test-recoup-api.vercel.app/api/artists/a239ff4b-79d6-4d8b-8b48-9b721b57a423/posts?page=1&limit=20 → 200

Response body (trimmed):

{
  "status": "success",
  "posts": [
    { "id": "ddebb5b1-a60c-4a54-8bdd-27e486f7355e", "post_url": "https://www.instagram.com/p/DOOiLqHDlps/", "updated_at": "2025-09-05T16:22:16+00:00" },
    { "id": "16420a97-4622-490e-883f-b6d7e1c77815", "post_url": "https://www.instagram.com/p/DNTSuzkubQP/", "updated_at": "2025-08-13T16:12:04+00:00" },
    ...14 more...
  ],
  "pagination": { "total_count": 16, "page": 1, "limit": 20, "total_pages": 1 }
}

Request headers on both calls

  • authorization: Bearer <Privy JWT> (iss: privy.io, aud: cmc52us2g01rtjr0m0k2m5fkv)
  • origin: https://recoup-chat-git-feat-chat-artist-pos-...vercel.app
  • x-matched-path: /api/artists/[id]/posts (server-side confirms the new route resolved)

Verified

  • New host: test-recoup-api.vercel.app (via getClientApiBaseUrl()), not the old hardcoded api.recoupable.com.
  • New path pattern: /api/artists/{id}/posts, not the old /api/posts?artist_account_id=….
  • Bearer auth now attached on every call; previous implementation sent no auth header.
  • Auth-gated hook: enabled: !!artistAccountId && authenticated — the request doesn't fire until both a selected artist and a Privy session exist.
  • Response shape matches ArtistPostsResponse / ArtistPost / ArtistPostsPagination from docs#155 exactly: { status, posts: [{ id, post_url, updated_at }], pagination: { total_count, page, limit, total_pages } }.
  • Ordering holds: updated_at DESC (Sep 2025 → ... → Jan 2024 across the 16 posts).
  • End-to-end render: following the 16-post response, the client fires Instagram embed bootloader requests (instagram.com/ajax/bootloader-endpoint/?modules=PolarisEmbedVideoWrapper × many) — confirms posts are actually rendering in the DOM, not just cached in React Query.

End-to-end chain (chat UI → Privy Bearer → migrated endpoint → Supabase → Instagram embed) confirmed working.

🤖 Tested with Claude Code via Chrome DevTools MCP

@sweetmantech sweetmantech merged commit 0471ef0 into test Apr 23, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants