diff --git a/.gitignore b/.gitignore index f965bc5..ecc4832 100644 --- a/.gitignore +++ b/.gitignore @@ -2,8 +2,8 @@ # This .gitignore file was automatically created by Microsoft(R) Visual Studio. ################################################################################ -/.vs -/node_modules/.bin -/node_modules -/package-lock.json -.env \ No newline at end of file +*/.vs +*/node_modules/.bin +*/node_modules +*/package-lock.json +*/.env \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..8bc22ee --- /dev/null +++ b/Dockerfile @@ -0,0 +1,27 @@ +FROM ubuntu +LABEL maintainer="Jeffery Russell" + +# install all dependencies +RUN apt-get update && \ + apt-get upgrade -y && \ + apt-get install -y build-essential && \ + apt-get install -y sudo curl && \ + curl -sL https://deb.nodesource.com/setup_13.x | sudo -E bash - && \ + apt-get install -y nodejs && \ + apt-get update && \ + apt-get clean + +# Create a working directory for the container +RUN mkdir /github-graphs + +# copy files from the directory of the Dockerfile to the docker container +COPY /server /github-graphs/server +COPY README.md /github-graphs/ +COPY LICENSE /github-graphs/ + +# setup working directory to the default in the container +WORKDIR /github-graphs/server + +# Install dependencies and start the program at RUN +RUN npm install +CMD ["node", "server.js"] diff --git a/README.md b/README.md index 5282733..0dcefcd 100644 --- a/README.md +++ b/README.md @@ -4,40 +4,217 @@ Website for visualizing a persons github network. ![Example Graph](./doc/graphExample.png) -If you are lucky, you can find the site live [here](https://github-graphs.com/); +If you are lucky, you can find the site live [here](https://github-graphs.com/). - -# Built With +## Built With - [BootStrap](https://getbootstrap.com/) - [jQuery](https://jquery.com/) - [Vis JS](http://visjs.org/) -- [Github v3 API](https://developer.github.com/v3/) +- [Github v3 API](https://developer.github.com/v3/) - [Node.js](https://nodejs.org/en/) - ![javascript](./doc/javaScript.jpg) -# Running -Create a .env file with the code below filled in. +## Deployment + +The easiest way to get started with Github-Graphs is to fork this repository +and follow the instructions below. + +#### Create a new OAuth app + +The objective of creating an app under your github account is to access an +endpoint through the GitHub API and obtain credentials to set your environment +file. For more information on how to create a new OAuth app, consult the corresponding +Github developer documentation [here](https://developer.github.com/apps/building-oauth-apps/creating-an-oauth-app/). + +#### Create a .env file + +After forking this repository, run the command `cd server/`. Inside that folder, +setup your environment credentials by creating a `.env file` with the code below filled in. ``` -CLIENT_ID = -CLIENT_SECRET = +CLIENT_ID = +CLIENT_SECRET = SESSION_SECRET = PORT = ``` +The creation of your OAuth app and the .env file are required steps, +irrespective of your desired deployment environment. For specific directions, +continue by following the steps specified below. + +### Deployment on the local machine + +#### Install dependencies + +Dependencies are installed using `npm`. Therefore, please install the package manager +before proceeding. If you already have `npm` installed, run the command below inside +`server/` in order to install the dependencies in the package directory. + ```bash npm install ``` +#### Explore GitHub-Graphs + +Inside `server/`, run the following command to start the program, and in your +browser, check `localhost:8000` to visualize your Github network. + ```bash node server.js ``` +### Deployment in a Docker container -# Contributing +Github-graphs can also be deployed inside a docker container and displayed in +your browser through port mapping. To get started run the following commands +inside your forked repository. + +The easiest way to deploy in a docker container is through the use of our proposed +`docker-compose.yml` file. If you choose this methodology, make sure the port numbers +in your `.env` file matches the docker-compose file. Note that this approach will work +only on systems which have Docker and Docker-compose both installed. +Considering the example provided in our provided docker-compose, +the port number of the .env file should be `PORT= 8000`. Therefore, you could +visualize the Github-Graphs page at `localhost:8080` after running: + +``` + docker-compose up -d --build +``` + +In order to clean the environment, you can run the following command. + +``` + docker-compose down --rmi all + +``` + +Besides the use of docker-compose, deployment with just docker is possible with the following commands:\ + +``` + docker build -t . + docker run -d --name -p : +``` -We are very open to new contributors. If you want to contribute to this project, and don't know where to start, look at the [open issues](https://github.com/jrtechs/github-graphs/issues). Once you know what you want to work on, comment on the issue and file a pull request. \ No newline at end of file +For instance, assume I name my image `graph-app`, my container `github-graphs`, +and set the port number in my .env file to `8000`, I can decide to listen on my localhost at port `8080`. +Therefore, my commands are: + +``` + docker build -t graph-app . + docker run -d --name github-graphs -p 8080:8000 graph-app +``` + +If you are willing to read the debugging statement on the CLI, do not add `-d` to the `docker run` statement. + +At this step, you can now visualize Github graphs at `localhost:`. + + + +## Contributing + +We are very open to new contributors. If you want to contribute to this project, +and don't know where to start, look at the [open issues](https://github.com/jrtechs/github-graphs/issues). +Once you know what you want to work on, comment on the issue and file a pull request. + +## API Reference + +`GET https://github-graphs.com/api/friends/` + +Fetches `https://api.github.com/users//followers` [(GitHub Reference)](https://developer.github.com/v3/users/followers/#list-followers-of-a-user) +and `https://api.github.com/users//following` [(GitHub Reference)](https://developer.github.com/v3/users/followers/#list-users-followed-by-another-user) +to generate an array of users following `` and that `` follows each with values `login` and `avatar_url`. + +Example result: + +```json +[ + { + "login": "jrtechs", + "avatar_url": "https://avatars1.githubusercontent.com/u/13894625?s=460&v=4" + } +] +``` + +--- + +`GET https://github-graphs.com/api/repositories/` + +Fetches `https://api.github.com/users//repos` and returns an array of the repositories `` owns. + +Example result: + +```json +[ + { + "name": "bash_manager", + "created_at": "2017-09-27T14:38:01Z", + "homepage": "", + "description": "Python scripts I use to manage my ssh connections, drive mounts, and other bash related things. ", + "language": "Python", + "forks": 26, + "watchers": 4, + "open_issues_count": 7, + "license": { + "key": "mpl-2.0", + "name": "Mozilla Public License 2.0", + "spdx_id": "MPL-2.0", + "url": "https://api.github.com/licenses/mpl-2.0", + "node_id": "MDc6TGljZW5zZTE0" + }, + "html_url": "https://github.com/jrtechs/bash_manager" + } +] +``` + +--- + +`GET https://github-graphs.com/api/org/users/` + +Fetches `https://api.github.com/orgs//members` [(GitHub Reference)](https://developer.github.com/v3/orgs/members/#members-list) +to generate an array of users that are in `` each with values `login` and `avatar_url`. + +Example result: + +```json +[ + { + "login": "jrtechs", + "avatar_url": "https://avatars1.githubusercontent.com/u/13894625?s=460&v=4" + } +] +``` + +--- + +`GET https://github-graphs.com/api/org/repositories/` + +Fetches `https://api.github.com/orgs//repos` [(GitHub Reference)](https://developer.github.com/v3/repos/#list-organization-repositories) +to return an array of repositories `` owns. + +Example result: + +```json +[ + { + "name": "vue", + "created_at": "2013-07-29T03:24:51Z", + "homepage": "http://vuejs.org", + "description": "🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.", + "language": "JavaScript", + "forks": 23228, + "watchers": 154891, + "open_issues_count": 419, + "license": { + "key": "mit", + "name": "MIT License", + "spdx_id": "MIT", + "url": "https://api.github.com/licenses/mit", + "node_id": "MDc6TGljZW5zZTEz" + }, + "html_url": "https://github.com/vuejs/vue" + } +] +``` diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..d89633e --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,11 @@ + +version: '3.7' + +services: + github-graphs: + build: + dockerfile: Dockerfile + image: graph-app:latest + container_name: github-graphs + ports: + - "8080:8000" diff --git a/gitGraph.service b/server/gitGraph.service similarity index 100% rename from gitGraph.service rename to server/gitGraph.service diff --git a/package.json b/server/package.json similarity index 53% rename from package.json rename to server/package.json index f021d3b..5951414 100644 --- a/package.json +++ b/server/package.json @@ -1,28 +1,34 @@ { "name": "github-graphs", - "version": "0.0.1", + "version": "0.0.2", "description": "Generates graphs of github things.", "main": "server.js", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1", + "coveralls": "nyc npm test && nyc report --reporter=text-lcov | coveralls", + "test": "mocha routes/test --exit --reporter spec", "start": "node server.js" }, "repository": { "type": "git", "url": "git+https://github.com/jrtechs/github-graphs.git" }, - "author": "Jeffery Russell", + "author": "Jeffery Russell, Eric Lang", "license": "ISC", "bugs": { "url": "https://github.com/jrtechs/github-graphs/issues" }, "homepage": "https://github.com/jrtechs/github-graphs#readme", "dependencies": { - "dotenv": "^8.2.0", - "express": "^4.16.4", - "express-session": "^1.15.6", + "dotenv": "~8.2.0", + "express": "~4.16.4", + "express-session": "~1.15.6", "fs": "0.0.1-security", - "got": "^9.6.0", - "memory-cache": "^0.2.0" + "got": "~9.6.0", + "memory-cache": "~0.2.0" + }, + "devDependencies": { + "coveralls": "*", + "mocha": "*", + "nyc": "*" } } diff --git a/public/404.html b/server/public/404.html similarity index 100% rename from public/404.html rename to server/public/404.html diff --git a/public/FriendsGraph.html b/server/public/FriendsGraph.html similarity index 100% rename from public/FriendsGraph.html rename to server/public/FriendsGraph.html diff --git a/public/GraphGenerator.html b/server/public/GraphGenerator.html similarity index 100% rename from public/GraphGenerator.html rename to server/public/GraphGenerator.html diff --git a/public/GraphTest.html b/server/public/GraphTest.html similarity index 100% rename from public/GraphTest.html rename to server/public/GraphTest.html diff --git a/public/OrgRepoGraph.html b/server/public/OrgRepoGraph.html similarity index 100% rename from public/OrgRepoGraph.html rename to server/public/OrgRepoGraph.html diff --git a/public/TimeLineGraph.html b/server/public/TimeLineGraph.html similarity index 100% rename from public/TimeLineGraph.html rename to server/public/TimeLineGraph.html diff --git a/public/about.html b/server/public/about.html similarity index 100% rename from public/about.html rename to server/public/about.html diff --git a/public/favicon.ico b/server/public/favicon.ico similarity index 100% rename from public/favicon.ico rename to server/public/favicon.ico diff --git a/public/fonts/Comfortaa-Bold.ttf b/server/public/fonts/Comfortaa-Bold.ttf similarity index 100% rename from public/fonts/Comfortaa-Bold.ttf rename to server/public/fonts/Comfortaa-Bold.ttf diff --git a/public/fonts/Comfortaa-Light.ttf b/server/public/fonts/Comfortaa-Light.ttf similarity index 100% rename from public/fonts/Comfortaa-Light.ttf rename to server/public/fonts/Comfortaa-Light.ttf diff --git a/public/fonts/Comfortaa-Regular.ttf b/server/public/fonts/Comfortaa-Regular.ttf similarity index 100% rename from public/fonts/Comfortaa-Regular.ttf rename to server/public/fonts/Comfortaa-Regular.ttf diff --git a/public/ico/baseline-bar_chart-24px.svg b/server/public/ico/baseline-bar_chart-24px.svg similarity index 100% rename from public/ico/baseline-bar_chart-24px.svg rename to server/public/ico/baseline-bar_chart-24px.svg diff --git a/public/ico/graph.png b/server/public/ico/graph.png similarity index 100% rename from public/ico/graph.png rename to server/public/ico/graph.png diff --git a/public/ico/outline-timeline-24px.svg b/server/public/ico/outline-timeline-24px.svg similarity index 100% rename from public/ico/outline-timeline-24px.svg rename to server/public/ico/outline-timeline-24px.svg diff --git a/public/icon.svg b/server/public/icon.svg similarity index 100% rename from public/icon.svg rename to server/public/icon.svg diff --git a/public/img/DolphinCroissant.png b/server/public/img/DolphinCroissant.png similarity index 100% rename from public/img/DolphinCroissant.png rename to server/public/img/DolphinCroissant.png diff --git a/public/img/email.svg b/server/public/img/email.svg similarity index 100% rename from public/img/email.svg rename to server/public/img/email.svg diff --git a/public/img/friends.png b/server/public/img/friends.png similarity index 100% rename from public/img/friends.png rename to server/public/img/friends.png diff --git a/public/img/friends2.png b/server/public/img/friends2.png similarity index 100% rename from public/img/friends2.png rename to server/public/img/friends2.png diff --git a/public/img/github-circle.svg b/server/public/img/github-circle.svg similarity index 100% rename from public/img/github-circle.svg rename to server/public/img/github-circle.svg diff --git a/public/img/githubgraph-logo.svg b/server/public/img/githubgraph-logo.svg similarity index 100% rename from public/img/githubgraph-logo.svg rename to server/public/img/githubgraph-logo.svg diff --git a/public/img/graphExample.png b/server/public/img/graphExample.png similarity index 100% rename from public/img/graphExample.png rename to server/public/img/graphExample.png diff --git a/public/img/home-image.png b/server/public/img/home-image.png similarity index 100% rename from public/img/home-image.png rename to server/public/img/home-image.png diff --git a/public/img/linkedin-box.svg b/server/public/img/linkedin-box.svg similarity index 100% rename from public/img/linkedin-box.svg rename to server/public/img/linkedin-box.svg diff --git a/public/index.html b/server/public/index.html similarity index 100% rename from public/index.html rename to server/public/index.html diff --git a/public/js/createOrgInfo.js b/server/public/js/createOrgInfo.js similarity index 100% rename from public/js/createOrgInfo.js rename to server/public/js/createOrgInfo.js diff --git a/public/js/createOrgRepoGraph.js b/server/public/js/createOrgRepoGraph.js similarity index 100% rename from public/js/createOrgRepoGraph.js rename to server/public/js/createOrgRepoGraph.js diff --git a/public/js/createOrgTable.js b/server/public/js/createOrgTable.js similarity index 100% rename from public/js/createOrgTable.js rename to server/public/js/createOrgTable.js diff --git a/public/js/friendsGraph.js b/server/public/js/friendsGraph.js similarity index 100% rename from public/js/friendsGraph.js rename to server/public/js/friendsGraph.js diff --git a/public/js/githubAPI.js b/server/public/js/githubAPI.js similarity index 100% rename from public/js/githubAPI.js rename to server/public/js/githubAPI.js diff --git a/public/js/profileGen.js b/server/public/js/profileGen.js similarity index 100% rename from public/js/profileGen.js rename to server/public/js/profileGen.js diff --git a/public/js/profileTimeLine.js b/server/public/js/profileTimeLine.js similarity index 100% rename from public/js/profileTimeLine.js rename to server/public/js/profileTimeLine.js diff --git a/public/js/repoGen.js b/server/public/js/repoGen.js similarity index 100% rename from public/js/repoGen.js rename to server/public/js/repoGen.js diff --git a/public/js/utilities.js b/server/public/js/utilities.js similarity index 100% rename from public/js/utilities.js rename to server/public/js/utilities.js diff --git a/public/js/vis/img/network/acceptDeleteIcon.png b/server/public/js/vis/img/network/acceptDeleteIcon.png similarity index 100% rename from public/js/vis/img/network/acceptDeleteIcon.png rename to server/public/js/vis/img/network/acceptDeleteIcon.png diff --git a/public/js/vis/img/network/addNodeIcon.png b/server/public/js/vis/img/network/addNodeIcon.png similarity index 100% rename from public/js/vis/img/network/addNodeIcon.png rename to server/public/js/vis/img/network/addNodeIcon.png diff --git a/public/js/vis/img/network/backIcon.png b/server/public/js/vis/img/network/backIcon.png similarity index 100% rename from public/js/vis/img/network/backIcon.png rename to server/public/js/vis/img/network/backIcon.png diff --git a/public/js/vis/img/network/connectIcon.png b/server/public/js/vis/img/network/connectIcon.png similarity index 100% rename from public/js/vis/img/network/connectIcon.png rename to server/public/js/vis/img/network/connectIcon.png diff --git a/public/js/vis/img/network/cross.png b/server/public/js/vis/img/network/cross.png similarity index 100% rename from public/js/vis/img/network/cross.png rename to server/public/js/vis/img/network/cross.png diff --git a/public/js/vis/img/network/cross2.png b/server/public/js/vis/img/network/cross2.png similarity index 100% rename from public/js/vis/img/network/cross2.png rename to server/public/js/vis/img/network/cross2.png diff --git a/public/js/vis/img/network/deleteIcon.png b/server/public/js/vis/img/network/deleteIcon.png similarity index 100% rename from public/js/vis/img/network/deleteIcon.png rename to server/public/js/vis/img/network/deleteIcon.png diff --git a/public/js/vis/img/network/downArrow.png b/server/public/js/vis/img/network/downArrow.png similarity index 100% rename from public/js/vis/img/network/downArrow.png rename to server/public/js/vis/img/network/downArrow.png diff --git a/public/js/vis/img/network/editIcon.png b/server/public/js/vis/img/network/editIcon.png similarity index 100% rename from public/js/vis/img/network/editIcon.png rename to server/public/js/vis/img/network/editIcon.png diff --git a/public/js/vis/img/network/leftArrow.png b/server/public/js/vis/img/network/leftArrow.png similarity index 100% rename from public/js/vis/img/network/leftArrow.png rename to server/public/js/vis/img/network/leftArrow.png diff --git a/public/js/vis/img/network/minus.png b/server/public/js/vis/img/network/minus.png similarity index 100% rename from public/js/vis/img/network/minus.png rename to server/public/js/vis/img/network/minus.png diff --git a/public/js/vis/img/network/plus.png b/server/public/js/vis/img/network/plus.png similarity index 100% rename from public/js/vis/img/network/plus.png rename to server/public/js/vis/img/network/plus.png diff --git a/public/js/vis/img/network/rightArrow.png b/server/public/js/vis/img/network/rightArrow.png similarity index 100% rename from public/js/vis/img/network/rightArrow.png rename to server/public/js/vis/img/network/rightArrow.png diff --git a/public/js/vis/img/network/upArrow.png b/server/public/js/vis/img/network/upArrow.png similarity index 100% rename from public/js/vis/img/network/upArrow.png rename to server/public/js/vis/img/network/upArrow.png diff --git a/public/js/vis/img/network/zoomExtends.png b/server/public/js/vis/img/network/zoomExtends.png similarity index 100% rename from public/js/vis/img/network/zoomExtends.png rename to server/public/js/vis/img/network/zoomExtends.png diff --git a/public/js/vis/vis-graph3d.min.js b/server/public/js/vis/vis-graph3d.min.js similarity index 100% rename from public/js/vis/vis-graph3d.min.js rename to server/public/js/vis/vis-graph3d.min.js diff --git a/public/js/vis/vis-network.min.css b/server/public/js/vis/vis-network.min.css similarity index 100% rename from public/js/vis/vis-network.min.css rename to server/public/js/vis/vis-network.min.css diff --git a/public/js/vis/vis-network.min.js b/server/public/js/vis/vis-network.min.js similarity index 100% rename from public/js/vis/vis-network.min.js rename to server/public/js/vis/vis-network.min.js diff --git a/public/js/vis/vis-timeline-graph2d.min.css b/server/public/js/vis/vis-timeline-graph2d.min.css similarity index 100% rename from public/js/vis/vis-timeline-graph2d.min.css rename to server/public/js/vis/vis-timeline-graph2d.min.css diff --git a/public/js/vis/vis-timeline-graph2d.min.js b/server/public/js/vis/vis-timeline-graph2d.min.js similarity index 100% rename from public/js/vis/vis-timeline-graph2d.min.js rename to server/public/js/vis/vis-timeline-graph2d.min.js diff --git a/public/js/vis/vis.css b/server/public/js/vis/vis.css similarity index 100% rename from public/js/vis/vis.css rename to server/public/js/vis/vis.css diff --git a/public/js/vis/vis.js b/server/public/js/vis/vis.js similarity index 100% rename from public/js/vis/vis.js rename to server/public/js/vis/vis.js diff --git a/public/js/vis/vis.js.map b/server/public/js/vis/vis.js.map similarity index 100% rename from public/js/vis/vis.js.map rename to server/public/js/vis/vis.js.map diff --git a/public/js/vis/vis.map b/server/public/js/vis/vis.map similarity index 100% rename from public/js/vis/vis.map rename to server/public/js/vis/vis.map diff --git a/public/js/vis/vis.min.css b/server/public/js/vis/vis.min.css similarity index 100% rename from public/js/vis/vis.min.css rename to server/public/js/vis/vis.min.css diff --git a/public/js/vis/vis.min.js b/server/public/js/vis/vis.min.js similarity index 100% rename from public/js/vis/vis.min.js rename to server/public/js/vis/vis.min.js diff --git a/public/logo.svg b/server/public/logo.svg similarity index 100% rename from public/logo.svg rename to server/public/logo.svg diff --git a/public/style.css b/server/public/style.css similarity index 100% rename from public/style.css rename to server/public/style.css diff --git a/server/routes/api/index.js b/server/routes/api/index.js new file mode 100644 index 0000000..864dc35 --- /dev/null +++ b/server/routes/api/index.js @@ -0,0 +1,2 @@ +exports.V1 = require('./v1'); +exports.V2 = require('./v2'); \ No newline at end of file diff --git a/routes/api.js b/server/routes/api/v1.js similarity index 95% rename from routes/api.js rename to server/routes/api/v1.js index 2ecc147..dc024f9 100644 --- a/routes/api.js +++ b/server/routes/api/v1.js @@ -16,13 +16,18 @@ const API_PAGINATION = "&per_page=" + API_PAGINATION_SIZE; const REPOS_PATH = "/repos"; +const got_options = { + json: true, + username : process.env.CLIENT_ID, + password : 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) @@ -40,7 +45,7 @@ function queryGithubAPIRaw(requestURL) } console.log(queryURL); - got(queryURL, { json: true }).then(response => + got(queryURL, got_options).then(response => { resolve(response.body); cache.put(requestURL, response.body); @@ -56,8 +61,8 @@ function queryGithubAPIRaw(requestURL) /** * Queries data from the github api server * and caches the results locally. - * - * @param {*} requestURL + * + * @param {*} requestURL */ function queryGitHubAPI(requestURL) { @@ -87,7 +92,7 @@ function queryGitHubAPI(requestURL) /** * 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 @@ -137,9 +142,9 @@ function fetchAllWithPagination(apiPath, page, lst) /** * Makes a copy of a JS object with certain properties - * - * @param {*} props - * @param {*} obj + * + * @param {*} props + * @param {*} obj */ function copyWithProperties(props, obj) { @@ -155,10 +160,10 @@ function copyWithProperties(props, obj) /** * 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 + * + * @param {*} followingAndFollowers */ function minimizeFriends(people) { @@ -172,7 +177,7 @@ function minimizeFriends(people) { ids.add(people[i].id); friendLst.push({ - login: people[i].login, + login: people[i].login, avatar_url: people[i].avatar_url }); } @@ -185,8 +190,8 @@ function minimizeFriends(people) * 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 + * + * @param {*} user */ function queryFriends(user) { @@ -204,7 +209,7 @@ function queryFriends(user) cache.put("/friends/" + user, fList); }).catch((err)=> { - console.log(err); + console.log(err); reject("API ERROR"); }) }).catch((error)=> @@ -223,13 +228,13 @@ function queryFriends(user) /** - * + * * Fetches all of the members of an organization from the * API or cache * * /orgs/RITlug/members?page=1 * - * @param {*} orgName + * @param {*} orgName */ function getOrganizationMembers(orgName) { @@ -259,8 +264,8 @@ function getOrganizationMembers(orgName) /** * Minimizes the JSON for a list of repositories - * - * @param {*} repositories + * + * @param {*} repositories */ function minimizeRepositories(repositories) { @@ -268,7 +273,7 @@ function minimizeRepositories(repositories) for(var i = 0; i < repositories.length; i++) { - rList.push(copyWithProperties(["name", "created_at", "homepage", + rList.push(copyWithProperties(["name", "created_at", "homepage", "description", "language", "forks", "watchers", "open_issues_count", "license", "html_url"], repositories[i])); @@ -279,7 +284,7 @@ function minimizeRepositories(repositories) /** * Fetches all repositories from the API - * + * * @param {*} user name of org/user * @param {*} orgsOrUsers either /users/ or /orgs/ */ @@ -405,7 +410,7 @@ routes.get('/*', (request, result) => } } - catch(error) + catch(error) { result.write("[]"); }; @@ -418,4 +423,4 @@ routes.get('/*', (request, result) => } }); -module.exports = routes; \ No newline at end of file +module.exports = routes; diff --git a/server/routes/api/v2.js b/server/routes/api/v2.js new file mode 100644 index 0000000..b71a463 --- /dev/null +++ b/server/routes/api/v2.js @@ -0,0 +1,310 @@ +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); + if(page < API_MAX_PAGES && req.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"); + } +} + + +/** + * /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; diff --git a/routes/index.js b/server/routes/index.js similarity index 70% rename from routes/index.js rename to server/routes/index.js index 4e8bd6d..67ab663 100644 --- a/routes/index.js +++ b/server/routes/index.js @@ -1,6 +1,6 @@ const routes = require('express').Router(); -const api = require('./api'); -routes.use('/api', api); +const apiV1 = require('./api/v1'); +routes.use('/api', apiV1); routes.get("/", (request, response) => { response.redirect("index.html"); diff --git a/server/routes/test/v2.js b/server/routes/test/v2.js new file mode 100644 index 0000000..1a940c9 --- /dev/null +++ b/server/routes/test/v2.js @@ -0,0 +1,11 @@ +const assert = require('assert'); +const V2 = require('../api/v2'); + +describe('github api v2', function() { + it('successfully queries friends', async function() { + var queryFriends = V2.queryFriends; + // it was this point that Eric realized he doesn't understand + // module.exports very well. + assert.strictEqual(typeof(queryFriends), typeof(queryFriends)); + }); +}); \ No newline at end of file diff --git a/run.sh b/server/run.sh old mode 100644 new mode 100755 similarity index 100% rename from run.sh rename to server/run.sh diff --git a/server.js b/server/server.js similarity index 68% rename from server.js rename to server/server.js index b99e1e7..f80ec48 100644 --- a/server.js +++ b/server/server.js @@ -1,10 +1,14 @@ +const crypto = require('crypto'); + +const app = express(); + +const dotenv = require("dotenv").config(); const express = require("express"); const session = require('express-session'); -const dotenv = require("dotenv").config(); -const app = express(); + const sessionProperties = { - secret: process.env.SESSION_SECRET, + secret: process.env.SESSION_SECRET || crypto.randomBytes(64), cookie: { maxAge: 6000000 }, resave: false, saveUninitialized: false @@ -18,4 +22,4 @@ const routes = require('./routes'); app.use('/', routes); -app.listen(process.env.PORT, () => console.log(`App listening on port ${process.env.PORT}!`)); \ No newline at end of file +app.listen(process.env.PORT || 8100, () => console.log(`App listening on port ${process.env.PORT || 8100}!`)); \ No newline at end of file