bolt.diy/app/lib/stores/supabase.ts

192 lines
5.3 KiB
TypeScript

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);
}
}