From d5121ebddc6d8cdd02a2bec05c3c4f0f440cd936 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 25 Jun 2026 03:23:08 +0000 Subject: [PATCH 1/4] =?UTF-8?q?refactor(api):=20knowledge=20=E2=86=92=20sr?= =?UTF-8?q?c/api=20modules=20(Effort=201)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mechanical move of the knowledge feature from src/hooks/queries/knowledge to src/api/knowledge, adding queryOptions factories for the query hooks. No behavior change. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01K513rsQz6Lg1HbbfYiafrE --- src/api/knowledge/types.ts | 4 + src/api/knowledge/useKnowledge.ts | 34 ++++++++ .../knowledge/useKnowledgeToggleMutation.ts} | 79 +------------------ src/api/knowledge/useUserKnowledgeQuery.ts | 38 +++++++++ 4 files changed, 78 insertions(+), 77 deletions(-) create mode 100644 src/api/knowledge/types.ts create mode 100644 src/api/knowledge/useKnowledge.ts rename src/{hooks/queries/knowledge/useKnowledge.ts => api/knowledge/useKnowledgeToggleMutation.ts} (51%) create mode 100644 src/api/knowledge/useUserKnowledgeQuery.ts diff --git a/src/api/knowledge/types.ts b/src/api/knowledge/types.ts new file mode 100644 index 00000000..294b7c3a --- /dev/null +++ b/src/api/knowledge/types.ts @@ -0,0 +1,4 @@ +export const knowledgeKeys = { + all: ["knowledge"] as const, + user: (userId: string) => [...knowledgeKeys.all, "user", userId] as const, +}; diff --git a/src/api/knowledge/useKnowledge.ts b/src/api/knowledge/useKnowledge.ts new file mode 100644 index 00000000..20e6846c --- /dev/null +++ b/src/api/knowledge/useKnowledge.ts @@ -0,0 +1,34 @@ +import { useAuth } from "@/contexts/AuthContext"; +import { useUserKnowledgeQuery } from "./useUserKnowledgeQuery"; +import { useKnowledgeToggleMutation } from "./useKnowledgeToggleMutation"; + +export function useKnowledge() { + const { user } = useAuth(); + const { data: userKnowledge = {} } = useUserKnowledgeQuery(user?.id); + const knowledgeToggleMutation = useKnowledgeToggleMutation(); + + async function handleKnowledgeToggle(artistId: string) { + if (!user) { + return { requiresAuth: true }; + } + + const isKnown = userKnowledge[artistId]; + + try { + await knowledgeToggleMutation.mutateAsync({ + artistId, + userId: user.id, + isKnown, + }); + return { requiresAuth: false }; + } catch (error) { + console.error("failed toggling knowledge", error); + return { requiresAuth: false }; + } + } + + return { + userKnowledge, + handleKnowledgeToggle, + }; +} diff --git a/src/hooks/queries/knowledge/useKnowledge.ts b/src/api/knowledge/useKnowledgeToggleMutation.ts similarity index 51% rename from src/hooks/queries/knowledge/useKnowledge.ts rename to src/api/knowledge/useKnowledgeToggleMutation.ts index 80e81a3a..af4ed689 100644 --- a/src/hooks/queries/knowledge/useKnowledge.ts +++ b/src/api/knowledge/useKnowledgeToggleMutation.ts @@ -1,35 +1,7 @@ -import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; import { useToast } from "@/hooks/use-toast"; import { supabase } from "@/integrations/supabase/client"; -import { useAuth } from "@/contexts/AuthContext"; - -// Query key factory -export const knowledgeKeys = { - all: ["knowledge"] as const, - user: (userId: string) => [...knowledgeKeys.all, "user", userId] as const, -}; - -// Business logic functions -async function fetchUserKnowledge( - userId: string, -): Promise> { - const { data, error } = await supabase - .from("artist_knowledge") - .select("artist_id") - .eq("user_id", userId); - - if (error) { - throw new Error("Failed to fetch user knowledge"); - } - - return (data || []).reduce( - (acc, knowledge) => { - acc[knowledge.artist_id] = true; - return acc; - }, - {} as Record, - ); -} +import { knowledgeKeys } from "./types"; async function toggleKnowledge(variables: { artistId: string; @@ -58,15 +30,6 @@ async function toggleKnowledge(variables: { } } -// Hooks -export function useUserKnowledgeQuery(userId: string | undefined) { - return useQuery({ - queryKey: knowledgeKeys.user(userId || ""), - queryFn: () => fetchUserKnowledge(userId!), - enabled: !!userId, - }); -} - export function useKnowledgeToggleMutation() { const queryClient = useQueryClient(); const { toast } = useToast(); @@ -76,17 +39,14 @@ export function useKnowledgeToggleMutation() { onMutate: async (variables) => { const { artistId, userId, isKnown } = variables; - // Cancel any outgoing refetches await queryClient.cancelQueries({ queryKey: knowledgeKeys.user(userId), }); - // Snapshot the previous value const previousKnowledge = queryClient.getQueryData< Record >(knowledgeKeys.user(userId)); - // Optimistically update to the new value queryClient.setQueryData>( knowledgeKeys.user(userId), (old) => { @@ -94,10 +54,8 @@ export function useKnowledgeToggleMutation() { const newKnowledge = { ...old }; if (isKnown) { - // Remove knowledge delete newKnowledge[artistId]; } else { - // Add knowledge newKnowledge[artistId] = true; } @@ -108,7 +66,6 @@ export function useKnowledgeToggleMutation() { return { previousKnowledge, userId }; }, onError: (_error, _variables, context) => { - // If the mutation fails, use the context returned from onMutate to roll back if (context?.previousKnowledge) { queryClient.setQueryData( knowledgeKeys.user(context.userId), @@ -123,41 +80,9 @@ export function useKnowledgeToggleMutation() { }); }, onSettled: (_data, _error, variables) => { - // Always refetch after error or success to ensure consistency queryClient.invalidateQueries({ queryKey: knowledgeKeys.user(variables.userId), }); }, }); } - -export function useKnowledge() { - const { user } = useAuth(); - const { data: userKnowledge = {} } = useUserKnowledgeQuery(user?.id); - const knowledgeToggleMutation = useKnowledgeToggleMutation(); - - async function handleKnowledgeToggle(artistId: string) { - if (!user) { - return { requiresAuth: true }; - } - - const isKnown = userKnowledge[artistId]; - - try { - await knowledgeToggleMutation.mutateAsync({ - artistId, - userId: user.id, - isKnown, - }); - return { requiresAuth: false }; - } catch (error) { - console.error("failed toggling knowledge", error); - return { requiresAuth: false }; - } - } - - return { - userKnowledge, - handleKnowledgeToggle, - }; -} diff --git a/src/api/knowledge/useUserKnowledgeQuery.ts b/src/api/knowledge/useUserKnowledgeQuery.ts new file mode 100644 index 00000000..cf02e5e1 --- /dev/null +++ b/src/api/knowledge/useUserKnowledgeQuery.ts @@ -0,0 +1,38 @@ +import { queryOptions, useQuery } from "@tanstack/react-query"; +import { supabase } from "@/integrations/supabase/client"; +import { knowledgeKeys } from "./types"; + +async function fetchUserKnowledge( + userId: string, +): Promise> { + const { data, error } = await supabase + .from("artist_knowledge") + .select("artist_id") + .eq("user_id", userId); + + if (error) { + throw new Error("Failed to fetch user knowledge"); + } + + return (data || []).reduce( + (acc, knowledge) => { + acc[knowledge.artist_id] = true; + return acc; + }, + {} as Record, + ); +} + +export function userKnowledgeQuery(userId: string) { + return queryOptions({ + queryKey: knowledgeKeys.user(userId), + queryFn: () => fetchUserKnowledge(userId), + }); +} + +export function useUserKnowledgeQuery(userId: string | undefined) { + return useQuery({ + ...userKnowledgeQuery(userId!), + enabled: !!userId, + }); +} From ec524452bae0974c4dd4b6fac97ee3a5d79dbe89 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 25 Jun 2026 03:43:18 +0000 Subject: [PATCH 2/4] fix(api): preserve empty-string key normalization in useUserKnowledgeQuery The mechanical move changed knowledgeKeys.user(userId || "") to userKnowledgeQuery(userId!), shifting the disabled-state cache key from ["...", ""] to ["...", undefined]. Restore ?? "" to keep the move behavior-neutral, matching main. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01K513rsQz6Lg1HbbfYiafrE --- src/api/knowledge/useUserKnowledgeQuery.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/knowledge/useUserKnowledgeQuery.ts b/src/api/knowledge/useUserKnowledgeQuery.ts index cf02e5e1..4bf26d37 100644 --- a/src/api/knowledge/useUserKnowledgeQuery.ts +++ b/src/api/knowledge/useUserKnowledgeQuery.ts @@ -32,7 +32,7 @@ export function userKnowledgeQuery(userId: string) { export function useUserKnowledgeQuery(userId: string | undefined) { return useQuery({ - ...userKnowledgeQuery(userId!), + ...userKnowledgeQuery(userId ?? ""), enabled: !!userId, }); } From 730edd9b14c4e8b1f44b2eca8c59bafc985afcb7 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 1 Jul 2026 08:54:43 +0000 Subject: [PATCH 3/4] refactor(knowledge): use mutate callbacks and non-null key assertion - handleKnowledgeToggle: replace mutateAsync/try-catch with mutate(vars, { onError }) per project mutation convention - useUserKnowledgeQuery: use userId! instead of ?? "" in the query key (fetch is gated by enabled: !!userId) Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01K513rsQz6Lg1HbbfYiafrE --- src/api/knowledge/useKnowledge.ts | 21 ++++++++++++--------- src/api/knowledge/useUserKnowledgeQuery.ts | 2 +- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/api/knowledge/useKnowledge.ts b/src/api/knowledge/useKnowledge.ts index 20e6846c..530f27e3 100644 --- a/src/api/knowledge/useKnowledge.ts +++ b/src/api/knowledge/useKnowledge.ts @@ -7,24 +7,27 @@ export function useKnowledge() { const { data: userKnowledge = {} } = useUserKnowledgeQuery(user?.id); const knowledgeToggleMutation = useKnowledgeToggleMutation(); - async function handleKnowledgeToggle(artistId: string) { + function handleKnowledgeToggle(artistId: string) { if (!user) { return { requiresAuth: true }; } const isKnown = userKnowledge[artistId]; - try { - await knowledgeToggleMutation.mutateAsync({ + knowledgeToggleMutation.mutate( + { artistId, userId: user.id, isKnown, - }); - return { requiresAuth: false }; - } catch (error) { - console.error("failed toggling knowledge", error); - return { requiresAuth: false }; - } + }, + { + onError: (error) => { + console.error("failed toggling knowledge", error); + }, + }, + ); + + return { requiresAuth: false }; } return { diff --git a/src/api/knowledge/useUserKnowledgeQuery.ts b/src/api/knowledge/useUserKnowledgeQuery.ts index 4bf26d37..cf02e5e1 100644 --- a/src/api/knowledge/useUserKnowledgeQuery.ts +++ b/src/api/knowledge/useUserKnowledgeQuery.ts @@ -32,7 +32,7 @@ export function userKnowledgeQuery(userId: string) { export function useUserKnowledgeQuery(userId: string | undefined) { return useQuery({ - ...userKnowledgeQuery(userId ?? ""), + ...userKnowledgeQuery(userId!), enabled: !!userId, }); } From 4cd7024854b426aa7bf828d5c582a97cc36795ec Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 1 Jul 2026 09:54:32 +0000 Subject: [PATCH 4/4] chore(knowledge): remove unused, never-wired knowledge feature MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The knowledge slice (useKnowledge, useKnowledgeToggleMutation, useUserKnowledgeQuery) has never had a consumer anywhere in the app's history — it was introduced alongside the artist_knowledge table but no UI ever called it. Remove the dead code instead of carrying it forward in the src/api restructure. The artist_knowledge DB table is left untouched. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01K513rsQz6Lg1HbbfYiafrE --- src/api/knowledge/types.ts | 4 - src/api/knowledge/useKnowledge.ts | 37 -------- .../knowledge/useKnowledgeToggleMutation.ts | 88 ------------------- src/api/knowledge/useUserKnowledgeQuery.ts | 38 -------- 4 files changed, 167 deletions(-) delete mode 100644 src/api/knowledge/types.ts delete mode 100644 src/api/knowledge/useKnowledge.ts delete mode 100644 src/api/knowledge/useKnowledgeToggleMutation.ts delete mode 100644 src/api/knowledge/useUserKnowledgeQuery.ts diff --git a/src/api/knowledge/types.ts b/src/api/knowledge/types.ts deleted file mode 100644 index 294b7c3a..00000000 --- a/src/api/knowledge/types.ts +++ /dev/null @@ -1,4 +0,0 @@ -export const knowledgeKeys = { - all: ["knowledge"] as const, - user: (userId: string) => [...knowledgeKeys.all, "user", userId] as const, -}; diff --git a/src/api/knowledge/useKnowledge.ts b/src/api/knowledge/useKnowledge.ts deleted file mode 100644 index 530f27e3..00000000 --- a/src/api/knowledge/useKnowledge.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { useAuth } from "@/contexts/AuthContext"; -import { useUserKnowledgeQuery } from "./useUserKnowledgeQuery"; -import { useKnowledgeToggleMutation } from "./useKnowledgeToggleMutation"; - -export function useKnowledge() { - const { user } = useAuth(); - const { data: userKnowledge = {} } = useUserKnowledgeQuery(user?.id); - const knowledgeToggleMutation = useKnowledgeToggleMutation(); - - function handleKnowledgeToggle(artistId: string) { - if (!user) { - return { requiresAuth: true }; - } - - const isKnown = userKnowledge[artistId]; - - knowledgeToggleMutation.mutate( - { - artistId, - userId: user.id, - isKnown, - }, - { - onError: (error) => { - console.error("failed toggling knowledge", error); - }, - }, - ); - - return { requiresAuth: false }; - } - - return { - userKnowledge, - handleKnowledgeToggle, - }; -} diff --git a/src/api/knowledge/useKnowledgeToggleMutation.ts b/src/api/knowledge/useKnowledgeToggleMutation.ts deleted file mode 100644 index af4ed689..00000000 --- a/src/api/knowledge/useKnowledgeToggleMutation.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { useMutation, useQueryClient } from "@tanstack/react-query"; -import { useToast } from "@/hooks/use-toast"; -import { supabase } from "@/integrations/supabase/client"; -import { knowledgeKeys } from "./types"; - -async function toggleKnowledge(variables: { - artistId: string; - userId: string; - isKnown: boolean; -}) { - const { artistId, userId, isKnown } = variables; - - if (isKnown) { - const { error } = await supabase - .from("artist_knowledge") - .delete() - .eq("user_id", userId) - .eq("artist_id", artistId); - - if (error) throw new Error("Failed to remove knowledge"); - return false; - } else { - const { error } = await supabase.from("artist_knowledge").insert({ - user_id: userId, - artist_id: artistId, - }); - - if (error) throw new Error("Failed to add knowledge"); - return true; - } -} - -export function useKnowledgeToggleMutation() { - const queryClient = useQueryClient(); - const { toast } = useToast(); - - return useMutation({ - mutationFn: toggleKnowledge, - onMutate: async (variables) => { - const { artistId, userId, isKnown } = variables; - - await queryClient.cancelQueries({ - queryKey: knowledgeKeys.user(userId), - }); - - const previousKnowledge = queryClient.getQueryData< - Record - >(knowledgeKeys.user(userId)); - - queryClient.setQueryData>( - knowledgeKeys.user(userId), - (old) => { - if (!old) return {}; - const newKnowledge = { ...old }; - - if (isKnown) { - delete newKnowledge[artistId]; - } else { - newKnowledge[artistId] = true; - } - - return newKnowledge; - }, - ); - - return { previousKnowledge, userId }; - }, - onError: (_error, _variables, context) => { - if (context?.previousKnowledge) { - queryClient.setQueryData( - knowledgeKeys.user(context.userId), - context.previousKnowledge, - ); - } - - toast({ - title: "Error", - description: "Failed to update knowledge. Please try again.", - variant: "destructive", - }); - }, - onSettled: (_data, _error, variables) => { - queryClient.invalidateQueries({ - queryKey: knowledgeKeys.user(variables.userId), - }); - }, - }); -} diff --git a/src/api/knowledge/useUserKnowledgeQuery.ts b/src/api/knowledge/useUserKnowledgeQuery.ts deleted file mode 100644 index cf02e5e1..00000000 --- a/src/api/knowledge/useUserKnowledgeQuery.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { queryOptions, useQuery } from "@tanstack/react-query"; -import { supabase } from "@/integrations/supabase/client"; -import { knowledgeKeys } from "./types"; - -async function fetchUserKnowledge( - userId: string, -): Promise> { - const { data, error } = await supabase - .from("artist_knowledge") - .select("artist_id") - .eq("user_id", userId); - - if (error) { - throw new Error("Failed to fetch user knowledge"); - } - - return (data || []).reduce( - (acc, knowledge) => { - acc[knowledge.artist_id] = true; - return acc; - }, - {} as Record, - ); -} - -export function userKnowledgeQuery(userId: string) { - return queryOptions({ - queryKey: knowledgeKeys.user(userId), - queryFn: () => fetchUserKnowledge(userId), - }); -} - -export function useUserKnowledgeQuery(userId: string | undefined) { - return useQuery({ - ...userKnowledgeQuery(userId!), - enabled: !!userId, - }); -}