Add cached RSS feeds for actors endpoints

This commit is contained in:
Thomas Citharel 2019-02-27 16:28:09 +01:00
parent 976186a18d
commit 02d1cea2d7
No known key found for this signature in database
GPG Key ID: A061B9DDE0CA0773
14 changed files with 462 additions and 62 deletions

View File

@ -354,6 +354,9 @@ defmodule Mobilizon.Actors.Actor do
end
end
@doc """
Return the preferred_username with the eventual @domain suffix if it's a distant actor
"""
@spec actor_acct_from_actor(struct()) :: String.t()
def actor_acct_from_actor(%Actor{preferred_username: preferred_username, domain: domain}) do
if is_nil(domain) do
@ -362,4 +365,16 @@ defmodule Mobilizon.Actors.Actor do
"#{preferred_username}@#{domain}"
end
end
@doc """
Returns the display name if available, or the preferred_username (with the eventual @domain suffix if it's a distant actor).
"""
@spec display_name(struct()) :: String.t()
def display_name(%Actor{name: name} = actor) do
case name do
nil -> actor_acct_from_actor(actor)
"" -> actor_acct_from_actor(actor)
name -> name
end
end
end

View File

@ -612,67 +612,9 @@ defmodule Mobilizon.Actors do
with {:ok, %User{} = user} <-
%User{} |> User.registration_changeset(args) |> Mobilizon.Repo.insert() do
{:ok, user}
# else
# {:error, %Ecto.Changeset{} = changeset} ->
# {:error, Ecto.Changeset.traverse_errors(changeset, fn {msg, opts} ->
# Enum.reduce(opts, msg, fn {key, value}, acc ->
# String.replace(acc, "%{#{key}}", to_string(value))
# end)
# end)}
end
end
# @spec register(map()) :: {:ok, Actor.t()} | {:error, String.t()}
# def register(%{email: email, password: password, username: username}) do
# with avatar <- gravatar(email),
# user_changeset <-
# User.registration_changeset(%User{}, %{
# email: email,
# password: password,
# default_actor: %{
# preferred_username: username,
# domain: nil,
# keys: create_keys(),
# avatar_url: avatar
# }
# }),
# {:ok, %User{default_actor: %Actor{} = actor, id: user_id} = user} <-
# Mobilizon.Repo.insert(user_changeset),
# {:ok, %Actor{} = _actor} <- update_actor(actor, %{user_id: user_id}) do
# {:ok, Repo.preload(user, [:actors])}
# else
# {:error, %Ecto.Changeset{} = changeset} ->
# handle_actor_user_changeset(changeset)
# end
# end
# @spec handle_actor_user_changeset(Ecto.Changeset.t()) :: {:error, String.t()}
# defp handle_actor_user_changeset(changeset) do
# changeset =
# Ecto.Changeset.traverse_errors(changeset, fn
# {msg, _opts} -> msg
# msg -> msg
# end)
# email_msg = Map.get(changeset, :email) || [:empty_email]
# {:error, hd(email_msg)}
# end
# @spec gravatar(String.t()) :: String.t() | nil
# defp gravatar(nil), do: nil
# defp gravatar(email) do
# avatar_url = gravatar_url(email, default: "404")
# case HTTPoison.get(avatar_url) do
# {:ok, %HTTPoison.Response{status_code: 200}} ->
# avatar_url
# _ ->
# nil
# end
# end
@doc """
Create a new person actor
"""

View File

@ -17,6 +17,7 @@ defmodule Mobilizon.Application do
supervisor(MobilizonWeb.Endpoint, []),
# Start your own worker by calling: Mobilizon.Worker.start_link(arg1, arg2, arg3)
# worker(Mobilizon.Worker, [arg1, arg2, arg3]),
worker(Cachex, [:mobilizon, []]),
worker(Guardian.DB.Token.SweeperServer, []),
worker(Mobilizon.Service.Federator, [])
]

View File

@ -21,6 +21,10 @@ defmodule MobilizonWeb.ActivityPubController do
"application/activity+json, application/ld+json"
]
def actor(conn, %{"name" => _name, "_format" => "atom"} = params) do
MobilizonWeb.FeedController.actor(conn, params)
end
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

View File

@ -0,0 +1,105 @@
defmodule MobilizonWeb.FeedController do
@moduledoc """
Controller to serve RSS, ATOM and iCal Feeds
"""
use MobilizonWeb, :controller
alias Mobilizon.Actors
alias Mobilizon.Actors.Actor
alias Mobilizon.Events
alias Mobilizon.Events.Event
alias Atomex.{Feed, Entry}
import MobilizonWeb.Gettext
@version Mix.Project.config()[:version]
def version(), do: @version
def actor(conn, %{"name" => name, "_format" => format}) when format in ["atom"] do
name = String.replace_suffix(name, ".atom", "")
with {status, data} when status in [:ok, :commit] <-
Cachex.fetch(:mobilizon, "actor_" <> format <> "_" <> name, &create_cache/1) do
conn
|> put_resp_content_type("application/atom+xml")
|> send_resp(200, data)
else
_err ->
send_resp(conn, 404, "Not found")
end
end
@spec create_cache(String.t()) :: {:commit, String.t()} | {:ignore, any()}
defp create_cache(key) do
with ["actor", type, name] <- String.split(key, "_", parts: 3),
{:ok, res} <- fetch_actor_event_feed(type, name) do
{:commit, res}
else
err ->
{:ignore, err}
end
end
@spec fetch_actor_event_feed(String.t(), String.t()) :: String.t()
defp fetch_actor_event_feed(type, name) do
with %Actor{} = actor <- Actors.get_local_actor_by_name(name),
{:ok, events, _count} <- Events.get_public_events_for_actor(actor) do
{:ok, build_actor_feed(actor, events, type)}
else
err ->
{:error, err}
end
end
@spec build_actor_feed(Actor.t(), list(), String.t()) :: String.t()
defp build_actor_feed(%Actor{} = actor, events, type) do
display_name = Actor.display_name(actor)
# Title uses default instance language
feed =
Feed.new(
actor.url <> ".rss",
DateTime.utc_now(),
gettext("%{actor}'s public events feed", actor: display_name)
)
|> Feed.author(display_name, uri: actor.url)
|> Feed.link(actor.url <> "." <> type, rel: "self")
|> Feed.link(actor.url, rel: "alternate")
|> Feed.generator("Mobilizon", uri: "https://joinmobilizon.org", version: version())
|> Feed.entries(Enum.map(events, &get_entry/1))
feed = if actor.avatar_url, do: Feed.icon(feed, actor.avatar_url), else: feed
feed =
if actor.banner_url,
do: Feed.logo(feed, actor.banner_url),
else: feed
feed
|> Feed.build()
|> Atomex.generate_document()
end
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.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
# Add tags
entry =
event.tags
|> Enum.map(& &1.title)
|> Enum.uniq()
|> Enum.reduce(entry, fn tag, acc -> Entry.category(acc, tag) end)
Entry.build(entry)
else
{:error, _html, error_messages} ->
require Logger
Logger.error("Unable to produce HTML for Markdown", details: inspect(error_messages))
end
end
end

View File

@ -22,6 +22,11 @@ defmodule MobilizonWeb.Router do
plug(:accepts, ["activity-json", "html"])
end
pipeline :activity_pub_rss do
plug(TrailingFormatPlug)
plug(:accepts, ["activity-json", "html", "atom"])
end
pipeline :browser do
plug(:accepts, ["html"])
plug(:fetch_session)
@ -52,9 +57,14 @@ defmodule MobilizonWeb.Router do
end
scope "/", MobilizonWeb do
pipe_through(:activity_pub)
pipe_through(:activity_pub_rss)
get("/@:name", ActivityPubController, :actor)
end
scope "/", MobilizonWeb do
pipe_through(:activity_pub)
get("/@:name/outbox", ActivityPubController, :outbox)
get("/@:name/following", ActivityPubController, :following)
get("/@:name/followers", ActivityPubController, :followers)

View File

@ -35,7 +35,7 @@ defmodule Mobilizon.Mixfile do
def application do
[
mod: {Mobilizon.Application, []},
extra_applications: [:logger, :runtime_tools, :guardian, :bamboo, :geolix, :crypto]
extra_applications: [:logger, :runtime_tools, :guardian, :bamboo, :geolix, :crypto, :cachex]
]
end
@ -85,6 +85,10 @@ defmodule Mobilizon.Mixfile do
{:arc, "~> 0.11.0"},
{:arc_ecto, "~> 0.11.0"},
{:plug_cowboy, "~> 2.0"},
{:atomex, "0.3.0"},
{:cachex, "~> 3.1"},
{:trailing_format_plug, "~> 0.0.5"},
{:earmark, "~> 1.3.1"},
# Dev and test dependencies
{:phoenix_live_reload, "~> 1.2", only: :dev},
{:ex_machina, "~> 2.2", only: [:dev, :test]},
@ -95,7 +99,8 @@ defmodule Mobilizon.Mixfile do
{:dialyxir, "~> 1.0.0-rc.4", only: [:dev], runtime: false},
{:exvcr, "~> 0.10", only: :test},
{:credo, "~> 1.0.0", only: [:dev, :test], runtime: false},
{:mock, "~> 0.3.0", only: :test}
{:mock, "~> 0.3.0", only: :test},
{:feeder_ex, "~> 1.1", only: :test}
]
end

View File

@ -6,10 +6,12 @@
"arc": {:hex, :arc, "0.11.0", "ac7a0cc03035317b6fef9fe94c97d7d9bd183a3e7ce1606aa0c175cfa8d1ba6d", [:mix], [{:ex_aws, "~> 2.0", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:ex_aws_s3, "~> 2.0", [hex: :ex_aws_s3, repo: "hexpm", optional: true]}, {:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}, {:poison, "~> 2.2 or ~> 3.1", [hex: :poison, repo: "hexpm", optional: true]}, {:sweet_xml, "~> 0.6", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm"},
"arc_ecto": {:hex, :arc_ecto, "0.11.1", "27aedf8c236b2097eed09d96f4ae73b43eb4c042a0e2ae42d44bf644cf16115c", [:mix], [{:arc, "~> 0.11.0", [hex: :arc, repo: "hexpm", optional: false]}, {:ecto, "~> 2.1 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}], "hexpm"},
"argon2_elixir": {:hex, :argon2_elixir, "2.0.0", "e3539f441930d4c8296e36024168526626351c1f2c2df97cfd50f4e90b15386a", [:make, :mix], [{:comeonin, "~> 5.0", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm"},
"atomex": {:hex, :atomex, "0.3.0", "19b5d1a2aef8706dbd307385f7d5d9f6f273869226d317492c396c7bacf26402", [:mix], [{:xml_builder, "~> 2.0.0", [hex: :xml_builder, repo: "hexpm", optional: false]}], "hexpm"},
"bamboo": {:hex, :bamboo, "1.2.0", "8aebd24f7c606c32d0163c398004a11608ca1028182a169b2e527793bfab7561", [:mix], [{:hackney, ">= 1.13.0", [hex: :hackney, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, ">= 1.5.0", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm"},
"bamboo_smtp": {:hex, :bamboo_smtp, "1.6.0", "0a3607b77f22554af58c547350c1c73ebba6f4fb2c4bd0b11713ab5b4081588f", [:mix], [{:bamboo, "~> 1.0", [hex: :bamboo, repo: "hexpm", optional: false]}, {:gen_smtp, "~> 0.12.0", [hex: :gen_smtp, repo: "hexpm", optional: false]}], "hexpm"},
"base64url": {:hex, :base64url, "0.0.1", "36a90125f5948e3afd7be97662a1504b934dd5dac78451ca6e9abf85a10286be", [:rebar], [], "hexpm"},
"bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm"},
"cachex": {:hex, :cachex, "3.1.3", "86ed0669ea4b2f3e3982dbb5c6ca9e0964e46738e572c9156f22ceb75f57c336", [:mix], [{:eternal, "~> 1.2", [hex: :eternal, repo: "hexpm", optional: false]}, {:jumper, "~> 1.0", [hex: :jumper, repo: "hexpm", optional: false]}, {:sleeplocks, "~> 1.1", [hex: :sleeplocks, repo: "hexpm", optional: false]}, {:unsafe, "~> 1.0", [hex: :unsafe, repo: "hexpm", optional: false]}], "hexpm"},
"certifi": {:hex, :certifi, "2.4.2", "75424ff0f3baaccfd34b1214184b6ef616d89e420b258bb0a5ea7d7bc628f7f0", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm"},
"combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm"},
"comeonin": {:hex, :comeonin, "5.0.0", "e87716d3b1c31e56312f6a1545a5548cdc80376cff5025fe3b12be2046934837", [:mix], [], "hexpm"},
@ -29,6 +31,7 @@
"ecto_sql": {:hex, :ecto_sql, "3.0.5", "7e44172b4f7aca4469f38d7f6a3da394dbf43a1bcf0ca975e958cb957becd74e", [:mix], [{:db_connection, "~> 2.0", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.0.6", [hex: :ecto, repo: "hexpm", optional: false]}, {:mariaex, "~> 0.9.1", [hex: :mariaex, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.14.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.3.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm"},
"elixir_make": {:hex, :elixir_make, "0.5.2", "96a28c79f5b8d34879cd95ebc04d2a0d678cfbbd3e74c43cb63a76adf0ee8054", [:mix], [], "hexpm"},
"erlex": {:hex, :erlex, "0.2.1", "cee02918660807cbba9a7229cae9b42d1c6143b768c781fa6cee1eaf03ad860b", [:mix], [], "hexpm"},
"eternal": {:hex, :eternal, "1.2.1", "d5b6b2499ba876c57be2581b5b999ee9bdf861c647401066d3eeed111d096bc4", [:mix], [], "hexpm"},
"ex_crypto": {:hex, :ex_crypto, "0.10.0", "af600a89b784b36613a989da6e998c1b200ff1214c3cfbaf8deca4aa2f0a1739", [:mix], [{:poison, ">= 2.0.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"},
"ex_doc": {:hex, :ex_doc, "0.19.3", "3c7b0f02851f5fc13b040e8e925051452e41248f685e40250d7e40b07b9f8c10", [:mix], [{:earmark, "~> 1.2", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.10", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm"},
"ex_ical": {:hex, :ex_ical, "0.2.0", "4b928b554614704016cc0c9ee226eb854da9327a1cc460457621ceacb1ac29a6", [:mix], [{:timex, "~> 3.1", [hex: :timex, repo: "hexpm", optional: false]}], "hexpm"},
@ -39,6 +42,8 @@
"exgravatar": {:hex, :exgravatar, "2.0.1", "66d595c7d63dd6bbac442c5542a724375ae29144059c6fe093e61553850aace4", [:mix], [], "hexpm"},
"exjsx": {:hex, :exjsx, "4.0.0", "60548841e0212df401e38e63c0078ec57b33e7ea49b032c796ccad8cde794b5c", [:mix], [{:jsx, "~> 2.8.0", [hex: :jsx, repo: "hexpm", optional: false]}], "hexpm"},
"exvcr": {:hex, :exvcr, "0.10.3", "1ae3b97560430acfa88ebc737c85b2b7a9dbacd8a2b26789a19718b51ae3522c", [:mix], [{:exactor, "~> 2.2", [hex: :exactor, repo: "hexpm", optional: false]}, {:exjsx, "~> 4.0", [hex: :exjsx, repo: "hexpm", optional: false]}, {:httpoison, "~> 1.0", [hex: :httpoison, repo: "hexpm", optional: true]}, {:httpotion, "~> 3.1", [hex: :httpotion, repo: "hexpm", optional: true]}, {:ibrowse, "~> 4.4", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:meck, "~> 0.8", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm"},
"feeder": {:hex, :feeder, "2.2.4", "56ec535cf2f79719bc53b5c2abe5f6cf481fc01e5ae6229ab7cc829644f039ec", [:make], [], "hexpm"},
"feeder_ex": {:hex, :feeder_ex, "1.1.0", "0be3732255cdb45dec949e0ede6852b5261c9ff173360e8274a6ac65183b2b55", [:mix], [{:feeder, "~> 2.2", [hex: :feeder, repo: "hexpm", optional: false]}], "hexpm"},
"file_system": {:hex, :file_system, "0.2.6", "fd4dc3af89b9ab1dc8ccbcc214a0e60c41f34be251d9307920748a14bf41f1d3", [:mix], [], "hexpm"},
"gen_smtp": {:hex, :gen_smtp, "0.12.0", "97d44903f5ca18ca85cb39aee7d9c77e98d79804bbdef56078adcf905cb2ef00", [:rebar3], [], "hexpm"},
"geo": {:hex, :geo, "3.1.0", "727e005262430d037e870ff364e65d80ca5ca21d5ac8eddd57a1ada72c3f83b0", [:mix], [], "hexpm"},
@ -56,6 +61,7 @@
"jose": {:hex, :jose, "1.9.0", "4167c5f6d06ffaebffd15cdb8da61a108445ef5e85ab8f5a7ad926fdf3ada154", [:mix, :rebar3], [{:base64url, "~> 0.0.1", [hex: :base64url, repo: "hexpm", optional: false]}], "hexpm"},
"json_ld": {:hex, :json_ld, "0.3.0", "92f508ca831b9e4530e3e6c950976fdafcf26323e6817c325b3e1ee78affc4bd", [:mix], [{:jason, "~> 1.1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:rdf, "~> 0.5", [hex: :rdf, repo: "hexpm", optional: false]}], "hexpm"},
"jsx": {:hex, :jsx, "2.8.3", "a05252d381885240744d955fbe3cf810504eb2567164824e19303ea59eef62cf", [:mix, :rebar3], [], "hexpm"},
"jumper": {:hex, :jumper, "1.0.1", "3c00542ef1a83532b72269fab9f0f0c82bf23a35e27d278bfd9ed0865cecabff", [:mix], [], "hexpm"},
"makeup": {:hex, :makeup, "0.8.0", "9cf32aea71c7fe0a4b2e9246c2c4978f9070257e5c9ce6d4a28ec450a839b55f", [:mix], [{:nimble_parsec, "~> 0.5.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"},
"makeup_elixir": {:hex, :makeup_elixir, "0.13.0", "be7a477997dcac2e48a9d695ec730b2d22418292675c75aa2d34ba0909dcdeda", [:mix], [{:makeup, "~> 0.8", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm"},
"meck": {:hex, :meck, "0.8.13", "ffedb39f99b0b99703b8601c6f17c7f76313ee12de6b646e671e3188401f7866", [:rebar3], [], "hexpm"},
@ -81,10 +87,14 @@
"ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm"},
"rdf": {:hex, :rdf, "0.5.4", "57e09d4adfe7646fe0c3514b703b76eaf29d537b250b36abae75e66d7e5920cf", [:mix], [{:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm"},
"rsa_ex": {:hex, :rsa_ex, "0.4.0", "e28dd7dc5236e156df434af0e4aa822384c8866c928e17b785d4edb7c253b558", [:mix], [], "hexpm"},
"sleeplocks": {:hex, :sleeplocks, "1.1.1", "3d462a0639a6ef36cc75d6038b7393ae537ab394641beb59830a1b8271faeed3", [:rebar3], [], "hexpm"},
"slugger": {:hex, :slugger, "0.3.0", "efc667ab99eee19a48913ccf3d038b1fb9f165fa4fbf093be898b8099e61b6ed", [:mix], [], "hexpm"},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.4", "f0eafff810d2041e93f915ef59899c923f4568f4585904d010387ed74988e77b", [:make, :mix, :rebar3], [], "hexpm"},
"telemetry": {:hex, :telemetry, "0.3.0", "099a7f3ce31e4780f971b4630a3c22ec66d22208bc090fe33a2a3a6a67754a73", [:rebar3], [], "hexpm"},
"timex": {:hex, :timex, "3.5.0", "b0a23167da02d0fe4f1a4e104d1f929a00d348502b52432c05de875d0b9cffa5", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm"},
"trailing_format_plug": {:hex, :trailing_format_plug, "0.0.7", "64b877f912cf7273bed03379936df39894149e35137ac9509117e59866e10e45", [:mix], [{:plug, "> 0.12.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
"tzdata": {:hex, :tzdata, "0.5.19", "7962a3997bf06303b7d1772988ede22260f3dae1bf897408ebdac2b4435f4e6a", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
"unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm"},
"unsafe": {:hex, :unsafe, "1.0.1", "a27e1874f72ee49312e0a9ec2e0b27924214a05e3ddac90e91727bc76f8613d8", [:mix], [], "hexpm"},
"xml_builder": {:hex, :xml_builder, "2.0.0", "371ed27bb63bf0598dbaf3f0c466e5dc7d16cb4ecb68f06a67f953654062e21b", [:mix], [], "hexpm"},
}

59
priv/gettext/default.pot Normal file
View File

@ -0,0 +1,59 @@
## This file is a PO Template file.
##
## `msgid`s here are often extracted from source code.
## Add new translations manually only if they're dynamic
## translations that can't be statically extracted.
##
## Run `mix gettext.extract` to bring this file up to
## date. Leave `msgstr`s empty as changing them here as no
## effect: edit them in PO (`.po`) files instead.
#, elixir-format
#: lib/mobilizon_web/controllers/feed_controller.ex:59
msgid "%{actor}'s public events feed"
msgstr ""
#, elixir-format
#: lib/mobilizon_web/templates/email/email.html.eex:8
#: lib/mobilizon_web/templates/email/email.text.eex:3
msgid "An email sent by Mobilizon on %{instance}."
msgstr ""
#, elixir-format
#: lib/mobilizon_web/templates/email/registration_confirmation.html.eex:1
#: lib/mobilizon_web/templates/email/registration_confirmation.text.eex:1
msgid "Confirm the email address"
msgstr ""
#, elixir-format
#: lib/mobilizon_web/templates/email/password_reset.html.eex:3
#: lib/mobilizon_web/templates/email/password_reset.text.eex:7
msgid "If you didn't request this, please ignore this email. Your password won't change until you access the link below and create a new one."
msgstr ""
#, elixir-format
#: lib/mobilizon/email/user.ex:19
msgid "Mobilizon: Confirmation instructions for %{instance}"
msgstr ""
#, elixir-format
#: lib/mobilizon/email/user.ex:34
msgid "Mobilizon: Reset your password on %{instance} instructions"
msgstr ""
#, elixir-format
#: lib/mobilizon_web/templates/email/password_reset.html.eex:1
#: lib/mobilizon_web/templates/email/password_reset.text.eex:1
msgid "Password reset"
msgstr ""
#, elixir-format
#: lib/mobilizon_web/templates/email/registration_confirmation.html.eex:2
#: lib/mobilizon_web/templates/email/registration_confirmation.text.eex:5
msgid "You created an account on %{host} with this email address. You are one click away from activating it. If this wasn't you, please ignore this email."
msgstr ""
#, elixir-format
#: lib/mobilizon_web/templates/email/password_reset.html.eex:2
#: lib/mobilizon_web/templates/email/password_reset.text.eex:5
msgid "You requested a new password for your account on %{host}."
msgstr ""

View File

@ -0,0 +1,63 @@
## `msgid`s in this file come from POT (.pot) files.
##
## Do not add, change, or remove `msgid`s manually here as
## they're tied to the ones in the corresponding POT file
## (with the same domain).
##
## Use `mix gettext.extract --merge` or `mix gettext.merge`
## to merge POT files into PO files.
msgid ""
msgstr ""
"Language: en\n"
"Plural-Forms: nplurals=2\n"
#, elixir-format
#: lib/mobilizon_web/controllers/feed_controller.ex:59
msgid "%{actor}'s public events feed"
msgstr ""
#, elixir-format
#: lib/mobilizon_web/templates/email/email.html.eex:8
#: lib/mobilizon_web/templates/email/email.text.eex:3
msgid "An email sent by Mobilizon on %{instance}."
msgstr ""
#, elixir-format
#: lib/mobilizon_web/templates/email/registration_confirmation.html.eex:1
#: lib/mobilizon_web/templates/email/registration_confirmation.text.eex:1
msgid "Confirm the email address"
msgstr ""
#, elixir-format
#: lib/mobilizon_web/templates/email/password_reset.html.eex:3
#: lib/mobilizon_web/templates/email/password_reset.text.eex:7
msgid "If you didn't request this, please ignore this email. Your password won't change until you access the link below and create a new one."
msgstr ""
#, elixir-format
#: lib/mobilizon/email/user.ex:19
msgid "Mobilizon: Confirmation instructions for %{instance}"
msgstr ""
#, elixir-format
#: lib/mobilizon/email/user.ex:34
msgid "Mobilizon: Reset your password on %{instance} instructions"
msgstr ""
#, elixir-format
#: lib/mobilizon_web/templates/email/password_reset.html.eex:1
#: lib/mobilizon_web/templates/email/password_reset.text.eex:1
msgid "Password reset"
msgstr ""
#, elixir-format
#: lib/mobilizon_web/templates/email/registration_confirmation.html.eex:2
#: lib/mobilizon_web/templates/email/registration_confirmation.text.eex:5
msgid "You created an account on %{host} with this email address. You are one click away from activating it. If this wasn't you, please ignore this email."
msgstr ""
#, elixir-format
#: lib/mobilizon_web/templates/email/password_reset.html.eex:2
#: lib/mobilizon_web/templates/email/password_reset.text.eex:5
msgid "You requested a new password for your account on %{host}."
msgstr ""

View File

@ -7,7 +7,6 @@
## Run `mix gettext.extract` to bring this file up to
## date. Leave `msgstr`s empty as changing them here as no
## effect: edit them in PO (`.po`) files instead.
## From Ecto.Changeset.cast/4
msgid "can't be blank"
msgstr ""

View File

@ -0,0 +1,63 @@
## `msgid`s in this file come from POT (.pot) files.
##
## Do not add, change, or remove `msgid`s manually here as
## they're tied to the ones in the corresponding POT file
## (with the same domain).
##
## Use `mix gettext.extract --merge` or `mix gettext.merge`
## to merge POT files into PO files.
msgid ""
msgstr ""
"Language: fr_FR\n"
"Plural-Forms: nplurals=2\n"
#, elixir-format
#: lib/mobilizon_web/controllers/feed_controller.ex:59
msgid "%{actor}'s public events feed"
msgstr ""
#, elixir-format
#: lib/mobilizon_web/templates/email/email.html.eex:8
#: lib/mobilizon_web/templates/email/email.text.eex:3
msgid "An email sent by Mobilizon on %{instance}."
msgstr ""
#, elixir-format
#: lib/mobilizon_web/templates/email/registration_confirmation.html.eex:1
#: lib/mobilizon_web/templates/email/registration_confirmation.text.eex:1
msgid "Confirm the email address"
msgstr ""
#, elixir-format
#: lib/mobilizon_web/templates/email/password_reset.html.eex:3
#: lib/mobilizon_web/templates/email/password_reset.text.eex:7
msgid "If you didn't request this, please ignore this email. Your password won't change until you access the link below and create a new one."
msgstr ""
#, elixir-format
#: lib/mobilizon/email/user.ex:19
msgid "Mobilizon: Confirmation instructions for %{instance}"
msgstr ""
#, elixir-format
#: lib/mobilizon/email/user.ex:34
msgid "Mobilizon: Reset your password on %{instance} instructions"
msgstr ""
#, elixir-format
#: lib/mobilizon_web/templates/email/password_reset.html.eex:1
#: lib/mobilizon_web/templates/email/password_reset.text.eex:1
msgid "Password reset"
msgstr ""
#, elixir-format
#: lib/mobilizon_web/templates/email/registration_confirmation.html.eex:2
#: lib/mobilizon_web/templates/email/registration_confirmation.text.eex:5
msgid "You created an account on %{host} with this email address. You are one click away from activating it. If this wasn't you, please ignore this email."
msgstr ""
#, elixir-format
#: lib/mobilizon_web/templates/email/password_reset.html.eex:2
#: lib/mobilizon_web/templates/email/password_reset.text.eex:5
msgid "You requested a new password for your account on %{host}."
msgstr ""

View File

@ -0,0 +1,87 @@
## `msgid`s in this file come from POT (.pot) files.
##
## Do not add, change, or remove `msgid`s manually here as
## they're tied to the ones in the corresponding POT file
## (with the same domain).
##
## Use `mix gettext.extract --merge` or `mix gettext.merge`
## to merge POT files into PO files.
msgid ""
msgstr ""
"Language: fr_FR\n"
"Plural-Forms: nplurals=2\n"
msgid "can't be blank"
msgstr ""
msgid "has already been taken"
msgstr ""
msgid "is invalid"
msgstr ""
msgid "must be accepted"
msgstr ""
msgid "has invalid format"
msgstr ""
msgid "has an invalid entry"
msgstr ""
msgid "is reserved"
msgstr ""
msgid "does not match confirmation"
msgstr ""
msgid "is still associated with this entry"
msgstr ""
msgid "are still associated with this entry"
msgstr ""
msgid "should be %{count} character(s)"
msgid_plural "should be %{count} character(s)"
msgstr[0] ""
msgstr[1] ""
msgid "should have %{count} item(s)"
msgid_plural "should have %{count} item(s)"
msgstr[0] ""
msgstr[1] ""
msgid "should be at least %{count} character(s)"
msgid_plural "should be at least %{count} character(s)"
msgstr[0] ""
msgstr[1] ""
msgid "should have at least %{count} item(s)"
msgid_plural "should have at least %{count} item(s)"
msgstr[0] ""
msgstr[1] ""
msgid "should be at most %{count} character(s)"
msgid_plural "should be at most %{count} character(s)"
msgstr[0] ""
msgstr[1] ""
msgid "should have at most %{count} item(s)"
msgid_plural "should have at most %{count} item(s)"
msgstr[0] ""
msgstr[1] ""
msgid "must be less than %{number}"
msgstr ""
msgid "must be greater than %{number}"
msgstr ""
msgid "must be less than or equal to %{number}"
msgstr ""
msgid "must be greater than or equal to %{number}"
msgstr ""
msgid "must be equal to %{number}"
msgstr ""

View File

@ -0,0 +1,37 @@
defmodule MobilizonWeb.FeedControllerTest do
use MobilizonWeb.ConnCase
import Mobilizon.Factory
describe "/@:preferred_username.atom" do
test "it returns an RSS representation of the actor's public events", %{conn: conn} do
actor = insert(:actor)
event1 = insert(:event, organizer_actor: actor)
event2 = insert(:event, organizer_actor: actor)
conn =
conn
|> put_req_header("accept", "application/atom+xml")
|> get("/@#{actor.preferred_username}.atom")
assert response(conn, 200) =~ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
assert response_content_type(conn, :xml) =~ "charset=utf-8"
{:ok, feed, _} = FeederEx.parse(conn.resp_body)
assert feed.title == actor.preferred_username <> "'s public events feed"
Enum.each(feed.entries, fn entry ->
assert entry.title in [event1.title, event2.title]
end)
end
test "it doesn't return anything for an not existing actor", %{conn: conn} do
conn =
conn
|> put_req_header("accept", "application/atom+xml")
|> get("/@notexistent.atom")
assert response(conn, 404) == "Not found"
end
end
end