Skip to content

Commit 45ebec0

Browse files
committed
Merge branch 'main' of github.com:algora-io/console into vibe/code
2 parents 5e45b07 + 82e98ff commit 45ebec0

File tree

5 files changed

+254
-38
lines changed

5 files changed

+254
-38
lines changed

lib/algora/activities/discord_views.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ defmodule Algora.Activities.DiscordViews do
2121
"https://github.com/#{bounty.ticket.repository.user.provider_login}/#{bounty.ticket.repository.name}/issues/#{bounty.ticket.number}"
2222
},
2323
footer: %{
24-
text: "Created by #{bounty.creator.name}",
24+
text: bounty.creator.name,
2525
icon_url: bounty.creator.avatar_url
2626
},
2727
thumbnail: %{url: bounty.owner.avatar_url},

lib/algora_web/controllers/webhooks/github_controller.ex

Lines changed: 122 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ defmodule AlgoraWeb.Webhooks.GithubController do
55
import Ecto.Query
66

77
alias Algora.Accounts
8+
alias Algora.Activities.SendDiscord
89
alias Algora.Bounties
910
alias Algora.Bounties.Bounty
1011
alias Algora.Bounties.Claim
@@ -22,16 +23,26 @@ defmodule AlgoraWeb.Webhooks.GithubController do
2223

2324
require Logger
2425

25-
# TODO: persist & alert about failed deliveries
26-
# TODO: auto-retry failed deliveries with exponential backoff
26+
# TODO: auto-retry failed deliveries with backoff
2727

2828
def process_delivery(webhook) do
2929
with :ok <- ensure_human_author(webhook),
3030
{:ok, commands} <- process_commands(webhook),
3131
:ok <- process_event(webhook, commands) do
3232
Logger.debug("✅ #{inspect(webhook.event_action)}")
33+
notify_event(webhook, :ok)
3334
:ok
35+
else
36+
{:error, reason} ->
37+
Logger.error("❌ #{inspect(webhook.event_action)}: #{inspect(reason)}")
38+
notify_event(webhook, {:error, reason})
39+
{:error, reason}
3440
end
41+
rescue
42+
error ->
43+
Logger.error("❌ #{inspect(webhook.event_action)}: #{inspect(error)}")
44+
notify_event(webhook, {:error, error})
45+
{:error, error}
3546
end
3647

3748
defp ensure_human_author(%Webhook{author: author}) do
@@ -301,12 +312,7 @@ defmodule AlgoraWeb.Webhooks.GithubController do
301312
"pull_request" -> {:ticket, payload["pull_request"]["id"]}
302313
end
303314

304-
ticket_number =
305-
case webhook.event do
306-
"issue_comment" -> payload["issue"]["number"]
307-
"issues" -> payload["issue"]["number"]
308-
"pull_request" -> payload["pull_request"]["number"]
309-
end
315+
ticket_number = get_github_ticket(webhook)["number"]
310316

311317
# TODO: perform compensating action if needed
312318
# ❌ comment1.created (:set) -> comment2.created (:increase) -> comment2.edited (:increase)
@@ -624,12 +630,8 @@ defmodule AlgoraWeb.Webhooks.GithubController do
624630
if recipient == author["login"], do: nil, else: recipient
625631
end
626632

627-
defp handle_ticket_state_change(%Webhook{event: event, payload: payload, event_action: event_action}) do
628-
github_ticket =
629-
case event do
630-
"issues" -> payload["issue"]
631-
"pull_request" -> payload["pull_request"]
632-
end
633+
defp handle_ticket_state_change(%Webhook{payload: payload, event_action: event_action} = webhook) do
634+
github_ticket = get_github_ticket(webhook)
633635

634636
state =
635637
if event_action == "issues.deleted" do
@@ -659,4 +661,110 @@ defmodule AlgoraWeb.Webhooks.GithubController do
659661
end
660662
end
661663
end
664+
665+
defp notify_event(%Webhook{event_action: event_action, author: author, payload: payload} = webhook, :ok) do
666+
with github_ticket when not is_nil(github_ticket) <- get_github_ticket(webhook),
667+
ticket when not is_nil(ticket) <-
668+
Workspace.get_ticket(
669+
payload["repository"]["owner"]["login"],
670+
payload["repository"]["name"],
671+
github_ticket["number"]
672+
) do
673+
discord_payload = %{
674+
payload: %{
675+
embeds: [
676+
%{
677+
color: 0x64748B,
678+
title: event_action,
679+
author: %{
680+
name: payload["repository"]["owner"]["login"],
681+
icon_url: payload["repository"]["owner"]["avatar_url"],
682+
url: github_ticket["html_url"]
683+
},
684+
footer: %{
685+
text: author["login"],
686+
icon_url: author["avatar_url"]
687+
},
688+
fields: [
689+
%{
690+
name: "Ticket",
691+
value: "#{payload["repository"]["name"]}##{github_ticket["number"]}: #{github_ticket["title"]}",
692+
inline: false
693+
}
694+
],
695+
url: github_ticket["html_url"],
696+
timestamp: DateTime.utc_now()
697+
}
698+
]
699+
}
700+
}
701+
702+
case discord_payload |> SendDiscord.changeset() |> Repo.insert() do
703+
{:ok, _} ->
704+
:ok
705+
706+
{:error, reason} ->
707+
Logger.error("Error sending discord notification: #{inspect(reason)}")
708+
{:error, reason}
709+
end
710+
else
711+
_ -> :ok
712+
end
713+
end
714+
715+
defp notify_event(%Webhook{event_action: event_action, author: author, payload: payload} = webhook, {:error, error}) do
716+
case get_github_ticket(webhook) do
717+
github_ticket when not is_nil(github_ticket) ->
718+
discord_payload = %{
719+
payload: %{
720+
embeds: [
721+
%{
722+
color: 0xEF4444,
723+
title: event_action,
724+
description: inspect(error),
725+
author: %{
726+
name: payload["repository"]["owner"]["login"],
727+
icon_url: payload["repository"]["owner"]["avatar_url"],
728+
url: github_ticket["html_url"]
729+
},
730+
footer: %{
731+
text: author["login"],
732+
icon_url: author["avatar_url"]
733+
},
734+
fields: [
735+
%{
736+
name: "Ticket",
737+
value: "#{payload["repository"]["name"]}##{github_ticket["number"]}: #{github_ticket["title"]}",
738+
inline: false
739+
}
740+
],
741+
url: github_ticket["html_url"],
742+
timestamp: DateTime.utc_now()
743+
}
744+
]
745+
}
746+
}
747+
748+
case discord_payload |> SendDiscord.changeset() |> Repo.insert() do
749+
{:ok, _} ->
750+
:ok
751+
752+
{:error, reason} ->
753+
Logger.error("Error sending discord notification: #{inspect(reason)}")
754+
{:error, reason}
755+
end
756+
757+
_ ->
758+
:ok
759+
end
760+
end
761+
762+
defp get_github_ticket(%Webhook{event: event, payload: payload}) do
763+
case event do
764+
"issues" -> payload["issue"]
765+
"issue_comment" -> payload["issue"]
766+
"pull_request" -> payload["pull_request"]
767+
_ -> nil
768+
end
769+
end
662770
end

lib/algora_web/controllers/webhooks/stripe_controller.ex

Lines changed: 124 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ defmodule AlgoraWeb.Webhooks.StripeController do
44
import Ecto.Changeset
55
import Ecto.Query
66

7+
alias Algora.Activities.SendDiscord
78
alias Algora.Bounties
89
alias Algora.Bounties.Bounty
910
alias Algora.Bounties.Tip
@@ -19,11 +20,38 @@ defmodule AlgoraWeb.Webhooks.StripeController do
1920
@metadata_version Payments.metadata_version()
2021

2122
@impl true
22-
def handle_event(%Stripe.Event{
23-
type: "charge.succeeded",
24-
data: %{object: %Stripe.Charge{metadata: %{"version" => @metadata_version, "group_id" => group_id}}}
25-
})
26-
when is_binary(group_id) do
23+
def handle_event(%Stripe.Event{} = event) do
24+
result =
25+
case process_event(event) do
26+
:ok -> :ok
27+
{:ok, _} -> :ok
28+
{:error, reason} -> {:error, reason}
29+
:error -> {:error, :unknown_error}
30+
end
31+
32+
case result do
33+
:ok ->
34+
Logger.debug("✅ #{inspect(event.type)}")
35+
notify_event(event, :ok)
36+
:ok
37+
38+
{:error, reason} ->
39+
Logger.error("❌ #{inspect(event.type)}: #{inspect(reason)}")
40+
notify_event(event, {:error, reason})
41+
{:error, reason}
42+
end
43+
rescue
44+
error ->
45+
Logger.error("❌ #{inspect(event.type)}: #{inspect(error)}")
46+
notify_event(event, {:error, error})
47+
{:error, error}
48+
end
49+
50+
defp process_event(%Stripe.Event{
51+
type: "charge.succeeded",
52+
data: %{object: %Stripe.Charge{metadata: %{"version" => @metadata_version, "group_id" => group_id}}}
53+
})
54+
when is_binary(group_id) do
2755
Repo.transact(fn ->
2856
{_, txs} =
2957
Repo.update_all(from(t in Transaction, where: t.group_id == ^group_id, select: t),
@@ -77,11 +105,10 @@ defmodule AlgoraWeb.Webhooks.StripeController do
77105
end)
78106
end
79107

80-
@impl true
81-
def handle_event(%Stripe.Event{
82-
type: "transfer.created",
83-
data: %{object: %Stripe.Transfer{metadata: %{"version" => @metadata_version}} = transfer}
84-
}) do
108+
defp process_event(%Stripe.Event{
109+
type: "transfer.created",
110+
data: %{object: %Stripe.Transfer{metadata: %{"version" => @metadata_version}} = transfer}
111+
}) do
85112
with {:ok, transaction} <- Repo.fetch_by(Transaction, provider: "stripe", provider_id: transfer.id),
86113
{:ok, _transaction} <- maybe_update_transaction(transaction, transfer),
87114
{:ok, _job} <- Oban.insert(Bounties.Jobs.NotifyTransfer.new(%{transfer_id: transaction.id})) do
@@ -94,11 +121,10 @@ defmodule AlgoraWeb.Webhooks.StripeController do
94121
end
95122
end
96123

97-
@impl true
98-
def handle_event(%Stripe.Event{
99-
type: "checkout.session.completed",
100-
data: %{object: %Stripe.Session{customer: customer_id, mode: "setup", setup_intent: setup_intent_id}}
101-
}) do
124+
defp process_event(%Stripe.Event{
125+
type: "checkout.session.completed",
126+
data: %{object: %Stripe.Session{customer: customer_id, mode: "setup", setup_intent: setup_intent_id}}
127+
}) do
102128
with {:ok, setup_intent} <- Algora.PSP.SetupIntent.retrieve(setup_intent_id, %{}),
103129
pm_id = setup_intent.payment_method,
104130
{:ok, payment_method} <- Algora.PSP.PaymentMethod.attach(%{payment_method: pm_id, customer: customer_id}),
@@ -109,13 +135,11 @@ defmodule AlgoraWeb.Webhooks.StripeController do
109135
end
110136
end
111137

112-
@impl true
113-
def handle_event(%Stripe.Event{type: "checkout.session.completed"} = event) do
138+
defp process_event(%Stripe.Event{type: "checkout.session.completed"} = event) do
114139
Logger.info("Stripe #{event.type} event: #{event.id}")
115140
end
116141

117-
@impl true
118-
def handle_event(_event), do: :ok
142+
defp process_event(_event), do: :ok
119143

120144
defp maybe_update_transaction(transaction, transfer) do
121145
if transaction.status == :succeeded do
@@ -130,4 +154,85 @@ defmodule AlgoraWeb.Webhooks.StripeController do
130154
|> Repo.update()
131155
end
132156
end
157+
158+
defp notify_event(%Stripe.Event{} = event, :ok) do
159+
discord_payload = %{
160+
payload: %{
161+
embeds: [
162+
%{
163+
color: 0x64748B,
164+
title: event.type,
165+
footer: %{
166+
text: "Stripe",
167+
icon_url: "https://github.com/stripe.png"
168+
},
169+
fields: [
170+
%{
171+
name: "Event",
172+
value: event.id,
173+
inline: true
174+
},
175+
%{
176+
name: event.data.object.object,
177+
value: event.data.object.id,
178+
inline: true
179+
}
180+
],
181+
url: "https://dashboard.stripe.com/payments?status[0]=successful",
182+
timestamp: DateTime.utc_now()
183+
}
184+
]
185+
}
186+
}
187+
188+
case discord_payload |> SendDiscord.changeset() |> Repo.insert() do
189+
{:ok, _} ->
190+
:ok
191+
192+
{:error, reason} ->
193+
Logger.error("Error sending discord notification: #{inspect(reason)}")
194+
{:error, reason}
195+
end
196+
end
197+
198+
defp notify_event(%Stripe.Event{} = event, {:error, error}) do
199+
discord_payload = %{
200+
payload: %{
201+
embeds: [
202+
%{
203+
color: 0xEF4444,
204+
title: event.type,
205+
# description: inspect(error),
206+
footer: %{
207+
text: "Stripe",
208+
icon_url: "https://github.com/stripe.png"
209+
},
210+
fields: [
211+
%{
212+
name: "Event",
213+
value: event.id,
214+
inline: true
215+
},
216+
%{
217+
name: event.data.object.object,
218+
value: event.data.object.id,
219+
inline: true
220+
}
221+
],
222+
url: "https://dashboard.stripe.com/payments?status[0]=failed",
223+
timestamp: DateTime.utc_now()
224+
}
225+
]
226+
}
227+
}
228+
229+
case discord_payload |> SendDiscord.changeset() |> Repo.insert() do
230+
{:ok, _} ->
231+
:ok
232+
233+
{:error, reason} ->
234+
Logger.error("Error sending discord notification: #{inspect(reason)}")
235+
{:error, reason}
236+
end
237+
end
133238
end

lib/algora_web/live/home_live.ex

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,16 @@ defmodule AlgoraWeb.HomeLive do
1313
alias Algora.Payments.Transaction
1414
alias Algora.PSP.ConnectCountries
1515
alias Algora.Repo
16+
alias Algora.Workspace
1617
alias AlgoraWeb.Components.Footer
1718
alias AlgoraWeb.Components.Header
1819
alias AlgoraWeb.Components.Wordmarks
1920
alias AlgoraWeb.Data.PlatformStats
2021
alias AlgoraWeb.Forms.BountyForm
2122
alias AlgoraWeb.Forms.TipForm
2223

24+
require Logger
25+
2326
@impl true
2427
def mount(%{"country_code" => country_code}, _session, socket) do
2528
Gettext.put_locale(AlgoraWeb.Gettext, Algora.Util.locale_from_country_code(country_code))

0 commit comments

Comments
 (0)