/** * Sync User GitHub Data Route * * POST /api/sync/user-data * Triggers an immediate fetch of the user's GitHub contribution history * for new contributors to populate the heatmap and stats. */ import { NextRequest, NextResponse } from "next/server"; import { getCurrentUser } from "@/lib/auth"; import { Octokit } from "@octokit/rest"; import { db } from "@/db"; import { users, profiles } from "@/db/schema"; import { eq } from "drizzle-orm"; import { fetchGitHubContributions } from "@/lib/github-contributions"; export async function POST(request: NextRequest) { try { const user = await getCurrentUser(request); if (!user) { return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); } if (!user.githubAccessToken) { return NextResponse.json({ error: "GitHub access token not found" }, { status: 400 }); } const octokit = new Octokit({ auth: user.githubAccessToken }); console.log(`[SyncUserData] Starting sync for ${user.username}`); let totalPRs = 0; let totalIssues = 0; const errors: string[] = []; // 1. Fetch contribution calendar data first (for heatmap) const contributionData = await fetchGitHubContributions(user.username, user.githubAccessToken); // 2. Search for all PRs authored by this user to get count try { const { data: prSearchResult } = await octokit.search.issuesAndPullRequests({ q: `type:pr author:${user.username}`, sort: 'updated', order: 'desc', per_page: 1, // We just need the total count }); totalPRs = prSearchResult.total_count; } catch (searchError: any) { console.error('PR search error:', searchError); errors.push(`PR search: ${searchError.message}`); } // 3. Search for all issues authored by this user to get count try { const { data: issueSearchResult } = await octokit.search.issuesAndPullRequests({ q: `type:issue author:${user.username}`, sort: 'updated', order: 'desc', per_page: 1, // We just need the total count }); totalIssues = issueSearchResult.total_count; } catch (searchError: any) { console.error('Issue search error:', searchError); errors.push(`Issue search: ${searchError.message}`); } // 4. Update user's profile with GitHub stats const existingProfile = await db.select() .from(profiles) .where(eq(profiles.userId, user.id)) .limit(1); if (existingProfile.length > 0) { const currentStats = existingProfile[0].githubStats ? (typeof existingProfile[0].githubStats === 'string' ? JSON.parse(existingProfile[0].githubStats) : existingProfile[0].githubStats) : {}; // Calculate streak from contribution data let currentStreak = 0; if (contributionData?.weeks) { const allDays = contributionData.weeks.flatMap(w => w.contributionDays).reverse(); for (const day of allDays) { if (day.contributionCount > 0) { currentStreak++; } else { break; } } } await db.update(profiles) .set({ githubStats: JSON.stringify({ ...currentStats, total_prs: totalPRs, total_issues: totalIssues, total_contributions: contributionData?.totalContributions || currentStats.total_contributions || 0, current_streak: currentStreak || currentStats.current_streak || 0, last_sync: new Date().toISOString(), }), updatedAt: new Date().toISOString(), }) .where(eq(profiles.userId, user.id)); } // 5. Update user's last sync timestamp await db.update(users) .set({ updatedAt: new Date().toISOString() }) .where(eq(users.id, user.id)); console.log(`[SyncUserData] Completed for ${user.username}: ${totalPRs} PRs, ${totalIssues} issues`); return NextResponse.json({ success: true, username: user.username, totalPRs, totalIssues, contributionData: contributionData ? { totalContributions: contributionData.totalContributions, weeksCount: contributionData.weeks?.length || 0 } : null, errors: errors.length > 0 ? errors : undefined, message: `Synced contribution data for ${user.username}` }); } catch (error: any) { console.error("POST /api/sync/user-data error:", error); return NextResponse.json({ error: error.message || "Internal server error" }, { status: 500 }); } } // GET endpoint to check sync status export async function GET(request: NextRequest) { try { const user = await getCurrentUser(request); if (!user) { return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); } // Get profile stats const profile = await db.select() .from(profiles) .where(eq(profiles.userId, user.id)) .limit(1); const githubStats = profile[0]?.githubStats ? (typeof profile[0].githubStats === 'string' ? JSON.parse(profile[0].githubStats) : profile[0].githubStats) : null; const totalPRs = githubStats?.total_prs || 0; const totalIssues = githubStats?.total_issues || 0; const lastSync = githubStats?.last_sync || null; return NextResponse.json({ username: user.username, totalPRs, totalIssues, totalContributions: githubStats?.total_contributions || 0, currentStreak: githubStats?.current_streak || 0, lastSync, needsSync: !lastSync || totalPRs + totalIssues === 0 }); } catch (error) { console.error("GET /api/sync/user-data error:", error); return NextResponse.json({ error: "Internal server error" }, { status: 500 }); } }