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.

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