Ver código fonte

Merge branch 'feature/digest-email' into 'develop'

Feature/digest email

See merge request pleroma/pleroma!1078
tags/v1.0.90
lain 2 meses atrás
pai
commit
29807ef6a5

+ 2
- 0
CHANGELOG.md Ver arquivo

@@ -93,6 +93,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
93 93
 - Rich media: Do not crawl private IP ranges
94 94
 
95 95
 ### Added
96
+- Digest email for inactive users
96 97
 - Add a generic settings store for frontends / clients to use.
97 98
 - Explicit addressing option for posting.
98 99
 - Optional SSH access mode. (Needs `erlang-ssh` package on some distributions).
@@ -119,6 +120,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
119 120
 - Configuration: `notify_email` option
120 121
 - Configuration: Media proxy `whitelist` option
121 122
 - Configuration: `report_uri` option
123
+- Configuration: `email_notifications` option
122 124
 - Configuration: `limit_to_local_content` option
123 125
 - Pleroma API: User subscriptions
124 126
 - Pleroma API: Healthcheck endpoint

+ 8
- 0
config/config.exs Ver arquivo

@@ -514,6 +514,14 @@ config :pleroma, Pleroma.ScheduledActivity,
514 514
   total_user_limit: 300,
515 515
   enabled: true
516 516
 
517
+config :pleroma, :email_notifications,
518
+  digest: %{
519
+    active: false,
520
+    schedule: "0 0 * * 0",
521
+    interval: 7,
522
+    inactivity_threshold: 7
523
+  }
524
+
517 525
 config :pleroma, :oauth2,
518 526
   token_expires_in: 600,
519 527
   issue_new_refresh_token: true,

+ 2
- 0
config/test.exs Ver arquivo

@@ -81,6 +81,8 @@ rum_enabled = System.get_env("RUM_ENABLED") == "true"
81 81
 config :pleroma, :database, rum_enabled: rum_enabled
82 82
 IO.puts("RUM enabled: #{rum_enabled}")
83 83
 
84
+config :joken, default_signer: "yU8uHKq+yyAkZ11Hx//jcdacWc8yQ1bxAAGrplzB0Zwwjkp35v0RK9SO8WTPr6QZ"
85
+
84 86
 config :pleroma, Pleroma.ReverseProxy.Client, Pleroma.ReverseProxy.ClientMock
85 87
 
86 88
 try do

+ 12
- 0
docs/config.md Ver arquivo

@@ -536,6 +536,18 @@ Authentication / authorization settings.
536 536
 * `oauth_consumer_template`: OAuth consumer mode authentication form template. By default it's `consumer.html` which corresponds to `lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex`.
537 537
 * `oauth_consumer_strategies`: the list of enabled OAuth consumer strategies; by default it's set by `OAUTH_CONSUMER_STRATEGIES` environment variable. Each entry in this space-delimited string should be of format `<strategy>` or `<strategy>:<dependency>` (e.g. `twitter` or `keycloak:ueberauth_keycloak_strategy` in case dependency is named differently than `ueberauth_<strategy>`).
538 538
 
539
+## :email_notifications
540
+
541
+Email notifications settings.
542
+
543
+  - digest - emails of "what you've missed" for users who have been
544
+    inactive for a while.
545
+    - active: globally enable or disable digest emails
546
+    - schedule: When to send digest email, in [crontab format](https://en.wikipedia.org/wiki/Cron).
547
+      "0 0 * * 0" is the default, meaning "once a week at midnight on Sunday morning"
548
+    - interval: Minimum interval between digest emails to one user
549
+    - inactivity_threshold: Minimum user inactivity threshold
550
+
539 551
 ## OAuth consumer mode
540 552
 
541 553
 OAuth consumer mode allows sign in / sign up via external OAuth providers (e.g. Twitter, Facebook, Google, Microsoft, etc.).

+ 33
- 0
lib/mix/tasks/pleroma/digest.ex Ver arquivo

@@ -0,0 +1,33 @@
1
+defmodule Mix.Tasks.Pleroma.Digest do
2
+  use Mix.Task
3
+
4
+  @shortdoc "Manages digest emails"
5
+  @moduledoc """
6
+  Manages digest emails
7
+
8
+  ## Send digest email since given date (user registration date by default)
9
+  ignoring user activity status.
10
+
11
+  ``mix pleroma.digest test <nickname> <since_date>``
12
+
13
+  Example: ``mix pleroma.digest test donaldtheduck 2019-05-20``
14
+  """
15
+  def run(["test", nickname | opts]) do
16
+    Mix.Pleroma.start_pleroma()
17
+
18
+    user = Pleroma.User.get_by_nickname(nickname)
19
+
20
+    last_digest_emailed_at =
21
+      with [date] <- opts,
22
+           {:ok, datetime} <- Timex.parse(date, "{YYYY}-{0M}-{0D}") do
23
+        datetime
24
+      else
25
+        _ -> user.inserted_at
26
+      end
27
+
28
+    patched_user = %{user | last_digest_emailed_at: last_digest_emailed_at}
29
+
30
+    _user = Pleroma.DigestEmailWorker.perform(patched_user)
31
+    Mix.shell().info("Digest email have been sent to #{nickname} (#{user.email})")
32
+  end
33
+end

+ 2
- 0
lib/mix/tasks/pleroma/instance.ex Ver arquivo

@@ -183,6 +183,7 @@ defmodule Mix.Tasks.Pleroma.Instance do
183 183
         )
184 184
 
185 185
       secret = :crypto.strong_rand_bytes(64) |> Base.encode64() |> binary_part(0, 64)
186
+      jwt_secret = :crypto.strong_rand_bytes(64) |> Base.encode64() |> binary_part(0, 64)
186 187
       signing_salt = :crypto.strong_rand_bytes(8) |> Base.encode64() |> binary_part(0, 8)
187 188
       {web_push_public_key, web_push_private_key} = :crypto.generate_key(:ecdh, :prime256v1)
188 189
       template_dir = Application.app_dir(:pleroma, "priv") <> "/templates"
@@ -200,6 +201,7 @@ defmodule Mix.Tasks.Pleroma.Instance do
200 201
           dbuser: dbuser,
201 202
           dbpass: dbpass,
202 203
           secret: secret,
204
+          jwt_secret: jwt_secret,
203 205
           signing_salt: signing_salt,
204 206
           web_push_public_key: Base.url_encode64(web_push_public_key, padding: false),
205 207
           web_push_private_key: Base.url_encode64(web_push_private_key, padding: false),

+ 16
- 1
lib/pleroma/application.ex Ver arquivo

@@ -162,7 +162,9 @@ defmodule Pleroma.Application do
162 162
     # See http://elixir-lang.org/docs/stable/elixir/Supervisor.html
163 163
     # for other strategies and supported options
164 164
     opts = [strategy: :one_for_one, name: Pleroma.Supervisor]
165
-    Supervisor.start_link(children, opts)
165
+    result = Supervisor.start_link(children, opts)
166
+    :ok = after_supervisor_start()
167
+    result
166 168
   end
167 169
 
168 170
   defp setup_instrumenters do
@@ -227,4 +229,17 @@ defmodule Pleroma.Application do
227 229
       :hackney_pool.child_spec(pool, options)
228 230
     end
229 231
   end
232
+
233
+  defp after_supervisor_start do
234
+    with digest_config <- Application.get_env(:pleroma, :email_notifications)[:digest],
235
+         true <- digest_config[:active] do
236
+      PleromaJobQueue.schedule(
237
+        digest_config[:schedule],
238
+        :digest_emails,
239
+        Pleroma.DigestEmailWorker
240
+      )
241
+    end
242
+
243
+    :ok
244
+  end
230 245
 end

+ 35
- 0
lib/pleroma/digest_email_worker.ex Ver arquivo

@@ -0,0 +1,35 @@
1
+defmodule Pleroma.DigestEmailWorker do
2
+  import Ecto.Query
3
+
4
+  @queue_name :digest_emails
5
+
6
+  def perform do
7
+    config = Pleroma.Config.get([:email_notifications, :digest])
8
+    negative_interval = -Map.fetch!(config, :interval)
9
+    inactivity_threshold = Map.fetch!(config, :inactivity_threshold)
10
+    inactive_users_query = Pleroma.User.list_inactive_users_query(inactivity_threshold)
11
+
12
+    now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
13
+
14
+    from(u in inactive_users_query,
15
+      where: fragment(~s(? #> '{"email_notifications","digest"}' @> 'true'), u.info),
16
+      where: u.last_digest_emailed_at < datetime_add(^now, ^negative_interval, "day"),
17
+      select: u
18
+    )
19
+    |> Pleroma.Repo.all()
20
+    |> Enum.each(&PleromaJobQueue.enqueue(@queue_name, __MODULE__, [&1]))
21
+  end
22
+
23
+  @doc """
24
+  Send digest email to the given user.
25
+  Updates `last_digest_emailed_at` field for the user and returns the updated user.
26
+  """
27
+  @spec perform(Pleroma.User.t()) :: Pleroma.User.t()
28
+  def perform(user) do
29
+    with %Swoosh.Email{} = email <- Pleroma.Emails.UserEmail.digest_email(user) do
30
+      Pleroma.Emails.Mailer.deliver_async(email)
31
+    end
32
+
33
+    Pleroma.User.touch_last_digest_emailed_at(user)
34
+  end
35
+end

+ 70
- 1
lib/pleroma/emails/user_email.ex Ver arquivo

@@ -5,7 +5,7 @@
5 5
 defmodule Pleroma.Emails.UserEmail do
6 6
   @moduledoc "User emails"
7 7
 
8
-  import Swoosh.Email
8
+  use Phoenix.Swoosh, view: Pleroma.Web.EmailView, layout: {Pleroma.Web.LayoutView, :email}
9 9
 
10 10
   alias Pleroma.Web.Endpoint
11 11
   alias Pleroma.Web.Router
@@ -87,4 +87,73 @@ defmodule Pleroma.Emails.UserEmail do
87 87
     |> subject("#{instance_name()} account confirmation")
88 88
     |> html_body(html_body)
89 89
   end
90
+
91
+  @doc """
92
+  Email used in digest email notifications
93
+  Includes Mentions and New Followers data
94
+  If there are no mentions (even when new followers exist), the function will return nil
95
+  """
96
+  @spec digest_email(Pleroma.User.t()) :: Swoosh.Email.t() | nil
97
+  def digest_email(user) do
98
+    new_notifications =
99
+      Pleroma.Notification.for_user_since(user, user.last_digest_emailed_at)
100
+      |> Enum.reduce(%{followers: [], mentions: []}, fn
101
+        %{activity: %{data: %{"type" => "Create"}, actor: actor} = activity} = notification,
102
+        acc ->
103
+          new_mention = %{
104
+            data: notification,
105
+            object: Pleroma.Object.normalize(activity),
106
+            from: Pleroma.User.get_by_ap_id(actor)
107
+          }
108
+
109
+          %{acc | mentions: [new_mention | acc.mentions]}
110
+
111
+        %{activity: %{data: %{"type" => "Follow"}, actor: actor} = activity} = notification,
112
+        acc ->
113
+          new_follower = %{
114
+            data: notification,
115
+            object: Pleroma.Object.normalize(activity),
116
+            from: Pleroma.User.get_by_ap_id(actor)
117
+          }
118
+
119
+          %{acc | followers: [new_follower | acc.followers]}
120
+
121
+        _, acc ->
122
+          acc
123
+      end)
124
+
125
+    with [_ | _] = mentions <- new_notifications.mentions do
126
+      html_data = %{
127
+        instance: instance_name(),
128
+        user: user,
129
+        mentions: mentions,
130
+        followers: new_notifications.followers,
131
+        unsubscribe_link: unsubscribe_url(user, "digest")
132
+      }
133
+
134
+      new()
135
+      |> to(recipient(user))
136
+      |> from(sender())
137
+      |> subject("Your digest from #{instance_name()}")
138
+      |> render_body("digest.html", html_data)
139
+    else
140
+      _ ->
141
+        nil
142
+    end
143
+  end
144
+
145
+  @doc """
146
+  Generate unsubscribe link for given user and notifications type.
147
+  The link contains JWT token with the data, and subscription can be modified without
148
+  authorization.
149
+  """
150
+  @spec unsubscribe_url(Pleroma.User.t(), String.t()) :: String.t()
151
+  def unsubscribe_url(user, notifications_type) do
152
+    token =
153
+      %{"sub" => user.id, "act" => %{"unsubscribe" => notifications_type}, "exp" => false}
154
+      |> Pleroma.JWT.generate_and_sign!()
155
+      |> Base.encode64()
156
+
157
+    Router.Helpers.subscription_url(Pleroma.Web.Endpoint, :unsubscribe, token)
158
+  end
90 159
 end

+ 9
- 0
lib/pleroma/jwt.ex Ver arquivo

@@ -0,0 +1,9 @@
1
+defmodule Pleroma.JWT do
2
+  use Joken.Config
3
+
4
+  @impl true
5
+  def token_config do
6
+    default_claims(skip: [:aud])
7
+    |> add_claim("aud", &Pleroma.Web.Endpoint.url/0, &(&1 == Pleroma.Web.Endpoint.url()))
8
+  end
9
+end

+ 26
- 2
lib/pleroma/notification.ex Ver arquivo

@@ -18,6 +18,8 @@ defmodule Pleroma.Notification do
18 18
   import Ecto.Query
19 19
   import Ecto.Changeset
20 20
 
21
+  @type t :: %__MODULE__{}
22
+
21 23
   schema "notifications" do
22 24
     field(:seen, :boolean, default: false)
23 25
     belongs_to(:user, User, type: Pleroma.FlakeId)
@@ -31,7 +33,7 @@ defmodule Pleroma.Notification do
31 33
     |> cast(attrs, [:seen])
32 34
   end
33 35
 
34
-  def for_user_query(user, opts) do
36
+  def for_user_query(user, opts \\ []) do
35 37
     query =
36 38
       Notification
37 39
       |> where(user_id: ^user.id)
@@ -75,6 +77,25 @@ defmodule Pleroma.Notification do
75 77
     |> Pagination.fetch_paginated(opts)
76 78
   end
77 79
 
80
+  @doc """
81
+  Returns notifications for user received since given date.
82
+
83
+  ## Examples
84
+
85
+      iex> Pleroma.Notification.for_user_since(%Pleroma.User{}, ~N[2019-04-13 11:22:33])
86
+      [%Pleroma.Notification{}, %Pleroma.Notification{}]
87
+
88
+      iex> Pleroma.Notification.for_user_since(%Pleroma.User{}, ~N[2019-04-15 11:22:33])
89
+      []
90
+  """
91
+  @spec for_user_since(Pleroma.User.t(), NaiveDateTime.t()) :: [t()]
92
+  def for_user_since(user, date) do
93
+    from(n in for_user_query(user),
94
+      where: n.updated_at > ^date
95
+    )
96
+    |> Repo.all()
97
+  end
98
+
78 99
   def set_read_up_to(%{id: user_id} = _user, id) do
79 100
     query =
80 101
       from(
@@ -82,7 +103,10 @@ defmodule Pleroma.Notification do
82 103
         where: n.user_id == ^user_id,
83 104
         where: n.id <= ^id,
84 105
         update: [
85
-          set: [seen: true]
106
+          set: [
107
+            seen: true,
108
+            updated_at: ^NaiveDateTime.utc_now()
109
+          ]
86 110
         ]
87 111
       )
88 112
 

+ 75
- 0
lib/pleroma/user.ex Ver arquivo

@@ -57,6 +57,7 @@ defmodule Pleroma.User do
57 57
     field(:search_type, :integer, virtual: true)
58 58
     field(:tags, {:array, :string}, default: [])
59 59
     field(:last_refreshed_at, :naive_datetime_usec)
60
+    field(:last_digest_emailed_at, :naive_datetime)
60 61
     has_many(:notifications, Notification)
61 62
     has_many(:registrations, Registration)
62 63
     embeds_one(:info, User.Info)
@@ -1419,6 +1420,80 @@ defmodule Pleroma.User do
1419 1420
     target.ap_id not in user.info.muted_reblogs
1420 1421
   end
1421 1422
 
1423
+  @doc """
1424
+  The function returns a query to get users with no activity for given interval of days.
1425
+  Inactive users are those who didn't read any notification, or had any activity where
1426
+  the user is the activity's actor, during `inactivity_threshold` days.
1427
+  Deactivated users will not appear in this list.
1428
+
1429
+  ## Examples
1430
+
1431
+      iex> Pleroma.User.list_inactive_users()
1432
+      %Ecto.Query{}
1433
+  """
1434
+  @spec list_inactive_users_query(integer()) :: Ecto.Query.t()
1435
+  def list_inactive_users_query(inactivity_threshold \\ 7) do
1436
+    negative_inactivity_threshold = -inactivity_threshold
1437
+    now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1438
+    # Subqueries are not supported in `where` clauses, join gets too complicated.
1439
+    has_read_notifications =
1440
+      from(n in Pleroma.Notification,
1441
+        where: n.seen == true,
1442
+        group_by: n.id,
1443
+        having: max(n.updated_at) > datetime_add(^now, ^negative_inactivity_threshold, "day"),
1444
+        select: n.user_id
1445
+      )
1446
+      |> Pleroma.Repo.all()
1447
+
1448
+    from(u in Pleroma.User,
1449
+      left_join: a in Pleroma.Activity,
1450
+      on: u.ap_id == a.actor,
1451
+      where: not is_nil(u.nickname),
1452
+      where: fragment("not (?->'deactivated' @> 'true')", u.info),
1453
+      where: u.id not in ^has_read_notifications,
1454
+      group_by: u.id,
1455
+      having:
1456
+        max(a.inserted_at) < datetime_add(^now, ^negative_inactivity_threshold, "day") or
1457
+          is_nil(max(a.inserted_at))
1458
+    )
1459
+  end
1460
+
1461
+  @doc """
1462
+  Enable or disable email notifications for user
1463
+
1464
+  ## Examples
1465
+
1466
+      iex> Pleroma.User.switch_email_notifications(Pleroma.User{info: %{email_notifications: %{"digest" => false}}}, "digest", true)
1467
+      Pleroma.User{info: %{email_notifications: %{"digest" => true}}}
1468
+
1469
+      iex> Pleroma.User.switch_email_notifications(Pleroma.User{info: %{email_notifications: %{"digest" => true}}}, "digest", false)
1470
+      Pleroma.User{info: %{email_notifications: %{"digest" => false}}}
1471
+  """
1472
+  @spec switch_email_notifications(t(), String.t(), boolean()) ::
1473
+          {:ok, t()} | {:error, Ecto.Changeset.t()}
1474
+  def switch_email_notifications(user, type, status) do
1475
+    info = Pleroma.User.Info.update_email_notifications(user.info, %{type => status})
1476
+
1477
+    change(user)
1478
+    |> put_embed(:info, info)
1479
+    |> update_and_set_cache()
1480
+  end
1481
+
1482
+  @doc """
1483
+  Set `last_digest_emailed_at` value for the user to current time
1484
+  """
1485
+  @spec touch_last_digest_emailed_at(t()) :: t()
1486
+  def touch_last_digest_emailed_at(user) do
1487
+    now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
1488
+
1489
+    {:ok, updated_user} =
1490
+      user
1491
+      |> change(%{last_digest_emailed_at: now})
1492
+      |> update_and_set_cache()
1493
+
1494
+    updated_user
1495
+  end
1496
+
1422 1497
   @spec toggle_confirmation(User.t()) :: {:ok, User.t()} | {:error, Changeset.t()}
1423 1498
   def toggle_confirmation(%User{} = user) do
1424 1499
     need_confirmation? = !user.info.confirmation_pending

+ 25
- 0
lib/pleroma/user/info.ex Ver arquivo

@@ -45,6 +45,7 @@ defmodule Pleroma.User.Info do
45 45
     field(:hide_follows, :boolean, default: false)
46 46
     field(:hide_favorites, :boolean, default: true)
47 47
     field(:pinned_activities, {:array, :string}, default: [])
48
+    field(:email_notifications, :map, default: %{"digest" => false})
48 49
     field(:mascot, :map, default: nil)
49 50
     field(:emoji, {:array, :map}, default: [])
50 51
     field(:pleroma_settings_store, :map, default: %{})
@@ -95,6 +96,30 @@ defmodule Pleroma.User.Info do
95 96
     |> validate_required([:notification_settings])
96 97
   end
97 98
 
99
+  @doc """
100
+  Update email notifications in the given User.Info struct.
101
+
102
+  Examples:
103
+
104
+      iex> update_email_notifications(%Pleroma.User.Info{email_notifications: %{"digest" => false}}, %{"digest" => true})
105
+      %Pleroma.User.Info{email_notifications: %{"digest" => true}}
106
+
107
+  """
108
+  @spec update_email_notifications(t(), map()) :: Ecto.Changeset.t()
109
+  def update_email_notifications(info, settings) do
110
+    email_notifications =
111
+      info.email_notifications
112
+      |> Map.merge(settings)
113
+      |> Map.take(["digest"])
114
+
115
+    params = %{email_notifications: email_notifications}
116
+    fields = [:email_notifications]
117
+
118
+    info
119
+    |> cast(params, fields)
120
+    |> validate_required(fields)
121
+  end
122
+
98 123
   def add_to_note_count(info, number) do
99 124
     set_note_count(info, info.note_count + number)
100 125
   end

+ 20
- 0
lib/pleroma/web/mailer/subscription_controller.ex Ver arquivo

@@ -0,0 +1,20 @@
1
+defmodule Pleroma.Web.Mailer.SubscriptionController do
2
+  use Pleroma.Web, :controller
3
+
4
+  alias Pleroma.JWT
5
+  alias Pleroma.Repo
6
+  alias Pleroma.User
7
+
8
+  def unsubscribe(conn, %{"token" => encoded_token}) do
9
+    with {:ok, token} <- Base.decode64(encoded_token),
10
+         {:ok, claims} <- JWT.verify_and_validate(token),
11
+         %{"act" => %{"unsubscribe" => type}, "sub" => uid} <- claims,
12
+         %User{} = user <- Repo.get(User, uid),
13
+         {:ok, _user} <- User.switch_email_notifications(user, type, false) do
14
+      render(conn, "unsubscribe_success.html", email: user.email)
15
+    else
16
+      _err ->
17
+        render(conn, "unsubscribe_failure.html")
18
+    end
19
+  end
20
+end

+ 2
- 0
lib/pleroma/web/router.ex Ver arquivo

@@ -608,6 +608,8 @@ defmodule Pleroma.Web.Router do
608 608
     post("/push/hub/:nickname", Websub.WebsubController, :websub_subscription_request)
609 609
     get("/push/subscriptions/:id", Websub.WebsubController, :websub_subscription_confirmation)
610 610
     post("/push/subscriptions/:id", Websub.WebsubController, :websub_incoming)
611
+
612
+    get("/mailer/unsubscribe/:token", Mailer.SubscriptionController, :unsubscribe)
611 613
   end
612 614
 
613 615
   pipeline :activitypub do

+ 20
- 0
lib/pleroma/web/templates/email/digest.html.eex Ver arquivo

@@ -0,0 +1,20 @@
1
+<h1>Hey <%= @user.nickname %>, here is what you've missed!</h1>
2
+
3
+<h2>New Mentions:</h2>
4
+<ul>
5
+<%= for %{data: mention, object: object, from: from} <- @mentions do %>
6
+  <li><%= link from.nickname, to: mention.activity.actor %>: <%= raw object.data["content"] %></li>
7
+<% end %>
8
+</ul>
9
+
10
+<%= if @followers != [] do %>
11
+<h2><%= length(@followers) %> New Followers:</h2>
12
+<ul>
13
+<%= for %{data: follow, from: from} <- @followers do %>
14
+  <li><%= link from.nickname, to: follow.activity.actor %></li>
15
+<% end %>
16
+</ul>
17
+<% end %>
18
+
19
+<p>You have received this email because you have signed up to receive digest emails from <b><%= @instance %></b> Pleroma instance.</p>
20
+<p>The email address you are subscribed as is <%= @user.email %>. To unsubscribe, please go <%= link "here", to: @unsubscribe_link %>.</p>

+ 10
- 0
lib/pleroma/web/templates/layout/email.html.eex Ver arquivo

@@ -0,0 +1,10 @@
1
+<!DOCTYPE html>
2
+<html lang="en">
3
+  <head>
4
+    <meta charset="utf-8">
5
+    <title><%= @email.subject %></title>
6
+  </head>
7
+  <body>
8
+    <%= render @view_module, @view_template, assigns %>
9
+  </body>
10
+</html>

+ 1
- 0
lib/pleroma/web/templates/mailer/subscription/unsubscribe_failure.html.eex Ver arquivo

@@ -0,0 +1 @@
1
+<h1>UNSUBSCRIBE FAILURE</h1>

+ 1
- 0
lib/pleroma/web/templates/mailer/subscription/unsubscribe_success.html.eex Ver arquivo

@@ -0,0 +1 @@
1
+<h1>UNSUBSCRIBE SUCCESSFUL</h1>

+ 5
- 0
lib/pleroma/web/views/email_view.ex Ver arquivo

@@ -0,0 +1,5 @@
1
+defmodule Pleroma.Web.EmailView do
2
+  use Pleroma.Web, :view
3
+  import Phoenix.HTML
4
+  import Phoenix.HTML.Link
5
+end

+ 3
- 0
lib/pleroma/web/views/mailer/subscription_view.ex Ver arquivo

@@ -0,0 +1,3 @@
1
+defmodule Pleroma.Web.Mailer.SubscriptionView do
2
+  use Pleroma.Web, :view
3
+end

+ 3
- 1
mix.exs Ver arquivo

@@ -127,6 +127,7 @@ defmodule Pleroma.Mixfile do
127 127
       {:ex_doc, "~> 0.20.2", only: :dev, runtime: false},
128 128
       {:web_push_encryption, "~> 0.2.1"},
129 129
       {:swoosh, "~> 0.23.2"},
130
+      {:phoenix_swoosh, "~> 0.2"},
130 131
       {:gen_smtp, "~> 0.13"},
131 132
       {:websocket_client, git: "https://github.com/jeremyong/websocket_client.git", only: :test},
132 133
       {:floki, "~> 0.20.0"},
@@ -139,7 +140,7 @@ defmodule Pleroma.Mixfile do
139 140
       {:http_signatures,
140 141
        git: "https://git.pleroma.social/pleroma/http_signatures.git",
141 142
        ref: "293d77bb6f4a67ac8bde1428735c3b42f22cbb30"},
142
-      {:pleroma_job_queue, "~> 0.2.0"},
143
+      {:pleroma_job_queue, "~> 0.3"},
143 144
       {:telemetry, "~> 0.3"},
144 145
       {:prometheus_ex, "~> 3.0"},
145 146
       {:prometheus_plugs, "~> 1.1"},
@@ -147,6 +148,7 @@ defmodule Pleroma.Mixfile do
147 148
       {:prometheus_ecto, "~> 1.4"},
148 149
       {:recon, github: "ferd/recon", tag: "2.4.0"},
149 150
       {:quack, "~> 0.1.1"},
151
+      {:joken, "~> 2.0"},
150 152
       {:benchee, "~> 1.0"},
151 153
       {:esshd, "~> 0.1.0", runtime: Application.get_env(:esshd, :enabled, false)},
152 154
       {:ex_rated, "~> 1.3"},

+ 9
- 8
mix.lock Ver arquivo

@@ -5,16 +5,17 @@
5 5
   "bbcode": {:hex, :bbcode, "0.1.1", "0023e2c7814119b2e620b7add67182e3f6019f92bfec9a22da7e99821aceba70", [:mix], [{:nimble_parsec, "~> 0.5", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"},
6 6
   "benchee": {:hex, :benchee, "1.0.1", "66b211f9bfd84bd97e6d1beaddf8fc2312aaabe192f776e8931cb0c16f53a521", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}], "hexpm"},
7 7
   "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm"},
8
-  "cachex": {:hex, :cachex, "3.0.2", "1351caa4e26e29f7d7ec1d29b53d6013f0447630bbf382b4fb5d5bad0209f203", [:mix], [{:eternal, "~> 1.2", [hex: :eternal, repo: "hexpm", optional: false]}, {:unsafe, "~> 1.0", [hex: :unsafe, repo: "hexpm", optional: false]}], "hexpm"},
8
+  "cachex": {:hex, :cachex, "3.0.3", "4e2d3e05814a5738f5ff3903151d5c25636d72a3527251b753f501ad9c657967", [:mix], [{:eternal, "~> 1.2", [hex: :eternal, repo: "hexpm", optional: false]}, {:unsafe, "~> 1.0", [hex: :unsafe, repo: "hexpm", optional: false]}], "hexpm"},
9 9
   "calendar": {:hex, :calendar, "0.17.6", "ec291cb2e4ba499c2e8c0ef5f4ace974e2f9d02ae9e807e711a9b0c7850b9aee", [:mix], [{:tzdata, "~> 0.5.20 or ~> 0.1.201603 or ~> 1.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm"},
10 10
   "certifi": {:hex, :certifi, "2.5.1", "867ce347f7c7d78563450a18a6a28a8090331e77fa02380b4a21962a65d36ee5", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm"},
11 11
   "combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm"},
12
-  "comeonin": {:hex, :comeonin, "4.1.1", "c7304fc29b45b897b34142a91122bc72757bc0c295e9e824999d5179ffc08416", [:mix], [{:argon2_elixir, "~> 1.2", [hex: :argon2_elixir, repo: "hexpm", optional: true]}, {:bcrypt_elixir, "~> 0.12.1 or ~> 1.0", [hex: :bcrypt_elixir, repo: "hexpm", optional: true]}, {:pbkdf2_elixir, "~> 0.12", [hex: :pbkdf2_elixir, repo: "hexpm", optional: true]}], "hexpm"},
12
+  "comeonin": {:hex, :comeonin, "4.1.2", "3eb5620fd8e35508991664b4c2b04dd41e52f1620b36957be837c1d7784b7592", [:mix], [{:argon2_elixir, "~> 1.2", [hex: :argon2_elixir, repo: "hexpm", optional: true]}, {:bcrypt_elixir, "~> 0.12.1 or ~> 1.0", [hex: :bcrypt_elixir, repo: "hexpm", optional: true]}, {:pbkdf2_elixir, "~> 0.12", [hex: :pbkdf2_elixir, repo: "hexpm", optional: true]}], "hexpm"},
13 13
   "connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm"},
14 14
   "cors_plug": {:hex, :cors_plug, "1.5.2", "72df63c87e4f94112f458ce9d25800900cc88608c1078f0e4faddf20933eda6e", [:mix], [{:plug, "~> 1.3 or ~> 1.4 or ~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
15 15
   "cowboy": {:hex, :cowboy, "2.6.3", "99aa50e94e685557cad82e704457336a453d4abcb77839ad22dbe71f311fcc06", [:rebar3], [{:cowlib, "~> 2.7.3", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm"},
16 16
   "cowlib": {:hex, :cowlib, "2.7.3", "a7ffcd0917e6d50b4d5fb28e9e2085a0ceb3c97dea310505f7460ff5ed764ce9", [:rebar3], [], "hexpm"},
17 17
   "credo": {:hex, :credo, "0.9.3", "76fa3e9e497ab282e0cf64b98a624aa11da702854c52c82db1bf24e54ab7c97a", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:poison, ">= 0.0.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"},
18
+  "crontab": {:hex, :crontab, "1.1.7", "b9219f0bdc8678b94143655a8f229716c5810c0636a4489f98c0956137e53985", [:mix], [{:ecto, "~> 1.0 or ~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm"},
18 19
   "crypt": {:git, "https://github.com/msantos/crypt", "1f2b58927ab57e72910191a7ebaeff984382a1d3", [ref: "1f2b58927ab57e72910191a7ebaeff984382a1d3"]},
19 20
   "db_connection": {:hex, :db_connection, "2.0.6", "bde2f85d047969c5b5800cb8f4b3ed6316c8cb11487afedac4aa5f93fd39abfa", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm"},
20 21
   "decimal": {:hex, :decimal, "1.8.0", "ca462e0d885f09a1c5a342dbd7c1dcf27ea63548c65a65e67334f4b61803822e", [:mix], [], "hexpm"},
@@ -43,14 +44,15 @@
43 44
   "httpoison": {:hex, :httpoison, "1.2.0", "2702ed3da5fd7a8130fc34b11965c8cfa21ade2f232c00b42d96d4967c39a3a3", [:mix], [{:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
44 45
   "idna": {:hex, :idna, "6.0.0", "689c46cbcdf3524c44d5f3dde8001f364cd7608a99556d8fbd8239a5798d4c10", [:rebar3], [{:unicode_util_compat, "0.4.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"},
45 46
   "jason": {:hex, :jason, "1.1.2", "b03dedea67a99223a2eaf9f1264ce37154564de899fd3d8b9a21b1a6fd64afe7", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm"},
46
-  "jose": {:hex, :jose, "1.8.4", "7946d1e5c03a76ac9ef42a6e6a20001d35987afd68c2107bcd8f01a84e75aa73", [:mix, :rebar3], [{:base64url, "~> 0.0.1", [hex: :base64url, repo: "hexpm", optional: false]}], "hexpm"},
47
+  "joken": {:hex, :joken, "2.0.1", "ec9ab31bf660f343380da033b3316855197c8d4c6ef597fa3fcb451b326beb14", [:mix], [{:jose, "~> 1.9", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm"},
48
+  "jose": {:hex, :jose, "1.9.0", "4167c5f6d06ffaebffd15cdb8da61a108445ef5e85ab8f5a7ad926fdf3ada154", [:mix, :rebar3], [{:base64url, "~> 0.0.1", [hex: :base64url, repo: "hexpm", optional: false]}], "hexpm"},
47 49
   "makeup": {:hex, :makeup, "0.8.0", "9cf32aea71c7fe0a4b2e9246c2c4978f9070257e5c9ce6d4a28ec450a839b55f", [:mix], [{:nimble_parsec, "~> 0.5.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"},
48 50
   "makeup_elixir": {:hex, :makeup_elixir, "0.13.0", "be7a477997dcac2e48a9d695ec730b2d22418292675c75aa2d34ba0909dcdeda", [:mix], [{:makeup, "~> 0.8", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm"},
49 51
   "meck": {:hex, :meck, "0.8.13", "ffedb39f99b0b99703b8601c6f17c7f76313ee12de6b646e671e3188401f7866", [:rebar3], [], "hexpm"},
50 52
   "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm"},
51 53
   "mime": {:hex, :mime, "1.3.1", "30ce04ab3175b6ad0bdce0035cba77bba68b813d523d1aac73d9781b4d193cf8", [:mix], [], "hexpm"},
52 54
   "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm"},
53
-  "mochiweb": {:hex, :mochiweb, "2.15.0", "e1daac474df07651e5d17cc1e642c4069c7850dc4508d3db7263a0651330aacc", [:rebar3], [], "hexpm"},
55
+  "mochiweb": {:hex, :mochiweb, "2.18.0", "eb55f1db3e6e960fac4e6db4e2db9ec3602cc9f30b86cd1481d56545c3145d2e", [:rebar3], [], "hexpm"},
54 56
   "mock": {:hex, :mock, "0.3.3", "42a433794b1291a9cf1525c6d26b38e039e0d3a360732b5e467bfc77ef26c914", [:mix], [{:meck, "~> 0.8.13", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm"},
55 57
   "mogrify": {:hex, :mogrify, "0.6.1", "de1b527514f2d95a7bbe9642eb556061afb337e220cf97adbf3a4e6438ed70af", [:mix], [], "hexpm"},
56 58
   "mox": {:hex, :mox, "0.5.1", "f86bb36026aac1e6f924a4b6d024b05e9adbed5c63e8daa069bd66fb3292165b", [:mix], [], "hexpm"},
@@ -61,20 +63,19 @@
61 63
   "phoenix_ecto": {:hex, :phoenix_ecto, "4.0.0", "c43117a136e7399ea04ecaac73f8f23ee0ffe3e07acfcb8062fe5f4c9f0f6531", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.9", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
62 64
   "phoenix_html": {:hex, :phoenix_html, "2.13.1", "fa8f034b5328e2dfa0e4131b5569379003f34bc1fafdaa84985b0b9d2f12e68b", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
63 65
   "phoenix_pubsub": {:hex, :phoenix_pubsub, "1.1.2", "496c303bdf1b2e98a9d26e89af5bba3ab487ba3a3735f74bf1f4064d2a845a3e", [:mix], [], "hexpm"},
64
-  "pleroma_job_queue": {:hex, :pleroma_job_queue, "0.2.0", "879e660aa1cebe8dc6f0aaaa6aa48b4875e89cd961d4a585fd128e0773b31a18", [:mix], [], "hexpm"},
66
+  "phoenix_swoosh": {:hex, :phoenix_swoosh, "0.2.0", "a7e0b32077cd6d2323ae15198839b05d9caddfa20663fd85787479e81f89520e", [:mix], [{:phoenix, "~> 1.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.2", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:swoosh, "~> 0.1", [hex: :swoosh, repo: "hexpm", optional: false]}], "hexpm"},
67
+  "pleroma_job_queue": {:hex, :pleroma_job_queue, "0.3.0", "b84538d621f0c3d6fcc1cff9d5648d3faaf873b8b21b94e6503428a07a48ec47", [:mix], [{:crontab, "~> 1.1", [hex: :crontab, repo: "hexpm", optional: false]}], "hexpm"},
65 68
   "plug": {:hex, :plug, "1.8.2", "0bcce1daa420f189a6491f3940cc77ea7fb1919761175c9c3b59800d897440fc", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm"},
66 69
   "plug_cowboy": {:hex, :plug_cowboy, "2.1.0", "b75768153c3a8a9e8039d4b25bb9b14efbc58e9c4a6e6a270abff1cd30cbe320", [:mix], [{:cowboy, "~> 2.5", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
67 70
   "plug_crypto": {:hex, :plug_crypto, "1.0.0", "18e49317d3fa343f24620ed22795ec29d4a5e602d52d1513ccea0b07d8ea7d4d", [:mix], [], "hexpm"},
68 71
   "plug_static_index_html": {:hex, :plug_static_index_html, "1.0.0", "840123d4d3975585133485ea86af73cb2600afd7f2a976f9f5fd8b3808e636a0", [:mix], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
69 72
   "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"},
70
-  "poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm"},
71 73
   "postgrex": {:hex, :postgrex, "0.14.3", "5754dee2fdf6e9e508cbf49ab138df964278700b764177e8f3871e658b345a1e", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.0", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"},
72 74
   "prometheus": {:hex, :prometheus, "4.4.1", "1e96073b3ed7788053768fea779cbc896ddc3bdd9ba60687f2ad50b252ac87d6", [:mix, :rebar3], [], "hexpm"},
73 75
   "prometheus_ecto": {:hex, :prometheus_ecto, "1.4.1", "6c768ea9654de871e5b32fab2eac348467b3021604ebebbcbd8bcbe806a65ed5", [:mix], [{:ecto, "~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.1 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}], "hexpm"},
74 76
   "prometheus_ex": {:hex, :prometheus_ex, "3.0.5", "fa58cfd983487fc5ead331e9a3e0aa622c67232b3ec71710ced122c4c453a02f", [:mix], [{:prometheus, "~> 4.0", [hex: :prometheus, repo: "hexpm", optional: false]}], "hexpm"},
75 77
   "prometheus_phoenix": {:hex, :prometheus_phoenix, "1.3.0", "c4b527e0b3a9ef1af26bdcfbfad3998f37795b9185d475ca610fe4388fdd3bb5", [:mix], [{:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.3 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}], "hexpm"},
76 78
   "prometheus_plugs": {:hex, :prometheus_plugs, "1.1.5", "25933d48f8af3a5941dd7b621c889749894d8a1082a6ff7c67cc99dec26377c5", [:mix], [{:accept, "~> 0.1", [hex: :accept, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.1 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}, {:prometheus_process_collector, "~> 1.1", [hex: :prometheus_process_collector, repo: "hexpm", optional: true]}], "hexpm"},
77
-  "prometheus_process_collector": {:hex, :prometheus_process_collector, "1.4.0", "6dbd39e3165b9ef1c94a7a820e9ffe08479f949dcdd431ed4aaea7b250eebfde", [:rebar3], [{:prometheus, "~> 4.0", [hex: :prometheus, repo: "hexpm", optional: false]}], "hexpm"},
78 79
   "quack": {:hex, :quack, "0.1.1", "cca7b4da1a233757fdb44b3334fce80c94785b3ad5a602053b7a002b5a8967bf", [:mix], [{:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: false]}, {:tesla, "~> 1.2.0", [hex: :tesla, repo: "hexpm", optional: false]}], "hexpm"},
79 80
   "ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm"},
80 81
   "recon": {:git, "https://github.com/ferd/recon.git", "75d70c7c08926d2f24f1ee6de14ee50fe8a52763", [tag: "2.4.0"]},
@@ -88,7 +89,7 @@
88 89
   "tzdata": {:hex, :tzdata, "1.0.1", "f6027a331af7d837471248e62733c6ebee86a72e57c613aa071ebb1f750fc71a", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
89 90
   "ueberauth": {:hex, :ueberauth, "0.6.1", "9e90d3337dddf38b1ca2753aca9b1e53d8a52b890191cdc55240247c89230412", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
90 91
   "unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm"},
91
-  "unsafe": {:hex, :unsafe, "1.0.0", "7c21742cd05380c7875546b023481d3a26f52df8e5dfedcb9f958f322baae305", [:mix], [], "hexpm"},
92
+  "unsafe": {:hex, :unsafe, "1.0.1", "a27e1874f72ee49312e0a9ec2e0b27924214a05e3ddac90e91727bc76f8613d8", [:mix], [], "hexpm"},
92 93
   "web_push_encryption": {:hex, :web_push_encryption, "0.2.1", "d42cecf73420d9dc0053ba3299cc8c8d6ff2be2487d67ca2a57265868e4d9a98", [:mix], [{:httpoison, "~> 1.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:jose, "~> 1.8", [hex: :jose, repo: "hexpm", optional: false]}, {:poison, "~> 3.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"},
93 94
   "websocket_client": {:git, "https://github.com/jeremyong/websocket_client.git", "9a6f65d05ebf2725d62fb19262b21f1805a59fbf", []},
94 95
 }

+ 20
- 0
priv/repo/migrations/20190412052952_add_user_info_fields.exs Ver arquivo

@@ -0,0 +1,20 @@
1
+defmodule Pleroma.Repo.Migrations.AddEmailNotificationsToUserInfo do
2
+  use Ecto.Migration
3
+
4
+  def up do
5
+    execute("
6
+    UPDATE users
7
+    SET info = info || '{
8
+      \"email_notifications\": {
9
+        \"digest\": false
10
+      }
11
+    }'")
12
+  end
13
+
14
+  def down do
15
+    execute("
16
+      UPDATE users
17
+      SET info = info - 'email_notifications'
18
+    ")
19
+  end
20
+end

+ 9
- 0
priv/repo/migrations/20190413085040_add_signin_and_last_digest_dates_to_user.exs Ver arquivo

@@ -0,0 +1,9 @@
1
+defmodule Pleroma.Repo.Migrations.AddSigninAndLastDigestDatesToUser do
2
+  use Ecto.Migration
3
+
4
+  def change do
5
+    alter table(:users) do
6
+      add(:last_digest_emailed_at, :naive_datetime, default: fragment("now()"))
7
+    end
8
+  end
9
+end

+ 2
- 0
priv/templates/sample_config.eex Ver arquivo

@@ -68,3 +68,5 @@ config :pleroma, Pleroma.Uploaders.Local, uploads: "<%= uploads_dir %>"
68 68
 # For using third-party S3 clones like wasabi, also do:
69 69
 # config :ex_aws, :s3,
70 70
 #   host: "s3.wasabisys.com"
71
+
72
+config :joken, default_signer: "<%= jwt_secret %>"

+ 51
- 0
test/mix/tasks/pleroma.digest_test.exs Ver arquivo

@@ -0,0 +1,51 @@
1
+defmodule Mix.Tasks.Pleroma.DigestTest do
2
+  use Pleroma.DataCase
3
+
4
+  import Pleroma.Factory
5
+  import Swoosh.TestAssertions
6
+
7
+  alias Pleroma.Web.CommonAPI
8
+
9
+  setup_all do
10
+    Mix.shell(Mix.Shell.Process)
11
+
12
+    on_exit(fn ->
13
+      Mix.shell(Mix.Shell.IO)
14
+    end)
15
+
16
+    :ok
17
+  end
18
+
19
+  describe "pleroma.digest test" do
20
+    test "Sends digest to the given user" do
21
+      user1 = insert(:user)
22
+      user2 = insert(:user)
23
+
24
+      Enum.each(0..10, fn i ->
25
+        {:ok, _activity} =
26
+          CommonAPI.post(user1, %{
27
+            "status" => "hey ##{i} @#{user2.nickname}!"
28
+          })
29
+      end)
30
+
31
+      yesterday =
32
+        NaiveDateTime.add(
33
+          NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second),
34
+          -60 * 60 * 24,
35
+          :second
36
+        )
37
+
38
+      {:ok, yesterday_date} = Timex.format(yesterday, "%F", :strftime)
39
+
40
+      :ok = Mix.Tasks.Pleroma.Digest.run(["test", user2.nickname, yesterday_date])
41
+
42
+      assert_receive {:mix_shell, :info, [message]}
43
+      assert message =~ "Digest email have been sent"
44
+
45
+      assert_email_sent(
46
+        to: {user2.name, user2.email},
47
+        html_body: ~r/new mentions:/i
48
+      )
49
+    end
50
+  end
51
+end

+ 48
- 1
test/notification_test.exs Ver arquivo

@@ -4,13 +4,15 @@
4 4
 
5 5
 defmodule Pleroma.NotificationTest do
6 6
   use Pleroma.DataCase
7
+
8
+  import Pleroma.Factory
9
+
7 10
   alias Pleroma.Notification
8 11
   alias Pleroma.User
9 12
   alias Pleroma.Web.ActivityPub.Transmogrifier
10 13
   alias Pleroma.Web.CommonAPI
11 14
   alias Pleroma.Web.Streamer
12 15
   alias Pleroma.Web.TwitterAPI.TwitterAPI
13
-  import Pleroma.Factory
14 16
 
15 17
   describe "create_notifications" do
16 18
     test "notifies someone when they are directly addressed" do
@@ -352,6 +354,51 @@ defmodule Pleroma.NotificationTest do
352 354
     end
353 355
   end
354 356
 
357
+  describe "for_user_since/2" do
358
+    defp days_ago(days) do
359
+      NaiveDateTime.add(
360
+        NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second),
361
+        -days * 60 * 60 * 24,
362
+        :second
363
+      )
364
+    end
365
+
366
+    test "Returns recent notifications" do
367
+      user1 = insert(:user)
368
+      user2 = insert(:user)
369
+
370
+      Enum.each(0..10, fn i ->
371
+        {:ok, _activity} =
372
+          CommonAPI.post(user1, %{
373
+            "status" => "hey ##{i} @#{user2.nickname}!"
374
+          })
375
+      end)
376
+
377
+      {old, new} = Enum.split(Notification.for_user(user2), 5)
378
+
379
+      Enum.each(old, fn notification ->
380
+        notification
381
+        |> cast(%{updated_at: days_ago(10)}, [:updated_at])
382
+        |> Pleroma.Repo.update!()
383
+      end)
384
+
385
+      recent_notifications_ids =
386
+        user2
387
+        |> Notification.for_user_since(
388
+          NaiveDateTime.add(NaiveDateTime.utc_now(), -5 * 86_400, :second)
389
+        )
390
+        |> Enum.map(& &1.id)
391
+
392
+      Enum.each(old, fn %{id: id} ->
393
+        refute id in recent_notifications_ids
394
+      end)
395
+
396
+      Enum.each(new, fn %{id: id} ->
397
+        assert id in recent_notifications_ids
398
+      end)
399
+    end
400
+  end
401
+
355 402
   describe "notification target determination" do
356 403
     test "it sends notifications to addressed users in new messages" do
357 404
       user = insert(:user)

+ 2
- 1
test/support/builders/user_builder.ex Ver arquivo

@@ -9,7 +9,8 @@ defmodule Pleroma.Builders.UserBuilder do
9 9
       nickname: "testname",
10 10
       password_hash: Comeonin.Pbkdf2.hashpwsalt("test"),
11 11
       bio: "A tester.",
12
-      ap_id: "some id"
12
+      ap_id: "some id",
13
+      last_digest_emailed_at: NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
13 14
     }
14 15
 
15 16
     Map.merge(user, data)

+ 2
- 1
test/support/factory.ex Ver arquivo

@@ -31,7 +31,8 @@ defmodule Pleroma.Factory do
31 31
       nickname: sequence(:nickname, &"nick#{&1}"),
32 32
       password_hash: Comeonin.Pbkdf2.hashpwsalt("test"),
33 33
       bio: sequence(:bio, &"Tester Number #{&1}"),
34
-      info: %{}
34
+      info: %{},
35
+      last_digest_emailed_at: NaiveDateTime.utc_now()
35 36
     }
36 37
 
37 38
     %{

+ 24
- 0
test/user_info_test.exs Ver arquivo

@@ -0,0 +1,24 @@
1
+defmodule Pleroma.UserInfoTest do
2
+  alias Pleroma.Repo
3
+  alias Pleroma.User.Info
4
+
5
+  use Pleroma.DataCase
6
+
7
+  import Pleroma.Factory
8
+
9
+  describe "update_email_notifications/2" do
10
+    setup do
11
+      user = insert(:user, %{info: %{email_notifications: %{"digest" => true}}})
12
+
13
+      {:ok, user: user}
14
+    end
15
+
16
+    test "Notifications are updated", %{user: user} do
17
+      true = user.info.email_notifications["digest"]
18
+      changeset = Info.update_email_notifications(user.info, %{"digest" => false})
19
+      assert changeset.valid?
20
+      {:ok, result} = Ecto.Changeset.apply_action(changeset, :insert)
21
+      assert result.email_notifications["digest"] == false
22
+    end
23
+  end
24
+end

+ 8
- 1
test/user_search_test.exs Ver arquivo

@@ -193,7 +193,14 @@ defmodule Pleroma.UserSearchTest do
193 193
       user = User.get_cached_by_ap_id("http://mastodon.example.org/users/admin")
194 194
 
195 195
       assert length(results) == 1
196
-      assert user == result |> Map.put(:search_rank, nil) |> Map.put(:search_type, nil)
196
+
197
+      expected =
198
+        result
199
+        |> Map.put(:search_rank, nil)
200
+        |> Map.put(:search_type, nil)
201
+        |> Map.put(:last_digest_emailed_at, nil)
202
+
203
+      assert user == expected
197 204
     end
198 205
 
199 206
     test "excludes a blocked users from search result" do

+ 103
- 0
test/user_test.exs Ver arquivo

@@ -1237,6 +1237,109 @@ defmodule Pleroma.UserTest do
1237 1237
     assert Map.get(user_show, "followers_count") == 2
1238 1238
   end
1239 1239
 
1240
+  describe "list_inactive_users_query/1" do
1241
+    defp days_ago(days) do
1242
+      NaiveDateTime.add(
1243
+        NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second),
1244
+        -days * 60 * 60 * 24,
1245
+        :second
1246
+      )
1247
+    end
1248
+
1249
+    test "Users are inactive by default" do
1250
+      total = 10
1251
+
1252
+      users =
1253
+        Enum.map(1..total, fn _ ->
1254
+          insert(:user, last_digest_emailed_at: days_ago(20), info: %{deactivated: false})
1255
+        end)
1256
+
1257
+      inactive_users_ids =
1258
+        Pleroma.User.list_inactive_users_query()
1259
+        |> Pleroma.Repo.all()
1260
+        |> Enum.map(& &1.id)
1261
+
1262
+      Enum.each(users, fn user ->
1263
+        assert user.id in inactive_users_ids
1264
+      end)
1265
+    end
1266
+
1267
+    test "Only includes users who has no recent activity" do
1268
+      total = 10
1269
+
1270
+      users =
1271
+        Enum.map(1..total, fn _ ->
1272
+          insert(:user, last_digest_emailed_at: days_ago(20), info: %{deactivated: false})
1273
+        end)
1274
+
1275
+      {inactive, active} = Enum.split(users, trunc(total / 2))
1276
+
1277
+      Enum.map(active, fn user ->
1278
+        to = Enum.random(users -- [user])
1279
+
1280
+        {:ok, _} =
1281
+          Pleroma.Web.TwitterAPI.TwitterAPI.create_status(user, %{
1282
+            "status" => "hey @#{to.nickname}"
1283
+          })
1284
+      end)
1285
+
1286
+      inactive_users_ids =
1287
+        Pleroma.User.list_inactive_users_query()
1288
+        |> Pleroma.Repo.all()
1289
+        |> Enum.map(& &1.id)
1290
+
1291
+      Enum.each(active, fn user ->
1292
+        refute user.id in inactive_users_ids
1293
+      end)
1294
+
1295
+      Enum.each(inactive, fn user ->
1296
+        assert user.id in inactive_users_ids
1297
+      end)
1298
+    end
1299
+
1300
+    test "Only includes users with no read notifications" do
1301
+      total = 10
1302
+
1303
+      users =
1304
+        Enum.map(1..total, fn _ ->
1305
+          insert(:user, last_digest_emailed_at: days_ago(20), info: %{deactivated: false})
1306
+        end)
1307
+
1308
+      [sender | recipients] = users
1309
+      {inactive, active} = Enum.split(recipients, trunc(total / 2))
1310
+
1311
+      Enum.each(recipients, fn to ->
1312
+        {:ok, _} =
1313
+          Pleroma.Web.TwitterAPI.TwitterAPI.create_status(sender, %{
1314
+            "status" => "hey @#{to.nickname}"
1315
+          })
1316
+
1317
+        {:ok, _} =
1318
+          Pleroma.Web.TwitterAPI.TwitterAPI.create_status(sender, %{
1319
+            "status" => "hey again @#{to.nickname}"
1320
+          })
1321
+      end)
1322
+
1323
+      Enum.each(active, fn user ->
1324
+        [n1, _n2] = Pleroma.Notification.for_user(user)
1325
+        {:ok, _} = Pleroma.Notification.read_one(user, n1.id)
1326
+      end)
1327
+
1328
+      inactive_users_ids =
1329
+        Pleroma.User.list_inactive_users_query()
1330
+        |> Pleroma.Repo.all()
1331
+        |> Enum.map(& &1.id)
1332
+
1333
+      Enum.each(active, fn user ->
1334
+        refute user.id in inactive_users_ids
1335
+      end)
1336
+
1337
+      Enum.each(inactive, fn user ->
1338
+        assert user.id in inactive_users_ids
1339
+      end)
1340
+    end
1341
+  end
1342
+
1240 1343
   describe "toggle_confirmation/1" do
1241 1344
     test "if user is confirmed" do
1242 1345
       user = insert(:user, info: %{confirmation_pending: false})

Carregando…
Cancelar
Salvar