Spaces:
Paused
Paused
| /** | |
| * Social Media OAuth Routes | |
| * Unified routes for all social platforms | |
| */ | |
| import { Router, Request, Response } from 'express'; | |
| import { socialMediaOAuth, SocialPlatform } from '../services/SocialMediaOAuthService.js'; | |
| const router = Router(); | |
| // Store state parameters (in production, use Redis) | |
| const stateStore = new Map<string, { timestamp: number; platform: SocialPlatform; returnUrl?: string }>(); | |
| /** | |
| * GET /api/auth/:platform/login | |
| * Initiate OAuth flow for any platform | |
| */ | |
| router.get('/:platform/login', (req: Request, res: Response) => { | |
| try { | |
| const platform = req.params.platform as SocialPlatform; | |
| const returnUrl = req.query.returnUrl as string; | |
| if (!Object.values(SocialPlatform).includes(platform)) { | |
| return res.status(400).json({ | |
| success: false, | |
| error: `Invalid platform: ${platform}`, | |
| supportedPlatforms: Object.values(SocialPlatform), | |
| }); | |
| } | |
| const state = Math.random().toString(36).substring(7); | |
| stateStore.set(state, { | |
| timestamp: Date.now(), | |
| platform, | |
| returnUrl, | |
| }); | |
| // Clean old states | |
| for (const [key, value] of stateStore.entries()) { | |
| if (Date.now() - value.timestamp > 300000) { | |
| stateStore.delete(key); | |
| } | |
| } | |
| const authUrl = socialMediaOAuth.getAuthorizationUrl(platform, state); | |
| res.json({ | |
| success: true, | |
| authUrl, | |
| platform, | |
| message: `Redirect user to authUrl to authorize ${platform}`, | |
| }); | |
| } catch (error: any) { | |
| res.status(500).json({ | |
| success: false, | |
| error: error.message, | |
| }); | |
| } | |
| }); | |
| /** | |
| * GET /api/auth/:platform/callback | |
| * OAuth callback for any platform | |
| */ | |
| router.get('/:platform/callback', async (req: Request, res: Response) => { | |
| try { | |
| const platform = req.params.platform as SocialPlatform; | |
| const { code, state, error, error_description } = req.query; | |
| // Handle OAuth errors | |
| if (error) { | |
| return res.status(400).send(` | |
| <html> | |
| <body style="font-family: system-ui; padding: 40px; text-align: center;"> | |
| <h1>❌ ${platform} Authorization Failed</h1> | |
| <p><strong>Error:</strong> ${error}</p> | |
| <p><strong>Description:</strong> ${error_description || 'Unknown error'}</p> | |
| <p><a href="http://localhost:8888/dashboard">Return to Dashboard</a></p> | |
| </body> | |
| </html> | |
| `); | |
| } | |
| // Verify state | |
| const stateData = stateStore.get(state as string); | |
| if (!stateData || stateData.platform !== platform) { | |
| return res.status(400).send(` | |
| <html> | |
| <body style="font-family: system-ui; padding: 40px; text-align: center;"> | |
| <h1>❌ Invalid State Parameter</h1> | |
| <p>State verification failed. Please try again.</p> | |
| <p><a href="http://localhost:8888/dashboard">Return to Dashboard</a></p> | |
| </body> | |
| </html> | |
| `); | |
| } | |
| stateStore.delete(state as string); | |
| // Exchange code for token | |
| const { accessToken, userId } = await socialMediaOAuth.exchangeCodeForToken(platform, code as string); | |
| // Success | |
| const returnUrl = stateData.returnUrl || 'http://localhost:8888/dashboard'; | |
| res.send(` | |
| <html> | |
| <head> | |
| <style> | |
| body { | |
| font-family: system-ui; | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| height: 100vh; | |
| margin: 0; | |
| color: white; | |
| } | |
| .container { | |
| background: rgba(255, 255, 255, 0.1); | |
| backdrop-filter: blur(10px); | |
| border-radius: 20px; | |
| padding: 40px; | |
| text-align: center; | |
| box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); | |
| } | |
| h1 { margin-top: 0; } | |
| .button { | |
| display: inline-block; | |
| background: white; | |
| color: #667eea; | |
| padding: 12px 24px; | |
| border-radius: 8px; | |
| text-decoration: none; | |
| font-weight: 600; | |
| margin-top: 20px; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <h1>✅ ${platform.toUpperCase()} Connected</h1> | |
| <p><strong>User ID:</strong> ${userId}</p> | |
| <p>You can now ingest ${platform} content into WidgeTDC.</p> | |
| <a href="${returnUrl}" class="button">Return to Dashboard</a> | |
| </div> | |
| <script> | |
| if (window.opener) { | |
| window.opener.postMessage({ | |
| type: 'social-oauth-success', | |
| platform: '${platform}', | |
| userId: '${userId}' | |
| }, '*'); | |
| setTimeout(() => window.close(), 2000); | |
| } | |
| </script> | |
| </body> | |
| </html> | |
| `); | |
| } catch (error: any) { | |
| console.error('❌ OAuth callback error:', error); | |
| res.status(500).send(` | |
| <html> | |
| <body style="font-family: system-ui; padding: 40px; text-align: center;"> | |
| <h1>❌ Authentication Error</h1> | |
| <p>${error.message}</p> | |
| <p><a href="http://localhost:8888/dashboard">Return to Dashboard</a></p> | |
| </body> | |
| </html> | |
| `); | |
| } | |
| }); | |
| /** | |
| * GET /api/social/:platform/status | |
| * Check authentication status | |
| */ | |
| router.get('/:platform/status', (req: Request, res: Response) => { | |
| const platform = req.params.platform as SocialPlatform; | |
| const userId = req.query.userId as string; | |
| if (!userId) { | |
| return res.json({ authenticated: false }); | |
| } | |
| const isAuth = socialMediaOAuth.isAuthenticated(platform, userId); | |
| res.json({ | |
| authenticated: isAuth, | |
| platform, | |
| userId: isAuth ? userId : null, | |
| }); | |
| }); | |
| /** | |
| * POST /api/social/:platform/ingest | |
| * Fetch and ingest posts (requires approval) | |
| */ | |
| router.post('/:platform/ingest', async (req: Request, res: Response) => { | |
| try { | |
| const platform = req.params.platform as SocialPlatform; | |
| const { userId, limit = 10 } = req.body; | |
| if (!userId) { | |
| return res.status(400).json({ | |
| success: false, | |
| error: 'userId required in request body', | |
| }); | |
| } | |
| if (!socialMediaOAuth.isAuthenticated(platform, userId)) { | |
| return res.status(401).json({ | |
| success: false, | |
| error: 'Not authenticated. Please login first.', | |
| loginUrl: `/api/auth/${platform}/login`, | |
| }); | |
| } | |
| const { posts, approvalRequestId } = await socialMediaOAuth.fetchAndIngestPosts( | |
| platform, | |
| userId, | |
| limit | |
| ); | |
| res.json({ | |
| success: true, | |
| platform, | |
| postCount: posts.length, | |
| approvalRequestId, | |
| message: `${posts.length} posts from ${platform} ingested after approval`, | |
| }); | |
| } catch (error: any) { | |
| res.status(500).json({ | |
| success: false, | |
| error: error.message, | |
| }); | |
| } | |
| }); | |
| /** | |
| * DELETE /api/social/:platform/logout | |
| * Revoke access token | |
| */ | |
| router.delete('/:platform/logout', (req: Request, res: Response) => { | |
| const platform = req.params.platform as SocialPlatform; | |
| const { userId } = req.body; | |
| if (!userId) { | |
| return res.status(400).json({ | |
| success: false, | |
| error: 'userId required', | |
| }); | |
| } | |
| socialMediaOAuth.revokeAccess(platform, userId); | |
| res.json({ | |
| success: true, | |
| platform, | |
| message: `${platform} access revoked`, | |
| }); | |
| }); | |
| export default router; | |