gitlab: initial implementation

This commit is contained in:
selfisekai 2020-08-22 00:06:18 +02:00
parent bc67a758e8
commit 3d57ea347c
2 changed files with 221 additions and 0 deletions

165
src/vendor/gitlab/repomgr.ts vendored Normal file
View file

@ -0,0 +1,165 @@
/*
* Copycat. Copyright (C) 2020 selfisekai <laura@selfisekai.rocks> and other contributors.
*
* This is free software, and you are welcome to redistribute it
* under the GNU General Public License 3.0 or later; see the LICENSE file for details,
* or, if the file is unavailable, visit <https://www.gnu.org/licenses/gpl-3.0-standalone.html>.
*/
import {
RepoManager,
Issue,
Repo,
ACTOR_TYPE,
ISSUE_STATE,
MergeRequest,
MERGE_REQUEST_STATE,
MERGE_REQUEST_MERGABILITY,
} from '../../types';
import GitHubVendorManager from './vendormgr';
import assert from 'assert';
export default class GitHubRepoManager implements RepoManager {
vendorMgr: GitHubVendorManager;
repo: Repo;
repoPath: string;
constructor(vendorMgr: GitHubVendorManager, repoPath: string) {
this.vendorMgr = vendorMgr;
const mobj = /^([^/\s]+)\/([^\s]+)$/.exec(repoPath);
assert(mobj, 'invalid repo path');
// "small path" may be just repository name
// or the whole path except the top-level owner
const [, owner, repoSmallPath] = mobj;
this.repo = {
vendor: this.vendorMgr.vendor,
owner: {
type: ACTOR_TYPE.UNKNOWN,
username: owner,
vendor: this.vendorMgr.vendor,
},
name: repoSmallPath,
};
this.repoPath = repoPath;
}
public async initialize() {
let meta = await this.vendorMgr._doRequest(
`
query ($path: ID!, $ownerStr: String!, $ownerID: ID!) {
project(fullPath: $path) {
path
namespace {
path
}
}
group(fullPath: $ownerID) {
id
}
user(username: $ownerStr) {
id
}
}
`,
{
path: this.repoPath,
// there's no way to check if the namespace is a user or group,
// so we check if there's anyone using that username
// but one is a String, and one is an ID
// and these types just don't overlap 🤷‍♀️
ownerStr: this.repo.owner.username,
ownerID: this.repo.owner.username,
},
);
assert(meta.project);
assert(meta.group || meta.user);
this.repo.owner.username = meta.project.path;
if (meta.group) {
this.repo.owner.type = ACTOR_TYPE.ORG;
} else if (meta.user) {
this.repo.owner.type = ACTOR_TYPE.USER;
}
return this;
}
public async getIssue(number: string): Promise<Issue> {
const resp = await this.vendorMgr._doRequest(
`
query ($path: ID!, $id: String!) {
project(fullPath: $path) {
issue(iid: $id) {
title
description
state
}
}
}
`,
{
path: this.repoPath,
id: number,
},
);
assert(resp.project, 'no project');
assert(resp.project.issue, 'no issue');
const { issue } = resp.project;
return {
id: number,
content: issue.description || '',
title: issue.title,
repo: this.repo,
state: {
opened: ISSUE_STATE.OPEN,
closed: ISSUE_STATE.CLOSED,
locked: ISSUE_STATE.CLOSED,
}[issue.state],
};
}
public async getMergeRequest(number: string): Promise<MergeRequest> {
const resp = await this.vendorMgr._doRequest(
`
query ($path: ID!, $id: String!) {
project(fullPath: $path) {
mergeRequest(iid: $id) {
title
description
state
mergeStatus
workInProgress
}
}
}
`,
{
path: this.repoPath,
id: number,
},
);
assert(resp.project, 'no project');
assert(resp.project.mergeRequest, 'no merge request');
const { mergeRequest } = resp.project;
return {
id: number,
content: mergeRequest.description || '',
title: mergeRequest.title,
repo: this.repo,
state: {
opened: MERGE_REQUEST_STATE.OPEN,
closed: MERGE_REQUEST_STATE.CLOSED,
merged: MERGE_REQUEST_STATE.MERGED,
locked: MERGE_REQUEST_STATE.CLOSED,
}[mergeRequest.state],
mergability:
({
can_be_merged: MERGE_REQUEST_MERGABILITY.MERGEABLE,
cannot_be_merged: MERGE_REQUEST_MERGABILITY.CONFLICTING,
} as { [key: string]: MERGE_REQUEST_MERGABILITY | undefined })[
mergeRequest.mergeStatus || ''
] || null,
isDraft: mergeRequest.workInProgress,
};
}
}

56
src/vendor/gitlab/vendormgr.ts vendored Normal file
View file

@ -0,0 +1,56 @@
/*
* Copycat. Copyright (C) 2020 selfisekai <laura@selfisekai.rocks> and other contributors.
*
* This is free software, and you are welcome to redistribute it
* under the GNU General Public License 3.0 or later; see the LICENSE file for details,
* or, if the file is unavailable, visit <https://www.gnu.org/licenses/gpl-3.0-standalone.html>.
*/
import got from 'got';
import { VendorManager, VENDOR_TYPE, Vendor, RepoManager } from '../../types';
import { GLQuery } from './api-types';
import GitLabRepoManager from './repomgr';
export interface GitLabConfig {
/** not required for read actions on public repos */
token?: string | null;
domain: string;
}
export default class GitHubVendorManager implements VendorManager<GitLabConfig> {
vendor: Vendor;
config: GitLabConfig;
gqlEndpoint: string;
constructor(config: GitLabConfig) {
this.vendor = {
display: `GitLab @ ${config.domain}`,
type: VENDOR_TYPE.GITLAB,
domain: config.domain,
};
this.config = config;
this.gqlEndpoint = `https://${this.vendor.domain}/api/graphql`;
}
public async initialize() {
return this;
}
public async getRepo(path: string) {
return new GitLabRepoManager(this, path).initialize() as Promise<RepoManager>;
}
/** internal and for RepoManager */
public async _doRequest(query: string, variables: any) {
return got
.post(this.gqlEndpoint, {
body: JSON.stringify({ query, variables }),
headers: {
Authorization: `Bearer ${this.config.token}`,
'Content-Type': 'application/json',
},
})
.then((res) => JSON.parse(res.body))
.then((res) => res.data) as Promise<GLQuery>;
}
}