VIBN Frontend for Coolify deployment

This commit is contained in:
2026-02-15 19:25:52 -08:00
commit 40bf8428cd
398 changed files with 76513 additions and 0 deletions

177
lib/github/oauth.ts Normal file
View File

@@ -0,0 +1,177 @@
/**
* 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<GitHubUser> {
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<GitHubRepo[]> {
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<GitHubRepo> {
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;
}