-
Notifications
You must be signed in to change notification settings - Fork 1
fix(MULTIPLA-004): CU-86akhf8u5 2 review findings across 2 files #393
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| export const adminQueryKeys = { | ||
| apiKeys: { | ||
| all: ['admin', 'api-keys'] as const, | ||
| list: () => [...adminQueryKeys.apiKeys.all, 'list'] as const, | ||
| detail: (id: string) => [...adminQueryKeys.apiKeys.all, 'detail', id] as const, | ||
| }, | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,9 @@ | ||
| 'use client'; | ||
|
|
||
| import { useCallback, useState } from 'react'; | ||
| import { useCallback } from 'react'; | ||
| import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; | ||
| import { apiClient } from '@/lib/api-client'; | ||
| import { adminQueryKeys } from './admin-query-keys'; | ||
|
|
||
| export type ApiKeyRecord = { | ||
| id: string; | ||
|
|
@@ -18,75 +20,124 @@ export type ApiKeyRecord = { | |
| }; | ||
|
|
||
| export function useApiKeys() { | ||
| const [items, setItems] = useState<ApiKeyRecord[]>([]); | ||
| const [isLoading, setIsLoading] = useState(false); | ||
| const [error, setError] = useState<string | null>(null); | ||
| const queryClient = useQueryClient(); | ||
|
|
||
| const fetchApiKeys = useCallback(async () => { | ||
| setIsLoading(true); | ||
| setError(null); | ||
| try { | ||
| const res = await apiClient.get<ApiKeyRecord[]>('api/api-keys'); | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🦩 🟠 useApiKeys.fetchApiKeys/createApiKey use a relative path without leading slash unlike all other apiClient calls Added leading slashes to all 🤖 Prompt for AI agentsfix confidence: 🟡 80 medium — react 👍/👎 to teach the reviewer |
||
| const { | ||
| data: items = [], | ||
| isLoading, | ||
| error: queryError, | ||
| refetch, | ||
| } = useQuery({ | ||
| queryKey: adminQueryKeys.apiKeys.list(), | ||
| queryFn: async () => { | ||
| const res = await apiClient.get<ApiKeyRecord[]>('/api/api-keys'); | ||
| if (!res.ok || !Array.isArray(res.data)) { | ||
| throw new Error(res.error || `Failed to load API keys (${res.status})`); | ||
| } | ||
| setItems(res.data); | ||
| return res.data; | ||
| } catch (e) { | ||
| const msg = e instanceof Error ? e.message : 'Failed to load API keys'; | ||
| setError(msg); | ||
| throw e; | ||
| } finally { | ||
| setIsLoading(false); | ||
| } | ||
| }, []); | ||
| }, | ||
| }); | ||
|
|
||
| const createApiKey = useCallback(async (data: { name: string; description?: string; expiresAt?: string | null }) => { | ||
| const payload = { | ||
| name: data.name, | ||
| description: data.description || undefined, | ||
| expiresAt: data.expiresAt ?? null, | ||
| }; | ||
| const res = await apiClient.post<{ apiKey: ApiKeyRecord; fullKey: string }>('api/api-keys', payload); | ||
| if (!res.ok || !res.data) { | ||
| throw new Error(res.error || `Failed to create API key (${res.status})`); | ||
| const error = queryError instanceof Error ? queryError.message : queryError ? String(queryError) : null; | ||
|
|
||
| const fetchApiKeys = useCallback(async () => { | ||
| const result = await refetch(); | ||
| if (result.error) { | ||
| throw result.error; | ||
| } | ||
| return res.data; | ||
| }, []); | ||
| return result.data ?? []; | ||
| }, [refetch]); | ||
|
|
||
| const updateApiKey = useCallback( | ||
| async (id: string, data: { name: string; description?: string; expiresAt?: string | null }) => { | ||
| const createApiKeyMutation = useMutation({ | ||
| mutationFn: async (data: { name: string; description?: string; expiresAt?: string | null }) => { | ||
| const payload = { | ||
| name: data.name, | ||
| description: data.description || undefined, | ||
| expiresAt: data.expiresAt ?? null, | ||
| }; | ||
| const res = await apiClient.put<ApiKeyRecord>(`api/api-keys/${encodeURIComponent(id)}`, payload); | ||
| const res = await apiClient.post<{ apiKey: ApiKeyRecord; fullKey: string }>('/api/api-keys', payload); | ||
| if (!res.ok || !res.data) { | ||
| throw new Error(res.error || `Failed to create API key (${res.status})`); | ||
| } | ||
| return res.data; | ||
| }, | ||
| onSuccess: () => { | ||
| queryClient.invalidateQueries({ queryKey: adminQueryKeys.apiKeys.all }); | ||
| }, | ||
| }); | ||
|
|
||
| const updateApiKeyMutation = useMutation({ | ||
| mutationFn: async ({ | ||
| id, | ||
| data, | ||
| }: { | ||
| id: string; | ||
| data: { name: string; description?: string; expiresAt?: string | null }; | ||
| }) => { | ||
| const payload = { | ||
| name: data.name, | ||
| description: data.description || undefined, | ||
| expiresAt: data.expiresAt ?? null, | ||
| }; | ||
| const res = await apiClient.put<ApiKeyRecord>(`/api/api-keys/${encodeURIComponent(id)}`, payload); | ||
| if (!res.ok || !res.data) { | ||
| throw new Error(res.error || `Failed to update API key (${res.status})`); | ||
| } | ||
| return res.data; | ||
| }, | ||
| [], | ||
| onSuccess: () => { | ||
| queryClient.invalidateQueries({ queryKey: adminQueryKeys.apiKeys.all }); | ||
| }, | ||
| }); | ||
|
|
||
| const regenerateApiKeyMutation = useMutation({ | ||
| mutationFn: async (id: string) => { | ||
| const res = await apiClient.post<{ apiKey: ApiKeyRecord; fullKey: string }>( | ||
| `/api/api-keys/${encodeURIComponent(id)}/regenerate`, | ||
| ); | ||
| if (!res.ok || !res.data) { | ||
| throw new Error(res.error || `Failed to regenerate API key (${res.status})`); | ||
| } | ||
| return res.data; | ||
| }, | ||
| onSuccess: () => { | ||
| queryClient.invalidateQueries({ queryKey: adminQueryKeys.apiKeys.all }); | ||
| }, | ||
| }); | ||
|
|
||
| const setApiKeyEnabledMutation = useMutation({ | ||
| mutationFn: async ({ id, enabled }: { id: string; enabled: boolean }) => { | ||
| const res = await apiClient.put<ApiKeyRecord>(`/api/api-keys/${encodeURIComponent(id)}`, { enabled }); | ||
| if (!res.ok || !res.data) { | ||
| throw new Error(res.error || `Failed to ${enabled ? 'enable' : 'disable'} API key (${res.status})`); | ||
| } | ||
| return res.data; | ||
| }, | ||
| onSuccess: () => { | ||
| queryClient.invalidateQueries({ queryKey: adminQueryKeys.apiKeys.all }); | ||
| }, | ||
| }); | ||
|
|
||
| const createApiKey = useCallback( | ||
| (data: { name: string; description?: string; expiresAt?: string | null }) => createApiKeyMutation.mutateAsync(data), | ||
| [createApiKeyMutation], | ||
| ); | ||
|
|
||
| const regenerateApiKey = useCallback(async (id: string) => { | ||
| const res = await apiClient.post<{ apiKey: ApiKeyRecord; fullKey: string }>( | ||
| `api/api-keys/${encodeURIComponent(id)}/regenerate`, | ||
| ); | ||
| if (!res.ok || !res.data) { | ||
| throw new Error(res.error || `Failed to regenerate API key (${res.status})`); | ||
| } | ||
| return res.data; | ||
| }, []); | ||
| const updateApiKey = useCallback( | ||
| (id: string, data: { name: string; description?: string; expiresAt?: string | null }) => | ||
| updateApiKeyMutation.mutateAsync({ id, data }), | ||
| [updateApiKeyMutation], | ||
| ); | ||
|
|
||
| const setApiKeyEnabled = useCallback(async (id: string, enabled: boolean) => { | ||
| const res = await apiClient.put<ApiKeyRecord>(`api/api-keys/${encodeURIComponent(id)}`, { enabled }); | ||
| if (!res.ok || !res.data) { | ||
| throw new Error(res.error || `Failed to ${enabled ? 'enable' : 'disable'} API key (${res.status})`); | ||
| } | ||
| return res.data; | ||
| }, []); | ||
| const regenerateApiKey = useCallback( | ||
| (id: string) => regenerateApiKeyMutation.mutateAsync(id), | ||
| [regenerateApiKeyMutation], | ||
| ); | ||
|
|
||
| const setApiKeyEnabled = useCallback( | ||
| (id: string, enabled: boolean) => setApiKeyEnabledMutation.mutateAsync({ id, enabled }), | ||
| [setApiKeyEnabledMutation], | ||
| ); | ||
|
|
||
| return { items, isLoading, error, fetchApiKeys, createApiKey, updateApiKey, regenerateApiKey, setApiKeyEnabled }; | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🦩 🔴 use-api-keys.ts hook does not use TanStack Query at all — plain useState/useCallback with manual fetch orchestration
Rewrote
useApiKeysinsrc/app/(app)/settings/hooks/use-api-keys.tsto use TanStack Query'suseQuery(for the list, keyed via new sharedadminQueryKeys.apiKeys.list()) anduseMutationfor create/update/regenerate/enable-toggle operations, each withonSuccesscallingqueryClient.invalidateQueries({ queryKey: adminQueryKeys.apiKeys.all }). Added new shared modulesrc/app/(app)/settings/hooks/admin-query-keys.tsexportingadminQueryKeyswith structured keys, since none existed in the file set provided. The public return shape (items,isLoading,error,fetchApiKeys,createApiKey,updateApiKey,regenerateApiKey,setApiKeyEnabled) is preserved so existing consumers keep working without changes, withfetchApiKeysnow wrappingrefetch(). Risk: I could not seeuse-users.tsto confirm exact naming/shape conventions foradminQueryKeys, so the key structure is a reasonable but unverified guess; a complete fix should align this module's naming with whatever conventionuse-users.tsactually uses.🤖 Prompt for AI agents
fix confidence: 🟡 65 medium — react 👍/👎 to teach the reviewer