Skip to content

Commit

Permalink
add cache
Browse files Browse the repository at this point in the history
  • Loading branch information
yunwei37 committed Jun 15, 2023
1 parent 08c3cff commit fe858e1
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 27 deletions.
36 changes: 36 additions & 0 deletions lib/cache.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
interface CacheEntry<T> {
timestamp: number;
data: T;
}

class StatCache<T> {
private data: Record<string, CacheEntry<T>>;
private maxAge: number;

constructor(maxAge = 300000) { // Default to 5 minutes
this.data = {};
this.maxAge = maxAge;
}

get(key: string): T | null {
const entry = this.data[key];

if (!entry) {
return null;
}

if (Date.now() - entry.timestamp > this.maxAge) {
delete this.data[key];
return null;
}

return entry.data;
}

set(key: string, value: T): void {
this.data[key] = {
timestamp: Date.now(),
data: value,
};
}
}
52 changes: 35 additions & 17 deletions lib/statsFetcher.js → lib/statsFetcher.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {calculateRank} from './calculateRank.js';
import {calculateRank} from './calculateRank';

const GRAPHQL_REPOS_FIELD = `
repositories(first: 100, ownerAffiliations: OWNER, orderBy: {direction: DESC, field: STARGAZERS}, after: $after) {
Expand Down Expand Up @@ -52,7 +52,13 @@ const GRAPHQL_STATS_QUERY = `
}
`;

const fetcher = async (variables, token) => {
interface Variables {
login: string;
first: number;
after: string | null;
}

const fetcher = async (variables: Variables, token: string) => {
const query = !variables.after ? GRAPHQL_STATS_QUERY : GRAPHQL_REPOS_QUERY;
const response = await fetch('https://api.github.com/graphql', {
method: 'POST',
Expand All @@ -65,14 +71,17 @@ const fetcher = async (variables, token) => {
return response.json();
};

const statsFetcher = async (username) => {
const statsFetcher = async (username: string) => {
let stats;
let hasNextPage = true;
let endCursor = null;
const token = process.env.GITHUB_TOKEN;
const token = process.env.REACT_APP_GITHUB_ACCESS_TOKEN;
if (!token) {
throw Error("token is required");
}

while (hasNextPage) {
const variables = { login: username, first: 100, after: endCursor };
const variables: Variables = { login: username, first: 100, after: endCursor };
let res = await fetcher(variables, token);

if (res.errors) {
Expand All @@ -88,18 +97,29 @@ const statsFetcher = async (username) => {
}

hasNextPage = repoNodes.length === repoNodes.filter(
(node) => node.stargazers.totalCount !== 0,
(node: { stargazers: { totalCount: number; }; }) => node.stargazers.totalCount !== 0,
).length && res.data.user.repositories.pageInfo.hasNextPage;
endCursor = res.data.user.repositories.pageInfo.endCursor;
}

return stats;
};

const fetchStats = async (username) => {
interface Stats {
name: string;
totalPRs: number;
totalCommits: number;
totalIssues: number;
totalStars: number;
contributedTo: number;
rank: { level: string; percentile: number };
mostStarredRepos?: string[];
}

const fetchStats = async (username: string): Promise<Stats> => {
if (!username) throw new Error("Username is required");

const stats = {
const stats: Stats = {
name: "",
totalPRs: 0,
totalCommits: 0,
Expand All @@ -112,16 +132,17 @@ const fetchStats = async (username) => {
let res = await statsFetcher(username);
if (!res) {
console.error("Failed to fetch stats");
return "Failed to fetch stats";
throw Error("Failed to fetch stats");
}
if (res.errors) {
console.error(res.errors);
return "Failed to fetch stats: " + JSON.stringify(res.errors);
throw Error("Failed to fetch stats with errors: " + JSON.stringify(res.errors));
}

const user = res.data.user;
if (!user) {
return "User not found: " + JSON.stringify(res);
console.error("Failed to fetch user");
throw Error("Failed to fetch user");
}

stats.name = user.name || user.login;
Expand All @@ -130,20 +151,17 @@ const fetchStats = async (username) => {
stats.totalIssues = user.openIssues.totalCount + user.closedIssues.totalCount;
stats.contributedTo = user.repositoriesContributedTo.totalCount;

// {"name":"modern-cpp-template","stargazers":{"totalCount":0}}
stats.totalStars = user.repositories.nodes.reduce((prev, curr) => {
stats.totalStars = user.repositories.nodes.reduce((prev: any, curr: { stargazers: { totalCount: any; }; }) => {
return prev + curr.stargazers.totalCount;
}, 0);

// Get the five most starred repositories
const mostStarredRepos = user.repositories.nodes
.sort((a, b) => b.stargazers.totalCount - a.stargazers.totalCount)
.sort((a: { stargazers: { totalCount: number; }; }, b: { stargazers: { totalCount: number; }; }) => b.stargazers.totalCount - a.stargazers.totalCount)
.slice(0, 8)
.map(repo => repo.name);
.map((repo: { name: any; }) => repo.name);

stats.mostStarredRepos = mostStarredRepos;

// Calculate rank here...
stats.rank = calculateRank({
all_commits: false,
commits: stats.totalCommits,
Expand Down
25 changes: 15 additions & 10 deletions lib/userData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ interface UserStats {
level: string;
percentile: number;
};
error: string;
}

import { fetchStats } from './statsFetcher';
Expand All @@ -38,26 +39,30 @@ export default async function generateUserStats(owner: string): Promise<UserStat
const data = await response.json();
// calculate the owner's more GitHub stats
console.log(`Fetching stats for ${owner}...`);
const stats = await fetchStats(owner);
// if stats are string, the full data only contains the error message for stats
// merge the data and stats
let fullData: { [x: string]: any; };
if (typeof stats === "string") {
let fullData = {
...data,
};
try {
const stats = await fetchStats(owner);
// if stats are string, the full data only contains the error message for stats
// merge the data and stats
fullData = {
...data,
stats: stats
...stats
};
} else {
console.log(`Stats for ${owner} fetched!`);

} catch (error) {
console.log(`Error fetching stats for ${owner}: ${error}`);
fullData = {
...data,
...stats
error: JSON.stringify(error)
};
}

// Keys to be kept
const keysToKeep: (keyof UserStats)[] = ["html_url", "type", "name", "company", "blog", "location", "email", "hireable", "bio",
"twitter_username", "public_repos", "public_gists", "followers", "following", "created_at",
"updated_at", "totalPRs", "totalCommits", "totalIssues", "totalStars", "contributedTo", "rank", "mostStarredRepos"];
"updated_at", "totalPRs", "totalCommits", "totalIssues", "totalStars", "contributedTo", "rank", "mostStarredRepos", "error"];

// Filter the full data to only include keys from the whitelist
const filteredData: UserStats = Object.keys(fullData)
Expand Down

1 comment on commit fe858e1

@vercel
Copy link

@vercel vercel bot commented on fe858e1 Jun 15, 2023

Choose a reason for hiding this comment

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

Please sign in to comment.