Fork of Pleroma with site-specific changes and feature branches https://git.pleroma.social/pleroma/pleroma
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

386 line
11KB

  1. # Pleroma: A lightweight social networking server
  2. # Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
  3. # SPDX-License-Identifier: AGPL-3.0-only
  4. defmodule Pleroma.Activity do
  5. use Ecto.Schema
  6. alias Pleroma.Activity
  7. alias Pleroma.Activity.Queries
  8. alias Pleroma.Bookmark
  9. alias Pleroma.Notification
  10. alias Pleroma.Object
  11. alias Pleroma.Repo
  12. alias Pleroma.ReportNote
  13. alias Pleroma.ThreadMute
  14. alias Pleroma.User
  15. alias Pleroma.Web.ActivityPub.ActivityPub
  16. import Ecto.Changeset
  17. import Ecto.Query
  18. @type t :: %__MODULE__{}
  19. @type actor :: String.t()
  20. @primary_key {:id, FlakeId.Ecto.CompatType, autogenerate: true}
  21. @cachex Pleroma.Config.get([:cachex, :provider], Cachex)
  22. schema "activities" do
  23. field(:data, :map)
  24. field(:local, :boolean, default: true)
  25. field(:actor, :string)
  26. field(:recipients, {:array, :string}, default: [])
  27. field(:thread_muted?, :boolean, virtual: true)
  28. # A field that can be used if you need to join some kind of other
  29. # id to order / paginate this field by
  30. field(:pagination_id, :string, virtual: true)
  31. # This is a fake relation,
  32. # do not use outside of with_preloaded_user_actor/with_joined_user_actor
  33. has_one(:user_actor, User, on_delete: :nothing, foreign_key: :id)
  34. # This is a fake relation, do not use outside of with_preloaded_bookmark/get_bookmark
  35. has_one(:bookmark, Bookmark)
  36. # This is a fake relation, do not use outside of with_preloaded_report_notes
  37. has_many(:report_notes, ReportNote)
  38. has_many(:notifications, Notification, on_delete: :delete_all)
  39. # Attention: this is a fake relation, don't try to preload it blindly and expect it to work!
  40. # The foreign key is embedded in a jsonb field.
  41. #
  42. # To use it, you probably want to do an inner join and a preload:
  43. #
  44. # ```
  45. # |> join(:inner, [activity], o in Object,
  46. # on: fragment("(?->>'id') = COALESCE((?)->'object'->> 'id', (?)->>'object')",
  47. # o.data, activity.data, activity.data))
  48. # |> preload([activity, object], [object: object])
  49. # ```
  50. #
  51. # As a convenience, Activity.with_preloaded_object() sets up an inner join and preload for the
  52. # typical case.
  53. has_one(:object, Object, on_delete: :nothing, foreign_key: :id)
  54. timestamps()
  55. end
  56. def with_joined_object(query, join_type \\ :inner) do
  57. join(query, join_type, [activity], o in Object,
  58. on:
  59. fragment(
  60. "(?->>'id') = COALESCE(?->'object'->>'id', ?->>'object')",
  61. o.data,
  62. activity.data,
  63. activity.data
  64. ),
  65. as: :object
  66. )
  67. end
  68. def with_preloaded_object(query, join_type \\ :inner) do
  69. query
  70. |> has_named_binding?(:object)
  71. |> if(do: query, else: with_joined_object(query, join_type))
  72. |> preload([activity, object: object], object: object)
  73. end
  74. # Note: applies to fake activities (ActivityPub.Utils.get_notified_from_object/1 etc.)
  75. def user_actor(%Activity{actor: nil}), do: nil
  76. def user_actor(%Activity{} = activity) do
  77. with %User{} <- activity.user_actor do
  78. activity.user_actor
  79. else
  80. _ -> User.get_cached_by_ap_id(activity.actor)
  81. end
  82. end
  83. def with_joined_user_actor(query, join_type \\ :inner) do
  84. join(query, join_type, [activity], u in User,
  85. on: u.ap_id == activity.actor,
  86. as: :user_actor
  87. )
  88. end
  89. def with_preloaded_user_actor(query, join_type \\ :inner) do
  90. query
  91. |> with_joined_user_actor(join_type)
  92. |> preload([activity, user_actor: user_actor], user_actor: user_actor)
  93. end
  94. def with_preloaded_bookmark(query, %User{} = user) do
  95. from([a] in query,
  96. left_join: b in Bookmark,
  97. on: b.user_id == ^user.id and b.activity_id == a.id,
  98. preload: [bookmark: b]
  99. )
  100. end
  101. def with_preloaded_bookmark(query, _), do: query
  102. def with_preloaded_report_notes(query) do
  103. from([a] in query,
  104. left_join: r in ReportNote,
  105. on: a.id == r.activity_id,
  106. preload: [report_notes: r]
  107. )
  108. end
  109. def with_preloaded_report_notes(query, _), do: query
  110. def with_set_thread_muted_field(query, %User{} = user) do
  111. from([a] in query,
  112. left_join: tm in ThreadMute,
  113. on: tm.user_id == ^user.id and tm.context == fragment("?->>'context'", a.data),
  114. as: :thread_mute,
  115. select: %Activity{a | thread_muted?: not is_nil(tm.id)}
  116. )
  117. end
  118. def with_set_thread_muted_field(query, _), do: query
  119. def get_by_ap_id(ap_id) do
  120. ap_id
  121. |> Queries.by_ap_id()
  122. |> Repo.one()
  123. end
  124. def get_bookmark(%Activity{} = activity, %User{} = user) do
  125. if Ecto.assoc_loaded?(activity.bookmark) do
  126. activity.bookmark
  127. else
  128. Bookmark.get(user.id, activity.id)
  129. end
  130. end
  131. def get_bookmark(_, _), do: nil
  132. def get_report(activity_id) do
  133. opts = %{
  134. type: "Flag",
  135. skip_preload: true,
  136. preload_report_notes: true
  137. }
  138. ActivityPub.fetch_activities_query([], opts)
  139. |> where(id: ^activity_id)
  140. |> Repo.one()
  141. end
  142. def change(struct, params \\ %{}) do
  143. struct
  144. |> cast(params, [:data, :recipients])
  145. |> validate_required([:data])
  146. |> unique_constraint(:ap_id, name: :activities_unique_apid_index)
  147. end
  148. def get_by_ap_id_with_object(ap_id) do
  149. ap_id
  150. |> Queries.by_ap_id()
  151. |> with_preloaded_object(:left)
  152. |> Repo.one()
  153. end
  154. @spec get_by_id(String.t()) :: Activity.t() | nil
  155. def get_by_id(id) do
  156. case FlakeId.flake_id?(id) do
  157. true ->
  158. Activity
  159. |> where([a], a.id == ^id)
  160. |> restrict_deactivated_users()
  161. |> Repo.one()
  162. _ ->
  163. nil
  164. end
  165. end
  166. def get_by_id_with_user_actor(id) do
  167. case FlakeId.flake_id?(id) do
  168. true ->
  169. Activity
  170. |> where([a], a.id == ^id)
  171. |> with_preloaded_user_actor()
  172. |> Repo.one()
  173. _ ->
  174. nil
  175. end
  176. end
  177. def get_by_id_with_object(id) do
  178. Activity
  179. |> where(id: ^id)
  180. |> with_preloaded_object()
  181. |> Repo.one()
  182. end
  183. def all_by_ids_with_object(ids) do
  184. Activity
  185. |> where([a], a.id in ^ids)
  186. |> with_preloaded_object()
  187. |> Repo.all()
  188. end
  189. @doc """
  190. Accepts `ap_id` or list of `ap_id`.
  191. Returns a query.
  192. """
  193. @spec create_by_object_ap_id(String.t() | [String.t()]) :: Ecto.Queryable.t()
  194. def create_by_object_ap_id(ap_id) do
  195. ap_id
  196. |> Queries.by_object_id()
  197. |> Queries.by_type("Create")
  198. end
  199. def get_all_create_by_object_ap_id(ap_id) do
  200. ap_id
  201. |> create_by_object_ap_id()
  202. |> Repo.all()
  203. end
  204. def get_create_by_object_ap_id(ap_id) when is_binary(ap_id) do
  205. create_by_object_ap_id(ap_id)
  206. |> restrict_deactivated_users()
  207. |> Repo.one()
  208. end
  209. def get_create_by_object_ap_id(_), do: nil
  210. @doc """
  211. Accepts `ap_id` or list of `ap_id`.
  212. Returns a query.
  213. """
  214. @spec create_by_object_ap_id_with_object(String.t() | [String.t()]) :: Ecto.Queryable.t()
  215. def create_by_object_ap_id_with_object(ap_id) do
  216. ap_id
  217. |> create_by_object_ap_id()
  218. |> with_preloaded_object()
  219. end
  220. def get_create_by_object_ap_id_with_object(ap_id) when is_binary(ap_id) do
  221. ap_id
  222. |> create_by_object_ap_id_with_object()
  223. |> Repo.one()
  224. end
  225. def get_create_by_object_ap_id_with_object(_), do: nil
  226. defp get_in_reply_to_activity_from_object(%Object{data: %{"inReplyTo" => ap_id}}) do
  227. get_create_by_object_ap_id_with_object(ap_id)
  228. end
  229. defp get_in_reply_to_activity_from_object(_), do: nil
  230. def get_in_reply_to_activity(%Activity{} = activity) do
  231. get_in_reply_to_activity_from_object(Object.normalize(activity, fetch: false))
  232. end
  233. def normalize(obj) when is_map(obj), do: get_by_ap_id_with_object(obj["id"])
  234. def normalize(ap_id) when is_binary(ap_id), do: get_by_ap_id_with_object(ap_id)
  235. def normalize(_), do: nil
  236. def delete_all_by_object_ap_id(id) when is_binary(id) do
  237. id
  238. |> Queries.by_object_id()
  239. |> Queries.exclude_type("Delete")
  240. |> select([u], u)
  241. |> Repo.delete_all()
  242. |> elem(1)
  243. |> Enum.find(fn
  244. %{data: %{"type" => "Create", "object" => ap_id}} when is_binary(ap_id) -> ap_id == id
  245. %{data: %{"type" => "Create", "object" => %{"id" => ap_id}}} -> ap_id == id
  246. _ -> nil
  247. end)
  248. |> purge_web_resp_cache()
  249. end
  250. def delete_all_by_object_ap_id(_), do: nil
  251. defp purge_web_resp_cache(%Activity{} = activity) do
  252. %{path: path} = URI.parse(activity.data["id"])
  253. @cachex.del(:web_resp_cache, path)
  254. activity
  255. end
  256. defp purge_web_resp_cache(nil), do: nil
  257. def follow_accepted?(
  258. %Activity{data: %{"type" => "Follow", "object" => followed_ap_id}} = activity
  259. ) do
  260. with %User{} = follower <- Activity.user_actor(activity),
  261. %User{} = followed <- User.get_cached_by_ap_id(followed_ap_id) do
  262. Pleroma.FollowingRelationship.following?(follower, followed)
  263. else
  264. _ -> false
  265. end
  266. end
  267. def follow_accepted?(_), do: false
  268. def all_by_actor_and_id(actor, status_ids \\ [])
  269. def all_by_actor_and_id(_actor, []), do: []
  270. def all_by_actor_and_id(actor, status_ids) do
  271. Activity
  272. |> where([s], s.id in ^status_ids)
  273. |> where([s], s.actor == ^actor)
  274. |> Repo.all()
  275. end
  276. def follow_requests_for_actor(%User{ap_id: ap_id}) do
  277. ap_id
  278. |> Queries.by_object_id()
  279. |> Queries.by_type("Follow")
  280. |> where([a], fragment("? ->> 'state' = 'pending'", a.data))
  281. end
  282. def following_requests_for_actor(%User{ap_id: ap_id}) do
  283. Queries.by_type("Follow")
  284. |> where([a], fragment("?->>'state' = 'pending'", a.data))
  285. |> where([a], a.actor == ^ap_id)
  286. |> Repo.all()
  287. end
  288. def restrict_deactivated_users(query) do
  289. deactivated_users =
  290. from(u in User.Query.build(%{deactivated: true}), select: u.ap_id)
  291. |> Repo.all()
  292. Activity.Queries.exclude_authors(query, deactivated_users)
  293. end
  294. defdelegate search(user, query, options \\ []), to: Pleroma.Activity.Search
  295. def direct_conversation_id(activity, for_user) do
  296. alias Pleroma.Conversation.Participation
  297. with %{data: %{"context" => context}} when is_binary(context) <- activity,
  298. %Pleroma.Conversation{} = conversation <- Pleroma.Conversation.get_for_ap_id(context),
  299. %Participation{id: participation_id} <-
  300. Participation.for_user_and_conversation(for_user, conversation) do
  301. participation_id
  302. else
  303. _ -> nil
  304. end
  305. end
  306. @spec pinned_by_actor?(Activity.t()) :: boolean()
  307. def pinned_by_actor?(%Activity{} = activity) do
  308. actor = user_actor(activity)
  309. activity.id in actor.pinned_activities
  310. end
  311. @spec get_by_object_ap_id_with_object(String.t()) :: t() | nil
  312. def get_by_object_ap_id_with_object(ap_id) when is_binary(ap_id) do
  313. ap_id
  314. |> Queries.by_object_id()
  315. |> with_preloaded_object()
  316. |> first()
  317. |> Repo.one()
  318. end
  319. def get_by_object_ap_id_with_object(_), do: nil
  320. end