diff options
Diffstat (limited to 'lib/pleroma/web/pleroma_api/controllers')
16 files changed, 1441 insertions, 0 deletions
diff --git a/lib/pleroma/web/pleroma_api/controllers/account_controller.ex b/lib/pleroma/web/pleroma_api/controllers/account_controller.ex new file mode 100644 index 0000000..591391b --- /dev/null +++ b/lib/pleroma/web/pleroma_api/controllers/account_controller.ex @@ -0,0 +1,159 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.PleromaAPI.AccountController do + use Pleroma.Web, :controller + + import Pleroma.Web.ControllerHelper, + only: [ + json_response: 3, + add_link_headers: 2, + embed_relationships?: 1, + assign_account_by_id: 2 + ] + + alias Pleroma.User + alias Pleroma.Web.ActivityPub.ActivityPub + alias Pleroma.Web.MastodonAPI.StatusView + alias Pleroma.Web.Plugs.OAuthScopesPlug + alias Pleroma.Web.Plugs.RateLimiter + + require Pleroma.Constants + + plug( + Majic.Plug, + [pool: Pleroma.MajicPool] when action in [:update_avatar, :update_background, :update_banner] + ) + + plug( + OpenApiSpex.Plug.PutApiSpec, + [module: Pleroma.Web.ApiSpec] when action == :confirmation_resend + ) + + plug(Pleroma.Web.ApiSpec.CastAndValidate) + + plug(:skip_auth when action == :confirmation_resend) + + plug( + OAuthScopesPlug, + %{scopes: ["follow", "write:follows"]} when action in [:subscribe, :unsubscribe] + ) + + plug( + OAuthScopesPlug, + %{scopes: ["read:favourites"], fallback: :proceed_unauthenticated} when action == :favourites + ) + + plug( + OAuthScopesPlug, + %{fallback: :proceed_unauthenticated, scopes: ["read:accounts"]} + when action == :endorsements + ) + + plug( + OAuthScopesPlug, + %{scopes: ["read:accounts"]} when action == :birthdays + ) + + plug(RateLimiter, [name: :account_confirmation_resend] when action == :confirmation_resend) + + plug( + :assign_account_by_id + when action in [:favourites, :endorsements, :subscribe, :unsubscribe] + ) + + defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaAccountOperation + + @doc "POST /api/v1/pleroma/accounts/confirmation_resend" + def confirmation_resend(conn, params) do + nickname_or_email = params[:email] || params[:nickname] + + with %User{} = user <- User.get_by_nickname_or_email(nickname_or_email), + {:ok, _} <- User.maybe_send_confirmation_email(user) do + json_response(conn, :no_content, "") + end + end + + @doc "GET /api/v1/pleroma/accounts/:id/favourites" + def favourites(%{assigns: %{account: %{hide_favorites: true}}} = conn, _params) do + render_error(conn, :forbidden, "Can't get favorites") + end + + def favourites(%{assigns: %{user: for_user, account: user}} = conn, params) do + params = + params + |> Map.put(:type, "Create") + |> Map.put(:favorited_by, user.ap_id) + |> Map.put(:blocking_user, for_user) + + recipients = + if for_user do + [Pleroma.Constants.as_public()] ++ [for_user.ap_id | User.following(for_user)] + else + [Pleroma.Constants.as_public()] + end + + activities = + recipients + |> ActivityPub.fetch_activities(params) + |> Enum.reverse() + + conn + |> add_link_headers(activities) + |> put_view(StatusView) + |> render("index.json", + activities: activities, + for: for_user, + as: :activity + ) + end + + @doc "GET /api/v1/pleroma/accounts/:id/endorsements" + def endorsements(%{assigns: %{user: for_user, account: user}} = conn, params) do + users = + user + |> User.endorsed_users_relation(_restrict_deactivated = true) + |> Pleroma.Repo.all() + + conn + |> render("index.json", + for: for_user, + users: users, + as: :user, + embed_relationships: embed_relationships?(params) + ) + end + + @doc "POST /api/v1/pleroma/accounts/:id/subscribe" + def subscribe(%{assigns: %{user: user, account: subscription_target}} = conn, _params) do + with {:ok, _subscription} <- User.subscribe(user, subscription_target) do + render(conn, "relationship.json", user: user, target: subscription_target) + else + {:error, message} -> json_response(conn, :forbidden, %{error: message}) + end + end + + @doc "POST /api/v1/pleroma/accounts/:id/unsubscribe" + def unsubscribe(%{assigns: %{user: user, account: subscription_target}} = conn, _params) do + with {:ok, _subscription} <- User.unsubscribe(user, subscription_target) do + render(conn, "relationship.json", user: user, target: subscription_target) + else + {:error, message} -> json_response(conn, :forbidden, %{error: message}) + end + end + + @doc "GET /api/v1/pleroma/birthdays" + def birthdays(%{assigns: %{user: %User{} = user}} = conn, %{day: day, month: month} = _params) do + birthdays = + User.get_friends_birthdays_query(user, day, month) + |> Pleroma.Repo.all() + + conn + |> render("index.json", + for: user, + users: birthdays, + as: :user + ) + end +end diff --git a/lib/pleroma/web/pleroma_api/controllers/app_controller.ex b/lib/pleroma/web/pleroma_api/controllers/app_controller.ex new file mode 100644 index 0000000..3e84f75 --- /dev/null +++ b/lib/pleroma/web/pleroma_api/controllers/app_controller.ex @@ -0,0 +1,23 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.PleromaAPI.AppController do + use Pleroma.Web, :controller + + alias Pleroma.Web.OAuth.App + alias Pleroma.Web.Plugs.OAuthScopesPlug + + plug(OAuthScopesPlug, %{scopes: ["follow", "read"]} when action in [:index]) + + plug(Pleroma.Web.ApiSpec.CastAndValidate) + + defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaAppOperation + + @doc "GET /api/v1/pleroma/apps" + def index(%{assigns: %{user: user}} = conn, _params) do + with apps <- App.get_user_apps(user) do + render(conn, "index.json", %{apps: apps}) + end + end +end diff --git a/lib/pleroma/web/pleroma_api/controllers/backup_controller.ex b/lib/pleroma/web/pleroma_api/controllers/backup_controller.ex new file mode 100644 index 0000000..b9daed2 --- /dev/null +++ b/lib/pleroma/web/pleroma_api/controllers/backup_controller.ex @@ -0,0 +1,28 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.PleromaAPI.BackupController do + use Pleroma.Web, :controller + + alias Pleroma.User.Backup + alias Pleroma.Web.Plugs.OAuthScopesPlug + + action_fallback(Pleroma.Web.MastodonAPI.FallbackController) + plug(OAuthScopesPlug, %{scopes: ["read:backups"]} when action in [:index, :create]) + plug(Pleroma.Web.ApiSpec.CastAndValidate) + + defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaBackupOperation + + def index(%{assigns: %{user: user}} = conn, _params) do + backups = Backup.list(user) + render(conn, "index.json", backups: backups) + end + + def create(%{assigns: %{user: user}} = conn, _params) do + with {:ok, _} <- Backup.create(user) do + backups = Backup.list(user) + render(conn, "index.json", backups: backups) + end + end +end diff --git a/lib/pleroma/web/pleroma_api/controllers/chat_controller.ex b/lib/pleroma/web/pleroma_api/controllers/chat_controller.ex new file mode 100644 index 0000000..3d7b6a4 --- /dev/null +++ b/lib/pleroma/web/pleroma_api/controllers/chat_controller.ex @@ -0,0 +1,188 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only +defmodule Pleroma.Web.PleromaAPI.ChatController do + use Pleroma.Web, :controller + + import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2] + + alias Pleroma.Activity + alias Pleroma.Chat + alias Pleroma.Chat.MessageReference + alias Pleroma.Object + alias Pleroma.Pagination + alias Pleroma.Repo + alias Pleroma.User + alias Pleroma.Web.CommonAPI + alias Pleroma.Web.PleromaAPI.Chat.MessageReferenceView + alias Pleroma.Web.Plugs.OAuthScopesPlug + + import Ecto.Query + + action_fallback(Pleroma.Web.MastodonAPI.FallbackController) + + plug( + OAuthScopesPlug, + %{scopes: ["write:chats"]} + when action in [ + :post_chat_message, + :create, + :mark_as_read, + :mark_message_as_read, + :delete_message + ] + ) + + plug( + OAuthScopesPlug, + %{scopes: ["read:chats"]} when action in [:messages, :index, :index2, :show] + ) + + plug(Pleroma.Web.ApiSpec.CastAndValidate) + + defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.ChatOperation + + def delete_message(%{assigns: %{user: %{id: user_id} = user}} = conn, %{ + message_id: message_id, + id: chat_id + }) do + with %MessageReference{} = cm_ref <- + MessageReference.get_by_id(message_id), + ^chat_id <- to_string(cm_ref.chat_id), + %Chat{user_id: ^user_id} <- Chat.get_by_id(chat_id), + {:ok, _} <- remove_or_delete(cm_ref, user) do + conn + |> put_view(MessageReferenceView) + |> render("show.json", chat_message_reference: cm_ref) + else + _e -> + {:error, :could_not_delete} + end + end + + defp remove_or_delete( + %{object: %{data: %{"actor" => actor, "id" => id}}}, + %{ap_id: actor} = user + ) do + with %Activity{} = activity <- Activity.get_create_by_object_ap_id(id) do + CommonAPI.delete(activity.id, user) + end + end + + defp remove_or_delete(cm_ref, _), do: MessageReference.delete(cm_ref) + + def post_chat_message( + %{body_params: params, assigns: %{user: user}} = conn, + %{id: id} + ) do + with {:ok, chat} <- Chat.get_by_user_and_id(user, id), + %User{} = recipient <- User.get_cached_by_ap_id(chat.recipient), + {:ok, activity} <- + CommonAPI.post_chat_message(user, recipient, params[:content], + media_id: params[:media_id], + idempotency_key: idempotency_key(conn) + ), + message <- Object.normalize(activity, fetch: false), + cm_ref <- MessageReference.for_chat_and_object(chat, message) do + conn + |> put_view(MessageReferenceView) + |> render("show.json", chat_message_reference: cm_ref) + else + {:reject, message} -> + conn + |> put_status(:unprocessable_entity) + |> json(%{error: message}) + + {:error, message} -> + conn + |> put_status(:bad_request) + |> json(%{error: message}) + end + end + + def mark_message_as_read( + %{assigns: %{user: %{id: user_id}}} = conn, + %{id: chat_id, message_id: message_id} + ) do + with %MessageReference{} = cm_ref <- MessageReference.get_by_id(message_id), + ^chat_id <- to_string(cm_ref.chat_id), + %Chat{user_id: ^user_id} <- Chat.get_by_id(chat_id), + {:ok, cm_ref} <- MessageReference.mark_as_read(cm_ref) do + conn + |> put_view(MessageReferenceView) + |> render("show.json", chat_message_reference: cm_ref) + end + end + + def mark_as_read( + %{body_params: %{last_read_id: last_read_id}, assigns: %{user: user}} = conn, + %{id: id} + ) do + with {:ok, chat} <- Chat.get_by_user_and_id(user, id), + {_n, _} <- MessageReference.set_all_seen_for_chat(chat, last_read_id) do + render(conn, "show.json", chat: chat) + end + end + + def messages(%{assigns: %{user: user}} = conn, %{id: id} = params) do + with {:ok, chat} <- Chat.get_by_user_and_id(user, id) do + chat_message_refs = + chat + |> MessageReference.for_chat_query() + |> Pagination.fetch_paginated(params) + + conn + |> add_link_headers(chat_message_refs) + |> put_view(MessageReferenceView) + |> render("index.json", chat_message_references: chat_message_refs) + end + end + + def index(%{assigns: %{user: user}} = conn, params) do + chats = + index_query(user, params) + |> Repo.all() + + render(conn, "index.json", chats: chats) + end + + def index2(%{assigns: %{user: user}} = conn, params) do + chats = + index_query(user, params) + |> Pagination.fetch_paginated(params) + + conn + |> add_link_headers(chats) + |> render("index.json", chats: chats) + end + + defp index_query(%{id: user_id} = user, params) do + exclude_users = + User.cached_blocked_users_ap_ids(user) ++ + if params[:with_muted], do: [], else: User.cached_muted_users_ap_ids(user) + + user_id + |> Chat.for_user_query() + |> where([c], c.recipient not in ^exclude_users) + end + + def create(%{assigns: %{user: user}} = conn, %{id: id}) do + with %User{ap_id: recipient} <- User.get_cached_by_id(id), + {:ok, %Chat{} = chat} <- Chat.get_or_create(user.id, recipient) do + render(conn, "show.json", chat: chat) + end + end + + def show(%{assigns: %{user: user}} = conn, %{id: id}) do + with {:ok, chat} <- Chat.get_by_user_and_id(user, id) do + render(conn, "show.json", chat: chat) + end + end + + defp idempotency_key(conn) do + case get_req_header(conn, "idempotency-key") do + [key] -> key + _ -> nil + end + end +end diff --git a/lib/pleroma/web/pleroma_api/controllers/conversation_controller.ex b/lib/pleroma/web/pleroma_api/controllers/conversation_controller.ex new file mode 100644 index 0000000..37990db --- /dev/null +++ b/lib/pleroma/web/pleroma_api/controllers/conversation_controller.ex @@ -0,0 +1,93 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.PleromaAPI.ConversationController do + use Pleroma.Web, :controller + + import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2] + + alias Pleroma.Conversation.Participation + alias Pleroma.Web.ActivityPub.ActivityPub + alias Pleroma.Web.MastodonAPI.StatusView + alias Pleroma.Web.Plugs.OAuthScopesPlug + + plug(Pleroma.Web.ApiSpec.CastAndValidate) + plug(OAuthScopesPlug, %{scopes: ["read:statuses"]} when action in [:show, :statuses]) + + plug( + OAuthScopesPlug, + %{scopes: ["write:conversations"]} when action in [:update, :mark_as_read] + ) + + defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaConversationOperation + + def show(%{assigns: %{user: %{id: user_id} = user}} = conn, %{id: participation_id}) do + with %Participation{user_id: ^user_id} = participation <- Participation.get(participation_id) do + render(conn, "participation.json", participation: participation, for: user) + else + _error -> + conn + |> put_status(:not_found) + |> json(%{"error" => "Unknown conversation id"}) + end + end + + def statuses( + %{assigns: %{user: %{id: user_id} = user}} = conn, + %{id: participation_id} = params + ) do + with %Participation{user_id: ^user_id} = participation <- + Participation.get(participation_id, preload: [:conversation]) do + params = + params + |> Map.put(:blocking_user, user) + |> Map.put(:muting_user, user) + |> Map.put(:user, user) + + activities = + participation.conversation.ap_id + |> ActivityPub.fetch_activities_for_context_query(params) + |> Pleroma.Pagination.fetch_paginated(Map.put(params, :total, false)) + |> Enum.reverse() + + conn + |> add_link_headers(activities) + |> put_view(StatusView) + |> render("index.json", activities: activities, for: user, as: :activity) + else + _error -> + conn + |> put_status(:not_found) + |> json(%{"error" => "Unknown conversation id"}) + end + end + + def update( + %{assigns: %{user: %{id: user_id} = user}} = conn, + %{id: participation_id, recipients: recipients} + ) do + with %Participation{user_id: ^user_id} = participation <- Participation.get(participation_id), + {:ok, participation} <- Participation.set_recipients(participation, recipients) do + render(conn, "participation.json", participation: participation, for: user) + else + {:error, message} -> + conn + |> put_status(:bad_request) + |> json(%{"error" => message}) + + _error -> + conn + |> put_status(:not_found) + |> json(%{"error" => "Unknown conversation id"}) + end + end + + def mark_as_read(%{assigns: %{user: user}} = conn, _params) do + with {:ok, _, participations} <- Participation.mark_all_as_read(user) do + conn + |> add_link_headers(participations) + |> render("participations.json", participations: participations, for: user) + end + end +end diff --git a/lib/pleroma/web/pleroma_api/controllers/emoji_file_controller.ex b/lib/pleroma/web/pleroma_api/controllers/emoji_file_controller.ex new file mode 100644 index 0000000..f854cf9 --- /dev/null +++ b/lib/pleroma/web/pleroma_api/controllers/emoji_file_controller.ex @@ -0,0 +1,154 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.PleromaAPI.EmojiFileController do + use Pleroma.Web, :controller + + alias Pleroma.Emoji.Pack + alias Pleroma.Web.ApiSpec + + plug(Pleroma.Web.ApiSpec.CastAndValidate) + + plug( + Pleroma.Web.Plugs.OAuthScopesPlug, + %{scopes: ["admin:write"]} + when action in [ + :create, + :update, + :delete + ] + ) + + defdelegate open_api_operation(action), to: ApiSpec.PleromaEmojiFileOperation + + def create(%{body_params: params} = conn, %{name: pack_name}) do + filename = params[:filename] || get_filename(params[:file]) + shortcode = params[:shortcode] || Path.basename(filename, Path.extname(filename)) + + with {:ok, pack} <- Pack.load_pack(pack_name), + {:ok, file} <- get_file(params[:file]), + {:ok, pack} <- Pack.add_file(pack, shortcode, filename, file) do + json(conn, pack.files) + else + {:error, :already_exists} -> + conn + |> put_status(:conflict) + |> json(%{error: "An emoji with the \"#{shortcode}\" shortcode already exists"}) + + {:error, :empty_values} -> + conn + |> put_status(:unprocessable_entity) + |> json(%{error: "pack name, shortcode or filename cannot be empty"}) + + {:error, _} = error -> + handle_error(conn, error, %{ + pack_name: pack_name, + message: "Unexpected error occurred while adding file to pack." + }) + end + end + + def update(%{body_params: %{shortcode: shortcode} = params} = conn, %{name: pack_name}) do + new_shortcode = params[:new_shortcode] + new_filename = params[:new_filename] + force = params[:force] + + with {:ok, pack} <- Pack.load_pack(pack_name), + {:ok, pack} <- Pack.update_file(pack, shortcode, new_shortcode, new_filename, force) do + json(conn, pack.files) + else + {:error, :already_exists} -> + conn + |> put_status(:conflict) + |> json(%{ + error: + "New shortcode \"#{new_shortcode}\" is already used. If you want to override emoji use 'force' option" + }) + + {:error, :empty_values} -> + conn + |> put_status(:unprocessable_entity) + |> json(%{error: "new_shortcode or new_filename cannot be empty"}) + + {:error, _} = error -> + handle_error(conn, error, %{ + pack_name: pack_name, + code: shortcode, + message: "Unexpected error occurred while updating." + }) + end + end + + def delete(conn, %{name: pack_name, shortcode: shortcode}) do + with {:ok, pack} <- Pack.load_pack(pack_name), + {:ok, pack} <- Pack.delete_file(pack, shortcode) do + json(conn, pack.files) + else + {:error, :empty_values} -> + conn + |> put_status(:unprocessable_entity) + |> json(%{error: "pack name or shortcode cannot be empty"}) + + {:error, _} = error -> + handle_error(conn, error, %{ + pack_name: pack_name, + code: shortcode, + message: "Unexpected error occurred while deleting emoji file." + }) + end + end + + defp handle_error(conn, {:error, :doesnt_exist}, %{code: emoji_code}) do + conn + |> put_status(:bad_request) + |> json(%{error: "Emoji \"#{emoji_code}\" does not exist"}) + end + + defp handle_error(conn, {:error, :enoent}, %{pack_name: pack_name}) do + conn + |> put_status(:not_found) + |> json(%{error: "pack \"#{pack_name}\" is not found"}) + end + + defp handle_error(conn, {:error, error}, opts) do + message = + [ + Map.get(opts, :message, "Unexpected error occurred."), + Pleroma.Utils.posix_error_message(error) + ] + |> Enum.join(" ") + |> String.trim() + + conn + |> put_status(:internal_server_error) + |> json(%{error: message}) + end + + defp get_filename(%Plug.Upload{filename: filename}), do: filename + defp get_filename(url) when is_binary(url), do: Path.basename(url) + + def get_file(%Plug.Upload{} = file), do: {:ok, file} + + def get_file(url) when is_binary(url) do + with {:ok, %Tesla.Env{body: body, status: code, headers: headers}} + when code in 200..299 <- Pleroma.HTTP.get(url) do + path = Plug.Upload.random_file!("emoji") + + content_type = + case List.keyfind(headers, "content-type", 0) do + {"content-type", value} -> value + nil -> nil + end + + File.write(path, body) + + {:ok, + %Plug.Upload{ + filename: Path.basename(url), + path: path, + content_type: content_type + }} + end + end +end diff --git a/lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex b/lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex new file mode 100644 index 0000000..420fea1 --- /dev/null +++ b/lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex @@ -0,0 +1,230 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.PleromaAPI.EmojiPackController do + use Pleroma.Web, :controller + + alias Pleroma.Emoji.Pack + + plug(Pleroma.Web.ApiSpec.CastAndValidate) + + plug( + Pleroma.Web.Plugs.OAuthScopesPlug, + %{scopes: ["admin:write"]} + when action in [ + :import_from_filesystem, + :remote, + :download, + :create, + :update, + :delete + ] + ) + + plug(:skip_auth when action in [:index, :archive, :show]) + + defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaEmojiPackOperation + + def remote(conn, params) do + with {:ok, packs} <- + Pack.list_remote(url: params.url, page_size: params.page_size, page: params.page) do + json(conn, packs) + else + {:error, :not_shareable} -> + conn + |> put_status(:internal_server_error) + |> json(%{error: "The requested instance does not support sharing emoji packs"}) + end + end + + def index(conn, params) do + emoji_path = + [:instance, :static_dir] + |> Pleroma.Config.get!() + |> Path.join("emoji") + + with {:ok, packs, count} <- Pack.list_local(page: params.page, page_size: params.page_size) do + json(conn, %{packs: packs, count: count}) + else + {:error, :create_dir, e} -> + conn + |> put_status(:internal_server_error) + |> json(%{error: "Failed to create the emoji pack directory at #{emoji_path}: #{e}"}) + + {:error, :ls, e} -> + conn + |> put_status(:internal_server_error) + |> json(%{ + error: "Failed to get the contents of the emoji pack directory at #{emoji_path}: #{e}" + }) + end + end + + def show(conn, %{name: name, page: page, page_size: page_size}) do + name = String.trim(name) + + with {:ok, pack} <- Pack.show(name: name, page: page, page_size: page_size) do + json(conn, pack) + else + {:error, :enoent} -> + conn + |> put_status(:not_found) + |> json(%{error: "Pack #{name} does not exist"}) + + {:error, :empty_values} -> + conn + |> put_status(:bad_request) + |> json(%{error: "pack name cannot be empty"}) + + {:error, error} -> + error_message = + add_posix_error( + "Failed to get the contents of the `#{name}` pack.", + error + ) + + conn + |> put_status(:internal_server_error) + |> json(%{error: error_message}) + end + end + + def archive(conn, %{name: name}) do + with {:ok, archive} <- Pack.get_archive(name) do + send_download(conn, {:binary, archive}, filename: "#{name}.zip") + else + {:error, :cant_download} -> + conn + |> put_status(:forbidden) + |> json(%{ + error: + "Pack #{name} cannot be downloaded from this instance, either pack sharing was disabled for this pack or some files are missing" + }) + + {:error, :enoent} -> + conn + |> put_status(:not_found) + |> json(%{error: "Pack #{name} does not exist"}) + end + end + + def download(%{body_params: %{url: url, name: name} = params} = conn, _) do + with {:ok, _pack} <- Pack.download(name, url, params[:as]) do + json(conn, "ok") + else + {:error, :not_shareable} -> + conn + |> put_status(:internal_server_error) + |> json(%{error: "The requested instance does not support sharing emoji packs"}) + + {:error, :invalid_checksum} -> + conn + |> put_status(:internal_server_error) + |> json(%{error: "SHA256 for the pack doesn't match the one sent by the server"}) + + {:error, error} -> + conn + |> put_status(:internal_server_error) + |> json(%{error: error}) + end + end + + def create(conn, %{name: name}) do + name = String.trim(name) + + with {:ok, _pack} <- Pack.create(name) do + json(conn, "ok") + else + {:error, :eexist} -> + conn + |> put_status(:conflict) + |> json(%{error: "A pack named \"#{name}\" already exists"}) + + {:error, :empty_values} -> + conn + |> put_status(:bad_request) + |> json(%{error: "pack name cannot be empty"}) + + {:error, error} -> + error_message = + add_posix_error( + "Unexpected error occurred while creating pack.", + error + ) + + conn + |> put_status(:internal_server_error) + |> json(%{error: error_message}) + end + end + + def delete(conn, %{name: name}) do + name = String.trim(name) + + with {:ok, deleted} when deleted != [] <- Pack.delete(name) do + json(conn, "ok") + else + {:ok, []} -> + conn + |> put_status(:not_found) + |> json(%{error: "Pack #{name} does not exist"}) + + {:error, :empty_values} -> + conn + |> put_status(:bad_request) + |> json(%{error: "pack name cannot be empty"}) + + {:error, error, _} -> + error_message = add_posix_error("Couldn't delete the `#{name}` pack", error) + + conn + |> put_status(:internal_server_error) + |> json(%{error: error_message}) + end + end + + def update(%{body_params: %{metadata: metadata}} = conn, %{name: name}) do + with {:ok, pack} <- Pack.update_metadata(name, metadata) do + json(conn, pack.pack) + else + {:error, :incomplete} -> + conn + |> put_status(:bad_request) + |> json(%{error: "The fallback archive does not have all files specified in pack.json"}) + + {:error, error} -> + error_message = + add_posix_error( + "Unexpected error occurred while updating pack metadata.", + error + ) + + conn + |> put_status(:internal_server_error) + |> json(%{error: error_message}) + end + end + + def import_from_filesystem(conn, _params) do + with {:ok, names} <- Pack.import_from_filesystem() do + json(conn, names) + else + {:error, :no_read_write} -> + conn + |> put_status(:internal_server_error) + |> json(%{error: "Error: emoji pack directory must be writable"}) + + {:error, _} -> + conn + |> put_status(:internal_server_error) + |> json(%{error: "Error accessing emoji pack directory"}) + end + end + + defp add_posix_error(msg, error) do + [msg, Pleroma.Utils.posix_error_message(error)] + |> Enum.join(" ") + |> String.trim() + end +end diff --git a/lib/pleroma/web/pleroma_api/controllers/emoji_reaction_controller.ex b/lib/pleroma/web/pleroma_api/controllers/emoji_reaction_controller.ex new file mode 100644 index 0000000..78fd0b2 --- /dev/null +++ b/lib/pleroma/web/pleroma_api/controllers/emoji_reaction_controller.ex @@ -0,0 +1,94 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.PleromaAPI.EmojiReactionController do + use Pleroma.Web, :controller + + alias Pleroma.Activity + alias Pleroma.Object + alias Pleroma.User + alias Pleroma.Web.CommonAPI + alias Pleroma.Web.MastodonAPI.StatusView + alias Pleroma.Web.Plugs.OAuthScopesPlug + + plug(Pleroma.Web.ApiSpec.CastAndValidate) + plug(OAuthScopesPlug, %{scopes: ["write:statuses"]} when action in [:create, :delete]) + + plug( + OAuthScopesPlug, + %{scopes: ["read:statuses"], fallback: :proceed_unauthenticated} + when action == :index + ) + + defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.EmojiReactionOperation + + action_fallback(Pleroma.Web.MastodonAPI.FallbackController) + + def index(%{assigns: %{user: user}} = conn, %{id: activity_id} = params) do + with true <- Pleroma.Config.get([:instance, :show_reactions]), + %Activity{} = activity <- Activity.get_by_id_with_object(activity_id), + %Object{data: %{"reactions" => reactions}} when is_list(reactions) <- + Object.normalize(activity, fetch: false) do + reactions = + reactions + |> filter(params) + |> filter_allowed_users(user, Map.get(params, :with_muted, false)) + + render(conn, "index.json", emoji_reactions: reactions, user: user) + else + _e -> json(conn, []) + end + end + + def filter_allowed_users(reactions, user, with_muted) do + exclude_ap_ids = + if is_nil(user) do + [] + else + User.cached_blocked_users_ap_ids(user) ++ + if not with_muted, do: User.cached_muted_users_ap_ids(user), else: [] + end + + filter_emoji = fn emoji, users -> + case Enum.reject(users, &(&1 in exclude_ap_ids)) do + [] -> nil + users -> {emoji, users} + end + end + + reactions + |> Stream.map(fn + [emoji, users] when is_list(users) -> filter_emoji.(emoji, users) + {emoji, users} when is_list(users) -> filter_emoji.(emoji, users) + _ -> nil + end) + |> Stream.reject(&is_nil/1) + end + + defp filter(reactions, %{emoji: emoji}) when is_binary(emoji) do + Enum.filter(reactions, fn [e, _] -> e == emoji end) + end + + defp filter(reactions, _), do: reactions + + def create(%{assigns: %{user: user}} = conn, %{id: activity_id, emoji: emoji}) do + with {:ok, _activity} <- CommonAPI.react_with_emoji(activity_id, user, emoji) do + activity = Activity.get_by_id(activity_id) + + conn + |> put_view(StatusView) + |> render("show.json", activity: activity, for: user, as: :activity) + end + end + + def delete(%{assigns: %{user: user}} = conn, %{id: activity_id, emoji: emoji}) do + with {:ok, _activity} <- CommonAPI.unreact_with_emoji(activity_id, user, emoji) do + activity = Activity.get_by_id(activity_id) + + conn + |> put_view(StatusView) + |> render("show.json", activity: activity, for: user, as: :activity) + end + end +end diff --git a/lib/pleroma/web/pleroma_api/controllers/instances_controller.ex b/lib/pleroma/web/pleroma_api/controllers/instances_controller.ex new file mode 100644 index 0000000..6257e31 --- /dev/null +++ b/lib/pleroma/web/pleroma_api/controllers/instances_controller.ex @@ -0,0 +1,21 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.PleromaAPI.InstancesController do + use Pleroma.Web, :controller + + alias Pleroma.Instances + + plug(Pleroma.Web.ApiSpec.CastAndValidate) + + defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaInstancesOperation + + def show(conn, _params) do + unreachable = + Instances.get_consistently_unreachable() + |> Map.new(fn {host, date} -> {host, to_string(date)} end) + + json(conn, %{"unreachable" => unreachable}) + end +end diff --git a/lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex b/lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex new file mode 100644 index 0000000..66e9d84 --- /dev/null +++ b/lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex @@ -0,0 +1,42 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.PleromaAPI.MascotController do + use Pleroma.Web, :controller + + alias Pleroma.User + alias Pleroma.Web.ActivityPub.ActivityPub + alias Pleroma.Web.Plugs.OAuthScopesPlug + + plug(Majic.Plug, [pool: Pleroma.MajicPool] when action in [:update]) + plug(Pleroma.Web.ApiSpec.CastAndValidate) + plug(OAuthScopesPlug, %{scopes: ["read:accounts"]} when action == :show) + plug(OAuthScopesPlug, %{scopes: ["write:accounts"]} when action != :show) + + defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaMascotOperation + + @doc "GET /api/v1/pleroma/mascot" + def show(%{assigns: %{user: user}} = conn, _params) do + json(conn, User.get_mascot(user)) + end + + @doc "PUT /api/v1/pleroma/mascot" + def update(%{assigns: %{user: user}, body_params: %{file: file}} = conn, _) do + with {:content_type, "image" <> _} <- {:content_type, file.content_type}, + {:ok, object} <- ActivityPub.upload(file, actor: User.ap_id(user)) do + attachment = render_attachment(object) + {:ok, _user} = User.mascot_update(user, attachment) + + json(conn, attachment) + else + {:content_type, _} -> + render_error(conn, :unsupported_media_type, "mascots can only be images") + end + end + + defp render_attachment(object) do + attachment_data = Map.put(object.data, "id", object.id) + Pleroma.Web.MastodonAPI.StatusView.render("attachment.json", %{attachment: attachment_data}) + end +end diff --git a/lib/pleroma/web/pleroma_api/controllers/notification_controller.ex b/lib/pleroma/web/pleroma_api/controllers/notification_controller.ex new file mode 100644 index 0000000..87ea81c --- /dev/null +++ b/lib/pleroma/web/pleroma_api/controllers/notification_controller.ex @@ -0,0 +1,38 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.PleromaAPI.NotificationController do + use Pleroma.Web, :controller + + alias Pleroma.Notification + + plug(Pleroma.Web.ApiSpec.CastAndValidate) + + plug( + Pleroma.Web.Plugs.OAuthScopesPlug, + %{scopes: ["write:notifications"]} when action == :mark_as_read + ) + + defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaNotificationOperation + + def mark_as_read(%{assigns: %{user: user}, body_params: %{id: notification_id}} = conn, _) do + with {:ok, notification} <- Notification.read_one(user, notification_id) do + render(conn, "show.json", notification: notification, for: user) + else + {:error, message} -> + conn + |> put_status(:bad_request) + |> json(%{"error" => message}) + end + end + + def mark_as_read(%{assigns: %{user: user}, body_params: %{max_id: max_id}} = conn, _) do + notifications = + user + |> Notification.set_read_up_to(max_id) + |> Enum.take(80) + + render(conn, "index.json", notifications: notifications, for: user) + end +end diff --git a/lib/pleroma/web/pleroma_api/controllers/report_controller.ex b/lib/pleroma/web/pleroma_api/controllers/report_controller.ex new file mode 100644 index 0000000..1f0a82c --- /dev/null +++ b/lib/pleroma/web/pleroma_api/controllers/report_controller.ex @@ -0,0 +1,46 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.PleromaAPI.ReportController do + use Pleroma.Web, :controller + + alias Pleroma.Activity + alias Pleroma.Web.ActivityPub.Utils + alias Pleroma.Web.AdminAPI.Report + + action_fallback(Pleroma.Web.MastodonAPI.FallbackController) + plug(Pleroma.Web.ApiSpec.CastAndValidate) + plug(Pleroma.Web.Plugs.OAuthScopesPlug, %{scopes: ["read:reports"]}) + + defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaReportOperation + + @doc "GET /api/v0/pleroma/reports" + def index(%{assigns: %{user: user}, body_params: params} = conn, _) do + params = + params + |> Map.put(:actor_id, user.ap_id) + + reports = Utils.get_reports(params, Map.get(params, :page, 1), Map.get(params, :size, 20)) + + render(conn, "index.json", %{reports: reports, for: user}) + end + + @doc "GET /api/v0/pleroma/reports/:id" + def show(%{assigns: %{user: user}} = conn, %{id: id}) do + with %Activity{} = report <- Activity.get_report(id), + true <- report.actor == user.ap_id, + %{} = report_info <- Report.extract_report_info(report) do + render(conn, "show.json", Map.put(report_info, :for, user)) + else + false -> + {:error, :not_found} + + nil -> + {:error, :not_found} + + e -> + {:error, inspect(e)} + end + end +end diff --git a/lib/pleroma/web/pleroma_api/controllers/scrobble_controller.ex b/lib/pleroma/web/pleroma_api/controllers/scrobble_controller.ex new file mode 100644 index 0000000..bf6dc50 --- /dev/null +++ b/lib/pleroma/web/pleroma_api/controllers/scrobble_controller.ex @@ -0,0 +1,52 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.PleromaAPI.ScrobbleController do + use Pleroma.Web, :controller + + import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2] + + alias Pleroma.User + alias Pleroma.Web.ActivityPub.ActivityPub + alias Pleroma.Web.CommonAPI + alias Pleroma.Web.Plugs.OAuthScopesPlug + + plug(Pleroma.Web.ApiSpec.CastAndValidate) + + plug( + OAuthScopesPlug, + %{scopes: ["read"], fallback: :proceed_unauthenticated} when action == :index + ) + + plug(OAuthScopesPlug, %{scopes: ["write"]} when action == :create) + + defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaScrobbleOperation + + def create(%{assigns: %{user: user}, body_params: params} = conn, _) do + with {:ok, activity} <- CommonAPI.listen(user, params) do + render(conn, "show.json", activity: activity, for: user) + else + {:error, message} -> + conn + |> put_status(:bad_request) + |> json(%{"error" => message}) + end + end + + def index(%{assigns: %{user: reading_user}} = conn, %{id: id} = params) do + with %User{} = user <- User.get_cached_by_nickname_or_id(id, for: reading_user) do + params = Map.put(params, :type, ["Listen"]) + + activities = ActivityPub.fetch_user_abstract_activities(user, reading_user, params) + + conn + |> add_link_headers(activities) + |> render("index.json", %{ + activities: activities, + for: reading_user, + as: :activity + }) + end + end +end diff --git a/lib/pleroma/web/pleroma_api/controllers/settings_controller.ex b/lib/pleroma/web/pleroma_api/controllers/settings_controller.ex new file mode 100644 index 0000000..1136575 --- /dev/null +++ b/lib/pleroma/web/pleroma_api/controllers/settings_controller.ex @@ -0,0 +1,79 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.PleromaAPI.SettingsController do + use Pleroma.Web, :controller + + alias Pleroma.Web.Plugs.OAuthScopesPlug + + plug(Pleroma.Web.ApiSpec.CastAndValidate) + + plug( + OAuthScopesPlug, + %{scopes: ["write:accounts"]} when action in [:update] + ) + + plug( + OAuthScopesPlug, + %{scopes: ["read:accounts"]} when action in [:show] + ) + + defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaSettingsOperation + + @doc "GET /api/v1/pleroma/settings/:app" + def show(%{assigns: %{user: user}} = conn, %{app: app} = _params) do + conn + |> json(get_settings(user, app)) + end + + @doc "PATCH /api/v1/pleroma/settings/:app" + def update(%{assigns: %{user: user}, body_params: body_params} = conn, %{app: app} = _params) do + settings = + get_settings(user, app) + |> merge_recursively(body_params) + + with changeset <- + Pleroma.User.update_changeset( + user, + %{pleroma_settings_store: %{app => settings}} + ), + {:ok, _} <- Pleroma.Repo.update(changeset) do + conn + |> json(settings) + end + end + + defp merge_recursively(old, %{} = new) do + old = ensure_object(old) + + Enum.reduce( + new, + old, + fn + {k, nil}, acc -> + Map.drop(acc, [k]) + + {k, %{} = new_child}, acc -> + Map.put(acc, k, merge_recursively(acc[k], new_child)) + + {k, v}, acc -> + Map.put(acc, k, v) + end + ) + end + + defp get_settings(user, app) do + user.pleroma_settings_store + |> Map.get(app, %{}) + |> ensure_object() + end + + defp ensure_object(%{} = object) do + object + end + + defp ensure_object(_) do + %{} + end +end diff --git a/lib/pleroma/web/pleroma_api/controllers/two_factor_authentication_controller.ex b/lib/pleroma/web/pleroma_api/controllers/two_factor_authentication_controller.ex new file mode 100644 index 0000000..e69847b --- /dev/null +++ b/lib/pleroma/web/pleroma_api/controllers/two_factor_authentication_controller.ex @@ -0,0 +1,133 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.PleromaAPI.TwoFactorAuthenticationController do + @moduledoc "The module represents actions to manage MFA" + use Pleroma.Web, :controller + + import Pleroma.Web.ControllerHelper, only: [json_response: 3] + + alias Pleroma.MFA + alias Pleroma.MFA.TOTP + alias Pleroma.Web.CommonAPI.Utils + alias Pleroma.Web.Plugs.OAuthScopesPlug + + plug(OAuthScopesPlug, %{scopes: ["read:security"]} when action in [:settings]) + + plug( + OAuthScopesPlug, + %{scopes: ["write:security"]} when action in [:setup, :confirm, :disable, :backup_codes] + ) + + @doc """ + Gets user multi factor authentication settings + + ## Endpoint + GET /api/pleroma/accounts/mfa + + """ + def settings(%{assigns: %{user: user}} = conn, _params) do + json(conn, %{settings: MFA.mfa_settings(user)}) + end + + @doc """ + Prepare setup mfa method + + ## Endpoint + GET /api/pleroma/accounts/mfa/setup/[:method] + + """ + def setup(%{assigns: %{user: user}} = conn, %{"method" => "totp"} = _params) do + with {:ok, user} <- MFA.setup_totp(user), + %{secret: secret} = _ <- user.multi_factor_authentication_settings.totp do + provisioning_uri = TOTP.provisioning_uri(secret, "#{user.email}") + + json(conn, %{provisioning_uri: provisioning_uri, key: secret}) + else + {:error, message} -> + json_response(conn, :unprocessable_entity, %{error: message}) + end + end + + def setup(conn, _params) do + json_response(conn, :bad_request, %{error: "undefined method"}) + end + + @doc """ + Confirms setup and enable mfa method + + ## Endpoint + POST /api/pleroma/accounts/mfa/confirm/:method + + - params: + `code` - confirmation code + `password` - current password + """ + def confirm( + %{assigns: %{user: user}} = conn, + %{"method" => "totp", "password" => _, "code" => _} = params + ) do + with {:ok, _user} <- Utils.confirm_current_password(user, params["password"]), + {:ok, _user} <- MFA.confirm_totp(user, params) do + json(conn, %{}) + else + {:error, message} -> + json_response(conn, :unprocessable_entity, %{error: message}) + end + end + + def confirm(conn, _) do + json_response(conn, :bad_request, %{error: "undefined mfa method"}) + end + + @doc """ + Disable mfa method and disable mfa if need. + """ + def disable(%{assigns: %{user: user}} = conn, %{"method" => "totp"} = params) do + with {:ok, user} <- Utils.confirm_current_password(user, params["password"]), + {:ok, _user} <- MFA.disable_totp(user) do + json(conn, %{}) + else + {:error, message} -> + json_response(conn, :unprocessable_entity, %{error: message}) + end + end + + def disable(%{assigns: %{user: user}} = conn, %{"method" => "mfa"} = params) do + with {:ok, user} <- Utils.confirm_current_password(user, params["password"]), + {:ok, _user} <- MFA.disable(user) do + json(conn, %{}) + else + {:error, message} -> + json_response(conn, :unprocessable_entity, %{error: message}) + end + end + + def disable(conn, _) do + json_response(conn, :bad_request, %{error: "undefined mfa method"}) + end + + @doc """ + Generates backup codes. + + ## Endpoint + GET /api/pleroma/accounts/mfa/backup_codes + + ## Response + ### Success + `{codes: [codes]}` + + ### Error + `{error: [error_message]}` + + """ + def backup_codes(%{assigns: %{user: user}} = conn, _params) do + with {:ok, codes} <- MFA.generate_backup_codes(user) do + json(conn, %{codes: codes}) + else + {:error, message} -> + json_response(conn, :unprocessable_entity, %{error: message}) + end + end +end diff --git a/lib/pleroma/web/pleroma_api/controllers/user_import_controller.ex b/lib/pleroma/web/pleroma_api/controllers/user_import_controller.ex new file mode 100644 index 0000000..90428a5 --- /dev/null +++ b/lib/pleroma/web/pleroma_api/controllers/user_import_controller.ex @@ -0,0 +1,61 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.PleromaAPI.UserImportController do + use Pleroma.Web, :controller + + require Logger + + alias Pleroma.User + alias Pleroma.Web.ApiSpec + alias Pleroma.Web.Plugs.OAuthScopesPlug + + plug(OAuthScopesPlug, %{scopes: ["follow", "write:follows"]} when action == :follow) + plug(OAuthScopesPlug, %{scopes: ["follow", "write:blocks"]} when action == :blocks) + plug(OAuthScopesPlug, %{scopes: ["follow", "write:mutes"]} when action == :mutes) + + plug(Pleroma.Web.ApiSpec.CastAndValidate) + defdelegate open_api_operation(action), to: ApiSpec.UserImportOperation + + def follow(%{body_params: %{list: %Plug.Upload{path: path}}} = conn, _) do + follow(%Plug.Conn{conn | body_params: %{list: File.read!(path)}}, %{}) + end + + def follow(%{assigns: %{user: follower}, body_params: %{list: list}} = conn, _) do + identifiers = + list + |> String.split("\n") + |> Enum.map(&(&1 |> String.split(",") |> List.first())) + |> List.delete("Account address") + |> Enum.map(&(&1 |> String.trim() |> String.trim_leading("@"))) + |> Enum.reject(&(&1 == "")) + + User.Import.follow_import(follower, identifiers) + json(conn, "job started") + end + + def blocks(%{body_params: %{list: %Plug.Upload{path: path}}} = conn, _) do + blocks(%Plug.Conn{conn | body_params: %{list: File.read!(path)}}, %{}) + end + + def blocks(%{assigns: %{user: blocker}, body_params: %{list: list}} = conn, _) do + User.Import.blocks_import(blocker, prepare_user_identifiers(list)) + json(conn, "job started") + end + + def mutes(%{body_params: %{list: %Plug.Upload{path: path}}} = conn, _) do + mutes(%Plug.Conn{conn | body_params: %{list: File.read!(path)}}, %{}) + end + + def mutes(%{assigns: %{user: user}, body_params: %{list: list}} = conn, _) do + User.Import.mutes_import(user, prepare_user_identifiers(list)) + json(conn, "job started") + end + + defp prepare_user_identifiers(list) do + list + |> String.split() + |> Enum.map(&String.trim_leading(&1, "@")) + end +end |
