Spaces:
Runtime error
Runtime error
| import { createClient } from '@supabase/supabase-js'; | |
| import { SUPABASE_URL, SUPABASE_ANON_KEY } from './config.js'; | |
| import { isPostgresStorageMode } from './dataPaths.js'; | |
| import { | |
| decryptJsonPayload, | |
| encryptJsonPayload, | |
| makeLookupToken, | |
| makeOwnerLookup, | |
| pgQuery, | |
| } from './postgres.js'; | |
| const supabaseAnon = createClient(SUPABASE_URL, SUPABASE_ANON_KEY); | |
| function userClient(accessToken) { | |
| return createClient(SUPABASE_URL, SUPABASE_ANON_KEY, { | |
| global: { headers: { Authorization: `Bearer ${accessToken}` } }, | |
| auth: { persistSession: false }, | |
| }); | |
| } | |
| export async function verifySupabaseToken(accessToken) { | |
| if (!accessToken) return null; | |
| try { | |
| const { data, error } = await supabaseAnon.auth.getUser(accessToken); | |
| if (error || !data?.user) return null; | |
| return data.user; | |
| } catch { return null; } | |
| } | |
| export async function getUserSettings(userId, accessToken) { | |
| if (isPostgresStorageMode()) { | |
| const { rows } = await pgQuery( | |
| 'SELECT payload FROM user_settings WHERE owner_lookup = $1', | |
| [makeOwnerLookup({ type: 'user', id: userId })] | |
| ); | |
| const payload = rows[0] | |
| ? decryptJsonPayload(rows[0].payload, `user-settings:${userId}`) | |
| : null; | |
| return { ...defaultSettings(), ...(payload?.settings || {}) }; | |
| } | |
| try { | |
| const uc = userClient(accessToken); | |
| const { data } = await uc.from('user_settings').select('settings') | |
| .eq('user_id', userId).single(); | |
| return { ...defaultSettings(), ...(data?.settings || {}) }; | |
| } catch { return defaultSettings(); } | |
| } | |
| export async function saveUserSettings(userId, accessToken, settings) { | |
| if (isPostgresStorageMode()) { | |
| try { | |
| const payload = { | |
| userId, | |
| settings, | |
| updatedAt: new Date().toISOString(), | |
| }; | |
| await pgQuery( | |
| `INSERT INTO user_settings (owner_lookup, updated_at, payload) | |
| VALUES ($1, $2, $3::jsonb) | |
| ON CONFLICT (owner_lookup) | |
| DO UPDATE SET updated_at = EXCLUDED.updated_at, payload = EXCLUDED.payload`, | |
| [ | |
| makeOwnerLookup({ type: 'user', id: userId }), | |
| payload.updatedAt, | |
| JSON.stringify(encryptJsonPayload(payload, `user-settings:${userId}`)), | |
| ] | |
| ); | |
| return; | |
| } catch (e) { | |
| console.error('saveUserSettings', e.message); | |
| return; | |
| } | |
| } | |
| try { | |
| const uc = userClient(accessToken); | |
| await uc.from('user_settings').upsert({ | |
| user_id: userId, settings, updated_at: new Date().toISOString(), | |
| }); | |
| } catch (e) { console.error('saveUserSettings', e.message); } | |
| } | |
| export async function getUserProfile(userId, accessToken) { | |
| if (isPostgresStorageMode()) { | |
| try { | |
| const { rows } = await pgQuery( | |
| 'SELECT payload FROM user_profiles WHERE owner_lookup = $1', | |
| [makeOwnerLookup({ type: 'user', id: userId })] | |
| ); | |
| const payload = rows[0] | |
| ? decryptJsonPayload(rows[0].payload, `user-profile:${userId}`) | |
| : null; | |
| return payload?.username ? { username: payload.username } : null; | |
| } catch { return null; } | |
| } | |
| try { | |
| const uc = userClient(accessToken); | |
| const { data } = await uc.from('profiles').select('username') | |
| .eq('id', userId).maybeSingle(); | |
| return data || null; | |
| } catch { return null; } | |
| } | |
| export async function setUsername(userId, accessToken, username) { | |
| if (!username?.trim()) return { error: 'Invalid username' }; | |
| const trimmed = username.trim().toLowerCase().replace(/[^a-z0-9_]/g, ''); | |
| if (trimmed.length < 3) return { error: 'Username must be at least 3 characters' }; | |
| if (isPostgresStorageMode()) { | |
| try { | |
| const usernameLookup = makeLookupToken('username', trimmed); | |
| const ownerLookup = makeOwnerLookup({ type: 'user', id: userId }); | |
| const { rows: existingRows } = await pgQuery( | |
| 'SELECT owner_lookup FROM user_profiles WHERE username_lookup = $1', | |
| [usernameLookup] | |
| ); | |
| if (existingRows[0] && existingRows[0].owner_lookup !== ownerLookup) { | |
| return { error: 'Username already taken' }; | |
| } | |
| const payload = { | |
| userId, | |
| username: trimmed, | |
| updatedAt: new Date().toISOString(), | |
| }; | |
| await pgQuery( | |
| `INSERT INTO user_profiles (owner_lookup, username_lookup, updated_at, payload) | |
| VALUES ($1, $2, $3, $4::jsonb) | |
| ON CONFLICT (owner_lookup) | |
| DO UPDATE SET username_lookup = EXCLUDED.username_lookup, updated_at = EXCLUDED.updated_at, payload = EXCLUDED.payload`, | |
| [ | |
| ownerLookup, | |
| usernameLookup, | |
| payload.updatedAt, | |
| JSON.stringify(encryptJsonPayload(payload, `user-profile:${userId}`)), | |
| ] | |
| ); | |
| return { success: true, username: trimmed }; | |
| } catch (e) { return { error: e.message }; } | |
| } | |
| try { | |
| const uc = userClient(accessToken); | |
| const { data: existing } = await uc.from('profiles') | |
| .select('id').eq('username', trimmed).maybeSingle(); | |
| if (existing) return { error: 'Username already taken' }; | |
| await uc.from('profiles').upsert({ id: userId, username: trimmed }, { onConflict: 'id' }); | |
| return { success: true, username: trimmed }; | |
| } catch (e) { return { error: e.message }; } | |
| } | |
| export async function getSubscriptionInfo(accessToken) { | |
| try { | |
| const r = await fetch('https://sharktide-lightning.hf.space/subscription', { | |
| headers: { Authorization: `Bearer ${accessToken}`, Accept: 'application/json' }, | |
| }); | |
| if (!r.ok) { | |
| console.warn(`[Subscription] Failed: HTTP ${r.status}`); | |
| return null; | |
| } | |
| const data = await r.json(); | |
| // Normalize snake_case keys from HF Space to camelCase | |
| const normalized = { | |
| planKey: data.plan_key || null, | |
| planName: data.plan_name || null, | |
| email: data.email, | |
| signedUp: data.signed_up, | |
| subscription: data.subscription, | |
| }; | |
| return normalized; | |
| } catch (err) { | |
| console.error('[Subscription] Error fetching subscription info:', err.message); | |
| return null; | |
| } | |
| } | |
| export async function getTierConfig() { | |
| try { | |
| const r = await fetch('https://sharktide-lightning.hf.space/tier-config', | |
| { headers: { Accept: 'application/json' } }); | |
| return r.ok ? r.json() : null; | |
| } catch { return null; } | |
| } | |
| export async function getUsageInfo(accessToken, clientId = '') { | |
| try { | |
| const h = { Accept: 'application/json' }; | |
| if (accessToken) h.Authorization = `Bearer ${accessToken}`; | |
| if (clientId) h['X-Client-ID'] = clientId; | |
| const r = await fetch('https://sharktide-lightning.hf.space/usage', { headers: h }); | |
| const payload = r.ok ? await r.json() : null; | |
| return payload; | |
| } catch (err) { | |
| console.error('[Usage API] request failed:', err.message); | |
| return null; | |
| } | |
| } | |
| function defaultSettings() { | |
| return { theme: 'dark', webSearch: true, imageGen: true, videoGen: true, audioGen: true }; | |
| } | |