feat: move API keys storage to Postgres database instead of localStorage
Build and Deploy / build-and-push (push) Failing after 21s
Build and Deploy / build-and-push (push) Failing after 21s
This commit is contained in:
+8
-1
@@ -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
|
||||||
)
|
)
|
||||||
`);
|
`);
|
||||||
|
|
||||||
@@ -36,6 +37,12 @@ const initDB = async () => {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
// 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 (
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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,
|
||||||
onClose();
|
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;
|
if (!isOpen) return null;
|
||||||
|
|||||||
@@ -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
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user