Skip to content

Commit aa15dea

Browse files
wilzbachPetarKirov
authored andcommitted
Move GitHub API code to separate module
1 parent c547fb8 commit aa15dea

File tree

3 files changed

+291
-280
lines changed

3 files changed

+291
-280
lines changed

source/dlangbot/app.d

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import dlangbot.bugzilla, dlangbot.github, dlangbot.trello,
44
dlangbot.utils;
55

66
public import dlangbot.bugzilla : bugzillaURL;
7-
public import dlangbot.github : githubAPIURL, githubAuth, hookSecret;
7+
public import dlangbot.github_api : githubAPIURL, githubAuth, hookSecret;
88
public import dlangbot.trello : trelloAPIURL, trelloAuth, trelloSecret;
99

1010
import std.datetime : Clock, days, Duration, minutes, seconds, SysTime;

source/dlangbot/github.d

Lines changed: 2 additions & 279 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
module dlangbot.github;
22

3-
string githubAPIURL = "https://api.github.com";
4-
string githubAuth, hookSecret;
5-
63
import dlangbot.bugzilla : bugzillaURL, Issue, IssueRef;
74
import dlangbot.warnings : printMessages, UserMessage;
85

@@ -13,9 +10,8 @@ import std.typecons : Tuple;
1310

1411
import vibe.core.log;
1512
import vibe.data.json;
16-
import vibe.http.client : HTTPClientRequest, requestHTTP;
17-
import vibe.http.common : HTTPMethod;
18-
import vibe.stream.operations : readAllUTF8;
13+
14+
public import dlangbot.github_api;
1915

2016
//==============================================================================
2117
// Github comments
@@ -108,50 +104,6 @@ GHComment getBotComment(in ref PullRequest pr)
108104
return GHComment();
109105
}
110106

111-
auto ghGetRequest(string url)
112-
{
113-
return requestHTTP(url, (scope req) {
114-
req.headers["Authorization"] = githubAuth;
115-
});
116-
}
117-
118-
auto ghGetRequest(scope void delegate(scope HTTPClientRequest req) userReq, string url)
119-
{
120-
return requestHTTP(url, (scope req) {
121-
req.headers["Authorization"] = githubAuth;
122-
userReq(req);
123-
});
124-
}
125-
126-
auto ghSendRequest(scope void delegate(scope HTTPClientRequest req) userReq, string url)
127-
{
128-
HTTPMethod method;
129-
requestHTTP(url, (scope req) {
130-
req.headers["Authorization"] = githubAuth;
131-
userReq(req);
132-
method = req.method;
133-
}, (scope res) {
134-
if (res.statusCode / 100 == 2)
135-
{
136-
logInfo("%s %s, %s\n", method, url, res.statusPhrase);
137-
res.bodyReader.readAllUTF8;
138-
}
139-
else
140-
logWarn("%s %s failed; %s %s.\n%s", method, url,
141-
res.statusPhrase, res.statusCode, res.bodyReader.readAllUTF8);
142-
});
143-
}
144-
145-
auto ghSendRequest(T...)(HTTPMethod method, string url, T arg)
146-
if (T.length <= 1)
147-
{
148-
return ghSendRequest((scope req) {
149-
req.method = method;
150-
static if (T.length)
151-
req.writeJsonBody(arg);
152-
}, url);
153-
}
154-
155107
void updateGithubComment(in ref PullRequest pr, in ref GHComment comment,
156108
string action, IssueRef[] refs, Issue[] descs, UserMessage[] msgs)
157109
{
@@ -177,7 +129,6 @@ void updateGithubComment(in ref PullRequest pr, in ref GHComment comment,
177129
//==============================================================================
178130

179131
alias LabelsAndCommits = Tuple!(Json[], "labels", Json[], "commits");
180-
enum MergeMethod { none = 0, merge, squash, rebase }
181132

182133
string labelName(MergeMethod method)
183134
{
@@ -357,35 +308,6 @@ void checkTitleForLabels(in ref PullRequest pr)
357308
// Search for inactive PRs
358309
//==============================================================================
359310

360-
// range-based page loader for the GH API
361-
private struct AllPages
362-
{
363-
private string url;
364-
private string link = "next";
365-
366-
// does not cache
367-
Json front() {
368-
scope req = ghGetRequest(url);
369-
link = req.headers.get("Link");
370-
return req.readJson;
371-
}
372-
void popFront()
373-
{
374-
import std.utf : byCodeUnit;
375-
if (link)
376-
url = link[1..$].byCodeUnit.until(">").array;
377-
}
378-
bool empty()
379-
{
380-
return !link.canFind("next");
381-
}
382-
}
383-
384-
auto ghGetAllPages(string url)
385-
{
386-
return AllPages(url);
387-
}
388-
389311
void searchForInactivePrs(string repoSlug, Duration dur)
390312
{
391313
auto now = Clock.currTime;
@@ -467,202 +389,3 @@ void searchForInactivePrs(string repoSlug, Duration dur)
467389
}
468390
logInfo("ended cron.daily for repo: %s (pages: %d)", repoSlug, loadedPages);
469391
}
470-
471-
//==============================================================================
472-
// Github API objects
473-
//==============================================================================
474-
475-
struct PullRequest
476-
{
477-
import std.typecons : Nullable;
478-
479-
static struct Repo
480-
{
481-
@name("full_name") string fullName;
482-
GHUser owner;
483-
}
484-
static struct Branch
485-
{
486-
string sha;
487-
string ref_;
488-
Repo repo;
489-
}
490-
Branch base, head;
491-
enum State { open, closed }
492-
enum MergeableState { clean, dirty, unstable, blocked, unknown }
493-
@byName State state;
494-
uint number;
495-
string title;
496-
@optional Nullable!bool mergeable;
497-
@optional @byName Nullable!MergeableState mergeable_state;
498-
@name("created_at") SysTime createdAt;
499-
@name("updated_at") SysTime updatedAt;
500-
bool locked;
501-
502-
GHUser user;
503-
Nullable!GHUser assignee;
504-
GHUser[] assignees;
505-
506-
string baseRepoSlug() const { return base.repo.fullName; }
507-
string headRepoSlug() const { return head.repo.fullName; }
508-
alias repoSlug = baseRepoSlug;
509-
bool isOpen() const { return state == State.open; }
510-
511-
string htmlURL() const { return "https://github.com/%s/pull/%d".format(repoSlug, number); }
512-
string commentsURL() const { return "%s/repos/%s/issues/%d/comments".format(githubAPIURL, repoSlug, number); }
513-
string commitsURL() const { return "%s/repos/%s/pulls/%d/commits".format(githubAPIURL, repoSlug, number); }
514-
string eventsURL() const { return "%s/repos/%s/issues/%d/events".format(githubAPIURL, repoSlug, number); }
515-
string labelsURL() const { return "%s/repos/%s/issues/%d/labels".format(githubAPIURL, repoSlug, number); }
516-
string reviewsURL() const { return "%s/repos/%s/pulls/%d/reviews".format(githubAPIURL, repoSlug, number); }
517-
string mergeURL() const { return "%s/repos/%s/pulls/%d/merge".format(githubAPIURL, repoSlug, number); }
518-
string statusURL() const { return "%s/repos/%s/status/%s".format(githubAPIURL, repoSlug, head.sha); }
519-
string membersURL() const { return "%s/orgs/%s/public_members".format(githubAPIURL, base.repo.owner.login); }
520-
521-
string pid() const
522-
{
523-
import std.conv : text;
524-
return text(repoSlug, "/", number);
525-
}
526-
527-
GHComment[] comments() const {
528-
return ghGetRequest(commentsURL)
529-
.readJson
530-
.deserializeJson!(GHComment[]);
531-
}
532-
GHCommit[] commits() const {
533-
return ghGetRequest(commitsURL)
534-
.readJson
535-
.deserializeJson!(GHCommit[]);
536-
}
537-
GHReview[] reviews() const {
538-
return ghGetRequest((scope req) {
539-
// custom media type is required during preview period:
540-
// preview review api: https://developer.github.com/changes/2016-12-14-reviews-api
541-
req.headers["Accept"] = "application/vnd.github.black-cat-preview+json";
542-
}, reviewsURL)
543-
.readJson
544-
.deserializeJson!(GHReview[]);
545-
}
546-
GHCiStatus[] status() const {
547-
return ghGetRequest(statusURL)
548-
.readJson["statuses"]
549-
.deserializeJson!(GHCiStatus[]);
550-
}
551-
552-
void postMerge(in ref GHMerge merge) const
553-
{
554-
ghSendRequest((scope req){
555-
req.method = HTTPMethod.PUT;
556-
// custom media type is required during preview period:
557-
// https://developer.github.com/changes/2016-09-26-pull-request-merge-api-update/
558-
req.headers["Accept"] = "application/vnd.github.polaris-preview+json";
559-
req.writeJsonBody(merge);
560-
}, mergeURL);
561-
}
562-
}
563-
564-
static struct GHUser
565-
{
566-
string login;
567-
ulong id;
568-
@name("avatar_url") string avatarURL;
569-
@name("gravatar_id") string gravatarId;
570-
string type;
571-
@name("site_admin") bool siteAdmin;
572-
}
573-
574-
struct GHComment
575-
{
576-
@name("created_at") SysTime createdAt;
577-
@name("updated_at") SysTime updatedAt;
578-
GHUser user;
579-
string body_;
580-
string url;
581-
582-
static void post(in ref PullRequest pr, string msg)
583-
{
584-
ghSendRequest(HTTPMethod.POST, pr.commentsURL, ["body" : msg]);
585-
}
586-
587-
void update(string msg) const
588-
{
589-
ghSendRequest(HTTPMethod.PATCH, url, ["body" : msg]);
590-
}
591-
592-
void remove() const
593-
{
594-
if (url.length) // delete any existing comment
595-
ghSendRequest(HTTPMethod.DELETE, url);
596-
}
597-
}
598-
599-
struct GHReview
600-
{
601-
GHUser user;
602-
@name("commit_id") string commitId;
603-
string body_;
604-
enum State { APPROVED, CHANGES_REQUESTED, COMMENTED }
605-
@byName State state;
606-
}
607-
608-
struct GHCommit
609-
{
610-
string sha;
611-
static struct CommitAuthor
612-
{
613-
string name;
614-
string email;
615-
SysTime date;
616-
}
617-
static struct Commit
618-
{
619-
CommitAuthor author;
620-
CommitAuthor committer;
621-
string message;
622-
}
623-
Commit commit;
624-
GHUser author;
625-
GHUser committer;
626-
}
627-
628-
struct GHCiStatus
629-
{
630-
enum State { success, error, failure, pending }
631-
@byName State state;
632-
string description;
633-
@name("target_url") string targetUrl;
634-
string context; // "CyberShadow/DAutoTest", "Project Tester",
635-
// "ci/circleci", "auto-tester", "codecov/project",
636-
// "codecov/patch", "continuous-integration/travis-ci/pr"
637-
}
638-
639-
struct GHMerge
640-
{
641-
@name("commit_message") string commitMessage;
642-
string sha;
643-
@name("merge_method") @byName MergeMethod mergeMethod;
644-
}
645-
646-
//==============================================================================
647-
// Github hook signature
648-
//==============================================================================
649-
650-
auto getSignature(string data)
651-
{
652-
import std.digest.digest, std.digest.hmac, std.digest.sha;
653-
import std.string : representation;
654-
655-
auto hmac = HMAC!SHA1(hookSecret.representation);
656-
hmac.put(data.representation);
657-
return hmac.finish.toHexString!(LetterCase.lower);
658-
}
659-
660-
Json verifyRequest(string signature, string data)
661-
{
662-
import std.exception : enforce;
663-
import std.string : chompPrefix;
664-
665-
enforce(getSignature(data) == signature.chompPrefix("sha1="),
666-
"Hook signature mismatch");
667-
return parseJsonString(data);
668-
}

0 commit comments

Comments
 (0)