11import { NextResponse } from "next/server" ;
2- import { fetchGitHubUserData } from "../../../lib/github" ;
2+ import { getUserData } from "../../../lib/github" ;
33import { calculateUserScore } from "../../../lib/score" ;
44import { normalizeSelectedLanguages } from "@/lib/scoring/languageScoring" ;
55import { toSafeApiError } from "@/lib/github-graphql-client" ;
6+ import { getDatabaseStore } from "@/lib/db-store" ;
7+ import { createCacheStore , getCacheConfigFromEnv } from "@/lib/cache-store" ;
8+ import { detectCountry } from "@/lib/location-detector" ;
69import type { CompareInsights , SafeApiError } from "@/types/api-response" ;
710import {
811 DEFAULT_LOCALE ,
@@ -11,6 +14,7 @@ import {
1114 parseAcceptLanguage ,
1215 type Locale ,
1316} from "@/lib/i18n-core" ;
17+ import { GitHubUserData } from "@/types/github" ;
1418
1519export const runtime = "nodejs" ;
1620
@@ -48,7 +52,10 @@ type ComparedUserResult = {
4852 explanations : ReturnType < typeof calculateUserScore > [ "explanations" ] ;
4953} ;
5054
51- type ClientSafeError = Pick < SafeApiError , "code" | "message" | "targetUsernames" > ;
55+ type ClientSafeError = Pick <
56+ SafeApiError ,
57+ "code" | "message" | "targetUsernames"
58+ > ;
5259
5360function parseSelectedLanguagesFromSearchParams (
5461 searchParams : URLSearchParams ,
@@ -60,7 +67,10 @@ function parseSelectedLanguagesFromSearchParams(
6067 . map ( ( language ) => language . trim ( ) )
6168 . filter ( Boolean ) ;
6269
63- return normalizeSelectedLanguages ( [ ...( fromRepeated ?? [ ] ) , ...( fromCsv ?? [ ] ) ] ) ;
70+ return normalizeSelectedLanguages ( [
71+ ...( fromRepeated ?? [ ] ) ,
72+ ...( fromCsv ?? [ ] ) ,
73+ ] ) ;
6474}
6575
6676function calculateWinner ( users : ComparedUserResult [ ] ) : {
@@ -82,7 +92,8 @@ function calculateWinner(users: ComparedUserResult[]): {
8292
8393 const [ userA , userB ] = users ;
8494 const overallWinner = userA . finalScore >= userB . finalScore ? userA : userB ;
85- const overallLoser = overallWinner . username === userA . username ? userB : userA ;
95+ const overallLoser =
96+ overallWinner . username === userA . username ? userB : userA ;
8697 const overallDifference = Math . abs ( userA . finalScore - userB . finalScore ) ;
8798 const overallPercentage =
8899 overallLoser . finalScore > 0
@@ -114,7 +125,8 @@ function calculateWinner(users: ComparedUserResult[]): {
114125 userA . languageScores . finalScore >= userB . languageScores . finalScore
115126 ? userA
116127 : userB ;
117- const languageLoser = languageWinner . username === userA . username ? userB : userA ;
128+ const languageLoser =
129+ languageWinner . username === userA . username ? userB : userA ;
118130 const winnerLanguageScores = languageWinner . languageScores ! ;
119131 const loserLanguageScores = languageLoser . languageScores ! ;
120132 const languageDifference = Math . abs (
@@ -306,9 +318,14 @@ async function compareUsers(
306318 const results : ComparedUserResult [ ] = [ ] ;
307319
308320 for ( const username of usernames ) {
309- let data : Awaited < ReturnType < typeof fetchGitHubUserData > > ;
321+ let data : Awaited < GitHubUserData > ;
310322 try {
311- data = await fetchGitHubUserData ( username ) ;
323+ const { data : userData , metrics } = await getUserData ( username , {
324+ cacheInRedis : true ,
325+ withMetrics : true ,
326+ } ) ;
327+ data = userData ;
328+ console . log ( metrics ) ;
312329 } catch ( error : unknown ) {
313330 throw new CompareUserFetchError ( username , error ) ;
314331 }
@@ -322,7 +339,7 @@ async function compareUsers(
322339 ) ;
323340
324341 results . push ( {
325- username,
342+ username : data . login ,
326343 name : data . name ,
327344 avatarUrl : data . avatarUrl ,
328345 repoScore : Math . round ( score . repoScore ) ,
@@ -331,7 +348,9 @@ async function compareUsers(
331348 finalScore : Math . round ( score . finalScore ) ,
332349 normalizedRepoScore : Math . round ( score . normalizedRepoScore ) ,
333350 normalizedPRScore : Math . round ( score . normalizedPRScore ) ,
334- normalizedContributionScore : Math . round ( score . normalizedContributionScore ) ,
351+ normalizedContributionScore : Math . round (
352+ score . normalizedContributionScore ,
353+ ) ,
335354 normalizedFinalScore : Math . round ( score . normalizedFinalScore ) ,
336355 topRepos : score . topRepos ,
337356 topPullRequests : score . topPullRequests ,
@@ -340,18 +359,59 @@ async function compareUsers(
340359 signals : score . signals ,
341360 explanations : score . explanations ,
342361 } ) ;
362+
363+ // ── Fire-and-forget: detect country & upsert into DB ──────────────
364+ const country = detectCountry ( data . location ) ;
365+ if ( country ) {
366+ const staleDays = parseInt (
367+ process . env . GITHUB_USER_STALE_DAYS ?? "14" ,
368+ 10 ,
369+ ) ;
370+
371+ const db = getDatabaseStore ( ) ;
372+ db . upsertUser ( {
373+ username : data . login ,
374+ name : data . name ,
375+ avatarUrl : data . avatarUrl ,
376+ location : data . location ,
377+ country,
378+ rawData : data ,
379+ scores : score ,
380+ repoScore : Math . round ( score . repoScore ) ,
381+ prScore : Math . round ( score . prScore ) ,
382+ contributionScore : Math . round ( score . contributionScore ) ,
383+ finalScore : Math . round ( score . finalScore ) ,
384+ staleDays,
385+ } )
386+ . then ( ( ) => {
387+ // Invalidate Redis cache for this country
388+ const cacheConfig = getCacheConfigFromEnv ( ) ;
389+ const cacheStore = createCacheStore ( cacheConfig ) ;
390+ if ( cacheStore . enabled && cacheStore . del ) {
391+ const key = `${ cacheConfig . namespace } :leaderboard:${ country } ` ;
392+ cacheStore . del ( key ) . catch ( ( ) => { } ) ;
393+ }
394+ } )
395+ . catch ( ( err : unknown ) => {
396+ console . warn ( "Failed to upsert user from compare:" , err ) ;
397+ } ) ;
398+ }
343399 }
344400
345401 return results ;
346402}
347403
348- function toApiErrorStatus ( code : ReturnType < typeof toSafeApiError > [ "code" ] ) : number {
404+ function toApiErrorStatus (
405+ code : ReturnType < typeof toSafeApiError > [ "code" ] ,
406+ ) : number {
349407 switch ( code ) {
350408 case "RATE_LIMITED" :
351409 case "TEMPORARY_THROTTLE" :
352410 return 429 ;
411+ case "GITHUB_TIMEOUT" :
412+ case "GITHUB_RESOURCE_LIMIT" :
353413 case "GITHUB_AUTH" :
354- return 401 ;
414+ return code === "GITHUB_AUTH" ? 401 : 503 ;
355415 case "GITHUB_NOT_FOUND" :
356416 return 404 ;
357417 case "NETWORK" :
@@ -385,7 +445,8 @@ export async function GET(request: Request) {
385445
386446 try {
387447 const locale = resolveLocale ( request ) ;
388- const selectedLanguages = parseSelectedLanguagesFromSearchParams ( searchParams ) ;
448+ const selectedLanguages =
449+ parseSelectedLanguagesFromSearchParams ( searchParams ) ;
389450 const users = await compareUsers ( usernames , selectedLanguages ) ;
390451 const winnerData = calculateWinner ( users ) ;
391452 const insights = createComparisonInsights ( users , locale ) ;
0 commit comments