Skip to content

Commit caed346

Browse files
committed
show activities in header
broadcast from Oban job enqueue email deilvery add mail templates
1 parent 256a115 commit caed346

39 files changed

+259
-73
lines changed

config/config.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ config :algora, Oban,
3333
github_og_image: 5,
3434
notify_bounty: 1,
3535
activity_notifier: 1,
36-
email_notfier: 1
36+
activity_mailer: 1
3737
]
3838

3939
# Configures the mailer

lib/algora/activities/activities.ex

Lines changed: 109 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ defmodule Algora.Activities do
55
alias Algora.Accounts.Identity
66
alias Algora.Accounts.User
77
alias Algora.Activities.Activity
8+
alias Algora.Activities.Router
9+
alias Algora.Activities.Views
810
alias Algora.Bounties.Bounty
911
alias Algora.Repo
1012

@@ -199,48 +201,130 @@ defmodule Algora.Activities do
199201
assoc_id: a.assoc_id,
200202
assoc_name: ^table,
201203
assoc: t,
202-
inserted_at: a.inserted_at
204+
notify_users: a.notify_users,
205+
visibility: a.visibility,
206+
template: a.template,
207+
meta: a.meta,
208+
changes: a.changes,
209+
trace_id: a.trace_id,
210+
previous_event_id: a.previous_event_id,
211+
inserted_at: a.inserted_at,
212+
updated_at: a.updated_at
203213
}
204214

215+
struct(Activity, Algora.Repo.one(query))
216+
end
217+
218+
def get_with_preloaded_assoc(table, id) do
219+
schema = schema_from_table(table)
220+
221+
with %{assoc_id: assoc_id} = activity <- get(table, id),
222+
assoc when is_map(assoc) <- get_preloaded_assoc(schema, assoc_id) do
223+
Map.put(activity, :assoc, assoc)
224+
end
225+
end
226+
227+
def get_preloaded_assoc(schema, assoc_id) do
228+
query =
229+
if Kernel.function_exported?(schema, :query, 1) do
230+
schema.query(assoc_id)
231+
else
232+
from a in schema, where: a.id == ^assoc_id
233+
end
234+
205235
Algora.Repo.one(query)
206236
end
207237

208-
def get_assoc(prefix, assoc_id) when prefix in ["bounty_activities"] do
209-
get_assoc(prefix, assoc_id, [:owner])
238+
def assoc_url(table, id) do
239+
table |> get(id) |> Router.route()
210240
end
211241

212-
def get_assoc(prefix, assoc_id) when prefix in ["identity_activities"] do
213-
get_assoc(prefix, assoc_id, [:user])
242+
def subscribe do
243+
Phoenix.PubSub.subscribe(Algora.PubSub, "activities")
214244
end
215245

216-
def get_assoc(prefix, assoc_id, preload) do
217-
assoc_table = schema_from_table(prefix)
246+
def subscribe(schema) when is_atom(schema) do
247+
schema |> schema_from_table() |> subscribe()
248+
end
218249

219-
query =
220-
from a in assoc_table,
221-
preload: ^preload,
222-
where: a.id == ^assoc_id
250+
def subscribe_table(table) when is_binary(table) do
251+
Phoenix.PubSub.subscribe(Algora.PubSub, "activity:table:#{table}")
252+
end
223253

224-
Algora.Repo.one(query)
254+
def subscribe_user(user_id) when is_binary(user_id) do
255+
Phoenix.PubSub.subscribe(Algora.PubSub, "activity:users:#{user_id}")
225256
end
226257

227-
def get_with_assoc(table, id) do
228-
with %{assoc_id: assoc_id} = activity <- get(table, id),
229-
assoc when is_map(assoc) <- get_assoc(table, assoc_id) do
230-
Map.put(activity, :assoc, assoc)
231-
end
258+
def broadcast(%{notify_users: []}), do: []
259+
260+
def broadcast(%{notify_users: user_ids} = activity) do
261+
:ok = Phoenix.PubSub.broadcast(Algora.PubSub, "activities", activity)
262+
:ok = Phoenix.PubSub.broadcast(Algora.PubSub, "activity:table:#{activity.assoc_name}", activity)
263+
264+
users_query =
265+
from u in Algora.Accounts.User,
266+
where: u.id in ^user_ids,
267+
select: u
268+
269+
users_query
270+
|> Algora.Repo.all()
271+
|> Enum.reduce([], fn user, not_online ->
272+
# TODO setup notification preferences
273+
:ok = Phoenix.PubSub.broadcast(Algora.PubSub, "activity:users:#{user.id}", activity)
274+
[user | not_online]
275+
end)
232276
end
233277

234-
def assoc_url(table, id) do
235-
activity = get_with_assoc(table, id)
236-
build_url(activity)
278+
def notify_users(_activity, []), do: :ok
279+
280+
def notify_users(activity, users_to_notify) do
281+
title = activity_email_title(activity)
282+
body = activity_email_body(activity)
283+
284+
users_to_notify
285+
|> Enum.reduce([], fn
286+
%{name: display_name, email: email, id: id}, acc ->
287+
changeset =
288+
Algora.Activities.SendEmail.changeset(%{
289+
title: title,
290+
body: body,
291+
user_id: id,
292+
activity_id: activity.id,
293+
activity_type: activity.type,
294+
activity_table: activity.assoc_name,
295+
name: display_name,
296+
email: email
297+
})
298+
299+
[changeset | acc]
300+
301+
_user, acc ->
302+
acc
303+
end)
304+
|> Oban.insert_all()
305+
end
306+
307+
def activity_email_title(activity) do
308+
apply(Views, :"#{activity.type}_title", [activity, activity.assoc])
237309
end
238310

239-
def build_url(%{assoc: %Bounty{owner: user}}), do: {:ok, "/org/#{user.handle}/bounties"}
240-
def build_url(%{assoc: %Identity{user: %{type: :individual} = user}}), do: {:ok, "/@/#{user.handle}"}
241-
def build_url(%{assoc: %Identity{user: %{type: :organization} = user}}), do: {:ok, "/org/#{user.handle}"}
311+
def activity_email_body(activity) do
312+
apply(Views, :"#{activity.type}_txt", [activity, activity.assoc])
313+
end
314+
315+
def redirect_url_for_activity(activity) do
316+
slug =
317+
activity.assoc_name
318+
|> to_string()
319+
|> String.replace("_activities", "")
320+
321+
"/a/#{slug}/#{activity.id}"
322+
end
242323

243-
def build_url(_activity) do
244-
{:error, :not_found}
324+
def activity_type_to_name(type) do
325+
type
326+
|> to_string()
327+
|> String.split("_")
328+
|> Enum.map_join(" ", &String.capitalize(&1))
245329
end
246330
end

lib/algora/activities/jobs/notifier.ex

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,16 @@ defmodule Algora.Activities.Notifier do
1919
end
2020

2121
@impl Oban.Worker
22-
def perform(%Oban.Job{args: args} = job) do
22+
def perform(%Oban.Job{args: args}) do
2323
case args do
2424
%{
2525
"activity_id" => activity_id,
26-
"target_id" => target_id,
2726
"table_name" => table
28-
} = args
27+
}
2928
when is_binary(table) ->
30-
_activity = Activities.get(table, activity_id)
31-
29+
activity = Activities.get_with_preloaded_assoc(table, activity_id)
30+
users_to_notify = Activities.broadcast(activity)
31+
Activities.notify_users(activity, users_to_notify)
3232
:ok
3333

3434
_args ->

lib/algora/activities/jobs/send_email.ex

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
defmodule Algora.Activities.SendEmail do
22
@moduledoc false
33
use Oban.Worker,
4-
queue: :activities,
5-
max_attempts: 3,
6-
tags: ["mail", "activities"]
4+
queue: :activity_mailer,
5+
max_attempts: 1,
6+
tags: ["email", "activities"]
77

88
# unique: [period: 30]
99

10-
def changeset(activity, target) do
11-
new(%{activity_id: activity.id, target_id: target.id})
10+
def changeset(attrs) do
11+
new(attrs)
1212
end
1313

1414
@impl Oban.Worker

lib/algora/activities/router.ex

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
defmodule Algora.Activities.Router do
2+
alias Algora.Accounts.Identity
3+
alias Algora.Bounties.Bounty
4+
5+
def route(%{assoc: %Bounty{owner: user}}), do: {:ok, "/org/#{user.handle}/bounties"}
6+
7+
def route(%{assoc: %Identity{user: %{type: :individual} = user}}), do: {:ok, "/@/#{user.handle}"}
8+
9+
def route(%{assoc: %Identity{user: %{type: :organization} = user}}), do: {:ok, "/org/#{user.handle}"}
10+
11+
def route(_activity) do
12+
{:error, :not_found}
13+
end
14+
end

lib/algora/activities/schemas/activity.ex

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,6 @@ defmodule Algora.Activities.Activity do
99
contract_prepaid
1010
contract_created
1111
contract_renewed
12-
transaction_status_change
13-
transaction_created
14-
transaction_failed
15-
transaction_processed
1612
identity_created
1713
bounty_awarded
1814
bounty_posted
@@ -32,17 +28,30 @@ defmodule Algora.Activities.Activity do
3228
field :trace_id, :string
3329
field :notify_users, {:array, :string}, default: []
3430
field :assoc_name, :string, virtual: true
31+
field :assoc, :map, virtual: true
3532

3633
belongs_to :user, Algora.Accounts.User
3734
belongs_to :previous_event, __MODULE__
3835

3936
timestamps()
4037
end
4138

39+
def types, do: @activity_types
40+
4241
@doc false
4342
def changeset(activity, attrs) do
4443
activity
45-
|> cast(attrs, [:type, :visibility, :template, :meta, :changes, :trace_id, :user_id, :previous_event_id])
44+
|> cast(attrs, [
45+
:type,
46+
:visibility,
47+
:template,
48+
:meta,
49+
:changes,
50+
:trace_id,
51+
:user_id,
52+
:previous_event_id,
53+
:notify_users
54+
])
4655
|> validate_required([:type])
4756
|> foreign_key_constraint(:assoc_id)
4857
|> foreign_key_constraint(:user_id)

lib/algora/activities/views.ex

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
defmodule Algora.Activities.Views do
2+
@moduledoc false
3+
alias Algora.Activities.Activity
4+
5+
require Algora.Activities.Activity
6+
require EEx
7+
8+
@base_path Path.join([File.cwd!(), "lib", "algora", "activities", "views"])
9+
10+
Enum.each(Activity.types(), fn type ->
11+
base_type = type |> to_string() |> String.split("_") |> List.first() |> String.to_atom()
12+
EEx.function_from_file(:def, :"#{type}_txt", Path.join(@base_path, "#{type}.txt.eex"), [:activity, base_type])
13+
EEx.function_from_file(:def, :"#{type}_title", Path.join(@base_path, "#{type}.title.eex"), [:activity, base_type])
14+
end)
15+
end
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
You have been rewarded a bounty on Algora!
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<%= bounty.creator.display_name %> awarded you a Bounty for <% bounty.amount %>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
New bounty posted to Algora

0 commit comments

Comments
 (0)