diff --git a/backend/src/routes/apolloApiKey.ts b/backend/src/routes/apolloApiKey.ts index e76a6b6..3cd8c97 100644 --- a/backend/src/routes/apolloApiKey.ts +++ b/backend/src/routes/apolloApiKey.ts @@ -1,83 +1,91 @@ -import express, {Response} from 'express'; +import express, {Request, Response} from 'express'; +import {SessionRequest} from 'supertokens-node/framework/express'; +import {ApolloApiKey} from '../models/apolloApiKey'; import {DI} from '../server'; import {logger} from '../utilities/logger'; -import {ApolloApiKey} from '../models/apolloApiKey'; -import {SessionRequest} from 'supertokens-node/framework/express'; const router = express.Router(); -router.get('/apollo-api-key', async (req: SessionRequest, res: Response) => { - try { - const userId = req.session?.getUserId(); - if (!userId) { - return res.status(401).json({error: 'Unauthorized'}); - } +const handleRequest = async ( + req: Request | SessionRequest, + defaultUserId = 'anonymous' +) => { + const userId = (req as SessionRequest).session?.getUserId() || defaultUserId; + return userId; +}; + +router.get( + '/apollo-api-key', + async (req: Request | SessionRequest, res: Response) => { + try { + const userId = await handleRequest(req); + const apiKey = await DI.apolloApiKeys.findOne({userId}); - const apiKey = await DI.apolloApiKeys.findOne({userId}); - if (apiKey) { - const decryptedKey = apiKey.getDecryptedKey(); - const obfuscatedKey = `${decryptedKey.slice(0, 4)}****${decryptedKey.slice(-4)}`; - res.json({key: obfuscatedKey}); - } else { - res.json({key: null}); + if (apiKey) { + const decryptedKey = apiKey.getDecryptedKey(); + const obfuscatedKey = `${decryptedKey.slice(0, 4)}****${decryptedKey.slice(-4)}`; + res.json({key: obfuscatedKey}); + } else { + res.json({key: null}); + } + } catch (error) { + logger.error('Failed to fetch API key', {error}); + res.status(500).json({error: 'Failed to fetch API key'}); } - } catch (error) { - logger.error('Failed to fetch API key', {error}); - res.status(500).json({error: 'Failed to fetch API key'}); } -}); +); -router.post('/apollo-api-key', async (req: SessionRequest, res: Response) => { - try { - const userId = req.session?.getUserId(); - if (!userId) { - return res.status(401).json({error: 'Unauthorized'}); - } +router.post( + '/apollo-api-key', + async (req: Request | SessionRequest, res: Response) => { + try { + const userId = await handleRequest(req); + const {key} = req.body; - const {key} = req.body; - if (!key) { - return res.status(400).json({error: 'API key is required'}); - } + if (!key) { + return res.status(400).json({error: 'API key is required'}); + } - let apiKey = await DI.apolloApiKeys.findOne({userId}); - if (apiKey) { - const newApiKey = new ApolloApiKey(key, userId); - apiKey.encryptedKey = newApiKey.encryptedKey; - apiKey.iv = newApiKey.iv; - apiKey.tag = newApiKey.tag; - } else { - apiKey = new ApolloApiKey(key, userId); - DI.em.persist(apiKey); - } + let apiKey = await DI.apolloApiKeys.findOne({userId}); + if (apiKey) { + const newApiKey = new ApolloApiKey(key, userId); + apiKey.encryptedKey = newApiKey.encryptedKey; + apiKey.iv = newApiKey.iv; + apiKey.tag = newApiKey.tag; + } else { + apiKey = new ApolloApiKey(key, userId); + DI.em.persist(apiKey); + } - await DI.em.flush(); - await DI.apolloClient.updateApiKey(key, userId); - res.status(200).json({message: 'API key saved successfully'}); - } catch (error) { - logger.error('Failed to save API key', {error}); - res.status(500).json({error: 'Failed to save API key'}); + await DI.em.flush(); + await DI.apolloClient.updateApiKey(key, userId); + res.status(200).json({message: 'API key saved successfully'}); + } catch (error) { + logger.error('Failed to save API key', {error}); + res.status(500).json({error: 'Failed to save API key'}); + } } -}); +); -router.delete('/apollo-api-key', async (req: SessionRequest, res: Response) => { - try { - const userId = req.session?.getUserId(); - if (!userId) { - return res.status(401).json({error: 'Unauthorized'}); - } +router.delete( + '/apollo-api-key', + async (req: Request | SessionRequest, res: Response) => { + try { + const userId = await handleRequest(req); + const apiKey = await DI.apolloApiKeys.findOne({userId}); - const apiKey = await DI.apolloApiKeys.findOne({userId}); - if (!apiKey) { - return res.status(404).json({error: 'API key not found'}); - } + if (!apiKey) { + return res.status(404).json({error: 'API key not found'}); + } - await DI.em.remove(apiKey).flush(); - await DI.apolloClient.updateApiKey('', userId); - res.status(200).json({message: 'API key deleted successfully'}); - } catch (error) { - logger.error('Failed to delete API key', {error}); - res.status(500).json({error: 'Failed to delete API key'}); + await DI.em.remove(apiKey).flush(); + await DI.apolloClient.updateApiKey('', userId); + res.status(200).json({message: 'API key deleted successfully'}); + } catch (error) { + logger.error('Failed to delete API key', {error}); + res.status(500).json({error: 'Failed to delete API key'}); + } } -}); +); export default router; diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 43a62b6..ec55a24 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "frontend", - "version": "1.0.0-beta.2", + "version": "1.0.0-beta.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "frontend", - "version": "1.0.0-beta.2", + "version": "1.0.0-beta.3", "dependencies": { "@apollo/explorer": "^3.7.0", "@apollo/sandbox": "^2.6.0", diff --git a/frontend/src/components/ui/home.tsx b/frontend/src/components/ui/home.tsx index c7d2d96..0957365 100644 --- a/frontend/src/components/ui/home.tsx +++ b/frontend/src/components/ui/home.tsx @@ -81,6 +81,8 @@ import {Tabs, TabsContent, TabsList, TabsTrigger} from './tabs'; import {Textarea} from './textarea'; import {Toaster} from './toaster'; import {toast} from './use-toast'; +import {Badge} from './badge'; +import {SettingsBadge} from './settings-badge'; const Home = () => { const navigate = useNavigate(); @@ -117,6 +119,17 @@ const Home = () => { const serverBaseUrl = getApiBaseUrl(); + const [hasApolloKey, setHasApolloKey] = useState(true); + + useEffect(() => { + fetch(`${serverBaseUrl}/api/apollo-api-key`) + .then((response) => response.json()) + .then((data) => { + setHasApolloKey(!!data.key); + }) + .catch(() => setHasApolloKey(false)); + }, [serverBaseUrl]); + const handleSettingsClick = () => navigate('/settings'); const handleCreateSeedClick = () => { populateSeedForm(); @@ -586,8 +599,8 @@ const Home = () => {
- diff --git a/frontend/src/components/ui/settings-badge.tsx b/frontend/src/components/ui/settings-badge.tsx new file mode 100644 index 0000000..9149a74 --- /dev/null +++ b/frontend/src/components/ui/settings-badge.tsx @@ -0,0 +1,51 @@ +import {cn} from '../../lib/utils'; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from './tooltip'; +import {Settings} from 'lucide-react'; + +interface SettingsBadgeProps { + hasApolloKey: boolean; + onClick: () => void; +} + +export function SettingsBadge({hasApolloKey, onClick}: SettingsBadgeProps) { + return ( + + + +
+ + {!hasApolloKey && ( + + )} +
+
+ +

+ Connect to Apollo Studio + + Enter your user-scoped Apollo API key to instantly build custom + mock seeds against existing supergraph variants and schema + proposals + +

+
+
+
+ ); +} diff --git a/frontend/src/components/ui/settings.tsx b/frontend/src/components/ui/settings.tsx index 55a3c5b..137e287 100644 --- a/frontend/src/components/ui/settings.tsx +++ b/frontend/src/components/ui/settings.tsx @@ -14,6 +14,7 @@ import { } from './card'; import {Input} from './input'; import {useToast} from './use-toast'; +import {useNavigate} from 'react-router-dom'; interface ApiKey { id: string; @@ -28,6 +29,7 @@ export default function SettingsPage() { const [isLoading, setIsLoading] = useState(false); const {toast} = useToast(); const serverBaseUrl = getApiBaseUrl(); + const navigate = useNavigate(); useEffect(() => { fetchApiKey(); @@ -65,6 +67,7 @@ export default function SettingsPage() { title: 'Apollo API Key Saved', description: 'Your Apollo API key has been saved successfully.', }); + navigate('/'); } }); }; diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js index 47739fa..551bcd1 100644 --- a/frontend/tailwind.config.js +++ b/frontend/tailwind.config.js @@ -82,7 +82,11 @@ module.exports = { to: { height: '0' } - } + }, + pulse: { + '0%, 100%': { opacity: '0.7', transform: 'scale(1)' }, + '50%': { opacity: '0.3', transform: 'scale(0.95)' }, + }, }, animation: { 'accordion-down': 'accordion-down 0.2s ease-out',