Merge branch 'feature/770-add-emoji-tags' into 'develop'
authorlambda <lain@soykaf.club>
Mon, 8 Apr 2019 09:50:00 +0000 (09:50 +0000)
committerlambda <lain@soykaf.club>
Mon, 8 Apr 2019 09:50:00 +0000 (09:50 +0000)
Feature/770 add emoji tags

See merge request pleroma/pleroma!998

1  2 
config/config.exs
lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
test/web/mastodon_api/mastodon_api_controller_test.exs

diff --combined config/config.exs
index 8a977ece5d5089a60ff33b8c2956dea3555a0e03,8f9dd734fc35b2779e8446024e3429c8e5206bd8..5d959a859de4aec7804c464367f7ac2c20bedacf
@@@ -8,10 -8,6 +8,10 @@@ use Mix.Confi
  # General application configuration
  config :pleroma, ecto_repos: [Pleroma.Repo]
  
 +config :pleroma, Pleroma.Repo,
 +  types: Pleroma.PostgresTypes,
 +  telemetry_event: [Pleroma.Repo.Instrumenter]
 +
  config :pleroma, Pleroma.Captcha,
    enabled: false,
    seconds_valid: 60,
@@@ -58,7 -54,13 +58,13 @@@ config :pleroma, Pleroma.Uploaders.MDII
    cgi: "https://mdii.sakura.ne.jp/mdii-post.cgi",
    files: "https://mdii.sakura.ne.jp"
  
- config :pleroma, :emoji, shortcode_globs: ["/emoji/custom/**/*.png"]
+ config :pleroma, :emoji,
+   shortcode_globs: ["/emoji/custom/**/*.png"],
+   groups: [
+     # Put groups that have higher priority than defaults here. Example in `docs/config/custom_emoji.md`
+     Finmoji: "/finmoji/128px/*-128.png",
+     Custom: ["/emoji/*.png", "/emoji/custom/*.png"]
+   ]
  
  config :pleroma, :uri_schemes,
    valid_schemes: [
@@@ -91,7 -93,6 +97,7 @@@ websocket_config = 
  
  # Configures the endpoint
  config :pleroma, Pleroma.Web.Endpoint,
 +  instrumenters: [Pleroma.Web.Endpoint.Instrumenter],
    url: [host: "localhost"],
    http: [
      dispatch: [
@@@ -361,8 -362,7 +367,8 @@@ config :pleroma, Pleroma.Web.Federator.
  config :pleroma_job_queue, :queues,
    federator_incoming: 50,
    federator_outgoing: 50,
 -  mailer: 10
 +  mailer: 10,
 +  scheduled_activities: 10
  
  config :pleroma, :fetch_initial_posts,
    enabled: false,
@@@ -391,13 -391,6 +397,13 @@@ config :pleroma, :ldap
  
  config :pleroma, Pleroma.Mailer, adapter: Swoosh.Adapters.Sendmail
  
 +config :prometheus, Pleroma.Web.Endpoint.MetricsExporter, path: "/api/pleroma/app_metrics"
 +
 +config :pleroma, Pleroma.ScheduledActivity,
 +  daily_user_limit: 25,
 +  total_user_limit: 300,
 +  enabled: true
 +
  # Import environment specific config. This must remain at the bottom
  # of this file so it overrides the configuration defined above.
  import_config "#{Mix.env()}.exs"
index fc8a2458c354e5bddabd9ac7a63dde44ebcd2e0d,98943e5d12477e02de1f2e441ec1a943c594bda1..5462ce8bebd19a78d9be63dc855abdcc2998b17b
@@@ -5,14 -5,12 +5,14 @@@
  defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
    use Pleroma.Web, :controller
  
 +  alias Ecto.Changeset
    alias Pleroma.Activity
    alias Pleroma.Config
    alias Pleroma.Filter
    alias Pleroma.Notification
    alias Pleroma.Object
    alias Pleroma.Repo
 +  alias Pleroma.ScheduledActivity
    alias Pleroma.Stats
    alias Pleroma.User
    alias Pleroma.Web
@@@ -27,7 -25,6 +27,7 @@@
    alias Pleroma.Web.MastodonAPI.MastodonView
    alias Pleroma.Web.MastodonAPI.NotificationView
    alias Pleroma.Web.MastodonAPI.ReportView
 +  alias Pleroma.Web.MastodonAPI.ScheduledActivityView
    alias Pleroma.Web.MastodonAPI.StatusView
    alias Pleroma.Web.MediaProxy
    alias Pleroma.Web.OAuth.App
  
    defp mastodonized_emoji do
      Pleroma.Emoji.get_all()
-     |> Enum.map(fn {shortcode, relative_url} ->
+     |> Enum.map(fn {shortcode, relative_url, tags} ->
        url = to_string(URI.merge(Web.base_url(), relative_url))
  
        %{
          "shortcode" => shortcode,
          "static_url" => url,
          "visible_in_picker" => true,
-         "url" => url
+         "url" => url,
+         "tags" => String.split(tags, ",")
        }
      end)
    end
      end
    end
  
 +  def scheduled_statuses(%{assigns: %{user: user}} = conn, params) do
 +    with scheduled_activities <- MastodonAPI.get_scheduled_activities(user, params) do
 +      conn
 +      |> add_link_headers(:scheduled_statuses, scheduled_activities)
 +      |> put_view(ScheduledActivityView)
 +      |> render("index.json", %{scheduled_activities: scheduled_activities})
 +    end
 +  end
 +
 +  def show_scheduled_status(%{assigns: %{user: user}} = conn, %{"id" => scheduled_activity_id}) do
 +    with %ScheduledActivity{} = scheduled_activity <-
 +           ScheduledActivity.get(user, scheduled_activity_id) do
 +      conn
 +      |> put_view(ScheduledActivityView)
 +      |> render("show.json", %{scheduled_activity: scheduled_activity})
 +    else
 +      _ -> {:error, :not_found}
 +    end
 +  end
 +
 +  def update_scheduled_status(
 +        %{assigns: %{user: user}} = conn,
 +        %{"id" => scheduled_activity_id} = params
 +      ) do
 +    with %ScheduledActivity{} = scheduled_activity <-
 +           ScheduledActivity.get(user, scheduled_activity_id),
 +         {:ok, scheduled_activity} <- ScheduledActivity.update(scheduled_activity, params) do
 +      conn
 +      |> put_view(ScheduledActivityView)
 +      |> render("show.json", %{scheduled_activity: scheduled_activity})
 +    else
 +      nil -> {:error, :not_found}
 +      error -> error
 +    end
 +  end
 +
 +  def delete_scheduled_status(%{assigns: %{user: user}} = conn, %{"id" => scheduled_activity_id}) do
 +    with %ScheduledActivity{} = scheduled_activity <-
 +           ScheduledActivity.get(user, scheduled_activity_id),
 +         {:ok, scheduled_activity} <- ScheduledActivity.delete(scheduled_activity) do
 +      conn
 +      |> put_view(ScheduledActivityView)
 +      |> render("show.json", %{scheduled_activity: scheduled_activity})
 +    else
 +      nil -> {:error, :not_found}
 +      error -> error
 +    end
 +  end
 +
    def post_status(conn, %{"status" => "", "media_ids" => media_ids} = params)
        when length(media_ids) > 0 do
      params =
          _ -> Ecto.UUID.generate()
        end
  
 -    {:ok, activity} =
 -      Cachex.fetch!(:idempotency_cache, idempotency_key, fn _ -> CommonAPI.post(user, params) end)
 +    scheduled_at = params["scheduled_at"]
  
 -    conn
 -    |> put_view(StatusView)
 -    |> try_render("status.json", %{activity: activity, for: user, as: :activity})
 +    if scheduled_at && ScheduledActivity.far_enough?(scheduled_at) do
 +      with {:ok, scheduled_activity} <-
 +             ScheduledActivity.create(user, %{"params" => params, "scheduled_at" => scheduled_at}) do
 +        conn
 +        |> put_view(ScheduledActivityView)
 +        |> render("show.json", %{scheduled_activity: scheduled_activity})
 +      end
 +    else
 +      params = Map.drop(params, ["scheduled_at"])
 +
 +      {:ok, activity} =
 +        Cachex.fetch!(:idempotency_cache, idempotency_key, fn _ ->
 +          CommonAPI.post(user, params)
 +        end)
 +
 +      conn
 +      |> put_view(StatusView)
 +      |> try_render("status.json", %{activity: activity, for: user, as: :activity})
 +    end
    end
  
    def delete_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
  
    # fallback action
    #
 +  def errors(conn, {:error, %Changeset{} = changeset}) do
 +    error_message =
 +      changeset
 +      |> Changeset.traverse_errors(fn {message, _opt} -> message end)
 +      |> Enum.map_join(", ", fn {_k, v} -> v end)
 +
 +    conn
 +    |> put_status(422)
 +    |> json(%{error: error_message})
 +  end
 +
 +  def errors(conn, {:error, :not_found}) do
 +    conn
 +    |> put_status(404)
 +    |> json(%{error: "Record not found"})
 +  end
 +
    def errors(conn, _) do
      conn
      |> put_status(500)
index cd01116e29d228fd43e156678200384f36754c79,62e6c2a3a03583beb251a18fac4f0328dd681c97..e16862a48cc9c1f9a9f3e73df130082e92fd5812
@@@ -10,7 -10,6 +10,7 @@@ defmodule Pleroma.Web.MastodonAPI.Masto
    alias Pleroma.Notification
    alias Pleroma.Object
    alias Pleroma.Repo
 +  alias Pleroma.ScheduledActivity
    alias Pleroma.User
    alias Pleroma.Web.ActivityPub.ActivityPub
    alias Pleroma.Web.CommonAPI
      assert acc_two == acc_three
    end
  
+   describe "custom emoji" do
+     test "with tags", %{conn: conn} do
+       [emoji | _body] =
+         conn
+         |> get("/api/v1/custom_emojis")
+         |> json_response(200)
+       assert Map.has_key?(emoji, "shortcode")
+       assert Map.has_key?(emoji, "static_url")
+       assert Map.has_key?(emoji, "tags")
+       assert is_list(emoji["tags"])
+       assert Map.has_key?(emoji, "url")
+       assert Map.has_key?(emoji, "visible_in_picker")
+     end
+   end
    describe "index/2 redirections" do
      setup %{conn: conn} do
        session_opts = [
        assert redirected_to(conn) == "/web/getting-started"
      end
    end
 +
 +  describe "scheduled activities" do
 +    test "creates a scheduled activity", %{conn: conn} do
 +      user = insert(:user)
 +      scheduled_at = NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond)
 +
 +      conn =
 +        conn
 +        |> assign(:user, user)
 +        |> post("/api/v1/statuses", %{
 +          "status" => "scheduled",
 +          "scheduled_at" => scheduled_at
 +        })
 +
 +      assert %{"scheduled_at" => expected_scheduled_at} = json_response(conn, 200)
 +      assert expected_scheduled_at == Pleroma.Web.CommonAPI.Utils.to_masto_date(scheduled_at)
 +      assert [] == Repo.all(Activity)
 +    end
 +
 +    test "creates a scheduled activity with a media attachment", %{conn: conn} do
 +      user = insert(:user)
 +      scheduled_at = NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond)
 +
 +      file = %Plug.Upload{
 +        content_type: "image/jpg",
 +        path: Path.absname("test/fixtures/image.jpg"),
 +        filename: "an_image.jpg"
 +      }
 +
 +      {:ok, upload} = ActivityPub.upload(file, actor: user.ap_id)
 +
 +      conn =
 +        conn
 +        |> assign(:user, user)
 +        |> post("/api/v1/statuses", %{
 +          "media_ids" => [to_string(upload.id)],
 +          "status" => "scheduled",
 +          "scheduled_at" => scheduled_at
 +        })
 +
 +      assert %{"media_attachments" => [media_attachment]} = json_response(conn, 200)
 +      assert %{"type" => "image"} = media_attachment
 +    end
 +
 +    test "skips the scheduling and creates the activity if scheduled_at is earlier than 5 minutes from now",
 +         %{conn: conn} do
 +      user = insert(:user)
 +
 +      scheduled_at =
 +        NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(5) - 1, :millisecond)
 +
 +      conn =
 +        conn
 +        |> assign(:user, user)
 +        |> post("/api/v1/statuses", %{
 +          "status" => "not scheduled",
 +          "scheduled_at" => scheduled_at
 +        })
 +
 +      assert %{"content" => "not scheduled"} = json_response(conn, 200)
 +      assert [] == Repo.all(ScheduledActivity)
 +    end
 +
 +    test "returns error when daily user limit is exceeded", %{conn: conn} do
 +      user = insert(:user)
 +
 +      today =
 +        NaiveDateTime.utc_now()
 +        |> NaiveDateTime.add(:timer.minutes(6), :millisecond)
 +        |> NaiveDateTime.to_iso8601()
 +
 +      attrs = %{params: %{}, scheduled_at: today}
 +      {:ok, _} = ScheduledActivity.create(user, attrs)
 +      {:ok, _} = ScheduledActivity.create(user, attrs)
 +
 +      conn =
 +        conn
 +        |> assign(:user, user)
 +        |> post("/api/v1/statuses", %{"status" => "scheduled", "scheduled_at" => today})
 +
 +      assert %{"error" => "daily limit exceeded"} == json_response(conn, 422)
 +    end
 +
 +    test "returns error when total user limit is exceeded", %{conn: conn} do
 +      user = insert(:user)
 +
 +      today =
 +        NaiveDateTime.utc_now()
 +        |> NaiveDateTime.add(:timer.minutes(6), :millisecond)
 +        |> NaiveDateTime.to_iso8601()
 +
 +      tomorrow =
 +        NaiveDateTime.utc_now()
 +        |> NaiveDateTime.add(:timer.hours(36), :millisecond)
 +        |> NaiveDateTime.to_iso8601()
 +
 +      attrs = %{params: %{}, scheduled_at: today}
 +      {:ok, _} = ScheduledActivity.create(user, attrs)
 +      {:ok, _} = ScheduledActivity.create(user, attrs)
 +      {:ok, _} = ScheduledActivity.create(user, %{params: %{}, scheduled_at: tomorrow})
 +
 +      conn =
 +        conn
 +        |> assign(:user, user)
 +        |> post("/api/v1/statuses", %{"status" => "scheduled", "scheduled_at" => tomorrow})
 +
 +      assert %{"error" => "total limit exceeded"} == json_response(conn, 422)
 +    end
 +
 +    test "shows scheduled activities", %{conn: conn} do
 +      user = insert(:user)
 +      scheduled_activity_id1 = insert(:scheduled_activity, user: user).id |> to_string()
 +      scheduled_activity_id2 = insert(:scheduled_activity, user: user).id |> to_string()
 +      scheduled_activity_id3 = insert(:scheduled_activity, user: user).id |> to_string()
 +      scheduled_activity_id4 = insert(:scheduled_activity, user: user).id |> to_string()
 +
 +      conn =
 +        conn
 +        |> assign(:user, user)
 +
 +      # min_id
 +      conn_res =
 +        conn
 +        |> get("/api/v1/scheduled_statuses?limit=2&min_id=#{scheduled_activity_id1}")
 +
 +      result = json_response(conn_res, 200)
 +      assert [%{"id" => ^scheduled_activity_id3}, %{"id" => ^scheduled_activity_id2}] = result
 +
 +      # since_id
 +      conn_res =
 +        conn
 +        |> get("/api/v1/scheduled_statuses?limit=2&since_id=#{scheduled_activity_id1}")
 +
 +      result = json_response(conn_res, 200)
 +      assert [%{"id" => ^scheduled_activity_id4}, %{"id" => ^scheduled_activity_id3}] = result
 +
 +      # max_id
 +      conn_res =
 +        conn
 +        |> get("/api/v1/scheduled_statuses?limit=2&max_id=#{scheduled_activity_id4}")
 +
 +      result = json_response(conn_res, 200)
 +      assert [%{"id" => ^scheduled_activity_id3}, %{"id" => ^scheduled_activity_id2}] = result
 +    end
 +
 +    test "shows a scheduled activity", %{conn: conn} do
 +      user = insert(:user)
 +      scheduled_activity = insert(:scheduled_activity, user: user)
 +
 +      res_conn =
 +        conn
 +        |> assign(:user, user)
 +        |> get("/api/v1/scheduled_statuses/#{scheduled_activity.id}")
 +
 +      assert %{"id" => scheduled_activity_id} = json_response(res_conn, 200)
 +      assert scheduled_activity_id == scheduled_activity.id |> to_string()
 +
 +      res_conn =
 +        conn
 +        |> assign(:user, user)
 +        |> get("/api/v1/scheduled_statuses/404")
 +
 +      assert %{"error" => "Record not found"} = json_response(res_conn, 404)
 +    end
 +
 +    test "updates a scheduled activity", %{conn: conn} do
 +      user = insert(:user)
 +      scheduled_activity = insert(:scheduled_activity, user: user)
 +
 +      new_scheduled_at =
 +        NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond)
 +
 +      res_conn =
 +        conn
 +        |> assign(:user, user)
 +        |> put("/api/v1/scheduled_statuses/#{scheduled_activity.id}", %{
 +          scheduled_at: new_scheduled_at
 +        })
 +
 +      assert %{"scheduled_at" => expected_scheduled_at} = json_response(res_conn, 200)
 +      assert expected_scheduled_at == Pleroma.Web.CommonAPI.Utils.to_masto_date(new_scheduled_at)
 +
 +      res_conn =
 +        conn
 +        |> assign(:user, user)
 +        |> put("/api/v1/scheduled_statuses/404", %{scheduled_at: new_scheduled_at})
 +
 +      assert %{"error" => "Record not found"} = json_response(res_conn, 404)
 +    end
 +
 +    test "deletes a scheduled activity", %{conn: conn} do
 +      user = insert(:user)
 +      scheduled_activity = insert(:scheduled_activity, user: user)
 +
 +      res_conn =
 +        conn
 +        |> assign(:user, user)
 +        |> delete("/api/v1/scheduled_statuses/#{scheduled_activity.id}")
 +
 +      assert %{} = json_response(res_conn, 200)
 +      assert nil == Repo.get(ScheduledActivity, scheduled_activity.id)
 +
 +      res_conn =
 +        conn
 +        |> assign(:user, user)
 +        |> delete("/api/v1/scheduled_statuses/#{scheduled_activity.id}")
 +
 +      assert %{"error" => "Record not found"} = json_response(res_conn, 404)
 +    end
 +  end
  end