const routes = require('express').Router();
|
|
const got = require("got");
|
|
const cache = require('memory-cache');
|
|
const dotenv = require("dotenv").config();
|
|
const GITHUB_API = "https://api.github.com";
|
|
const authenticate = `client_id=${process.env.CLIENT_ID}&client_secret=${process.env.CLIENT_SECRET}`;
|
|
|
|
const API_FOLLOWING = "/following";
|
|
const API_FOLLOWERS = "/followers";
|
|
const API_USER_PATH = "/users/";
|
|
const API_ORGS_PATH = "/orgs/";
|
|
const API_PAGINATION_SIZE = 100; // 100 is the max, 30 is the default
|
|
// if this is too large, it would be infeasible to make graphs for people following popular people
|
|
const API_MAX_PAGES = 2;
|
|
const API_PAGINATION = "&per_page=" + API_PAGINATION_SIZE;
|
|
|
|
const REPOS_PATH = "/repos";
|
|
|
|
|
|
/**
|
|
* Queries data from the github APi server and returns it as
|
|
* a json object in a promise.
|
|
*
|
|
* This makes no attempt to cache
|
|
*
|
|
* @param {*} requestURL endpoint on githubapi: ex: /users/jrtechs/following
|
|
*/
|
|
const queryGithubAPIRaw = async requestURL => {
|
|
let queryURL = requestURL.includes("?page=") ? `${GITHUB_API}${requestURL}&${authenticate}` :`${GITHUB_API}${requestURL}?${authenticate}`;
|
|
console.log(queryURL);
|
|
try {
|
|
const req = await got(queryURL, { json: true });
|
|
cache.put(requestURL, req);
|
|
return req;
|
|
} catch (error) {
|
|
console.log(error);
|
|
cache.put(requestURL, `${error.statusCode} - ${error.statusMessage}`);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Queries data from the github api server
|
|
* and caches the results locally.
|
|
*
|
|
* @param {*} requestURL
|
|
*/
|
|
const queryGitHubAPI = async requestURL => {
|
|
const apiData = cache.get(requestURL);
|
|
if (apiData) {
|
|
console.log("Fetched From Cache");
|
|
return apiData
|
|
}
|
|
|
|
try {
|
|
return await queryGithubAPIRaw(requestURL);
|
|
} catch (error) {
|
|
console.log(error);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Fetches all content from a particular github api endpoint
|
|
* using their pagination schema.
|
|
*
|
|
* @param {*} username username of github client
|
|
* @param {*} apiPath following or followers
|
|
* @param {*} page current pagination page
|
|
* @param {*} lst list we are building on
|
|
*/
|
|
const fetchAllWithPagination = async (apiPath, page, lst) => {
|
|
try {
|
|
const req = await queryGithubAPIRaw(`${apiPath}?page=${page}${API_PAGINATION}`);
|
|
if (req.body.hasOwnProperty("length"))
|
|
{
|
|
const list = lst.concat(req.body);
|
|
console.log(req.body.length);
|
|
//console.log(req.body);
|
|
if(page < API_MAX_PAGES && req.body.length === API_PAGINATION_SIZE) {
|
|
const redo = await fetchAllWithPagination(apiPath, page + 1, list);
|
|
return redo;
|
|
}
|
|
return list;
|
|
}
|
|
} catch (error) {
|
|
console.log("Error with api request");
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Makes a copy of a JS object with certain properties
|
|
*
|
|
* @param {*} props
|
|
* @param {*} obj
|
|
*/
|
|
const copyWithProperties = (props, obj) => {
|
|
let newO = new Object();
|
|
props.forEach(prop => {
|
|
newO[prop] = obj[prop];
|
|
})
|
|
return newO;
|
|
}
|
|
|
|
|
|
/**
|
|
* Combines the list of friends and followers ignoring duplicates
|
|
* that are already in the list. (person is both following and followed by someone)
|
|
*
|
|
* This also removes any unused properties like events_url and organizations_url
|
|
*
|
|
* @param {*} followingAndFollowers
|
|
*/
|
|
const minimizeFriends = people => {
|
|
let friendLst = [];
|
|
let ids = new Set();
|
|
people.forEach(person => {
|
|
if(!ids.has(person.id)) {
|
|
ids.add(person.id);
|
|
friendLst.push({
|
|
login: person.login,
|
|
avatar_url: person.avatar_url
|
|
});
|
|
}
|
|
});
|
|
return friendLst;
|
|
}
|
|
|
|
|
|
/**
|
|
* Fetches all the people that are either following or is followed
|
|
* by a person on github. This will cache the results to make simultaneous
|
|
* connections easier and less demanding on the github API.
|
|
*
|
|
* @param {*} user
|
|
*/
|
|
const queryFriends = async user => {
|
|
const cacheHit = cache.get("/friends/" + user);
|
|
if (cacheHit){
|
|
console.log("Friends cache hit");
|
|
return cacheHit;
|
|
}
|
|
|
|
try {
|
|
const followers = await fetchAllWithPagination(API_USER_PATH + user + API_FOLLOWERS, 1, []);
|
|
const following = await fetchAllWithPagination(API_USER_PATH + user + API_FOLLOWING, 1, []);
|
|
const fList = minimizeFriends(following.concat(followers));
|
|
cache.put(`/friends/${user}`, fList);
|
|
return fList;
|
|
} catch (error) {
|
|
console.log("API Error", err);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
*
|
|
* Fetches all of the members of an organization from the
|
|
* API or cache
|
|
*
|
|
* /orgs/RITlug/members?page=1
|
|
*
|
|
* @param {*} orgName
|
|
*/
|
|
const getOrganizationMembers = async orgName => {
|
|
const cacheHit = cache.get("/org/users/" + orgName);
|
|
if (cacheHit){
|
|
console.log("Org members cache hit");
|
|
return cacheHit;
|
|
}
|
|
|
|
try {
|
|
const members = await fetchAllWithPagination(API_ORGS_PATH + orgName + "/members", 1, []);
|
|
const membersMin = minimizeFriends(members);
|
|
cache.put("/org/users/" + orgName, membersMin);
|
|
return membersMin;
|
|
} catch (error) {
|
|
console.log(error);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Minimizes the JSON for a list of repositories
|
|
*
|
|
* @param {*} repositories
|
|
*/
|
|
const minimizeRepositories = repositories => {
|
|
let rList = [];
|
|
repositories.forEach(repo => {
|
|
rList.push(copyWithProperties(["name", "created_at", "homepage",
|
|
"description", "language", "forks", "watchers",
|
|
"open_issues_count", "license", "html_url"],
|
|
repo));
|
|
})
|
|
|
|
return rList;
|
|
}
|
|
|
|
|
|
/**
|
|
* Fetches all repositories from the API
|
|
*
|
|
* @param {*} user name of org/user
|
|
* @param {*} orgsOrUsers either /users/ or /orgs/
|
|
*/
|
|
const queryRepositories = async (user, orgsOrUsers) => {
|
|
const cacheHit = cache.get(user + REPOS_PATH);
|
|
if (cacheHit) {
|
|
console.log("Repositories cache hit");
|
|
return cacheHit;
|
|
}
|
|
|
|
try {
|
|
const repos = await fetchAllWithPagination(orgsOrUsers + user + REPOS_PATH, 1, []);
|
|
const minRepos = minimizeRepositories(repos);
|
|
cache.put(`${user}${REPOS_PATH}`, minRepos);
|
|
return minRepos;
|
|
} catch (error) {
|
|
console.log(error)
|
|
console.log("bad things went down");
|
|
}
|
|
}
|
|
|
|
const EVENTS_PATH = "/events";
|
|
|
|
|
|
/**
|
|
*
|
|
* @param {https://api.github.com/repos/jrtechs/bash_manager/events?page=1&per_page=100} repositoryName
|
|
*/
|
|
const queryRepositoryEvents = async(user, repositoryName) =>
|
|
{
|
|
console.log()
|
|
try {
|
|
const repo_events = await fetchAllWithPagination(`${REPOS_PATH}/${user}/${repositoryName}${EVENTS_PATH}`, 1, []);
|
|
// const minRepos = minimizeRepositories(repos);
|
|
// cache.put(`${user}${REPOS_PATH}`, minRepos);
|
|
return repo_events;
|
|
} catch (error) {
|
|
console.log(error)
|
|
console.log("bad things went down");
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Minimizes the JSON for a list of commits
|
|
*
|
|
* @param {*} commits
|
|
*/
|
|
const minimizeCommits = commits => {
|
|
let cList = [];
|
|
commits.forEach(c =>
|
|
{
|
|
let obj = new Object();
|
|
obj.message = c.commit.message;
|
|
obj.date = c.commit.author.date;
|
|
obj.sha = c.sha;
|
|
if(c.author == null)
|
|
{
|
|
obj.name = c.commit.author.name;
|
|
}
|
|
else
|
|
{
|
|
obj.name = c.author.login;
|
|
}
|
|
cList.push(obj);
|
|
});
|
|
|
|
return cList;
|
|
}
|
|
|
|
|
|
const COMMITS_PATH = "/commits";
|
|
|
|
/**
|
|
* TODO figure out if we should cache this because commits
|
|
* frequently change opposed to friends
|
|
*
|
|
* @param {https://api.github.com/repos/jrtechs/bash_manager/commits?page=1&per_page=100} repositoryName
|
|
*/
|
|
const queryRepositoryCommits = async(user, repositoryName) =>
|
|
{
|
|
try
|
|
{
|
|
const path = `${REPOS_PATH}/${user}/${repositoryName}${COMMITS_PATH}`;
|
|
|
|
const repo_commits = await fetchAllWithPagination(path, 1, []);
|
|
const min_commits = minimizeCommits(repo_commits);
|
|
// cache.put(`${user}${REPOS_PATH}`, minRepos);
|
|
return min_commits;
|
|
} catch (error) {
|
|
console.log(error)
|
|
console.log("error fetching commits");
|
|
}
|
|
}
|
|
|
|
|
|
routes.get("/repositories/events/:name/:repository", async (req, res)=>
|
|
{
|
|
try
|
|
{
|
|
const query = await queryRepositoryEvents(req.params.name, req.params.repository);
|
|
res.json(query);
|
|
} catch (error) {
|
|
res.status(500).json({error: 'API error fetching repository events'});
|
|
}
|
|
});
|
|
|
|
|
|
|
|
routes.get("/repositories/commits/:name/:repository", async (req, res)=>
|
|
{
|
|
try
|
|
{
|
|
const query = await queryRepositoryCommits(req.params.name, req.params.repository);
|
|
res.json(query);
|
|
} catch (error) {
|
|
res.status(500).json({error: 'API error fetching repository events'});
|
|
}
|
|
});
|
|
|
|
|
|
/**
|
|
* /users/name/following/followers
|
|
*/
|
|
routes.get("/friends/:name", async (req, res)=> {
|
|
try {
|
|
const query = await queryFriends(req.params.name);
|
|
res.json(query);
|
|
} catch (error) {
|
|
res.status(500).json({error: 'API error fetching friends'});
|
|
}
|
|
});
|
|
|
|
|
|
routes.get("/org/users/:name", async (request, res) => {
|
|
try {
|
|
const orgMembers = await getOrganizationMembers(request.params.name);
|
|
res.json(orgMembers).end();
|
|
} catch (error) {
|
|
res.status(500).json({error: 'API error fetching friends'}).end();
|
|
}
|
|
});
|
|
|
|
|
|
routes.get("/repositories/:name", async (req, res) => {
|
|
try {
|
|
const repos = await queryRepositories(req.params.name, API_USER_PATH);
|
|
res.json(repos).end();
|
|
} catch (error) {
|
|
res.status(500).json({error: 'API error fetching friends'}).end();
|
|
}
|
|
});
|
|
|
|
|
|
routes.get("/org/repositories/:name", async (req, res) => {
|
|
try {
|
|
const repos = await queryRepositories(req.params.name, API_ORGS_PATH);
|
|
res.json(repos).end();
|
|
} catch (error) {
|
|
res.status(500).json({error: 'API error fetching friends'}).end();
|
|
}
|
|
});
|
|
|
|
|
|
routes.get('/*', (request, result) =>
|
|
{
|
|
var gitHubAPIURL = request.url;
|
|
|
|
result.setHeader('Content-Type', 'application/json');
|
|
queryGitHubAPI(gitHubAPIURL).then((data)=>
|
|
{
|
|
if(data.hasOwnProperty("id") || data[0].hasOwnProperty("id"))
|
|
{
|
|
result.write(JSON.stringify(data));
|
|
}
|
|
else
|
|
{
|
|
result.write("[]");
|
|
}
|
|
result.end();
|
|
}).catch((error)=>
|
|
{
|
|
try
|
|
{
|
|
if(error.hasOwnProperty("id") || error[0].hasOwnProperty("id"))
|
|
{
|
|
result.write(JSON.stringify(error));
|
|
}
|
|
else
|
|
{
|
|
result.write("[]");
|
|
}
|
|
|
|
}
|
|
catch(error)
|
|
{
|
|
result.write("[]");
|
|
};
|
|
result.end();
|
|
});
|
|
|
|
if(cache.size() > 50000)
|
|
{
|
|
cache.clear();
|
|
}
|
|
});
|
|
|
|
module.exports = routes;
|