509 lines
18 KiB
TypeScript
509 lines
18 KiB
TypeScript
import { supabase as defaultSupabase } from "@/integrations/supabase/client";
|
|
import { UserProfile } from "@/pages/Post/types";
|
|
import { SupabaseClient } from "@supabase/supabase-js";
|
|
import { fetchWithDeduplication } from "@/lib/db";
|
|
|
|
|
|
/** Fetch full profile data from server API endpoint */
|
|
export const fetchProfileAPI = async (userId: string): Promise<{ profile: any; recentPosts: any[] } | null> => {
|
|
const res = await fetch(`/api/profile/${userId}`);
|
|
if (!res.ok) {
|
|
if (res.status === 404) return null;
|
|
throw new Error(`Failed to fetch profile: ${res.statusText}`);
|
|
}
|
|
return await res.json();
|
|
};
|
|
|
|
export const fetchAuthorProfile = async (userId: string, client?: SupabaseClient): Promise<UserProfile | null> => {
|
|
const supabase = client || defaultSupabase;
|
|
return fetchWithDeduplication(`profile-${userId}`, async () => {
|
|
console.log('Fetching profile for user:', userId);
|
|
const { data, error } = await supabase
|
|
.from('profiles')
|
|
.select('user_id, avatar_url, display_name, username')
|
|
.eq('user_id', userId)
|
|
.single();
|
|
|
|
if (error && error.code !== 'PGRST116') throw error;
|
|
return data as UserProfile;
|
|
});
|
|
};
|
|
|
|
export const getUserSettings = async (userId: string, client?: SupabaseClient) => {
|
|
return fetchWithDeduplication(`settings-${userId}`, async () => {
|
|
const token = await getAuthToken();
|
|
const res = await fetch('/api/me/settings', {
|
|
headers: { 'Authorization': `Bearer ${token}` }
|
|
});
|
|
if (!res.ok) throw new Error(`Failed to fetch settings: ${res.statusText}`);
|
|
return await res.json();
|
|
}, 100000);
|
|
};
|
|
|
|
export const updateUserSettings = async (userId: string, settings: any, client?: SupabaseClient) => {
|
|
const token = await getAuthToken();
|
|
const res = await fetch('/api/me/settings', {
|
|
method: 'PATCH',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Authorization': `Bearer ${token}`
|
|
},
|
|
body: JSON.stringify(settings)
|
|
});
|
|
if (!res.ok) throw new Error(`Failed to update settings: ${res.statusText}`);
|
|
};
|
|
|
|
export const getUserOpenAIKey = async (userId: string, client?: SupabaseClient) => {
|
|
const supabase = client || defaultSupabase;
|
|
return fetchWithDeduplication(`openai-${userId}`, async () => {
|
|
const { data, error } = await supabase
|
|
.from('user_secrets')
|
|
.select('settings')
|
|
.eq('user_id', userId)
|
|
.maybeSingle();
|
|
|
|
if (error) throw error;
|
|
const settings = data?.settings as any;
|
|
return settings?.api_keys?.openai_api_key;
|
|
});
|
|
}
|
|
|
|
/** Get all API keys from user_secrets.settings.api_keys */
|
|
export const getUserApiKeys = async (userId: string, client?: SupabaseClient): Promise<Record<string, string> | null> => {
|
|
const supabase = client || defaultSupabase;
|
|
return fetchWithDeduplication(`api-keys-${userId}`, async () => {
|
|
const { data, error } = await supabase
|
|
.from('user_secrets')
|
|
.select('settings')
|
|
.eq('user_id', userId)
|
|
.maybeSingle();
|
|
|
|
if (error) throw error;
|
|
const settings = data?.settings as any;
|
|
return (settings?.api_keys as Record<string, string>) || null;
|
|
});
|
|
};
|
|
|
|
export const getUserGoogleApiKey = async (userId: string, client?: SupabaseClient) => {
|
|
const supabase = client || defaultSupabase;
|
|
return fetchWithDeduplication(`google-${userId}`, async () => {
|
|
const { data, error } = await supabase
|
|
.from('user_secrets')
|
|
.select('settings')
|
|
.eq('user_id', userId)
|
|
.maybeSingle();
|
|
|
|
if (error) throw error;
|
|
const settings = data?.settings as any;
|
|
return settings?.api_keys?.google_api_key;
|
|
});
|
|
}
|
|
|
|
export const getUserSecrets = async (userId: string, client?: SupabaseClient) => {
|
|
const supabase = client || defaultSupabase;
|
|
return fetchWithDeduplication(`user-secrets-${userId}`, async () => {
|
|
console.log('Fetching user secrets for user:', userId);
|
|
const { data, error } = await supabase
|
|
.from('user_secrets')
|
|
.select('settings')
|
|
.eq('user_id', userId)
|
|
.maybeSingle();
|
|
|
|
if (error) throw error;
|
|
|
|
// Return whole settings object or specific part?
|
|
// Instructions involve variables in settings.variables
|
|
const settings = data?.settings as any;
|
|
return settings?.variables || {};
|
|
});
|
|
};
|
|
|
|
export const getProviderConfig = async (userId: string, provider: string, client?: SupabaseClient) => {
|
|
return fetchWithDeduplication(`provider-${userId}-${provider}`, async () => {
|
|
const token = await getAuthToken();
|
|
const res = await fetch(`/api/me/provider-config/${encodeURIComponent(provider)}`, {
|
|
headers: { 'Authorization': `Bearer ${token}` }
|
|
});
|
|
if (res.status === 404) return null;
|
|
if (!res.ok) throw new Error(`Failed to fetch provider config: ${res.statusText}`);
|
|
return await res.json();
|
|
});
|
|
}
|
|
|
|
|
|
|
|
export const fetchUserRoles = async (userId: string, client?: SupabaseClient) => {
|
|
return fetchWithDeduplication(`roles-${userId}`, async () => {
|
|
const token = await getAuthToken();
|
|
const res = await fetch('/api/me/roles', {
|
|
headers: { 'Authorization': `Bearer ${token}` }
|
|
});
|
|
if (!res.ok) {
|
|
console.error('Error fetching user roles:', res.statusText);
|
|
return [];
|
|
}
|
|
return await res.json();
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Update user secrets in user_secrets table (settings column)
|
|
*/
|
|
export const updateUserSecrets = async (userId: string, secrets: Record<string, string>): Promise<void> => {
|
|
try {
|
|
// Check if record exists
|
|
const { data: existing } = await defaultSupabase
|
|
.from('user_secrets')
|
|
.select('settings')
|
|
.eq('user_id', userId)
|
|
.maybeSingle();
|
|
|
|
if (existing) {
|
|
// Update existing
|
|
const currentSettings = (existing.settings as Record<string, any>) || {};
|
|
const currentApiKeys = (currentSettings.api_keys as Record<string, any>) || {};
|
|
|
|
const newSettings = {
|
|
...currentSettings,
|
|
api_keys: { ...currentApiKeys, ...secrets }
|
|
};
|
|
|
|
const { error } = await defaultSupabase
|
|
.from('user_secrets')
|
|
.update({ settings: newSettings })
|
|
.eq('user_id', userId);
|
|
|
|
if (error) throw error;
|
|
} else {
|
|
// Insert new
|
|
const { error } = await defaultSupabase
|
|
.from('user_secrets')
|
|
.insert({
|
|
user_id: userId,
|
|
settings: { api_keys: secrets }
|
|
});
|
|
|
|
if (error) throw error;
|
|
}
|
|
} catch (error) {
|
|
console.error('Error updating user secrets:', error);
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Get user variables from user_secrets table (settings.variables)
|
|
*/
|
|
export const getUserVariables = async (userId: string): Promise<Record<string, any> | null> => {
|
|
console.log('getUserVariables Fetching user variables for user:', userId);
|
|
try {
|
|
const { data: secretData } = await defaultSupabase
|
|
.from('user_secrets')
|
|
.select('settings')
|
|
.eq('user_id', userId)
|
|
.single();
|
|
|
|
if (!secretData?.settings) return null;
|
|
|
|
const settings = secretData.settings as Record<string, any>;
|
|
return (settings.variables as Record<string, any>) || {};
|
|
} catch (error) {
|
|
console.error('Error fetching user variables:', error);
|
|
return null;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Update user variables in user_secrets table (settings.variables)
|
|
*/
|
|
export const updateUserVariables = async (userId: string, variables: Record<string, any>): Promise<void> => {
|
|
console.log('Updating user variables for user:', userId);
|
|
try {
|
|
// Check if record exists
|
|
const { data: existing } = await defaultSupabase
|
|
.from('user_secrets')
|
|
.select('settings')
|
|
.eq('user_id', userId)
|
|
.maybeSingle();
|
|
|
|
if (existing) {
|
|
// Update existing
|
|
const currentSettings = (existing.settings as Record<string, any>) || {};
|
|
|
|
const newSettings = {
|
|
...currentSettings,
|
|
variables: variables // Replace variables entirely with new state (since editor manages full set)
|
|
};
|
|
|
|
const { error } = await defaultSupabase
|
|
.from('user_secrets')
|
|
.update({ settings: newSettings })
|
|
.eq('user_id', userId);
|
|
|
|
if (error) throw error;
|
|
} else {
|
|
// Insert new
|
|
const { error } = await defaultSupabase
|
|
.from('user_secrets')
|
|
.insert({
|
|
user_id: userId,
|
|
settings: { variables: variables }
|
|
});
|
|
|
|
if (error) throw error;
|
|
}
|
|
} catch (error) {
|
|
console.error('Error updating user variables:', error);
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
// =============================================
|
|
// Shipping Addresses (stored in user_secrets.settings.shipping_addresses)
|
|
// =============================================
|
|
|
|
export interface SavedShippingAddress {
|
|
id: string;
|
|
label: string;
|
|
fullName: string;
|
|
email: string;
|
|
phone: string;
|
|
address: string;
|
|
city: string;
|
|
zip: string;
|
|
country: string;
|
|
note: string;
|
|
isDefault: boolean;
|
|
}
|
|
|
|
/** Get shipping addresses from user_secrets.settings.shipping_addresses */
|
|
export const getShippingAddresses = async (userId: string, client?: SupabaseClient): Promise<SavedShippingAddress[]> => {
|
|
const supabase = client || defaultSupabase;
|
|
try {
|
|
const { data, error } = await supabase
|
|
.from('user_secrets')
|
|
.select('settings')
|
|
.eq('user_id', userId)
|
|
.maybeSingle();
|
|
|
|
if (error) throw error;
|
|
const settings = data?.settings as any;
|
|
return (settings?.shipping_addresses as SavedShippingAddress[]) || [];
|
|
} catch (error) {
|
|
console.error('Error fetching shipping addresses:', error);
|
|
return [];
|
|
}
|
|
};
|
|
|
|
/** Save shipping addresses to user_secrets.settings.shipping_addresses (full replace) */
|
|
export const saveShippingAddresses = async (userId: string, addresses: SavedShippingAddress[], client?: SupabaseClient): Promise<void> => {
|
|
const supabase = client || defaultSupabase;
|
|
try {
|
|
const { data: existing } = await supabase
|
|
.from('user_secrets')
|
|
.select('settings')
|
|
.eq('user_id', userId)
|
|
.maybeSingle();
|
|
|
|
if (existing) {
|
|
const currentSettings = (existing.settings as Record<string, any>) || {};
|
|
const newSettings = { ...currentSettings, shipping_addresses: addresses };
|
|
|
|
const { error } = await supabase
|
|
.from('user_secrets')
|
|
.update({ settings: newSettings })
|
|
.eq('user_id', userId);
|
|
|
|
if (error) throw error;
|
|
} else {
|
|
const { error } = await supabase
|
|
.from('user_secrets')
|
|
.insert({
|
|
user_id: userId,
|
|
settings: { shipping_addresses: addresses }
|
|
});
|
|
|
|
if (error) throw error;
|
|
}
|
|
} catch (error) {
|
|
console.error('Error saving shipping addresses:', error);
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
// =============================================
|
|
// Vendor Profiles (stored in user_secrets.settings.vendor_profiles)
|
|
// =============================================
|
|
|
|
export interface VendorProfile {
|
|
id: string;
|
|
label: string;
|
|
companyName: string;
|
|
email: string;
|
|
phone: string;
|
|
vatId: string;
|
|
eoriNumber: string;
|
|
logoUrl: string;
|
|
address: string;
|
|
city: string;
|
|
zip: string;
|
|
country: string;
|
|
defaultCurrency: string;
|
|
defaultCountryOfOrigin: string;
|
|
note: string;
|
|
isDefault: boolean;
|
|
}
|
|
|
|
/** Get vendor profiles from user_secrets.settings.vendor_profiles */
|
|
export const getVendorProfiles = async (userId: string, client?: SupabaseClient): Promise<VendorProfile[]> => {
|
|
const supabase = client || defaultSupabase;
|
|
try {
|
|
const { data, error } = await supabase
|
|
.from('user_secrets')
|
|
.select('settings')
|
|
.eq('user_id', userId)
|
|
.maybeSingle();
|
|
|
|
if (error) throw error;
|
|
const settings = data?.settings as any;
|
|
return (settings?.vendor_profiles as VendorProfile[]) || [];
|
|
} catch (error) {
|
|
console.error('Error fetching vendor profiles:', error);
|
|
return [];
|
|
}
|
|
};
|
|
|
|
/** Save vendor profiles to user_secrets.settings.vendor_profiles (full replace) */
|
|
export const saveVendorProfiles = async (userId: string, profiles: VendorProfile[], client?: SupabaseClient): Promise<void> => {
|
|
const supabase = client || defaultSupabase;
|
|
try {
|
|
const { data: existing } = await supabase
|
|
.from('user_secrets')
|
|
.select('settings')
|
|
.eq('user_id', userId)
|
|
.maybeSingle();
|
|
|
|
if (existing) {
|
|
const currentSettings = (existing.settings as Record<string, any>) || {};
|
|
const newSettings = { ...currentSettings, vendor_profiles: profiles };
|
|
|
|
const { error } = await supabase
|
|
.from('user_secrets')
|
|
.update({ settings: newSettings })
|
|
.eq('user_id', userId);
|
|
|
|
if (error) throw error;
|
|
} else {
|
|
const { error } = await supabase
|
|
.from('user_secrets')
|
|
.insert({
|
|
user_id: userId,
|
|
settings: { vendor_profiles: profiles }
|
|
});
|
|
|
|
if (error) throw error;
|
|
}
|
|
} catch (error) {
|
|
console.error('Error saving vendor profiles:', error);
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
/** Update the current user's profile via API */
|
|
export const updateProfileAPI = async (profileData: {
|
|
username?: string | null;
|
|
display_name?: string | null;
|
|
bio?: string | null;
|
|
avatar_url?: string | null;
|
|
settings?: any;
|
|
}): Promise<any> => {
|
|
const { data: sessionData } = await defaultSupabase.auth.getSession();
|
|
const token = sessionData.session?.access_token;
|
|
if (!token) throw new Error('Not authenticated');
|
|
|
|
const res = await fetch('/api/profile', {
|
|
method: 'PATCH',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Authorization': `Bearer ${token}`
|
|
},
|
|
body: JSON.stringify(profileData)
|
|
});
|
|
if (!res.ok) throw new Error(`Failed to update profile: ${res.statusText}`);
|
|
return await res.json();
|
|
};
|
|
|
|
/** Update the current user's email (auth-level operation) */
|
|
export const updateUserEmail = async (newEmail: string): Promise<void> => {
|
|
const { error } = await defaultSupabase.auth.updateUser({ email: newEmail });
|
|
if (error) throw error;
|
|
};
|
|
|
|
// =============================================
|
|
// Admin User Management API Wrappers
|
|
// =============================================
|
|
|
|
const getAuthToken = async (): Promise<string> => {
|
|
const { data: sessionData } = await defaultSupabase.auth.getSession();
|
|
const token = sessionData.session?.access_token;
|
|
if (!token) throw new Error('Not authenticated');
|
|
return token;
|
|
};
|
|
|
|
/** Fetch all users (admin) */
|
|
export const fetchAdminUsersAPI = async (): Promise<any[]> => {
|
|
const token = await getAuthToken();
|
|
const res = await fetch('/api/admin/users', {
|
|
headers: { 'Authorization': `Bearer ${token}` }
|
|
});
|
|
if (!res.ok) throw new Error(`Failed to fetch users: ${res.statusText}`);
|
|
return await res.json();
|
|
};
|
|
|
|
/** Create a new user (admin) */
|
|
export const createAdminUserAPI = async (email: string, password: string, copySettings: boolean): Promise<any> => {
|
|
const token = await getAuthToken();
|
|
const res = await fetch('/api/admin/users', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Authorization': `Bearer ${token}`
|
|
},
|
|
body: JSON.stringify({ email, password, copySettings })
|
|
});
|
|
if (!res.ok) {
|
|
const data = await res.json().catch(() => ({}));
|
|
throw new Error(data.error || `Failed to create user: ${res.statusText}`);
|
|
}
|
|
return await res.json();
|
|
};
|
|
|
|
/** Update a user's profile (admin) */
|
|
export const updateAdminUserAPI = async (userId: string, profile: {
|
|
username?: string | null;
|
|
display_name?: string | null;
|
|
bio?: string | null;
|
|
}): Promise<any> => {
|
|
const token = await getAuthToken();
|
|
const res = await fetch(`/api/admin/users/${userId}`, {
|
|
method: 'PATCH',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Authorization': `Bearer ${token}`
|
|
},
|
|
body: JSON.stringify(profile)
|
|
});
|
|
if (!res.ok) throw new Error(`Failed to update user: ${res.statusText}`);
|
|
return await res.json();
|
|
};
|
|
|
|
/** Delete a user (admin) */
|
|
export const deleteAdminUserAPI = async (userId: string): Promise<void> => {
|
|
const token = await getAuthToken();
|
|
const res = await fetch(`/api/admin/users/${userId}`, {
|
|
method: 'DELETE',
|
|
headers: { 'Authorization': `Bearer ${token}` }
|
|
});
|
|
if (!res.ok) throw new Error(`Failed to delete user: ${res.statusText}`);
|
|
};
|