logo

pleroma

My custom branche(s) on git.pleroma.social/pleroma/pleroma git clone https://hacktivis.me/git/pleroma.git

query.ex (8411B)


  1. # Pleroma: A lightweight social networking server
  2. # Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
  3. # SPDX-License-Identifier: AGPL-3.0-only
  4. defmodule Pleroma.User.Query do
  5. @moduledoc """
  6. User query builder module. Builds query from new query or another user query.
  7. ## Example:
  8. query = Pleroma.User.Query.build(%{nickname: "nickname"})
  9. another_query = Pleroma.User.Query.build(query, %{email: "email@example.com"})
  10. Pleroma.Repo.all(query)
  11. Pleroma.Repo.all(another_query)
  12. Adding new rules:
  13. - *ilike criteria*
  14. - add field to @ilike_criteria list
  15. - pass non empty string
  16. - e.g. Pleroma.User.Query.build(%{nickname: "nickname"})
  17. - *equal criteria*
  18. - add field to @equal_criteria list
  19. - pass non empty string
  20. - e.g. Pleroma.User.Query.build(%{email: "email@example.com"})
  21. - *contains criteria*
  22. - add field to @contains_criteria list
  23. - pass values list
  24. - e.g. Pleroma.User.Query.build(%{ap_id: ["http://ap_id1", "http://ap_id2"]})
  25. """
  26. import Ecto.Query
  27. import Pleroma.Web.Utils.Guards, only: [not_empty_string: 1]
  28. alias Pleroma.Config
  29. alias Pleroma.FollowingRelationship
  30. alias Pleroma.User
  31. @type criteria ::
  32. %{
  33. query: String.t(),
  34. tags: [String.t()],
  35. name: String.t(),
  36. email: String.t(),
  37. local: boolean(),
  38. external: boolean(),
  39. active: boolean(),
  40. deactivated: boolean(),
  41. need_approval: boolean(),
  42. unconfirmed: boolean(),
  43. is_admin: boolean(),
  44. is_moderator: boolean(),
  45. is_suggested: boolean(),
  46. is_discoverable: boolean(),
  47. super_users: boolean(),
  48. is_privileged: atom(),
  49. invisible: boolean(),
  50. internal: boolean(),
  51. followers: User.t(),
  52. friends: User.t(),
  53. recipients_from_activity: [String.t()],
  54. nickname: [String.t()] | String.t(),
  55. ap_id: [String.t()],
  56. order_by: term(),
  57. select: term(),
  58. limit: pos_integer(),
  59. actor_types: [String.t()],
  60. birthday_day: pos_integer(),
  61. birthday_month: pos_integer()
  62. }
  63. | map()
  64. @ilike_criteria [:nickname, :name, :query]
  65. @equal_criteria [:email]
  66. @contains_criteria [:ap_id, :nickname]
  67. @spec build(Ecto.Query.t(), criteria()) :: Ecto.Query.t()
  68. def build(query \\ base_query(), criteria) do
  69. prepare_query(query, criteria)
  70. end
  71. @spec paginate(Ecto.Query.t(), pos_integer(), pos_integer()) :: Ecto.Query.t()
  72. def paginate(query, page, page_size) do
  73. from(u in query,
  74. limit: ^page_size,
  75. offset: ^((page - 1) * page_size)
  76. )
  77. end
  78. defp base_query do
  79. from(u in User)
  80. end
  81. defp prepare_query(query, criteria) do
  82. criteria
  83. |> Map.put_new(:internal, false)
  84. |> Enum.reduce(query, &compose_query/2)
  85. end
  86. defp compose_query({key, value}, query)
  87. when key in @ilike_criteria and not_empty_string(value) do
  88. # hack for :query key
  89. key = if key == :query, do: :nickname, else: key
  90. where(query, [u], ilike(field(u, ^key), ^"%#{value}%"))
  91. end
  92. defp compose_query({:invisible, bool}, query) when is_boolean(bool) do
  93. where(query, [u], u.invisible == ^bool)
  94. end
  95. defp compose_query({key, value}, query)
  96. when key in @equal_criteria and not_empty_string(value) do
  97. where(query, [u], ^[{key, value}])
  98. end
  99. defp compose_query({key, values}, query) when key in @contains_criteria and is_list(values) do
  100. where(query, [u], field(u, ^key) in ^values)
  101. end
  102. defp compose_query({:tags, tags}, query) when is_list(tags) and length(tags) > 0 do
  103. where(query, [u], fragment("? && ?", u.tags, ^tags))
  104. end
  105. defp compose_query({:is_admin, bool}, query) do
  106. where(query, [u], u.is_admin == ^bool)
  107. end
  108. defp compose_query({:actor_types, actor_types}, query) when is_list(actor_types) do
  109. where(query, [u], u.actor_type in ^actor_types)
  110. end
  111. defp compose_query({:is_moderator, bool}, query) do
  112. where(query, [u], u.is_moderator == ^bool)
  113. end
  114. defp compose_query({:super_users, _}, query) do
  115. where(
  116. query,
  117. [u],
  118. u.is_admin or u.is_moderator
  119. )
  120. end
  121. defp compose_query({:is_privileged, privilege}, query) do
  122. moderator_privileged = privilege in Config.get([:instance, :moderator_privileges])
  123. admin_privileged = privilege in Config.get([:instance, :admin_privileges])
  124. query = compose_query({:active, true}, query)
  125. query = compose_query({:local, true}, query)
  126. case {admin_privileged, moderator_privileged} do
  127. {false, false} ->
  128. where(
  129. query,
  130. false
  131. )
  132. {true, true} ->
  133. where(
  134. query,
  135. [u],
  136. u.is_admin or u.is_moderator
  137. )
  138. {true, false} ->
  139. where(
  140. query,
  141. [u],
  142. u.is_admin
  143. )
  144. {false, true} ->
  145. where(
  146. query,
  147. [u],
  148. u.is_moderator
  149. )
  150. end
  151. end
  152. defp compose_query({:local, _}, query), do: location_query(query, true)
  153. defp compose_query({:external, _}, query), do: location_query(query, false)
  154. defp compose_query({:active, _}, query) do
  155. where(query, [u], u.is_active == true)
  156. |> where([u], u.is_approved == true)
  157. |> where([u], u.is_confirmed == true)
  158. end
  159. defp compose_query({:legacy_active, _}, query) do
  160. query
  161. |> where([u], fragment("not (?->'deactivated' @> 'true')", u.info))
  162. end
  163. defp compose_query({:deactivated, false}, query) do
  164. where(query, [u], u.is_active == true)
  165. end
  166. defp compose_query({:deactivated, true}, query) do
  167. where(query, [u], u.is_active == false)
  168. end
  169. defp compose_query({:confirmation_pending, bool}, query) do
  170. where(query, [u], u.is_confirmed != ^bool)
  171. end
  172. defp compose_query({:need_approval, _}, query) do
  173. where(query, [u], u.is_approved == false)
  174. end
  175. defp compose_query({:unconfirmed, _}, query) do
  176. where(query, [u], u.is_confirmed == false)
  177. end
  178. defp compose_query({:is_suggested, bool}, query) do
  179. where(query, [u], u.is_suggested == ^bool)
  180. end
  181. defp compose_query({:is_discoverable, bool}, query) do
  182. where(query, [u], u.is_discoverable == ^bool)
  183. end
  184. defp compose_query({:followers, %User{id: id}}, query) do
  185. query
  186. |> where([u], u.id != ^id)
  187. |> join(:inner, [u], r in FollowingRelationship,
  188. as: :relationships,
  189. on: r.following_id == ^id and r.follower_id == u.id
  190. )
  191. |> where([relationships: r], r.state == ^:follow_accept)
  192. end
  193. defp compose_query({:friends, %User{id: id}}, query) do
  194. query
  195. |> where([u], u.id != ^id)
  196. |> join(:inner, [u], r in FollowingRelationship,
  197. as: :relationships,
  198. on: r.following_id == u.id and r.follower_id == ^id
  199. )
  200. |> where([relationships: r], r.state == ^:follow_accept)
  201. end
  202. defp compose_query({:recipients_from_activity, to}, query) do
  203. following_query =
  204. from(u in User,
  205. join: f in FollowingRelationship,
  206. on: u.id == f.following_id,
  207. where: f.state == ^:follow_accept,
  208. where: u.follower_address in ^to,
  209. select: f.follower_id
  210. )
  211. from(u in query,
  212. where: u.ap_id in ^to or u.id in subquery(following_query)
  213. )
  214. end
  215. defp compose_query({:order_by, key}, query) do
  216. order_by(query, [u], field(u, ^key))
  217. end
  218. defp compose_query({:select, keys}, query) do
  219. select(query, [u], ^keys)
  220. end
  221. defp compose_query({:limit, limit}, query) do
  222. limit(query, ^limit)
  223. end
  224. defp compose_query({:internal, false}, query) do
  225. query
  226. |> where([u], not is_nil(u.nickname))
  227. |> where([u], not like(u.nickname, "internal.%"))
  228. end
  229. defp compose_query({:birthday_day, day}, query) do
  230. query
  231. |> where([u], u.show_birthday == true)
  232. |> where([u], not is_nil(u.birthday))
  233. |> where([u], fragment("date_part('day', ?)", u.birthday) == ^day)
  234. end
  235. defp compose_query({:birthday_month, month}, query) do
  236. query
  237. |> where([u], u.show_birthday == true)
  238. |> where([u], not is_nil(u.birthday))
  239. |> where([u], fragment("date_part('month', ?)", u.birthday) == ^month)
  240. end
  241. defp compose_query(_unsupported_param, query), do: query
  242. defp location_query(query, local) do
  243. where(query, [u], u.local == ^local)
  244. end
  245. end