Skip to content

Commit 3e51fc2

Browse files
authored
Merge pull request #1094 from code-corps/change-github-issue-and-pull-request-relationships
Rewrite our GitHub API event syncing infrastructure
2 parents 82482bd + 363127e commit 3e51fc2

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+1257
-1122
lines changed

.github/CONTRIBUTING.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,3 +193,5 @@ We've written some convenience helper modules and functions to help with API tes
193193
- `CodeCorpsWeb.ApiCase` - used to simplify testing JSON API endpoints
194194
- `CodeCorps.AuthenticationTestHelpers` - provides authentication helpers for authenticating a `conn`
195195
- `CodeCorps.Factories` - provides factories using [`ex_machina`](https://github.com/thoughtbot/ex_machina), which makes it easy to create test data and associations with Ecto
196+
- `CodeCorps.GitHub.TestHelpers` - provides test helpers for loading GitHub API endpoint or event JSON fixtures, as well as mocking the API
197+
- `CodeCorps.GitHub.SuccessAPI` - provides some automatic mocking of the GitHub API, or raises an exception if you have an unhandled request to the API

lib/code_corps/github/api/api.ex

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
defmodule CodeCorps.GitHub.API do
22
alias CodeCorps.{
3+
GithubAppInstallation,
34
GitHub,
45
GitHub.APIError,
5-
GitHub.HTTPClientError
6+
GitHub.HTTPClientError,
7+
User
68
}
79

810
@spec request(GitHub.method, String.t, GitHub.headers, GitHub.body, list) :: GitHub.response
@@ -12,6 +14,37 @@ defmodule CodeCorps.GitHub.API do
1214
|> marshall_response()
1315
end
1416

17+
@doc """
18+
Get access token headers for a given `CodeCorps.User` and
19+
`CodeCorps.GithubAppInstallation`.
20+
21+
If the user does not have a `github_auth_token` (meaning they are not
22+
connected to GitHub), then we default to the installation which will post on
23+
behalf of the user as a bot.
24+
"""
25+
@spec opts_for(User.t, GithubAppInstallation.t) :: list
26+
def opts_for(%User{github_auth_token: nil}, %GithubAppInstallation{} = installation) do
27+
opts_for(installation)
28+
end
29+
def opts_for(%User{github_auth_token: token}, %GithubAppInstallation{}) do
30+
[access_token: token]
31+
end
32+
33+
@doc """
34+
Get access token headers for a given `CodeCorps.GithubAppInstallation`.
35+
36+
This should only be used in instances where the full permissions of the
37+
application are needed and there is no need for attribution to a user.
38+
"""
39+
@spec opts_for(GithubAppInstallation.t) :: list
40+
def opts_for(%GithubAppInstallation{} = installation) do
41+
with {:ok, token} <- installation |> GitHub.API.Installation.get_access_token do
42+
[access_token: token]
43+
else
44+
{:error, github_error} -> {:error, github_error}
45+
end
46+
end
47+
1548
@typep http_success :: {:ok, integer, [{String.t, String.t}], String.t}
1649
@typep http_failure :: {:error, term}
1750

lib/code_corps/github/api/comment.ex

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
defmodule CodeCorps.GitHub.API.Comment do
22
@moduledoc ~S"""
3-
Handles GitHub API requests for actions on Comments
3+
Functions for working with comments on GitHub.
44
"""
55

66
alias CodeCorps.{
@@ -14,6 +14,9 @@ defmodule CodeCorps.GitHub.API.Comment do
1414
User
1515
}
1616

17+
@doc """
18+
Create a comment on GitHub's API for a `CodeCorps.Comment`.
19+
"""
1720
@spec create(Comment.t) :: GitHub.response
1821
def create(
1922
%Comment{
@@ -28,13 +31,16 @@ defmodule CodeCorps.GitHub.API.Comment do
2831
endpoint = comment |> create_endpoint_for()
2932
attrs = comment |> GitHub.Adapters.Comment.to_api
3033

31-
with opts when is_list(opts) <- opts_for(user, installation) do
34+
with opts when is_list(opts) <- GitHub.API.opts_for(user, installation) do
3235
GitHub.request(:post, endpoint, %{}, attrs, opts)
3336
else
3437
{:error, github_error} -> {:error, github_error}
3538
end
3639
end
3740

41+
@doc """
42+
Update a comment on GitHub's API for a `CodeCorps.Comment`.
43+
"""
3844
@spec update(Comment.t) :: GitHub.response
3945
def update(
4046
%Comment{
@@ -49,7 +55,7 @@ defmodule CodeCorps.GitHub.API.Comment do
4955
endpoint = comment |> update_endpoint_for()
5056
attrs = comment |> GitHub.Adapters.Comment.to_api
5157

52-
with opts when is_list(opts) <- opts_for(user, installation) do
58+
with opts when is_list(opts) <- GitHub.API.opts_for(user, installation) do
5359
GitHub.request(:patch, endpoint, %{}, attrs, opts)
5460
else
5561
{:error, github_error} -> {:error, github_error}
@@ -83,16 +89,4 @@ defmodule CodeCorps.GitHub.API.Comment do
8389
}) do
8490
"/repos/#{owner}/#{repo}/issues/#{number}/comments"
8591
end
86-
87-
@spec opts_for(User.t, GithubAppInstallation.t) :: list
88-
defp opts_for(%User{github_auth_token: nil}, %GithubAppInstallation{} = installation) do
89-
with {:ok, token} <- installation |> GitHub.API.Installation.get_access_token do
90-
[access_token: token]
91-
else
92-
{:error, github_error} -> {:error, github_error}
93-
end
94-
end
95-
defp opts_for(%User{github_auth_token: token}, %GithubAppInstallation{}) do
96-
[access_token: token]
97-
end
9892
end

lib/code_corps/github/api/installation.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
defmodule CodeCorps.GitHub.API.Installation do
22
@moduledoc """
3-
Used to perform installation actions on the GitHub API.
3+
Functions for performing installation actions on the GitHub API.
44
"""
55

66
alias CodeCorps.{

lib/code_corps/github/api/issue.ex

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,32 @@ defmodule CodeCorps.GitHub.API.Issue do
33
Functions for working with issues on GitHub.
44
"""
55

6-
alias CodeCorps.{GitHub, GithubAppInstallation, GithubIssue, GithubRepo, Task, User}
6+
alias CodeCorps.{
7+
GitHub,
8+
GithubAppInstallation,
9+
GithubIssue,
10+
GithubRepo,
11+
Task,
12+
User
13+
}
714

15+
@doc """
16+
Fetches an issue from the GitHub API, given the API URL for the issue and the
17+
`CodeCorps.GithubRepo` record that points to its GitHub repository.
18+
"""
19+
def from_url(url, %GithubRepo{github_app_installation: %GithubAppInstallation{} = installation}) do
20+
"https://api.github.com/" <> endpoint = url
21+
22+
with opts when is_list(opts) <- GitHub.API.opts_for(installation) do
23+
GitHub.request(:get, endpoint, %{}, %{}, opts)
24+
else
25+
{:error, github_error} -> {:error, github_error}
26+
end
27+
end
28+
29+
@doc """
30+
Create an issue on GitHub's API for a `CodeCorps.Task`.
31+
"""
832
@spec create(Task.t) :: GitHub.response
933
def create(%Task{
1034
github_repo: %GithubRepo{
@@ -16,13 +40,16 @@ defmodule CodeCorps.GitHub.API.Issue do
1640
endpoint = github_repo |> get_endpoint()
1741
attrs = task |> GitHub.Adapters.Issue.to_api
1842

19-
with opts when is_list(opts) <- opts_for(user, installation) do
43+
with opts when is_list(opts) <- GitHub.API.opts_for(user, installation) do
2044
GitHub.request(:post, endpoint, %{}, attrs, opts)
2145
else
2246
{:error, github_error} -> {:error, github_error}
2347
end
2448
end
2549

50+
@doc """
51+
Update an issue on GitHub's API for a `CodeCorps.Task`.
52+
"""
2653
@spec update(Task.t) :: GitHub.response
2754
def update(%Task{
2855
github_issue: %GithubIssue{number: number},
@@ -35,7 +62,7 @@ defmodule CodeCorps.GitHub.API.Issue do
3562
endpoint = "#{github_repo |> get_endpoint()}/#{number}"
3663
attrs = task |> GitHub.Adapters.Issue.to_api
3764

38-
with opts when is_list(opts) <- opts_for(user, installation) do
65+
with opts when is_list(opts) <- GitHub.API.opts_for(user, installation) do
3966
GitHub.request(:patch, endpoint, %{}, attrs, opts)
4067
else
4168
{:error, github_error} -> {:error, github_error}
@@ -46,16 +73,4 @@ defmodule CodeCorps.GitHub.API.Issue do
4673
defp get_endpoint(%GithubRepo{github_account_login: owner, name: repo}) do
4774
"/repos/#{owner}/#{repo}/issues"
4875
end
49-
50-
@spec opts_for(User.t, GithubAppInstallation.t) :: list
51-
defp opts_for(%User{github_auth_token: nil}, %GithubAppInstallation{} = installation) do
52-
with {:ok, token} <- installation |> GitHub.API.Installation.get_access_token do
53-
[access_token: token]
54-
else
55-
{:error, github_error} -> {:error, github_error}
56-
end
57-
end
58-
defp opts_for(%User{github_auth_token: token}, %GithubAppInstallation{}) do
59-
[access_token: token]
60-
end
6176
end
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
defmodule CodeCorps.GitHub.API.PullRequest do
2+
@moduledoc ~S"""
3+
Functions for working with pull requests on GitHub.
4+
"""
5+
6+
alias CodeCorps.{
7+
GitHub,
8+
GithubAppInstallation,
9+
GithubRepo
10+
}
11+
12+
@doc """
13+
Fetches a pull request from the GitHub API, given the API URL for the pull
14+
request and the `CodeCorps.GithubRepo` record that points to its GitHub
15+
repository.
16+
"""
17+
def from_url(url, %GithubRepo{github_app_installation: %GithubAppInstallation{} = installation}) do
18+
"https://api.github.com/" <> endpoint = url
19+
20+
with opts when is_list(opts) <- GitHub.API.opts_for(installation) do
21+
GitHub.request(:get, endpoint, %{}, %{}, opts)
22+
else
23+
{:error, github_error} -> {:error, github_error}
24+
end
25+
end
26+
end

lib/code_corps/github/event/issue_comment/comment_deleter.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ defmodule CodeCorps.GitHub.Event.IssueComment.CommentDeleter do
1212
the specified `IssueComment`.
1313
"""
1414
@spec delete_all(map) :: {:ok, list(Comment.t)}
15-
def delete_all(%{"comment" => %{"id" => github_id}}) do
15+
def delete_all(%{"id" => github_id}) do
1616
query =
1717
from c in Comment,
1818
join: gc in GithubComment, on: gc.id == c.github_comment_id, where: gc.github_id == ^github_id

lib/code_corps/github/event/issue_comment/issue_comment.ex

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ defmodule CodeCorps.GitHub.Event.IssueComment do
1010
GitHub,
1111
GitHub.Event.IssueComment.Validator
1212
}
13-
alias GitHub.Sync.Comment, as: CommentSyncer
13+
alias GitHub.Sync
1414

15-
@type outcome :: CommentSyncer.outcome
15+
@type outcome :: Sync.outcome
1616
| {:error, :unexpected_action}
1717
| {:error, :unexpected_payload}
1818

@@ -29,7 +29,7 @@ defmodule CodeCorps.GitHub.Event.IssueComment do
2929
def handle(payload) do
3030
with {:ok, :valid} <- validate_payload(payload),
3131
{:ok, :implemented} <- validate_action(payload) do
32-
CommentSyncer.sync(payload)
32+
Sync.issue_comment_event(payload)
3333
else
3434
{:error, error} -> {:error, error}
3535
end

lib/code_corps/github/event/issues/issues.ex

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ defmodule CodeCorps.GitHub.Event.Issues do
1111
GitHub,
1212
GitHub.Event.Issues.Validator
1313
}
14-
alias GitHub.Sync.Issue, as: IssueSyncer
14+
alias GitHub.Sync
1515

16-
@type outcome :: IssueSyncer.outcome
16+
@type outcome :: Sync.outcome
1717
| {:error, :unexpected_action}
1818
| {:error, :not_fully_implemented}
1919
| {:error, :unexpected_payload}
@@ -31,7 +31,7 @@ defmodule CodeCorps.GitHub.Event.Issues do
3131
def handle(payload) do
3232
with {:ok, :valid} <- validate_payload(payload),
3333
{:ok, :implemented} <- validate_action(payload) do
34-
IssueSyncer.sync(payload)
34+
Sync.issue_event(payload)
3535
else
3636
{:error, error} -> {:error, error}
3737
end

lib/code_corps/github/event/pull_request/pull_request.ex

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ defmodule CodeCorps.GitHub.Event.PullRequest do
1111
GitHub,
1212
GitHub.Event.PullRequest.Validator
1313
}
14-
alias GitHub.Sync.PullRequest, as: PullRequestSyncer
14+
alias GitHub.Sync
1515

16-
@type outcome :: PullRequestSyncer.outcome
16+
@type outcome :: Sync.outcome
1717
| {:error, :unexpected_action}
1818
| {:error, :not_fully_implemented}
1919
| {:error, :unexpected_payload}
@@ -31,7 +31,7 @@ defmodule CodeCorps.GitHub.Event.PullRequest do
3131
def handle(payload) do
3232
with {:ok, :valid} <- validate_payload(payload),
3333
{:ok, :implemented} <- validate_action(payload) do
34-
PullRequestSyncer.sync(payload)
34+
Sync.pull_request_event(payload)
3535
else
3636
{:error, error} -> {:error, error}
3737
end

0 commit comments

Comments
 (0)