Redirect properly to correct endpoint depending on content-type
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
parent
634157eb4b
commit
66e67aa816
|
@ -8,6 +8,7 @@
|
||||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||||
<link rel="stylesheet" href="//cdn.materialdesignicons.com/2.5.94/css/materialdesignicons.min.css">
|
<link rel="stylesheet" href="//cdn.materialdesignicons.com/2.5.94/css/materialdesignicons.min.css">
|
||||||
<title>mobilizon</title>
|
<title>mobilizon</title>
|
||||||
|
<!--server-generated-meta-->
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|
|
@ -19,4 +19,19 @@ module.exports = {
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
chainWebpack: config => {
|
||||||
|
config
|
||||||
|
.plugin('html')
|
||||||
|
.tap(args => {
|
||||||
|
args[0].minify = {
|
||||||
|
collapseWhitespace: true,
|
||||||
|
removeComments: false,
|
||||||
|
removeRedundantAttributes: true,
|
||||||
|
removeScriptTypeAttributes: true,
|
||||||
|
removeStyleLinkTypeAttributes: true,
|
||||||
|
useShortDoctype: true
|
||||||
|
};
|
||||||
|
return args
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -378,6 +378,12 @@ defmodule Mobilizon.Actors.Actor do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def display_name_and_username(%Actor{name: nil} = actor), do: actor_acct_from_actor(actor)
|
||||||
|
def display_name_and_username(%Actor{name: ""} = actor), do: actor_acct_from_actor(actor)
|
||||||
|
|
||||||
|
def display_name_and_username(%Actor{name: name} = actor),
|
||||||
|
do: name <> " (" <> actor_acct_from_actor(actor) <> ")"
|
||||||
|
|
||||||
def clear_cache(%Actor{preferred_username: preferred_username, domain: nil}) do
|
def clear_cache(%Actor{preferred_username: preferred_username, domain: nil}) do
|
||||||
Cachex.del(:activity_pub, "actor_" <> preferred_username)
|
Cachex.del(:activity_pub, "actor_" <> preferred_username)
|
||||||
end
|
end
|
||||||
|
|
|
@ -467,6 +467,20 @@ defmodule Mobilizon.Actors do
|
||||||
|> Repo.preload(:organized_events)
|
|> Repo.preload(:organized_events)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Returns a cached local actor by username
|
||||||
|
"""
|
||||||
|
@spec get_cached_local_actor_by_name(String.t()) ::
|
||||||
|
{:ok, Actor.t()} | {:commit, Actor.t()} | {:ignore, any()}
|
||||||
|
def get_cached_local_actor_by_name(name) do
|
||||||
|
Cachex.fetch(:activity_pub, "actor_" <> name, fn "actor_" <> name ->
|
||||||
|
case get_local_actor_by_name(name) do
|
||||||
|
nil -> {:ignore, nil}
|
||||||
|
%Actor{} = actor -> {:commit, actor}
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Getting an actor from url, eventually creating it
|
Getting an actor from url, eventually creating it
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -154,6 +154,16 @@ defmodule Mobilizon.Events do
|
||||||
|> Repo.one()
|
|> Repo.one()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def get_cached_event_full_by_uuid(uuid) do
|
||||||
|
Cachex.fetch(:activity_pub, "event_" <> uuid, fn "event_" <> uuid ->
|
||||||
|
with %Event{} = event <- get_event_full_by_uuid(uuid) do
|
||||||
|
{:commit, event}
|
||||||
|
else
|
||||||
|
_ -> {:ignore, nil}
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Gets a single event, with all associations loaded.
|
Gets a single event, with all associations loaded.
|
||||||
"""
|
"""
|
||||||
|
@ -1018,6 +1028,16 @@ defmodule Mobilizon.Events do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def get_cached_comment_full_by_uuid("comment_" <> uuid) do
|
||||||
|
Cachex.fetch(:activity_pub, "comment_" <> uuid, fn "comment_" <> uuid ->
|
||||||
|
with %Comment{} = comment <- Events.get_comment_full_from_uuid(uuid) do
|
||||||
|
{:commit, comment}
|
||||||
|
else
|
||||||
|
_ -> {:ignore, nil}
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
def get_comment_from_url(url), do: Repo.get_by(Comment, url: url)
|
def get_comment_from_url(url), do: Repo.get_by(Comment, url: url)
|
||||||
|
|
||||||
def get_comment_from_url!(url), do: Repo.get_by!(Comment, url: url)
|
def get_comment_from_url!(url), do: Repo.get_by!(Comment, url: url)
|
||||||
|
|
|
@ -16,101 +16,55 @@ defmodule MobilizonWeb.ActivityPubController do
|
||||||
|
|
||||||
action_fallback(:errors)
|
action_fallback(:errors)
|
||||||
|
|
||||||
@activity_pub_headers [
|
|
||||||
"application/activity+json",
|
|
||||||
"application/activity+json, application/ld+json"
|
|
||||||
]
|
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Show an Actor's ActivityPub representation
|
Renders an Actor ActivityPub's representation
|
||||||
"""
|
"""
|
||||||
@spec actor(Plug.Conn.t(), map()) :: Plug.Conn.t()
|
@spec actor(Plug.Conn.t(), String.t()) :: Plug.Conn.t()
|
||||||
def actor(conn, %{"name" => name}) do
|
def actor(conn, %{"name" => name}) do
|
||||||
if conn |> get_req_header("accept") |> is_ap_header() do
|
with {status, %Actor{} = actor} when status in [:ok, :commit] <-
|
||||||
render_cached_actor(conn, name)
|
Actors.get_cached_local_actor_by_name(name) do
|
||||||
else
|
|
||||||
conn
|
conn
|
||||||
|> put_resp_content_type("text/html")
|
|> put_resp_header("content-type", "application/activity+json")
|
||||||
|> send_file(200, "priv/static/index.html")
|
|> json(ActorView.render("actor.json", %{actor: actor}))
|
||||||
end
|
else
|
||||||
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, _} ->
|
{:ignore, _} ->
|
||||||
{:error, :not_found}
|
{:error, :not_found}
|
||||||
end
|
end
|
||||||
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
|
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Renders an Event ActivityPub's representation
|
Renders an Event ActivityPub's representation
|
||||||
"""
|
"""
|
||||||
@spec event(Plug.Conn.t(), map()) :: Plug.Conn.t()
|
@spec event(Plug.Conn.t(), map()) :: Plug.Conn.t()
|
||||||
def event(conn, %{"uuid" => uuid}) do
|
def event(conn, %{"uuid" => uuid}) do
|
||||||
case Cachex.fetch(:activity_pub, "event_" <> uuid, &get_event_full_by_uuid/1) do
|
with {status, %Event{}} = event when status in [:ok, :commit] <-
|
||||||
{status, %Event{} = event} when status in [:ok, :commit] ->
|
Events.get_cached_event_full_by_uuid(uuid),
|
||||||
conn
|
true <- event.visibility in [:public, :unlisted] do
|
||||||
|> put_resp_header("content-type", "application/activity+json")
|
conn
|
||||||
|> json(ObjectView.render("event.json", %{event: event |> Utils.make_event_data()}))
|
|> put_resp_header("content-type", "application/activity+json")
|
||||||
|
|> json(ObjectView.render("event.json", %{event: event |> Utils.make_event_data()}))
|
||||||
|
else
|
||||||
{:ignore, _} ->
|
{:ignore, _} ->
|
||||||
{:error, :not_found}
|
{:error, :not_found}
|
||||||
end
|
end
|
||||||
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 """
|
@doc """
|
||||||
Renders a Comment ActivityPub's representation
|
Renders a Comment ActivityPub's representation
|
||||||
"""
|
"""
|
||||||
@spec comment(Plug.Conn.t(), map()) :: Plug.Conn.t()
|
@spec comment(Plug.Conn.t(), map()) :: Plug.Conn.t()
|
||||||
def comment(conn, %{"uuid" => uuid}) do
|
def comment(conn, %{"uuid" => uuid}) do
|
||||||
case Cachex.fetch(:activity_pub, "comment_" <> uuid, &get_comment_full_by_uuid/1) do
|
with {status, %Comment{} = comment} when status in [:ok, :commit] <-
|
||||||
{status, %Comment{} = comment} when status in [:ok, :commit] ->
|
Events.get_cached_comment_full_by_uuid(uuid) do
|
||||||
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
|
# Comments are always public for now
|
||||||
# TODO : Make comments maybe restricted
|
# TODO : Make comments maybe restricted
|
||||||
# true <- comment.public do
|
# true <- comment.public do
|
||||||
{:commit, comment}
|
conn
|
||||||
|
|> put_resp_header("content-type", "application/activity+json")
|
||||||
|
|> json(ObjectView.render("comment.json", %{comment: comment |> Utils.make_comment_data()}))
|
||||||
else
|
else
|
||||||
_ -> {:ignore, nil}
|
{:ignore, _} ->
|
||||||
|
{:error, :not_found}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,11 @@ defmodule MobilizonWeb.PageController do
|
||||||
Controller to load our webapp
|
Controller to load our webapp
|
||||||
"""
|
"""
|
||||||
use MobilizonWeb, :controller
|
use MobilizonWeb, :controller
|
||||||
|
alias Mobilizon.Service.Metadata
|
||||||
|
alias Mobilizon.Actors
|
||||||
|
alias Mobilizon.Actors.Actor
|
||||||
|
alias Mobilizon.Events
|
||||||
|
alias Mobilizon.Events.{Event, Comment}
|
||||||
|
|
||||||
plug(:put_layout, false)
|
plug(:put_layout, false)
|
||||||
|
|
||||||
|
@ -11,4 +16,71 @@ defmodule MobilizonWeb.PageController do
|
||||||
|> put_resp_content_type("text/html")
|
|> put_resp_content_type("text/html")
|
||||||
|> send_file(200, "priv/static/index.html")
|
|> send_file(200, "priv/static/index.html")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def actor(conn, %{"name" => name}) do
|
||||||
|
case get_format(conn) do
|
||||||
|
"html" ->
|
||||||
|
with {status, %Actor{} = actor} when status in [:ok, :commit] <-
|
||||||
|
Actors.get_cached_local_actor_by_name(name) do
|
||||||
|
render_with_meta(conn, actor)
|
||||||
|
end
|
||||||
|
|
||||||
|
"activity+json" ->
|
||||||
|
MobilizonWeb.ActivityPubController.call(conn, :actor)
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
{:error, :not_found}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def event(conn, %{"uuid" => uuid}) do
|
||||||
|
case get_format(conn) do
|
||||||
|
"html" ->
|
||||||
|
with {status, %Event{} = event} when status in [:ok, :commit] <-
|
||||||
|
Events.get_cached_event_full_by_uuid(uuid),
|
||||||
|
true <- event.visibility in [:public, :unlisted] do
|
||||||
|
render_with_meta(conn, event)
|
||||||
|
end
|
||||||
|
|
||||||
|
"activity+json" ->
|
||||||
|
MobilizonWeb.ActivityPubController.call(conn, :event)
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
{:error, :not_found}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def comment(conn, %{"uuid" => uuid}) do
|
||||||
|
case get_format(conn) do
|
||||||
|
"html" ->
|
||||||
|
with {status, %Comment{} = comment} when status in [:ok, :commit] <-
|
||||||
|
Events.get_cached_comment_full_by_uuid(uuid) do
|
||||||
|
# Comments are always public for now
|
||||||
|
# TODO : Make comments maybe restricted
|
||||||
|
# true <- comment.public do
|
||||||
|
render_with_meta(conn, comment)
|
||||||
|
end
|
||||||
|
|
||||||
|
"activity+json" ->
|
||||||
|
MobilizonWeb.ActivityPubController.call(conn, :comment)
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
{:error, :not_found}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Inject OpenGraph information
|
||||||
|
defp render_with_meta(conn, object) do
|
||||||
|
{:ok, index_content} = File.read(index_file_path())
|
||||||
|
tags = Metadata.build_tags(object)
|
||||||
|
response = String.replace(index_content, "<!--server-generated-meta-->", tags)
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> put_resp_content_type("text/html")
|
||||||
|
|> send_resp(200, response)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp index_file_path() do
|
||||||
|
Path.join(Application.app_dir(:mobilizon, "priv/static/"), "index.html")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -19,6 +19,10 @@ defmodule MobilizonWeb.Router do
|
||||||
end
|
end
|
||||||
|
|
||||||
pipeline :activity_pub do
|
pipeline :activity_pub do
|
||||||
|
plug(:accepts, ["activity-json"])
|
||||||
|
end
|
||||||
|
|
||||||
|
pipeline :activity_pub_and_html do
|
||||||
plug(:accepts, ["activity-json", "html"])
|
plug(:accepts, ["activity-json", "html"])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -61,15 +65,19 @@ defmodule MobilizonWeb.Router do
|
||||||
get("/@:name/feed/:format", FeedController, :actor)
|
get("/@:name/feed/:format", FeedController, :actor)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
scope "/", MobilizonWeb do
|
||||||
|
pipe_through(:activity_pub_and_html)
|
||||||
|
get("/@:name", PageController, :actor)
|
||||||
|
get("/events/:uuid", PageController, :event)
|
||||||
|
get("/comments/:uuid", PageController, :comment)
|
||||||
|
end
|
||||||
|
|
||||||
scope "/", MobilizonWeb do
|
scope "/", MobilizonWeb do
|
||||||
pipe_through(:activity_pub)
|
pipe_through(:activity_pub)
|
||||||
|
|
||||||
get("/@:name", ActivityPubController, :actor)
|
|
||||||
get("/@:name/outbox", ActivityPubController, :outbox)
|
get("/@:name/outbox", ActivityPubController, :outbox)
|
||||||
get("/@:name/following", ActivityPubController, :following)
|
get("/@:name/following", ActivityPubController, :following)
|
||||||
get("/@:name/followers", ActivityPubController, :followers)
|
get("/@:name/followers", ActivityPubController, :followers)
|
||||||
get("/events/:uuid", ActivityPubController, :event)
|
|
||||||
get("/comments/:uuid", ActivityPubController, :comment)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
scope "/", MobilizonWeb do
|
scope "/", MobilizonWeb do
|
||||||
|
|
6
lib/service/metadata.ex
Normal file
6
lib/service/metadata.ex
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
defprotocol Mobilizon.Service.Metadata do
|
||||||
|
@doc """
|
||||||
|
Build tags
|
||||||
|
"""
|
||||||
|
def build_tags(entity)
|
||||||
|
end
|
25
lib/service/metadata/actor.ex
Normal file
25
lib/service/metadata/actor.ex
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
defimpl Mobilizon.Service.Metadata, for: Mobilizon.Actors.Actor do
|
||||||
|
alias Phoenix.HTML
|
||||||
|
alias Phoenix.HTML.Tag
|
||||||
|
alias Mobilizon.Actors.Actor
|
||||||
|
require Logger
|
||||||
|
|
||||||
|
def build_tags(%Actor{} = actor) do
|
||||||
|
actor
|
||||||
|
|> do_build_tags()
|
||||||
|
|> Enum.map(&HTML.safe_to_string/1)
|
||||||
|
|> Enum.reduce("", fn tag, acc -> acc <> tag end)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp do_build_tags(%Actor{} = actor) do
|
||||||
|
[
|
||||||
|
Tag.tag(:meta, property: "og:title", content: Actor.display_name_and_username(actor)),
|
||||||
|
Tag.tag(:meta, property: "og:url", content: actor.url),
|
||||||
|
Tag.tag(:meta, property: "og:description", content: actor.summary),
|
||||||
|
Tag.tag(:meta, property: "og:type", content: "profile"),
|
||||||
|
Tag.tag(:meta, property: "profile:username", content: actor.preferred_username),
|
||||||
|
Tag.tag(:meta, property: "og:image", content: actor.avatar_url),
|
||||||
|
Tag.tag(:meta, property: "twitter:card", content: "summary")
|
||||||
|
]
|
||||||
|
end
|
||||||
|
end
|
22
lib/service/metadata/comment.ex
Normal file
22
lib/service/metadata/comment.ex
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
defimpl Mobilizon.Service.Metadata, for: Mobilizon.Events.Comment do
|
||||||
|
alias Phoenix.HTML
|
||||||
|
alias Phoenix.HTML.Tag
|
||||||
|
alias Mobilizon.Events.Comment
|
||||||
|
|
||||||
|
def build_tags(%Comment{} = comment) do
|
||||||
|
comment
|
||||||
|
|> do_build_tags()
|
||||||
|
|> Enum.map(&HTML.safe_to_string/1)
|
||||||
|
|> Enum.reduce("", fn tag, acc -> acc <> tag end)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp do_build_tags(%Comment{} = comment) do
|
||||||
|
[
|
||||||
|
Tag.tag(:meta, property: "og:title", content: comment.actor.preferred_username),
|
||||||
|
Tag.tag(:meta, property: "og:url", content: comment.url),
|
||||||
|
Tag.tag(:meta, property: "og:description", content: comment.text),
|
||||||
|
Tag.tag(:meta, property: "og:type", content: "website"),
|
||||||
|
Tag.tag(:meta, property: "twitter:card", content: "summary")
|
||||||
|
]
|
||||||
|
end
|
||||||
|
end
|
24
lib/service/metadata/event.ex
Normal file
24
lib/service/metadata/event.ex
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
defimpl Mobilizon.Service.Metadata, for: Mobilizon.Events.Event do
|
||||||
|
alias Phoenix.HTML
|
||||||
|
alias Phoenix.HTML.Tag
|
||||||
|
alias Mobilizon.Events.Event
|
||||||
|
|
||||||
|
def build_tags(%Event{} = event) do
|
||||||
|
event
|
||||||
|
|> do_build_tags()
|
||||||
|
|> Enum.map(&HTML.safe_to_string/1)
|
||||||
|
|> Enum.reduce("", fn tag, acc -> acc <> tag end)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp do_build_tags(%Event{} = event) do
|
||||||
|
[
|
||||||
|
Tag.tag(:meta, property: "og:title", content: event.title),
|
||||||
|
Tag.tag(:meta, property: "og:url", content: event.url),
|
||||||
|
Tag.tag(:meta, property: "og:description", content: event.description),
|
||||||
|
Tag.tag(:meta, property: "og:type", content: "website"),
|
||||||
|
Tag.tag(:meta, property: "og:image", content: event.thumbnail),
|
||||||
|
Tag.tag(:meta, property: "og:image", content: event.large_image),
|
||||||
|
Tag.tag(:meta, property: "twitter:card", content: "summary_large_image")
|
||||||
|
]
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in a new issue