Introduce group basic federation, event new page and notifications
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
62
lib/web/cache/activity_pub.ex
vendored
62
lib/web/cache/activity_pub.ex
vendored
@@ -3,12 +3,13 @@ defmodule Mobilizon.Web.Cache.ActivityPub do
|
||||
ActivityPub related cache.
|
||||
"""
|
||||
|
||||
alias Mobilizon.{Actors, Events, Tombstone}
|
||||
alias Mobilizon.{Actors, Conversations, Events, Resources, Todos, Tombstone}
|
||||
alias Mobilizon.Actors.Actor
|
||||
alias Mobilizon.Events.{Comment, Event}
|
||||
|
||||
alias Mobilizon.Conversations.Comment
|
||||
alias Mobilizon.Events.Event
|
||||
alias Mobilizon.Federation.ActivityPub.Relay
|
||||
|
||||
alias Mobilizon.Resources.Resource
|
||||
alias Mobilizon.Todos.{Todo, TodoList}
|
||||
alias Mobilizon.Web.Endpoint
|
||||
alias Mobilizon.Web.Router.Helpers, as: Routes
|
||||
|
||||
@@ -60,7 +61,7 @@ defmodule Mobilizon.Web.Cache.ActivityPub do
|
||||
{:commit, Comment.t()} | {:ignore, nil}
|
||||
def get_comment_by_uuid_with_preload(uuid) do
|
||||
Cachex.fetch(@cache, "comment_" <> uuid, fn "comment_" <> uuid ->
|
||||
case Events.get_comment_from_uuid_with_preload(uuid) do
|
||||
case Conversations.get_comment_from_uuid_with_preload(uuid) do
|
||||
%Comment{} = comment ->
|
||||
{:commit, comment}
|
||||
|
||||
@@ -70,6 +71,57 @@ defmodule Mobilizon.Web.Cache.ActivityPub do
|
||||
end)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets a resource by its UUID, with all associations loaded.
|
||||
"""
|
||||
@spec get_resource_by_uuid_with_preload(String.t()) ::
|
||||
{:commit, Resource.t()} | {:ignore, nil}
|
||||
def get_resource_by_uuid_with_preload(uuid) do
|
||||
Cachex.fetch(@cache, "resource_" <> uuid, fn "resource_" <> uuid ->
|
||||
case Resources.get_resource_with_preloads(uuid) do
|
||||
%Resource{} = resource ->
|
||||
{:commit, resource}
|
||||
|
||||
nil ->
|
||||
{:ignore, nil}
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets a todo list by its UUID, with all associations loaded.
|
||||
"""
|
||||
@spec get_todo_list_by_uuid_with_preload(String.t()) ::
|
||||
{:commit, TodoList.t()} | {:ignore, nil}
|
||||
def get_todo_list_by_uuid_with_preload(uuid) do
|
||||
Cachex.fetch(@cache, "todo_list_" <> uuid, fn "todo_list_" <> uuid ->
|
||||
case Todos.get_todo_list(uuid) do
|
||||
%TodoList{} = todo_list ->
|
||||
{:commit, todo_list}
|
||||
|
||||
nil ->
|
||||
{:ignore, nil}
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets a todo by its UUID, with all associations loaded.
|
||||
"""
|
||||
@spec get_todo_by_uuid_with_preload(String.t()) ::
|
||||
{:commit, Todo.t()} | {:ignore, nil}
|
||||
def get_todo_by_uuid_with_preload(uuid) do
|
||||
Cachex.fetch(@cache, "todo_" <> uuid, fn "todo_" <> uuid ->
|
||||
case Todos.get_todo(uuid) do
|
||||
%Todo{} = todo ->
|
||||
{:commit, todo}
|
||||
|
||||
nil ->
|
||||
{:ignore, nil}
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets a relay.
|
||||
"""
|
||||
|
||||
3
lib/web/cache/cache.ex
vendored
3
lib/web/cache/cache.ex
vendored
@@ -20,5 +20,8 @@ defmodule Mobilizon.Web.Cache do
|
||||
defdelegate get_local_actor_by_name(name), to: ActivityPub
|
||||
defdelegate get_public_event_by_uuid_with_preload(uuid), to: ActivityPub
|
||||
defdelegate get_comment_by_uuid_with_preload(uuid), to: ActivityPub
|
||||
defdelegate get_resource_by_uuid_with_preload(uuid), to: ActivityPub
|
||||
defdelegate get_todo_list_by_uuid_with_preload(uuid), to: ActivityPub
|
||||
defdelegate get_todo_by_uuid_with_preload(uuid), to: ActivityPub
|
||||
defdelegate get_relay, to: ActivityPub
|
||||
end
|
||||
|
||||
@@ -67,6 +67,47 @@ defmodule Mobilizon.Web.ActivityPubController do
|
||||
end
|
||||
end
|
||||
|
||||
def members(conn, %{"name" => name, "page" => page}) do
|
||||
with {page, ""} <- Integer.parse(page),
|
||||
%Actor{} = group <- Actors.get_local_actor_by_name_with_preload(name) do
|
||||
conn
|
||||
|> put_resp_header("content-type", "application/activity+json")
|
||||
|> json(
|
||||
ActorView.render("members.json", %{
|
||||
group: group,
|
||||
page: page,
|
||||
actor_applicant: Map.get(conn.assigns, :actor)
|
||||
})
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def members(conn, %{"name" => name}) do
|
||||
with %Actor{} = group <- Actors.get_local_actor_by_name_with_preload(name) do
|
||||
conn
|
||||
|> put_resp_header("content-type", "application/activity+json")
|
||||
|> json(
|
||||
ActorView.render("members.json", %{
|
||||
group: group,
|
||||
actor_applicant: Map.get(conn.assigns, :actor)
|
||||
})
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def resources(conn, %{"name" => name}) do
|
||||
with %Actor{} = group <- Actors.get_local_actor_by_name_with_preload(name) do
|
||||
conn
|
||||
|> put_resp_header("content-type", "application/activity+json")
|
||||
|> json(
|
||||
ActorView.render("resources.json", %{
|
||||
group: group,
|
||||
actor_applicant: Map.get(conn.assigns, :actor)
|
||||
})
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def outbox(conn, %{"name" => name, "page" => page}) do
|
||||
with {page, ""} <- Integer.parse(page),
|
||||
%Actor{} = actor <- Actors.get_local_actor_by_name(name) do
|
||||
|
||||
@@ -4,31 +4,65 @@ defmodule Mobilizon.Web.PageController do
|
||||
"""
|
||||
use Mobilizon.Web, :controller
|
||||
|
||||
alias Mobilizon.Events.{Comment, Event}
|
||||
alias Mobilizon.Conversations.Comment
|
||||
alias Mobilizon.Events.Event
|
||||
alias Mobilizon.Federation.ActivityPub
|
||||
alias Mobilizon.Tombstone
|
||||
alias Mobilizon.Web.Cache
|
||||
alias Mobilizon.Web.{ActivityPubController, Cache}
|
||||
|
||||
plug(:put_layout, false)
|
||||
action_fallback(Mobilizon.Web.FallbackController)
|
||||
|
||||
@spec index(Plug.Conn.t(), any) :: Plug.Conn.t()
|
||||
def index(conn, _params), do: render(conn, :index)
|
||||
|
||||
@spec actor(Plug.Conn.t(), map) :: {:error, :not_found} | Plug.Conn.t()
|
||||
def actor(conn, %{"name" => name}) do
|
||||
{status, actor} = Cache.get_local_actor_by_name(name)
|
||||
render_or_error(conn, &ok_status?/3, status, :actor, actor)
|
||||
end
|
||||
|
||||
@spec event(Plug.Conn.t(), map) :: {:error, :not_found} | Plug.Conn.t()
|
||||
def event(conn, %{"uuid" => uuid}) do
|
||||
{status, event} = Cache.get_public_event_by_uuid_with_preload(uuid)
|
||||
render_or_error(conn, &checks?/3, status, :event, event)
|
||||
end
|
||||
|
||||
@spec comment(Plug.Conn.t(), map) :: {:error, :not_found} | Plug.Conn.t()
|
||||
def comment(conn, %{"uuid" => uuid}) do
|
||||
{status, comment} = Cache.get_comment_by_uuid_with_preload(uuid)
|
||||
render_or_error(conn, &checks?/3, status, :comment, comment)
|
||||
end
|
||||
|
||||
@spec resource(Plug.Conn.t(), map()) :: Plug.Conn.t() | {:error, :not_found}
|
||||
def resource(conn, %{"uuid" => uuid}) do
|
||||
{status, resource} = Cache.get_resource_by_uuid_with_preload(uuid)
|
||||
render_or_error(conn, &checks?/3, status, :resource, resource)
|
||||
end
|
||||
|
||||
def resources(conn, %{"name" => _name}) do
|
||||
case get_format(conn) do
|
||||
"html" ->
|
||||
render(conn, :index)
|
||||
|
||||
"activity-json" ->
|
||||
ActivityPubController.call(conn, :resources)
|
||||
end
|
||||
end
|
||||
|
||||
@spec todo_list(Plug.Conn.t(), map) :: {:error, :not_found} | Plug.Conn.t()
|
||||
def todo_list(conn, %{"uuid" => uuid}) do
|
||||
{status, todo_list} = Cache.get_todo_list_by_uuid_with_preload(uuid)
|
||||
render_or_error(conn, &checks?/3, status, :todo_list, todo_list)
|
||||
end
|
||||
|
||||
@spec todo(Plug.Conn.t(), map) :: {:error, :not_found} | Plug.Conn.t()
|
||||
def todo(conn, %{"uuid" => uuid}) do
|
||||
{status, todo} = Cache.get_todo_by_uuid_with_preload(uuid)
|
||||
render_or_error(conn, &checks?/3, status, :todo, todo)
|
||||
end
|
||||
|
||||
@spec interact(Plug.Conn.t(), map()) :: Plug.Conn.t() | {:error, :not_found}
|
||||
def interact(conn, %{"uri" => uri}) do
|
||||
case ActivityPub.fetch_object_from_url(uri) do
|
||||
{:ok, %Event{uuid: uuid}} -> redirect(conn, to: "/events/#{uuid}")
|
||||
@@ -60,6 +94,7 @@ defmodule Mobilizon.Web.PageController do
|
||||
|
||||
defp is_visible?(%{visibility: v}), do: v in [:public, :unlisted]
|
||||
defp is_visible?(%Tombstone{}), do: true
|
||||
defp is_visible?(_), do: true
|
||||
|
||||
defp ok_status?(status), do: status in [:ok, :commit]
|
||||
defp ok_status?(_conn, status, _), do: ok_status?(status)
|
||||
@@ -75,6 +110,6 @@ defmodule Mobilizon.Web.PageController do
|
||||
end
|
||||
end
|
||||
|
||||
defp is_local?(%Event{local: local}), do: if(local, do: true, else: :remote)
|
||||
defp is_local?(%Comment{local: local}), do: if(local, do: true, else: :remote)
|
||||
defp is_local?(%{local: local}), do: if(local, do: true, else: :remote)
|
||||
defp is_local?(_), do: false
|
||||
end
|
||||
|
||||
@@ -19,7 +19,7 @@ defmodule Mobilizon.Web.Email.Event do
|
||||
|
||||
@important_changes [:title, :begins_on, :ends_on, :status]
|
||||
|
||||
@spec event_updated(User.t(), Actor.t(), Event.t(), Event.t(), list(), String.t()) ::
|
||||
@spec event_updated(User.t(), Actor.t(), Event.t(), Event.t(), MapSet.t(), String.t()) ::
|
||||
Bamboo.Email.t()
|
||||
def event_updated(
|
||||
%User{} = user,
|
||||
@@ -82,4 +82,12 @@ defmodule Mobilizon.Web.Email.Event do
|
||||
|> Email.Event.event_updated(actor, old_event, event, diff, locale)
|
||||
|> Email.Mailer.deliver_later()
|
||||
end
|
||||
|
||||
defp send_notification_for_event_update_to_participant(user, old_event, new_event, diff) do
|
||||
require Logger
|
||||
Logger.error(inspect(user))
|
||||
Logger.error(inspect(old_event))
|
||||
Logger.error(inspect(new_event))
|
||||
Logger.error(inspect(diff))
|
||||
end
|
||||
end
|
||||
|
||||
47
lib/web/email/group.ex
Normal file
47
lib/web/email/group.ex
Normal file
@@ -0,0 +1,47 @@
|
||||
defmodule Mobilizon.Web.Email.Group do
|
||||
@moduledoc """
|
||||
Handles emails sent about participation.
|
||||
"""
|
||||
use Bamboo.Phoenix, view: Mobilizon.Web.EmailView
|
||||
|
||||
import Bamboo.Phoenix
|
||||
import Mobilizon.Web.Gettext
|
||||
|
||||
alias Mobilizon.{Actors, Users}
|
||||
alias Mobilizon.Actors.{Actor, Member}
|
||||
alias Mobilizon.Users.User
|
||||
alias Mobilizon.Web.{Email, Gettext}
|
||||
|
||||
@doc """
|
||||
Send emails to local user
|
||||
"""
|
||||
def send_invite_to_user(
|
||||
%Member{actor: %Actor{user_id: user_id}, parent: %Actor{} = group, role: :invited} =
|
||||
member,
|
||||
locale \\ "en"
|
||||
) do
|
||||
with %User{email: email} <- Users.get_user!(user_id) do
|
||||
Gettext.put_locale(locale)
|
||||
%Actor{name: invited_by_name} = inviter = Actors.get_actor(member.invited_by_id)
|
||||
|
||||
subject =
|
||||
gettext(
|
||||
"You have been invited by %{inviter} to join group %{group}",
|
||||
inviter: invited_by_name,
|
||||
group: group.name
|
||||
)
|
||||
|
||||
Email.base_email(to: email, subject: subject)
|
||||
|> assign(:locale, locale)
|
||||
|> assign(:inviter, inviter)
|
||||
|> assign(:group, group)
|
||||
|> assign(:subject, subject)
|
||||
|> render(:group_invite)
|
||||
|> Email.Mailer.deliver_later()
|
||||
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
# TODO : def send_confirmation_to_inviter()
|
||||
end
|
||||
59
lib/web/email/notification.ex
Normal file
59
lib/web/email/notification.ex
Normal file
@@ -0,0 +1,59 @@
|
||||
defmodule Mobilizon.Web.Email.Notification do
|
||||
@moduledoc """
|
||||
Handles emails sent about event notifications.
|
||||
"""
|
||||
use Bamboo.Phoenix, view: Mobilizon.Web.EmailView
|
||||
|
||||
import Bamboo.Phoenix
|
||||
import Mobilizon.Web.Gettext
|
||||
|
||||
alias Mobilizon.Events.Participant
|
||||
alias Mobilizon.Users.{Setting, User}
|
||||
alias Mobilizon.Web.{Email, Gettext}
|
||||
|
||||
@spec before_event_notification(String.t(), Participant.t(), String.t()) ::
|
||||
Bamboo.Email.t()
|
||||
def before_event_notification(
|
||||
email,
|
||||
%Participant{event: event, role: :participant} = participant,
|
||||
locale \\ "en"
|
||||
) do
|
||||
Gettext.put_locale(locale)
|
||||
|
||||
subject =
|
||||
gettext(
|
||||
"Don't forget to go to %{title}",
|
||||
title: event.title
|
||||
)
|
||||
|
||||
Email.base_email(to: email, subject: subject)
|
||||
|> assign(:locale, locale)
|
||||
|> assign(:participant, participant)
|
||||
|> assign(:subject, subject)
|
||||
|> render(:before_event_notification)
|
||||
end
|
||||
|
||||
def on_day_notification(
|
||||
%User{email: email, settings: %Setting{timezone: timezone}},
|
||||
participations,
|
||||
total,
|
||||
locale \\ "en"
|
||||
) do
|
||||
Gettext.put_locale(locale)
|
||||
participation = hd(participations)
|
||||
|
||||
subject =
|
||||
ngettext("One event planned today", "%{nb_events} events planned today", total,
|
||||
nb_events: total
|
||||
)
|
||||
|
||||
Email.base_email(to: email, subject: subject)
|
||||
|> assign(:locale, locale)
|
||||
|> assign(:participation, participation)
|
||||
|> assign(:participations, participations)
|
||||
|> assign(:total, total)
|
||||
|> assign(:timezone, timezone)
|
||||
|> assign(:subject, subject)
|
||||
|> render(:on_day_notification)
|
||||
end
|
||||
end
|
||||
@@ -28,9 +28,11 @@ defmodule Mobilizon.Web.Endpoint do
|
||||
plug(
|
||||
Plug.Static,
|
||||
at: "/",
|
||||
from: :mobilizon,
|
||||
from: {:mobilizon, "priv/static"},
|
||||
gzip: false,
|
||||
only: ~w(css fonts images js favicon.ico robots.txt)
|
||||
only:
|
||||
~w(index.html manifest.json service-worker.js css fonts images js favicon.ico robots.txt),
|
||||
only_matching: ["precache-manifest"]
|
||||
)
|
||||
|
||||
# Code reloading can be explicitly enabled under the
|
||||
|
||||
@@ -30,6 +30,10 @@ defmodule Mobilizon.Web.Router do
|
||||
|
||||
pipeline :activity_pub_and_html do
|
||||
plug(:accepts, ["html", "activity-json"])
|
||||
|
||||
plug(Cldr.Plug.AcceptLanguage,
|
||||
cldr_backend: Mobilizon.Cldr
|
||||
)
|
||||
end
|
||||
|
||||
pipeline :atom_and_ical do
|
||||
@@ -38,6 +42,11 @@ defmodule Mobilizon.Web.Router do
|
||||
|
||||
pipeline :browser do
|
||||
plug(Plug.Static, at: "/", from: "priv/static")
|
||||
|
||||
plug(Cldr.Plug.AcceptLanguage,
|
||||
cldr_backend: Mobilizon.Cldr
|
||||
)
|
||||
|
||||
plug(:accepts, ["html"])
|
||||
plug(:fetch_session)
|
||||
plug(:fetch_flash)
|
||||
@@ -72,8 +81,13 @@ defmodule Mobilizon.Web.Router do
|
||||
pipe_through(:activity_pub_signature)
|
||||
|
||||
get("/@:name", PageController, :actor)
|
||||
get("/events/me", PageController, :index)
|
||||
get("/events/:uuid", PageController, :event)
|
||||
get("/comments/:uuid", PageController, :comment)
|
||||
get("/resource/:uuid", PageController, :resource, as: "resource")
|
||||
get("/todo-list/:uuid", PageController, :todo_list, as: "todo_list")
|
||||
get("/todo/:uuid", PageController, :todo, as: "todo")
|
||||
get("/@:name/resources", PageController, :resources)
|
||||
end
|
||||
|
||||
scope "/", Mobilizon.Web do
|
||||
@@ -82,6 +96,8 @@ defmodule Mobilizon.Web.Router do
|
||||
get("/@:name/outbox", ActivityPubController, :outbox)
|
||||
get("/@:name/following", ActivityPubController, :following)
|
||||
get("/@:name/followers", ActivityPubController, :followers)
|
||||
get("/@:name/members", ActivityPubController, :members)
|
||||
get("/@:name/todo-lists", ActivityPubController, :todo_lists)
|
||||
end
|
||||
|
||||
scope "/", Mobilizon.Web do
|
||||
@@ -131,6 +147,7 @@ defmodule Mobilizon.Web.Router do
|
||||
)
|
||||
|
||||
get("/validate/email/:token", PageController, :index, as: "user_email_validation")
|
||||
get("/groups/me", PageController, :index, as: "my_groups")
|
||||
|
||||
get("/interact", PageController, :interact)
|
||||
end
|
||||
|
||||
74
lib/web/templates/email/before_event_notification.html.eex
Normal file
74
lib/web/templates/email/before_event_notification.html.eex
Normal file
@@ -0,0 +1,74 @@
|
||||
<!-- HERO -->
|
||||
<tr>
|
||||
<td bgcolor="#424056" align="center" style="padding: 0px 10px 0px 10px;">
|
||||
<!--[if (gte mso 9)|(IE)]>
|
||||
<table align="center" border="0" cellspacing="0" cellpadding="0" width="600">
|
||||
<tr>
|
||||
<td align="center" valign="top" width="600">
|
||||
<![endif]-->
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 600px;" >
|
||||
<tr>
|
||||
<td bgcolor="#ffffff" align="center" valign="top" style="padding: 40px 20px 20px 20px; border-radius: 4px 4px 0px 0px; color: #111111; font-family: 'Lato', Helvetica, Arial, sans-serif; font-size: 48px; font-weight: 400; line-height: 48px;">
|
||||
<h1 style="font-size: 48px; font-weight: 400; margin: 0;">
|
||||
<%= gettext "Upcoming event" %>
|
||||
</h1>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<!--[if (gte mso 9)|(IE)]>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
<!-- COPY BLOCK -->
|
||||
<tr>
|
||||
<td bgcolor="#f4f4f4" align="center" style="padding: 0px 10px 0px 10px;">
|
||||
<!--[if (gte mso 9)|(IE)]>
|
||||
<table align="center" border="0" cellspacing="0" cellpadding="0" width="600">
|
||||
<tr>
|
||||
<td align="center" valign="top" width="600">
|
||||
<![endif]-->
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 600px;" >
|
||||
<!-- COPY -->
|
||||
<tr>
|
||||
<td bgcolor="#ffffff" align="left" style="padding: 20px 30px 0px 30px; color: #666666; font-family: 'Lato', Helvetica, Arial, sans-serif; font-size: 18px; font-weight: 400; line-height: 25px;" >
|
||||
<p style="margin: 0;">
|
||||
<%= gettext "Get ready for %{title}", title: @participant.event.title %>
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- BULLETPROOF BUTTON -->
|
||||
<tr>
|
||||
<td bgcolor="#ffffff" align="left">
|
||||
<table width="100%" border="0" cellspacing="0" cellpadding="0">
|
||||
<tr>
|
||||
<td bgcolor="#ffffff" align="center" style="padding: 20px 30px 60px 30px;">
|
||||
<table border="0" cellspacing="0" cellpadding="0">
|
||||
<tr>
|
||||
<td align="center" style="border-radius: 3px;" bgcolor="#424056"><a href="<%= page_url(Mobilizon.Web.Endpoint, :event, @participant.event.uuid) %>" target="_blank" style="font-size: 20px; font-family: Helvetica, Arial, sans-serif; color: #ffffff; text-decoration: none; color: #ffffff; text-decoration: none; padding: 15px 25px; border-radius: 2px; border: 1px solid #424056; display: inline-block;">
|
||||
<%= gettext "Go to event page" %>
|
||||
</a></td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td bgcolor="#ffffff" align="left" style="padding: 20px 30px 40px 30px; color: #777777; font-family: 'Lato', Helvetica, Arial, sans-serif; font-size: 14px; font-weight: 400; line-height: 20px;" >
|
||||
<p style="margin: 0">
|
||||
<%= gettext "If you need to cancel your participation, just access the event page through link above and click on the participation button." %>
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<!--[if (gte mso 9)|(IE)]>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
@@ -0,0 +1,5 @@
|
||||
<%= gettext "Upcoming event" %>
|
||||
==
|
||||
<%= gettext "Get ready for %{title}", title: @participant.event.title %>
|
||||
<%= gettext "View the event on: %{link}", link: page_url(Mobilizon.Web.Endpoint, :event, @participant.event.uuid) %>
|
||||
<%= gettext "If you need to cancel your participation, just access the event page through link above and click on the participation button." %>
|
||||
@@ -121,4 +121,4 @@
|
||||
</table>
|
||||
<![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tr>
|
||||
|
||||
@@ -4,6 +4,17 @@
|
||||
|
||||
<%= gettext "The event %{title} was just updated", title: @old_event.title %>
|
||||
|
||||
<%= if MapSet.member?(@changes, :status) do %>
|
||||
<%= case @event.status do %>
|
||||
<% :confirmed -> %>
|
||||
<%= gettext "Event has been confirmed" %>
|
||||
<% :tentative -> %>
|
||||
<%= gettext "Event status has been set as tentative" %>
|
||||
<% :cancelled -> %>
|
||||
<%= gettext "Event has been cancelled" %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
<%= if MapSet.member?(@changes, :title) do %>
|
||||
<%= gettext "New title: %{title}", title: @event.title %>
|
||||
<% end %>
|
||||
|
||||
76
lib/web/templates/email/group_invite.html.eex
Normal file
76
lib/web/templates/email/group_invite.html.eex
Normal file
@@ -0,0 +1,76 @@
|
||||
<!-- HERO -->
|
||||
<tr>
|
||||
<td bgcolor="#424056" align="center" style="padding: 0px 10px 0px 10px;">
|
||||
<!--[if (gte mso 9)|(IE)]>
|
||||
<table align="center" border="0" cellspacing="0" cellpadding="0" width="600">
|
||||
<tr>
|
||||
<td align="center" valign="top" width="600">
|
||||
<![endif]-->
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 600px;" >
|
||||
<tr>
|
||||
<td bgcolor="#ffffff" align="center" valign="top" style="padding: 40px 20px 20px 20px; border-radius: 4px 4px 0px 0px; color: #111111; font-family: 'Lato', Helvetica, Arial, sans-serif; font-size: 48px; font-weight: 400; line-height: 48px;">
|
||||
<h1 style="font-size: 48px; font-weight: 400; margin: 0;">
|
||||
<%= gettext "Come along!" %>
|
||||
</h1>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<!--[if (gte mso 9)|(IE)]>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
<!-- COPY BLOCK -->
|
||||
<tr>
|
||||
<td bgcolor="#f4f4f4" align="center" style="padding: 0px 10px 0px 10px;">
|
||||
<!--[if (gte mso 9)|(IE)]>
|
||||
<table align="center" border="0" cellspacing="0" cellpadding="0" width="600">
|
||||
<tr>
|
||||
<td align="center" valign="top" width="600">
|
||||
<![endif]-->
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 600px;" >
|
||||
<!-- COPY -->
|
||||
<tr>
|
||||
<td bgcolor="#ffffff" align="left" style="padding: 20px 30px 0px 30px; color: #666666; font-family: 'Lato', Helvetica, Arial, sans-serif; font-size: 18px; font-weight: 400; line-height: 25px;" >
|
||||
<p style="margin: 0;">
|
||||
<%= gettext "%{inviter} just invited you to join their group %{group}", group: @group.name, inviter: @inviter.name %>
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td bgcolor="#ffffff" align="left" style="padding: 20px 30px 40px 30px; color: #777777; font-family: 'Lato', Helvetica, Arial, sans-serif; font-size: 18px; font-weight: 400; line-height: 25px;" >
|
||||
<p style="margin: 0">
|
||||
<%= gettext "To accept this invitation, head over to your groups." %>
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- BULLETPROOF BUTTON -->
|
||||
<tr>
|
||||
<td bgcolor="#ffffff" align="left">
|
||||
<table width="100%" border="0" cellspacing="0" cellpadding="0">
|
||||
<tr>
|
||||
<td bgcolor="#ffffff" align="center" style="padding: 20px 30px 60px 30px;">
|
||||
<table border="0" cellspacing="0" cellpadding="0">
|
||||
<tr>
|
||||
<td align="center" style="border-radius: 3px;" bgcolor="#424056">
|
||||
<a href="<%= my_groups_url(Mobilizon.Web.Endpoint, :index) %>" target="_blank" style="font-size: 20px; font-family: Helvetica, Arial, sans-serif; color: #ffffff; text-decoration: none; color: #ffffff; text-decoration: none; padding: 15px 25px; border-radius: 2px; border: 1px solid #424056; display: inline-block;">
|
||||
<%= gettext "See my groups" %>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<!--[if (gte mso 9)|(IE)]>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
9
lib/web/templates/email/group_invite.text.eex
Normal file
9
lib/web/templates/email/group_invite.text.eex
Normal file
@@ -0,0 +1,9 @@
|
||||
<%= gettext "Come along!" %>
|
||||
|
||||
==
|
||||
|
||||
<%= gettext "%{inviter} just invited you to join their group %{group}", group: @group.name, inviter: @inviter.name %>
|
||||
|
||||
<%= gettext "To accept this invitation, head over to your groups." %>
|
||||
|
||||
<%= my_groups_url(Mobilizon.Web.Endpoint, :index) %>
|
||||
81
lib/web/templates/email/on_day_notification.html.eex
Normal file
81
lib/web/templates/email/on_day_notification.html.eex
Normal file
@@ -0,0 +1,81 @@
|
||||
<!-- HERO -->
|
||||
<tr>
|
||||
<td bgcolor="#424056" align="center" style="padding: 0px 10px 0px 10px;">
|
||||
<!--[if (gte mso 9)|(IE)]>
|
||||
<table align="center" border="0" cellspacing="0" cellpadding="0" width="600">
|
||||
<tr>
|
||||
<td align="center" valign="top" width="600">
|
||||
<![endif]-->
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 600px;" >
|
||||
<tr>
|
||||
<td bgcolor="#ffffff" align="center" valign="top" style="padding: 40px 20px 20px 20px; border-radius: 4px 4px 0px 0px; color: #111111; font-family: 'Lato', Helvetica, Arial, sans-serif; font-size: 48px; font-weight: 400; line-height: 48px;">
|
||||
<h1 style="font-size: 48px; font-weight: 400; margin: 0;">
|
||||
<%= gettext "Events today" %>
|
||||
</h1>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<!--[if (gte mso 9)|(IE)]>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
<!-- COPY BLOCK -->
|
||||
<tr>
|
||||
<td bgcolor="#f4f4f4" align="center" style="padding: 0px 10px 0px 10px;">
|
||||
<!--[if (gte mso 9)|(IE)]>
|
||||
<table align="center" border="0" cellspacing="0" cellpadding="0" width="600">
|
||||
<tr>
|
||||
<td align="center" valign="top" width="600">
|
||||
<![endif]-->
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 600px;" >
|
||||
<!-- COPY -->
|
||||
<tr>
|
||||
<td bgcolor="#ffffff" align="left" style="padding: 20px 30px 0px 30px; color: #666666; font-family: 'Lato', Helvetica, Arial, sans-serif; font-size: 18px; font-weight: 400; line-height: 25px;" >
|
||||
<p style="margin: 0;">
|
||||
<%= ngettext "You have one event today:", "You have %{total} events today:", @total, total: @total %>
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td bgcolor="#ffffff" align="left" style="padding: 20px 30px 0px 30px; color: #666666; font-family: 'Lato', Helvetica, Arial, sans-serif; font-size: 18px; font-weight: 400; line-height: 25px;" >
|
||||
<%= if @total > 1 do %>
|
||||
<ul style="margin: 0;">
|
||||
<%= for participation <- @participations do %>
|
||||
<li>
|
||||
<strong>
|
||||
<%= participation.event.begins_on |> DateTime.shift_zone!(@timezone) |> datetime_to_time_string(@locale) %>
|
||||
</strong>
|
||||
<a href="<%= page_url(Mobilizon.Web.Endpoint, :event, participation.event.uuid) %>" target="_blank">
|
||||
<%= participation.event.title %>
|
||||
</a>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
<% else %>
|
||||
<strong>
|
||||
<%= @participation.event.begins_on |> DateTime.shift_zone!(@timezone) |> datetime_to_time_string(@locale) %>
|
||||
</strong>
|
||||
<a href="<%= page_url(Mobilizon.Web.Endpoint, :event, @participation.event.uuid) %>" target="_blank">
|
||||
<%= @participation.event.title %>
|
||||
</a>
|
||||
<% end %>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td bgcolor="#ffffff" align="left" style="padding: 20px 30px 40px 30px; color: #777777; font-family: 'Lato', Helvetica, Arial, sans-serif; font-size: 14px; font-weight: 400; line-height: 20px;" >
|
||||
<p style="margin: 0">
|
||||
<%= ngettext "If you need to cancel your participation, just access the event page through the link above and click on the participation button.", "If you need to cancel your participation, just access the event page through the links above and click on the participation button.", @total %>
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<!--[if (gte mso 9)|(IE)]>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
14
lib/web/templates/email/on_day_notification.text.eex
Normal file
14
lib/web/templates/email/on_day_notification.text.eex
Normal file
@@ -0,0 +1,14 @@
|
||||
<%= gettext "Events today" %>
|
||||
==
|
||||
|
||||
<%= ngettext "You have one event today:", "You have %{total} events today:", @total, total: @total %>
|
||||
|
||||
<%= if @total > 1 do %>
|
||||
<%= for participation <- @participations do %>
|
||||
- <%= participation.event.begins_on |> DateTime.shift_zone!(@timezone) |> datetime_to_time_string(@locale) %> - <%= participation.event.title %> <%= page_url(Mobilizon.Web.Endpoint, :event, participation.event.uuid) %>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<%= DateTime.shift_zone!(@participation.event.begins_on, @timezone) |> datetime_to_time_string(@locale) %> - <%= @participation.event.title %> <%= page_url(Mobilizon.Web.Endpoint, :event, @participation.event.uuid) %>
|
||||
<% end %>
|
||||
|
||||
<%= ngettext "If you need to cancel your participation, just access the event page through the link above and click on the participation button.", "If you need to cancel your participation, just access the event page through the links above and click on the participation button.", @total %>
|
||||
@@ -2,7 +2,9 @@ defmodule Mobilizon.Web.ActivityPub.ActorView do
|
||||
use Mobilizon.Web, :view
|
||||
|
||||
alias Mobilizon.Actors
|
||||
alias Mobilizon.Actors.Actor
|
||||
alias Mobilizon.Actors.{Actor, Member}
|
||||
alias Mobilizon.Resources
|
||||
alias Mobilizon.Resources.Resource
|
||||
|
||||
alias Mobilizon.Federation.ActivityPub
|
||||
alias Mobilizon.Federation.ActivityPub.{Activity, Utils}
|
||||
@@ -60,7 +62,7 @@ defmodule Mobilizon.Web.ActivityPub.ActorView do
|
||||
else: @private_visibility_empty_collection
|
||||
|
||||
%{
|
||||
"id" => Actor.build_url(actor.preferred_username, :followers),
|
||||
"id" => actor.followers_url,
|
||||
"type" => "OrderedCollection",
|
||||
"totalItems" => total,
|
||||
"first" => collection(followers, actor.preferred_username, :followers, 1, total)
|
||||
@@ -68,6 +70,64 @@ defmodule Mobilizon.Web.ActivityPub.ActorView do
|
||||
|> Map.merge(Utils.make_json_ld_header())
|
||||
end
|
||||
|
||||
def render("members.json", %{group: group, page: page, actor_applicant: actor_applicant}) do
|
||||
%{total: total, elements: members} =
|
||||
if Actor.is_public_visibility(group) ||
|
||||
actor_applicant_group_member?(group, actor_applicant),
|
||||
do: Actors.list_members_for_group(group, page),
|
||||
else: @private_visibility_empty_collection
|
||||
|
||||
members
|
||||
|> collection(group.preferred_username, :members, page, total)
|
||||
|> Map.merge(Utils.make_json_ld_header())
|
||||
end
|
||||
|
||||
def render("members.json", %{group: group, actor_applicant: actor_applicant}) do
|
||||
%{total: total, elements: members} =
|
||||
if Actor.is_public_visibility(group) ||
|
||||
actor_applicant_group_member?(group, actor_applicant),
|
||||
do: Actors.list_members_for_group(group),
|
||||
else: @private_visibility_empty_collection
|
||||
|
||||
%{
|
||||
"id" => group.url,
|
||||
"attributedTo" => group.url,
|
||||
"type" => "OrderedCollection",
|
||||
"totalItems" => total,
|
||||
"first" => collection(members, group.preferred_username, :members, 1, total)
|
||||
}
|
||||
|> Map.merge(Utils.make_json_ld_header())
|
||||
end
|
||||
|
||||
def render("resources.json", %{group: group, page: page, actor_applicant: actor_applicant}) do
|
||||
%{total: total, elements: resources} =
|
||||
if Actor.is_public_visibility(group) ||
|
||||
actor_applicant_group_member?(group, actor_applicant),
|
||||
do: Resources.get_top_level_resources_for_group(group),
|
||||
else: @private_visibility_empty_collection
|
||||
|
||||
resources
|
||||
|> collection(group.preferred_username, :resources, page, total)
|
||||
|> Map.merge(Utils.make_json_ld_header())
|
||||
end
|
||||
|
||||
def render("resources.json", %{group: group, actor_applicant: actor_applicant}) do
|
||||
%{total: total, elements: resources} =
|
||||
if Actor.is_public_visibility(group) ||
|
||||
actor_applicant_group_member?(group, actor_applicant),
|
||||
do: Resources.get_top_level_resources_for_group(group),
|
||||
else: @private_visibility_empty_collection
|
||||
|
||||
%{
|
||||
"id" => group.resources_url,
|
||||
"attributedTo" => group.url,
|
||||
"type" => "OrderedCollection",
|
||||
"totalItems" => total,
|
||||
"first" => collection(resources, group.preferred_username, :resources, 1, total)
|
||||
}
|
||||
|> Map.merge(Utils.make_json_ld_header())
|
||||
end
|
||||
|
||||
def render("outbox.json", %{actor: actor, page: page}) do
|
||||
%{total: total, elements: followers} =
|
||||
if Actor.is_public_visibility(actor),
|
||||
@@ -96,11 +156,12 @@ defmodule Mobilizon.Web.ActivityPub.ActorView do
|
||||
|
||||
@spec collection(list(), String.t(), atom(), integer(), integer()) :: map()
|
||||
defp collection(collection, preferred_username, endpoint, page, total)
|
||||
when endpoint in [:followers, :following, :outbox] do
|
||||
when endpoint in [:followers, :following, :outbox, :members, :resources, :todos] do
|
||||
offset = (page - 1) * 10
|
||||
|
||||
map = %{
|
||||
"id" => Actor.build_url(preferred_username, endpoint, page: page),
|
||||
"attributedTo" => Actor.build_url(preferred_username, :page),
|
||||
"type" => "OrderedCollectionPage",
|
||||
"partOf" => Actor.build_url(preferred_username, endpoint),
|
||||
"orderedItems" => Enum.map(collection, &item/1)
|
||||
@@ -115,4 +176,17 @@ defmodule Mobilizon.Web.ActivityPub.ActorView do
|
||||
|
||||
def item(%Activity{data: %{"id" => id}}), do: id
|
||||
def item(%Actor{url: url}), do: url
|
||||
def item(%Member{} = member), do: Convertible.model_to_as(member)
|
||||
def item(%Resource{} = resource), do: Convertible.model_to_as(resource)
|
||||
|
||||
defp actor_applicant_group_member?(%Actor{}, nil), do: false
|
||||
|
||||
defp actor_applicant_group_member?(%Actor{id: group_id}, %Actor{id: actor_applicant_id}),
|
||||
do:
|
||||
Actors.get_member(actor_applicant_id, group_id, [
|
||||
:member,
|
||||
:moderator,
|
||||
:administrator,
|
||||
:creator
|
||||
]) != {:error, :member_not_found}
|
||||
end
|
||||
|
||||
@@ -3,9 +3,16 @@ defmodule Mobilizon.Web.EmailView do
|
||||
|
||||
import Mobilizon.Web.Gettext
|
||||
|
||||
def datetime_to_string(%DateTime{} = datetime, locale \\ "en") do
|
||||
def datetime_to_string(%DateTime{} = datetime, locale \\ "en", format \\ :medium) do
|
||||
with {:ok, string} <-
|
||||
Cldr.DateTime.to_string(datetime, Mobilizon.Cldr, format: :medium, locale: locale) do
|
||||
Mobilizon.Cldr.DateTime.to_string(datetime, format: format, locale: locale) do
|
||||
string
|
||||
end
|
||||
end
|
||||
|
||||
def datetime_to_time_string(%DateTime{} = datetime, locale \\ "en", format \\ :hm) do
|
||||
with {:ok, string} <-
|
||||
Mobilizon.Cldr.DateTime.to_string(datetime, format: format, locale: locale) do
|
||||
string
|
||||
end
|
||||
end
|
||||
|
||||
@@ -6,7 +6,9 @@ defmodule Mobilizon.Web.PageView do
|
||||
use Mobilizon.Web, :view
|
||||
|
||||
alias Mobilizon.Actors.Actor
|
||||
alias Mobilizon.Events.{Comment, Event}
|
||||
alias Mobilizon.Conversations.Comment
|
||||
alias Mobilizon.Events.Event
|
||||
alias Mobilizon.Resources.Resource
|
||||
alias Mobilizon.Tombstone
|
||||
|
||||
alias Mobilizon.Service.Metadata
|
||||
@@ -40,35 +42,56 @@ defmodule Mobilizon.Web.PageView do
|
||||
|> Map.merge(Utils.make_json_ld_header())
|
||||
end
|
||||
|
||||
def render(page, %{object: object} = _assigns)
|
||||
def render("resource.activity-json", %{conn: %{assigns: %{object: %Resource{} = resource}}}) do
|
||||
resource
|
||||
|> Convertible.model_to_as()
|
||||
|> Map.merge(Utils.make_json_ld_header())
|
||||
end
|
||||
|
||||
def render(page, %{object: object, conn: conn} = _assigns)
|
||||
when page in ["actor.html", "event.html", "comment.html"] do
|
||||
tags = object |> Metadata.build_tags()
|
||||
inject_tags(conn, tags)
|
||||
end
|
||||
|
||||
def render("index.html", %{conn: conn}) do
|
||||
tags = Instance.build_tags()
|
||||
inject_tags(conn, tags)
|
||||
end
|
||||
|
||||
@spec inject_tags(Conn.t(), List.t()) :: {:safe, String.t()}
|
||||
defp inject_tags(conn, tags) do
|
||||
with {:ok, index_content} <- File.read(index_file_path()) do
|
||||
tags = object |> Metadata.build_tags() |> MetadataUtils.stringify_tags()
|
||||
locale = get_locale(conn)
|
||||
|
||||
index_content = replace_meta(index_content, tags)
|
||||
|
||||
{:safe, index_content}
|
||||
end
|
||||
end
|
||||
|
||||
def render("index.html", _assigns) do
|
||||
with {:ok, index_content} <- File.read(index_file_path()) do
|
||||
tags = Instance.build_tags() |> MetadataUtils.stringify_tags()
|
||||
|
||||
index_content = replace_meta(index_content, tags)
|
||||
|
||||
{:safe, index_content}
|
||||
do_replacements(index_content, MetadataUtils.stringify_tags(tags), locale)
|
||||
end
|
||||
end
|
||||
|
||||
@spec index_file_path :: String.t()
|
||||
defp index_file_path do
|
||||
Path.join(Application.app_dir(:mobilizon, "priv/static"), "index.html")
|
||||
end
|
||||
|
||||
@spec replace_meta(String.t(), String.t()) :: String.t()
|
||||
# TODO: Find why it's different in dev/prod and during tests
|
||||
defp replace_meta(index_content, tags) do
|
||||
index_content
|
||||
|> String.replace("<meta name=\"server-injected-data\" />", tags)
|
||||
|> String.replace("<meta name=server-injected-data>", tags)
|
||||
end
|
||||
|
||||
@spec do_replacements(String.t(), String.t(), String.t()) :: {:safe, String.t()}
|
||||
defp do_replacements(index_content, tags, locale) do
|
||||
index_content
|
||||
|> replace_meta(tags)
|
||||
|> String.replace("<html lang=\"en\">", "<html lang=\"#{locale}\">")
|
||||
|> String.replace("<html lang=en>", "<html lang=\"#{locale}\">")
|
||||
|> (&{:safe, &1}).()
|
||||
end
|
||||
|
||||
@spec get_locale(Conn.t()) :: String.t()
|
||||
defp get_locale(%{private: %{cldr_locale: nil}}), do: "en"
|
||||
defp get_locale(%{private: %{cldr_locale: %{requested_locale_name: locale}}}), do: locale
|
||||
defp get_locale(_), do: "en"
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user