Spaces:
Sleeping
Sleeping
Implement dynamic runtime configuration to fetch Supabase secrets from backend
Browse files- Dockerfile +0 -10
- algospaced-ui/src/main.tsx +24 -5
- algospaced-ui/src/supabaseClient.ts +23 -3
- main.py +7 -0
Dockerfile
CHANGED
|
@@ -2,16 +2,6 @@
|
|
| 2 |
FROM node:20-alpine AS frontend-builder
|
| 3 |
WORKDIR /app/frontend
|
| 4 |
|
| 5 |
-
# Declare build arguments (Hugging Face passes secrets as build args)
|
| 6 |
-
ARG VITE_SUPABASE_URL
|
| 7 |
-
ARG VITE_SUPABASE_ANON_KEY
|
| 8 |
-
ARG SUPABASE_URL
|
| 9 |
-
ARG SUPABASE_KEY
|
| 10 |
-
|
| 11 |
-
# Expose them to Vite compilation environment, with fallback to Streamlit secret names
|
| 12 |
-
ENV VITE_SUPABASE_URL=${VITE_SUPABASE_URL:-$SUPABASE_URL}
|
| 13 |
-
ENV VITE_SUPABASE_ANON_KEY=${VITE_SUPABASE_ANON_KEY:-$SUPABASE_KEY}
|
| 14 |
-
|
| 15 |
|
| 16 |
# Copy package files and install dependencies
|
| 17 |
COPY algospaced-ui/package*.json ./
|
|
|
|
| 2 |
FROM node:20-alpine AS frontend-builder
|
| 3 |
WORKDIR /app/frontend
|
| 4 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
|
| 6 |
# Copy package files and install dependencies
|
| 7 |
COPY algospaced-ui/package*.json ./
|
algospaced-ui/src/main.tsx
CHANGED
|
@@ -3,8 +3,27 @@ import { createRoot } from 'react-dom/client'
|
|
| 3 |
import './index.css'
|
| 4 |
import App from './App.tsx'
|
| 5 |
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
import './index.css'
|
| 4 |
import App from './App.tsx'
|
| 5 |
|
| 6 |
+
import { initializeSupabase } from './supabaseClient'
|
| 7 |
+
|
| 8 |
+
async function init() {
|
| 9 |
+
try {
|
| 10 |
+
const apiBase = import.meta.env.VITE_API_BASE_URL || '';
|
| 11 |
+
const res = await fetch(`${apiBase}/api/config`);
|
| 12 |
+
if (!res.ok) throw new Error("Failed to load runtime configuration");
|
| 13 |
+
const config = await res.json();
|
| 14 |
+
initializeSupabase(config.supabaseUrl, config.supabaseAnonKey);
|
| 15 |
+
} catch (err) {
|
| 16 |
+
console.error("Failed to load config from API, falling back to build environment:", err);
|
| 17 |
+
const url = import.meta.env.VITE_SUPABASE_URL || '';
|
| 18 |
+
const key = import.meta.env.VITE_SUPABASE_ANON_KEY || '';
|
| 19 |
+
initializeSupabase(url, key);
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
createRoot(document.getElementById('root')!).render(
|
| 23 |
+
<StrictMode>
|
| 24 |
+
<App />
|
| 25 |
+
</StrictMode>,
|
| 26 |
+
)
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
init();
|
algospaced-ui/src/supabaseClient.ts
CHANGED
|
@@ -1,7 +1,27 @@
|
|
| 1 |
import { createClient } from '@supabase/supabase-js';
|
| 2 |
|
| 3 |
-
|
| 4 |
-
const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY || '';
|
| 5 |
|
| 6 |
-
export
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
|
|
|
|
| 1 |
import { createClient } from '@supabase/supabase-js';
|
| 2 |
|
| 3 |
+
let supabaseInstance: any = null;
|
|
|
|
| 4 |
|
| 5 |
+
export function initializeSupabase(url: string, key: string) {
|
| 6 |
+
if (url && key) {
|
| 7 |
+
supabaseInstance = createClient(url, key);
|
| 8 |
+
}
|
| 9 |
+
}
|
| 10 |
+
|
| 11 |
+
// Proxy wrapper that delegates all calls to the dynamically initialized instance
|
| 12 |
+
export const supabase = new Proxy({} as any, {
|
| 13 |
+
get(target, prop) {
|
| 14 |
+
if (!supabaseInstance) {
|
| 15 |
+
// Fallback: try loading from build-time environment if not initialized yet
|
| 16 |
+
const url = import.meta.env.VITE_SUPABASE_URL || '';
|
| 17 |
+
const key = import.meta.env.VITE_SUPABASE_ANON_KEY || '';
|
| 18 |
+
if (url && key) {
|
| 19 |
+
supabaseInstance = createClient(url, key);
|
| 20 |
+
} else {
|
| 21 |
+
throw new Error("Supabase client is not initialized. Please call initializeSupabase(url, key) first.");
|
| 22 |
+
}
|
| 23 |
+
}
|
| 24 |
+
return supabaseInstance[prop];
|
| 25 |
+
}
|
| 26 |
+
});
|
| 27 |
|
main.py
CHANGED
|
@@ -119,6 +119,13 @@ def get_supabase_client(authorization: str = Header(None)) -> Client:
|
|
| 119 |
except Exception as e:
|
| 120 |
raise HTTPException(status_code=401, detail=f"Authentication failed: {str(e)}")
|
| 121 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 122 |
@app.get("/api/streak")
|
| 123 |
def get_streak(client: Client = Depends(get_supabase_client)):
|
| 124 |
streak_data = db.get_active_streak(client)
|
|
|
|
| 119 |
except Exception as e:
|
| 120 |
raise HTTPException(status_code=401, detail=f"Authentication failed: {str(e)}")
|
| 121 |
|
| 122 |
+
@app.get("/api/config")
|
| 123 |
+
def get_public_config():
|
| 124 |
+
return {
|
| 125 |
+
"supabaseUrl": SUPABASE_URL,
|
| 126 |
+
"supabaseAnonKey": SUPABASE_KEY
|
| 127 |
+
}
|
| 128 |
+
|
| 129 |
@app.get("/api/streak")
|
| 130 |
def get_streak(client: Client = Depends(get_supabase_client)):
|
| 131 |
streak_data = db.get_active_streak(client)
|