diff --git a/backend/database.js b/backend/database.js index 0f312db..647b53d 100644 --- a/backend/database.js +++ b/backend/database.js @@ -25,7 +25,8 @@ const initDB = async () => { role TEXT DEFAULT 'user', api_credits INTEGER DEFAULT 10, storage_limit_mb INTEGER DEFAULT 500, - is_active BOOLEAN DEFAULT false + is_active BOOLEAN DEFAULT false, + api_keys TEXT ) `); @@ -36,6 +37,12 @@ const initDB = async () => { } catch (err) { // Ignore if it already exists } + + try { + await client.query(`ALTER TABLE profiles ADD COLUMN api_keys TEXT`); + } catch (err) { + // Ignore if it already exists + } await client.query(` CREATE TABLE IF NOT EXISTS projects ( diff --git a/backend/server.js b/backend/server.js index 514db41..84a6d22 100644 --- a/backend/server.js +++ b/backend/server.js @@ -116,6 +116,40 @@ app.get('/api/auth/me', authenticate, async (req, res) => { } }); +// 3.5. Get User API Keys +app.get('/api/user/keys', authenticate, async (req, res) => { + try { + const profile = await get('SELECT api_keys FROM profiles WHERE id = ?', [req.user.id]); + let keys = {}; + if (profile && profile.api_keys) { + try { + keys = JSON.parse(profile.api_keys); + } catch (e) { + // ignore invalid json + } + } + res.json({ keys }); + } catch (err) { + res.status(500).json({ error: err.message }); + } +}); + +// 3.6. Update User API Keys +app.put('/api/user/keys', authenticate, async (req, res) => { + const { keys } = req.body; + if (!keys || typeof keys !== 'object') { + return res.status(400).json({ error: 'Invalid keys object' }); + } + + try { + const keysStr = JSON.stringify(keys); + await run('UPDATE profiles SET api_keys = ? WHERE id = ?', [keysStr, req.user.id]); + res.json({ success: true }); + } catch (err) { + res.status(500).json({ error: err.message }); + } +}); + // 4. Get all projects for user app.get('/api/projects', authenticate, async (req, res) => { try { diff --git a/src/components/DashboardSettings.tsx b/src/components/DashboardSettings.tsx index 6b80481..33cdc61 100644 --- a/src/components/DashboardSettings.tsx +++ b/src/components/DashboardSettings.tsx @@ -2,6 +2,7 @@ import React, { useState, useEffect } from 'react'; import { motion, AnimatePresence } from 'framer-motion'; import { X, Settings, Key, Sparkles, Image as ImageIcon, Lightbulb, Box } from 'lucide-react'; import { cn } from '../lib/utils'; +import { api } from '../lib/api'; interface DashboardSettingsProps { isOpen: boolean; @@ -19,25 +20,37 @@ export const DashboardSettings: React.FC = ({ isOpen, on useEffect(() => { if (isOpen) { - setFalApiKey(localStorage.getItem('FAL_API_KEY') || ''); - setTripoApiKey(localStorage.getItem('TRIPO_API_KEY') || ''); - setMeshyApiKey(localStorage.getItem('MESHY_API_KEY') || ''); - setStabilityApiKey(localStorage.getItem('stability_api_key') || ''); - setOpenAiKey(localStorage.getItem('openai_api_key') || ''); - setGeminiApiKey(localStorage.getItem('GEMINI_API_KEY') || ''); - setNanoBananaApiKey(localStorage.getItem('NANO_BANANA_API_KEY') || ''); + api.getUserKeys().then(keys => { + setFalApiKey(keys.FAL_API_KEY || ''); + setTripoApiKey(keys.TRIPO_API_KEY || ''); + setMeshyApiKey(keys.MESHY_API_KEY || ''); + setStabilityApiKey(keys.stability_api_key || ''); + setOpenAiKey(keys.openai_api_key || ''); + setGeminiApiKey(keys.GEMINI_API_KEY || ''); + setNanoBananaApiKey(keys.NANO_BANANA_API_KEY || ''); + }).catch(err => { + console.error("Failed to load user keys", err); + }); } }, [isOpen]); - const saveKeys = () => { - localStorage.setItem('FAL_API_KEY', falApiKey); - localStorage.setItem('TRIPO_API_KEY', tripoApiKey); - localStorage.setItem('MESHY_API_KEY', meshyApiKey); - localStorage.setItem('stability_api_key', stabilityApiKey); - localStorage.setItem('openai_api_key', openAiKey); - localStorage.setItem('GEMINI_API_KEY', geminiApiKey); - localStorage.setItem('NANO_BANANA_API_KEY', nanoBananaApiKey); - onClose(); + const saveKeys = async () => { + const keys = { + FAL_API_KEY: falApiKey, + TRIPO_API_KEY: tripoApiKey, + MESHY_API_KEY: meshyApiKey, + stability_api_key: stabilityApiKey, + openai_api_key: openAiKey, + GEMINI_API_KEY: geminiApiKey, + NANO_BANANA_API_KEY: nanoBananaApiKey + }; + try { + await api.saveUserKeys(keys); + onClose(); + } catch (err) { + console.error("Failed to save keys", err); + alert("Failed to save API keys to database"); + } }; if (!isOpen) return null; diff --git a/src/lib/api.ts b/src/lib/api.ts index b779fd1..1508a8a 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -39,6 +39,25 @@ export const api = { return data; }, + // API Keys + getUserKeys: async () => { + const res = await fetch(`${API_URL}/user/keys`, { headers: getHeaders() }); + const data = await res.json(); + if (!res.ok) throw new Error(data.error); + return data.keys || {}; + }, + + saveUserKeys: async (keys) => { + const res = await fetch(`${API_URL}/user/keys`, { + method: 'PUT', + headers: getHeaders(), + body: JSON.stringify({ keys }) + }); + const data = await res.json(); + if (!res.ok) throw new Error(data.error); + return data; + }, + // Projects getProjects: async () => { const res = await fetch(`${API_URL}/projects`, { headers: getHeaders() }); diff --git a/src/pages/SparkPlug.tsx b/src/pages/SparkPlug.tsx index fb0ea86..ce7acba 100644 --- a/src/pages/SparkPlug.tsx +++ b/src/pages/SparkPlug.tsx @@ -40,8 +40,10 @@ export default function SparkPlug() { setIsLoading(true); try { - // Simulate reading PDF with Gemini - const geminiKey = localStorage.getItem('GEMINI_API_KEY'); + // Validate keys + const keys = await api.getUserKeys(); + const geminiKey = keys.GEMINI_API_KEY; + if (!geminiKey) { alert("Please configure your Gemini API Key in the Dashboard Settings first."); setIsLoading(false); @@ -69,7 +71,10 @@ export default function SparkPlug() { setIsLoading(true); try { - const nanoBananaKey = localStorage.getItem('NANO_BANANA_API_KEY'); + // Validate keys + const keys = await api.getUserKeys(); + const nanoBananaKey = keys.NANO_BANANA_API_KEY; + if (!nanoBananaKey) { alert("Please configure your Nano Banana API Key in the Dashboard Settings first."); setIsLoading(false); @@ -102,7 +107,10 @@ export default function SparkPlug() { setIsLoading(true); try { - const meshyKey = localStorage.getItem('MESHY_API_KEY'); + // Validate keys + const keys = await api.getUserKeys(); + const meshyKey = keys.MESHY_API_KEY; + if (!meshyKey) { alert("Please configure your Meshy API Key in the Dashboard Settings first."); setIsLoading(false);