/** * GitHub OAuth integration for VIBN * Allows users to connect their GitHub account for repo analysis and tracking */ export interface GitHubUser { id: number; login: string; name: string | null; email: string | null; avatar_url: string; } export interface GitHubRepo { id: number; name: string; full_name: string; description: string | null; html_url: string; language: string | null; default_branch: string; private: boolean; topics: string[]; } export interface GitHubConnection { userId: string; githubUserId: number; githubUsername: string; accessToken: string; // Encrypted refreshToken?: string; // Encrypted tokenExpiresAt?: Date; scopes: string[]; connectedAt: Date; lastSyncedAt?: Date; } /** * Initiates GitHub OAuth flow * Redirects user to GitHub authorization page */ export function initiateGitHubOAuth(redirectUri: string) { const clientId = process.env.NEXT_PUBLIC_GITHUB_CLIENT_ID; if (!clientId) { throw new Error('GitHub OAuth not configured'); } // Scopes we need: // - repo: Access repositories (read commits, PRs, issues) // - read:user: Get user profile const scopes = ['repo', 'read:user']; // Generate state for CSRF protection const state = generateRandomString(32); sessionStorage.setItem('github_oauth_state', state); const params = new URLSearchParams({ client_id: clientId, redirect_uri: redirectUri, scope: scopes.join(' '), state, }); window.location.href = `https://github.com/login/oauth/authorize?${params}`; } /** * Exchanges authorization code for access token */ export async function exchangeCodeForToken(code: string): Promise<{ access_token: string; token_type: string; scope: string; }> { const response = await fetch('/api/github/oauth/token', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ code }), }); if (!response.ok) { throw new Error('Failed to exchange code for token'); } return response.json(); } /** * Fetches GitHub user profile */ export async function getGitHubUser(accessToken: string): Promise { const response = await fetch('https://api.github.com/user', { headers: { Authorization: `Bearer ${accessToken}`, Accept: 'application/vnd.github.v3+json', }, }); if (!response.ok) { throw new Error('Failed to fetch GitHub user'); } return response.json(); } /** * Fetches user's repositories */ export async function getGitHubRepos( accessToken: string, options?: { sort?: 'created' | 'updated' | 'pushed' | 'full_name'; direction?: 'asc' | 'desc'; per_page?: number; } ): Promise { const params = new URLSearchParams({ sort: options?.sort || 'updated', direction: options?.direction || 'desc', per_page: String(options?.per_page || 100), }); const response = await fetch(`https://api.github.com/user/repos?${params}`, { headers: { Authorization: `Bearer ${accessToken}`, Accept: 'application/vnd.github.v3+json', }, }); if (!response.ok) { throw new Error('Failed to fetch repositories'); } return response.json(); } /** * Fetches a specific repository */ export async function getGitHubRepo( accessToken: string, owner: string, repo: string ): Promise { const response = await fetch(`https://api.github.com/repos/${owner}/${repo}`, { headers: { Authorization: `Bearer ${accessToken}`, Accept: 'application/vnd.github.v3+json', }, }); if (!response.ok) { throw new Error('Failed to fetch repository'); } return response.json(); } /** * Utility: Generate random string for state parameter */ function generateRandomString(length: number): string { const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; let result = ''; const randomValues = new Uint8Array(length); crypto.getRandomValues(randomValues); for (let i = 0; i < length; i++) { result += chars[randomValues[i] % chars.length]; } return result; }