Browse Source

Feature/gh api deprecation (#42)

* Updated jquery version

* Updated docker image to run basic node image, fixed GH deprecated authentication method
master
Jeffery Russell 1 day ago
committed by GitHub
parent
commit
8738b426ce
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
15 changed files with 60 additions and 392 deletions
  1. +28
    -9
      .gitignore
  2. +1
    -11
      Dockerfile
  3. +13
    -9
      README.md
  4. +4
    -5
      docker-compose.yml
  5. +1
    -3
      server/public/FriendsGraph.html
  6. +1
    -3
      server/public/GraphGenerator.html
  7. +1
    -3
      server/public/GraphTest.html
  8. +1
    -3
      server/public/OrgRepoGraph.html
  9. +1
    -3
      server/public/TimeLineGraph.html
  10. +1
    -3
      server/public/about.html
  11. +1
    -3
      server/public/index.html
  12. +5
    -12
      server/routes/api/v1.js
  13. +0
    -310
      server/routes/api/v2.js
  14. +0
    -11
      server/routes/test/v2.js
  15. +2
    -4
      server/server.js

+ 28
- 9
.gitignore View File

@ -1,9 +1,28 @@
################################################################################
# This .gitignore file was automatically created by Microsoft(R) Visual Studio.
################################################################################
*/.vs
*/node_modules/.bin
*/node_modules
*/package-lock.json
*/.env
# Dependencies
node_modules/
package-lock.json
# Environment variables
.env
server/.env
# Logs
logs/
*.log
npm-debug.log*
# Runtime / OS
.DS_Store
Thumbs.db
# IDE / Editor
.vs/
.vscode/
.idea/
# Test coverage
coverage/
.nyc_output/
# Docker volumes
volumes/

+ 1
- 11
Dockerfile View File

@ -1,16 +1,6 @@
FROM ubuntu
FROM node:bookworm-slim
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

+ 13
- 9
README.md View File

@ -22,26 +22,30 @@ If you are lucky, you can find the site live [here](https://github-graphs.com/).
The easiest way to get started with Github-Graphs is to fork this repository
and follow the instructions below.
#### Create a new OAuth app
#### Create a GitHub Personal Access Token (PAT)
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/).
Authentication is handled via a GitHub Personal Access Token (PAT). The old
OAuth App `client_id`/`client_secret` query parameter method is no longer used,
as GitHub deprecated unauthenticated and OAuth-param-based requests.
To create a PAT:
1. Go to **GitHub → Settings → Developer settings → Personal access tokens**
2. Choose **Fine-grained tokens** (recommended) or **Tokens (classic)**
3. For a classic token, grant the `public_repo` and `read:user` scopes (read-only is sufficient)
4. Copy the generated token — it will only be shown once
#### 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.
setup your environment credentials by creating a `.env` file with the values below filled in.
```
CLIENT_ID = <your_client_ID_from_oauth_app>
CLIENT_SECRET = <your_generated_personal_access_from_oauth_app>
GITHUB_TOKEN = <your_personal_access_token>
SESSION_SECRET = <any_string_you_want>
PORT = <any_number>
```
The creation of your OAuth app and the .env file are required steps,
The creation of your PAT and the `.env` file are required steps,
irrespective of your desired deployment environment. For specific directions,
continue by following the steps specified below.

+ 4
- 5
docker-compose.yml View File

@ -1,11 +1,10 @@
version: '3.7'
services:
github-graphs:
build:
dockerfile: Dockerfile
image: graph-app:latest
image: graph-app
container_name: github-graphs
env_file:
- .env
ports:
- "8080:8000"
- "4002:4002"

+ 1
- 3
server/public/FriendsGraph.html View File

@ -9,9 +9,7 @@
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<link rel="stylesheet" href="./style.css" />
<script
src="https://code.jquery.com/jquery-3.3.1.min.js"
integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
crossorigin="anonymous">
src="https://code.jquery.com/jquery-3.7.1.min.js">
</script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"
integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM"

+ 1
- 3
server/public/GraphGenerator.html View File

@ -8,9 +8,7 @@
<link rel="shortcut icon" href="./favicon.ico" type="image/x-icon" />
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<link rel="stylesheet" href="./style.css" />
<script src="https://code.jquery.com/jquery-3.3.1.min.js"
integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
crossorigin="anonymous">
<script src="https://code.jquery.com/jquery-3.7.1.min.js">
</script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"
integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM"

+ 1
- 3
server/public/GraphTest.html View File

@ -18,9 +18,7 @@
</style>
<script
src="https://code.jquery.com/jquery-3.3.1.min.js"
integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
crossorigin="anonymous">
src="https://code.jquery.com/jquery-3.7.1.min.js">
</script>
<script src="js/githubAPI.js"></script>
<script src="js/friendsGraph.js"></script>

+ 1
- 3
server/public/OrgRepoGraph.html View File

@ -10,9 +10,7 @@
<link rel="stylesheet" href="./style.css" />
<link rel="stylesheet" href="https://cdn.rawgit.com/konpa/devicon/df6431e323547add1b4cf45992913f15286456d3/devicon.min.css">
<script
src="https://code.jquery.com/jquery-3.3.1.min.js"
integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
crossorigin="anonymous">
src="https://code.jquery.com/jquery-3.7.1.min.js">
</script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"
integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM"

+ 1
- 3
server/public/TimeLineGraph.html View File

@ -9,9 +9,7 @@
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<link rel="stylesheet" href="./style.css" />
<script
src="https://code.jquery.com/jquery-3.3.1.min.js"
integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
crossorigin="anonymous">
src="https://code.jquery.com/jquery-3.7.1.min.js">
</script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"
integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM"

+ 1
- 3
server/public/about.html View File

@ -9,9 +9,7 @@
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<link rel="stylesheet" href="./style.css" />
<script
src="https://code.jquery.com/jquery-3.3.1.min.js"
integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
crossorigin="anonymous">
src="https://code.jquery.com/jquery-3.7.1.min.js">
</script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"
integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM"

+ 1
- 3
server/public/index.html View File

@ -8,9 +8,7 @@
<link rel="shortcut icon" href="./favicon.ico" type="image/x-icon" />
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<link rel="stylesheet" href="./style.css" />
<script src="https://code.jquery.com/jquery-3.3.1.min.js"
integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
crossorigin="anonymous">
<script src="https://code.jquery.com/jquery-3.7.1.min.js">
</script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"
integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM"

+ 5
- 12
server/routes/api/v1.js View File

@ -3,7 +3,6 @@ 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";
@ -18,8 +17,10 @@ const REPOS_PATH = "/repos";
const got_options = {
json: true,
username : process.env.CLIENT_ID,
password : process.env.CLIENT_SECRET
headers: {
'Authorization': `Bearer ${process.env.GITHUB_TOKEN}`,
'User-Agent': 'github-graphs-app'
}
};
/**
@ -34,15 +35,7 @@ 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;
}
var queryURL = GITHUB_API + requestURL;
console.log(queryURL);
got(queryURL, got_options).then(response =>

+ 0
- 310
server/routes/api/v2.js View File

@ -1,310 +0,0 @@
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;

+ 0
- 11
server/routes/test/v2.js View File

@ -1,11 +0,0 @@
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));
});
});

+ 2
- 4
server/server.js View File

@ -1,11 +1,9 @@
const crypto = require('crypto');
const app = express();
const dotenv = require("dotenv").config();
const express = require("express");
const session = require('express-session');
const app = express();
const sessionProperties = {
secret: process.env.SESSION_SECRET || crypto.randomBytes(64),
@ -22,4 +20,4 @@ const routes = require('./routes');
app.use('/', routes);
app.listen(process.env.PORT || 8100, () => console.log(`App listening on port ${process.env.PORT || 8100}!`));
app.listen(process.env.PORT || 4002, () => console.log(`App listening on port ${process.env.PORT || 4002}!`));

Loading…
Cancel
Save