Added limits and media attachments for scheduled activities.
[akkoma] / lib / pleroma / scheduled_activity.ex
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Pleroma.ScheduledActivity do
6 use Ecto.Schema
7
8 alias Pleroma.Config
9 alias Pleroma.Object
10 alias Pleroma.Repo
11 alias Pleroma.ScheduledActivity
12 alias Pleroma.User
13 alias Pleroma.Web.CommonAPI.Utils
14
15 import Ecto.Query
16 import Ecto.Changeset
17
18 @min_offset :timer.minutes(5)
19
20 schema "scheduled_activities" do
21 belongs_to(:user, User, type: Pleroma.FlakeId)
22 field(:scheduled_at, :naive_datetime)
23 field(:params, :map)
24
25 timestamps()
26 end
27
28 def changeset(%ScheduledActivity{} = scheduled_activity, attrs) do
29 scheduled_activity
30 |> cast(attrs, [:scheduled_at, :params])
31 |> validate_required([:scheduled_at, :params])
32 |> validate_scheduled_at()
33 |> with_media_attachments()
34 end
35
36 defp with_media_attachments(
37 %{changes: %{params: %{"media_ids" => media_ids} = params}} = changeset
38 )
39 when is_list(media_ids) do
40 user = User.get_cached_by_id(changeset.data.user_id)
41 media_ids = Object.enforce_user_objects(user, media_ids) |> Enum.map(&to_string(&1))
42 media_attachments = Utils.attachments_from_ids(%{"media_ids" => media_ids})
43
44 params =
45 params
46 |> Map.put("media_attachments", media_attachments)
47 |> Map.put("media_ids", media_ids)
48
49 put_change(changeset, :params, params)
50 end
51
52 defp with_media_attachments(changeset), do: changeset
53
54 def update_changeset(%ScheduledActivity{} = scheduled_activity, attrs) do
55 scheduled_activity
56 |> cast(attrs, [:scheduled_at])
57 |> validate_required([:scheduled_at])
58 |> validate_scheduled_at()
59 end
60
61 def validate_scheduled_at(changeset) do
62 validate_change(changeset, :scheduled_at, fn _, scheduled_at ->
63 cond do
64 not far_enough?(scheduled_at) ->
65 [scheduled_at: "must be at least 5 minutes from now"]
66
67 exceeds_daily_user_limit?(changeset.data.user_id, scheduled_at) ->
68 [scheduled_at: "daily limit exceeded"]
69
70 exceeds_total_user_limit?(changeset.data.user_id) ->
71 [scheduled_at: "total limit exceeded"]
72
73 true ->
74 []
75 end
76 end)
77 end
78
79 def exceeds_daily_user_limit?(user_id, scheduled_at) do
80 ScheduledActivity
81 |> where(user_id: ^user_id)
82 |> where([s], type(s.scheduled_at, :date) == type(^scheduled_at, :date))
83 |> select([u], count(u.id))
84 |> Repo.one()
85 |> Kernel.>=(Config.get([ScheduledActivity, :daily_user_limit]))
86 end
87
88 def exceeds_total_user_limit?(user_id) do
89 ScheduledActivity
90 |> where(user_id: ^user_id)
91 |> select([u], count(u.id))
92 |> Repo.one()
93 |> Kernel.>=(Config.get([ScheduledActivity, :total_user_limit]))
94 end
95
96 def far_enough?(scheduled_at) when is_binary(scheduled_at) do
97 with {:ok, scheduled_at} <- Ecto.Type.cast(:naive_datetime, scheduled_at) do
98 far_enough?(scheduled_at)
99 else
100 _ -> false
101 end
102 end
103
104 def far_enough?(scheduled_at) do
105 now = NaiveDateTime.utc_now()
106 diff = NaiveDateTime.diff(scheduled_at, now, :millisecond)
107 diff > @min_offset
108 end
109
110 def new(%User{} = user, attrs) do
111 %ScheduledActivity{user_id: user.id}
112 |> changeset(attrs)
113 end
114
115 def create(%User{} = user, attrs) do
116 user
117 |> new(attrs)
118 |> Repo.insert()
119 end
120
121 def get(%User{} = user, scheduled_activity_id) do
122 ScheduledActivity
123 |> where(user_id: ^user.id)
124 |> where(id: ^scheduled_activity_id)
125 |> Repo.one()
126 end
127
128 def update(scheduled_activity, attrs) do
129 scheduled_activity
130 |> update_changeset(attrs)
131 |> Repo.update()
132 end
133
134 def delete(scheduled_activity) do
135 scheduled_activity
136 |> Repo.delete()
137 end
138
139 def for_user_query(%User{} = user) do
140 ScheduledActivity
141 |> where(user_id: ^user.id)
142 end
143 end