• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

zaneriley / personal-site / #63

06 Sep 2024 02:04AM UTC coverage: 69.7%. Remained the same
#63

push

github

web-flow
Merge 371543f7d into 697ba7fb5

68 of 97 new or added lines in 8 files covered. (70.1%)

16 existing lines in 5 files now uncovered.

674 of 967 relevant lines covered (69.7%)

37.74 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

87.88
/lib/portfolio_web/controllers/content_webhook_controller.ex
1
defmodule PortfolioWeb.ContentWebhookController do
2
  @moduledoc """
3
  Handles incoming GitHub webhook payloads for content updates.
4

5
  This controller is responsible for processing webhook payloads from GitHub,
6
  determining if they contain relevant changes to the content, and triggering
7
  content updates when necessary.
8

9
  ## Key functions
10

11
  - `handle_push/2`: Entrypoint for processing webhook payloads
12
  - `process_payload/1`: Determines if a payload contains relevant changes
13
  - `trigger_update/0`: Initiates the content update process
14

15
  ## Dependencies
16

17
  This module relies on:
18
  - `Portfolio.Content.Remote.RemoteUpdateTrigger` for triggering updates
19
  - `Portfolio.Content.Types` for determining content types
20
  """
21

22
  require Logger
23
  use PortfolioWeb, :controller
24
  alias Portfolio.Content.Remote.RemoteUpdateTrigger
25
  alias Portfolio.Content.Types
26

27
  @doc """
28
  Processes the webhook payload and determines if an update is needed.
29

30
  This function examines the payload for relevant file changes and triggers
31
  an update if necessary.
32

33
  ## Parameters
34

35
  - `payload`: A map containing the webhook payload from GitHub
36

37
  ## Returns
38

39
  - `{:ok, :updated}` if relevant changes were found and an update was triggered
40
  - `{:ok, :no_relevant_changes}` if no relevant changes were found
41
  - `{:error, reason}` if an error occurred during processing
42

43
  ## Examples
44

45
      iex> payload = %{"commits" => [%{"added" => ["content/new_post.md"]}]}
46
      iex> ContentWebhookController.process_payload(payload)
47
      {:ok, :updated}
48

49
  """
50
  @spec handle_push(Plug.Conn.t(), map()) :: Plug.Conn.t()
51
  def handle_push(conn, %{"payload" => payload}) do
52
    Logger.info("Received webhook payload: #{inspect(payload)}")
5✔
53

54
    payload
55
    |> validate_payload()
56
    |> process_payload()
57
    |> handle_process_result(conn)
5✔
58
  end
59

60
  @spec validate_payload(map()) ::
61
          {:ok, map()} | {:error, :invalid_payload, String.t()}
62
  defp validate_payload(payload) do
63
    cond do
5✔
64
      is_nil(payload["repository"]) ->
65
        {:error, :invalid_payload, "Missing repository information"}
2✔
66

67
      is_nil(payload["commits"]) ->
3✔
68
        {:error, :invalid_payload, "Missing commits information"}
1✔
69

70
      true ->
2✔
71
        {:ok, payload}
72
    end
73
  end
74

75
  @spec process_payload({:ok, map()} | {:error, :invalid_payload, String.t()}) ::
76
          {:ok, :updated | :no_relevant_changes}
77
          | {:error, :invalid_payload, String.t()}
78
          | {:error, any()}
79
  defp process_payload({:error, _, _} = error), do: error
80

81
  defp process_payload({:ok, payload}) do
82
    if payload_has_relevant_changes?(payload) do
2✔
83
      Logger.info("Relevant file changes detected")
1✔
84
      trigger_update()
1✔
85
    else
86
      Logger.info("No relevant file changes detected")
1✔
87
      {:ok, :no_relevant_changes}
88
    end
89
  end
90

91
  @spec payload_has_relevant_changes?(map()) :: boolean()
92
  defp payload_has_relevant_changes?(payload) do
93
    payload
94
    |> extract_changed_files()
95
    |> Enum.any?(&relevant_file_change?/1)
2✔
96
  end
97

98
  @spec extract_changed_files(map()) :: [String.t()]
99
  defp extract_changed_files(payload) do
100
    payload["commits"]
101
    |> Enum.flat_map(fn commit ->
2✔
102
      (commit["added"] || []) ++ (commit["modified"] || [])
1✔
103
    end)
104
  end
105

106
  @spec relevant_file_change?(String.t()) :: boolean()
107
  defp relevant_file_change?(path) do
108
    with true <- Path.extname(path) == ".md",
1✔
109
         true <- not String.starts_with?(Path.basename(path), "."),
1✔
110
         {:ok, _type} <- Types.get_type(path) do
1✔
111
      true
112
    else
113
      _ -> false
114
    end
115
  end
116

117
  @spec trigger_update() :: {:ok, :updated} | {:error, any()}
118
  defp trigger_update() do
119
    Logger.info("Triggering update with RemoteUpdateTrigger")
1✔
120

121
    case RemoteUpdateTrigger.trigger_update(content_repo_url()) do
1✔
122
      {:ok, _} = result ->
NEW
123
        Logger.info("RemoteUpdateTrigger completed successfully")
×
NEW
124
        result
×
125

126
      {:error, reason} = error ->
127
        Logger.error("RemoteUpdateTrigger failed: #{inspect(reason)}")
1✔
128
        error
1✔
129
    end
130
  end
131

132
  @spec handle_process_result(
133
          {:ok, :updated | :no_relevant_changes}
134
          | {:error, :invalid_payload, String.t()}
135
          | {:error, any()},
136
          Plug.Conn.t()
137
        ) :: Plug.Conn.t()
138
  defp handle_process_result({:ok, :updated}, conn) do
NEW
139
    Logger.info("Webhook processed successfully")
×
140

NEW
141
    send_json_resp(conn, :ok, %{
×
142
      message: "Content update process initiated successfully"
143
    })
144
  end
145

146
  defp handle_process_result({:ok, :no_relevant_changes}, conn) do
147
    Logger.info("No relevant changes detected")
1✔
148
    send_json_resp(conn, :ok, %{message: "No relevant changes detected"})
1✔
149
  end
150

151
  defp handle_process_result({:error, :invalid_payload, message}, conn) do
152
    Logger.warning("Invalid payload received: #{message}")
3✔
153

154
    send_json_resp(conn, :bad_request, %{
3✔
155
      error: "Invalid payload",
156
      message: message
157
    })
158
  end
159

160
  defp handle_process_result({:error, reason}, conn) do
161
    Logger.error("Failed to process webhook: #{inspect(reason)}")
1✔
162

163
    send_json_resp(conn, :internal_server_error, %{
1✔
164
      error: "Internal server error"
165
    })
166
  end
167

168
  # Updated helper function to send JSON responses
169
  defp send_json_resp(conn, status, data) do
170
    conn
171
    |> put_resp_content_type("application/json")
172
    |> send_resp(Plug.Conn.Status.code(status), Jason.encode!(data))
5✔
173
  end
174

175
  defp content_repo_url do
176
    Application.get_env(:portfolio, :content_repo_url)
1✔
177
  end
178
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc