Skip to content

Commit f897a5f

Browse files
committed
feat: Add settings page for connection management, initial backend application, API reference, and onboarding pages.
1 parent 9bfc9e9 commit f897a5f

File tree

6 files changed

+315
-852
lines changed

6 files changed

+315
-852
lines changed

backend/app.py

Lines changed: 48 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -407,14 +407,13 @@ def linkedin_callback(code: str = None, state: str = None, redirect_uri: str = N
407407

408408

409409
# User settings endpoints
410+
# SECURITY: Only safe fields are accepted from frontend
410411
class UserSettingsRequest(BaseModel):
411412
user_id: str
412-
linkedin_client_id: Optional[str] = None
413-
linkedin_client_secret: Optional[str] = None
414-
linkedin_user_urn: Optional[str] = None
415-
groq_api_key: Optional[str] = None
416413
github_username: Optional[str] = None
417-
unsplash_access_key: Optional[str] = None
414+
onboarding_complete: Optional[bool] = None
415+
# NOTE: API keys are managed server-side via environment variables
416+
# Frontend NEVER sends credentials to this endpoint
418417

419418

420419
class AuthRefreshRequest(BaseModel):
@@ -454,40 +453,66 @@ def save_settings(settings: UserSettingsRequest):
454453

455454
@app.get("/api/settings/{user_id}")
456455
def get_settings(user_id: str):
457-
"""Get user settings by user ID"""
456+
"""
457+
Get user settings by user ID.
458+
459+
SECURITY: Returns ONLY safe, non-sensitive data.
460+
No credentials, tokens, or API keys are returned.
461+
"""
458462
if not get_user_settings:
459463
return {"error": "User settings service not available"}
460464
try:
461465
settings = get_user_settings(user_id)
462466
if settings:
463-
# Return masked versions of secrets so frontend knows they're saved
464-
# but doesn't expose the actual values
465-
groq_key = settings.get("groq_api_key")
466-
unsplash_key = settings.get("unsplash_access_key")
467-
linkedin_id = settings.get("linkedin_client_id")
468-
linkedin_secret = settings.get("linkedin_client_secret")
469-
467+
# SECURITY: Only return safe data, no credentials
470468
return {
471469
"user_id": settings.get("user_id"),
472470
"github_username": settings.get("github_username") or "",
473-
# Return masked versions for display
474-
"groq_api_key": f"{groq_key[:8]}...{groq_key[-4:]}" if groq_key and len(groq_key) > 12 else ("••••••••" if groq_key else ""),
475-
"unsplash_access_key": f"{unsplash_key[:8]}...{unsplash_key[-4:]}" if unsplash_key and len(unsplash_key) > 12 else ("••••••••" if unsplash_key else ""),
476-
"linkedin_client_id": linkedin_id or "",
477-
"linkedin_client_secret": "••••••••" if linkedin_secret else "",
478-
# Also keep the boolean flags for compatibility
479-
"has_linkedin": bool(linkedin_id),
480-
"has_groq": bool(groq_key),
481-
"has_unsplash": bool(unsplash_key),
471+
"onboarding_complete": settings.get("onboarding_complete", False),
482472
"subscription_tier": settings.get("subscription_tier") or "free",
483473
"subscription_status": settings.get("subscription_status") or "active",
484-
"subscription_expires_at": settings.get("subscription_expires_at"),
485474
}
486475
return {"error": "User not found"}
487476
except Exception as e:
488477
return {"error": str(e)}
489478

490479

480+
@app.get("/api/connection-status/{user_id}")
481+
def get_connection_status_endpoint(user_id: str):
482+
"""
483+
Get connection status for a user.
484+
485+
SECURITY: Returns ONLY boolean status and public identifiers.
486+
No tokens or credentials are ever returned.
487+
"""
488+
try:
489+
# Import get_connection_status from token_store
490+
from services.token_store import get_connection_status
491+
492+
status = get_connection_status(user_id)
493+
494+
# Also get github_username from settings
495+
github_username = ''
496+
if get_user_settings:
497+
settings = get_user_settings(user_id)
498+
if settings:
499+
github_username = settings.get('github_username', '')
500+
501+
return {
502+
"linkedin_connected": status.get("linkedin_connected", False),
503+
"linkedin_urn": status.get("linkedin_urn", ""),
504+
"github_connected": bool(github_username),
505+
"github_username": github_username,
506+
"token_expires_at": status.get("token_expires_at"),
507+
}
508+
except Exception as e:
509+
return {
510+
"linkedin_connected": False,
511+
"github_connected": False,
512+
"error": str(e)
513+
}
514+
515+
491516
# GitHub activity endpoints
492517
@app.get("/api/github/activity/{username}")
493518
def github_activity(username: str, limit: int = 10):

backend_tokens.db

0 Bytes
Binary file not shown.

user_settings.db

0 Bytes
Binary file not shown.

web/src/pages/api-reference.tsx

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -80,30 +80,34 @@ export default function APIReference() {
8080
{
8181
method: 'GET',
8282
path: '/api/settings/{user_id}',
83-
description: 'Get user settings',
83+
description: 'Get user settings (safe data only, no credentials)',
8484
params: ['user_id: User identifier'],
85-
response: '{"linkedin_client_id": "...", "groq_api_key": "...", ...}',
85+
response: '{"user_id": "...", "github_username": "...", "onboarding_complete": true, "subscription_tier": "free"}',
8686
},
8787
{
8888
method: 'POST',
8989
path: '/api/settings',
90-
description: 'Update user settings',
90+
description: 'Update user settings (public identifiers only)',
9191
body: `{
9292
"user_id": "user_123",
93-
"linkedin_client_id": "...",
94-
"linkedin_client_secret": "...",
95-
"groq_api_key": "...",
9693
"github_username": "johndoe",
97-
"unsplash_access_key": "..."
94+
"onboarding_complete": true
9895
}`,
9996
response: '{"success": true}',
10097
},
98+
{
99+
method: 'GET',
100+
path: '/api/connection-status/{user_id}',
101+
description: 'Get connection status (LinkedIn/GitHub)',
102+
params: ['user_id: User identifier'],
103+
response: '{"linkedin_connected": true, "github_connected": true, "github_username": "johndoe"}',
104+
},
101105
{
102106
method: 'POST',
103107
path: '/api/auth/refresh',
104-
description: 'Refresh LinkedIn access token',
108+
description: 'Check LinkedIn connection status',
105109
body: '{"user_id": "user_123"}',
106-
response: '{"access_token": "...", "expires_in": 3600}',
110+
response: '{"authenticated": true, "user_urn": "..."}',
107111
},
108112
];
109113

@@ -129,11 +133,11 @@ export default function APIReference() {
129133
<ThemeToggle />
130134
</div>
131135
</header>
132-
<SEOHead
136+
<SEOHead
133137
title="API Reference - LinkedIn Post Bot"
134138
description="Complete API documentation for LinkedIn Post Bot. Integrate and automate your LinkedIn content creation."
135139
/>
136-
140+
137141
{/* Header */}
138142
<header className="bg-white shadow-sm border-b border-gray-200">
139143
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4 flex items-center justify-between">
@@ -162,7 +166,7 @@ export default function APIReference() {
162166
<p className="text-lg text-gray-600 mb-6">
163167
Complete documentation for integrating with LinkedIn Post Bot API
164168
</p>
165-
169+
166170
<div className="bg-blue-50 border-l-4 border-blue-600 p-6 rounded-r-lg">
167171
<p className="text-blue-900 font-medium mb-2">Base URL</p>
168172
<code className="bg-white px-3 py-1 rounded text-blue-600 font-mono">
@@ -176,7 +180,7 @@ export default function APIReference() {
176180

177181
<div className="space-y-6">
178182
{endpoints.map((endpoint, index) => (
179-
<div
183+
<div
180184
key={index}
181185
className="bg-white rounded-2xl p-6 shadow-lg border border-gray-100 hover:shadow-xl transition-all"
182186
>
@@ -234,7 +238,7 @@ export default function APIReference() {
234238
Most endpoints require authentication via LinkedIn OAuth 2.0. Include the access token in requests:
235239
</p>
236240
<pre className="bg-black/20 p-4 rounded-lg text-sm">
237-
{`Authorization: Bearer YOUR_ACCESS_TOKEN`}
241+
{`Authorization: Bearer YOUR_ACCESS_TOKEN`}
238242
</pre>
239243
<p className="text-blue-100 mt-4 text-sm">
240244
Tokens expire after 60 days. Use the <code className="bg-white/20 px-2 py-0.5 rounded">/api/auth/refresh</code> endpoint to renew.

0 commit comments

Comments
 (0)