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}`;
|
|
|
|
|
|
/**
|
|
* 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
|
|
*/
|
|
function queryGithubAPIRaw(requestURL)
|
|
{
|
|
return new Promise((resolve, reject)=>
|
|
{
|
|
var queryURL;
|
|
if(requestURL.includes("?page="))
|
|
{
|
|
queryURL = GITHUB_API + requestURL + "&" + authenticate;
|
|
}
|
|
else
|
|
{
|
|
queryURL = GITHUB_API + requestURL + "?" + authenticate;
|
|
}
|
|
console.log(queryURL);
|
|
|
|
got(queryURL, { json: true }).then(response =>
|
|
{
|
|
resolve(response.body);
|
|
cache.put(requestURL, response.body);
|
|
}).catch(error =>
|
|
{
|
|
resolve(error);
|
|
cache.put(requestURL, error);
|
|
});
|
|
});
|
|
}
|
|
|
|
|
|
/**
|
|
* Queries data from the github api server
|
|
* and caches the results locally.
|
|
*
|
|
* @param {*} requestURL
|
|
*/
|
|
function queryGitHubAPI(requestURL)
|
|
{
|
|
const apiData = cache.get(requestURL);
|
|
|
|
return new Promise(function(resolve, reject)
|
|
{
|
|
if(apiData == null)
|
|
{
|
|
queryGithubAPIRaw(requestURL).then((dd)=>
|
|
{
|
|
resolve(dd);
|
|
}).catch((err)=>
|
|
{
|
|
resolve(err);
|
|
})
|
|
}
|
|
else
|
|
{
|
|
console.log("Fetched From Cache");
|
|
resolve(apiData);
|
|
}
|
|
})
|
|
}
|
|
|
|
|
|
const API_FOLLOWING = "/following";
|
|
const API_FOLLOWERS = "/followers";
|
|
const API_USER_PATH = "/users/";
|
|
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 = 3;
|
|
const API_PAGINATION = "&per_page=" + API_PAGINATION_SIZE;
|
|
|
|
|
|
/**
|
|
* This will fetch all of the either followers or following of a
|
|
* github user.
|
|
*
|
|
* This function is recursive to traverse all the pagination so we
|
|
* can get a complete list of all the friends. The max amount of
|
|
* followers/following you can get at once is 100.
|
|
*
|
|
* @param {*} username username of github client
|
|
* @param {*} apiPath following or followers
|
|
* @param {*} page current pagination page
|
|
* @param {*} lst list we are building on
|
|
*/
|
|
function fetchAllUsers(username, apiPath, page, lst)
|
|
{
|
|
return new Promise((resolve, reject)=>
|
|
{
|
|
queryGithubAPIRaw(API_USER_PATH + username + apiPath + "?page=" + page + API_PAGINATION).then((data)=>
|
|
{
|
|
if(data.hasOwnProperty("length"))
|
|
{
|
|
lst = lst.concat(data)
|
|
if(page < API_MAX_PAGES && data.length === API_PAGINATION_SIZE)
|
|
{
|
|
fetchAllUsers(username, apiPath, page + 1, lst).then((l)=>
|
|
{
|
|
resolve(l);
|
|
});
|
|
}
|
|
else
|
|
{
|
|
resolve(lst);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
reject("Malformed data");
|
|
}
|
|
}).catch((err)=>
|
|
{
|
|
reject("error with api request");
|
|
});
|
|
},
|
|
(error)=>
|
|
{
|
|
if(error.hasOwnProperty("length"))
|
|
{
|
|
lst.concat(data);
|
|
resolve(lst);
|
|
}
|
|
});
|
|
}
|
|
|
|
|
|
/**
|
|
* 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
|
|
*/
|
|
function minimizeFriends(people)
|
|
{
|
|
var friendLst = [];
|
|
|
|
var ids = new Set();
|
|
|
|
for(var i = 0; i < people.length; i++)
|
|
{
|
|
if(!ids.has(people[i].id))
|
|
{
|
|
ids.add(people[i].id);
|
|
friendLst.push({
|
|
id: people[i].id,
|
|
login: people[i].login,
|
|
avatar_url: people[i].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
|
|
*/
|
|
function queryFriends(user)
|
|
{
|
|
const cacheHit = cache.get("/friends/" + user);
|
|
return new Promise((resolve, reject)=>
|
|
{
|
|
if(cacheHit == null)
|
|
{
|
|
fetchAllUsers(user, API_FOLLOWERS, 1, []).then((followers)=>
|
|
{
|
|
fetchAllUsers(user, API_FOLLOWING, 1, []).then((following)=>
|
|
{
|
|
var fList = minimizeFriends(following.concat(followers));
|
|
resolve(fList);
|
|
cache.put("/friends/" + user, fList);
|
|
}).catch((err)=>
|
|
{
|
|
console.log(err);
|
|
})
|
|
}).catch((error)=>
|
|
{
|
|
console.log(error);
|
|
})
|
|
}
|
|
else
|
|
{
|
|
console.log("Friends cache hit");
|
|
resolve(cacheHit);
|
|
}
|
|
});
|
|
}
|
|
|
|
|
|
routes.get("/friends/:name", (request, result)=>
|
|
{
|
|
queryFriends(request.params.name).then(friends=>
|
|
{
|
|
result.json(friends)
|
|
.end();
|
|
}).catch(error=>
|
|
{
|
|
result.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;
|