Website for visualizing a persons github network.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

354 lines
8.7 KiB

  1. const routes = require('express').Router();
  2. const got = require("got");
  3. const cache = require('memory-cache');
  4. const dotenv = require("dotenv").config();
  5. const GITHUB_API = "https://api.github.com";
  6. const authenticate = `client_id=${process.env.CLIENT_ID}&client_secret=${process.env.CLIENT_SECRET}`;
  7. const API_FOLLOWING = "/following";
  8. const API_FOLLOWERS = "/followers";
  9. const API_USER_PATH = "/users/";
  10. const API_ORGS_PATH = "/orgs/";
  11. const API_PAGINATION_SIZE = 100; // 100 is the max, 30 is the default
  12. // if this is too large, it would be infeasible to make graphs for people following popular people
  13. const API_MAX_PAGES = 2;
  14. const API_PAGINATION = "&per_page=" + API_PAGINATION_SIZE;
  15. const REPOS_PATH = "/repos";
  16. /**
  17. * Queries data from the github APi server and returns it as
  18. * a json object in a promise.
  19. *
  20. * This makes no attempt to cache
  21. *
  22. * @param {*} requestURL endpoint on githubapi: ex: /users/jrtechs/following
  23. */
  24. const queryGithubAPIRaw = async requestURL => {
  25. let queryURL = requestURL.includes("?page=")
  26. ? `${GITHUB_API}${requestURL}&${authenticate}`
  27. :`${GITHUB_API}${requestURL}?${authenticate}`;
  28. console.log(queryURL);
  29. try {
  30. const req = await got(queryURL, { json: true });
  31. cache.put(requestURL, req);
  32. return req;
  33. } catch (error) {
  34. console.log(error);
  35. cache.put(requestURL, `${error.statusCode} - ${error.statusMessage}`);
  36. }
  37. }
  38. /**
  39. * Queries data from the github api server
  40. * and caches the results locally.
  41. *
  42. * @param {*} requestURL
  43. */
  44. const queryGitHubAPI = async requestURL => {
  45. const apiData = cache.get(requestURL);
  46. if (apiData) {
  47. console.log("Fetched From Cache");
  48. return apiData
  49. }
  50. try {
  51. return await queryGithubAPIRaw(requestURL);
  52. } catch (error) {
  53. console.log(error);
  54. }
  55. }
  56. /**
  57. * Fetches all content from a particular github api endpoint
  58. * using their pagination schema.
  59. *
  60. * @param {*} username username of github client
  61. * @param {*} apiPath following or followers
  62. * @param {*} page current pagination page
  63. * @param {*} lst list we are building on
  64. */
  65. const fetchAllWithPagination = async (apiPath, page, lst) => {
  66. try {
  67. const req = await queryGithubAPIRaw(`${apiPath}?page=${page}${API_PAGINATION}`);
  68. if (req) {
  69. const list = lst.concat(req.body);
  70. if(page < API_MAX_PAGES && req.length === API_PAGINATION_SIZE) {
  71. const redo = await fetchAllWithPagination(apiPath, page + 1, list);
  72. return redo;
  73. }
  74. return list;
  75. }
  76. } catch (error) {
  77. console.log("Error with api request");
  78. }
  79. }
  80. /**
  81. * Makes a copy of a JS object with certain properties
  82. *
  83. * @param {*} props
  84. * @param {*} obj
  85. */
  86. function copyWithProperties(props, obj)
  87. {
  88. var newO = new Object();
  89. for(var i =0; i < props.length; i++)
  90. {
  91. newO[props[i]] = obj[props[i]];
  92. }
  93. return newO;
  94. }
  95. /**
  96. * Combines the list of friends and followers ignoring duplicates
  97. * that are already in the list. (person is both following and followed by someone)
  98. *
  99. * This also removes any unused properties like events_url and organizations_url
  100. *
  101. * @param {*} followingAndFollowers
  102. */
  103. function minimizeFriends(people)
  104. {
  105. var friendLst = [];
  106. var ids = new Set();
  107. for(var i = 0; i < people.length; i++)
  108. {
  109. if(!ids.has(people[i].id))
  110. {
  111. ids.add(people[i].id);
  112. friendLst.push({
  113. login: people[i].login,
  114. avatar_url: people[i].avatar_url
  115. });
  116. }
  117. }
  118. return friendLst;
  119. }
  120. /**
  121. * Fetches all the people that are either following or is followed
  122. * by a person on github. This will cache the results to make simultaneous
  123. * connections easier and less demanding on the github API.
  124. *
  125. * @param {*} user
  126. */
  127. const queryFriends = async user => {
  128. const cacheHit = cache.get("/friends/" + user);
  129. if (cacheHit){
  130. console.log("Friends cache hit");
  131. return cacheHit;
  132. }
  133. try {
  134. const followers = await fetchAllWithPagination(API_USER_PATH + user + API_FOLLOWERS, 1, []);
  135. const following = await fetchAllWithPagination(API_USER_PATH + user + API_FOLLOWING, 1, []);
  136. const fList = minimizeFriends(following.concat(followers));
  137. cache.put(`/friends/${user}`, fList);
  138. return fList;
  139. } catch (error) {
  140. console.log("API Error", err);
  141. }
  142. }
  143. /**
  144. *
  145. * Fetches all of the members of an organization from the
  146. * API or cache
  147. *
  148. * /orgs/RITlug/members?page=1
  149. *
  150. * @param {*} orgName
  151. */
  152. function getOrganizationMembers(orgName)
  153. {
  154. const cacheHit = cache.get("/org/users/" + orgName);
  155. return new Promise((resolve, reject)=>
  156. {
  157. if(cacheHit == null)
  158. {
  159. fetchAllWithPagination(API_ORGS_PATH + orgName + "/members", 1, []).then((mems)=>
  160. {
  161. var minimized = minimizeFriends(mems);
  162. resolve(minimized);
  163. cache.put("/org/users/" + orgName, minimized);
  164. }).catch((err)=>
  165. {
  166. console.log(err)
  167. })
  168. }
  169. else
  170. {
  171. console.log("Org members cache hit");
  172. resolve(cacheHit);
  173. }
  174. });
  175. }
  176. /**
  177. * Minimizes the JSON for a list of repositories
  178. *
  179. * @param {*} repositories
  180. */
  181. function minimizeRepositories(repositories)
  182. {
  183. var rList = [];
  184. for(var i = 0; i < repositories.length; i++)
  185. {
  186. rList.push(copyWithProperties(["name", "created_at", "homepage",
  187. "description", "language", "forks", "watchers",
  188. "open_issues_count", "license", "html_url"],
  189. repositories[i]));
  190. }
  191. return rList;
  192. }
  193. /**
  194. * Fetches all repositories from the API
  195. *
  196. * @param {*} user name of org/user
  197. * @param {*} orgsOrUsers either /users/ or /orgs/
  198. */
  199. function queryRepositories(user, orgsOrUsers)
  200. {
  201. const cacheHit = cache.get(user + REPOS_PATH);
  202. return new Promise((resolve, reject)=>
  203. {
  204. if(cacheHit == null)
  205. {
  206. fetchAllWithPagination(orgsOrUsers + user + REPOS_PATH, 1, []).then((repos)=>
  207. {
  208. var minimized = minimizeRepositories(repos);
  209. resolve(minimized);
  210. cache.put(user + REPOS_PATH, minimized);
  211. }).catch((err)=>
  212. {
  213. console.log(err)
  214. console.log("bad things went down");
  215. })
  216. }
  217. else
  218. {
  219. console.log("Repositories cache hit");
  220. resolve(cacheHit);
  221. }
  222. });
  223. }
  224. /**
  225. * /users/name/following/followers
  226. */
  227. routes.get("/friends/:name", async (req, res)=> {
  228. try {
  229. const query = await queryFriends(req.params.name);
  230. res.json(query);
  231. } catch (error) {
  232. res.status(500).json({error: 'API error fetching friends'});
  233. }
  234. });
  235. routes.get("/org/users/:name", (request, result)=>
  236. {
  237. getOrganizationMembers(request.params.name).then(friends=>
  238. {
  239. result.json(friends)
  240. .end();
  241. }).catch(error=>
  242. {
  243. result.status(500)
  244. .json({error: 'API error fetching friends'})
  245. .end();
  246. });
  247. });
  248. routes.get("/repositories/:name", (request, result)=>
  249. {
  250. queryRepositories(request.params.name, API_USER_PATH).then(repos=>
  251. {
  252. result.json(repos)
  253. .end();
  254. }).catch(error=>
  255. {
  256. result.status(500)
  257. .json({error: 'API error fetching friends'})
  258. .end();
  259. });
  260. });
  261. routes.get("/org/repositories/:name", (request, result)=>
  262. {
  263. queryRepositories(request.params.name, API_ORGS_PATH).then(repos=>
  264. {
  265. result.json(repos)
  266. .end();
  267. }).catch(error=>
  268. {
  269. result.status(500)
  270. .json({error: 'API error fetching friends'})
  271. .end();
  272. });
  273. });
  274. routes.get('/*', (request, result) =>
  275. {
  276. var gitHubAPIURL = request.url;
  277. result.setHeader('Content-Type', 'application/json');
  278. queryGitHubAPI(gitHubAPIURL).then((data)=>
  279. {
  280. if(data.hasOwnProperty("id") || data[0].hasOwnProperty("id"))
  281. {
  282. result.write(JSON.stringify(data));
  283. }
  284. else
  285. {
  286. result.write("[]");
  287. }
  288. result.end();
  289. }).catch((error)=>
  290. {
  291. try
  292. {
  293. if(error.hasOwnProperty("id") || error[0].hasOwnProperty("id"))
  294. {
  295. result.write(JSON.stringify(error));
  296. }
  297. else
  298. {
  299. result.write("[]");
  300. }
  301. }
  302. catch(error)
  303. {
  304. result.write("[]");
  305. };
  306. result.end();
  307. });
  308. if(cache.size() > 50000)
  309. {
  310. cache.clear();
  311. }
  312. });
  313. module.exports = routes;