feat: move API keys storage to Postgres database instead of localStorage
Build and Deploy / build-and-push (push) Failing after 21s

This commit is contained in:
AI Bot
2026-09-02 12:16:32 +05:30
parent abb818fecc
commit 658ca9e571
5 changed files with 102 additions and 21 deletions
+8 -1
View File
@@ -25,7 +25,8 @@ const initDB = async () => {
role TEXT DEFAULT 'user', role TEXT DEFAULT 'user',
api_credits INTEGER DEFAULT 10, api_credits INTEGER DEFAULT 10,
storage_limit_mb INTEGER DEFAULT 500, storage_limit_mb INTEGER DEFAULT 500,
is_active BOOLEAN DEFAULT false is_active BOOLEAN DEFAULT false,
api_keys TEXT
) )
`); `);
@@ -37,6 +38,12 @@ const initDB = async () => {
// Ignore if it already exists // 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(` await client.query(`
CREATE TABLE IF NOT EXISTS projects ( CREATE TABLE IF NOT EXISTS projects (
id TEXT PRIMARY KEY, id TEXT PRIMARY KEY,
+34
View File
@@ -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 // 4. Get all projects for user
app.get('/api/projects', authenticate, async (req, res) => { app.get('/api/projects', authenticate, async (req, res) => {
try { try {
+28 -15
View File
@@ -2,6 +2,7 @@ import React, { useState, useEffect } from 'react';
import { motion, AnimatePresence } from 'framer-motion'; import { motion, AnimatePresence } from 'framer-motion';
import { X, Settings, Key, Sparkles, Image as ImageIcon, Lightbulb, Box } from 'lucide-react'; import { X, Settings, Key, Sparkles, Image as ImageIcon, Lightbulb, Box } from 'lucide-react';
import { cn } from '../lib/utils'; import { cn } from '../lib/utils';
import { api } from '../lib/api';
interface DashboardSettingsProps { interface DashboardSettingsProps {
isOpen: boolean; isOpen: boolean;
@@ -19,25 +20,37 @@ export const DashboardSettings: React.FC<DashboardSettingsProps> = ({ isOpen, on
useEffect(() => { useEffect(() => {
if (isOpen) { if (isOpen) {
setFalApiKey(localStorage.getItem('FAL_API_KEY') || ''); api.getUserKeys().then(keys => {
setTripoApiKey(localStorage.getItem('TRIPO_API_KEY') || ''); setFalApiKey(keys.FAL_API_KEY || '');
setMeshyApiKey(localStorage.getItem('MESHY_API_KEY') || ''); setTripoApiKey(keys.TRIPO_API_KEY || '');
setStabilityApiKey(localStorage.getItem('stability_api_key') || ''); setMeshyApiKey(keys.MESHY_API_KEY || '');
setOpenAiKey(localStorage.getItem('openai_api_key') || ''); setStabilityApiKey(keys.stability_api_key || '');
setGeminiApiKey(localStorage.getItem('GEMINI_API_KEY') || ''); setOpenAiKey(keys.openai_api_key || '');
setNanoBananaApiKey(localStorage.getItem('NANO_BANANA_API_KEY') || ''); setGeminiApiKey(keys.GEMINI_API_KEY || '');
setNanoBananaApiKey(keys.NANO_BANANA_API_KEY || '');
}).catch(err => {
console.error("Failed to load user keys", err);
});
} }
}, [isOpen]); }, [isOpen]);
const saveKeys = () => { const saveKeys = async () => {
localStorage.setItem('FAL_API_KEY', falApiKey); const keys = {
localStorage.setItem('TRIPO_API_KEY', tripoApiKey); FAL_API_KEY: falApiKey,
localStorage.setItem('MESHY_API_KEY', meshyApiKey); TRIPO_API_KEY: tripoApiKey,
localStorage.setItem('stability_api_key', stabilityApiKey); MESHY_API_KEY: meshyApiKey,
localStorage.setItem('openai_api_key', openAiKey); stability_api_key: stabilityApiKey,
localStorage.setItem('GEMINI_API_KEY', geminiApiKey); openai_api_key: openAiKey,
localStorage.setItem('NANO_BANANA_API_KEY', nanoBananaApiKey); GEMINI_API_KEY: geminiApiKey,
NANO_BANANA_API_KEY: nanoBananaApiKey
};
try {
await api.saveUserKeys(keys);
onClose(); onClose();
} catch (err) {
console.error("Failed to save keys", err);
alert("Failed to save API keys to database");
}
}; };
if (!isOpen) return null; if (!isOpen) return null;
+19
View File
@@ -39,6 +39,25 @@ export const api = {
return data; 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 // Projects
getProjects: async () => { getProjects: async () => {
const res = await fetch(`${API_URL}/projects`, { headers: getHeaders() }); const res = await fetch(`${API_URL}/projects`, { headers: getHeaders() });
+12 -4
View File
@@ -40,8 +40,10 @@ export default function SparkPlug() {
setIsLoading(true); setIsLoading(true);
try { try {
// Simulate reading PDF with Gemini // Validate keys
const geminiKey = localStorage.getItem('GEMINI_API_KEY'); const keys = await api.getUserKeys();
const geminiKey = keys.GEMINI_API_KEY;
if (!geminiKey) { if (!geminiKey) {
alert("Please configure your Gemini API Key in the Dashboard Settings first."); alert("Please configure your Gemini API Key in the Dashboard Settings first.");
setIsLoading(false); setIsLoading(false);
@@ -69,7 +71,10 @@ export default function SparkPlug() {
setIsLoading(true); setIsLoading(true);
try { 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) { if (!nanoBananaKey) {
alert("Please configure your Nano Banana API Key in the Dashboard Settings first."); alert("Please configure your Nano Banana API Key in the Dashboard Settings first.");
setIsLoading(false); setIsLoading(false);
@@ -102,7 +107,10 @@ export default function SparkPlug() {
setIsLoading(true); setIsLoading(true);
try { try {
const meshyKey = localStorage.getItem('MESHY_API_KEY'); // Validate keys
const keys = await api.getUserKeys();
const meshyKey = keys.MESHY_API_KEY;
if (!meshyKey) { if (!meshyKey) {
alert("Please configure your Meshy API Key in the Dashboard Settings first."); alert("Please configure your Meshy API Key in the Dashboard Settings first.");
setIsLoading(false); setIsLoading(false);