From a3ffc08e577a438a8f83ade8c98b87c25f301f06 Mon Sep 17 00:00:00 2001 From: Thomas Citharel Date: Fri, 1 Mar 2019 18:30:46 +0100 Subject: [PATCH] Add Activity Pub endpoints cache Signed-off-by: Thomas Citharel --- lib/mobilizon/actors/actor.ex | 4 + lib/mobilizon/application.ex | 11 ++- .../controllers/activity_pub_controller.ex | 97 +++++++++++++------ .../views/activity_pub/object_view.ex | 16 ++- lib/service/activity_pub/utils.ex | 14 ++- lib/service/feed.ex | 5 +- 6 files changed, 104 insertions(+), 43 deletions(-) diff --git a/lib/mobilizon/actors/actor.ex b/lib/mobilizon/actors/actor.ex index 54844afff..5ce7c0db1 100644 --- a/lib/mobilizon/actors/actor.ex +++ b/lib/mobilizon/actors/actor.ex @@ -377,4 +377,8 @@ defmodule Mobilizon.Actors.Actor do name -> name end end + + def clear_cache(%Actor{preferred_username: preferred_username, domain: nil}) do + Cachex.del(:activity_pub, "actor_" <> preferred_username) + end end diff --git a/lib/mobilizon/application.ex b/lib/mobilizon/application.ex index bfe420ce1..6a8c2252c 100644 --- a/lib/mobilizon/application.ex +++ b/lib/mobilizon/application.ex @@ -37,12 +37,17 @@ defmodule Mobilizon.Application do worker( Cachex, [ - :json, + :activity_pub, [ - limit: 2500 + limit: 2500, + expiration: + expiration( + default: :timer.minutes(3), + interval: :timer.seconds(15) + ) ] ], - id: :cache_actor + id: :cache_activity_pub ), worker(Guardian.DB.Token.SweeperServer, []), worker(Mobilizon.Service.Federator, []) diff --git a/lib/mobilizon_web/controllers/activity_pub_controller.ex b/lib/mobilizon_web/controllers/activity_pub_controller.ex index c7c6a9636..76670b69b 100644 --- a/lib/mobilizon_web/controllers/activity_pub_controller.ex +++ b/lib/mobilizon_web/controllers/activity_pub_controller.ex @@ -21,53 +21,96 @@ defmodule MobilizonWeb.ActivityPubController do "application/activity+json, application/ld+json" ] + @doc """ + Show an Actor's ActivityPub representation + """ + @spec actor(Plug.Conn.t(), map()) :: Plug.Conn.t() def actor(conn, %{"name" => name}) do - with %Actor{} = actor <- Actors.get_local_actor_by_name(name) do - if conn |> get_req_header("accept") |> is_ap_header() do - conn |> render_ap_actor(actor) - else - conn - |> put_resp_content_type("text/html") - |> send_file(200, "priv/static/index.html") - end + if conn |> get_req_header("accept") |> is_ap_header() do + render_cached_actor(conn, name) else - nil -> {:error, :not_found} + conn + |> put_resp_content_type("text/html") + |> send_file(200, "priv/static/index.html") end end + @spec render_cached_actor(Plug.Conn.t(), String.t()) :: Plug.Conn.t() + defp render_cached_actor(conn, name) do + case Cachex.fetch(:activity_pub, "actor_" <> name, &get_local_actor_by_name/1) do + {status, %Actor{} = actor} when status in [:ok, :commit] -> + conn + |> put_resp_header("content-type", "application/activity+json") + |> json(ActorView.render("actor.json", %{actor: actor})) + + {:ignore, _} -> + {:error, :not_found} + end + end + + defp get_local_actor_by_name("actor_" <> name) do + case Actors.get_local_actor_by_name(name) do + nil -> {:ignore, nil} + %Actor{} = actor -> {:commit, actor} + end + end + + # Test if the request has an AP header defp is_ap_header(ap_headers) do length(@activity_pub_headers -- ap_headers) < 2 end - defp render_ap_actor(conn, %Actor{} = actor) do - conn - |> put_resp_header("content-type", "application/activity+json") - |> json(ActorView.render("actor.json", %{actor: actor})) - end - + @doc """ + Renders an Event ActivityPub's representation + """ + @spec event(Plug.Conn.t(), map()) :: Plug.Conn.t() def event(conn, %{"uuid" => uuid}) do - with %Event{} = event <- Events.get_event_full_by_uuid(uuid), - true <- event.visibility in [:public, :unlisted] do - conn - |> put_resp_header("content-type", "application/activity+json") - |> json(ObjectView.render("event.json", %{event: event |> Utils.make_event_data()})) - else - _ -> + case Cachex.fetch(:activity_pub, "event_" <> uuid, &get_event_full_by_uuid/1) do + {status, %Event{} = event} when status in [:ok, :commit] -> + conn + |> put_resp_header("content-type", "application/activity+json") + |> json(ObjectView.render("event.json", %{event: event |> Utils.make_event_data()})) + + {:ignore, _} -> {:error, :not_found} end end + defp get_event_full_by_uuid("event_" <> uuid) do + with %Event{} = event <- Events.get_event_full_by_uuid(uuid), + true <- event.visibility in [:public, :unlisted] do + {:commit, event} + else + _ -> {:ignore, nil} + end + end + + @doc """ + Renders a Comment ActivityPub's representation + """ + @spec comment(Plug.Conn.t(), map()) :: Plug.Conn.t() def comment(conn, %{"uuid" => uuid}) do + case Cachex.fetch(:activity_pub, "comment_" <> uuid, &get_comment_full_by_uuid/1) do + {status, %Comment{} = comment} when status in [:ok, :commit] -> + conn + |> put_resp_header("content-type", "application/activity+json") + |> json( + ObjectView.render("comment.json", %{comment: comment |> Utils.make_comment_data()}) + ) + + {:ignore, _} -> + {:error, :not_found} + end + end + + defp get_comment_full_by_uuid("comment_" <> uuid) do with %Comment{} = comment <- Events.get_comment_full_from_uuid(uuid) do # Comments are always public for now # TODO : Make comments maybe restricted # true <- comment.public do - conn - |> put_resp_header("content-type", "application/activity+json") - |> json(ObjectView.render("comment.json", %{comment: comment |> Utils.make_comment_data()})) + {:commit, comment} else - _ -> - {:error, :not_found} + _ -> {:ignore, nil} end end diff --git a/lib/mobilizon_web/views/activity_pub/object_view.ex b/lib/mobilizon_web/views/activity_pub/object_view.ex index e8dcfa12a..a0372e96b 100644 --- a/lib/mobilizon_web/views/activity_pub/object_view.ex +++ b/lib/mobilizon_web/views/activity_pub/object_view.ex @@ -3,16 +3,22 @@ defmodule MobilizonWeb.ActivityPub.ObjectView do alias Mobilizon.Service.ActivityPub.Utils def render("event.json", %{event: event}) do + {:ok, html, []} = Earmark.as_html(event["summary"]) + event = %{ "type" => "Event", - "actor" => event["actor"], + "attributedTo" => event["actor"], "id" => event["id"], "name" => event["title"], "category" => event["category"], - "content" => event["summary"], - "mediaType" => "text/html" - # "published" => Timex.format!(event.inserted_at, "{ISO:Extended}"), - # "updated" => Timex.format!(event.updated_at, "{ISO:Extended}") + "content" => html, + "source" => %{ + "content" => event["summary"], + "mediaType" => "text/markdown" + }, + "mediaType" => "text/html", + "published" => event["publish_at"], + "updated" => event["updated_at"] } Map.merge(event, Utils.make_json_ld_header()) diff --git a/lib/service/activity_pub/utils.ex b/lib/service/activity_pub/utils.ex index d00a49a93..35cb8e8e4 100644 --- a/lib/service/activity_pub/utils.ex +++ b/lib/service/activity_pub/utils.ex @@ -283,16 +283,20 @@ defmodule Mobilizon.Service.ActivityPub.Utils do @spec make_event_data(Event.t(), list(String.t())) :: map() def make_event_data( - %Event{title: title, organizer_actor: actor, uuid: uuid}, + %Event{} = event, to \\ ["https://www.w3.org/ns/activitystreams#Public"] ) do %{ "type" => "Event", "to" => to, - "title" => title, - "actor" => actor.url, - "uuid" => uuid, - "id" => "#{MobilizonWeb.Endpoint.url()}/events/#{uuid}" + "title" => event.title, + "actor" => event.organizer_actor.url, + "uuid" => event.uuid, + "category" => event.category, + "summary" => event.description, + "publish_at" => (event.publish_at || event.inserted_at) |> DateTime.to_iso8601(), + "updated_at" => event.updated_at |> DateTime.to_iso8601(), + "id" => "#{MobilizonWeb.Endpoint.url()}/events/#{event.uuid}" } end diff --git a/lib/service/feed.ex b/lib/service/feed.ex index d63611154..7cfb4720e 100644 --- a/lib/service/feed.ex +++ b/lib/service/feed.ex @@ -72,11 +72,10 @@ defmodule Mobilizon.Service.Feed do defp get_entry(%Event{} = event) do with {:ok, html, []} <- Earmark.as_html(event.description) do entry = - Entry.new(event.url, event.inserted_at, event.title) + Entry.new(event.url, event.publish_at || event.inserted_at, event.title) |> Entry.link(event.url, rel: "alternate", type: "text/html") |> Entry.content({:cdata, html}, type: "html") - - entry = if event.publish_at, do: Entry.published(entry, event.publish_at), else: entry + |> Entry.published(event.publish_at || event.inserted_at) # Add tags entry =