Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions src/hooks/useGitHubRepos.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { useState, useCallback } from 'react';
import { Octokit } from '@octokit/core';

export interface GitHubRepo {
id: number;
name: string;
full_name: string;
html_url: string;
description: string | null;
language: string | null;
stargazers_count: number;
forks_count: number;
open_issues_count: number;
visibility: string;
fork: boolean;
pushed_at: string;
created_at: string;
updated_at: string;
topics: string[];
license: { name: string } | null;
default_branch: string;
size: number;
}

export const useGitHubRepos = (getOctokit: () => Octokit | null) => {
const [repos, setRepos] = useState<GitHubRepo[]>([]);
const [totalRepos, setTotalRepos] = useState(0);
const [loading, setLoading] = useState(false);
const [error, setError] = useState('');

const fetchRepos = useCallback(
async (username: string, page = 1, perPage = 12) => {
const octokit = getOctokit();
if (!octokit || !username.trim()) return;

setLoading(true);
setError('');

try {
const response = await octokit.request('GET /users/{username}/repos', {
username,
per_page: perPage,
page,
sort: 'pushed',
direction: 'desc',
type: 'owner',
});
Comment on lines +40 to +47
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the hook implementation around the referenced lines
sed -n '1,200p' src/hooks/useGitHubRepos.ts

# Inspect Tracker usage for toast.promise contract
sed -n '1,240p' src/pages/Tracker/Tracker.tsx

Repository: GitMetricsLab/github_tracker

Length of output: 9576


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate toast.promise / handleSubmit usage in Tracker.tsx
rg -n "toast\.promise|handleSubmit|Promise\.all\(\[|fetchRepos\(" src/pages/Tracker/Tracker.tsx

# Print the relevant region around handleSubmit/toast.promise (limit output)
sed -n '1,260p' src/pages/Tracker/Tracker.tsx
sed -n '260,560p' src/pages/Tracker/Tracker.tsx
sed -n '560,920p' src/pages/Tracker/Tracker.tsx

Repository: GitMetricsLab/github_tracker

Length of output: 37926


🌐 Web query:

GitHub REST API GET /users/{username}/repos private repositories authenticated visibility=all include private

💡 Result:

GitHub REST API does not support the exact endpoint/parameters in your query. 1) Endpoint mismatch - GET /users/{username}/repos is the endpoint for listing repositories for a specified user, but the REST API docs describe a limited set of query parameters (not a visibility=all style “include private” for that endpoint) and it’s not the authenticated-user listing endpoint. [1] 2) Correct endpoint for “include private” - To list both public and private repositories, you should use the “list repositories for the authenticated user” endpoint: GET /user/repos. [1] - That endpoint supports a visibility query parameter with allowed values all, public, private; default is all. [1] - Therefore, to include private repos, use visibility=private (or visibility=all) together with authentication. [1] 3) Authentication/scope requirement - Private repositories are returned only if the authenticated token/app has sufficient OAuth scope/permissions; generally GitHub requires appropriate repo/user permissions for private data. A related users endpoint notes that OAuth tokens need the repo scope to include private information. [2] Practical example (REST): - GET https://api.github.com/user/repos?visibility=all - Authorization: Bearer (must be authorized to access the private repos) [1][2] If you tell me whether you’re using a Personal Access Token, GitHub App installation token, or OAuth app token (and which scopes/permissions you have), I can recommend the exact visibility/affiliation/type filters and the right auth headers.

Citations:


Fix repo fetching: private-repo endpoint, pagination count, and toast success/error contract

  • useGitHubRepos requests GET /users/{username}/repos (with type: 'owner'), which won’t include private repositories even with a PAT; switch to the authenticated-user endpoint (GET /user/repos) and pass visibility=all|private (with proper token scopes).
    const response = await octokit.request('GET /users/{username}/repos', {
      username,
      per_page: perPage,
      page,
      sort: 'pushed',
      direction: 'desc',
      type: 'owner',
    });
  • totalRepos multiplies the rel="last" page number by perPage, which overestimates when the last page is partial.
  • fetchRepos swallows errors (sets error state but never rethrows), so Tracker.tsx’s toast.promise(Promise.all([p1, p2])) can show the success message even when repos fail to load; make fetchRepos reject on failure (or change toast to use reposError).


const linkHeader = (response.headers as any)?.link ?? '';
const lastMatch = linkHeader.match(/page=(\d+)>; rel="last"/);
const total = lastMatch
? parseInt(lastMatch[1], 10) * perPage
: (page - 1) * perPage + response.data.length;

setRepos(response.data as GitHubRepo[]);
setTotalRepos(total);
Comment on lines +49 to +56
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

totalRepos is wrong when the last page is not full.

Multiplying rel="last" by perPage overcounts for any user whose final page has fewer than perPage repositories. A user with 13 repos and perPage=12 will be reported as 24 here, which then breaks the repo stat card and pagination bounds.

🧰 Tools
🪛 ESLint

[error] 49-49: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/hooks/useGitHubRepos.ts` around lines 49 - 56, The code incorrectly
computes totalRepos by always multiplying the rel="last" page number by perPage;
change the logic in useGitHubRepos to parse last page via lastMatch (keep
linkHeader, lastMatch, perPage, page, response.data.length, setTotalRepos) and
compute total as (lastPage - 1) * perPage + itemsOnLastPage, where
itemsOnLastPage should be response.data.length when the current page is the last
page (lastPage === page) and perPage otherwise; if you need exact totals even
when the current page is not the last, perform an extra fetch for the last page
to read its length and then setTotalRepos accordingly.

} catch (err: any) {
const status = err?.status;
const message = err?.message?.toLowerCase() ?? '';

if (status === 403) {
setError('GitHub API rate limit exceeded. Please provide a PAT to continue.');
} else if (status === 404 || message.includes('not found')) {
setError('User not found. Please check the GitHub username.');
} else if (status === 401) {
setError('Invalid token. Please check your Personal Access Token.');
} else {
setError('Unable to fetch repositories. Please verify the username or network connection.');
Comment on lines +57 to +68
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Propagate repo fetch failures back to the caller.

This catch path only updates local state, so fetchRepos still resolves successfully. In Tracker.tsx, handleSubmit wraps it in toast.promise, which means a repo-only failure still produces the success toast. Re-throw after setError(...) or return an explicit failure result so the caller can surface the right outcome.

🧰 Tools
🪛 ESLint

[error] 57-57: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/hooks/useGitHubRepos.ts` around lines 57 - 68, The catch block in
useGitHubRepos.ts swallowing errors (inside fetchRepos) only calls setError and
lets the promise resolve, so callers like Tracker.tsx's handleSubmit (which uses
toast.promise) incorrectly show success; after each setError branch in the
catch, propagate the failure by re-throwing the original error (or return
Promise.reject(err)) so fetchRepos rejects and the caller can surface the
correct toast/result—update the catch in fetchRepos to throw err (or reject)
after setError.

}
} finally {
setLoading(false);
}
},
[getOctokit]
);

return { repos, totalRepos, loading, error, fetchRepos };
};
Loading
Loading