Skip to content

Commit 7887564

Browse files
authored
Merge pull request #68 from ArjinAlbay/main
refactor: stargazer type can be null
2 parents e41ab4d + 71966aa commit 7887564

File tree

6 files changed

+187
-40
lines changed

6 files changed

+187
-40
lines changed

src/app/api/search/route.ts

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import { NextRequest, NextResponse } from 'next/server'
2+
import { githubAPIClient } from '@/lib/api/github-api-client'
3+
4+
export async function GET(request: NextRequest) {
5+
try {
6+
const { searchParams } = new URL(request.url)
7+
const query = searchParams.get('q')
8+
const type = searchParams.get('type') || 'repos'
9+
const limit = parseInt(searchParams.get('limit') || '20')
10+
11+
if (!query) {
12+
return NextResponse.json(
13+
{ error: 'Query parameter "q" is required' },
14+
{ status: 400 }
15+
)
16+
}
17+
18+
let results
19+
if (type === 'users') {
20+
results = await githubAPIClient.searchUsers(query, 'all', limit)
21+
} else {
22+
results = await githubAPIClient.searchRepositories(query, 'stars', limit)
23+
}
24+
25+
return NextResponse.json({
26+
query,
27+
type,
28+
results,
29+
total: results.length
30+
})
31+
} catch (error) {
32+
console.error('Search API error:', error)
33+
return NextResponse.json(
34+
{ error: 'Internal server error' },
35+
{ status: 500 }
36+
)
37+
}
38+
}
39+
40+
export async function POST(request: NextRequest) {
41+
try {
42+
const body = await request.json()
43+
const { query, type = 'repos', limit = 20 } = body
44+
45+
if (!query) {
46+
return NextResponse.json(
47+
{ error: 'Query is required in request body' },
48+
{ status: 400 }
49+
)
50+
}
51+
52+
let results
53+
if (type === 'users') {
54+
results = await githubAPIClient.searchUsers(query, 'all', limit)
55+
} else {
56+
results = await githubAPIClient.searchRepositories(query, 'stars', limit)
57+
}
58+
59+
return NextResponse.json({
60+
query,
61+
type,
62+
results,
63+
total: results.length
64+
})
65+
} catch (error) {
66+
console.error('Search API error:', error)
67+
return NextResponse.json(
68+
{ error: 'Internal server error' },
69+
{ status: 500 }
70+
)
71+
}
72+
}

src/app/api/user/[username]/route.ts

Whitespace-only changes.

src/app/search/page.tsx

Lines changed: 39 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,44 @@ export default function SearchPage() {
108108
);
109109
const [loadingAnalytics, setLoadingAnalytics] = useState<boolean>(false);
110110

111+
const loadUserAnalytics = useCallback(async () => {
112+
if (!userParam) return;
113+
114+
setLoadingAnalytics(true);
115+
try {
116+
const analytics = await githubAPIClient.getUserAnalytics(userParam);
117+
if (analytics) {
118+
// Convert GitHubUserDetailed to the expected profile format
119+
const convertedAnalytics: UserAnalytics = {
120+
profile: analytics.profile ? {
121+
avatar_url: analytics.profile.avatar_url,
122+
login: analytics.profile.login,
123+
type: analytics.profile.type,
124+
bio: analytics.profile.bio,
125+
public_repos: analytics.profile.public_repos,
126+
followers: analytics.profile.followers,
127+
following: analytics.profile.following,
128+
location: analytics.profile.location,
129+
company: analytics.profile.company,
130+
html_url: analytics.profile.html_url
131+
} : undefined,
132+
overview: analytics.overview,
133+
languages: analytics.languages,
134+
behavior: analytics.behavior
135+
};
136+
setUserAnalytics(convertedAnalytics);
137+
} else {
138+
setUserAnalytics(null);
139+
}
140+
} catch (error) {
141+
console.error("Analytics error:", error);
142+
// Fallback to null if API fails
143+
setUserAnalytics(null);
144+
} finally {
145+
setLoadingAnalytics(false);
146+
}
147+
}, [userParam]);
148+
111149
const throttle = useCallback(
112150
<T extends (...args: unknown[]) => void>(
113151
func: T,
@@ -202,7 +240,7 @@ export default function SearchPage() {
202240
loadUserAnalytics();
203241
}
204242
}
205-
}, [userParam, repoParam, setCurrentQuery, setCurrentSearchType]);
243+
}, [userParam, repoParam, setCurrentQuery, setCurrentSearchType, loadUserAnalytics]);
206244
const performSearch = async (query: string, type: "users" | "repos") => {
207245
setSearchResults((prev) => ({ ...prev, loading: true, error: null }));
208246

@@ -239,22 +277,6 @@ export default function SearchPage() {
239277
}
240278
};
241279

242-
const loadUserAnalytics = async () => {
243-
if (!userParam) return;
244-
245-
setLoadingAnalytics(true);
246-
try {
247-
const analytics = await githubAPIClient.getUserAnalytics(userParam);
248-
setUserAnalytics(analytics);
249-
} catch (error) {
250-
console.error("Analytics error:", error);
251-
// Fallback to null if API fails
252-
setUserAnalytics(null);
253-
} finally {
254-
setLoadingAnalytics(false);
255-
}
256-
};
257-
258280
// Dispatch active section to sidebar
259281
useEffect(() => {
260282
const event = new CustomEvent("activeSectionChange", {

src/components/quick-wins/hooks/useQuickWins.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ export function useQuickWins() {
4848
])
4949
}
5050
isInitialized.current = true
51-
}, [loadFromCache, isQuickWinsCacheExpired, goodIssues.length, easyFixes.length, fetchGoodIssues, fetchEasyFixes])
51+
}, [loadFromCache, isQuickWinsCacheExpired, fetchGoodIssues, fetchEasyFixes])
5252

5353
useEffect(() => {
5454
if (goodIssues.length > 0) {

src/lib/api/github-api-client.ts

Lines changed: 71 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ import type {
22
TrendingRepo,
33
TopContributor
44
} from '@/types/oss-insight'
5+
import type {
6+
GitHubUserDetailed,
7+
GitHubRepositoryDetailed
8+
} from '@/types/github'
59

610
interface GitHubSearchResponse<T> {
711
items: T[]
@@ -558,32 +562,54 @@ async getEasyFixes(): Promise<MappedIssue[]> {
558562

559563
// ============ USER ANALYTICS API METHODS ============
560564

561-
async getUserProfile(username: string): Promise<any> {
565+
async getUserProfile(username: string): Promise<GitHubUserDetailed | null> {
562566
try {
563567
const endpoint = `/users/${username}`
564568
return await this.fetchWithCache(endpoint, true)
565569
} catch (error) {
566570
console.error('Failed to fetch user profile:', error)
567571
// Return fallback profile data
568572
return {
569-
avatar_url: `https://github.com/${username}.png`,
573+
id: 0,
570574
login: username,
571-
type: 'User',
572-
bio: null,
575+
node_id: '',
576+
avatar_url: `https://github.com/${username}.png`,
577+
gravatar_id: '',
578+
url: '',
579+
html_url: `https://github.com/${username}`,
580+
followers_url: '',
581+
following_url: '',
582+
gists_url: '',
583+
starred_url: '',
584+
subscriptions_url: '',
585+
organizations_url: '',
586+
repos_url: '',
587+
events_url: '',
588+
received_events_url: '',
589+
type: 'User' as const,
590+
site_admin: false,
591+
name: undefined,
592+
company: undefined,
593+
blog: undefined,
594+
location: undefined,
595+
email: undefined,
596+
hireable: undefined,
597+
bio: undefined,
598+
twitter_username: undefined,
573599
public_repos: 0,
600+
public_gists: 0,
574601
followers: 0,
575602
following: 0,
576-
location: null,
577-
company: null,
578-
html_url: `https://github.com/${username}`
603+
created_at: new Date().toISOString(),
604+
updated_at: new Date().toISOString()
579605
}
580606
}
581607
}
582608

583-
async getUserRepositories(username: string, limit = 100): Promise<any[]> {
609+
async getUserRepositories(username: string, limit = 100): Promise<GitHubRepositoryDetailed[]> {
584610
try {
585611
const endpoint = `/users/${username}/repos?per_page=${limit}&sort=updated`
586-
const repos = await this.fetchWithCache<any[]>(endpoint, true)
612+
const repos = await this.fetchWithCache<GitHubRepositoryDetailed[]>(endpoint, true)
587613

588614
// Ensure we always return an array
589615
if (Array.isArray(repos)) {
@@ -631,7 +657,7 @@ async getEasyFixes(): Promise<MappedIssue[]> {
631657
}
632658

633659
async getUserAnalytics(username: string): Promise<{
634-
profile: any
660+
profile: GitHubUserDetailed | null
635661
overview: Array<{ name: string; commits: number; stars: number; repos: number }>
636662
languages: Array<{ name: string; value: number }>
637663
behavior: Array<{ day: string; commits: number; prs: number; issues: number }>
@@ -653,7 +679,7 @@ async getEasyFixes(): Promise<MappedIssue[]> {
653679

654680
// Generate real overview data from repos
655681
const overview = Array.isArray(repos) && repos.length > 0
656-
? repos.slice(0, 10).map((repo: any) => ({
682+
? repos.slice(0, 10).map((repo: GitHubRepositoryDetailed) => ({
657683
name: repo?.name?.length > 15 ? repo.name.substring(0, 15) + '...' : (repo?.name || 'Unknown'),
658684
commits: Math.max(1, Math.floor(Math.random() * 50) + 10), // GitHub API doesn't provide commit counts easily
659685
stars: repo?.stargazers_count || 0,
@@ -695,24 +721,51 @@ async getEasyFixes(): Promise<MappedIssue[]> {
695721
keysToDelete.forEach(key => this.cache.delete(key));
696722
}
697723

698-
private getDemoAnalytics(username: string) {
724+
private getDemoAnalytics(username: string): {
725+
profile: GitHubUserDetailed | null
726+
overview: Array<{ name: string; commits: number; stars: number; repos: number }>
727+
languages: Array<{ name: string; value: number }>
728+
behavior: Array<{ day: string; commits: number; prs: number; issues: number }>
729+
} {
699730
const isRealUser = ['torvalds', 'octocat', 'gaearon', 'sindresorhus', 'tj', 'defunkt'].includes(username.toLowerCase());
700731

701732
const baseData = {
702733
profile: {
703-
avatar_url: `https://github.com/${username}.png`, // GitHub always provides avatar for any username
734+
id: 0,
704735
login: username,
705-
type: 'User',
736+
node_id: '',
737+
avatar_url: `https://github.com/${username}.png`, // GitHub always provides avatar for any username
738+
gravatar_id: '',
739+
url: '',
740+
html_url: `https://github.com/${username}`,
741+
followers_url: '',
742+
following_url: '',
743+
gists_url: '',
744+
starred_url: '',
745+
subscriptions_url: '',
746+
organizations_url: '',
747+
repos_url: '',
748+
events_url: '',
749+
received_events_url: '',
750+
type: 'User' as const,
751+
site_admin: false,
752+
name: undefined,
753+
company: isRealUser ? 'Open Source' : 'Demo Company',
754+
blog: undefined,
755+
location: isRealUser ? 'Global' : 'Demo Location',
756+
email: undefined,
757+
hireable: undefined,
706758
bio: isRealUser
707759
? `Real GitHub user ${username} - Limited data due to API constraints`
708760
: `Demo profile for ${username}`,
761+
twitter_username: undefined,
709762
public_repos: isRealUser ? Math.floor(Math.random() * 50) + 20 : 25,
763+
public_gists: 0,
710764
followers: isRealUser ? Math.floor(Math.random() * 5000) + 500 : 150,
711765
following: isRealUser ? Math.floor(Math.random() * 200) + 50 : 75,
712-
location: isRealUser ? 'Global' : 'Demo Location',
713-
company: isRealUser ? 'Open Source' : 'Demo Company',
714-
html_url: `https://github.com/${username}`
715-
},
766+
created_at: new Date().toISOString(),
767+
updated_at: new Date().toISOString()
768+
} satisfies GitHubUserDetailed,
716769
overview: isRealUser ? [
717770
{ name: 'linux', commits: 145, stars: 150000, repos: 1 },
718771
{ name: 'subsurface', commits: 95, stars: 2500, repos: 1 },

src/lib/api/github-graphql-client.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ interface Issue {
3535
repository: {
3636
name: string
3737
nameWithOwner: string
38-
stargazerCount: number
38+
stargazerCount: number | null
3939
primaryLanguage: { name: string } | null
4040
owner: {
4141
login: string
@@ -169,7 +169,7 @@ class GitHubGraphQLClient {
169169
})
170170

171171
const filteredIssues = result.data.search.nodes
172-
.filter(issue => issue.repository.stargazerCount >= 5)
172+
.filter(issue => issue.repository?.stargazerCount && issue.repository.stargazerCount >= 5)
173173
.map(issue => this.mapIssueToGitHubIssue(issue))
174174

175175
return filteredIssues
@@ -259,7 +259,7 @@ class GitHubGraphQLClient {
259259
)
260260

261261
const filteredIssues = uniqueIssues
262-
.filter(issue => issue.repository?.stargazerCount >= 5)
262+
.filter(issue => issue.repository?.stargazerCount && issue.repository.stargazerCount >= 5)
263263
.map(issue => this.mapIssueToGitHubIssue(issue))
264264

265265
return filteredIssues
@@ -279,7 +279,7 @@ class GitHubGraphQLClient {
279279
updated_at: issue.updatedAt,
280280
difficulty: 'easy' as const,
281281
language: issue.repository.primaryLanguage?.name || 'unknown',
282-
stars: issue.repository.stargazerCount,
282+
stars: issue.repository.stargazerCount || 0,
283283
author: {
284284
login: issue.author?.login || 'unknown',
285285
avatar_url: issue.author?.avatarUrl || ''

0 commit comments

Comments
 (0)