feat(notifications): add missing notifications when an user registers to an event

Closes #1344

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
Thomas Citharel 2023-08-24 11:42:03 +02:00
parent f99267c611
commit da532c7059
No known key found for this signature in database
GPG key ID: A061B9DDE0CA0773
15 changed files with 149 additions and 15 deletions

View file

@ -42,6 +42,7 @@ import { usernameWithDomain } from "@/types/actor";
import { formatTimeString } from "@/filters/datetime"; import { formatTimeString } from "@/filters/datetime";
import { import {
ActivityEventCommentSubject, ActivityEventCommentSubject,
ActivityEventParticipantSubject,
ActivityEventSubject, ActivityEventSubject,
} from "@/types/enums"; } from "@/types/enums";
import { computed } from "vue"; import { computed } from "vue";
@ -90,6 +91,14 @@ const translation = computed((): string | undefined => {
return "You posted a comment on the event {event}."; return "You posted a comment on the event {event}.";
} }
return "{profile} posted a comment on the event {event}."; return "{profile} posted a comment on the event {event}.";
case ActivityEventParticipantSubject.EVENT_NEW_PARTICIPATION:
if (isAuthorCurrentActor.value) {
return "You joined the event {event}.";
}
if (props.activity.author.preferredUsername === "anonymous") {
return "An anonymous profile joined the event {event}.";
}
return "{profile} joined the the event {event}.";
default: default:
return undefined; return undefined;
} }

View file

@ -1579,5 +1579,8 @@
"Access drafts events": "Access drafts events", "Access drafts events": "Access drafts events",
"This application will be allowed to list and view your draft events": "This application will be allowed to list and view your draft events", "This application will be allowed to list and view your draft events": "This application will be allowed to list and view your draft events",
"Access group suggested events": "Access group suggested events", "Access group suggested events": "Access group suggested events",
"This application will be allowed to list your suggested group events": "This application will be allowed to list your suggested group events" "This application will be allowed to list your suggested group events": "This application will be allowed to list your suggested group events",
"{profile} joined the the event {event}.": "{profile} joined the the event {event}.",
"You joined the event {event}.": "You joined the event {event}.",
"An anonymous profile joined the event {event}.": "An anonymous profile joined the event {event}."
} }

View file

@ -1575,5 +1575,8 @@
"Access drafts events": "Accéder aux événements brouillons", "Access drafts events": "Accéder aux événements brouillons",
"This application will be allowed to list and view your draft events": "Cetta application sera autorisée à lister et accéder à vos événements brouillons", "This application will be allowed to list and view your draft events": "Cetta application sera autorisée à lister et accéder à vos événements brouillons",
"Access group suggested events": "Accéder aux événements des groupes suggérés", "Access group suggested events": "Accéder aux événements des groupes suggérés",
"This application will be allowed to list your suggested group events": "Cetta application sera autorisée à lister les événements de vos groupes qui vous sont suggérés" "This application will be allowed to list your suggested group events": "Cetta application sera autorisée à lister les événements de vos groupes qui vous sont suggérés",
"{profile} joined the the event {event}.": "{profile} a rejoint l'événement {event}.",
"You joined the event {event}.": "Vous avez rejoint l'événement {event}.",
"An anonymous profile joined the event {event}.": "Un profil anonyme a rejoint l'événement {event}."
} }

View file

@ -3,6 +3,7 @@ import { IMember } from "./actor/member.model";
import { import {
ActivityDiscussionSubject, ActivityDiscussionSubject,
ActivityEventCommentSubject, ActivityEventCommentSubject,
ActivityEventParticipantSubject,
ActivityEventSubject, ActivityEventSubject,
ActivityGroupSubject, ActivityGroupSubject,
ActivityMemberSubject, ActivityMemberSubject,
@ -21,7 +22,8 @@ export type ActivitySubject =
| ActivityResourceSubject | ActivityResourceSubject
| ActivityDiscussionSubject | ActivityDiscussionSubject
| ActivityGroupSubject | ActivityGroupSubject
| ActivityEventCommentSubject; | ActivityEventCommentSubject
| ActivityEventParticipantSubject;
export interface IActivity { export interface IActivity {
id: string; id: string;

View file

@ -200,6 +200,10 @@ export enum ActivityEventCommentSubject {
COMMENT_POSTED = "comment_posted", COMMENT_POSTED = "comment_posted",
} }
export enum ActivityEventParticipantSubject {
EVENT_NEW_PARTICIPATION = "event_new_participation",
}
export enum ActivityPostSubject { export enum ActivityPostSubject {
POST_CREATED = "post_created", POST_CREATED = "post_created",
POST_UPDATED = "post_updated", POST_UPDATED = "post_updated",

View file

@ -82,6 +82,11 @@ defmodule Mobilizon.Federation.ActivityPub.Actions.Accept do
) )
Scheduler.trigger_notifications_for_participant(participant) Scheduler.trigger_notifications_for_participant(participant)
Mobilizon.Service.Activity.Participant.insert_activity(participant,
subject: "event_new_participation"
)
participant_as_data = Convertible.model_to_as(participant) participant_as_data = Convertible.model_to_as(participant)
audience = Audience.get_audience(participant) audience = Audience.get_audience(participant)

View file

@ -224,6 +224,10 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Events do
cond do cond do
Mobilizon.Events.get_default_participant_role(event) == :participant && Mobilizon.Events.get_default_participant_role(event) == :participant &&
role == :participant -> role == :participant ->
Mobilizon.Service.Activity.Participant.insert_activity(participant,
subject: "event_new_participation"
)
{:accept, {:accept,
Actions.Accept.accept( Actions.Accept.accept(
:join, :join,

View file

@ -19,6 +19,7 @@ defmodule Mobilizon.Activities do
@activity_types ["event", "post", "discussion", "resource", "group", "member", "comment"] @activity_types ["event", "post", "discussion", "resource", "group", "member", "comment"]
@event_activity_subjects ["event_created", "event_updated", "event_deleted", "comment_posted"] @event_activity_subjects ["event_created", "event_updated", "event_deleted", "comment_posted"]
@participant_activity_subjects ["event_new_participation"]
@post_activity_subjects ["post_created", "post_updated", "post_deleted"] @post_activity_subjects ["post_created", "post_updated", "post_deleted"]
@discussion_activity_subjects [ @discussion_activity_subjects [
"discussion_created", "discussion_created",
@ -48,12 +49,23 @@ defmodule Mobilizon.Activities do
@settings_activity_subjects ["group_created", "group_updated"] @settings_activity_subjects ["group_created", "group_updated"]
@subjects @event_activity_subjects ++ @subjects @event_activity_subjects ++
@participant_activity_subjects ++
@post_activity_subjects ++ @post_activity_subjects ++
@discussion_activity_subjects ++ @discussion_activity_subjects ++
@resource_activity_subjects ++ @resource_activity_subjects ++
@member_activity_subjects ++ @settings_activity_subjects @member_activity_subjects ++ @settings_activity_subjects
@object_type ["event", "actor", "post", "discussion", "resource", "member", "group", "comment"] @object_type [
"event",
"participant",
"actor",
"post",
"discussion",
"resource",
"member",
"group",
"comment"
]
defenum(Type, @activity_types) defenum(Type, @activity_types)
defenum(Subject, @subjects) defenum(Subject, @subjects)

View file

@ -4,7 +4,17 @@ defmodule Mobilizon.Service.Activity do
""" """
alias Mobilizon.Activities.Activity alias Mobilizon.Activities.Activity
alias Mobilizon.Service.Activity.{Comment, Discussion, Event, Group, Member, Post, Resource}
alias Mobilizon.Service.Activity.{
Comment,
Discussion,
Event,
Group,
Member,
Participant,
Post,
Resource
}
@callback insert_activity(entity :: struct(), options :: Keyword.t()) :: @callback insert_activity(entity :: struct(), options :: Keyword.t()) ::
{:ok, Oban.Job.t()} | {:ok, any()} | {:error, Ecto.Changeset.t()} {:ok, Oban.Job.t()} | {:ok, any()} | {:error, Ecto.Changeset.t()}
@ -45,4 +55,8 @@ defmodule Mobilizon.Service.Activity do
defp do_get_object(:comment, comment_id) do defp do_get_object(:comment, comment_id) do
Comment.get_object(comment_id) Comment.get_object(comment_id)
end end
defp do_get_object(:participant, participant_id) do
Participant.get_object(participant_id)
end
end end

View file

@ -0,0 +1,48 @@
defmodule Mobilizon.Service.Activity.Participant do
@moduledoc """
Insert an event activity
"""
alias Mobilizon.{Actors, Events}
alias Mobilizon.Actors.Actor
alias Mobilizon.Events.Participant
alias Mobilizon.Service.Activity
alias Mobilizon.Service.Workers.ActivityBuilder
@behaviour Activity
@impl Activity
def insert_activity(event, options \\ [])
def insert_activity(
%Participant{event_id: event_id, actor_id: actor_id, id: participant_id} =
_participant,
options
) do
actor = Actors.get_actor(actor_id)
event = Events.get_event!(event_id)
subject = Keyword.fetch!(options, :subject)
ActivityBuilder.enqueue(:build_activity, %{
"type" => "event",
"subject" => subject,
"subject_params" => %{
actor_name: Actor.display_name(actor),
event_title: event.title,
event_uuid: event.uuid
},
"group_id" => event.attributed_to_id,
"author_id" => actor.id,
"object_type" => "participant",
"object_id" => participant_id,
"inserted_at" => DateTime.utc_now()
})
end
@impl Activity
def insert_activity(_, _), do: {:ok, nil}
@impl Activity
def get_object(participant_id) do
Events.get_participant(participant_id)
end
end

View file

@ -1,6 +1,6 @@
defmodule Mobilizon.Service.Activity.Renderer.Event do defmodule Mobilizon.Service.Activity.Renderer.Event do
@moduledoc """ @moduledoc """
Insert a comment activity Insert an event activity
""" """
alias Mobilizon.Activities.Activity alias Mobilizon.Activities.Activity
alias Mobilizon.Actors.Actor alias Mobilizon.Actors.Actor
@ -67,6 +67,16 @@ defmodule Mobilizon.Service.Activity.Renderer.Event do
url: event_url(activity) url: event_url(activity)
} }
end end
:event_new_participation ->
%{
body:
dgettext("activity", "%{profile} joined your event %{event}.", %{
profile: profile(activity),
event: title(activity)
}),
url: event_url(activity)
}
end end
end end

View file

@ -96,7 +96,7 @@ defmodule Mobilizon.Service.Notifier.Email do
defp can_send_activity?(activity, user, options) do defp can_send_activity?(activity, user, options) do
Logger.warning( Logger.warning(
"Can't check if user #{inspect(user)} can be sent an activity (#{inspect(activity)}) (#{inspect(options)})" "Can't check if user #{inspect(user.email)} can be sent an activity (#{inspect(activity)}) (#{inspect(options)})"
) )
false false

View file

@ -51,4 +51,13 @@
}) })
|> raw %> |> raw %>
<% end %> <% end %>
<% :event_new_participation -> %>
<%= dgettext("activity", "%{profile} joined your event %{event}.", %{
profile: "<b>#{escaped_display_name_and_username(@activity.author)}</b>",
event:
"<a href=\"#{Routes.page_url(Mobilizon.Web.Endpoint,
:event,
@activity.subject_params["event_uuid"]) |> URI.decode()}\">#{escape_html(@activity.subject_params["event_title"])}</a>"
})
|> raw %>
<% end %> <% end %>

View file

@ -1,31 +1,37 @@
<%= case @activity.subject do %><% :event_created -> %><%= dgettext("activity", "The event %{event} was created by %{profile}.", <%= case @activity.subject do %><% :event_created -> %><%= dgettext("activity", "The event %{event} was created by %{profile}.",
%{ %{
profile: Mobilizon.Actors.Actor.display_name_and_username(@activity.author), profile: display_name_and_username(@activity.author),
event: @activity.subject_params["event_title"] event: @activity.subject_params["event_title"]
} }
) %> ) %>
<%= Routes.page_url(Mobilizon.Web.Endpoint, :event, @activity.subject_params["event_uuid"]) |> URI.decode() %><% :event_updated -> %><%= dgettext("activity", "The event %{event} was updated by %{profile}.", <%= Routes.page_url(Mobilizon.Web.Endpoint, :event, @activity.subject_params["event_uuid"]) |> URI.decode() %><% :event_updated -> %><%= dgettext("activity", "The event %{event} was updated by %{profile}.",
%{ %{
profile: Mobilizon.Actors.Actor.display_name_and_username(@activity.author), profile: display_name_and_username(@activity.author),
event: @activity.subject_params["event_title"] event: @activity.subject_params["event_title"]
} }
) %> ) %>
<%= Routes.page_url(Mobilizon.Web.Endpoint, :event, @activity.subject_params["event_uuid"]) |> URI.decode() %><% :event_deleted -> %><%= dgettext("activity", "The event %{event} was deleted by %{profile}.", <%= Routes.page_url(Mobilizon.Web.Endpoint, :event, @activity.subject_params["event_uuid"]) |> URI.decode() %><% :event_deleted -> %><%= dgettext("activity", "The event %{event} was deleted by %{profile}.",
%{ %{
profile: Mobilizon.Actors.Actor.display_name_and_username(@activity.author), profile: display_name_and_username(@activity.author),
event: @activity.subject_params["event_title"] event: @activity.subject_params["event_title"]
} }
) %> ) %>
<% :comment_posted -> %><%= if @activity.subject_params["comment_reply_to"] do %><%= dgettext("activity", "%{profile} replied to a comment on the event %{event}.", <% :comment_posted -> %><%= if @activity.subject_params["comment_reply_to"] do %><%= dgettext("activity", "%{profile} replied to a comment on the event %{event}.",
%{ %{
profile: Mobilizon.Actors.Actor.display_name_and_username(@activity.author), profile: display_name_and_username(@activity.author),
event: @activity.subject_params["event_title"] event: @activity.subject_params["event_title"]
} }
) %> ) %>
<%= Routes.page_url(Mobilizon.Web.Endpoint, :event, @activity.subject_params["event_uuid"]) |> URI.decode() %><% else %><%= dgettext("activity", "%{profile} posted a comment on the event %{event}.", <%= Routes.page_url(Mobilizon.Web.Endpoint, :event, @activity.subject_params["event_uuid"]) |> URI.decode() %><% else %><%= dgettext("activity", "%{profile} posted a comment on the event %{event}.",
%{ %{
profile: Mobilizon.Actors.Actor.display_name_and_username(@activity.author), profile: display_name_and_username(@activity.author),
event: @activity.subject_params["event_title"] event: @activity.subject_params["event_title"]
} }
) %> ) %>
<%= Routes.page_url(Mobilizon.Web.Endpoint, :event, @activity.subject_params["event_uuid"]) |> URI.decode() %><% end %><% end %> <%= Routes.page_url(Mobilizon.Web.Endpoint, :event, @activity.subject_params["event_uuid"]) |> URI.decode() %><% end %><% :event_new_participation -> %><%= dgettext("activity", "%{profile} joined your event %{event}.",
%{
profile: display_name_and_username(@activity.author),
event: @activity.subject_params["event_title"]
}
) %>
<%= Routes.page_url(Mobilizon.Web.Endpoint, :event, @activity.subject_params["event_uuid"]) |> URI.decode() %><% end %>

View file

@ -25,7 +25,6 @@ defmodule Mobilizon.Web.EmailView do
defdelegate datetime_relative(datetime, locale \\ "en"), to: DateTimeRenderer defdelegate datetime_relative(datetime, locale \\ "en"), to: DateTimeRenderer
defdelegate render_address(address), to: Address defdelegate render_address(address), to: Address
defdelegate is_same_day?(one, two), to: DateTimeRenderer defdelegate is_same_day?(one, two), to: DateTimeRenderer
defdelegate display_name_and_username(actor), to: Actor
defdelegate display_name(actor), to: Actor defdelegate display_name(actor), to: Actor
defdelegate preferred_username_and_domain(actor), to: Actor defdelegate preferred_username_and_domain(actor), to: Actor
@ -38,7 +37,13 @@ defmodule Mobilizon.Web.EmailView do
def escaped_display_name_and_username(actor) do def escaped_display_name_and_username(actor) do
actor actor
|> Actor.display_name_and_username() |> display_name_and_username()
|> escape_html() |> escape_html()
end end
def display_name_and_username(%Actor{preferred_username: "anonymous"}) do
dgettext("activity", "An anonymous profile")
end
def display_name_and_username(actor), do: Actor.display_name_and_username(actor)
end end