Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ Add GitHub API integration #3

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,9 @@
"vue-router": "^3.1.6",
"vuepress-types": "^0.9.1"
},
"dependencies": {},
"dependencies": {
"axios": "^0.19.2"
},
"prettier": {
"trailingComma": "all",
"tabWidth": 2,
Expand Down
15 changes: 14 additions & 1 deletion src/cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@ export default class Cache {

private readonly cachePath: string;

constructor(cachePath: string) {
constructor(cachePath: string, ttl?: number) {
this.cache = Cache.loadCache(cachePath);
if (ttl) {
this.purgeExpired(ttl);
}
this.cachePath = cachePath;
}

Expand Down Expand Up @@ -37,4 +40,14 @@ export default class Cache {
}
return file ? JSON.parse(file) : {};
}

private purgeExpired(ttl: number): void {
const expirationDate = new Date();
expirationDate.setDate(new Date().getDate() - ttl);
for (const [key, value] of Object.entries(this.cache)) {
if (value.updatedAt && value.updatedAt < expirationDate) {
this.delete(key);
}
}
}
}
93 changes: 93 additions & 0 deletions src/commit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { exec } from 'child_process';
import { promisify } from 'util';
import axios, { AxiosResponse } from 'axios';
import Cache from './cache';
import { Contributor } from './types';

const aexec = promisify(exec);

const cache = new Cache(process.env.CACHE_PATH as string);
const owner = process.env.GITHUB_OWNER as string;
const repository = process.env.GITHUB_REPO as string;
const authToken = process.env.AUTH_TOKEN as string;
const api = 'https://api.github.com/graphql';

// eslint-disable-next-line no-shadow
function buildQuery(owner: string, repository: string, hashes: Array<string>): string {
let query = `{repository(owner: "${owner}", name: "${repository}") {`;
for (const hash of hashes) {
hash.replace(/\s+/, '');
if (!hash) {
continue;
}
query += `_${hash}: object(oid: "${hash}") {
... on Commit {
author {
user {
avatarUrl
name
login
url
}
}
}
}
`;
}
query += '}}';
return query;
}

interface GitHubUser {
avatarUrl: string;
name: string;
login: string;
url: string;
}
interface GitHubAuthor {
user: GitHubUser;
}
interface GitHubCommit {
author: GitHubAuthor;
}
type GitHubHistory = Record<string, GitHubCommit>;
interface GitHubResponseData {
repository: GitHubHistory;
}
interface GitHubResponse {
data: GitHubResponseData;
}

function updateCacheWithCommits(response: GitHubResponse): void {
const commits = response.data.repository;
for (const [hash, commit] of Object.entries(commits)) {
const { user } = commit.author;
const contributor = {
name: user.name || user.login,
profileUrl: user.url,
profilePic: user.avatarUrl,
} as Contributor;
cache.set(hash.replace(/_/, ''), contributor);
}
cache.saveCache();
}

export default async function updateCacheForFile(filePath: string): Promise<void> {
const { stdout, stderr } = await aexec(`git log --pretty=format:"%H%n" ${filePath}`);
if (stderr) {
console.error(stderr);
return;
}
const hashes = stdout.split('\n').filter((hash) => cache.get(hash) === undefined);
const query = buildQuery(owner, repository, hashes);
let response: AxiosResponse;
try {
response = await axios.post(api, { query }, {
headers: { Authorization: `bearer ${authToken}` },
});
} catch (e) {
console.error(stderr);
return;
}
updateCacheWithCommits(response.data);
}
21 changes: 21 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -712,6 +712,13 @@ atob@^2.1.2:
resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9"
integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==

axios@^0.19.2:
version "0.19.2"
resolved "https://registry.yarnpkg.com/axios/-/axios-0.19.2.tgz#3ea36c5d8818d0d5f8a8a97a6d36b86cdc00cb27"
integrity sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==
dependencies:
follow-redirects "1.5.10"

balanced-match@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
Expand Down Expand Up @@ -1403,6 +1410,13 @@ [email protected], debug@^2.2.0, debug@^2.3.3, debug@^2.6.9:
dependencies:
ms "2.0.0"

debug@=3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==
dependencies:
ms "2.0.0"

debug@^3.0.0, debug@^3.1.1, debug@^3.2.5:
version "3.2.6"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b"
Expand Down Expand Up @@ -2199,6 +2213,13 @@ flush-write-stream@^1.0.0:
inherits "^2.0.3"
readable-stream "^2.3.6"

[email protected]:
version "1.5.10"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.10.tgz#7b7a9f9aea2fdff36786a94ff643ed07f4ff5e2a"
integrity sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==
dependencies:
debug "=3.1.0"

follow-redirects@^1.0.0:
version "1.11.0"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.11.0.tgz#afa14f08ba12a52963140fe43212658897bc0ecb"
Expand Down