-
-
Notifications
You must be signed in to change notification settings - Fork 22
feat: Add GitHub link with star count to navbar #2926
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 3 commits
843812c
8056516
30ec638
d3de002
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| export function useGitHubStars(repo: string) { | ||
| const stars = ref<string | null>(null) | ||
|
|
||
| if (import.meta.client) { | ||
| fetch(`/api/githubStars?repo=${encodeURIComponent(repo)}`) | ||
| .then((res) => { | ||
| if (!res.ok) { | ||
| throw new Error(`Failed to fetch GitHub stars: ${res.statusText}`) | ||
| } | ||
| return res.json() | ||
| }) | ||
| .then((data: { stars?: number }) => { | ||
| if (data.stars !== undefined) { | ||
| stars.value = formatStarCount(data.stars) | ||
| } | ||
| }) | ||
| .catch((error) => { | ||
| console.debug('Failed to fetch GitHub stars:', error) | ||
| }) | ||
| } | ||
|
|
||
| return { stars } | ||
| } | ||
|
|
||
| function formatStarCount(count: number): string { | ||
| if (count >= 1000) { | ||
| return `${(count / 1000).toFixed(1).replace(/\.0$/, '')}K` | ||
| } | ||
| return count.toString() | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,31 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export default defineEventHandler(async (event) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const query = getQuery(event) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const repo = query.repo as string | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!repo) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw createError({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| statusCode: 400, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| message: 'Missing repo parameter', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const response = await fetch(`https://api.github.com/repos/${repo}`) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | |
| const response = await fetch(`https://api.github.com/repos/${repo}`) | |
| const config = useRuntimeConfig() | |
| const githubToken = (config as { githubRepoToken?: string }).githubRepoToken | |
| try { | |
| const headers: Record<string, string> = { | |
| 'User-Agent': 'jabref-online', | |
| Accept: 'application/vnd.github.v3+json', | |
| } | |
| if (githubToken) { | |
| headers.Authorization = `token ${githubToken}` | |
| } | |
| const response = await fetch(`https://api.github.com/repos/${repo}`, { | |
| headers, | |
| }) |
Copilot
AI
Mar 20, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The PR description mentions caching, but this endpoint currently fetches GitHub on every request. In production this is likely to hit GitHub’s unauthenticated rate limits and adds latency to every page view. Consider adding server-side caching (e.g., Nitro storage-based TTL cache / cached event handler) and/or setting appropriate cache headers so repeated requests reuse the cached star count for a period (e.g., 5–30 minutes).
| export default defineEventHandler(async (event) => { | |
| const query = getQuery(event) | |
| const repo = query.repo as string | |
| if (!repo) { | |
| throw createError({ | |
| statusCode: 400, | |
| message: 'Missing repo parameter', | |
| }) | |
| } | |
| try { | |
| const response = await fetch(`https://api.github.com/repos/${repo}`) | |
| if (!response.ok) { | |
| throw new Error(`GitHub API responded with ${response.status}`) | |
| } | |
| const data = (await response.json()) as { stargazers_count?: number } | |
| return { | |
| stars: data.stargazers_count ?? 0, | |
| } | |
| } catch (error) { | |
| console.debug('Failed to fetch GitHub stars for repo', repo, error) | |
| throw createError({ | |
| statusCode: 500, | |
| message: 'Failed to fetch GitHub stars', | |
| }) | |
| } | |
| }) | |
| export default defineCachedEventHandler( | |
| async (event) => { | |
| const query = getQuery(event) | |
| const repo = query.repo as string | |
| if (!repo) { | |
| throw createError({ | |
| statusCode: 400, | |
| message: 'Missing repo parameter', | |
| }) | |
| } | |
| // Allow shared caches (CDN/reverse proxy) to reuse this response. | |
| // This is aligned with the server-side TTL below. | |
| event.node.res.setHeader( | |
| 'Cache-Control', | |
| 'public, s-maxage=300, stale-while-revalidate=600', | |
| ) | |
| try { | |
| const response = await fetch(`https://api.github.com/repos/${repo}`) | |
| if (!response.ok) { | |
| throw new Error(`GitHub API responded with ${response.status}`) | |
| } | |
| const data = (await response.json()) as { stargazers_count?: number } | |
| return { | |
| stars: data.stargazers_count ?? 0, | |
| } | |
| } catch (error) { | |
| console.debug('Failed to fetch GitHub stars for repo', repo, error) | |
| throw createError({ | |
| statusCode: 500, | |
| message: 'Failed to fetch GitHub stars', | |
| }) | |
| } | |
| }, | |
| { | |
| // Cache the computed star count on the server for 5 minutes | |
| maxAge: 300, | |
| }, | |
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
repois taken directly from the query string and interpolated into the GitHub API path without validation/normalization. Since the UI always queries a single repo, this effectively exposes a public proxy that can be abused to trigger arbitrary GitHub API calls (and burn rate limit). Prefer hardcoding the JabRef repo in the handler (norepoparam), or strictly validate the parameter (e.g.,owner/namewith a tight regex) and reject anything else.