Spaces:
Sleeping
Sleeping
| import { atom } from 'nanostores'; | |
| import type { SupabaseUser, SupabaseStats, SupabaseApiKey, SupabaseCredentials } from '~/types/supabase'; | |
| export interface SupabaseProject { | |
| id: string; | |
| name: string; | |
| region: string; | |
| organization_id: string; | |
| status: string; | |
| database?: { | |
| host: string; | |
| version: string; | |
| postgres_engine: string; | |
| release_channel: string; | |
| }; | |
| created_at: string; | |
| } | |
| export interface SupabaseConnectionState { | |
| user: SupabaseUser | null; | |
| token: string; | |
| stats?: SupabaseStats; | |
| selectedProjectId?: string; | |
| isConnected?: boolean; | |
| project?: SupabaseProject; | |
| credentials?: SupabaseCredentials; | |
| } | |
| const savedConnection = typeof localStorage !== 'undefined' ? localStorage.getItem('supabase_connection') : null; | |
| const savedCredentials = typeof localStorage !== 'undefined' ? localStorage.getItem('supabaseCredentials') : null; | |
| const initialState: SupabaseConnectionState = savedConnection | |
| ? JSON.parse(savedConnection) | |
| : { | |
| user: null, | |
| token: '', | |
| stats: undefined, | |
| selectedProjectId: undefined, | |
| isConnected: false, | |
| project: undefined, | |
| }; | |
| if (savedCredentials && !initialState.credentials) { | |
| try { | |
| initialState.credentials = JSON.parse(savedCredentials); | |
| } catch (e) { | |
| console.error('Failed to parse saved credentials:', e); | |
| } | |
| } | |
| export const supabaseConnection = atom<SupabaseConnectionState>(initialState); | |
| if (initialState.token && !initialState.stats) { | |
| fetchSupabaseStats(initialState.token).catch(console.error); | |
| } | |
| export const isConnecting = atom(false); | |
| export const isFetchingStats = atom(false); | |
| export const isFetchingApiKeys = atom(false); | |
| export function updateSupabaseConnection(connection: Partial<SupabaseConnectionState>) { | |
| const currentState = supabaseConnection.get(); | |
| if (connection.user !== undefined || connection.token !== undefined) { | |
| const newUser = connection.user !== undefined ? connection.user : currentState.user; | |
| const newToken = connection.token !== undefined ? connection.token : currentState.token; | |
| connection.isConnected = !!(newUser && newToken); | |
| } | |
| if (connection.selectedProjectId !== undefined) { | |
| if (connection.selectedProjectId && currentState.stats?.projects) { | |
| const selectedProject = currentState.stats.projects.find( | |
| (project) => project.id === connection.selectedProjectId, | |
| ); | |
| if (selectedProject) { | |
| connection.project = selectedProject; | |
| } else { | |
| connection.project = { | |
| id: connection.selectedProjectId, | |
| name: `Project ${connection.selectedProjectId.substring(0, 8)}...`, | |
| region: 'unknown', | |
| organization_id: '', | |
| status: 'active', | |
| created_at: new Date().toISOString(), | |
| }; | |
| } | |
| } else if (connection.selectedProjectId === '') { | |
| connection.project = undefined; | |
| connection.credentials = undefined; | |
| } | |
| } | |
| const newState = { ...currentState, ...connection }; | |
| supabaseConnection.set(newState); | |
| /* | |
| * Always save the connection state to localStorage to persist across chats | |
| */ | |
| if (connection.user || connection.token || connection.selectedProjectId !== undefined || connection.credentials) { | |
| localStorage.setItem('supabase_connection', JSON.stringify(newState)); | |
| if (newState.credentials) { | |
| localStorage.setItem('supabaseCredentials', JSON.stringify(newState.credentials)); | |
| } else { | |
| localStorage.removeItem('supabaseCredentials'); | |
| } | |
| } else { | |
| localStorage.removeItem('supabase_connection'); | |
| localStorage.removeItem('supabaseCredentials'); | |
| } | |
| } | |
| export async function fetchSupabaseStats(token: string) { | |
| isFetchingStats.set(true); | |
| try { | |
| // Use the internal API route instead of direct Supabase API call | |
| const response = await fetch('/api/supabase', { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| }, | |
| body: JSON.stringify({ | |
| token, | |
| }), | |
| }); | |
| if (!response.ok) { | |
| throw new Error('Failed to fetch projects'); | |
| } | |
| const data = (await response.json()) as any; | |
| updateSupabaseConnection({ | |
| user: data.user, | |
| stats: data.stats, | |
| }); | |
| } catch (error) { | |
| console.error('Failed to fetch Supabase stats:', error); | |
| throw error; | |
| } finally { | |
| isFetchingStats.set(false); | |
| } | |
| } | |
| export async function fetchProjectApiKeys(projectId: string, token: string) { | |
| isFetchingApiKeys.set(true); | |
| try { | |
| const response = await fetch('/api/supabase/variables', { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| }, | |
| body: JSON.stringify({ | |
| projectId, | |
| token, | |
| }), | |
| }); | |
| if (!response.ok) { | |
| throw new Error('Failed to fetch API keys'); | |
| } | |
| const data = (await response.json()) as any; | |
| const apiKeys = data.apiKeys; | |
| const anonKey = apiKeys.find((key: SupabaseApiKey) => key.name === 'anon' || key.name === 'public'); | |
| if (anonKey) { | |
| const supabaseUrl = `https://${projectId}.supabase.co`; | |
| updateSupabaseConnection({ | |
| credentials: { | |
| anonKey: anonKey.api_key, | |
| supabaseUrl, | |
| }, | |
| }); | |
| return { anonKey: anonKey.api_key, supabaseUrl }; | |
| } | |
| return null; | |
| } catch (error) { | |
| console.error('Failed to fetch project API keys:', error); | |
| throw error; | |
| } finally { | |
| isFetchingApiKeys.set(false); | |
| } | |
| } | |