logo

pleroma

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

account_controller.ex (22095B)


  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.Web.MastodonAPI.AccountController do
  5. use Pleroma.Web, :controller
  6. import Pleroma.Web.ControllerHelper,
  7. only: [
  8. add_link_headers: 2,
  9. assign_account_by_id: 2,
  10. embed_relationships?: 1,
  11. json_response: 3
  12. ]
  13. alias Pleroma.Maps
  14. alias Pleroma.User
  15. alias Pleroma.UserNote
  16. alias Pleroma.Web.ActivityPub.ActivityPub
  17. alias Pleroma.Web.ActivityPub.Builder
  18. alias Pleroma.Web.ActivityPub.Pipeline
  19. alias Pleroma.Web.CommonAPI
  20. alias Pleroma.Web.MastodonAPI.ListView
  21. alias Pleroma.Web.MastodonAPI.MastodonAPI
  22. alias Pleroma.Web.MastodonAPI.StatusView
  23. alias Pleroma.Web.OAuth.OAuthController
  24. alias Pleroma.Web.Plugs.OAuthScopesPlug
  25. alias Pleroma.Web.Plugs.RateLimiter
  26. alias Pleroma.Web.TwitterAPI.TwitterAPI
  27. alias Pleroma.Web.Utils.Params
  28. plug(Pleroma.Web.ApiSpec.CastAndValidate, replace_params: false)
  29. plug(:skip_auth when action in [:create, :lookup])
  30. plug(:skip_public_check when action in [:show, :statuses])
  31. plug(
  32. OAuthScopesPlug,
  33. %{fallback: :proceed_unauthenticated, scopes: ["read:accounts"]}
  34. when action in [:show, :followers, :following]
  35. )
  36. plug(
  37. OAuthScopesPlug,
  38. %{fallback: :proceed_unauthenticated, scopes: ["read:statuses"]}
  39. when action == :statuses
  40. )
  41. plug(
  42. OAuthScopesPlug,
  43. %{scopes: ["read:accounts"]}
  44. when action in [:verify_credentials, :endorsements]
  45. )
  46. plug(
  47. OAuthScopesPlug,
  48. %{scopes: ["write:accounts"]}
  49. when action in [:update_credentials, :note, :endorse, :unendorse]
  50. )
  51. plug(OAuthScopesPlug, %{scopes: ["read:lists"]} when action == :lists)
  52. plug(
  53. OAuthScopesPlug,
  54. %{scopes: ["follow", "read:blocks"]} when action == :blocks
  55. )
  56. plug(
  57. OAuthScopesPlug,
  58. %{scopes: ["follow", "write:blocks"]} when action in [:block, :unblock]
  59. )
  60. plug(
  61. OAuthScopesPlug,
  62. %{scopes: ["read:follows"]} when action in [:relationships, :familiar_followers]
  63. )
  64. plug(
  65. OAuthScopesPlug,
  66. %{scopes: ["follow", "write:follows"]}
  67. when action in [:follow_by_uri, :follow, :unfollow, :remove_from_followers]
  68. )
  69. plug(OAuthScopesPlug, %{scopes: ["follow", "read:mutes"]} when action == :mutes)
  70. plug(OAuthScopesPlug, %{scopes: ["follow", "write:mutes"]} when action in [:mute, :unmute])
  71. @relationship_actions [:follow, :unfollow, :remove_from_followers]
  72. @needs_account ~W(
  73. followers following lists follow unfollow mute unmute block unblock
  74. note endorse unendorse remove_from_followers
  75. )a
  76. plug(
  77. RateLimiter,
  78. [name: :relation_id_action, params: ["id", "uri"]] when action in @relationship_actions
  79. )
  80. plug(RateLimiter, [name: :relations_actions] when action in @relationship_actions)
  81. plug(RateLimiter, [name: :app_account_creation] when action == :create)
  82. plug(:assign_account_by_id when action in @needs_account)
  83. action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
  84. defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.AccountOperation
  85. @doc "POST /api/v1/accounts"
  86. def create(
  87. %{assigns: %{app: app}, private: %{open_api_spex: %{body_params: params}}} = conn,
  88. _params
  89. ) do
  90. with :ok <- validate_email_param(params),
  91. :ok <- TwitterAPI.validate_captcha(app, params),
  92. {:ok, user} <- TwitterAPI.register_user(params),
  93. {_, {:ok, token}} <-
  94. {:login, OAuthController.login(user, app, app.scopes)} do
  95. OAuthController.after_token_exchange(conn, %{user: user, token: token})
  96. else
  97. {:login, {:account_status, :confirmation_pending}} ->
  98. json_response(conn, :ok, %{
  99. message: "You have been registered. Please check your email for further instructions.",
  100. identifier: "missing_confirmed_email"
  101. })
  102. {:login, {:account_status, :approval_pending}} ->
  103. json_response(conn, :ok, %{
  104. message:
  105. "You have been registered. You'll be able to log in once your account is approved.",
  106. identifier: "awaiting_approval"
  107. })
  108. {:login, _} ->
  109. json_response(conn, :ok, %{
  110. message:
  111. "You have been registered. Some post-registration steps may be pending. " <>
  112. "Please log in manually.",
  113. identifier: "manual_login_required"
  114. })
  115. {:error, error} ->
  116. json_response(conn, :bad_request, %{error: error})
  117. end
  118. end
  119. def create(%{assigns: %{app: _app}} = conn, _) do
  120. render_error(conn, :bad_request, "Missing parameters")
  121. end
  122. def create(conn, _) do
  123. render_error(conn, :forbidden, "Invalid credentials")
  124. end
  125. defp validate_email_param(%{email: email}) when not is_nil(email), do: :ok
  126. defp validate_email_param(_) do
  127. case Pleroma.Config.get([:instance, :account_activation_required]) do
  128. true -> {:error, dgettext("errors", "Missing parameter: %{name}", name: "email")}
  129. _ -> :ok
  130. end
  131. end
  132. @doc "GET /api/v1/accounts/verify_credentials"
  133. def verify_credentials(%{assigns: %{user: user}} = conn, _) do
  134. chat_token = Phoenix.Token.sign(conn, "user socket", user.id)
  135. render(conn, "show.json",
  136. user: user,
  137. for: user,
  138. with_pleroma_settings: true,
  139. with_chat_token: chat_token
  140. )
  141. end
  142. @doc "PATCH /api/v1/accounts/update_credentials"
  143. def update_credentials(
  144. %{assigns: %{user: user}, private: %{open_api_spex: %{body_params: params}}} = conn,
  145. _params
  146. ) do
  147. params =
  148. params
  149. |> Enum.filter(fn {_, value} -> not is_nil(value) end)
  150. |> Enum.into(%{})
  151. # We use an empty string as a special value to reset
  152. # avatars, banners, backgrounds
  153. user_image_value = fn
  154. "" -> {:ok, nil}
  155. value -> {:ok, value}
  156. end
  157. user_params =
  158. [
  159. :no_rich_text,
  160. :hide_followers_count,
  161. :hide_follows_count,
  162. :hide_followers,
  163. :hide_follows,
  164. :hide_favorites,
  165. :show_role,
  166. :skip_thread_containment,
  167. :allow_following_move,
  168. :also_known_as,
  169. :accepts_chat_messages,
  170. :show_birthday
  171. ]
  172. |> Enum.reduce(%{}, fn key, acc ->
  173. Maps.put_if_present(acc, key, params[key], &{:ok, Params.truthy_param?(&1)})
  174. end)
  175. |> Maps.put_if_present(:name, params[:display_name])
  176. |> Maps.put_if_present(:bio, params[:note])
  177. |> Maps.put_if_present(:raw_bio, params[:note])
  178. |> Maps.put_if_present(:avatar, params[:avatar], user_image_value)
  179. |> Maps.put_if_present(:banner, params[:header], user_image_value)
  180. |> Maps.put_if_present(:background, params[:pleroma_background_image], user_image_value)
  181. |> Maps.put_if_present(
  182. :raw_fields,
  183. params[:fields_attributes],
  184. &{:ok, normalize_fields_attributes(&1)}
  185. )
  186. |> Maps.put_if_present(:pleroma_settings_store, params[:pleroma_settings_store])
  187. |> Maps.put_if_present(:default_scope, params[:default_scope])
  188. |> Maps.put_if_present(:default_scope, params["source"]["privacy"])
  189. |> Maps.put_if_present(:actor_type, params[:bot], fn bot ->
  190. if bot, do: {:ok, "Service"}, else: {:ok, "Person"}
  191. end)
  192. |> Maps.put_if_present(:actor_type, params[:actor_type])
  193. |> Maps.put_if_present(:also_known_as, params[:also_known_as])
  194. # Note: param name is indeed :locked (not an error)
  195. |> Maps.put_if_present(:is_locked, params[:locked])
  196. # Note: param name is indeed :discoverable (not an error)
  197. |> Maps.put_if_present(:is_discoverable, params[:discoverable])
  198. |> Maps.put_if_present(:birthday, params[:birthday])
  199. |> Maps.put_if_present(:language, Pleroma.Web.Gettext.normalize_locale(params[:language]))
  200. |> Maps.put_if_present(:avatar_description, params[:avatar_description])
  201. |> Maps.put_if_present(:header_description, params[:header_description])
  202. # What happens here:
  203. #
  204. # We want to update the user through the pipeline, but the ActivityPub
  205. # update information is not quite enough for this, because this also
  206. # contains local settings that don't federate and don't even appear
  207. # in the Update activity.
  208. #
  209. # So we first build the normal local changeset, then apply it to the
  210. # user data, but don't persist it. With this, we generate the object
  211. # data for our update activity. We feed this and the changeset as meta
  212. # information into the pipeline, where they will be properly updated and
  213. # federated.
  214. with changeset <- User.update_changeset(user, user_params),
  215. {:ok, unpersisted_user} <- Ecto.Changeset.apply_action(changeset, :update),
  216. updated_object <-
  217. Pleroma.Web.ActivityPub.UserView.render("user.json", user: unpersisted_user)
  218. |> Map.delete("@context"),
  219. {:ok, update_data, []} <- Builder.update(user, updated_object),
  220. {:ok, _update, _} <-
  221. Pipeline.common_pipeline(update_data,
  222. local: true,
  223. user_update_changeset: changeset
  224. ) do
  225. render(conn, "show.json",
  226. user: unpersisted_user,
  227. for: unpersisted_user,
  228. with_pleroma_settings: true
  229. )
  230. else
  231. {:error, %Ecto.Changeset{errors: [avatar: {"file is too large", _}]}} ->
  232. render_error(conn, :request_entity_too_large, "File is too large")
  233. {:error, %Ecto.Changeset{errors: [banner: {"file is too large", _}]}} ->
  234. render_error(conn, :request_entity_too_large, "File is too large")
  235. {:error, %Ecto.Changeset{errors: [background: {"file is too large", _}]}} ->
  236. render_error(conn, :request_entity_too_large, "File is too large")
  237. {:error, %Ecto.Changeset{errors: [{:bio, {_, _}} | _]}} ->
  238. render_error(conn, :request_entity_too_large, "Bio is too long")
  239. {:error, %Ecto.Changeset{errors: [{:name, {_, _}} | _]}} ->
  240. render_error(conn, :request_entity_too_large, "Name is too long")
  241. {:error, %Ecto.Changeset{errors: [{:avatar_description, {_, _}} | _]}} ->
  242. render_error(conn, :request_entity_too_large, "Avatar description is too long")
  243. {:error, %Ecto.Changeset{errors: [{:header_description, {_, _}} | _]}} ->
  244. render_error(conn, :request_entity_too_large, "Banner description is too long")
  245. {:error, %Ecto.Changeset{errors: [{:fields, {"invalid", _}} | _]}} ->
  246. render_error(conn, :request_entity_too_large, "One or more field entries are too long")
  247. {:error, %Ecto.Changeset{errors: [{:fields, {_, _}} | _]}} ->
  248. render_error(conn, :request_entity_too_large, "Too many field entries")
  249. _e ->
  250. render_error(conn, :forbidden, "Invalid request")
  251. end
  252. end
  253. defp normalize_fields_attributes(fields) do
  254. if(Enum.all?(fields, &is_tuple/1), do: Enum.map(fields, fn {_, v} -> v end), else: fields)
  255. |> Enum.map(fn
  256. %{} = field -> %{"name" => field.name, "value" => field.value}
  257. field -> field
  258. end)
  259. end
  260. @doc "GET /api/v1/accounts/relationships"
  261. def relationships(
  262. %{assigns: %{user: user}, private: %{open_api_spex: %{params: %{id: id}}}} = conn,
  263. _
  264. ) do
  265. targets = User.get_all_by_ids(List.wrap(id))
  266. render(conn, "relationships.json", user: user, targets: targets)
  267. end
  268. # Instead of returning a 400 when no "id" params is present, Mastodon returns an empty array.
  269. def relationships(%{assigns: %{user: _user}} = conn, _), do: json(conn, [])
  270. @doc "GET /api/v1/accounts/:id"
  271. def show(
  272. %{
  273. assigns: %{user: for_user},
  274. private: %{open_api_spex: %{params: %{id: nickname_or_id} = params}}
  275. } = conn,
  276. _params
  277. ) do
  278. with %User{} = user <- User.get_cached_by_nickname_or_id(nickname_or_id, for: for_user),
  279. :visible <- User.visible_for(user, for_user) do
  280. render(conn, "show.json",
  281. user: user,
  282. for: for_user,
  283. embed_relationships: embed_relationships?(params)
  284. )
  285. else
  286. error -> user_visibility_error(conn, error)
  287. end
  288. end
  289. @doc "GET /api/v1/accounts/:id/statuses"
  290. def statuses(
  291. %{assigns: %{user: reading_user}, private: %{open_api_spex: %{params: params}}} = conn,
  292. _params
  293. ) do
  294. with %User{} = user <- User.get_cached_by_nickname_or_id(params.id, for: reading_user),
  295. :visible <- User.visible_for(user, reading_user) do
  296. params =
  297. params
  298. |> Map.delete(:tagged)
  299. |> Map.put(:tag, params[:tagged])
  300. activities = ActivityPub.fetch_user_activities(user, reading_user, params)
  301. conn
  302. |> add_link_headers(activities)
  303. |> put_view(StatusView)
  304. |> render("index.json",
  305. activities: activities,
  306. for: reading_user,
  307. as: :activity,
  308. with_muted: Map.get(params, :with_muted, false)
  309. )
  310. else
  311. error -> user_visibility_error(conn, error)
  312. end
  313. end
  314. defp user_visibility_error(conn, error) do
  315. case error do
  316. :restrict_unauthenticated ->
  317. render_error(conn, :unauthorized, "This API requires an authenticated user")
  318. _ ->
  319. render_error(conn, :not_found, "Can't find user")
  320. end
  321. end
  322. @doc "GET /api/v1/accounts/:id/followers"
  323. def followers(
  324. %{assigns: %{user: for_user, account: user}, private: %{open_api_spex: %{params: params}}} =
  325. conn,
  326. _params
  327. ) do
  328. params =
  329. params
  330. |> Enum.map(fn {key, value} -> {to_string(key), value} end)
  331. |> Enum.into(%{})
  332. followers =
  333. cond do
  334. for_user && user.id == for_user.id -> MastodonAPI.get_followers(user, params)
  335. user.hide_followers -> []
  336. true -> MastodonAPI.get_followers(user, params)
  337. end
  338. conn
  339. |> add_link_headers(followers)
  340. # https://git.pleroma.social/pleroma/pleroma-fe/-/issues/838#note_59223
  341. |> render("index.json",
  342. for: for_user,
  343. users: followers,
  344. as: :user,
  345. embed_relationships: embed_relationships?(params)
  346. )
  347. end
  348. @doc "GET /api/v1/accounts/:id/following"
  349. def following(
  350. %{assigns: %{user: for_user, account: user}, private: %{open_api_spex: %{params: params}}} =
  351. conn,
  352. _params
  353. ) do
  354. params =
  355. params
  356. |> Enum.map(fn {key, value} -> {to_string(key), value} end)
  357. |> Enum.into(%{})
  358. followers =
  359. cond do
  360. for_user && user.id == for_user.id -> MastodonAPI.get_friends(user, params)
  361. user.hide_follows -> []
  362. true -> MastodonAPI.get_friends(user, params)
  363. end
  364. conn
  365. |> add_link_headers(followers)
  366. # https://git.pleroma.social/pleroma/pleroma-fe/-/issues/838#note_59223
  367. |> render("index.json",
  368. for: for_user,
  369. users: followers,
  370. as: :user,
  371. embed_relationships: embed_relationships?(params)
  372. )
  373. end
  374. @doc "GET /api/v1/accounts/:id/lists"
  375. def lists(%{assigns: %{user: user, account: account}} = conn, _params) do
  376. lists = Pleroma.List.get_lists_account_belongs(user, account)
  377. conn
  378. |> put_view(ListView)
  379. |> render("index.json", lists: lists)
  380. end
  381. @doc "POST /api/v1/accounts/:id/follow"
  382. def follow(%{assigns: %{user: %{id: id}, account: %{id: id}}}, _params) do
  383. {:error, "Can not follow yourself"}
  384. end
  385. def follow(
  386. %{
  387. assigns: %{user: follower, account: followed},
  388. private: %{open_api_spex: %{body_params: params}}
  389. } = conn,
  390. _
  391. ) do
  392. with {:ok, follower} <- MastodonAPI.follow(follower, followed, params) do
  393. render(conn, "relationship.json", user: follower, target: followed)
  394. else
  395. {:error, message} -> json_response(conn, :forbidden, %{error: message})
  396. end
  397. end
  398. @doc "POST /api/v1/accounts/:id/unfollow"
  399. def unfollow(%{assigns: %{user: %{id: id}, account: %{id: id}}}, _params) do
  400. {:error, "Can not unfollow yourself"}
  401. end
  402. def unfollow(%{assigns: %{user: follower, account: followed}} = conn, _params) do
  403. with {:ok, follower} <- CommonAPI.unfollow(followed, follower) do
  404. render(conn, "relationship.json", user: follower, target: followed)
  405. end
  406. end
  407. @doc "POST /api/v1/accounts/:id/mute"
  408. def mute(
  409. %{
  410. assigns: %{user: muter, account: muted},
  411. private: %{open_api_spex: %{body_params: params}}
  412. } = conn,
  413. _params
  414. ) do
  415. params =
  416. params
  417. |> Map.put_new(:duration, Map.get(params, :expires_in, 0))
  418. with {:ok, _user_relationships} <- User.mute(muter, muted, params) do
  419. render(conn, "relationship.json", user: muter, target: muted)
  420. else
  421. {:error, message} -> json_response(conn, :forbidden, %{error: message})
  422. end
  423. end
  424. @doc "POST /api/v1/accounts/:id/unmute"
  425. def unmute(%{assigns: %{user: muter, account: muted}} = conn, _params) do
  426. with {:ok, _user_relationships} <- User.unmute(muter, muted) do
  427. render(conn, "relationship.json", user: muter, target: muted)
  428. else
  429. {:error, message} -> json_response(conn, :forbidden, %{error: message})
  430. end
  431. end
  432. @doc "POST /api/v1/accounts/:id/block"
  433. def block(%{assigns: %{user: blocker, account: blocked}} = conn, _params) do
  434. with {:ok, _activity} <- CommonAPI.block(blocked, blocker) do
  435. render(conn, "relationship.json", user: blocker, target: blocked)
  436. else
  437. {:error, message} -> json_response(conn, :forbidden, %{error: message})
  438. end
  439. end
  440. @doc "POST /api/v1/accounts/:id/unblock"
  441. def unblock(%{assigns: %{user: blocker, account: blocked}} = conn, _params) do
  442. with {:ok, _activity} <- CommonAPI.unblock(blocked, blocker) do
  443. render(conn, "relationship.json", user: blocker, target: blocked)
  444. else
  445. {:error, message} -> json_response(conn, :forbidden, %{error: message})
  446. end
  447. end
  448. @doc "POST /api/v1/accounts/:id/note"
  449. def note(
  450. %{
  451. assigns: %{user: noter, account: target},
  452. private: %{open_api_spex: %{body_params: %{comment: comment}}}
  453. } = conn,
  454. _params
  455. ) do
  456. with {:ok, _user_note} <- UserNote.create(noter, target, comment) do
  457. render(conn, "relationship.json", user: noter, target: target)
  458. end
  459. end
  460. @doc "POST /api/v1/accounts/:id/pin"
  461. def endorse(%{assigns: %{user: endorser, account: endorsed}} = conn, _params) do
  462. with {:ok, _user_relationships} <- User.endorse(endorser, endorsed) do
  463. render(conn, "relationship.json", user: endorser, target: endorsed)
  464. else
  465. {:error, message} -> json_response(conn, :bad_request, %{error: message})
  466. end
  467. end
  468. @doc "POST /api/v1/accounts/:id/unpin"
  469. def unendorse(%{assigns: %{user: endorser, account: endorsed}} = conn, _params) do
  470. with {:ok, _user_relationships} <- User.unendorse(endorser, endorsed) do
  471. render(conn, "relationship.json", user: endorser, target: endorsed)
  472. else
  473. {:error, message} -> json_response(conn, :forbidden, %{error: message})
  474. end
  475. end
  476. @doc "POST /api/v1/accounts/:id/remove_from_followers"
  477. def remove_from_followers(%{assigns: %{user: %{id: id}, account: %{id: id}}}, _params) do
  478. {:error, "Can not unfollow yourself"}
  479. end
  480. def remove_from_followers(%{assigns: %{user: followed, account: follower}} = conn, _params) do
  481. with {:ok, follower} <- CommonAPI.reject_follow_request(follower, followed) do
  482. render(conn, "relationship.json", user: followed, target: follower)
  483. else
  484. nil ->
  485. render_error(conn, :not_found, "Record not found")
  486. end
  487. end
  488. @doc "POST /api/v1/follows"
  489. def follow_by_uri(%{private: %{open_api_spex: %{body_params: %{uri: uri}}}} = conn, _) do
  490. case User.get_cached_by_nickname(uri) do
  491. %User{} = user ->
  492. conn
  493. |> assign(:account, user)
  494. |> follow(%{})
  495. nil ->
  496. {:error, :not_found}
  497. end
  498. end
  499. @doc "GET /api/v1/mutes"
  500. def mutes(%{assigns: %{user: user}} = conn, params) do
  501. users =
  502. user
  503. |> User.muted_users_relation(_restrict_deactivated = true)
  504. |> Pleroma.Pagination.fetch_paginated(params)
  505. conn
  506. |> add_link_headers(users)
  507. |> render("index.json",
  508. users: users,
  509. for: user,
  510. as: :user,
  511. embed_relationships: embed_relationships?(params),
  512. mutes: true
  513. )
  514. end
  515. @doc "GET /api/v1/blocks"
  516. def blocks(%{assigns: %{user: user}} = conn, params) do
  517. users =
  518. user
  519. |> User.blocked_users_relation(_restrict_deactivated = true)
  520. |> Pleroma.Pagination.fetch_paginated(params)
  521. conn
  522. |> add_link_headers(users)
  523. |> render("index.json",
  524. users: users,
  525. for: user,
  526. as: :user,
  527. embed_relationships: embed_relationships?(params)
  528. )
  529. end
  530. @doc "GET /api/v1/accounts/lookup"
  531. def lookup(%{private: %{open_api_spex: %{params: %{acct: nickname}}}} = conn, _params) do
  532. with %User{} = user <- User.get_by_nickname(nickname) do
  533. render(conn, "show.json",
  534. user: user,
  535. skip_visibility_check: true
  536. )
  537. else
  538. error -> user_visibility_error(conn, error)
  539. end
  540. end
  541. @doc "GET /api/v1/endorsements"
  542. def endorsements(%{assigns: %{user: user}} = conn, params) do
  543. users =
  544. user
  545. |> User.endorsed_users_relation(_restrict_deactivated = true)
  546. |> Pleroma.Repo.all()
  547. conn
  548. |> render("index.json",
  549. users: users,
  550. for: user,
  551. as: :user,
  552. embed_relationships: embed_relationships?(params)
  553. )
  554. end
  555. @doc "GET /api/v1/accounts/familiar_followers"
  556. def familiar_followers(
  557. %{assigns: %{user: user}, private: %{open_api_spex: %{params: %{id: id}}}} = conn,
  558. _id
  559. ) do
  560. users =
  561. User.get_all_by_ids(List.wrap(id))
  562. |> Enum.map(&%{id: &1.id, accounts: get_familiar_followers(&1, user)})
  563. conn
  564. |> render("familiar_followers.json",
  565. for: user,
  566. users: users,
  567. as: :user
  568. )
  569. end
  570. defp get_familiar_followers(%{id: id} = user, %{id: id}) do
  571. User.get_familiar_followers(user, user)
  572. end
  573. defp get_familiar_followers(%{hide_followers: true}, _current_user) do
  574. []
  575. end
  576. defp get_familiar_followers(user, current_user) do
  577. User.get_familiar_followers(user, current_user)
  578. end
  579. end