Add APIs for checking the state of GitHub

This commit is contained in:
Will Hunt 2021-12-01 13:52:24 +00:00
parent fb6fdd548d
commit 4eb48afa21
3 changed files with 184 additions and 3 deletions

View File

@ -133,7 +133,7 @@ export class Bridge {
}
if (this.config.github) {
routers.push({
route: "/v1/jira",
route: "/v1/github",
router: new GitHubProvisionerRouter(this.config.github, this.tokenStore).getRouter(),
});
}

View File

@ -1,8 +1,30 @@
import { Router, Request, Response } from "express";
import { Router, Request, Response, NextFunction } from "express";
import { BridgeConfigGitHub } from "../Config/Config";
import { ApiError, ErrCode } from "../provisioning/api";
import { UserTokenStore } from "../UserTokenStore";
import { generateGitHubOAuthUrl } from "./AdminCommands";
import LogWrapper from "../LogWrapper";
const log = new LogWrapper("GitHubProvisionerRouter");
interface GitHubAccountStatus {
loggedIn: boolean;
organisations?: {
name: string;
avatarUrl: string;
}[]
}
interface GitHubRepoItem {
name: string;
owner: string;
fullName: string;
description: string|null;
avatarUrl: string;
}
interface GitHubRepoResponse {
page: number;
repositories: GitHubRepoItem[];
}
export class GitHubProvisionerRouter {
constructor(private readonly config: BridgeConfigGitHub, private readonly tokenStore: UserTokenStore) { }
@ -10,6 +32,9 @@ export class GitHubProvisionerRouter {
public getRouter() {
const router = Router();
router.get("/oauth", this.onOAuth.bind(this));
router.get("/account", this.onGetAccount.bind(this));
router.get("/orgs/:orgName/repositories", this.onGetOrgRepositories.bind(this));
router.get("/repositories", this.onGetRepositories.bind(this));
return router;
}
@ -21,4 +46,96 @@ export class GitHubProvisionerRouter {
url: generateGitHubOAuthUrl(this.config.oauth.client_id, this.config.oauth.redirect_uri, this.tokenStore.createStateForOAuth(req.query.userId))
});
}
private async onGetAccount(req: Request<undefined, undefined, undefined, {userId: string, page: string, perPage: string}>, res: Response<GitHubAccountStatus>, next: NextFunction) {
const octokit = await this.tokenStore.getOctokitForUser(req.query.userId);
if (!octokit) {
return res.send({
loggedIn: false,
});
}
const organisations = [];
const page = req.query.page ? parseInt(req.query.page) : 1;
const perPage = req.query.perPage ? parseInt(req.query.perPage) : 10;
try {
const orgRes = await octokit.orgs.listForAuthenticatedUser({page, per_page: perPage});
for (const org of orgRes.data) {
organisations.push({
name: org.login,
avatarUrl: org.avatar_url,
});
}
} catch (ex) {
log.warn(`Failed to fetch orgs for GitHub user ${req.query.userId}`, ex);
return next( new ApiError("Could not fetch orgs for GitHub user", ErrCode.Unknown));
}
return res.send({
loggedIn: true,
organisations,
})
}
private async onGetOrgRepositories(req: Request<{orgName: string}, undefined, undefined, {userId: string, page: string, perPage: string}>, res: Response<GitHubRepoResponse>, next: NextFunction) {
const octokit = await this.tokenStore.getOctokitForUser(req.query.userId);
if (!octokit) {
// TODO: Better error?
return next(new ApiError("Not logged in", ErrCode.ForbiddenUser));
}
const repositories = [];
const page = req.query.page ? parseInt(req.query.page) : 1;
const perPage = req.query.perPage ? parseInt(req.query.perPage) : 10;
try {
const orgRes = await octokit.repos.listForOrg({org: req.params.orgName, page, per_page: perPage});
for (const repo of orgRes.data) {
repositories.push({
name: repo.name,
owner: repo.owner.login,
fullName: repo.full_name,
description: repo.description,
avatarUrl: repo.owner.avatar_url,
});
}
return res.send({
page,
repositories,
});
} catch (ex) {
log.warn(`Failed to fetch accessible repos for ${req.params.orgName} / ${req.query.userId}`, ex);
return next(new ApiError("Could not fetch accessible repos for GitHub org", ErrCode.Unknown));
}
}
private async onGetRepositories(req: Request<undefined, undefined, undefined, {userId: string, page: string, perPage: string}>, res: Response<GitHubRepoResponse>, next: NextFunction) {
const octokit = await this.tokenStore.getOctokitForUser(req.query.userId);
if (!octokit) {
// TODO: Better error?
return next(new ApiError("Not logged in", ErrCode.ForbiddenUser));
}
const repositories = [];
const page = req.query.page ? parseInt(req.query.page) : 1;
const perPage = req.query.perPage ? parseInt(req.query.perPage) : 10;
try {
const orgRes = await octokit.repos.listForAuthenticatedUser({page, per_page: perPage, type: "member"});
for (const repo of orgRes.data) {
repositories.push({
name: repo.name,
owner: repo.owner.login,
fullName: repo.full_name,
description: repo.description,
avatarUrl: repo.owner.avatar_url,
});
}
return res.send({
page,
repositories,
});
} catch (ex) {
log.warn(`Failed to fetch accessible repos for ${req.query.userId}`, ex);
return next(new ApiError("Could not fetch accessible repos for GitHub user", ErrCode.Unknown));
}
}
}

View File

@ -178,6 +178,69 @@ the bridge will be granted access.
}]
```
### GET /github/v1/account?userId={userId}
Request the status of the users account. This will return a `loggedIn` value to determine if the
bridge has a GitHub identity stored for the user, and any organisations they have access to.
### Response
```json5
{
"loggedIn": true,
"organisations": {
"name": "half-shot",
"avatarUrl": "https://avatars.githubusercontent.com/u/8418310?v=4"
}
}
```
### GET /github/v1/orgs/{orgName}/repositories?userId={userId}&page={page}&perPage={perPage}
Request a list of all repositories a user is a member of in the given org. The `owner` and `name` value of a repository can be given to create a new GitHub connection.
This request is paginated, and `page` sets the page (defaults to `1`) while `perPage` (defaults to `10`) sets the number of entries per page.
This request can be retried until the number of entries is less than the value of `perPage`.
### Response
```json5
{
"loggedIn": true,
"repositories": {
"name": "matrix-hookshot",
"owner": "half-shot",
"fullName": "half-shot/matrix-hookshot",
"avatarUrl": "https://avatars.githubusercontent.com/u/8418310?v=4",
"description": "A bridge between Matrix and multiple project management services, such as GitHub, GitLab and JIRA. "
}
}
```
### GET /github/v1/repositories?userId={userId}&page={page}&perPage={perPage}
Request a list of all repositories a user is a member of (including those not belonging to an org). The `owner` and `name` value of a repository can be given to create a new GitHub connection.
This request is paginated, and `page` sets the page (defaults to `1`) while `perPage` (defaults to `10`) sets the number of entries per page.
This request can be retried until the number of entries is less than the value of `perPage`.
### Response
```json5
{
"loggedIn": true,
"repositories": {
"name": "matrix-hookshot",
"owner": "half-shot",
"fullName": "half-shot/matrix-hookshot",
"avatarUrl": "https://avatars.githubusercontent.com/u/8418310?v=4",
"description": "A bridge between Matrix and multiple project management services, such as GitHub, GitLab and JIRA. "
}
}
```
## JIRA
@ -228,4 +291,5 @@ a new JIRA connection.
"name": "Jira Playground",
"id": "10015"
}
}
}
```