Skip to content

Commit 4ee8fbe

Browse files
authored
Merge pull request #1206 from code-corps/1168-add-update-to-github-events-endpoint
Add update action to /github-events endpoint
2 parents 822b821 + 838f041 commit 4ee8fbe

9 files changed

Lines changed: 169 additions & 21 deletions

File tree

lib/code_corps/github/webhook/handler.ex

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ defmodule CodeCorps.GitHub.Webhook.Handler do
2525
"""
2626
@spec handle_supported(String.t, String.t, map) :: {:ok, GithubEvent.t}
2727
def handle_supported(type, id, %{} = payload) do
28-
with {:ok, %GithubEvent{} = event} <- type |> build_params(id, "unprocessed", payload) |> create_event() do
28+
with {:ok, %GithubEvent{} = event} <- find_or_create_event(type, id, payload, "unprocessed") do
2929
payload |> apply_handler(type) |> Event.stop_processing(event)
3030
end
3131
end
@@ -41,7 +41,7 @@ defmodule CodeCorps.GitHub.Webhook.Handler do
4141
"""
4242
@spec handle_unsupported(String.t, String.t, map) :: {:ok, GithubEvent.t}
4343
def handle_unsupported(type, id, %{} = payload) do
44-
type |> build_params(id, "unsupported", payload) |> create_event()
44+
find_or_create_event(type, id, payload, "unsupported")
4545
end
4646

4747
@spec build_params(String.t, String.t, String.t, map) :: map
@@ -55,6 +55,14 @@ defmodule CodeCorps.GitHub.Webhook.Handler do
5555
}
5656
end
5757

58+
@spec find_or_create_event(String.t, String.t, map, String.t) :: {:ok, GithubEvent.t}
59+
defp find_or_create_event(type, id, payload, status) do
60+
case GithubEvent |> Repo.get_by(github_delivery_id: id) do
61+
nil -> type |> build_params(id, status, payload) |> create_event()
62+
%GithubEvent{} = github_event -> {:ok, github_event}
63+
end
64+
end
65+
5866
@spec create_event(map) :: {:ok, GithubEvent.t}
5967
defp create_event(%{} = params) do
6068
%GithubEvent{} |> GithubEvent.changeset(params) |> Repo.insert()

lib/code_corps/model/github_event.ex

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,18 @@ defmodule CodeCorps.GithubEvent do
22
use CodeCorps.Model
33
use Scrivener, page_size: 20
44

5+
alias Ecto.Changeset
6+
57
@type t :: %__MODULE__{}
68

79
schema "github_events" do
810
field :action, :string
911
field :data, :string
12+
field :error, :string
1013
field :failure_reason, :string
1114
field :github_delivery_id, :string
1215
field :payload, :map
13-
field :error, :string
16+
field :retry, :boolean, virtual: true
1417
field :status, :string
1518
field :type, :string
1619

@@ -24,5 +27,26 @@ defmodule CodeCorps.GithubEvent do
2427
struct
2528
|> cast(params, [:action, :data, :github_delivery_id, :payload, :error, :status, :type])
2629
|> validate_required([:action, :github_delivery_id, :payload, :status, :type])
30+
|> validate_inclusion(:status, statuses())
31+
end
32+
33+
def update_changeset(struct, params \\ %{}) do
34+
struct
35+
|> cast(params, [:retry, :status])
36+
|> validate_acceptance(:retry)
37+
|> validate_retry()
38+
|> validate_inclusion(:status, statuses())
39+
end
40+
41+
def statuses do
42+
~w{unprocessed processing processed errored unsupported reprocessing}
43+
end
44+
45+
defp validate_retry(%Changeset{changes: %{retry: true}} = changeset) do
46+
case changeset |> Changeset.get_field(:status) do
47+
"errored" -> Changeset.put_change(changeset, :status, "reprocessing")
48+
_ -> Changeset.add_error(changeset, :retry, "only possible when status is errored")
49+
end
2750
end
51+
defp validate_retry(changeset), do: changeset
2852
end

lib/code_corps/policy/github_event.ex

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,7 @@ defmodule CodeCorps.Policy.GithubEvent do
66

77
def show?(%User{admin: true}), do: true
88
def show?(%User{admin: false}), do: false
9+
10+
def update?(%User{admin: true}), do: true
11+
def update?(%User{admin: false}), do: false
912
end

lib/code_corps/policy/policy.ex

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ defmodule CodeCorps.Policy do
4343
# GithubEvent
4444
defp can?(%User{} = current_user, :index, %GithubEvent{}, %{}), do: Policy.GithubEvent.index?(current_user)
4545
defp can?(%User{} = current_user, :show, %GithubEvent{}, %{}), do: Policy.GithubEvent.show?(current_user)
46+
defp can?(%User{} = current_user, :update, %GithubEvent{}, %{}), do: Policy.GithubEvent.update?(current_user)
4647

4748
# GithubRepo
4849
defp can?(%User{} = current_user, :update, %GithubRepo{} = github_repo, %{} = params), do: Policy.GithubRepo.update?(current_user, github_repo, params)

lib/code_corps_web/controllers/github_event_controller.ex

Lines changed: 48 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -46,16 +46,20 @@ defmodule CodeCorpsWeb.GithubEventController do
4646
type = conn |> get_event_type
4747
delivery_id = conn |> get_delivery_id()
4848
action = payload |> Map.get("action", "")
49+
event_support = type |> EventSupport.status(action)
50+
event_support |> process_event(type, delivery_id, payload)
51+
conn |> respond_to_webhook(event_support)
52+
end
4953

50-
case type |> EventSupport.status(action) do
51-
:supported ->
52-
Processor.process(fn -> Handler.handle_supported(type, delivery_id, payload) end)
53-
conn |> send_resp(200, "")
54-
:unsupported ->
55-
Processor.process(fn -> Handler.handle_unsupported(type, delivery_id, payload) end)
56-
conn |> send_resp(200, "")
57-
:ignored ->
58-
conn |> send_resp(202, "")
54+
@spec update(Conn.t, map) :: Conn.t
55+
def update(%Conn{} = conn, %{"id" => id} = params) do
56+
with %GithubEvent{} = github_event <- GithubEvent |> Repo.get(id),
57+
%User{} = current_user <- conn |> Guardian.Plug.current_resource,
58+
{:ok, :authorized} <- current_user |> Policy.authorize(:update, github_event, params),
59+
changeset <- github_event |> GithubEvent.update_changeset(params),
60+
{:ok, updated_github_event} <- changeset |> retry_event()
61+
do
62+
conn |> render("show.json-api", data: updated_github_event)
5963
end
6064
end
6165

@@ -76,4 +80,39 @@ defmodule CodeCorpsWeb.GithubEventController do
7680
defp paginate(query, _) do
7781
query |> Repo.all()
7882
end
83+
84+
@spec process_event(atom, String.t, String.t, map) :: any | :ok
85+
defp process_event(:supported, type, delivery_id, payload) do
86+
Processor.process(fn -> Handler.handle_supported(type, delivery_id, payload) end)
87+
end
88+
defp process_event(:unsupported, type, delivery_id, payload) do
89+
Processor.process(fn -> Handler.handle_unsupported(type, delivery_id, payload) end)
90+
end
91+
defp process_event(:ignored, _, _, _), do: :ok
92+
93+
@type retry_outcome :: {:ok, GithubEvent.t} | {:error, Ecto.Changeset.t} | :ok
94+
95+
@spec retry_event(Ecto.Changeset.t) :: retry_outcome
96+
defp retry_event(%Ecto.Changeset{data: %GithubEvent{action: action, type: type}} = changeset) do
97+
type
98+
|> EventSupport.status(action)
99+
|> do_retry_event(changeset)
100+
end
101+
102+
@spec do_retry_event(atom, Ecto.Changeset.t) :: retry_outcome
103+
defp do_retry_event(:ignored, _changeset), do: nil
104+
defp do_retry_event(support, %Ecto.Changeset{data: %GithubEvent{github_delivery_id: delivery_id, payload: payload, type: type}} = changeset) do
105+
case changeset |> Repo.update() do
106+
{:ok, %GithubEvent{} = github_event} ->
107+
process_event(support, type, delivery_id, payload)
108+
{:ok, github_event}
109+
{:error, error} ->
110+
{:error, error}
111+
end
112+
end
113+
114+
@spec respond_to_webhook(Conn.t, atom) :: Conn.t
115+
defp respond_to_webhook(conn, :supported), do: conn |> send_resp(200, "")
116+
defp respond_to_webhook(conn, :unsupported), do: conn |> send_resp(200, "")
117+
defp respond_to_webhook(conn, :ignored), do: conn |> send_resp(202, "")
79118
end

lib/code_corps_web/router.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ defmodule CodeCorpsWeb.Router do
7070
resources "/donation-goals", DonationGoalController, only: [:create, :update, :delete]
7171
post "/oauth/github", UserController, :github_oauth
7272
resources "/github-app-installations", GithubAppInstallationController, only: [:create]
73-
resources "/github-events", GithubEventController, only: [:index, :show]
73+
resources "/github-events", GithubEventController, only: [:index, :show, :update]
7474
resources "/github-repos", GithubRepoController, only: [:update]
7575
resources "/organization-github-app-installations", OrganizationGithubAppInstallationController, only: [:create, :delete]
7676
resources "/organizations", OrganizationController, only: [:create, :update]

test/lib/code_corps/model/github_event_test.exs

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,50 @@ defmodule CodeCorps.GithubEventTest do
77
action: "some content",
88
github_delivery_id: "71aeab80-9e59-11e7-81ac-198364bececc",
99
payload: %{"key" => "value"},
10-
status: "some content",
10+
status: "processing",
1111
type: "some content"
1212
}
1313
@invalid_attrs %{}
1414

15-
test "changeset with valid attributes" do
16-
changeset = GithubEvent.changeset(%GithubEvent{}, @valid_attrs)
17-
assert changeset.valid?
15+
describe "changeset/2" do
16+
test "with valid attributes" do
17+
changeset = GithubEvent.changeset(%GithubEvent{}, @valid_attrs)
18+
assert changeset.valid?
19+
end
20+
21+
test "with invalid attributes" do
22+
changeset = GithubEvent.changeset(%GithubEvent{}, @invalid_attrs)
23+
refute changeset.valid?
24+
end
25+
26+
test "validates inclusion of status" do
27+
attrs = @valid_attrs |> Map.put(:status, "foo")
28+
changeset = GithubEvent.changeset(%GithubEvent{}, attrs)
29+
refute changeset.valid?
30+
assert changeset.errors[:status] == {"is invalid", [validation: :inclusion]}
31+
end
1832
end
1933

20-
test "changeset with invalid attributes" do
21-
changeset = GithubEvent.changeset(%GithubEvent{}, @invalid_attrs)
22-
refute changeset.valid?
34+
describe "update_changeset/2" do
35+
test "with retry true and status errored" do
36+
attrs = @valid_attrs |> Map.merge(%{retry: true, status: "errored"})
37+
changeset = GithubEvent.update_changeset(%GithubEvent{status: "errored"}, attrs)
38+
assert changeset.valid?
39+
assert changeset.changes[:status] == "reprocessing"
40+
end
41+
42+
test "with retry true and status not errored" do
43+
attrs = @valid_attrs |> Map.put(:retry, true)
44+
changeset = GithubEvent.update_changeset(%GithubEvent{status: "foo"}, attrs)
45+
refute changeset.valid?
46+
assert_error_message(changeset, :retry, "only possible when status is errored")
47+
end
48+
49+
test "with retry false" do
50+
attrs = @valid_attrs |> Map.put(:retry, false)
51+
changeset = GithubEvent.update_changeset(%GithubEvent{}, attrs)
52+
refute changeset.valid?
53+
refute changeset.changes[:status] == "reprocessing"
54+
end
2355
end
2456
end

test/lib/code_corps/policy/github_event_test.exs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
defmodule CodeCorps.GithubEventPolicyTest do
22
use CodeCorps.PolicyCase
33

4-
import CodeCorps.Policy.GithubEvent, only: [index?: 1, show?: 1]
4+
import CodeCorps.Policy.GithubEvent, only: [index?: 1, show?: 1, update?: 1]
55

66
describe "index" do
77
test "returns true when user is an admin" do
@@ -26,4 +26,16 @@ defmodule CodeCorps.GithubEventPolicyTest do
2626
refute show?(user)
2727
end
2828
end
29+
30+
describe "update" do
31+
test "returns true when user is an admin" do
32+
user = insert(:user, admin: true)
33+
assert update?(user)
34+
end
35+
36+
test "returns false when user is not an admin" do
37+
user = insert(:user, admin: false)
38+
refute update?(user)
39+
end
40+
end
2941
end

test/lib/code_corps_web/controllers/github_event_controller_test.exs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,4 +114,33 @@ defmodule CodeCorpsWeb.GithubEventControllerTest do
114114
refute Repo.get_by(GithubEvent, github_delivery_id: "foo")
115115
end
116116
end
117+
118+
describe "update" do
119+
@valid_attrs %{retry: true}
120+
121+
@tag authenticated: :admin
122+
test "updates when the status was errored", %{conn: conn} do
123+
payload = load_event_fixture("pull_request_opened")
124+
github_event = insert(:github_event, action: "opened", github_delivery_id: "foo", payload: payload, status: "errored", type: "pull_request")
125+
126+
assert conn |> request_update(github_event, @valid_attrs) |> json_response(200)
127+
end
128+
129+
@tag authenticated: :admin
130+
test "does not update for any other status", %{conn: conn} do
131+
payload = load_event_fixture("pull_request_opened")
132+
github_event = insert(:github_event, action: "opened", payload: payload, status: "processed", type: "pull_request")
133+
134+
assert conn |> request_update(github_event, @valid_attrs) |> json_response(422)
135+
end
136+
137+
test "renders 401 when unauthenticated", %{conn: conn} do
138+
assert conn |> request_update |> json_response(401)
139+
end
140+
141+
@tag :authenticated
142+
test "renders 403 when unauthorized", %{conn: conn} do
143+
assert conn |> request_update |> json_response(403)
144+
end
145+
end
117146
end

0 commit comments

Comments
 (0)