Skip to content

Commit 4a10583

Browse files
authored
Merge pull request github#245 from github/imjohnbo/search-api
imjohnbo/search-api
2 parents 5131b00 + 4432fad commit 4a10583

File tree

6 files changed

+564
-0
lines changed

6 files changed

+564
-0
lines changed

api/javascript/search/.env

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
GLITCH_DEBUGGER=true
2+
# Environment Config
3+
4+
# reference these in your code with process.env.SECRET
5+
6+
GH_APP_ID=
7+
GH_CLIENT_ID=
8+
GH_CLIENT_SECRET=
9+
INSTALLATION_ID=
10+
11+
# note: .env is a shell file so there can't be spaces around =

api/javascript/search/README.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
GitHub Search API demo
2+
=================
3+
4+
This project employs several authentication strategies to avoid rate limiting while using the GitHub Search API:
5+
1. Using each user's OAuth access token, if available -- this will allow you a maximum of [30 requests per-user / per-minute](https://developer.github.com/v3/search/#rate-limit)
6+
2. Falling back to a server-to-server token, associated with a given installation of your GitHub App -- this will allow you a maximum of [30 requests per-organization / per-minute](https://developer.github.com/v3/search/#rate-limit)
7+
3. Falling back again to simplified functionality, such as validating a given GitHub username, via GET /users/:username -- this will allow you a minimum of [5000 requests per-organization / per-hour](https://developer.github.com/apps/building-github-apps/understanding-rate-limits-for-github-apps/)
8+
9+
Step 1a: Prereqs via [Glitch](https://glitch.com/~github-search-api)
10+
-----------
11+
12+
* Remix this app :)
13+
14+
Step 1b: Prereqs locally
15+
-----------
16+
* Install `node` from [the website](https://nodejs.org/en/) or [Homebrew](https://brew.sh/)
17+
* `git clone` the project
18+
* Navigate to the project directory and install dependencies using `npm i`
19+
20+
Step 2: App creation and variable-setting
21+
-----------
22+
* Create a new [GitHub App](https://developer.github.com/apps/building-github-apps/creating-a-github-app/).
23+
* Homepage URL = `<Your Glitch App URL>`
24+
* User authorization callback URL = `<Your Glitch App URL>/authorized`
25+
* Webhook URL (unused) = `<Your Glitch App URL>/hooks`
26+
* Download your private key at the bottom of the app settings page.
27+
* Make a new file in Glitch called `.data/pem` and paste the contents of the private key.
28+
* Set the following variables in your Glitch `.env` file:
29+
* `GH_CLIENT_ID` Client ID on app settings page
30+
* `GH_CLIENT_SECRET` Client secret on app settings page
31+
* `GH_APP_ID` App ID on app settings page
32+
* `INSTALLATION_ID` Installation ID, which you can retrieve from [here](https://developer.github.com/v3/apps/installations/#installations)
33+
34+
Step 3a: Running via Glitch
35+
-----------
36+
* Navigate to your URL for live-reloaded goodness
37+
38+
Step 3b: Running locally
39+
-----------
40+
* `npm start`
41+
42+
FYI
43+
-----------
44+
* This app is single-user (for now). It stores the OAuth token in a file found at `.data/oauth`.

api/javascript/search/package.json

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"name": "github-search-api",
3+
"version": "0.0.1",
4+
"description": "Demo of the GitHub Search API, using several authentication strategies to avoid rate limits.",
5+
"main": "server.js",
6+
"scripts": {
7+
"start": "node server.js"
8+
},
9+
"dependencies": {
10+
"express": "^4.16.4",
11+
"node-fetch": "^2.5.0",
12+
"node-localstorage": "^1.3.1",
13+
"@octokit/app": "^1.1.0",
14+
"@octokit/request": "^2.2.0"
15+
},
16+
"engines": {
17+
"node": "8.x"
18+
},
19+
"repository": {
20+
"url": "https://github-search-api.glitch.me/"
21+
},
22+
"license": "MIT",
23+
"keywords": [
24+
"node",
25+
"glitch",
26+
"express"
27+
]
28+
}
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
const searchInput = document.querySelector('.search-input');
2+
const searchResults = document.querySelector('.search-results');
3+
const searchError = document.querySelector('.search-error');
4+
const searchButton = document.querySelector('.search');
5+
const login = document.querySelector('.login');
6+
const loginButton = document.querySelector('.login-button');
7+
const loginText = document.querySelector('.login-text');
8+
const authType = document.querySelector('.auth-type');
9+
const authTarget = document.querySelector('.auth-target');
10+
const hitsRemaining = document.querySelector('.hits-remaining');
11+
const hitsTotal = document.querySelector('.hits-total');
12+
const scheme = document.querySelector('.scheme');
13+
14+
let localState = {};
15+
16+
// TODO change from javascript handler to <form>
17+
loginButton && loginButton.addEventListener('click', (evt) => {
18+
19+
window.location = `https://github.com/login/oauth/authorize?scope=repo&client_id=${localState.clientId}&state=${localState.oAuthState}`;
20+
console.log(localState.clientId);
21+
console.log(localState.oAuthState);
22+
});
23+
24+
searchInput && searchInput.addEventListener('input', (evt) => {
25+
const val = evt.target.value;
26+
if (!val) {
27+
searchResults.innerHTML = '';
28+
searchError.hidden = true;
29+
}
30+
});
31+
32+
searchButton && searchButton.addEventListener('click', (user) => {
33+
if (searchInput.value === '') return;
34+
searchResults.innerHTML = '';
35+
searchError.hidden = true;
36+
search()
37+
.then(data => data.json())
38+
.then(showResults)
39+
.then(syncState)
40+
.catch(err => {
41+
searchError.innerHTML = 'Error encountered while searching.'
42+
searchError.hidden = false;
43+
});
44+
});
45+
46+
function search() {
47+
return fetch(`/search/${searchInput.value}`, {
48+
headers: {
49+
"Content-Type": "application/json",
50+
}
51+
});
52+
};
53+
54+
function showResults(results) {
55+
// just one result from User API
56+
if (!results.items && !results.items.length) {
57+
if (results.login) {
58+
searchResults.innerHTML = `This user <a href="${results.html_url}">was found</a> on GitHub`;
59+
}
60+
else {
61+
searchResults.innerHTML = 'This user could not be found on GitHub.';
62+
}
63+
}
64+
// array of results from Search API
65+
else if (results.items.length) {
66+
results.items.forEach(createRow);
67+
}
68+
}
69+
70+
function createRow(result) {
71+
let node = document.createElement('li');
72+
let text = document.createTextNode(result.login)
73+
node.appendChild(text);
74+
searchResults.appendChild(node);
75+
}
76+
77+
function updateUI() {
78+
authType.innerHTML = localState.authType;
79+
authTarget.innerHTML = localState.authTarget;
80+
hitsRemaining.innerHTML = `(${localState.rateLimitRemaining} /`;
81+
hitsTotal.innerHTML = ` ${localState.rateLimitTotal})`;
82+
83+
if (localState.oAuthToken) {
84+
loginText.innerHTML = 'Logged in.';
85+
loginButton.disabled = true;
86+
}
87+
88+
if (localState.rateLimitRemaining) {
89+
scheme.hidden = false;
90+
}
91+
}
92+
93+
function syncState() {
94+
fetch(`/state`)
95+
.then(data => data.json())
96+
.then(remoteState => {
97+
localState = remoteState;
98+
updateUI();
99+
});
100+
}
101+
102+
// this executes immediately
103+
(() => {
104+
// await this.getRateLimits(this.getQueryAuthToken());
105+
scheme.hidden = true;
106+
syncState();
107+
})();
108+
109+
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<title>GitHub Search API</title>
5+
<link id="favicon" rel="icon" href="https://glitch.com/edit/favicon-app.ico" type="image/x-icon">
6+
<meta charset="utf-8">
7+
<meta http-equiv="X-UA-Compatible" content="IE=edge">
8+
<meta name="viewport" content="width=device-width, initial-scale=1">
9+
10+
<style>
11+
html, body {
12+
height: 100%;}
13+
body {
14+
display: flex;
15+
flex-direction: column;
16+
}
17+
.content {
18+
flex: 1 0 auto;
19+
}
20+
.footer {
21+
flex-shrink: 0;
22+
background: #eee;
23+
}
24+
.search-error {
25+
color: red;
26+
}
27+
28+
</style>
29+
30+
<link href="https://unpkg.com/primer/build/build.css" rel="stylesheet">
31+
32+
</head>
33+
<body>
34+
35+
<div class="content container-lg p-6">
36+
37+
<h2>
38+
Try searching for a GitHub user:
39+
</h2>
40+
41+
<br>
42+
43+
<input class="form-control search-input" type="text" placeholder="username"/>
44+
<button class="btn btn-primary search" type="button">
45+
Search
46+
</button>
47+
48+
<br>
49+
50+
<p class="search-error"></p>
51+
52+
<br>
53+
54+
<div class="login">
55+
<p class="login-text">
56+
For more API queries, try logging in to GitHub?
57+
</p>
58+
<button class="btn login-button">
59+
Login
60+
</button>
61+
</div>
62+
63+
<br>
64+
65+
<ul class="search-results pt-3 pl-3"></ul>
66+
67+
</div>
68+
69+
<footer class="footer pb-2 pt-3 text-center">
70+
<div class="scheme">
71+
<p>
72+
You are using <strong><span class="auth-type"></span> authentication</strong> against the <strong><span class="auth-target"></span> API</strong>.
73+
</p>
74+
<p>
75+
<span class="hits-remaining"></span><span class="hits-total"></span>
76+
</p>
77+
</div>
78+
</footer>
79+
80+
<script src="/client.js"></script>
81+
<!-- include the Glitch button to show what the webpage is about and
82+
to make it easier for folks to view source and remix -->
83+
<div class="glitchButton" style="position:fixed;top:20px;right:20px;"></div>
84+
<script src="https://button.glitch.me/button.js"></script>
85+
</body>
86+
</html>

0 commit comments

Comments
 (0)