From 3da65292b389c1f1edeff03fd5097579721fb681 Mon Sep 17 00:00:00 2001 From: lain Date: Mon, 26 Aug 2019 14:34:52 -0500 Subject: Transmogrifier: Fix follow handling when the actor is an object. --- lib/pleroma/object.ex | 4 ++++ lib/pleroma/web/activity_pub/transmogrifier.ex | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/object.ex b/lib/pleroma/object.ex index c8d339c19..468549c87 100644 --- a/lib/pleroma/object.ex +++ b/lib/pleroma/object.ex @@ -230,4 +230,8 @@ defmodule Pleroma.Object do _ -> :noop end end + + def get_ap_id(%{"id" => id}), do: id + def get_ap_id(id) when is_binary(id), do: id + def get_ap_id(_), do: {:error, "Object is not a string and has no id."} end diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index 36340a3a1..6c4259c02 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -464,8 +464,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do %{"type" => "Follow", "object" => followed, "actor" => follower, "id" => id} = data, _options ) do - with %User{local: true} = followed <- User.get_cached_by_ap_id(followed), - {:ok, %User{} = follower} <- User.get_or_fetch_by_ap_id(follower), + with %User{local: true} = followed <- User.get_cached_by_ap_id(Object.get_ap_id(followed)), + {:ok, %User{} = follower} <- User.get_or_fetch_by_ap_id(Object.get_ap_id(follower)), {:ok, activity} <- ActivityPub.follow(follower, followed, id, false) do with deny_follow_blocked <- Pleroma.Config.get([:user, :deny_follow_blocked]), {_, false} <- {:user_blocked, User.blocks?(followed, follower) && deny_follow_blocked}, -- cgit v1.2.3 From eb1739c59699754297149c92ea3d03ec688ae16a Mon Sep 17 00:00:00 2001 From: rinpatch Date: Tue, 27 Aug 2019 12:29:19 +0300 Subject: Remove most of TwitterAPIController --- lib/pleroma/web/router.ex | 106 --- .../web/twitter_api/twitter_api_controller.ex | 763 +-------------------- 2 files changed, 6 insertions(+), 863 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 1ad33630c..53728e298 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -482,53 +482,12 @@ defmodule Pleroma.Web.Router do scope "/api", Pleroma.Web do pipe_through(:api) - post("/account/register", TwitterAPI.Controller, :register) - post("/account/password_reset", TwitterAPI.Controller, :password_reset) - - post("/account/resend_confirmation_email", TwitterAPI.Controller, :resend_confirmation_email) - get( "/account/confirm_email/:user_id/:token", TwitterAPI.Controller, :confirm_email, as: :confirm_email ) - - scope [] do - pipe_through(:oauth_read_or_public) - - get("/statuses/user_timeline", TwitterAPI.Controller, :user_timeline) - get("/qvitter/statuses/user_timeline", TwitterAPI.Controller, :user_timeline) - get("/users/show", TwitterAPI.Controller, :show_user) - - get("/statuses/followers", TwitterAPI.Controller, :followers) - get("/statuses/friends", TwitterAPI.Controller, :friends) - get("/statuses/blocks", TwitterAPI.Controller, :blocks) - get("/statuses/show/:id", TwitterAPI.Controller, :fetch_status) - get("/statusnet/conversation/:id", TwitterAPI.Controller, :fetch_conversation) - - get("/search", TwitterAPI.Controller, :search) - get("/statusnet/tags/timeline/:tag", TwitterAPI.Controller, :public_and_external_timeline) - end - end - - scope "/api", Pleroma.Web do - pipe_through([:api, :oauth_read_or_public]) - - get("/statuses/public_timeline", TwitterAPI.Controller, :public_timeline) - - get( - "/statuses/public_and_external_timeline", - TwitterAPI.Controller, - :public_and_external_timeline - ) - - get("/statuses/networkpublic_timeline", TwitterAPI.Controller, :public_and_external_timeline) - end - - scope "/api", Pleroma.Web, as: :twitter_api_search do - pipe_through([:api, :oauth_read_or_public]) - get("/pleroma/search_user", TwitterAPI.Controller, :search_user) end scope "/api", Pleroma.Web, as: :authenticated_twitter_api do @@ -536,71 +495,6 @@ defmodule Pleroma.Web.Router do get("/oauth_tokens", TwitterAPI.Controller, :oauth_tokens) delete("/oauth_tokens/:id", TwitterAPI.Controller, :revoke_token) - - scope [] do - pipe_through(:oauth_read) - - get("/account/verify_credentials", TwitterAPI.Controller, :verify_credentials) - post("/account/verify_credentials", TwitterAPI.Controller, :verify_credentials) - - get("/statuses/home_timeline", TwitterAPI.Controller, :friends_timeline) - get("/statuses/friends_timeline", TwitterAPI.Controller, :friends_timeline) - get("/statuses/mentions", TwitterAPI.Controller, :mentions_timeline) - get("/statuses/mentions_timeline", TwitterAPI.Controller, :mentions_timeline) - get("/statuses/dm_timeline", TwitterAPI.Controller, :dm_timeline) - get("/qvitter/statuses/notifications", TwitterAPI.Controller, :notifications) - - get("/pleroma/friend_requests", TwitterAPI.Controller, :friend_requests) - - get("/friends/ids", TwitterAPI.Controller, :friends_ids) - get("/friendships/no_retweets/ids", TwitterAPI.Controller, :empty_array) - - get("/mutes/users/ids", TwitterAPI.Controller, :empty_array) - get("/qvitter/mutes", TwitterAPI.Controller, :raw_empty_array) - - get("/externalprofile/show", TwitterAPI.Controller, :external_profile) - - post("/qvitter/statuses/notifications/read", TwitterAPI.Controller, :notifications_read) - end - - scope [] do - pipe_through(:oauth_write) - - post("/account/update_profile", TwitterAPI.Controller, :update_profile) - post("/account/update_profile_banner", TwitterAPI.Controller, :update_banner) - post("/qvitter/update_background_image", TwitterAPI.Controller, :update_background) - - post("/statuses/update", TwitterAPI.Controller, :status_update) - post("/statuses/retweet/:id", TwitterAPI.Controller, :retweet) - post("/statuses/unretweet/:id", TwitterAPI.Controller, :unretweet) - post("/statuses/destroy/:id", TwitterAPI.Controller, :delete_post) - - post("/statuses/pin/:id", TwitterAPI.Controller, :pin) - post("/statuses/unpin/:id", TwitterAPI.Controller, :unpin) - - post("/statusnet/media/upload", TwitterAPI.Controller, :upload) - post("/media/upload", TwitterAPI.Controller, :upload_json) - post("/media/metadata/create", TwitterAPI.Controller, :update_media) - - post("/favorites/create/:id", TwitterAPI.Controller, :favorite) - post("/favorites/create", TwitterAPI.Controller, :favorite) - post("/favorites/destroy/:id", TwitterAPI.Controller, :unfavorite) - - post("/qvitter/update_avatar", TwitterAPI.Controller, :update_avatar) - end - - scope [] do - pipe_through(:oauth_follow) - - post("/pleroma/friendships/approve", TwitterAPI.Controller, :approve_friend_request) - post("/pleroma/friendships/deny", TwitterAPI.Controller, :deny_friend_request) - - post("/friendships/create", TwitterAPI.Controller, :follow) - post("/friendships/destroy", TwitterAPI.Controller, :unfollow) - - post("/blocks/create", TwitterAPI.Controller, :block) - post("/blocks/destroy", TwitterAPI.Controller, :unblock) - end end pipeline :ap_service_actor do diff --git a/lib/pleroma/web/twitter_api/twitter_api_controller.ex b/lib/pleroma/web/twitter_api/twitter_api_controller.ex index 5dfab6a6c..1c3b11a57 100644 --- a/lib/pleroma/web/twitter_api/twitter_api_controller.ex +++ b/lib/pleroma/web/twitter_api/twitter_api_controller.ex @@ -5,448 +5,15 @@ defmodule Pleroma.Web.TwitterAPI.Controller do use Pleroma.Web, :controller - import Pleroma.Web.ControllerHelper, only: [json_response: 3] - alias Ecto.Changeset - alias Pleroma.Activity - alias Pleroma.Formatter - alias Pleroma.Notification - alias Pleroma.Object - alias Pleroma.Repo alias Pleroma.User - alias Pleroma.Web.ActivityPub.ActivityPub - alias Pleroma.Web.ActivityPub.Visibility - alias Pleroma.Web.CommonAPI - alias Pleroma.Web.CommonAPI.Utils alias Pleroma.Web.OAuth.Token - alias Pleroma.Web.TwitterAPI.ActivityView - alias Pleroma.Web.TwitterAPI.NotificationView alias Pleroma.Web.TwitterAPI.TokenView - alias Pleroma.Web.TwitterAPI.TwitterAPI - alias Pleroma.Web.TwitterAPI.UserView require Logger - plug(Pleroma.Plugs.RateLimiter, :password_reset when action == :password_reset) - plug(:only_if_public_instance when action in [:public_timeline, :public_and_external_timeline]) action_fallback(:errors) - def verify_credentials(%{assigns: %{user: user}} = conn, _params) do - token = Phoenix.Token.sign(conn, "user socket", user.id) - - conn - |> put_view(UserView) - |> render("show.json", %{user: user, token: token, for: user}) - end - - def status_update(%{assigns: %{user: user}} = conn, %{"status" => _} = status_data) do - with media_ids <- extract_media_ids(status_data), - {:ok, activity} <- - TwitterAPI.create_status(user, Map.put(status_data, "media_ids", media_ids)) do - conn - |> json(ActivityView.render("activity.json", activity: activity, for: user)) - else - _ -> empty_status_reply(conn) - end - end - - def status_update(conn, _status_data) do - empty_status_reply(conn) - end - - defp empty_status_reply(conn) do - bad_request_reply(conn, "Client must provide a 'status' parameter with a value.") - end - - defp extract_media_ids(status_data) do - with media_ids when not is_nil(media_ids) <- status_data["media_ids"], - split_ids <- String.split(media_ids, ","), - clean_ids <- Enum.reject(split_ids, fn id -> String.length(id) == 0 end) do - clean_ids - else - _e -> [] - end - end - - def public_and_external_timeline(%{assigns: %{user: user}} = conn, params) do - params = - params - |> Map.put("type", ["Create", "Announce"]) - |> Map.put("blocking_user", user) - - activities = ActivityPub.fetch_public_activities(params) - - conn - |> put_view(ActivityView) - |> render("index.json", %{activities: activities, for: user}) - end - - def public_timeline(%{assigns: %{user: user}} = conn, params) do - params = - params - |> Map.put("type", ["Create", "Announce"]) - |> Map.put("local_only", true) - |> Map.put("blocking_user", user) - - activities = ActivityPub.fetch_public_activities(params) - - conn - |> put_view(ActivityView) - |> render("index.json", %{activities: activities, for: user}) - end - - def friends_timeline(%{assigns: %{user: user}} = conn, params) do - params = - params - |> Map.put("type", ["Create", "Announce", "Follow", "Like"]) - |> Map.put("blocking_user", user) - |> Map.put("user", user) - - activities = ActivityPub.fetch_activities([user.ap_id | user.following], params) - - conn - |> put_view(ActivityView) - |> render("index.json", %{activities: activities, for: user}) - end - - def show_user(conn, params) do - for_user = conn.assigns.user - - with {:ok, shown} <- TwitterAPI.get_user(params), - true <- - User.auth_active?(shown) || - (for_user && (for_user.id == shown.id || User.superuser?(for_user))) do - params = - if for_user do - %{user: shown, for: for_user} - else - %{user: shown} - end - - conn - |> put_view(UserView) - |> render("show.json", params) - else - {:error, msg} -> - bad_request_reply(conn, msg) - - false -> - conn - |> put_status(404) - |> json(%{error: "Unconfirmed user"}) - end - end - - def user_timeline(%{assigns: %{user: user}} = conn, params) do - case TwitterAPI.get_user(user, params) do - {:ok, target_user} -> - # Twitter and ActivityPub use a different name and sense for this parameter. - {include_rts, params} = Map.pop(params, "include_rts") - - params = - case include_rts do - x when x == "false" or x == "0" -> Map.put(params, "exclude_reblogs", "true") - _ -> params - end - - activities = ActivityPub.fetch_user_activities(target_user, user, params) - - conn - |> put_view(ActivityView) - |> render("index.json", %{activities: activities, for: user}) - - {:error, msg} -> - bad_request_reply(conn, msg) - end - end - - def mentions_timeline(%{assigns: %{user: user}} = conn, params) do - params = - params - |> Map.put("type", ["Create", "Announce", "Follow", "Like"]) - |> Map.put("blocking_user", user) - |> Map.put(:visibility, ~w[unlisted public private]) - - activities = ActivityPub.fetch_activities([user.ap_id], params) - - conn - |> put_view(ActivityView) - |> render("index.json", %{activities: activities, for: user}) - end - - def dm_timeline(%{assigns: %{user: user}} = conn, params) do - params = - params - |> Map.put("type", "Create") - |> Map.put("blocking_user", user) - |> Map.put("user", user) - |> Map.put(:visibility, "direct") - |> Map.put(:order, :desc) - - activities = - ActivityPub.fetch_activities_query([user.ap_id], params) - |> Repo.all() - - conn - |> put_view(ActivityView) - |> render("index.json", %{activities: activities, for: user}) - end - - def notifications(%{assigns: %{user: user}} = conn, params) do - params = - if Map.has_key?(params, "with_muted") do - Map.put(params, :with_muted, params["with_muted"] in [true, "True", "true", "1"]) - else - params - end - - notifications = Notification.for_user(user, params) - - conn - |> put_view(NotificationView) - |> render("notification.json", %{notifications: notifications, for: user}) - end - - def notifications_read(%{assigns: %{user: user}} = conn, %{"latest_id" => latest_id} = params) do - Notification.set_read_up_to(user, latest_id) - - notifications = Notification.for_user(user, params) - - conn - |> put_view(NotificationView) - |> render("notification.json", %{notifications: notifications, for: user}) - end - - def notifications_read(%{assigns: %{user: _user}} = conn, _) do - bad_request_reply(conn, "You need to specify latest_id") - end - - def follow(%{assigns: %{user: user}} = conn, params) do - case TwitterAPI.follow(user, params) do - {:ok, user, followed, _activity} -> - conn - |> put_view(UserView) - |> render("show.json", %{user: followed, for: user}) - - {:error, msg} -> - forbidden_json_reply(conn, msg) - end - end - - def block(%{assigns: %{user: user}} = conn, params) do - case TwitterAPI.block(user, params) do - {:ok, user, blocked} -> - conn - |> put_view(UserView) - |> render("show.json", %{user: blocked, for: user}) - - {:error, msg} -> - forbidden_json_reply(conn, msg) - end - end - - def unblock(%{assigns: %{user: user}} = conn, params) do - case TwitterAPI.unblock(user, params) do - {:ok, user, blocked} -> - conn - |> put_view(UserView) - |> render("show.json", %{user: blocked, for: user}) - - {:error, msg} -> - forbidden_json_reply(conn, msg) - end - end - - def delete_post(%{assigns: %{user: user}} = conn, %{"id" => id}) do - with {:ok, activity} <- TwitterAPI.delete(user, id) do - conn - |> put_view(ActivityView) - |> render("activity.json", %{activity: activity, for: user}) - end - end - - def unfollow(%{assigns: %{user: user}} = conn, params) do - case TwitterAPI.unfollow(user, params) do - {:ok, user, unfollowed} -> - conn - |> put_view(UserView) - |> render("show.json", %{user: unfollowed, for: user}) - - {:error, msg} -> - forbidden_json_reply(conn, msg) - end - end - - def fetch_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do - with %Activity{} = activity <- Activity.get_by_id(id), - true <- Visibility.visible_for_user?(activity, user) do - conn - |> put_view(ActivityView) - |> render("activity.json", %{activity: activity, for: user}) - end - end - - def fetch_conversation(%{assigns: %{user: user}} = conn, %{"id" => id}) do - with context when is_binary(context) <- Utils.conversation_id_to_context(id), - activities <- - ActivityPub.fetch_activities_for_context(context, %{ - "blocking_user" => user, - "user" => user - }) do - conn - |> put_view(ActivityView) - |> render("index.json", %{activities: activities, for: user}) - end - end - - @doc """ - Updates metadata of uploaded media object. - Derived from [Twitter API endpoint](https://developer.twitter.com/en/docs/media/upload-media/api-reference/post-media-metadata-create). - """ - def update_media(%{assigns: %{user: user}} = conn, %{"media_id" => id} = data) do - object = Repo.get(Object, id) - description = get_in(data, ["alt_text", "text"]) || data["name"] || data["description"] - - {conn, status, response_body} = - cond do - !object -> - {halt(conn), :not_found, ""} - - !Object.authorize_mutation(object, user) -> - {halt(conn), :forbidden, "You can only update your own uploads."} - - !is_binary(description) -> - {conn, :not_modified, ""} - - true -> - new_data = Map.put(object.data, "name", description) - - {:ok, _} = - object - |> Object.change(%{data: new_data}) - |> Repo.update() - - {conn, :no_content, ""} - end - - conn - |> put_status(status) - |> json(response_body) - end - - def upload(%{assigns: %{user: user}} = conn, %{"media" => media}) do - response = TwitterAPI.upload(media, user) - - conn - |> put_resp_content_type("application/atom+xml") - |> send_resp(200, response) - end - - def upload_json(%{assigns: %{user: user}} = conn, %{"media" => media}) do - response = TwitterAPI.upload(media, user, "json") - - conn - |> json_reply(200, response) - end - - def get_by_id_or_ap_id(id) do - activity = Activity.get_by_id(id) || Activity.get_create_by_object_ap_id(id) - - if activity.data["type"] == "Create" do - activity - else - Activity.get_create_by_object_ap_id(activity.data["object"]) - end - end - - def favorite(%{assigns: %{user: user}} = conn, %{"id" => id}) do - with {:ok, activity} <- TwitterAPI.fav(user, id) do - conn - |> put_view(ActivityView) - |> render("activity.json", %{activity: activity, for: user}) - else - _ -> json_reply(conn, 400, Jason.encode!(%{})) - end - end - - def unfavorite(%{assigns: %{user: user}} = conn, %{"id" => id}) do - with {:ok, activity} <- TwitterAPI.unfav(user, id) do - conn - |> put_view(ActivityView) - |> render("activity.json", %{activity: activity, for: user}) - else - _ -> json_reply(conn, 400, Jason.encode!(%{})) - end - end - - def retweet(%{assigns: %{user: user}} = conn, %{"id" => id}) do - with {:ok, activity} <- TwitterAPI.repeat(user, id) do - conn - |> put_view(ActivityView) - |> render("activity.json", %{activity: activity, for: user}) - else - _ -> json_reply(conn, 400, Jason.encode!(%{})) - end - end - - def unretweet(%{assigns: %{user: user}} = conn, %{"id" => id}) do - with {:ok, activity} <- TwitterAPI.unrepeat(user, id) do - conn - |> put_view(ActivityView) - |> render("activity.json", %{activity: activity, for: user}) - else - _ -> json_reply(conn, 400, Jason.encode!(%{})) - end - end - - def pin(%{assigns: %{user: user}} = conn, %{"id" => id}) do - with {:ok, activity} <- TwitterAPI.pin(user, id) do - conn - |> put_view(ActivityView) - |> render("activity.json", %{activity: activity, for: user}) - else - {:error, message} -> bad_request_reply(conn, message) - err -> err - end - end - - def unpin(%{assigns: %{user: user}} = conn, %{"id" => id}) do - with {:ok, activity} <- TwitterAPI.unpin(user, id) do - conn - |> put_view(ActivityView) - |> render("activity.json", %{activity: activity, for: user}) - else - {:error, message} -> bad_request_reply(conn, message) - err -> err - end - end - - def register(conn, params) do - with {:ok, user} <- TwitterAPI.register_user(params) do - conn - |> put_view(UserView) - |> render("show.json", %{user: user}) - else - {:error, errors} -> - conn - |> json_reply(400, Jason.encode!(errors)) - end - end - - def password_reset(conn, params) do - nickname_or_email = params["email"] || params["nickname"] - - with {:ok, _} <- TwitterAPI.password_reset(nickname_or_email) do - json_response(conn, :no_content, "") - else - {:error, "unknown user"} -> - send_resp(conn, :not_found, "") - - {:error, _} -> - send_resp(conn, :bad_request, "") - end - end - def confirm_email(conn, %{"user_id" => uid, "token" => token}) do with %User{} = user <- User.get_cached_by_id(uid), true <- user.local, @@ -460,147 +27,6 @@ defmodule Pleroma.Web.TwitterAPI.Controller do end end - def resend_confirmation_email(conn, params) do - nickname_or_email = params["email"] || params["nickname"] - - with %User{} = user <- User.get_by_nickname_or_email(nickname_or_email), - {:ok, _} <- User.try_send_confirmation_email(user) do - conn - |> json_response(:no_content, "") - end - end - - def update_avatar(%{assigns: %{user: user}} = conn, %{"img" => ""}) do - change = Changeset.change(user, %{avatar: nil}) - {:ok, user} = User.update_and_set_cache(change) - CommonAPI.update(user) - - conn - |> put_view(UserView) - |> render("show.json", %{user: user, for: user}) - end - - def update_avatar(%{assigns: %{user: user}} = conn, params) do - {:ok, object} = ActivityPub.upload(params, type: :avatar) - change = Changeset.change(user, %{avatar: object.data}) - {:ok, user} = User.update_and_set_cache(change) - CommonAPI.update(user) - - conn - |> put_view(UserView) - |> render("show.json", %{user: user, for: user}) - end - - def update_banner(%{assigns: %{user: user}} = conn, %{"banner" => ""}) do - with new_info <- %{"banner" => %{}}, - info_cng <- User.Info.profile_update(user.info, new_info), - changeset <- Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_cng), - {:ok, user} <- User.update_and_set_cache(changeset) do - CommonAPI.update(user) - response = %{url: nil} |> Jason.encode!() - - conn - |> json_reply(200, response) - end - end - - def update_banner(%{assigns: %{user: user}} = conn, params) do - with {:ok, object} <- ActivityPub.upload(%{"img" => params["banner"]}, type: :banner), - new_info <- %{"banner" => object.data}, - info_cng <- User.Info.profile_update(user.info, new_info), - changeset <- Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_cng), - {:ok, user} <- User.update_and_set_cache(changeset) do - CommonAPI.update(user) - %{"url" => [%{"href" => href} | _]} = object.data - response = %{url: href} |> Jason.encode!() - - conn - |> json_reply(200, response) - end - end - - def update_background(%{assigns: %{user: user}} = conn, %{"img" => ""}) do - with new_info <- %{"background" => %{}}, - info_cng <- User.Info.profile_update(user.info, new_info), - changeset <- Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_cng), - {:ok, _user} <- User.update_and_set_cache(changeset) do - response = %{url: nil} |> Jason.encode!() - - conn - |> json_reply(200, response) - end - end - - def update_background(%{assigns: %{user: user}} = conn, params) do - with {:ok, object} <- ActivityPub.upload(params, type: :background), - new_info <- %{"background" => object.data}, - info_cng <- User.Info.profile_update(user.info, new_info), - changeset <- Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_cng), - {:ok, _user} <- User.update_and_set_cache(changeset) do - %{"url" => [%{"href" => href} | _]} = object.data - response = %{url: href} |> Jason.encode!() - - conn - |> json_reply(200, response) - end - end - - def external_profile(%{assigns: %{user: current_user}} = conn, %{"profileurl" => uri}) do - with {:ok, user_map} <- TwitterAPI.get_external_profile(current_user, uri), - response <- Jason.encode!(user_map) do - conn - |> json_reply(200, response) - else - _e -> - conn - |> put_status(404) - |> json(%{error: "Can't find user"}) - end - end - - def followers(%{assigns: %{user: for_user}} = conn, params) do - {:ok, page} = Ecto.Type.cast(:integer, params["page"] || 1) - - with {:ok, user} <- TwitterAPI.get_user(for_user, params), - {:ok, followers} <- User.get_followers(user, page) do - followers = - cond do - for_user && user.id == for_user.id -> followers - user.info.hide_followers -> [] - true -> followers - end - - conn - |> put_view(UserView) - |> render("index.json", %{users: followers, for: conn.assigns[:user]}) - else - _e -> bad_request_reply(conn, "Can't get followers") - end - end - - def friends(%{assigns: %{user: for_user}} = conn, params) do - {:ok, page} = Ecto.Type.cast(:integer, params["page"] || 1) - {:ok, export} = Ecto.Type.cast(:boolean, params["all"] || false) - - page = if export, do: nil, else: page - - with {:ok, user} <- TwitterAPI.get_user(conn.assigns[:user], params), - {:ok, friends} <- User.get_friends(user, page) do - friends = - cond do - for_user && user.id == for_user.id -> friends - user.info.hide_follows -> [] - true -> friends - end - - conn - |> put_view(UserView) - |> render("index.json", %{users: friends, for: conn.assigns[:user]}) - else - _e -> bad_request_reply(conn, "Can't get friends") - end - end - def oauth_tokens(%{assigns: %{user: user}} = conn, _params) do with oauth_tokens <- Token.get_user_tokens(user) do conn @@ -615,189 +41,6 @@ defmodule Pleroma.Web.TwitterAPI.Controller do json_reply(conn, 201, "") end - def blocks(%{assigns: %{user: user}} = conn, _params) do - with blocked_users <- User.blocked_users(user) do - conn - |> put_view(UserView) - |> render("index.json", %{users: blocked_users, for: user}) - end - end - - def friend_requests(conn, params) do - with {:ok, user} <- TwitterAPI.get_user(conn.assigns[:user], params), - {:ok, friend_requests} <- User.get_follow_requests(user) do - conn - |> put_view(UserView) - |> render("index.json", %{users: friend_requests, for: conn.assigns[:user]}) - else - _e -> bad_request_reply(conn, "Can't get friend requests") - end - end - - def approve_friend_request(conn, %{"user_id" => uid} = _params) do - with followed <- conn.assigns[:user], - %User{} = follower <- User.get_cached_by_id(uid), - {:ok, follower} <- CommonAPI.accept_follow_request(follower, followed) do - conn - |> put_view(UserView) - |> render("show.json", %{user: follower, for: followed}) - else - e -> bad_request_reply(conn, "Can't approve user: #{inspect(e)}") - end - end - - def deny_friend_request(conn, %{"user_id" => uid} = _params) do - with followed <- conn.assigns[:user], - %User{} = follower <- User.get_cached_by_id(uid), - {:ok, follower} <- CommonAPI.reject_follow_request(follower, followed) do - conn - |> put_view(UserView) - |> render("show.json", %{user: follower, for: followed}) - else - e -> bad_request_reply(conn, "Can't deny user: #{inspect(e)}") - end - end - - def friends_ids(%{assigns: %{user: user}} = conn, _params) do - with {:ok, friends} <- User.get_friends(user) do - ids = - friends - |> Enum.map(fn x -> x.id end) - |> Jason.encode!() - - json(conn, ids) - else - _e -> bad_request_reply(conn, "Can't get friends") - end - end - - def empty_array(conn, _params) do - json(conn, Jason.encode!([])) - end - - def raw_empty_array(conn, _params) do - json(conn, []) - end - - defp build_info_cng(user, params) do - info_params = - [ - "no_rich_text", - "locked", - "hide_followers", - "hide_follows", - "hide_favorites", - "show_role", - "skip_thread_containment" - ] - |> Enum.reduce(%{}, fn key, res -> - if value = params[key] do - Map.put(res, key, value == "true") - else - res - end - end) - - info_params = - if value = params["default_scope"] do - Map.put(info_params, "default_scope", value) - else - info_params - end - - User.Info.profile_update(user.info, info_params) - end - - defp parse_profile_bio(user, params) do - if bio = params["description"] do - emojis_text = (params["description"] || "") <> " " <> (params["name"] || "") - - emojis = - ((user.info.emoji || []) ++ Formatter.get_emoji_map(emojis_text)) - |> Enum.dedup() - - user_info = - user.info - |> Map.put( - "emoji", - emojis - ) - - params - |> Map.put("bio", User.parse_bio(bio, user)) - |> Map.put("info", user_info) - else - params - end - end - - def update_profile(%{assigns: %{user: user}} = conn, params) do - params = parse_profile_bio(user, params) - info_cng = build_info_cng(user, params) - - with changeset <- User.update_changeset(user, params), - changeset <- Ecto.Changeset.put_embed(changeset, :info, info_cng), - {:ok, user} <- User.update_and_set_cache(changeset) do - CommonAPI.update(user) - - conn - |> put_view(UserView) - |> render("user.json", %{user: user, for: user}) - else - error -> - Logger.debug("Can't update user: #{inspect(error)}") - bad_request_reply(conn, "Can't update user") - end - end - - def search(%{assigns: %{user: user}} = conn, %{"q" => _query} = params) do - activities = TwitterAPI.search(user, params) - - conn - |> put_view(ActivityView) - |> render("index.json", %{activities: activities, for: user}) - end - - def search_user(%{assigns: %{user: user}} = conn, %{"query" => query}) do - users = User.search(query, resolve: true, for_user: user) - - conn - |> put_view(UserView) - |> render("index.json", %{users: users, for: user}) - end - - defp bad_request_reply(conn, error_message) do - json = error_json(conn, error_message) - json_reply(conn, 400, json) - end - - defp json_reply(conn, status, json) do - conn - |> put_resp_content_type("application/json") - |> send_resp(status, json) - end - - defp forbidden_json_reply(conn, error_message) do - json = error_json(conn, error_message) - json_reply(conn, 403, json) - end - - def only_if_public_instance(%{assigns: %{user: %User{}}} = conn, _), do: conn - - def only_if_public_instance(conn, _) do - if Pleroma.Config.get([:instance, :public]) do - conn - else - conn - |> forbidden_json_reply("Invalid credentials.") - |> halt() - end - end - - defp error_json(conn, error_message) do - %{"error" => error_message, "request" => conn.request_path} |> Jason.encode!() - end - def errors(conn, {:param_cast, _}) do conn |> put_status(400) @@ -809,4 +52,10 @@ defmodule Pleroma.Web.TwitterAPI.Controller do |> put_status(500) |> json("Something went wrong") end + + defp json_reply(conn, status, json) do + conn + |> put_resp_content_type("application/json") + |> send_resp(status, json) + end end -- cgit v1.2.3 From c30cc039e423e8f31d0222747e301514b7d0dd9e Mon Sep 17 00:00:00 2001 From: lain Date: Tue, 27 Aug 2019 12:22:30 -0500 Subject: Transmogrifier: Use Containment.get_actor to get actors. --- lib/pleroma/object.ex | 4 ---- lib/pleroma/web/activity_pub/transmogrifier.ex | 6 ++++-- 2 files changed, 4 insertions(+), 6 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/object.ex b/lib/pleroma/object.ex index 468549c87..c8d339c19 100644 --- a/lib/pleroma/object.ex +++ b/lib/pleroma/object.ex @@ -230,8 +230,4 @@ defmodule Pleroma.Object do _ -> :noop end end - - def get_ap_id(%{"id" => id}), do: id - def get_ap_id(id) when is_binary(id), do: id - def get_ap_id(_), do: {:error, "Object is not a string and has no id."} end diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index 6c4259c02..468961bd0 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -464,8 +464,10 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do %{"type" => "Follow", "object" => followed, "actor" => follower, "id" => id} = data, _options ) do - with %User{local: true} = followed <- User.get_cached_by_ap_id(Object.get_ap_id(followed)), - {:ok, %User{} = follower} <- User.get_or_fetch_by_ap_id(Object.get_ap_id(follower)), + with %User{local: true} = followed <- + User.get_cached_by_ap_id(Containment.get_actor(%{"actor" => followed})), + {:ok, %User{} = follower} <- + User.get_or_fetch_by_ap_id(Containment.get_actor(%{"actor" => follower})), {:ok, activity} <- ActivityPub.follow(follower, followed, id, false) do with deny_follow_blocked <- Pleroma.Config.get([:user, :deny_follow_blocked]), {_, false} <- {:user_blocked, User.blocks?(followed, follower) && deny_follow_blocked}, -- cgit v1.2.3 From 7853b3f17d3b57d7ac91bc909a57143674f57272 Mon Sep 17 00:00:00 2001 From: feld Date: Fri, 30 Aug 2019 00:38:03 +0000 Subject: Fix AntiFollowbotPolicy when trying to follow a relay --- lib/pleroma/web/activity_pub/mrf/anti_followbot_policy.ex | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/activity_pub/mrf/anti_followbot_policy.ex b/lib/pleroma/web/activity_pub/mrf/anti_followbot_policy.ex index de1eb4aa5..b3547ecd4 100644 --- a/lib/pleroma/web/activity_pub/mrf/anti_followbot_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/anti_followbot_policy.ex @@ -25,11 +25,15 @@ defmodule Pleroma.Web.ActivityPub.MRF.AntiFollowbotPolicy do defp score_displayname(_), do: 0.0 defp determine_if_followbot(%User{nickname: nickname, name: displayname}) do - # nickname will always be a binary string because it's generated by Pleroma. + # nickname will be a binary string except when following a relay nick_score = - nickname - |> String.downcase() - |> score_nickname() + if is_binary(nickname) do + nickname + |> String.downcase() + |> score_nickname() + else + 0.0 + end # displayname will either be a binary string or nil, if a displayname isn't set. name_score = -- cgit v1.2.3 From 90c2dae9a4d5fd7e7c1f0d0f532ce95fbc4c69f9 Mon Sep 17 00:00:00 2001 From: rinpatch Date: Sat, 31 Aug 2019 10:20:34 +0300 Subject: Remove most of Pleroma.Web.TwitterAPI.TwitterAPI --- lib/pleroma/web/twitter_api/twitter_api.ex | 195 ----------------------------- 1 file changed, 195 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/twitter_api/twitter_api.ex b/lib/pleroma/web/twitter_api/twitter_api.ex index 80082ea84..8eda762c7 100644 --- a/lib/pleroma/web/twitter_api/twitter_api.ex +++ b/lib/pleroma/web/twitter_api/twitter_api.ex @@ -3,133 +3,14 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.TwitterAPI.TwitterAPI do - alias Pleroma.Activity alias Pleroma.Emails.Mailer alias Pleroma.Emails.UserEmail alias Pleroma.Repo alias Pleroma.User alias Pleroma.UserInviteToken - alias Pleroma.Web.ActivityPub.ActivityPub - alias Pleroma.Web.CommonAPI - alias Pleroma.Web.TwitterAPI.UserView - - import Ecto.Query require Pleroma.Constants - def create_status(%User{} = user, %{"status" => _} = data) do - CommonAPI.post(user, data) - end - - def delete(%User{} = user, id) do - with %Activity{data: %{"type" => _type}} <- Activity.get_by_id(id), - {:ok, activity} <- CommonAPI.delete(id, user) do - {:ok, activity} - end - end - - def follow(%User{} = follower, params) do - with {:ok, %User{} = followed} <- get_user(params) do - CommonAPI.follow(follower, followed) - end - end - - def unfollow(%User{} = follower, params) do - with {:ok, %User{} = unfollowed} <- get_user(params), - {:ok, follower} <- CommonAPI.unfollow(follower, unfollowed) do - {:ok, follower, unfollowed} - end - end - - def block(%User{} = blocker, params) do - with {:ok, %User{} = blocked} <- get_user(params), - {:ok, blocker} <- User.block(blocker, blocked), - {:ok, _activity} <- ActivityPub.block(blocker, blocked) do - {:ok, blocker, blocked} - else - err -> err - end - end - - def unblock(%User{} = blocker, params) do - with {:ok, %User{} = blocked} <- get_user(params), - {:ok, blocker} <- User.unblock(blocker, blocked), - {:ok, _activity} <- ActivityPub.unblock(blocker, blocked) do - {:ok, blocker, blocked} - else - err -> err - end - end - - def repeat(%User{} = user, ap_id_or_id) do - with {:ok, _announce, %{data: %{"id" => id}}} <- CommonAPI.repeat(ap_id_or_id, user), - %Activity{} = activity <- Activity.get_create_by_object_ap_id(id) do - {:ok, activity} - end - end - - def unrepeat(%User{} = user, ap_id_or_id) do - with {:ok, _unannounce, %{data: %{"id" => id}}} <- CommonAPI.unrepeat(ap_id_or_id, user), - %Activity{} = activity <- Activity.get_create_by_object_ap_id(id) do - {:ok, activity} - end - end - - def pin(%User{} = user, ap_id_or_id) do - CommonAPI.pin(ap_id_or_id, user) - end - - def unpin(%User{} = user, ap_id_or_id) do - CommonAPI.unpin(ap_id_or_id, user) - end - - def fav(%User{} = user, ap_id_or_id) do - with {:ok, _fav, %{data: %{"id" => id}}} <- CommonAPI.favorite(ap_id_or_id, user), - %Activity{} = activity <- Activity.get_create_by_object_ap_id(id) do - {:ok, activity} - end - end - - def unfav(%User{} = user, ap_id_or_id) do - with {:ok, _unfav, _fav, %{data: %{"id" => id}}} <- CommonAPI.unfavorite(ap_id_or_id, user), - %Activity{} = activity <- Activity.get_create_by_object_ap_id(id) do - {:ok, activity} - end - end - - def upload(%Plug.Upload{} = file, %User{} = user, format \\ "xml") do - {:ok, object} = ActivityPub.upload(file, actor: User.ap_id(user)) - - url = List.first(object.data["url"]) - href = url["href"] - type = url["mediaType"] - - case format do - "xml" -> - # Fake this as good as possible... - """ - - - #{object.id} - #{object.id} - #{object.id} - #{href} - #{href} - - - """ - - "json" -> - %{ - media_id: object.id, - media_id_string: "#{object.id}}", - media_url: href, - size: 0 - } - |> Jason.encode!() - end - end - def register_user(params, opts \\ []) do token = params["token"] @@ -236,80 +117,4 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do {:error, "unknown user"} end end - - def get_user(user \\ nil, params) do - case params do - %{"user_id" => user_id} -> - case User.get_cached_by_nickname_or_id(user_id) do - nil -> - {:error, "No user with such user_id"} - - %User{info: %{deactivated: true}} -> - {:error, "User has been disabled"} - - user -> - {:ok, user} - end - - %{"screen_name" => nickname} -> - case User.get_cached_by_nickname(nickname) do - nil -> {:error, "No user with such screen_name"} - target -> {:ok, target} - end - - _ -> - if user do - {:ok, user} - else - {:error, "You need to specify screen_name or user_id"} - end - end - end - - defp parse_int(string, default) - - defp parse_int(string, default) when is_binary(string) do - with {n, _} <- Integer.parse(string) do - n - else - _e -> default - end - end - - defp parse_int(_, default), do: default - - # TODO: unify the search query with MastoAPI one and do only pagination here - def search(_user, %{"q" => query} = params) do - limit = parse_int(params["rpp"], 20) - page = parse_int(params["page"], 1) - offset = (page - 1) * limit - - q = - from( - [a, o] in Activity.with_preloaded_object(Activity), - where: fragment("?->>'type' = 'Create'", a.data), - where: ^Pleroma.Constants.as_public() in a.recipients, - where: - fragment( - "to_tsvector('english', ?->>'content') @@ plainto_tsquery('english', ?)", - o.data, - ^query - ), - limit: ^limit, - offset: ^offset, - # this one isn't indexed so psql won't take the wrong index. - order_by: [desc: :inserted_at] - ) - - _activities = Repo.all(q) - end - - def get_external_profile(for_user, uri) do - with {:ok, %User{} = user} <- User.get_or_fetch(uri) do - {:ok, UserView.render("show.json", %{user: user, for: for_user})} - else - _e -> - {:error, "Couldn't find user"} - end - end end -- cgit v1.2.3 From 985122cc03380b8e3decd4ac7180ea5b0f7ab30d Mon Sep 17 00:00:00 2001 From: rinpatch Date: Sat, 31 Aug 2019 10:31:15 +0300 Subject: Remove Activity, User and Notification views from TwitterAPI --- lib/pleroma/web/twitter_api/views/activity_view.ex | 366 --------------------- .../web/twitter_api/views/notification_view.ex | 71 ---- lib/pleroma/web/twitter_api/views/user_view.ex | 191 ----------- 3 files changed, 628 deletions(-) delete mode 100644 lib/pleroma/web/twitter_api/views/activity_view.ex delete mode 100644 lib/pleroma/web/twitter_api/views/notification_view.ex delete mode 100644 lib/pleroma/web/twitter_api/views/user_view.ex (limited to 'lib') diff --git a/lib/pleroma/web/twitter_api/views/activity_view.ex b/lib/pleroma/web/twitter_api/views/activity_view.ex deleted file mode 100644 index abae63877..000000000 --- a/lib/pleroma/web/twitter_api/views/activity_view.ex +++ /dev/null @@ -1,366 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.TwitterAPI.ActivityView do - use Pleroma.Web, :view - alias Pleroma.Activity - alias Pleroma.Formatter - alias Pleroma.HTML - alias Pleroma.Object - alias Pleroma.Repo - alias Pleroma.User - alias Pleroma.Web.CommonAPI - alias Pleroma.Web.CommonAPI.Utils - alias Pleroma.Web.MastodonAPI.StatusView - alias Pleroma.Web.TwitterAPI.ActivityView - alias Pleroma.Web.TwitterAPI.Representers.ObjectRepresenter - alias Pleroma.Web.TwitterAPI.UserView - - import Ecto.Query - require Logger - require Pleroma.Constants - - defp query_context_ids([]), do: [] - - defp query_context_ids(contexts) do - query = from(o in Object, where: fragment("(?)->>'id' = ANY(?)", o.data, ^contexts)) - - Repo.all(query) - end - - defp query_users([]), do: [] - - defp query_users(user_ids) do - query = from(user in User, where: user.ap_id in ^user_ids) - - Repo.all(query) - end - - defp collect_context_ids(activities) do - _contexts = - activities - |> Enum.reject(& &1.data["context_id"]) - |> Enum.map(fn %{data: data} -> - data["context"] - end) - |> Enum.filter(& &1) - |> query_context_ids() - |> Enum.reduce(%{}, fn %{data: %{"id" => ap_id}, id: id}, acc -> - Map.put(acc, ap_id, id) - end) - end - - defp collect_users(activities) do - activities - |> Enum.map(fn activity -> - case activity.data do - data = %{"type" => "Follow"} -> - [data["actor"], data["object"]] - - data -> - [data["actor"]] - end ++ activity.recipients - end) - |> List.flatten() - |> Enum.uniq() - |> query_users() - |> Enum.reduce(%{}, fn user, acc -> - Map.put(acc, user.ap_id, user) - end) - end - - defp get_context_id(%{data: %{"context_id" => context_id}}, _) when not is_nil(context_id), - do: context_id - - defp get_context_id(%{data: %{"context" => nil}}, _), do: nil - - defp get_context_id(%{data: %{"context" => context}}, options) do - cond do - id = options[:context_ids][context] -> id - true -> Utils.context_to_conversation_id(context) - end - end - - defp get_context_id(_, _), do: nil - - defp get_user(ap_id, opts) do - cond do - user = opts[:users][ap_id] -> - user - - String.ends_with?(ap_id, "/followers") -> - nil - - ap_id == Pleroma.Constants.as_public() -> - nil - - user = User.get_cached_by_ap_id(ap_id) -> - user - - user = User.get_by_guessed_nickname(ap_id) -> - user - - true -> - User.error_user(ap_id) - end - end - - def render("index.json", opts) do - context_ids = collect_context_ids(opts.activities) - users = collect_users(opts.activities) - - opts = - opts - |> Map.put(:context_ids, context_ids) - |> Map.put(:users, users) - - safe_render_many( - opts.activities, - ActivityView, - "activity.json", - opts - ) - end - - def render("activity.json", %{activity: %{data: %{"type" => "Delete"}} = activity} = opts) do - user = get_user(activity.data["actor"], opts) - created_at = activity.data["published"] |> Utils.date_to_asctime() - - %{ - "id" => activity.id, - "uri" => activity.data["object"], - "user" => UserView.render("show.json", %{user: user, for: opts[:for]}), - "attentions" => [], - "statusnet_html" => "deleted notice {{tag", - "text" => "deleted notice {{tag", - "is_local" => activity.local, - "is_post_verb" => false, - "created_at" => created_at, - "in_reply_to_status_id" => nil, - "external_url" => activity.data["id"], - "activity_type" => "delete" - } - end - - def render("activity.json", %{activity: %{data: %{"type" => "Follow"}} = activity} = opts) do - user = get_user(activity.data["actor"], opts) - created_at = activity.data["published"] || DateTime.to_iso8601(activity.inserted_at) - created_at = created_at |> Utils.date_to_asctime() - - followed = get_user(activity.data["object"], opts) - text = "#{user.nickname} started following #{followed.nickname}" - - %{ - "id" => activity.id, - "user" => UserView.render("show.json", %{user: user, for: opts[:for]}), - "attentions" => [], - "statusnet_html" => text, - "text" => text, - "is_local" => activity.local, - "is_post_verb" => false, - "created_at" => created_at, - "in_reply_to_status_id" => nil, - "external_url" => activity.data["id"], - "activity_type" => "follow" - } - end - - def render("activity.json", %{activity: %{data: %{"type" => "Announce"}} = activity} = opts) do - user = get_user(activity.data["actor"], opts) - created_at = activity.data["published"] |> Utils.date_to_asctime() - announced_activity = Activity.get_create_by_object_ap_id(activity.data["object"]) - - text = "#{user.nickname} repeated a status." - - retweeted_status = render("activity.json", Map.merge(opts, %{activity: announced_activity})) - - %{ - "id" => activity.id, - "user" => UserView.render("show.json", %{user: user, for: opts[:for]}), - "statusnet_html" => text, - "text" => text, - "is_local" => activity.local, - "is_post_verb" => false, - "uri" => "tag:#{activity.data["id"]}:objectType=note", - "created_at" => created_at, - "retweeted_status" => retweeted_status, - "statusnet_conversation_id" => get_context_id(announced_activity, opts), - "external_url" => activity.data["id"], - "activity_type" => "repeat" - } - end - - def render("activity.json", %{activity: %{data: %{"type" => "Like"}} = activity} = opts) do - user = get_user(activity.data["actor"], opts) - liked_activity = Activity.get_create_by_object_ap_id(activity.data["object"]) - liked_activity_id = if liked_activity, do: liked_activity.id, else: nil - - created_at = - activity.data["published"] - |> Utils.date_to_asctime() - - text = "#{user.nickname} favorited a status." - - favorited_status = - if liked_activity, - do: render("activity.json", Map.merge(opts, %{activity: liked_activity})), - else: nil - - %{ - "id" => activity.id, - "user" => UserView.render("show.json", %{user: user, for: opts[:for]}), - "statusnet_html" => text, - "text" => text, - "is_local" => activity.local, - "is_post_verb" => false, - "uri" => "tag:#{activity.data["id"]}:objectType=Favourite", - "created_at" => created_at, - "favorited_status" => favorited_status, - "in_reply_to_status_id" => liked_activity_id, - "external_url" => activity.data["id"], - "activity_type" => "like" - } - end - - def render( - "activity.json", - %{activity: %{data: %{"type" => "Create", "object" => object_id}} = activity} = opts - ) do - user = get_user(activity.data["actor"], opts) - - object = Object.normalize(object_id) - - created_at = object.data["published"] |> Utils.date_to_asctime() - like_count = object.data["like_count"] || 0 - announcement_count = object.data["announcement_count"] || 0 - favorited = opts[:for] && opts[:for].ap_id in (object.data["likes"] || []) - repeated = opts[:for] && opts[:for].ap_id in (object.data["announcements"] || []) - pinned = activity.id in user.info.pinned_activities - - attentions = - [] - |> Utils.maybe_notify_to_recipients(activity) - |> Utils.maybe_notify_mentioned_recipients(activity) - |> Enum.map(fn ap_id -> get_user(ap_id, opts) end) - |> Enum.filter(& &1) - |> Enum.map(fn user -> UserView.render("show.json", %{user: user, for: opts[:for]}) end) - - conversation_id = get_context_id(activity, opts) - - tags = object.data["tag"] || [] - possibly_sensitive = object.data["sensitive"] || Enum.member?(tags, "nsfw") - - tags = if possibly_sensitive, do: Enum.uniq(["nsfw" | tags]), else: tags - - {summary, content} = render_content(object.data) - - html = - content - |> HTML.get_cached_scrubbed_html_for_activity( - User.html_filter_policy(opts[:for]), - activity, - "twitterapi:content" - ) - |> Formatter.emojify(object.data["emoji"]) - - text = - if content do - content - |> String.replace(~r//, "\n") - |> HTML.get_cached_stripped_html_for_activity(activity, "twitterapi:content") - else - "" - end - - reply_parent = Activity.get_in_reply_to_activity(activity) - - reply_user = reply_parent && User.get_cached_by_ap_id(reply_parent.actor) - - summary = HTML.strip_tags(summary) - - card = - StatusView.render( - "card.json", - Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity) - ) - - thread_muted? = - case activity.thread_muted? do - thread_muted? when is_boolean(thread_muted?) -> thread_muted? - nil -> CommonAPI.thread_muted?(user, activity) - end - - %{ - "id" => activity.id, - "uri" => object.data["id"], - "user" => UserView.render("show.json", %{user: user, for: opts[:for]}), - "statusnet_html" => html, - "text" => text, - "is_local" => activity.local, - "is_post_verb" => true, - "created_at" => created_at, - "in_reply_to_status_id" => reply_parent && reply_parent.id, - "in_reply_to_screen_name" => reply_user && reply_user.nickname, - "in_reply_to_profileurl" => User.profile_url(reply_user), - "in_reply_to_ostatus_uri" => reply_user && reply_user.ap_id, - "in_reply_to_user_id" => reply_user && reply_user.id, - "statusnet_conversation_id" => conversation_id, - "attachments" => (object.data["attachment"] || []) |> ObjectRepresenter.enum_to_list(opts), - "attentions" => attentions, - "fave_num" => like_count, - "repeat_num" => announcement_count, - "favorited" => !!favorited, - "repeated" => !!repeated, - "pinned" => pinned, - "external_url" => object.data["external_url"] || object.data["id"], - "tags" => tags, - "activity_type" => "post", - "possibly_sensitive" => possibly_sensitive, - "visibility" => Pleroma.Web.ActivityPub.Visibility.get_visibility(object), - "summary" => summary, - "summary_html" => summary |> Formatter.emojify(object.data["emoji"]), - "card" => card, - "muted" => thread_muted? || User.mutes?(opts[:for], user) - } - end - - def render("activity.json", %{activity: unhandled_activity}) do - Logger.warn("#{__MODULE__} unhandled activity: #{inspect(unhandled_activity)}") - nil - end - - def render_content(%{"type" => "Note"} = object) do - summary = object["summary"] - - content = - if !!summary and summary != "" do - "

#{summary}

#{object["content"]}" - else - object["content"] - end - - {summary, content} - end - - def render_content(%{"type" => object_type} = object) - when object_type in ["Article", "Page", "Video"] do - summary = object["name"] || object["summary"] - - content = - if !!summary and summary != "" and is_bitstring(object["url"]) do - "

#{summary}

#{object["content"]}" - else - object["content"] - end - - {summary, content} - end - - def render_content(object) do - summary = object["summary"] || "Unhandled activity type: #{object["type"]}" - content = "

#{summary}

#{object["content"]}" - - {summary, content} - end -end diff --git a/lib/pleroma/web/twitter_api/views/notification_view.ex b/lib/pleroma/web/twitter_api/views/notification_view.ex deleted file mode 100644 index 085cd5aa3..000000000 --- a/lib/pleroma/web/twitter_api/views/notification_view.ex +++ /dev/null @@ -1,71 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.TwitterAPI.NotificationView do - use Pleroma.Web, :view - alias Pleroma.Notification - alias Pleroma.User - alias Pleroma.Web.CommonAPI.Utils - alias Pleroma.Web.TwitterAPI.ActivityView - alias Pleroma.Web.TwitterAPI.UserView - - require Pleroma.Constants - - defp get_user(ap_id, opts) do - cond do - user = opts[:users][ap_id] -> - user - - String.ends_with?(ap_id, "/followers") -> - nil - - ap_id == Pleroma.Constants.as_public() -> - nil - - true -> - User.get_cached_by_ap_id(ap_id) - end - end - - def render("notification.json", %{notifications: notifications, for: user}) do - render_many( - notifications, - Pleroma.Web.TwitterAPI.NotificationView, - "notification.json", - for: user - ) - end - - def render( - "notification.json", - %{ - notification: %Notification{ - id: id, - seen: seen, - activity: activity, - inserted_at: created_at - }, - for: user - } = opts - ) do - ntype = - case activity.data["type"] do - "Create" -> "mention" - "Like" -> "like" - "Announce" -> "repeat" - "Follow" -> "follow" - end - - from = get_user(activity.data["actor"], opts) - - %{ - "id" => id, - "ntype" => ntype, - "notice" => ActivityView.render("activity.json", %{activity: activity, for: user}), - "from_profile" => UserView.render("show.json", %{user: from, for: user}), - "is_seen" => if(seen, do: 1, else: 0), - "created_at" => created_at |> Utils.format_naive_asctime() - } - end -end diff --git a/lib/pleroma/web/twitter_api/views/user_view.ex b/lib/pleroma/web/twitter_api/views/user_view.ex deleted file mode 100644 index 8a7d2fc72..000000000 --- a/lib/pleroma/web/twitter_api/views/user_view.ex +++ /dev/null @@ -1,191 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.TwitterAPI.UserView do - use Pleroma.Web, :view - alias Pleroma.Formatter - alias Pleroma.HTML - alias Pleroma.User - alias Pleroma.Web.CommonAPI.Utils - alias Pleroma.Web.MediaProxy - - def render("show.json", %{user: user = %User{}} = assigns) do - render_one(user, Pleroma.Web.TwitterAPI.UserView, "user.json", assigns) - end - - def render("index.json", %{users: users, for: user}) do - users - |> render_many(Pleroma.Web.TwitterAPI.UserView, "user.json", for: user) - |> Enum.filter(&Enum.any?/1) - end - - def render("user.json", %{user: user = %User{}} = assigns) do - if User.visible_for?(user, assigns[:for]), - do: do_render("user.json", assigns), - else: %{} - end - - def render("short.json", %{ - user: %User{ - nickname: nickname, - id: id, - ap_id: ap_id, - name: name - } - }) do - %{ - "fullname" => name, - "id" => id, - "ostatus_uri" => ap_id, - "profile_url" => ap_id, - "screen_name" => nickname - } - end - - defp do_render("user.json", %{user: user = %User{}} = assigns) do - for_user = assigns[:for] - image = User.avatar_url(user) |> MediaProxy.url() - - {following, follows_you, statusnet_blocking} = - if for_user do - { - User.following?(for_user, user), - User.following?(user, for_user), - User.blocks?(for_user, user) - } - else - {false, false, false} - end - - user_info = User.get_cached_user_info(user) - - emoji = - (user.info.source_data["tag"] || []) - |> Enum.filter(fn %{"type" => t} -> t == "Emoji" end) - |> Enum.map(fn %{"icon" => %{"url" => url}, "name" => name} -> - {String.trim(name, ":"), url} - end) - - emoji = Enum.dedup(emoji ++ user.info.emoji) - - description_html = - (user.bio || "") - |> HTML.filter_tags(User.html_filter_policy(for_user)) - |> Formatter.emojify(emoji) - - fields = - user.info - |> User.Info.fields() - |> Enum.map(fn %{"name" => name, "value" => value} -> - %{ - "name" => Pleroma.HTML.strip_tags(name), - "value" => Pleroma.HTML.filter_tags(value, Pleroma.HTML.Scrubber.LinksOnly) - } - end) - - data = - %{ - "created_at" => user.inserted_at |> Utils.format_naive_asctime(), - "description" => HTML.strip_tags((user.bio || "") |> String.replace("
", "\n")), - "description_html" => description_html, - "favourites_count" => 0, - "followers_count" => user_info[:follower_count], - "following" => following, - "follows_you" => follows_you, - "statusnet_blocking" => statusnet_blocking, - "friends_count" => user_info[:following_count], - "id" => user.id, - "name" => user.name || user.nickname, - "name_html" => - if(user.name, - do: HTML.strip_tags(user.name) |> Formatter.emojify(emoji), - else: user.nickname - ), - "profile_image_url" => image, - "profile_image_url_https" => image, - "profile_image_url_profile_size" => image, - "profile_image_url_original" => image, - "screen_name" => user.nickname, - "statuses_count" => user_info[:note_count], - "statusnet_profile_url" => user.ap_id, - "cover_photo" => User.banner_url(user) |> MediaProxy.url(), - "background_image" => image_url(user.info.background) |> MediaProxy.url(), - "is_local" => user.local, - "locked" => user.info.locked, - "hide_followers" => user.info.hide_followers, - "hide_follows" => user.info.hide_follows, - "fields" => fields, - - # Pleroma extension - "pleroma" => - %{ - "confirmation_pending" => user_info.confirmation_pending, - "tags" => user.tags, - "skip_thread_containment" => user.info.skip_thread_containment - } - |> maybe_with_activation_status(user, for_user) - |> with_notification_settings(user, for_user) - } - |> maybe_with_user_settings(user, for_user) - |> maybe_with_role(user, for_user) - - if assigns[:token] do - Map.put(data, "token", token_string(assigns[:token])) - else - data - end - end - - defp with_notification_settings(data, %User{id: user_id} = user, %User{id: user_id}) do - Map.put(data, "notification_settings", user.info.notification_settings) - end - - defp with_notification_settings(data, _, _), do: data - - defp maybe_with_activation_status(data, user, %User{info: %{is_admin: true}}) do - Map.put(data, "deactivated", user.info.deactivated) - end - - defp maybe_with_activation_status(data, _, _), do: data - - defp maybe_with_role(data, %User{id: id} = user, %User{id: id}) do - Map.merge(data, %{ - "role" => role(user), - "show_role" => user.info.show_role, - "rights" => %{ - "delete_others_notice" => !!user.info.is_moderator, - "admin" => !!user.info.is_admin - } - }) - end - - defp maybe_with_role(data, %User{info: %{show_role: true}} = user, _user) do - Map.merge(data, %{ - "role" => role(user), - "rights" => %{ - "delete_others_notice" => !!user.info.is_moderator, - "admin" => !!user.info.is_admin - } - }) - end - - defp maybe_with_role(data, _, _), do: data - - defp maybe_with_user_settings(data, %User{info: info, id: id} = _user, %User{id: id}) do - data - |> Kernel.put_in(["default_scope"], info.default_scope) - |> Kernel.put_in(["no_rich_text"], info.no_rich_text) - end - - defp maybe_with_user_settings(data, _, _), do: data - defp role(%User{info: %{:is_admin => true}}), do: "admin" - defp role(%User{info: %{:is_moderator => true}}), do: "moderator" - defp role(_), do: "member" - - defp image_url(%{"url" => [%{"href" => href} | _]}), do: href - defp image_url(_), do: nil - - defp token_string(%Pleroma.Web.OAuth.Token{token: token_str}), do: token_str - defp token_string(token), do: token -end -- cgit v1.2.3 From 64410497d20869f9b6c1c92a48761157048b0cb9 Mon Sep 17 00:00:00 2001 From: rinpatch Date: Sat, 31 Aug 2019 10:41:15 +0300 Subject: Remove TwitterAPI representers --- .../twitter_api/representers/base_representer.ex | 38 --------------------- .../twitter_api/representers/object_representer.ex | 39 ---------------------- 2 files changed, 77 deletions(-) delete mode 100644 lib/pleroma/web/twitter_api/representers/base_representer.ex delete mode 100644 lib/pleroma/web/twitter_api/representers/object_representer.ex (limited to 'lib') diff --git a/lib/pleroma/web/twitter_api/representers/base_representer.ex b/lib/pleroma/web/twitter_api/representers/base_representer.ex deleted file mode 100644 index 3d31e6079..000000000 --- a/lib/pleroma/web/twitter_api/representers/base_representer.ex +++ /dev/null @@ -1,38 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.TwitterAPI.Representers.BaseRepresenter do - defmacro __using__(_opts) do - quote do - def to_json(object) do - to_json(object, %{}) - end - - def to_json(object, options) do - object - |> to_map(options) - |> Jason.encode!() - end - - def enum_to_list(enum, options) do - mapping = fn el -> to_map(el, options) end - Enum.map(enum, mapping) - end - - def to_map(object) do - to_map(object, %{}) - end - - def enum_to_json(enum) do - enum_to_json(enum, %{}) - end - - def enum_to_json(enum, options) do - enum - |> enum_to_list(options) - |> Jason.encode!() - end - end - end -end diff --git a/lib/pleroma/web/twitter_api/representers/object_representer.ex b/lib/pleroma/web/twitter_api/representers/object_representer.ex deleted file mode 100644 index 47130ba06..000000000 --- a/lib/pleroma/web/twitter_api/representers/object_representer.ex +++ /dev/null @@ -1,39 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.TwitterAPI.Representers.ObjectRepresenter do - use Pleroma.Web.TwitterAPI.Representers.BaseRepresenter - alias Pleroma.Object - - def to_map(%Object{data: %{"url" => [url | _]}} = object, _opts) do - data = object.data - - %{ - url: url["href"] |> Pleroma.Web.MediaProxy.url(), - mimetype: url["mediaType"] || url["mimeType"], - id: data["uuid"], - oembed: false, - description: data["name"] - } - end - - def to_map(%Object{data: %{"url" => url} = data}, _opts) when is_binary(url) do - %{ - url: url |> Pleroma.Web.MediaProxy.url(), - mimetype: data["mediaType"] || data["mimeType"], - id: data["uuid"], - oembed: false, - description: data["name"] - } - end - - def to_map(%Object{}, _opts) do - %{} - end - - # If we only get the naked data, wrap in an object - def to_map(%{} = data, opts) do - to_map(%Object{data: data}, opts) - end -end -- cgit v1.2.3 From bd3ed3a62299bad5d717aaff0a0bd088ff1c1ef7 Mon Sep 17 00:00:00 2001 From: rinpatch Date: Sat, 31 Aug 2019 11:40:04 +0300 Subject: Add back /api/qvitter/statuses/notifications/read.json --- lib/pleroma/web/router.ex | 6 ++++++ .../web/twitter_api/twitter_api_controller.ex | 25 ++++++++++++++++++++++ 2 files changed, 31 insertions(+) (limited to 'lib') diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 53728e298..eb7cbbc96 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -495,6 +495,12 @@ defmodule Pleroma.Web.Router do get("/oauth_tokens", TwitterAPI.Controller, :oauth_tokens) delete("/oauth_tokens/:id", TwitterAPI.Controller, :revoke_token) + + scope [] do + pipe_through(:oauth_read) + + post("/qvitter/statuses/notifications/read", TwitterAPI.Controller, :notifications_read) + end end pipeline :ap_service_actor do diff --git a/lib/pleroma/web/twitter_api/twitter_api_controller.ex b/lib/pleroma/web/twitter_api/twitter_api_controller.ex index 1c3b11a57..8ca754b51 100644 --- a/lib/pleroma/web/twitter_api/twitter_api_controller.ex +++ b/lib/pleroma/web/twitter_api/twitter_api_controller.ex @@ -7,6 +7,7 @@ defmodule Pleroma.Web.TwitterAPI.Controller do alias Ecto.Changeset alias Pleroma.User + alias Pleroma.Notification alias Pleroma.Web.OAuth.Token alias Pleroma.Web.TwitterAPI.TokenView @@ -58,4 +59,28 @@ defmodule Pleroma.Web.TwitterAPI.Controller do |> put_resp_content_type("application/json") |> send_resp(status, json) end + + def notifications_read(%{assigns: %{user: user}} = conn, %{"latest_id" => latest_id} = params) do + Notification.set_read_up_to(user, latest_id) + + notifications = Notification.for_user(user, params) + + conn + # XXX: This is a hack because pleroma-fe still uses that API. + |> put_view(Pleroma.Web.MastodonAPI.NotificationView) + |> render("index.json", %{notifications: notifications, for: user}) + end + + def notifications_read(%{assigns: %{user: _user}} = conn, _) do + bad_request_reply(conn, "You need to specify latest_id") + end + + defp bad_request_reply(conn, error_message) do + json = error_json(conn, error_message) + json_reply(conn, 400, json) + end + + defp error_json(conn, error_message) do + %{"error" => error_message, "request" => conn.request_path} |> Jason.encode!() + end end -- cgit v1.2.3 From 70eed0594ce4fe2ec668c5ee3ad42c941b29888e Mon Sep 17 00:00:00 2001 From: rinpatch Date: Sat, 31 Aug 2019 13:08:43 +0300 Subject: credo fixes --- lib/pleroma/web/twitter_api/twitter_api_controller.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/web/twitter_api/twitter_api_controller.ex b/lib/pleroma/web/twitter_api/twitter_api_controller.ex index 8ca754b51..42234ae09 100644 --- a/lib/pleroma/web/twitter_api/twitter_api_controller.ex +++ b/lib/pleroma/web/twitter_api/twitter_api_controller.ex @@ -6,8 +6,8 @@ defmodule Pleroma.Web.TwitterAPI.Controller do use Pleroma.Web, :controller alias Ecto.Changeset - alias Pleroma.User alias Pleroma.Notification + alias Pleroma.User alias Pleroma.Web.OAuth.Token alias Pleroma.Web.TwitterAPI.TokenView -- cgit v1.2.3 From a4c5f71e933c905433b80c90bcd626e7da703669 Mon Sep 17 00:00:00 2001 From: Maxim Filippov Date: Mon, 2 Sep 2019 22:48:52 +0300 Subject: Return total from pagination + tests --- lib/pleroma/activity/search.ex | 1 + lib/pleroma/conversation/participation.ex | 1 + lib/pleroma/notification.ex | 1 + lib/pleroma/pagination.ex | 28 +++++++++++++++------- lib/pleroma/user/search.ex | 1 + lib/pleroma/web/activity_pub/activity_pub.ex | 3 +++ .../controllers/mastodon_api_controller.ex | 2 ++ lib/pleroma/web/mastodon_api/mastodon_api.ex | 4 ++++ 8 files changed, 32 insertions(+), 9 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/activity/search.ex b/lib/pleroma/activity/search.ex index f847ac238..f7156c81c 100644 --- a/lib/pleroma/activity/search.ex +++ b/lib/pleroma/activity/search.ex @@ -27,6 +27,7 @@ defmodule Pleroma.Activity.Search do |> maybe_restrict_local(user) |> maybe_restrict_author(author) |> Pagination.fetch_paginated(%{"offset" => offset, "limit" => limit}, :offset) + |> Map.get(:items) |> maybe_fetch(user, search_query) end diff --git a/lib/pleroma/conversation/participation.ex b/lib/pleroma/conversation/participation.ex index ea5b9fe17..5fd8d3d41 100644 --- a/lib/pleroma/conversation/participation.ex +++ b/lib/pleroma/conversation/participation.ex @@ -67,6 +67,7 @@ defmodule Pleroma.Conversation.Participation do preload: [conversation: [:users]] ) |> Pleroma.Pagination.fetch_paginated(params) + |> Map.get(:items) end def for_user_and_conversation(user, conversation) do diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex index 5d29af853..3e4ddd2ba 100644 --- a/lib/pleroma/notification.ex +++ b/lib/pleroma/notification.ex @@ -75,6 +75,7 @@ defmodule Pleroma.Notification do user |> for_user_query(opts) |> Pagination.fetch_paginated(opts) + |> Map.get(:items) end @doc """ diff --git a/lib/pleroma/pagination.ex b/lib/pleroma/pagination.ex index 2b869ccdc..d21ecf628 100644 --- a/lib/pleroma/pagination.ex +++ b/lib/pleroma/pagination.ex @@ -18,19 +18,29 @@ defmodule Pleroma.Pagination do def fetch_paginated(query, params, :keyset) do options = cast_params(params) - - query - |> paginate(options, :keyset) - |> Repo.all() - |> enforce_order(options) + total = Repo.aggregate(query, :count, :id) + + %{ + total: total, + items: + query + |> paginate(options, :keyset) + |> Repo.all() + |> enforce_order(options) + } end def fetch_paginated(query, params, :offset) do options = cast_params(params) - - query - |> paginate(options, :offset) - |> Repo.all() + total = Repo.aggregate(query, :count, :id) + + %{ + total: total, + items: + query + |> paginate(options, :offset) + |> Repo.all() + } end def paginate(query, options, method \\ :keyset) diff --git a/lib/pleroma/user/search.ex b/lib/pleroma/user/search.ex index 6fb2c2352..bc05639b5 100644 --- a/lib/pleroma/user/search.ex +++ b/lib/pleroma/user/search.ex @@ -34,6 +34,7 @@ defmodule Pleroma.User.Search do query_string |> search_query(for_user, following) |> Pagination.fetch_paginated(%{"offset" => offset, "limit" => result_limit}, :offset) + |> Map.get(:items) end) results diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index eeb826814..8f07638cd 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -556,6 +556,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do q |> restrict_unlisted() |> Pagination.fetch_paginated(opts) + |> Map.get(:items) |> Enum.reverse() end @@ -953,6 +954,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do fetch_activities_query(recipients ++ list_memberships, opts) |> Pagination.fetch_paginated(opts) + |> Map.get(:items) |> Enum.reverse() |> maybe_update_cc(list_memberships, opts["user"]) end @@ -987,6 +989,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do fetch_activities_query([], opts) |> fetch_activities_bounded_query(recipients, recipients_with_public) |> Pagination.fetch_paginated(opts) + |> Map.get(:items) |> Enum.reverse() end diff --git a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex index 83e877c0e..d532ba685 100644 --- a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex @@ -420,6 +420,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do [user.ap_id] |> ActivityPub.fetch_activities_query(params) |> Pagination.fetch_paginated(params) + |> Map.get(:items) conn |> add_link_headers(:dm_timeline, activities) @@ -1194,6 +1195,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do bookmarks = Bookmark.for_user_query(user.id) |> Pagination.fetch_paginated(params) + |> Map.get(:items) activities = bookmarks diff --git a/lib/pleroma/web/mastodon_api/mastodon_api.ex b/lib/pleroma/web/mastodon_api/mastodon_api.ex index ac01d1ff3..cf3962944 100644 --- a/lib/pleroma/web/mastodon_api/mastodon_api.ex +++ b/lib/pleroma/web/mastodon_api/mastodon_api.ex @@ -45,12 +45,14 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPI do user |> User.get_followers_query() |> Pagination.fetch_paginated(params) + |> Map.get(:items) end def get_friends(user, params \\ %{}) do user |> User.get_friends_query() |> Pagination.fetch_paginated(params) + |> Map.get(:items) end def get_notifications(user, params \\ %{}) do @@ -60,12 +62,14 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPI do |> Notification.for_user_query(options) |> restrict(:exclude_types, options) |> Pagination.fetch_paginated(params) + |> Map.get(:items) end def get_scheduled_activities(user, params \\ %{}) do user |> ScheduledActivity.for_user_query() |> Pagination.fetch_paginated(params) + |> Map.get(:items) end defp cast_params(params) do -- cgit v1.2.3 From b15cfd80ef5d5bc971f78a53dfa3d37dec4499a5 Mon Sep 17 00:00:00 2001 From: Maxim Filippov Date: Tue, 3 Sep 2019 13:58:27 +0300 Subject: Return "total" optionally --- lib/pleroma/activity/search.ex | 1 - lib/pleroma/conversation/participation.ex | 1 - lib/pleroma/notification.ex | 1 - lib/pleroma/pagination.ex | 32 ++++++++++++++-------- lib/pleroma/user/search.ex | 1 - lib/pleroma/web/activity_pub/activity_pub.ex | 3 -- .../controllers/mastodon_api_controller.ex | 2 -- lib/pleroma/web/mastodon_api/mastodon_api.ex | 4 --- 8 files changed, 20 insertions(+), 25 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/activity/search.ex b/lib/pleroma/activity/search.ex index f7156c81c..f847ac238 100644 --- a/lib/pleroma/activity/search.ex +++ b/lib/pleroma/activity/search.ex @@ -27,7 +27,6 @@ defmodule Pleroma.Activity.Search do |> maybe_restrict_local(user) |> maybe_restrict_author(author) |> Pagination.fetch_paginated(%{"offset" => offset, "limit" => limit}, :offset) - |> Map.get(:items) |> maybe_fetch(user, search_query) end diff --git a/lib/pleroma/conversation/participation.ex b/lib/pleroma/conversation/participation.ex index 5fd8d3d41..ea5b9fe17 100644 --- a/lib/pleroma/conversation/participation.ex +++ b/lib/pleroma/conversation/participation.ex @@ -67,7 +67,6 @@ defmodule Pleroma.Conversation.Participation do preload: [conversation: [:users]] ) |> Pleroma.Pagination.fetch_paginated(params) - |> Map.get(:items) end def for_user_and_conversation(user, conversation) do diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex index 3e4ddd2ba..5d29af853 100644 --- a/lib/pleroma/notification.ex +++ b/lib/pleroma/notification.ex @@ -75,7 +75,6 @@ defmodule Pleroma.Notification do user |> for_user_query(opts) |> Pagination.fetch_paginated(opts) - |> Map.get(:items) end @doc """ diff --git a/lib/pleroma/pagination.ex b/lib/pleroma/pagination.ex index d21ecf628..b55379c4a 100644 --- a/lib/pleroma/pagination.ex +++ b/lib/pleroma/pagination.ex @@ -16,33 +16,41 @@ defmodule Pleroma.Pagination do def fetch_paginated(query, params, type \\ :keyset) - def fetch_paginated(query, params, :keyset) do - options = cast_params(params) + def fetch_paginated(query, %{"total" => true} = params, :keyset) do total = Repo.aggregate(query, :count, :id) %{ total: total, - items: - query - |> paginate(options, :keyset) - |> Repo.all() - |> enforce_order(options) + items: fetch_paginated(query, Map.drop(params, ["total"]), :keyset) } end - def fetch_paginated(query, params, :offset) do + def fetch_paginated(query, params, :keyset) do options = cast_params(params) + + query + |> paginate(options, :keyset) + |> Repo.all() + |> enforce_order(options) + end + + def fetch_paginated(query, %{"total" => true} = params, :offset) do total = Repo.aggregate(query, :count, :id) %{ total: total, - items: - query - |> paginate(options, :offset) - |> Repo.all() + items: fetch_paginated(query, Map.drop(params, ["total"]), :offset) } end + def fetch_paginated(query, params, :offset) do + options = cast_params(params) + + query + |> paginate(options, :offset) + |> Repo.all() + end + def paginate(query, options, method \\ :keyset) def paginate(query, options, :keyset) do diff --git a/lib/pleroma/user/search.ex b/lib/pleroma/user/search.ex index bc05639b5..6fb2c2352 100644 --- a/lib/pleroma/user/search.ex +++ b/lib/pleroma/user/search.ex @@ -34,7 +34,6 @@ defmodule Pleroma.User.Search do query_string |> search_query(for_user, following) |> Pagination.fetch_paginated(%{"offset" => offset, "limit" => result_limit}, :offset) - |> Map.get(:items) end) results diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 8f07638cd..eeb826814 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -556,7 +556,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do q |> restrict_unlisted() |> Pagination.fetch_paginated(opts) - |> Map.get(:items) |> Enum.reverse() end @@ -954,7 +953,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do fetch_activities_query(recipients ++ list_memberships, opts) |> Pagination.fetch_paginated(opts) - |> Map.get(:items) |> Enum.reverse() |> maybe_update_cc(list_memberships, opts["user"]) end @@ -989,7 +987,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do fetch_activities_query([], opts) |> fetch_activities_bounded_query(recipients, recipients_with_public) |> Pagination.fetch_paginated(opts) - |> Map.get(:items) |> Enum.reverse() end diff --git a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex index d532ba685..83e877c0e 100644 --- a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex @@ -420,7 +420,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do [user.ap_id] |> ActivityPub.fetch_activities_query(params) |> Pagination.fetch_paginated(params) - |> Map.get(:items) conn |> add_link_headers(:dm_timeline, activities) @@ -1195,7 +1194,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do bookmarks = Bookmark.for_user_query(user.id) |> Pagination.fetch_paginated(params) - |> Map.get(:items) activities = bookmarks diff --git a/lib/pleroma/web/mastodon_api/mastodon_api.ex b/lib/pleroma/web/mastodon_api/mastodon_api.ex index cf3962944..ac01d1ff3 100644 --- a/lib/pleroma/web/mastodon_api/mastodon_api.ex +++ b/lib/pleroma/web/mastodon_api/mastodon_api.ex @@ -45,14 +45,12 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPI do user |> User.get_followers_query() |> Pagination.fetch_paginated(params) - |> Map.get(:items) end def get_friends(user, params \\ %{}) do user |> User.get_friends_query() |> Pagination.fetch_paginated(params) - |> Map.get(:items) end def get_notifications(user, params \\ %{}) do @@ -62,14 +60,12 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPI do |> Notification.for_user_query(options) |> restrict(:exclude_types, options) |> Pagination.fetch_paginated(params) - |> Map.get(:items) end def get_scheduled_activities(user, params \\ %{}) do user |> ScheduledActivity.for_user_query() |> Pagination.fetch_paginated(params) - |> Map.get(:items) end defp cast_params(params) do -- cgit v1.2.3 From cc1d1ee4069c47d2e5e91347438b2a6c7bff86cf Mon Sep 17 00:00:00 2001 From: rinpatch Date: Tue, 3 Sep 2019 17:54:21 +0300 Subject: Mastdon API: Add ability to get a remote account by nickname to `/api/v1/accounts/:id` --- lib/pleroma/plugs/trailing_format_plug.ex | 40 ++++++++++++++++++++++ lib/pleroma/user.ex | 8 +++-- lib/pleroma/web/endpoint.ex | 2 +- .../controllers/mastodon_api_controller.ex | 25 ++++++++++++-- 4 files changed, 70 insertions(+), 5 deletions(-) create mode 100644 lib/pleroma/plugs/trailing_format_plug.ex (limited to 'lib') diff --git a/lib/pleroma/plugs/trailing_format_plug.ex b/lib/pleroma/plugs/trailing_format_plug.ex new file mode 100644 index 000000000..2473e07fe --- /dev/null +++ b/lib/pleroma/plugs/trailing_format_plug.ex @@ -0,0 +1,40 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Plugs.TrailingFormatPlug do + @moduledoc "Calls TrailingFormatPlug for specific paths. Ideally we would just do this in the router, but TrailingFormatPlug needs to be called before Plug.Parsers." + + @behaviour Plug + @paths [ + "/api/statusnet", + "/api/statuses", + "/api/qvitter", + "/api/search", + "/api/account", + "/api/friends", + "/api/mutes", + "/api/media", + "/api/favorites", + "/api/blocks", + "/api/friendships", + "/api/users", + "/users", + "/nodeinfo", + "/api/help", + "/api/externalprofile", + "/notice" + ] + + def init(opts) do + TrailingFormatPlug.init(opts) + end + + for path <- @paths do + def call(%{request_path: unquote(path) <> _} = conn, opts) do + TrailingFormatPlug.call(conn, opts) + end + end + + def call(conn, _opts), do: conn +end diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 29fd6d2ea..d68015a80 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -569,8 +569,12 @@ defmodule Pleroma.User do end) end - def get_cached_by_nickname_or_id(nickname_or_id) do - get_cached_by_id(nickname_or_id) || get_cached_by_nickname(nickname_or_id) + def get_cached_by_nickname_or_id(nickname_or_id, opts \\ []) do + if is_integer(nickname_or_id) or Pleroma.FlakeId.is_flake_id?(nickname_or_id) do + get_cached_by_id(nickname_or_id) || get_cached_by_nickname(nickname_or_id) + else + unless opts[:restrict_remote_nicknames], do: get_cached_by_nickname(nickname_or_id) + end end def get_by_nickname(nickname) do diff --git a/lib/pleroma/web/endpoint.ex b/lib/pleroma/web/endpoint.ex index c123530dc..eb805e853 100644 --- a/lib/pleroma/web/endpoint.ex +++ b/lib/pleroma/web/endpoint.ex @@ -57,7 +57,7 @@ defmodule Pleroma.Web.Endpoint do plug(Phoenix.CodeReloader) end - plug(TrailingFormatPlug) + plug(Pleroma.Plugs.TrailingFormatPlug) plug(Plug.RequestId) plug(Plug.Logger) diff --git a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex index 83e877c0e..c5f281976 100644 --- a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex @@ -290,7 +290,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do end def user(%{assigns: %{user: for_user}} = conn, %{"id" => nickname_or_id}) do - with %User{} = user <- User.get_cached_by_nickname_or_id(nickname_or_id), + with %User{} = user <- get_user_by_nickname_or_id(for_user, nickname_or_id), true <- User.auth_active?(user) || user.id == for_user.id || User.superuser?(for_user) do account = AccountView.render("account.json", %{user: user, for: for_user}) json(conn, account) @@ -390,7 +390,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do end def user_statuses(%{assigns: %{user: reading_user}} = conn, params) do - with %User{} = user <- User.get_cached_by_nickname_or_id(params["id"]) do + with %User{} = user <- get_user_by_nickname_or_id(reading_user, params["id"]) do params = params |> Map.put("tag", params["tagged"]) @@ -1697,4 +1697,25 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do defp present?(nil), do: false defp present?(false), do: false defp present?(_), do: true + + defp get_user_by_nickname_or_id(for_user, nickname_or_id) do + restrict_to_local = Pleroma.Config.get([:instance, :limit_to_local_content]) + + opts = + cond do + restrict_to_local == :all -> + [restrict_remote_nicknames: true] + + restrict_to_local == false -> + [] + + restrict_to_local == :unauthenticated and match?(%User{}, for_user) -> + [] + + true -> + [restrict_remote_nicknames: true] + end + + User.get_cached_by_nickname_or_id(nickname_or_id, opts) + end end -- cgit v1.2.3 From c2b6c1b089a813cf8c7cbd54c0f80bee4985522c Mon Sep 17 00:00:00 2001 From: rinpatch Date: Wed, 4 Sep 2019 11:33:08 +0300 Subject: Extend `/api/pleroma/notifications/read` to mark multiple notifications as read and make it respond with Mastoapi entities --- lib/pleroma/notification.ex | 21 ++++++++++++++++-- .../web/pleroma_api/pleroma_api_controller.ex | 25 ++++++++++++++++++++++ lib/pleroma/web/router.ex | 7 +----- 3 files changed, 45 insertions(+), 8 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex index 5d29af853..d7e232992 100644 --- a/lib/pleroma/notification.ex +++ b/lib/pleroma/notification.ex @@ -102,15 +102,32 @@ defmodule Pleroma.Notification do n in Notification, where: n.user_id == ^user_id, where: n.id <= ^id, + where: n.seen == false, update: [ set: [ seen: true, updated_at: ^NaiveDateTime.utc_now() ] - ] + ], + # Ideally we would preload object and activities here + # but Ecto does not support preloads in update_all + select: n.id ) - Repo.update_all(query, []) + {_, notification_ids} = Repo.update_all(query, []) + + from(n in Notification, where: n.id in ^notification_ids) + |> join(:inner, [n], activity in assoc(n, :activity)) + |> join(:left, [n, a], object in Object, + on: + fragment( + "(?->>'id') = COALESCE((? -> 'object'::text) ->> 'id'::text)", + object.data, + a.data + ) + ) + |> preload([n, a, o], activity: {a, object: o}) + |> Repo.all() end def read_one(%User{} = user, notification_id) do diff --git a/lib/pleroma/web/pleroma_api/pleroma_api_controller.ex b/lib/pleroma/web/pleroma_api/pleroma_api_controller.ex index b6d2bf86b..f4df3b024 100644 --- a/lib/pleroma/web/pleroma_api/pleroma_api_controller.ex +++ b/lib/pleroma/web/pleroma_api/pleroma_api_controller.ex @@ -8,8 +8,10 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do import Pleroma.Web.ControllerHelper, only: [add_link_headers: 7] alias Pleroma.Conversation.Participation + alias Pleroma.Notification alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.MastodonAPI.ConversationView + alias Pleroma.Web.MastodonAPI.NotificationView alias Pleroma.Web.MastodonAPI.StatusView def conversation(%{assigns: %{user: user}} = conn, %{"id" => participation_id}) do @@ -70,4 +72,27 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do |> render("participation.json", %{participation: participation, for: user}) end end + + def read_notification(%{assigns: %{user: user}} = conn, %{"id" => notification_id}) do + with {:ok, notification} <- Notification.read_one(user, notification_id) do + conn + |> put_view(NotificationView) + |> render("show.json", %{notification: notification, for: user}) + else + {:error, message} -> + conn + |> put_status(:bad_request) + |> json(%{"error" => message}) + end + end + + def read_notification(%{assigns: %{user: user}} = conn, %{"max_id" => max_id}) do + with notifications <- Notification.set_read_up_to(user, max_id) do + notifications = Enum.take(notifications, 80) + + conn + |> put_view(NotificationView) + |> render("index.json", %{notifications: notifications, for: user}) + end + end end diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 969dc66fd..44a4279f7 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -236,12 +236,6 @@ defmodule Pleroma.Web.Router do post("/blocks_import", UtilController, :blocks_import) post("/follow_import", UtilController, :follow_import) end - - scope [] do - pipe_through(:oauth_read) - - post("/notifications/read", UtilController, :notifications_read) - end end scope "/oauth", Pleroma.Web.OAuth do @@ -277,6 +271,7 @@ defmodule Pleroma.Web.Router do scope [] do pipe_through(:oauth_write) patch("/conversations/:id", PleromaAPIController, :update_conversation) + post("/notifications/read", PleromaAPIController, :read_notification) end end -- cgit v1.2.3 From 7c3838090f86fbfdbf4e45fcfbabc21c19f26924 Mon Sep 17 00:00:00 2001 From: rinpatch Date: Wed, 4 Sep 2019 10:14:15 +0000 Subject: Apply suggestion to lib/pleroma/notification.ex --- lib/pleroma/notification.ex | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex index d7e232992..b7c880c51 100644 --- a/lib/pleroma/notification.ex +++ b/lib/pleroma/notification.ex @@ -116,7 +116,8 @@ defmodule Pleroma.Notification do {_, notification_ids} = Repo.update_all(query, []) - from(n in Notification, where: n.id in ^notification_ids) + Notification + |> where([n], n.id in ^notification_ids) |> join(:inner, [n], activity in assoc(n, :activity)) |> join(:left, [n, a], object in Object, on: -- cgit v1.2.3 From 3face454671bfdf2b850daf9dcb05468eb909e95 Mon Sep 17 00:00:00 2001 From: rinpatch Date: Wed, 4 Sep 2019 14:16:56 +0300 Subject: Mastodon API: Add `pleroma.thread_muted` to Status entity Needed for pleroma-fe!941 --- lib/pleroma/web/mastodon_api/views/status_view.ex | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index a4ee0b5dd..4c3c8c564 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -299,7 +299,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do content: %{"text/plain" => content_plaintext}, spoiler_text: %{"text/plain" => summary_plaintext}, expires_at: expires_at, - direct_conversation_id: direct_conversation_id + direct_conversation_id: direct_conversation_id, + thread_muted: thread_muted? } } end -- cgit v1.2.3 From 558969a0fd7f64387e59a54b5733d63d3a46a031 Mon Sep 17 00:00:00 2001 From: rinpatch Date: Thu, 5 Sep 2019 08:32:49 +0300 Subject: Do not crash if one notification failed to render --- lib/pleroma/web/mastodon_api/views/notification_view.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/web/mastodon_api/views/notification_view.ex b/lib/pleroma/web/mastodon_api/views/notification_view.ex index 27e9cab06..ec8eadcaa 100644 --- a/lib/pleroma/web/mastodon_api/views/notification_view.ex +++ b/lib/pleroma/web/mastodon_api/views/notification_view.ex @@ -14,7 +14,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationView do alias Pleroma.Web.MastodonAPI.StatusView def render("index.json", %{notifications: notifications, for: user}) do - render_many(notifications, NotificationView, "show.json", %{for: user}) + safe_render_many(notifications, NotificationView, "show.json", %{for: user}) end def render("show.json", %{ -- cgit v1.2.3 From b312ca3d528305ebc3b0c72799af535a406ce251 Mon Sep 17 00:00:00 2001 From: rinpatch Date: Thu, 5 Sep 2019 11:58:02 +0300 Subject: Mastodon API Poll view: Fix handling of polls without an end date --- lib/pleroma/web/mastodon_api/views/status_view.ex | 33 +++++++++++++++-------- 1 file changed, 22 insertions(+), 11 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index 4c3c8c564..e71083b91 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -385,16 +385,27 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do end if options do - end_time = - (object.data["closed"] || object.data["endTime"]) - |> NaiveDateTime.from_iso8601!() - - expired = - end_time - |> NaiveDateTime.compare(NaiveDateTime.utc_now()) - |> case do - :lt -> true - _ -> false + {end_time, expired} = + case object.data["closed"] || object.data["endTime"] do + end_time when is_binary(end_time) -> + end_time = + (object.data["closed"] || object.data["endTime"]) + |> NaiveDateTime.from_iso8601!() + + expired = + end_time + |> NaiveDateTime.compare(NaiveDateTime.utc_now()) + |> case do + :lt -> true + _ -> false + end + + end_time = Utils.to_masto_date(end_time) + + {end_time, expired} + + _ -> + {nil, false} end voted = @@ -421,7 +432,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do # Mastodon uses separate ids for polls, but an object can't have # more than one poll embedded so object id is fine id: to_string(object.id), - expires_at: Utils.to_masto_date(end_time), + expires_at: end_time, expired: expired, multiple: multiple, votes_count: votes_count, -- cgit v1.2.3 From 26fe6f70c9cd6a37e72f4795a1a9a316ef5d95fb Mon Sep 17 00:00:00 2001 From: rinpatch Date: Thu, 5 Sep 2019 15:33:49 +0300 Subject: Move checking for restrict_local to User.get_cached_by_id_or_nickname --- lib/pleroma/user.ex | 18 ++++++++++++---- .../controllers/mastodon_api_controller.ex | 25 ++-------------------- 2 files changed, 16 insertions(+), 27 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index d68015a80..3aa245f2a 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -570,10 +570,20 @@ defmodule Pleroma.User do end def get_cached_by_nickname_or_id(nickname_or_id, opts \\ []) do - if is_integer(nickname_or_id) or Pleroma.FlakeId.is_flake_id?(nickname_or_id) do - get_cached_by_id(nickname_or_id) || get_cached_by_nickname(nickname_or_id) - else - unless opts[:restrict_remote_nicknames], do: get_cached_by_nickname(nickname_or_id) + restrict_to_local = Pleroma.Config.get([:instance, :limit_to_local_content]) + + cond do + is_integer(nickname_or_id) or Pleroma.FlakeId.is_flake_id?(nickname_or_id) -> + get_cached_by_id(nickname_or_id) || get_cached_by_nickname(nickname_or_id) + + restrict_to_local == false -> + get_cached_by_nickname(nickname_or_id) + + restrict_to_local == :unauthenticated and match?(%User{}, opts[:for]) -> + get_cached_by_nickname(nickname_or_id) + + true -> + nil end end diff --git a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex index c5f281976..8dfad7a54 100644 --- a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex @@ -290,7 +290,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do end def user(%{assigns: %{user: for_user}} = conn, %{"id" => nickname_or_id}) do - with %User{} = user <- get_user_by_nickname_or_id(for_user, nickname_or_id), + with %User{} = user <- User.get_cached_by_nickname_or_id(nickname_or_id, for: for_user), true <- User.auth_active?(user) || user.id == for_user.id || User.superuser?(for_user) do account = AccountView.render("account.json", %{user: user, for: for_user}) json(conn, account) @@ -390,7 +390,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do end def user_statuses(%{assigns: %{user: reading_user}} = conn, params) do - with %User{} = user <- get_user_by_nickname_or_id(reading_user, params["id"]) do + with %User{} = user <- User.get_cached_by_nickname_or_id(params["id"], for: reading_user) do params = params |> Map.put("tag", params["tagged"]) @@ -1697,25 +1697,4 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do defp present?(nil), do: false defp present?(false), do: false defp present?(_), do: true - - defp get_user_by_nickname_or_id(for_user, nickname_or_id) do - restrict_to_local = Pleroma.Config.get([:instance, :limit_to_local_content]) - - opts = - cond do - restrict_to_local == :all -> - [restrict_remote_nicknames: true] - - restrict_to_local == false -> - [] - - restrict_to_local == :unauthenticated and match?(%User{}, for_user) -> - [] - - true -> - [restrict_remote_nicknames: true] - end - - User.get_cached_by_nickname_or_id(nickname_or_id, opts) - end end -- cgit v1.2.3 From 3523bdcf262dddc7bdf14d759538097f8838cddb Mon Sep 17 00:00:00 2001 From: rinpatch Date: Thu, 5 Sep 2019 22:21:20 +0300 Subject: Call TrailingFormatPlug for /api/pleroma/emoji Apparently Pleroma-FE still calls it with trailing '.json' --- lib/pleroma/plugs/trailing_format_plug.ex | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/plugs/trailing_format_plug.ex b/lib/pleroma/plugs/trailing_format_plug.ex index 2473e07fe..ce366b218 100644 --- a/lib/pleroma/plugs/trailing_format_plug.ex +++ b/lib/pleroma/plugs/trailing_format_plug.ex @@ -23,7 +23,8 @@ defmodule Pleroma.Plugs.TrailingFormatPlug do "/nodeinfo", "/api/help", "/api/externalprofile", - "/notice" + "/notice", + "/api/pleroma/emoji" ] def init(opts) do -- cgit v1.2.3 From 5effb2cbca51534a68dad1c5a4dd24b1ae08360a Mon Sep 17 00:00:00 2001 From: Ariadne Conill Date: Fri, 6 Sep 2019 23:11:26 +0000 Subject: activitypub: help ecto build a better query for thread mute filtering using an indexed value in thread_mute table helps ecto build a better query. --- lib/pleroma/web/activity_pub/activity_pub.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index eeb826814..d23ec66ac 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -796,7 +796,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do ) unless opts["skip_preload"] do - from([thread_mute: tm] in query, where: is_nil(tm)) + from([thread_mute: tm] in query, where: is_nil(tm.user_id)) else query end -- cgit v1.2.3 From 40a61532cadbac8b196917c6f5843c3f6cd7e78b Mon Sep 17 00:00:00 2001 From: Ariadne Conill Date: Fri, 6 Sep 2019 23:14:29 +0000 Subject: activity: when restricting deactivated users, precalculate the user list the PostgreSQL query planner is easily confused due to the complexity of certain queries we make. while we plan to simplify these queries through unification of activities and objects, we are not yet there. it has been discovered that using a precalculated list of deactivated users encourages the query planner to prefer simpler indices instead of the activity_visibility index. accordingly, drop the subquery and precalc the user list instead. --- lib/pleroma/activity.ex | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/activity.ex b/lib/pleroma/activity.ex index 2d4e9da0c..a7844c36b 100644 --- a/lib/pleroma/activity.ex +++ b/lib/pleroma/activity.ex @@ -362,12 +362,12 @@ defmodule Pleroma.Activity do end def restrict_deactivated_users(query) do + deactivated_users = + from(u in User.Query.build(deactivated: true), select: u.ap_id) + |> Repo.all() + from(activity in query, - where: - fragment( - "? not in (SELECT ap_id FROM users WHERE info->'deactivated' @> 'true')", - activity.actor - ) + where: activity.actor not in ^deactivated_users ) end -- cgit v1.2.3 From e5c6bf3673a8361d1417eba1ccc44edec7658ac4 Mon Sep 17 00:00:00 2001 From: shadowfacts Date: Sat, 7 Sep 2019 19:50:45 +0000 Subject: Mastodon API: URI encode hashtag name in generated URLs Otherwise hashtags with word characters other than those allowed in URLs (e.g. Japanese characters) produce hashtag URLs that are invalid. --- lib/pleroma/web/mastodon_api/views/status_view.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index e71083b91..708b8c2fd 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -499,7 +499,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do object_tags = for tag when is_binary(tag) <- object_tags, do: tag Enum.reduce(object_tags, [], fn tag, tags -> - tags ++ [%{name: tag, url: "/tag/#{tag}"}] + tags ++ [%{name: tag, url: "/tag/#{URI.encode(tag)}"}] end) end -- cgit v1.2.3 From e0f84d0043d922f7cc88875a0bc52f2db8972b76 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Tue, 10 Sep 2019 01:11:57 +0700 Subject: Fix `ActivityPubController.read_inbox/2` --- .../web/activity_pub/activity_pub_controller.ex | 44 ++++++++++++++-------- 1 file changed, 29 insertions(+), 15 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex index 08bf1c752..7b0075477 100644 --- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex +++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex @@ -251,22 +251,36 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do def whoami(_conn, _params), do: {:error, :not_found} - def read_inbox(%{assigns: %{user: user}} = conn, %{"nickname" => nickname} = params) do - if nickname == user.nickname do - conn - |> put_resp_content_type("application/activity+json") - |> json(UserView.render("inbox.json", %{user: user, max_id: params["max_id"]})) - else - err = - dgettext("errors", "can't read inbox of %{nickname} as %{as_nickname}", - nickname: nickname, - as_nickname: user.nickname - ) + def read_inbox( + %{assigns: %{user: %{nickname: nickname} = user}} = conn, + %{"nickname" => nickname} = params + ) do + conn + |> put_resp_content_type("application/activity+json") + |> put_view(UserView) + |> render("inbox.json", user: user, max_id: params["max_id"]) + end - conn - |> put_status(:forbidden) - |> json(err) - end + def read_inbox(%{assigns: %{user: nil}} = conn, %{"nickname" => nickname}) do + err = dgettext("errors", "can't read inbox of %{nickname}", nickname: nickname) + + conn + |> put_status(:forbidden) + |> json(err) + end + + def read_inbox(%{assigns: %{user: %{nickname: as_nickname}}} = conn, %{ + "nickname" => nickname + }) do + err = + dgettext("errors", "can't read inbox of %{nickname} as %{as_nickname}", + nickname: nickname, + as_nickname: as_nickname + ) + + conn + |> put_status(:forbidden) + |> json(err) end def handle_user_activity(user, %{"type" => "Create"} = params) do -- cgit v1.2.3 From 11e12b5761bcd67aa609d91f6f8d1f6755b2312b Mon Sep 17 00:00:00 2001 From: minibikini Date: Mon, 9 Sep 2019 18:53:08 +0000 Subject: Add Pleroma.Plugs.Cache --- lib/pleroma/activity.ex | 9 ++ lib/pleroma/application.ex | 3 +- lib/pleroma/object.ex | 6 +- lib/pleroma/plugs/cache.ex | 122 +++++++++++++++++++++ .../web/activity_pub/activity_pub_controller.ex | 33 +++++- 5 files changed, 166 insertions(+), 7 deletions(-) create mode 100644 lib/pleroma/plugs/cache.ex (limited to 'lib') diff --git a/lib/pleroma/activity.ex b/lib/pleroma/activity.ex index a7844c36b..6a51d4cf3 100644 --- a/lib/pleroma/activity.ex +++ b/lib/pleroma/activity.ex @@ -308,10 +308,19 @@ defmodule Pleroma.Activity do %{data: %{"type" => "Create", "object" => %{"id" => ap_id}}} -> ap_id == id _ -> nil end) + |> purge_web_resp_cache() end def delete_by_ap_id(_), do: nil + defp purge_web_resp_cache(%Activity{} = activity) do + %{path: path} = URI.parse(activity.data["id"]) + Cachex.del(:web_resp_cache, path) + activity + end + + defp purge_web_resp_cache(nil), do: nil + for {ap_type, type} <- @mastodon_notification_types do def mastodon_notification_type(%Activity{data: %{"type" => unquote(ap_type)}}), do: unquote(type) diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex index 483ac1f39..1d46925f8 100644 --- a/lib/pleroma/application.ex +++ b/lib/pleroma/application.ex @@ -116,7 +116,8 @@ defmodule Pleroma.Application do build_cachex("object", default_ttl: 25_000, ttl_interval: 1000, limit: 2500), build_cachex("rich_media", default_ttl: :timer.minutes(120), limit: 5000), build_cachex("scrubber", limit: 2500), - build_cachex("idempotency", expiration: idempotency_expiration(), limit: 2500) + build_cachex("idempotency", expiration: idempotency_expiration(), limit: 2500), + build_cachex("web_resp", limit: 2500) ] end diff --git a/lib/pleroma/object.ex b/lib/pleroma/object.ex index d58eb7f7d..5033798ae 100644 --- a/lib/pleroma/object.ex +++ b/lib/pleroma/object.ex @@ -130,14 +130,16 @@ defmodule Pleroma.Object do def delete(%Object{data: %{"id" => id}} = object) do with {:ok, _obj} = swap_object_with_tombstone(object), deleted_activity = Activity.delete_by_ap_id(id), - {:ok, true} <- Cachex.del(:object_cache, "object:#{id}") do + {:ok, true} <- Cachex.del(:object_cache, "object:#{id}"), + {:ok, _} <- Cachex.del(:web_resp_cache, URI.parse(id).path) do {:ok, object, deleted_activity} end end def prune(%Object{data: %{"id" => id}} = object) do with {:ok, object} <- Repo.delete(object), - {:ok, true} <- Cachex.del(:object_cache, "object:#{id}") do + {:ok, true} <- Cachex.del(:object_cache, "object:#{id}"), + {:ok, _} <- Cachex.del(:web_resp_cache, URI.parse(id).path) do {:ok, object} end end diff --git a/lib/pleroma/plugs/cache.ex b/lib/pleroma/plugs/cache.ex new file mode 100644 index 000000000..a81a861d0 --- /dev/null +++ b/lib/pleroma/plugs/cache.ex @@ -0,0 +1,122 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Plugs.Cache do + @moduledoc """ + Caches successful GET responses. + + To enable the cache add the plug to a router pipeline or controller: + + plug(Pleroma.Plugs.Cache) + + ## Configuration + + To configure the plug you need to pass settings as the second argument to the `plug/2` macro: + + plug(Pleroma.Plugs.Cache, [ttl: nil, query_params: true]) + + Available options: + + - `ttl`: An expiration time (time-to-live). This value should be in milliseconds or `nil` to disable expiration. Defaults to `nil`. + - `query_params`: Take URL query string into account (`true`), ignore it (`false`) or limit to specific params only (list). Defaults to `true`. + + Additionally, you can overwrite the TTL inside a controller action by assigning `cache_ttl` to the connection struct: + + def index(conn, _params) do + ttl = 60_000 # one minute + + conn + |> assign(:cache_ttl, ttl) + |> render("index.html") + end + + """ + + import Phoenix.Controller, only: [current_path: 1, json: 2] + import Plug.Conn + + @behaviour Plug + + @defaults %{ttl: nil, query_params: true} + + @impl true + def init([]), do: @defaults + + def init(opts) do + opts = Map.new(opts) + Map.merge(@defaults, opts) + end + + @impl true + def call(%{method: "GET"} = conn, opts) do + key = cache_key(conn, opts) + + case Cachex.get(:web_resp_cache, key) do + {:ok, nil} -> + cache_resp(conn, opts) + + {:ok, record} -> + send_cached(conn, record) + + {atom, message} when atom in [:ignore, :error] -> + render_error(conn, message) + end + end + + def call(conn, _), do: conn + + # full path including query params + defp cache_key(conn, %{query_params: true}), do: current_path(conn) + + # request path without query params + defp cache_key(conn, %{query_params: false}), do: conn.request_path + + # request path with specific query params + defp cache_key(conn, %{query_params: query_params}) when is_list(query_params) do + query_string = + conn.params + |> Map.take(query_params) + |> URI.encode_query() + + conn.request_path <> "?" <> query_string + end + + defp cache_resp(conn, opts) do + register_before_send(conn, fn + %{status: 200, resp_body: body} = conn -> + ttl = Map.get(conn.assigns, :cache_ttl, opts.ttl) + key = cache_key(conn, opts) + content_type = content_type(conn) + record = {content_type, body} + + Cachex.put(:web_resp_cache, key, record, ttl: ttl) + + put_resp_header(conn, "x-cache", "MISS from Pleroma") + + conn -> + conn + end) + end + + defp content_type(conn) do + conn + |> Plug.Conn.get_resp_header("content-type") + |> hd() + end + + defp send_cached(conn, {content_type, body}) do + conn + |> put_resp_content_type(content_type, nil) + |> put_resp_header("x-cache", "HIT from Pleroma") + |> send_resp(:ok, body) + |> halt() + end + + defp render_error(conn, message) do + conn + |> put_status(:internal_server_error) + |> json(%{error: message}) + |> halt() + end +end diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex index 7b0075477..705dbc1c2 100644 --- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex +++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex @@ -23,6 +23,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do action_fallback(:errors) + plug(Pleroma.Plugs.Cache, [query_params: false] when action in [:activity, :object]) plug(Pleroma.Web.FederatingPlug when action in [:inbox, :relay]) plug(:set_requester_reachable when action in [:inbox]) plug(:relay_active? when action in [:relay]) @@ -53,8 +54,10 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do %Object{} = object <- Object.get_cached_by_ap_id(ap_id), {_, true} <- {:public?, Visibility.is_public?(object)} do conn + |> set_cache_ttl_for(object) |> put_resp_content_type("application/activity+json") - |> json(ObjectView.render("object.json", %{object: object})) + |> put_view(ObjectView) + |> render("object.json", object: object) else {:public?, false} -> {:error, :not_found} @@ -96,14 +99,36 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do %Activity{} = activity <- Activity.normalize(ap_id), {_, true} <- {:public?, Visibility.is_public?(activity)} do conn + |> set_cache_ttl_for(activity) |> put_resp_content_type("application/activity+json") - |> json(ObjectView.render("object.json", %{object: activity})) + |> put_view(ObjectView) + |> render("object.json", object: activity) else - {:public?, false} -> - {:error, :not_found} + {:public?, false} -> {:error, :not_found} + nil -> {:error, :not_found} end end + defp set_cache_ttl_for(conn, %Activity{object: object}) do + set_cache_ttl_for(conn, object) + end + + defp set_cache_ttl_for(conn, entity) do + ttl = + case entity do + %Object{data: %{"type" => "Question"}} -> + Pleroma.Config.get([:web_cache_ttl, :activity_pub_question]) + + %Object{} -> + Pleroma.Config.get([:web_cache_ttl, :activity_pub]) + + _ -> + nil + end + + assign(conn, :cache_ttl, ttl) + end + # GET /relay/following def following(%{assigns: %{relay: true}} = conn, _params) do conn -- cgit v1.2.3 From b40b10b53d00d13f24b5667acc02b1642abc6ec4 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Tue, 3 Sep 2019 16:23:03 +0700 Subject: Add an endpoint to get multiple statuses by IDs --- lib/pleroma/activity.ex | 7 +++++++ .../mastodon_api/controllers/mastodon_api_controller.ex | 14 ++++++++++++++ lib/pleroma/web/router.ex | 1 + 3 files changed, 22 insertions(+) (limited to 'lib') diff --git a/lib/pleroma/activity.ex b/lib/pleroma/activity.ex index 6a51d4cf3..44f1e3011 100644 --- a/lib/pleroma/activity.ex +++ b/lib/pleroma/activity.ex @@ -173,6 +173,13 @@ defmodule Pleroma.Activity do |> Repo.one() end + def all_by_ids_with_object(ids) do + Activity + |> where([a], a.id in ^ids) + |> with_preloaded_object() + |> Repo.all() + end + def by_object_ap_id(ap_id) do from( activity in Activity, diff --git a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex index 8dfad7a54..c54462bb3 100644 --- a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex @@ -427,6 +427,20 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do |> render("index.json", %{activities: activities, for: user, as: :activity}) end + def get_statuses(%{assigns: %{user: user}} = conn, %{"ids" => ids}) do + limit = 100 + + activities = + ids + |> Enum.take(limit) + |> Activity.all_by_ids_with_object() + |> Enum.filter(&Visibility.visible_for_user?(&1, user)) + + conn + |> put_view(StatusView) + |> render("index.json", activities: activities, for: user, as: :activity) + end + def get_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do with %Activity{} = activity <- Activity.get_by_id_with_object(id), true <- Visibility.visible_for_user?(activity, user) do diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index cfb973f53..7cd59acb2 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -443,6 +443,7 @@ defmodule Pleroma.Web.Router do get("/timelines/tag/:tag", MastodonAPIController, :hashtag_timeline) get("/timelines/list/:list_id", MastodonAPIController, :list_timeline) + get("/statuses", MastodonAPIController, :get_statuses) get("/statuses/:id", MastodonAPIController, :get_status) get("/statuses/:id/context", MastodonAPIController, :get_context) -- cgit v1.2.3 From 43f02dfe38547e07fb189aab78539af9e02302b3 Mon Sep 17 00:00:00 2001 From: rinpatch Date: Tue, 10 Sep 2019 22:01:45 +0300 Subject: Revert "Parallelize template rendering" This reverts commit 1ad71592adb47762287aec8c36d0fca565c38362. Since it had no limit on the number on concurrent processes it OOM killed instances while rendering hellthreads. When I tried introducing a concurrency limit with Task.async_stream/manual folds it lead to about 3 times worse performance on threads larger than 1000 activities (we are talking 30s vs 1.2 minutes), I think this is not worth the about 1.5 times performance increase on smaller threads when using it. --- lib/mix/tasks/pleroma/benchmark.ex | 38 ++++++----------------- lib/pleroma/web/mastodon_api/views/status_view.ex | 4 +-- lib/pleroma/web/web.ex | 18 ++--------- 3 files changed, 12 insertions(+), 48 deletions(-) (limited to 'lib') diff --git a/lib/mix/tasks/pleroma/benchmark.ex b/lib/mix/tasks/pleroma/benchmark.ex index 4cc634727..a45940bf3 100644 --- a/lib/mix/tasks/pleroma/benchmark.ex +++ b/lib/mix/tasks/pleroma/benchmark.ex @@ -37,37 +37,17 @@ defmodule Mix.Tasks.Pleroma.Benchmark do |> Map.put("blocking_user", user) |> Map.put("muting_user", user) |> Map.put("user", user) - |> Map.put("limit", 80) |> Pleroma.Web.ActivityPub.ActivityPub.fetch_public_activities() |> Enum.reverse() - inputs = %{ - "One activity" => Enum.take_random(activities, 1), - "Ten activities" => Enum.take_random(activities, 10), - "Twenty activities" => Enum.take_random(activities, 20), - "Forty activities" => Enum.take_random(activities, 40), - "Eighty activities" => Enum.take_random(activities, 80) - } - - Benchee.run( - %{ - "Parallel rendering" => fn activities -> - Pleroma.Web.MastodonAPI.StatusView.render("index.json", %{ - activities: activities, - for: user, - as: :activity - }) - end, - "Standart rendering" => fn activities -> - Pleroma.Web.MastodonAPI.StatusView.render("index.json", %{ - activities: activities, - for: user, - as: :activity, - parallel: false - }) - end - }, - inputs: inputs - ) + Benchee.run(%{ + "render_timeline" => fn -> + Pleroma.Web.MastodonAPI.StatusView.render("index.json", %{ + activities: activities, + for: user, + as: :activity + }) + end + }) end end diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index e71083b91..b6a3431f9 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -73,14 +73,12 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do def render("index.json", opts) do replied_to_activities = get_replied_to_activities(opts.activities) - parallel = unless is_nil(opts[:parallel]), do: opts[:parallel], else: true opts.activities |> safe_render_many( StatusView, "status.json", - Map.put(opts, :replied_to_activities, replied_to_activities), - parallel + Map.put(opts, :replied_to_activities, replied_to_activities) ) end diff --git a/lib/pleroma/web/web.ex b/lib/pleroma/web/web.ex index bfb6c7287..687346554 100644 --- a/lib/pleroma/web/web.ex +++ b/lib/pleroma/web/web.ex @@ -66,23 +66,9 @@ defmodule Pleroma.Web do end @doc """ - Same as `render_many/4` but wrapped in rescue block and parallelized (unless disabled by passing false as a fifth argument). + Same as `render_many/4` but wrapped in rescue block. """ - def safe_render_many(collection, view, template, assigns \\ %{}, parallel \\ true) - - def safe_render_many(collection, view, template, assigns, true) do - Enum.map(collection, fn resource -> - Task.async(fn -> - as = Map.get(assigns, :as) || view.__resource__ - assigns = Map.put(assigns, as, resource) - safe_render(view, template, assigns) - end) - end) - |> Enum.map(&Task.await(&1, :infinity)) - |> Enum.filter(& &1) - end - - def safe_render_many(collection, view, template, assigns, false) do + def safe_render_many(collection, view, template, assigns \\ %{}) do Enum.map(collection, fn resource -> as = Map.get(assigns, :as) || view.__resource__ assigns = Map.put(assigns, as, resource) -- cgit v1.2.3 From 67e430093187c50f307810e88ed0e73afe825b75 Mon Sep 17 00:00:00 2001 From: Alex S Date: Fri, 30 Aug 2019 13:21:48 +0300 Subject: description formatters --- lib/pleroma/docs/formatter.ex | 69 +++++++++++++++++++++++++++++++++++++++++++ lib/pleroma/docs/json.ex | 15 ++++++++++ lib/pleroma/docs/markdown.ex | 67 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 151 insertions(+) create mode 100644 lib/pleroma/docs/formatter.ex create mode 100644 lib/pleroma/docs/json.ex create mode 100644 lib/pleroma/docs/markdown.ex (limited to 'lib') diff --git a/lib/pleroma/docs/formatter.ex b/lib/pleroma/docs/formatter.ex new file mode 100644 index 000000000..a1c757936 --- /dev/null +++ b/lib/pleroma/docs/formatter.ex @@ -0,0 +1,69 @@ +defmodule Pleroma.Docs.Formatter do + @callback process(keyword()) :: {:ok, String.t()} + + @spec process(module(), keyword()) :: {:ok, String.t()} + def process(implementation, descriptions) do + implementation.process(descriptions) + end + + def uploaders_list do + {:ok, modules} = :application.get_key(:pleroma, :modules) + + Enum.filter(modules, fn module -> + name_as_list = Module.split(module) + + List.starts_with?(name_as_list, ["Pleroma", "Uploaders"]) and + List.last(name_as_list) in ["S3", "Local", "MDII"] + end) + end + + def filters_list do + {:ok, modules} = :application.get_key(:pleroma, :modules) + + Enum.filter(modules, fn module -> + name_as_list = Module.split(module) + + List.starts_with?(name_as_list, ["Pleroma", "Upload", "Filter"]) + end) + end + + def mrf_list do + {:ok, modules} = :application.get_key(:pleroma, :modules) + + Enum.filter(modules, fn module -> + name_as_list = Module.split(module) + + List.starts_with?(name_as_list, ["Pleroma", "Web", "ActivityPub", "MRF"]) and + length(name_as_list) > 4 + end) + end + + def richmedia_parsers do + {:ok, modules} = :application.get_key(:pleroma, :modules) + + Enum.filter(modules, fn module -> + name_as_list = Module.split(module) + + List.starts_with?(name_as_list, ["Pleroma", "Web", "RichMedia", "Parsers"]) and + length(name_as_list) == 5 + end) + end +end + +defimpl Jason.Encoder, for: Tuple do + def encode(tuple, opts) do + Jason.Encode.list(Tuple.to_list(tuple), opts) + end +end + +defimpl Jason.Encoder, for: [Regex, Function] do + def encode(term, opts) do + Jason.Encode.string(inspect(term), opts) + end +end + +defimpl String.Chars, for: Regex do + def to_string(term) do + inspect(term) + end +end diff --git a/lib/pleroma/docs/json.ex b/lib/pleroma/docs/json.ex new file mode 100644 index 000000000..38f015017 --- /dev/null +++ b/lib/pleroma/docs/json.ex @@ -0,0 +1,15 @@ +defmodule Pleroma.Docs.JSON do + @behaviour Pleroma.Docs.Formatter + def process(descriptions) do + config_path = "docs/generate_config.json" + {:ok, file} = File.open(config_path, [:write]) + json = generate_json(descriptions) + IO.write(file, json) + :ok = File.close(file) + {:ok, config_path} + end + + def generate_json(descriptions) do + Jason.encode!(descriptions) + end +end diff --git a/lib/pleroma/docs/markdown.ex b/lib/pleroma/docs/markdown.ex new file mode 100644 index 000000000..27a096631 --- /dev/null +++ b/lib/pleroma/docs/markdown.ex @@ -0,0 +1,67 @@ +defmodule Pleroma.Docs.Markdown do + @behaviour Pleroma.Docs.Formatter + + def process(descriptions) do + config_path = "docs/config.md" + {:ok, file} = File.open(config_path, [:write]) + IO.write(file, "# Generated configuration\r\n\r\n") + IO.write(file, "Date of generation: #{Date.utc_today()}\r\n\r\n") + + IO.write( + file, + "This file describe the configuration, it is recommended to edit the relevant *.secret.exs file instead of the others founds in the ``config`` directory. +If you run Pleroma with ``MIX_ENV=prod`` the file is ``prod.secret.exs``, otherwise it is ``dev.secret.exs``.\r\n\r\n" + ) + + for group <- descriptions do + if is_nil(group[:key]) do + IO.write(file, "## #{inspect(group[:group])}\r\n\r\n") + else + IO.write(file, "## #{inspect(group[:key])}\r\n\r\n") + end + + IO.write(file, "Type: `#{group[:type]}` \r\n") + IO.write(file, "#{group[:description]} \r\n\r\n") + + for child <- group[:children] do + print_child_header(file, child) + + print_suggestions(file, child[:suggestions]) + + if child[:children] do + for subchild <- child[:children] do + print_child_header(file, subchild) + + print_suggestions(file, subchild[:suggestions]) + end + end + end + + IO.write(file, "\r\n") + end + + :ok = File.close(file) + {:ok, config_path} + end + + defp print_suggestion(file, suggestion) when is_function(suggestion) do + IO.write(file, " `#{inspect(suggestion.())}`\r\n") + end + + defp print_suggestion(file, suggestion) do + IO.write(file, " `#{inspect(suggestion)}`\r\n") + end + + defp print_suggestions(file, suggestions) do + IO.write(file, "Suggestions: \r\n") + + for suggestion <- suggestions do + print_suggestion(file, suggestion) + end + end + + defp print_child_header(file, child) do + IO.write(file, "* `#{inspect(child[:key])}`: #{child[:description]} \r\n") + IO.write(file, "Type: `#{inspect(child[:type])}` \r\n") + end +end -- cgit v1.2.3 From 511d93fa5486a58b63576ce1b8af551bbafe703f Mon Sep 17 00:00:00 2001 From: Alex S Date: Fri, 30 Aug 2019 13:22:21 +0300 Subject: mix docs generates config.md --- lib/mix/tasks/pleroma/docs.ex | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 lib/mix/tasks/pleroma/docs.ex (limited to 'lib') diff --git a/lib/mix/tasks/pleroma/docs.ex b/lib/mix/tasks/pleroma/docs.ex new file mode 100644 index 000000000..5ae3b8716 --- /dev/null +++ b/lib/mix/tasks/pleroma/docs.ex @@ -0,0 +1,40 @@ +defmodule Mix.Tasks.Pleroma.Docs do + use Mix.Task + import Mix.Pleroma + + @shortdoc "Generates docs from descriptions.exs" + @moduledoc """ + Generates docs from `descriptions.exs`. + + Supports two formats: `markdown` and `json`. + + ## Generate markdown docs + + `mix pleroma.docs` + + ## Generate json docs + + `mix pleroma.docs json`s + """ + + def run(["json"]) do + do_run(Pleroma.Docs.JSON) + end + + def run(_) do + do_run(Pleroma.Docs.Markdown) + end + + defp do_run(implementation) do + start_pleroma() + descriptions = Config.Reader.read!("config/description.exs") + + {:ok, file_path} = + Pleroma.Docs.Formatter.process( + implementation, + descriptions[:pleroma][:config_description] + ) + + Mix.shell().info([:green, "Markdown docs successfully generated to #{file_path}."]) + end +end -- cgit v1.2.3 From a1f2dfb10a777592ea85d4bf8c5f91c859ec225b Mon Sep 17 00:00:00 2001 From: Alex S Date: Fri, 30 Aug 2019 14:04:21 +0300 Subject: expanding regex sigils to use modifiers --- lib/pleroma/web/admin_api/config.ex | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/admin_api/config.ex b/lib/pleroma/web/admin_api/config.ex index a10cc779b..1917a5580 100644 --- a/lib/pleroma/web/admin_api/config.ex +++ b/lib/pleroma/web/admin_api/config.ex @@ -90,6 +90,8 @@ defmodule Pleroma.Web.AdminAPI.Config do for v <- entity, into: [], do: do_convert(v) end + defp do_convert(%Regex{} = entity), do: inspect(entity) + defp do_convert(entity) when is_map(entity) do for {k, v} <- entity, into: %{}, do: {do_convert(k), do_convert(v)} end @@ -122,7 +124,7 @@ defmodule Pleroma.Web.AdminAPI.Config do def transform(entity), do: :erlang.term_to_binary(entity) - defp do_transform(%Regex{} = entity) when is_map(entity), do: entity + defp do_transform(%Regex{} = entity), do: entity defp do_transform(%{"tuple" => [":dispatch", [entity]]}) do {dispatch_settings, []} = do_eval(entity) @@ -154,8 +156,15 @@ defmodule Pleroma.Web.AdminAPI.Config do defp do_transform(entity), do: entity defp do_transform_string("~r/" <> pattern) do - pattern = String.trim_trailing(pattern, "/") - ~r/#{pattern}/ + modificator = String.split(pattern, "/") |> List.last() + pattern = String.trim_trailing(pattern, "/" <> modificator) + + case modificator do + "" -> ~r/#{pattern}/ + "i" -> ~r/#{pattern}/i + "u" -> ~r/#{pattern}/u + "s" -> ~r/#{pattern}/s + end end defp do_transform_string(":" <> atom), do: String.to_atom(atom) -- cgit v1.2.3 From 0559c82bdb39cba019099d404723f38fed6e2aba Mon Sep 17 00:00:00 2001 From: Alex S Date: Fri, 30 Aug 2019 14:27:55 +0300 Subject: fix --- lib/mix/tasks/pleroma/docs.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/mix/tasks/pleroma/docs.ex b/lib/mix/tasks/pleroma/docs.ex index 5ae3b8716..d68e02383 100644 --- a/lib/mix/tasks/pleroma/docs.ex +++ b/lib/mix/tasks/pleroma/docs.ex @@ -27,7 +27,7 @@ defmodule Mix.Tasks.Pleroma.Docs do defp do_run(implementation) do start_pleroma() - descriptions = Config.Reader.read!("config/description.exs") + {descriptions, _paths} = Mix.Config.eval!("config/description.exs") {:ok, file_path} = Pleroma.Docs.Formatter.process( -- cgit v1.2.3 From 6721301086674afe721f9eea478a2037756be93c Mon Sep 17 00:00:00 2001 From: Alex S Date: Fri, 30 Aug 2019 19:14:01 +0300 Subject: some changes --- lib/mix/tasks/pleroma/docs.ex | 6 ++-- lib/pleroma/docs/formatter.ex | 69 ---------------------------------------- lib/pleroma/docs/generator.ex | 73 +++++++++++++++++++++++++++++++++++++++++++ lib/pleroma/docs/json.ex | 5 ++- lib/pleroma/docs/markdown.ex | 36 ++++++++++++++------- 5 files changed, 104 insertions(+), 85 deletions(-) delete mode 100644 lib/pleroma/docs/formatter.ex create mode 100644 lib/pleroma/docs/generator.ex (limited to 'lib') diff --git a/lib/mix/tasks/pleroma/docs.ex b/lib/mix/tasks/pleroma/docs.ex index d68e02383..821ee74f9 100644 --- a/lib/mix/tasks/pleroma/docs.ex +++ b/lib/mix/tasks/pleroma/docs.ex @@ -8,11 +8,11 @@ defmodule Mix.Tasks.Pleroma.Docs do Supports two formats: `markdown` and `json`. - ## Generate markdown docs + ## Generate Markdown docs `mix pleroma.docs` - ## Generate json docs + ## Generate JSON docs `mix pleroma.docs json`s """ @@ -30,7 +30,7 @@ defmodule Mix.Tasks.Pleroma.Docs do {descriptions, _paths} = Mix.Config.eval!("config/description.exs") {:ok, file_path} = - Pleroma.Docs.Formatter.process( + Pleroma.Docs.Generator.process( implementation, descriptions[:pleroma][:config_description] ) diff --git a/lib/pleroma/docs/formatter.ex b/lib/pleroma/docs/formatter.ex deleted file mode 100644 index a1c757936..000000000 --- a/lib/pleroma/docs/formatter.ex +++ /dev/null @@ -1,69 +0,0 @@ -defmodule Pleroma.Docs.Formatter do - @callback process(keyword()) :: {:ok, String.t()} - - @spec process(module(), keyword()) :: {:ok, String.t()} - def process(implementation, descriptions) do - implementation.process(descriptions) - end - - def uploaders_list do - {:ok, modules} = :application.get_key(:pleroma, :modules) - - Enum.filter(modules, fn module -> - name_as_list = Module.split(module) - - List.starts_with?(name_as_list, ["Pleroma", "Uploaders"]) and - List.last(name_as_list) in ["S3", "Local", "MDII"] - end) - end - - def filters_list do - {:ok, modules} = :application.get_key(:pleroma, :modules) - - Enum.filter(modules, fn module -> - name_as_list = Module.split(module) - - List.starts_with?(name_as_list, ["Pleroma", "Upload", "Filter"]) - end) - end - - def mrf_list do - {:ok, modules} = :application.get_key(:pleroma, :modules) - - Enum.filter(modules, fn module -> - name_as_list = Module.split(module) - - List.starts_with?(name_as_list, ["Pleroma", "Web", "ActivityPub", "MRF"]) and - length(name_as_list) > 4 - end) - end - - def richmedia_parsers do - {:ok, modules} = :application.get_key(:pleroma, :modules) - - Enum.filter(modules, fn module -> - name_as_list = Module.split(module) - - List.starts_with?(name_as_list, ["Pleroma", "Web", "RichMedia", "Parsers"]) and - length(name_as_list) == 5 - end) - end -end - -defimpl Jason.Encoder, for: Tuple do - def encode(tuple, opts) do - Jason.Encode.list(Tuple.to_list(tuple), opts) - end -end - -defimpl Jason.Encoder, for: [Regex, Function] do - def encode(term, opts) do - Jason.Encode.string(inspect(term), opts) - end -end - -defimpl String.Chars, for: Regex do - def to_string(term) do - inspect(term) - end -end diff --git a/lib/pleroma/docs/generator.ex b/lib/pleroma/docs/generator.ex new file mode 100644 index 000000000..e788712cc --- /dev/null +++ b/lib/pleroma/docs/generator.ex @@ -0,0 +1,73 @@ +defmodule Pleroma.Docs.Generator do + @callback process(keyword()) :: {:ok, String.t()} + + @spec process(module(), keyword()) :: {:ok, String.t()} + def process(implementation, descriptions) do + implementation.process(descriptions) + end + + @spec uploaders_list() :: [module()] + def uploaders_list do + {:ok, modules} = :application.get_key(:pleroma, :modules) + + Enum.filter(modules, fn module -> + name_as_list = Module.split(module) + + List.starts_with?(name_as_list, ["Pleroma", "Uploaders"]) and + List.last(name_as_list) in ["S3", "Local", "MDII"] + end) + end + + @spec filters_list() :: [module()] + def filters_list do + {:ok, modules} = :application.get_key(:pleroma, :modules) + + Enum.filter(modules, fn module -> + name_as_list = Module.split(module) + + List.starts_with?(name_as_list, ["Pleroma", "Upload", "Filter"]) + end) + end + + @spec mrf_list() :: [module()] + def mrf_list do + {:ok, modules} = :application.get_key(:pleroma, :modules) + + Enum.filter(modules, fn module -> + name_as_list = Module.split(module) + + List.starts_with?(name_as_list, ["Pleroma", "Web", "ActivityPub", "MRF"]) and + length(name_as_list) > 4 + end) + end + + @spec richmedia_parsers() :: [module()] + def richmedia_parsers do + {:ok, modules} = :application.get_key(:pleroma, :modules) + + Enum.filter(modules, fn module -> + name_as_list = Module.split(module) + + List.starts_with?(name_as_list, ["Pleroma", "Web", "RichMedia", "Parsers"]) and + length(name_as_list) == 5 + end) + end +end + +defimpl Jason.Encoder, for: Tuple do + def encode(tuple, opts) do + Jason.Encode.list(Tuple.to_list(tuple), opts) + end +end + +defimpl Jason.Encoder, for: [Regex, Function] do + def encode(term, opts) do + Jason.Encode.string(inspect(term), opts) + end +end + +defimpl String.Chars, for: Regex do + def to_string(term) do + inspect(term) + end +end diff --git a/lib/pleroma/docs/json.ex b/lib/pleroma/docs/json.ex index 38f015017..aed730e78 100644 --- a/lib/pleroma/docs/json.ex +++ b/lib/pleroma/docs/json.ex @@ -1,5 +1,7 @@ defmodule Pleroma.Docs.JSON do - @behaviour Pleroma.Docs.Formatter + @behaviour Pleroma.Docs.Generator + + @spec process(keyword()) :: {:ok, String.t()} def process(descriptions) do config_path = "docs/generate_config.json" {:ok, file} = File.open(config_path, [:write]) @@ -9,6 +11,7 @@ defmodule Pleroma.Docs.JSON do {:ok, config_path} end + @spec generate_json([keyword()]) :: String.t() def generate_json(descriptions) do Jason.encode!(descriptions) end diff --git a/lib/pleroma/docs/markdown.ex b/lib/pleroma/docs/markdown.ex index 27a096631..c66640bf1 100644 --- a/lib/pleroma/docs/markdown.ex +++ b/lib/pleroma/docs/markdown.ex @@ -1,16 +1,17 @@ defmodule Pleroma.Docs.Markdown do - @behaviour Pleroma.Docs.Formatter + @behaviour Pleroma.Docs.Generator + @spec process(keyword()) :: {:ok, String.t()} def process(descriptions) do config_path = "docs/config.md" {:ok, file} = File.open(config_path, [:write]) - IO.write(file, "# Generated configuration\r\n\r\n") + IO.write(file, "# Configuration\r\n\r\n") IO.write(file, "Date of generation: #{Date.utc_today()}\r\n\r\n") IO.write( file, - "This file describe the configuration, it is recommended to edit the relevant *.secret.exs file instead of the others founds in the ``config`` directory. -If you run Pleroma with ``MIX_ENV=prod`` the file is ``prod.secret.exs``, otherwise it is ``dev.secret.exs``.\r\n\r\n" + "This file describe the configuration, it is recommended to edit the relevant `*.secret.exs` file instead of the others founds in the ``config`` directory. \r\n + If you run Pleroma with ``MIX_ENV=prod`` the file is ``prod.secret.exs``, otherwise it is ``dev.secret.exs``.\r\n\r\n" ) for group <- descriptions do @@ -20,7 +21,6 @@ If you run Pleroma with ``MIX_ENV=prod`` the file is ``prod.secret.exs``, otherw IO.write(file, "## #{inspect(group[:key])}\r\n\r\n") end - IO.write(file, "Type: `#{group[:type]}` \r\n") IO.write(file, "#{group[:description]} \r\n\r\n") for child <- group[:children] do @@ -44,24 +44,36 @@ If you run Pleroma with ``MIX_ENV=prod`` the file is ``prod.secret.exs``, otherw {:ok, config_path} end + defp print_suggestion(file, suggestion) when is_list(suggestion) do + IO.write(file, " `#{inspect(suggestion)}`\r\n") + end + defp print_suggestion(file, suggestion) when is_function(suggestion) do IO.write(file, " `#{inspect(suggestion.())}`\r\n") end - defp print_suggestion(file, suggestion) do - IO.write(file, " `#{inspect(suggestion)}`\r\n") + defp print_suggestion(file, suggestion, as_list \\ false) do + list_mark = if as_list, do: "*", else: "" + IO.write(file, " #{list_mark} `#{inspect(suggestion)}`\r\n") end + defp print_suggestions(_file, nil), do: nil + defp print_suggestions(file, suggestions) do - IO.write(file, "Suggestions: \r\n") + IO.write(file, " Suggestions: \r\n") - for suggestion <- suggestions do - print_suggestion(file, suggestion) + if length(suggestions) > 1 do + for suggestion <- suggestions do + print_suggestion(file, suggestion, true) + end + else + print_suggestion(file, List.first(suggestions)) end end defp print_child_header(file, child) do - IO.write(file, "* `#{inspect(child[:key])}`: #{child[:description]} \r\n") - IO.write(file, "Type: `#{inspect(child[:type])}` \r\n") + IO.write(file, "* `#{inspect(child[:key])}` \r\n") + IO.write(file, " #{child[:description]} \r\n") + IO.write(file, " Type: `#{inspect(child[:type])}` \r\n") end end -- cgit v1.2.3 From 8f5ee7db06e379e44505744fd21e59cc46432ac3 Mon Sep 17 00:00:00 2001 From: Alex S Date: Fri, 30 Aug 2019 19:59:13 +0300 Subject: typo fix --- lib/mix/tasks/pleroma/docs.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/mix/tasks/pleroma/docs.ex b/lib/mix/tasks/pleroma/docs.ex index 821ee74f9..fb8a2a014 100644 --- a/lib/mix/tasks/pleroma/docs.ex +++ b/lib/mix/tasks/pleroma/docs.ex @@ -14,7 +14,7 @@ defmodule Mix.Tasks.Pleroma.Docs do ## Generate JSON docs - `mix pleroma.docs json`s + `mix pleroma.docs json` """ def run(["json"]) do -- cgit v1.2.3 From 0624e06a9c981264f238041a2ad22392d4a8f792 Mon Sep 17 00:00:00 2001 From: Alex S Date: Fri, 30 Aug 2019 20:14:18 +0300 Subject: little fix --- lib/pleroma/docs/markdown.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/docs/markdown.ex b/lib/pleroma/docs/markdown.ex index c66640bf1..797ce73bf 100644 --- a/lib/pleroma/docs/markdown.ex +++ b/lib/pleroma/docs/markdown.ex @@ -10,8 +10,8 @@ defmodule Pleroma.Docs.Markdown do IO.write( file, - "This file describe the configuration, it is recommended to edit the relevant `*.secret.exs` file instead of the others founds in the ``config`` directory. \r\n - If you run Pleroma with ``MIX_ENV=prod`` the file is ``prod.secret.exs``, otherwise it is ``dev.secret.exs``.\r\n\r\n" + "This file describe the configuration, it is recommended to edit the relevant `*.secret.exs` file instead of the others founds in the ``config`` directory. \r\n\r\n" <> + " If you run Pleroma with ``MIX_ENV=prod`` the file is ``prod.secret.exs``, otherwise it is ``dev.secret.exs``.\r\n\r\n" ) for group <- descriptions do -- cgit v1.2.3 From 35757b6d0eb7b59d511bfea6a166683e18d6aa97 Mon Sep 17 00:00:00 2001 From: Alex S Date: Tue, 3 Sep 2019 09:45:54 +0300 Subject: don't add behaviour to suggestions --- lib/pleroma/docs/generator.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/docs/generator.ex b/lib/pleroma/docs/generator.ex index e788712cc..aa578eee2 100644 --- a/lib/pleroma/docs/generator.ex +++ b/lib/pleroma/docs/generator.ex @@ -14,7 +14,7 @@ defmodule Pleroma.Docs.Generator do name_as_list = Module.split(module) List.starts_with?(name_as_list, ["Pleroma", "Uploaders"]) and - List.last(name_as_list) in ["S3", "Local", "MDII"] + List.last(name_as_list) != "Uploader" end) end -- cgit v1.2.3 From 57dc59d98d0ae5b4315bbc5c8a9c7ca59f6341f9 Mon Sep 17 00:00:00 2001 From: Alex S Date: Tue, 3 Sep 2019 12:31:43 +0300 Subject: little fix --- lib/mix/tasks/pleroma/docs.ex | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/mix/tasks/pleroma/docs.ex b/lib/mix/tasks/pleroma/docs.ex index fb8a2a014..4be53ce75 100644 --- a/lib/mix/tasks/pleroma/docs.ex +++ b/lib/mix/tasks/pleroma/docs.ex @@ -35,6 +35,8 @@ defmodule Mix.Tasks.Pleroma.Docs do descriptions[:pleroma][:config_description] ) - Mix.shell().info([:green, "Markdown docs successfully generated to #{file_path}."]) + type = if implementation == Pleroma.Docs.Markdown, do: "Markdown", else: "JSON" + + Mix.shell().info([:green, "#{type} docs successfully generated to #{file_path}."]) end end -- cgit v1.2.3 From 5ff12e7df10dd70f70e5929ede5dc7570e066c57 Mon Sep 17 00:00:00 2001 From: Alex S Date: Tue, 3 Sep 2019 19:22:25 +0300 Subject: some changes --- lib/pleroma/docs/markdown.ex | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/docs/markdown.ex b/lib/pleroma/docs/markdown.ex index 797ce73bf..24930cc9f 100644 --- a/lib/pleroma/docs/markdown.ex +++ b/lib/pleroma/docs/markdown.ex @@ -4,24 +4,24 @@ defmodule Pleroma.Docs.Markdown do @spec process(keyword()) :: {:ok, String.t()} def process(descriptions) do config_path = "docs/config.md" - {:ok, file} = File.open(config_path, [:write]) - IO.write(file, "# Configuration\r\n\r\n") - IO.write(file, "Date of generation: #{Date.utc_today()}\r\n\r\n") + {:ok, file} = File.open(config_path, [:utf8, :write]) + IO.write(file, "# Configuration\n") + IO.write(file, "Date of generation: #{Date.utc_today()}\n\n") IO.write( file, - "This file describe the configuration, it is recommended to edit the relevant `*.secret.exs` file instead of the others founds in the ``config`` directory. \r\n\r\n" <> - " If you run Pleroma with ``MIX_ENV=prod`` the file is ``prod.secret.exs``, otherwise it is ``dev.secret.exs``.\r\n\r\n" + "This file describe the configuration, it is recommended to edit the relevant `*.secret.exs` file instead of the others founds in the ``config`` directory.\n\n" <> + "If you run Pleroma with ``MIX_ENV=prod`` the file is ``prod.secret.exs``, otherwise it is ``dev.secret.exs``.\n\n" ) for group <- descriptions do if is_nil(group[:key]) do - IO.write(file, "## #{inspect(group[:group])}\r\n\r\n") + IO.write(file, "## #{inspect(group[:group])}\n") else - IO.write(file, "## #{inspect(group[:key])}\r\n\r\n") + IO.write(file, "## #{inspect(group[:key])}\n") end - IO.write(file, "#{group[:description]} \r\n\r\n") + IO.write(file, "#{group[:description]}\n") for child <- group[:children] do print_child_header(file, child) @@ -37,7 +37,7 @@ defmodule Pleroma.Docs.Markdown do end end - IO.write(file, "\r\n") + IO.write(file, "\n") end :ok = File.close(file) @@ -45,22 +45,22 @@ defmodule Pleroma.Docs.Markdown do end defp print_suggestion(file, suggestion) when is_list(suggestion) do - IO.write(file, " `#{inspect(suggestion)}`\r\n") + IO.write(file, " `#{inspect(suggestion)}`\n") end defp print_suggestion(file, suggestion) when is_function(suggestion) do - IO.write(file, " `#{inspect(suggestion.())}`\r\n") + IO.write(file, " `#{inspect(suggestion.())}`\n") end defp print_suggestion(file, suggestion, as_list \\ false) do - list_mark = if as_list, do: "*", else: "" - IO.write(file, " #{list_mark} `#{inspect(suggestion)}`\r\n") + list_mark = if as_list, do: "- ", else: "" + IO.write(file, " #{list_mark}`#{inspect(suggestion)}`\n") end defp print_suggestions(_file, nil), do: nil defp print_suggestions(file, suggestions) do - IO.write(file, " Suggestions: \r\n") + IO.write(file, "Suggestions:\n") if length(suggestions) > 1 do for suggestion <- suggestions do @@ -72,8 +72,7 @@ defmodule Pleroma.Docs.Markdown do end defp print_child_header(file, child) do - IO.write(file, "* `#{inspect(child[:key])}` \r\n") - IO.write(file, " #{child[:description]} \r\n") - IO.write(file, " Type: `#{inspect(child[:type])}` \r\n") + IO.write(file, "- `#{inspect(child[:key])}` -`#{inspect(child[:type])}` \n") + IO.write(file, "#{child[:description]} \n") end end -- cgit v1.2.3 From be32d90a0cdee77f4cd6ecbbbade1748c88637b8 Mon Sep 17 00:00:00 2001 From: Alex S Date: Tue, 3 Sep 2019 20:06:22 +0300 Subject: little refactor --- lib/pleroma/docs/json.ex | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/docs/json.ex b/lib/pleroma/docs/json.ex index aed730e78..18ba01d58 100644 --- a/lib/pleroma/docs/json.ex +++ b/lib/pleroma/docs/json.ex @@ -4,11 +4,13 @@ defmodule Pleroma.Docs.JSON do @spec process(keyword()) :: {:ok, String.t()} def process(descriptions) do config_path = "docs/generate_config.json" - {:ok, file} = File.open(config_path, [:write]) - json = generate_json(descriptions) - IO.write(file, json) - :ok = File.close(file) - {:ok, config_path} + + with {:ok, file} <- File.open(config_path, [:write]), + json <- generate_json(descriptions), + :ok <- IO.write(file, json), + :ok <- File.close(file) do + {:ok, config_path} + end end @spec generate_json([keyword()]) :: String.t() -- cgit v1.2.3 From 38b29779c3372741ce1d7b652922058ad2fda79c Mon Sep 17 00:00:00 2001 From: Alex S Date: Tue, 3 Sep 2019 20:11:32 +0300 Subject: refactoring --- lib/mix/tasks/pleroma/docs.ex | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) (limited to 'lib') diff --git a/lib/mix/tasks/pleroma/docs.ex b/lib/mix/tasks/pleroma/docs.ex index 4be53ce75..0d2663648 100644 --- a/lib/mix/tasks/pleroma/docs.ex +++ b/lib/mix/tasks/pleroma/docs.ex @@ -27,16 +27,16 @@ defmodule Mix.Tasks.Pleroma.Docs do defp do_run(implementation) do start_pleroma() - {descriptions, _paths} = Mix.Config.eval!("config/description.exs") - {:ok, file_path} = - Pleroma.Docs.Generator.process( - implementation, - descriptions[:pleroma][:config_description] - ) + with {descriptions, _paths} <- Mix.Config.eval!("config/description.exs"), + {:ok, file_path} <- + Pleroma.Docs.Generator.process( + implementation, + descriptions[:pleroma][:config_description] + ) do + type = if implementation == Pleroma.Docs.Markdown, do: "Markdown", else: "JSON" - type = if implementation == Pleroma.Docs.Markdown, do: "Markdown", else: "JSON" - - Mix.shell().info([:green, "#{type} docs successfully generated to #{file_path}."]) + Mix.shell().info([:green, "#{type} docs successfully generated to #{file_path}."]) + end end end -- cgit v1.2.3 From 5a76d5d2391f0fa6b4c58bf190b0cdbfff96014f Mon Sep 17 00:00:00 2001 From: rinpatch Date: Tue, 10 Sep 2019 23:08:15 +0300 Subject: Add extended benchmark --- lib/mix/tasks/pleroma/benchmark.ex | 40 ++++++++++++++++++++++++++++++-------- 1 file changed, 32 insertions(+), 8 deletions(-) (limited to 'lib') diff --git a/lib/mix/tasks/pleroma/benchmark.ex b/lib/mix/tasks/pleroma/benchmark.ex index a45940bf3..84dccf7f3 100644 --- a/lib/mix/tasks/pleroma/benchmark.ex +++ b/lib/mix/tasks/pleroma/benchmark.ex @@ -27,7 +27,7 @@ defmodule Mix.Tasks.Pleroma.Benchmark do }) end - def run(["render_timeline", nickname]) do + def run(["render_timeline", nickname | _] = args) do start_pleroma() user = Pleroma.User.get_by_nickname(nickname) @@ -37,17 +37,41 @@ defmodule Mix.Tasks.Pleroma.Benchmark do |> Map.put("blocking_user", user) |> Map.put("muting_user", user) |> Map.put("user", user) + |> Map.put("limit", 4096) |> Pleroma.Web.ActivityPub.ActivityPub.fetch_public_activities() |> Enum.reverse() - Benchee.run(%{ - "render_timeline" => fn -> - Pleroma.Web.MastodonAPI.StatusView.render("index.json", %{ - activities: activities, - for: user, - as: :activity + inputs = %{ + "1 activity" => Enum.take_random(activities, 1), + "10 activities" => Enum.take_random(activities, 10), + "20 activities" => Enum.take_random(activities, 20), + "40 activities" => Enum.take_random(activities, 40), + "80 activities" => Enum.take_random(activities, 80) + } + + inputs = + if Enum.at(args, 2) == "extended" do + Map.merge(inputs, %{ + "200 activities" => Enum.take_random(activities, 200), + "500 activities" => Enum.take_random(activities, 500), + "2000 activities" => Enum.take_random(activities, 2000), + "4096 activities" => Enum.take_random(activities, 4096) }) + else + inputs end - }) + + Benchee.run( + %{ + "Standart rendering" => fn activities -> + Pleroma.Web.MastodonAPI.StatusView.render("index.json", %{ + activities: activities, + for: user, + as: :activity + }) + end + }, + inputs: inputs + ) end end -- cgit v1.2.3 From 56828abf6de81a52079b26cf899b91fdd822f2ce Mon Sep 17 00:00:00 2001 From: rinpatch Date: Wed, 11 Sep 2019 23:04:01 +0300 Subject: Use Jason for rendering responses Although Jason readme says Phoenix 1.4+ already does it by default, [it actually does it only for new projects](https://github.com/phoenixframework/phoenix/blob/3bfb9f6e900c9a2e31cb95736e2cb5bdad329b61/lib/phoenix.ex#L58-L59) --- lib/healthcheck.ex | 64 --------------------------------------------- lib/pleroma/healthcheck.ex | 65 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 64 deletions(-) delete mode 100644 lib/healthcheck.ex create mode 100644 lib/pleroma/healthcheck.ex (limited to 'lib') diff --git a/lib/healthcheck.ex b/lib/healthcheck.ex deleted file mode 100644 index f97d14432..000000000 --- a/lib/healthcheck.ex +++ /dev/null @@ -1,64 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Healthcheck do - @moduledoc """ - Module collects metrics about app and assign healthy status. - """ - alias Pleroma.Healthcheck - alias Pleroma.Repo - - defstruct pool_size: 0, - active: 0, - idle: 0, - memory_used: 0, - healthy: true - - @type t :: %__MODULE__{ - pool_size: non_neg_integer(), - active: non_neg_integer(), - idle: non_neg_integer(), - memory_used: number(), - healthy: boolean() - } - - @spec system_info() :: t() - def system_info do - %Healthcheck{ - memory_used: Float.round(:erlang.memory(:total) / 1024 / 1024, 2) - } - |> assign_db_info() - |> check_health() - end - - defp assign_db_info(healthcheck) do - database = Pleroma.Config.get([Repo, :database]) - - query = - "select state, count(pid) from pg_stat_activity where datname = '#{database}' group by state;" - - result = Repo.query!(query) - pool_size = Pleroma.Config.get([Repo, :pool_size]) - - db_info = - Enum.reduce(result.rows, %{active: 0, idle: 0}, fn [state, cnt], states -> - if state == "active" do - Map.put(states, :active, states.active + cnt) - else - Map.put(states, :idle, states.idle + cnt) - end - end) - |> Map.put(:pool_size, pool_size) - - Map.merge(healthcheck, db_info) - end - - @spec check_health(Healthcheck.t()) :: Healthcheck.t() - def check_health(%{pool_size: pool_size, active: active} = check) - when active >= pool_size do - %{check | healthy: false} - end - - def check_health(check), do: check -end diff --git a/lib/pleroma/healthcheck.ex b/lib/pleroma/healthcheck.ex new file mode 100644 index 000000000..977b78c26 --- /dev/null +++ b/lib/pleroma/healthcheck.ex @@ -0,0 +1,65 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Healthcheck do + @moduledoc """ + Module collects metrics about app and assign healthy status. + """ + alias Pleroma.Healthcheck + alias Pleroma.Repo + + @derive Jason.Encoder + defstruct pool_size: 0, + active: 0, + idle: 0, + memory_used: 0, + healthy: true + + @type t :: %__MODULE__{ + pool_size: non_neg_integer(), + active: non_neg_integer(), + idle: non_neg_integer(), + memory_used: number(), + healthy: boolean() + } + + @spec system_info() :: t() + def system_info do + %Healthcheck{ + memory_used: Float.round(:erlang.memory(:total) / 1024 / 1024, 2) + } + |> assign_db_info() + |> check_health() + end + + defp assign_db_info(healthcheck) do + database = Pleroma.Config.get([Repo, :database]) + + query = + "select state, count(pid) from pg_stat_activity where datname = '#{database}' group by state;" + + result = Repo.query!(query) + pool_size = Pleroma.Config.get([Repo, :pool_size]) + + db_info = + Enum.reduce(result.rows, %{active: 0, idle: 0}, fn [state, cnt], states -> + if state == "active" do + Map.put(states, :active, states.active + cnt) + else + Map.put(states, :idle, states.idle + cnt) + end + end) + |> Map.put(:pool_size, pool_size) + + Map.merge(healthcheck, db_info) + end + + @spec check_health(Healthcheck.t()) :: Healthcheck.t() + def check_health(%{pool_size: pool_size, active: active} = check) + when active >= pool_size do + %{check | healthy: false} + end + + def check_health(check), do: check +end -- cgit v1.2.3 From 74e4c72c4a0a079e8e2a245ec9b1d22684baf2e1 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Wed, 11 Sep 2019 16:16:09 -0500 Subject: Fix double quotes in error logs Example: pleroma: [error] Couldn't fetch ""https://pleroma.soykaf.com/objects/6288a14b-0623-40fc-a26a-0d358f8a11ca"", error: nil --- lib/pleroma/web/activity_pub/transmogrifier.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index 468961bd0..350b83abb 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -185,12 +185,12 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do |> Map.put("context", replied_object.data["context"] || object["conversation"]) else e -> - Logger.error("Couldn't fetch \"#{inspect(in_reply_to_id)}\", error: #{inspect(e)}") + Logger.error("Couldn't fetch #{inspect(in_reply_to_id)}, error: #{inspect(e)}") object end e -> - Logger.error("Couldn't fetch \"#{inspect(in_reply_to_id)}\", error: #{inspect(e)}") + Logger.error("Couldn't fetch #{inspect(in_reply_to_id)}, error: #{inspect(e)}") object end else -- cgit v1.2.3 From 25d8216804c7742cd8549799a7785723f2a70afa Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Fri, 13 Sep 2019 13:09:35 +0700 Subject: Add email change endpoint --- lib/pleroma/user.ex | 9 +++++++++ lib/pleroma/web/router.ex | 1 + .../web/twitter_api/controllers/util_controller.ex | 19 +++++++++++++++++++ 3 files changed, 29 insertions(+) (limited to 'lib') diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 3aa245f2a..1f6a75d03 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -1624,4 +1624,13 @@ defmodule Pleroma.User do def is_internal_user?(%User{nickname: nil}), do: true def is_internal_user?(%User{local: true, nickname: "internal." <> _}), do: true def is_internal_user?(_), do: false + + def change_email(user, email) do + user + |> cast(%{email: email}, [:email]) + |> validate_required([:email]) + |> unique_constraint(:email) + |> validate_format(:email, @email_regex) + |> update_and_set_cache() + end end diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 7cd59acb2..b0464037e 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -224,6 +224,7 @@ defmodule Pleroma.Web.Router do scope [] do pipe_through(:oauth_write) + post("/change_email", UtilController, :change_email) post("/change_password", UtilController, :change_password) post("/delete_account", UtilController, :delete_account) put("/notification_settings", UtilController, :update_notificaton_settings) diff --git a/lib/pleroma/web/twitter_api/controllers/util_controller.ex b/lib/pleroma/web/twitter_api/controllers/util_controller.ex index 3405bd3b7..867787c57 100644 --- a/lib/pleroma/web/twitter_api/controllers/util_controller.ex +++ b/lib/pleroma/web/twitter_api/controllers/util_controller.ex @@ -314,6 +314,25 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do end end + def change_email(%{assigns: %{user: user}} = conn, params) do + case CommonAPI.Utils.confirm_current_password(user, params["password"]) do + {:ok, user} -> + with {:ok, _user} <- User.change_email(user, params["email"]) do + json(conn, %{status: "success"}) + else + {:error, changeset} -> + {_, {error, _}} = Enum.at(changeset.errors, 0) + json(conn, %{error: "Email #{error}."}) + + _ -> + json(conn, %{error: "Unable to change email."}) + end + + {:error, msg} -> + json(conn, %{error: msg}) + end + end + def delete_account(%{assigns: %{user: user}} = conn, params) do case CommonAPI.Utils.confirm_current_password(user, params["password"]) do {:ok, user} -> -- cgit v1.2.3