From ac59a7b6e383cbad8a638158facf1d76c0ba0cd4 Mon Sep 17 00:00:00 2001 From: Arpit Gupta Date: Tue, 31 Mar 2026 00:58:54 +0530 Subject: [PATCH 1/3] feat: migrate room artist lookup to dedicated chats api --- app/api/room/artist/route.tsx | 50 ----------------------------------- hooks/useArtistFromRoom.ts | 26 ++++++++++-------- 2 files changed, 15 insertions(+), 61 deletions(-) delete mode 100644 app/api/room/artist/route.tsx diff --git a/app/api/room/artist/route.tsx b/app/api/room/artist/route.tsx deleted file mode 100644 index 0afd8d99e..000000000 --- a/app/api/room/artist/route.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import type { NextRequest } from "next/server"; -import { getRoomArtistId } from "@/lib/supabase/getRoomArtistId"; -import { ensureRoomAccess } from "@/lib/supabase/ensureRoomAccess"; -import { ensureArtistAccess } from "@/lib/supabase/ensureArtistAccess"; - -/** - * GET endpoint to get the artist ID for a room and handle sharing - */ -export async function GET(req: NextRequest) { - const searchParams = req.nextUrl.searchParams; - const roomId = searchParams.get("roomId"); - const accountId = searchParams.get("accountId"); - - if (!roomId) { - return Response.json({ error: "Missing roomId parameter" }, { status: 400 }); - } - - try { - const artistId = await getRoomArtistId(roomId); - const response = { - artist_id: artistId, - artist_exists: !!artistId, - room_id: roomId - }; - - if (!artistId || !accountId) { - return Response.json(response); - } - - const [newRoomId, artistAccessGranted] = await Promise.all([ - ensureRoomAccess(roomId, accountId), - ensureArtistAccess(artistId, accountId) - ]); - - return Response.json({ - ...response, - room_access_granted: !!newRoomId, - artist_added: artistAccessGranted, - new_room_id: newRoomId, - original_room_id: roomId - }); - } catch (error) { - console.error("Error processing room artist request:", error); - return Response.json({ error: "Failed to process request" }, { status: 500 }); - } -} - -// Ensure this API route is never cached -export const dynamic = "force-dynamic"; -export const revalidate = 0; \ No newline at end of file diff --git a/hooks/useArtistFromRoom.ts b/hooks/useArtistFromRoom.ts index 01e6563b3..02c515b49 100644 --- a/hooks/useArtistFromRoom.ts +++ b/hooks/useArtistFromRoom.ts @@ -2,6 +2,9 @@ import { useEffect, useRef } from "react"; import { useArtistProvider } from "@/providers/ArtistProvider"; import { useUserProvider } from "@/providers/UserProvder"; import type { ArtistRecord } from "@/types/Artist"; +import { useAccessToken } from "@/hooks/useAccessToken"; +import { useApiOverride } from "@/hooks/useApiOverride"; +import { NEW_API_BASE_URL } from "@/lib/consts"; /** * A hook that automatically selects the artist associated with a room. @@ -11,24 +14,25 @@ export function useArtistFromRoom(roomId: string) { const { userData } = useUserProvider(); const { selectedArtist, artists, setSelectedArtist, getArtists } = useArtistProvider(); const hasRun = useRef(false); + const accessToken = useAccessToken(); + const apiOverride = useApiOverride(); + const baseUrl = apiOverride || NEW_API_BASE_URL; useEffect(() => { - if (hasRun.current || !roomId || !userData?.id) return; + if (hasRun.current || !roomId || !userData?.id || !accessToken) return; hasRun.current = true; (async () => { try { - const response = await fetch( - `/api/room/artist?roomId=${encodeURIComponent(roomId)}&accountId=${encodeURIComponent(userData.id)}` - ); + const response = await fetch(`${baseUrl}/api/chats/${encodeURIComponent(roomId)}/artist`, { + headers: { + Authorization: `Bearer ${accessToken}`, + }, + }); if (!response.ok) return; const data = await response.json(); - - if (data.new_room_id && data.new_room_id !== roomId) { - window.history.replaceState({}, '', `/chat/${data.new_room_id}`); - } - + if (!data.artist_id || selectedArtist?.account_id === data.artist_id) return; const artistList = artists as ArtistRecord[]; @@ -43,5 +47,5 @@ export function useArtistFromRoom(roomId: string) { console.error("Error selecting artist for room:", error); } })(); - }, [roomId, userData, selectedArtist, artists, setSelectedArtist, getArtists]); -} \ No newline at end of file + }, [roomId, userData, selectedArtist, artists, setSelectedArtist, getArtists, accessToken, baseUrl]); +} From 3981b8c8a27c9a6b9a2ce56c51f93ffe1e0058db Mon Sep 17 00:00:00 2001 From: Sweets Sweetman Date: Mon, 30 Mar 2026 22:45:58 -0500 Subject: [PATCH 2/3] fix: move hasRun guard after successful fetch to allow retries Previously hasRun was set before the API call, so transient failures (401 during token refresh, 5xx) permanently blocked retries. Co-Authored-By: Claude Opus 4.6 (1M context) --- hooks/useArtistFromRoom.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/hooks/useArtistFromRoom.ts b/hooks/useArtistFromRoom.ts index 02c515b49..0c4a1220e 100644 --- a/hooks/useArtistFromRoom.ts +++ b/hooks/useArtistFromRoom.ts @@ -20,8 +20,7 @@ export function useArtistFromRoom(roomId: string) { useEffect(() => { if (hasRun.current || !roomId || !userData?.id || !accessToken) return; - hasRun.current = true; - + (async () => { try { const response = await fetch(`${baseUrl}/api/chats/${encodeURIComponent(roomId)}/artist`, { @@ -29,8 +28,9 @@ export function useArtistFromRoom(roomId: string) { Authorization: `Bearer ${accessToken}`, }, }); - + if (!response.ok) return; + hasRun.current = true; const data = await response.json(); if (!data.artist_id || selectedArtist?.account_id === data.artist_id) return; From 7bdd2e5f9118b9f7264c25c4e64dde65665c7adb Mon Sep 17 00:00:00 2001 From: Sweets Sweetman Date: Mon, 30 Mar 2026 22:48:40 -0500 Subject: [PATCH 3/3] refactor: use useQuery instead of useEffect for artist lookup - Extract fetch to lib/chats/getChatArtist.ts (SRP) - Use useQuery with staleTime: Infinity (replaces hasRun ref) - Use getAccessToken from Privy directly (KISS) - TanStack handles retries, caching, and dedup automatically Co-Authored-By: Claude Opus 4.6 (1M context) --- hooks/useArtistFromRoom.ts | 61 +++++++++++++++++--------------------- lib/chats/getChatArtist.ts | 31 +++++++++++++++++++ 2 files changed, 58 insertions(+), 34 deletions(-) create mode 100644 lib/chats/getChatArtist.ts diff --git a/hooks/useArtistFromRoom.ts b/hooks/useArtistFromRoom.ts index 0c4a1220e..d8faa06d0 100644 --- a/hooks/useArtistFromRoom.ts +++ b/hooks/useArtistFromRoom.ts @@ -1,51 +1,44 @@ -import { useEffect, useRef } from "react"; +import { useEffect } from "react"; +import { useQuery } from "@tanstack/react-query"; +import { usePrivy } from "@privy-io/react-auth"; import { useArtistProvider } from "@/providers/ArtistProvider"; import { useUserProvider } from "@/providers/UserProvder"; import type { ArtistRecord } from "@/types/Artist"; -import { useAccessToken } from "@/hooks/useAccessToken"; import { useApiOverride } from "@/hooks/useApiOverride"; -import { NEW_API_BASE_URL } from "@/lib/consts"; +import { getChatArtist } from "@/lib/chats/getChatArtist"; /** * A hook that automatically selects the artist associated with a room. * @param roomId The ID of the room to get the artist for */ export function useArtistFromRoom(roomId: string) { + const { getAccessToken } = usePrivy(); const { userData } = useUserProvider(); const { selectedArtist, artists, setSelectedArtist, getArtists } = useArtistProvider(); - const hasRun = useRef(false); - const accessToken = useAccessToken(); const apiOverride = useApiOverride(); - const baseUrl = apiOverride || NEW_API_BASE_URL; - - useEffect(() => { - if (hasRun.current || !roomId || !userData?.id || !accessToken) return; - (async () => { - try { - const response = await fetch(`${baseUrl}/api/chats/${encodeURIComponent(roomId)}/artist`, { - headers: { - Authorization: `Bearer ${accessToken}`, - }, - }); + const { data } = useQuery({ + queryKey: ["chatArtist", roomId], + queryFn: async () => { + const accessToken = await getAccessToken(); + if (!accessToken) throw new Error("No access token"); + return getChatArtist(roomId, accessToken, apiOverride ?? undefined); + }, + enabled: !!roomId && !!userData?.id, + staleTime: Infinity, + retry: 2, + }); + + useEffect(() => { + if (!data?.artist_id || selectedArtist?.account_id === data.artist_id) return; - if (!response.ok) return; - hasRun.current = true; - const data = await response.json(); + const artistList = artists as ArtistRecord[]; + const artist = artistList.find(a => a.account_id === data.artist_id); - if (!data.artist_id || selectedArtist?.account_id === data.artist_id) return; - - const artistList = artists as ArtistRecord[]; - const artist = artistList.find(a => a.account_id === data.artist_id); - - if (artist) { - setSelectedArtist(artist); - } else { - await getArtists(data.artist_id); - } - } catch (error) { - console.error("Error selecting artist for room:", error); - } - })(); - }, [roomId, userData, selectedArtist, artists, setSelectedArtist, getArtists, accessToken, baseUrl]); + if (artist) { + setSelectedArtist(artist); + } else { + getArtists(data.artist_id); + } + }, [data, selectedArtist, artists, setSelectedArtist, getArtists]); } diff --git a/lib/chats/getChatArtist.ts b/lib/chats/getChatArtist.ts new file mode 100644 index 000000000..ba56b1727 --- /dev/null +++ b/lib/chats/getChatArtist.ts @@ -0,0 +1,31 @@ +import { NEW_API_BASE_URL } from "@/lib/consts"; + +interface ChatArtistResponse { + status: string; + room_id: string; + artist_id: string | null; + artist_exists: boolean; +} + +/** + * Fetches the artist associated with a chat room. + */ +export async function getChatArtist( + roomId: string, + accessToken: string, + baseUrl?: string, +): Promise { + const url = baseUrl || NEW_API_BASE_URL; + + const response = await fetch(`${url}/api/chats/${encodeURIComponent(roomId)}/artist`, { + headers: { + Authorization: `Bearer ${accessToken}`, + }, + }); + + if (!response.ok) { + throw new Error("Failed to fetch chat artist"); + } + + return response.json(); +}