Merge branch 'fix-push-subscriptions-registration' into 'main'

Various fixes

Closes #1343 and #1344

See merge request framasoft/mobilizon!1440
This commit is contained in:
Thomas Citharel 2023-08-24 16:36:59 +00:00
commit a8ac41bfb8
27 changed files with 393 additions and 61 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

@ -67,7 +67,7 @@ import { EventJoinOptions } from "@/types/enums";
import { IParticipant } from "../../types/participant.model"; import { IParticipant } from "../../types/participant.model";
import RouteName from "../../router/name"; import RouteName from "../../router/name";
import { CONFIRM_PARTICIPATION } from "../../graphql/event"; import { CONFIRM_PARTICIPATION } from "../../graphql/event";
import { computed, ref } from "vue"; import { computed, ref, watchEffect } from "vue";
import { useMutation } from "@vue/apollo-composable"; import { useMutation } from "@vue/apollo-composable";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import { useHead } from "@vueuse/head"; import { useHead } from "@vueuse/head";
@ -90,9 +90,15 @@ const { onDone, onError, mutate } = useMutation<{
confirmParticipation: IParticipant; confirmParticipation: IParticipant;
}>(CONFIRM_PARTICIPATION); }>(CONFIRM_PARTICIPATION);
mutate(() => ({ const participationToken = computed(() => props.token);
token: props.token,
})); watchEffect(() => {
if (participationToken.value) {
mutate({
token: participationToken.value,
});
}
});
onDone(async ({ data }) => { onDone(async ({ data }) => {
participation.value = data?.confirmParticipation; participation.value = data?.confirmParticipation;

View File

@ -70,14 +70,16 @@ const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
const { loggedUser } = useUserSettings(); const { loggedUser } = useUserSettings();
const { mutate: doUpdateLocale } = updateLocale();
onMounted(() => { onMounted(() => {
updateLocale(locale as unknown as string); doUpdateLocale({ locale: locale as unknown as string });
doUpdateSetting({ timezone }); doUpdateSetting({ timezone });
}); });
watch(locale, () => { watch(locale, () => {
if (locale.value) { if (locale.value) {
updateLocale(locale.value as string); doUpdateLocale({ locale: locale as unknown as string });
saveLocaleData(locale.value as string); saveLocaleData(locale.value as string);
} }
}); });

View File

@ -59,12 +59,8 @@ export async function doUpdateSetting(
})); }));
} }
export async function updateLocale(locale: string) { export function updateLocale() {
useMutation<{ id: string; locale: string }>(UPDATE_USER_LOCALE, () => ({ return useMutation<{ id: string; locale: string }>(UPDATE_USER_LOCALE);
variables: {
locale,
},
}));
} }
export function registerAccount() { export function registerAccount() {

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

@ -32,9 +32,8 @@ export async function subscribeUserToPush(): Promise<PushSubscription | null> {
}; };
const registration = await navigator.serviceWorker.ready; const registration = await navigator.serviceWorker.ready;
try { try {
const pushSubscription = await registration.pushManager.subscribe( const pushSubscription =
subscribeOptions await registration.pushManager.subscribe(subscribeOptions);
);
console.debug("Received PushSubscription: ", pushSubscription); console.debug("Received PushSubscription: ", pushSubscription);
resolve(pushSubscription); resolve(pushSubscription);
} catch (e) { } catch (e) {

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

@ -21,7 +21,7 @@
<div class=""> <div class="">
<o-field :label="t('Status')" horizontal label-for="role-select"> <o-field :label="t('Status')" horizontal label-for="role-select">
<o-select v-model="role" id="role-select"> <o-select v-model="role" id="role-select">
<option :value="null"> <option value="EVERYTHING">
{{ t("Everything") }} {{ t("Everything") }}
</option> </option>
<option :value="ParticipantRole.CREATOR"> <option :value="ParticipantRole.CREATOR">
@ -303,17 +303,15 @@ const participantsExportFormats = useParticipantsExportFormats();
const ellipsize = (text?: string) => const ellipsize = (text?: string) =>
text && text.substring(0, MESSAGE_ELLIPSIS_LENGTH).concat("…"); text && text.substring(0, MESSAGE_ELLIPSIS_LENGTH).concat("…");
// metaInfo() { const eventId = computed(() => props.eventId);
// return {
// title: this.t("Participants") as string, const ParticipantAllRoles = { ...ParticipantRole, EVERYTHING: "EVERYTHING" };
// };
// },
const page = useRouteQuery("page", 1, integerTransformer); const page = useRouteQuery("page", 1, integerTransformer);
const role = useRouteQuery( const role = useRouteQuery(
"role", "role",
ParticipantRole.PARTICIPANT, "EVERYTHING",
enumTransformer(ParticipantRole) enumTransformer(ParticipantAllRoles)
); );
const checkedRows = ref<IParticipant[]>([]); const checkedRows = ref<IParticipant[]>([]);
@ -325,10 +323,10 @@ const { result: participantsResult, loading: participantsLoading } = useQuery<{
}>( }>(
PARTICIPANTS, PARTICIPANTS,
() => ({ () => ({
uuid: props.eventId, uuid: eventId.value,
page: page.value, page: page.value,
limit: PARTICIPANTS_PER_PAGE, limit: PARTICIPANTS_PER_PAGE,
roles: role.value, roles: role.value === "EVERYTHING" ? undefined : role.value,
}), }),
() => ({ () => ({
enabled: enabled:

View File

@ -47,6 +47,7 @@
<o-select <o-select
:loading="loadingTimezones || loadingUserSettings" :loading="loadingTimezones || loadingUserSettings"
v-model="$i18n.locale" v-model="$i18n.locale"
@update:modelValue="updateLanguage"
:placeholder="t('Select a language')" :placeholder="t('Select a language')"
id="setting-language" id="setting-language"
> >
@ -147,7 +148,7 @@ import RouteName from "../../router/name";
import { AddressSearchType } from "@/types/enums"; import { AddressSearchType } from "@/types/enums";
import { Address, IAddress } from "@/types/address.model"; import { Address, IAddress } from "@/types/address.model";
import { useTimezones } from "@/composition/apollo/config"; import { useTimezones } from "@/composition/apollo/config";
import { useUserSettings } from "@/composition/apollo/user"; import { useUserSettings, updateLocale } from "@/composition/apollo/user";
import { useHead } from "@vueuse/head"; import { useHead } from "@vueuse/head";
import { computed, defineAsyncComponent, ref, watch } from "vue"; import { computed, defineAsyncComponent, ref, watch } from "vue";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
@ -172,6 +173,12 @@ useHead({
const theme = ref(localStorage.getItem("theme")); const theme = ref(localStorage.getItem("theme"));
const systemTheme = ref(!("theme" in localStorage)); const systemTheme = ref(!("theme" in localStorage));
const { mutate: doUpdateLocale } = updateLocale();
const updateLanguage = (newLocale: string) => {
doUpdateLocale({ locale: newLocale });
};
watch(systemTheme, (newSystemTheme) => { watch(systemTheme, (newSystemTheme) => {
console.debug("changing system theme", newSystemTheme); console.debug("changing system theme", newSystemTheme);
if (newSystemTheme) { if (newSystemTheme) {

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

@ -6,6 +6,7 @@ defmodule Mobilizon.GraphQL.Resolvers.PushSubscription do
alias Mobilizon.Storage.Page alias Mobilizon.Storage.Page
alias Mobilizon.Users alias Mobilizon.Users
alias Mobilizon.Users.{PushSubscription, User} alias Mobilizon.Users.{PushSubscription, User}
import Mobilizon.Web.Gettext
@doc """ @doc """
List all of an user's registered push subscriptions List all of an user's registered push subscriptions
@ -33,6 +34,19 @@ defmodule Mobilizon.GraphQL.Resolvers.PushSubscription do
{:ok, %PushSubscription{}} -> {:ok, %PushSubscription{}} ->
{:ok, "OK"} {:ok, "OK"}
{:error,
%Ecto.Changeset{
errors: [
digest:
{"has already been taken",
[
constraint: :unique,
constraint_name: "user_push_subscriptions_user_id_digest_index"
]}
]
}} ->
{:error, dgettext("errors", "The same push subscription has already been registered")}
{:error, err} -> {:error, err} ->
require Logger require Logger
Logger.error(inspect(err)) Logger.error(inspect(err))

View File

@ -4,6 +4,7 @@ defmodule Mobilizon.GraphQL.Schema.Users.PushSubscription do
""" """
use Absinthe.Schema.Notation use Absinthe.Schema.Notation
alias Mobilizon.GraphQL.Resolvers.PushSubscription alias Mobilizon.GraphQL.Resolvers.PushSubscription
alias Mobilizon.Users.User
# object :push_subscription do # object :push_subscription do
# field(:id, :id) # field(:id, :id)
@ -29,8 +30,9 @@ defmodule Mobilizon.GraphQL.Schema.Users.PushSubscription do
middleware(Rajska.QueryAuthorization, middleware(Rajska.QueryAuthorization,
permit: :user, permit: :user,
scope: false, scope: User,
rule: :"write:user:setting:push" rule: :"write:user:setting:push",
args: %{}
) )
resolve(&PushSubscription.register_push_subscription/3) resolve(&PushSubscription.register_push_subscription/3)
@ -41,8 +43,9 @@ defmodule Mobilizon.GraphQL.Schema.Users.PushSubscription do
middleware(Rajska.QueryAuthorization, middleware(Rajska.QueryAuthorization,
permit: :user, permit: :user,
scope: false, scope: User,
rule: :"write:user:setting:push" rule: :"write:user:setting:push",
args: %{}
) )
resolve(&PushSubscription.unregister_push_subscription/3) resolve(&PushSubscription.unregister_push_subscription/3)

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

@ -3,6 +3,8 @@ defmodule Mobilizon.Service.DateTime do
Module to represent a datetime in a given locale Module to represent a datetime in a given locale
""" """
alias Cldr.DateTime.Relative alias Cldr.DateTime.Relative
alias Mobilizon.Cldr, as: MobilizonCldr
import Mobilizon.Cldr, only: [locale_or_default: 1]
@typep to_string_format :: :short | :medium | :long | :full @typep to_string_format :: :short | :medium | :long | :full
@ -10,25 +12,25 @@ defmodule Mobilizon.Service.DateTime do
@spec datetime_to_string(DateTime.t(), String.t(), to_string_format()) :: String.t() @spec datetime_to_string(DateTime.t(), String.t(), to_string_format()) :: String.t()
def datetime_to_string(%DateTime{} = datetime, locale \\ "en", format \\ :medium) do def datetime_to_string(%DateTime{} = datetime, locale \\ "en", format \\ :medium) do
Mobilizon.Cldr.DateTime.to_string!(datetime, MobilizonCldr.DateTime.to_string!(datetime,
format: format, format: format,
locale: Mobilizon.Cldr.locale_or_default(locale) locale: locale_or_default(locale)
) )
end end
@spec datetime_to_time_string(DateTime.t(), String.t(), to_string_format()) :: String.t() @spec datetime_to_time_string(DateTime.t(), String.t(), to_string_format()) :: String.t()
def datetime_to_time_string(%DateTime{} = datetime, locale \\ "en", format \\ :short) do def datetime_to_time_string(%DateTime{} = datetime, locale \\ "en", format \\ :short) do
Mobilizon.Cldr.Time.to_string!(datetime, MobilizonCldr.Time.to_string!(datetime,
format: format, format: format,
locale: Mobilizon.Cldr.locale_or_default(locale) locale: locale_or_default(locale)
) )
end end
@spec datetime_to_date_string(DateTime.t(), String.t(), to_string_format()) :: String.t() @spec datetime_to_date_string(DateTime.t(), String.t(), to_string_format()) :: String.t()
def datetime_to_date_string(%DateTime{} = datetime, locale \\ "en", format \\ :short) do def datetime_to_date_string(%DateTime{} = datetime, locale \\ "en", format \\ :short) do
Mobilizon.Cldr.Date.to_string!(datetime, MobilizonCldr.Date.to_string!(datetime,
format: format, format: format,
locale: Mobilizon.Cldr.locale_or_default(locale) locale: locale_or_default(locale)
) )
end end
@ -47,9 +49,9 @@ defmodule Mobilizon.Service.DateTime do
@spec datetime_relative(DateTime.t(), String.t()) :: String.t() @spec datetime_relative(DateTime.t(), String.t()) :: String.t()
def datetime_relative(%DateTime{} = datetime, locale \\ "en") do def datetime_relative(%DateTime{} = datetime, locale \\ "en") do
Relative.to_string!(datetime, Mobilizon.Cldr, Relative.to_string!(datetime, MobilizonCldr,
relative_to: DateTime.utc_now(), relative_to: DateTime.utc_now(),
locale: Mobilizon.Cldr.locale_or_default(locale) locale: locale_or_default(locale)
) )
end end

View File

@ -9,6 +9,7 @@ defmodule Mobilizon.Service.Export.Participants.Common do
alias Mobilizon.Events.Participant.Metadata alias Mobilizon.Events.Participant.Metadata
alias Mobilizon.Storage.Repo alias Mobilizon.Storage.Repo
import Mobilizon.Web.Gettext, only: [gettext: 1] import Mobilizon.Web.Gettext, only: [gettext: 1]
import Mobilizon.Service.DateTime, only: [datetime_to_string: 2]
@spec save_upload(String.t(), String.t(), String.t(), String.t(), String.t()) :: @spec save_upload(String.t(), String.t(), String.t(), String.t(), String.t()) ::
{:ok, Export.t()} | {:error, atom() | Ecto.Changeset.t()} {:ok, Export.t()} | {:error, atom() | Ecto.Changeset.t()}
@ -58,7 +59,12 @@ defmodule Mobilizon.Service.Export.Participants.Common do
@spec columns :: list(String.t()) @spec columns :: list(String.t())
def columns do def columns do
[gettext("Participant name"), gettext("Participant status"), gettext("Participant message")] [
gettext("Participant name"),
gettext("Participant status"),
gettext("Participant registration date"),
gettext("Participant message")
]
end end
# One hour # One hour
@ -82,14 +88,26 @@ defmodule Mobilizon.Service.Export.Participants.Common do
@spec to_list({Participant.t(), Actor.t()}) :: list(String.t()) @spec to_list({Participant.t(), Actor.t()}) :: list(String.t())
def to_list( def to_list(
{%Participant{role: role, metadata: metadata}, {%Participant{role: role, metadata: metadata, inserted_at: inserted_at},
%Actor{domain: nil, preferred_username: "anonymous"}} %Actor{domain: nil, preferred_username: "anonymous"}}
) do ) do
[gettext("Anonymous participant"), translate_role(role), convert_metadata(metadata)] [
gettext("Anonymous participant"),
translate_role(role),
datetime_to_string(inserted_at, Gettext.get_locale()),
convert_metadata(metadata)
]
end end
def to_list({%Participant{role: role, metadata: metadata}, %Actor{} = actor}) do def to_list(
[Actor.display_name_and_username(actor), translate_role(role), convert_metadata(metadata)] {%Participant{role: role, metadata: metadata, inserted_at: inserted_at}, %Actor{} = actor}
) do
[
Actor.display_name_and_username(actor),
translate_role(role),
datetime_to_string(inserted_at, Gettext.get_locale()),
convert_metadata(metadata)
]
end end
@spec convert_metadata(Metadata.t() | nil) :: String.t() @spec convert_metadata(Metadata.t() | nil) :: String.t()

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

View File

@ -0,0 +1,149 @@
defmodule Mobilizon.GraphQL.Resolvers.PushSubscriptionTest do
use Mobilizon.Web.ConnCase
import Mobilizon.Factory
alias Mobilizon.GraphQL.AbsintheHelpers
describe "create a new push subscription" do
@register_push_mutation """
mutation RegisterPush($endpoint: String!, $auth: String!, $p256dh: String!) {
registerPush(endpoint: $endpoint, auth: $auth, p256dh: $p256dh)
}
"""
test "without auth", %{conn: conn} do
res =
AbsintheHelpers.graphql_query(conn,
query: @register_push_mutation,
variables: %{endpoint: "https://yolo.com/gfjgfd", auth: "gjrigf", p256dh: "gbgof"}
)
assert hd(res["errors"])["status_code"] == 401
assert hd(res["errors"])["message"] == "You need to be logged in"
end
test "succeeds", %{conn: conn} do
user = insert(:user)
res =
conn
|> auth_conn(user)
|> AbsintheHelpers.graphql_query(
query: @register_push_mutation,
variables: %{endpoint: "https://yolo.com/gfjgfd", auth: "gjrigf", p256dh: "gbgof"}
)
assert res["errors"] == nil
assert res["data"]["registerPush"] == "OK"
end
test "fails on duplicate", %{conn: conn} do
user = insert(:user)
res =
conn
|> auth_conn(user)
|> AbsintheHelpers.graphql_query(
query: @register_push_mutation,
variables: %{
endpoint: "https://yolo.com/duplicate",
auth: "duplicate",
p256dh: "duplicate"
}
)
assert res["errors"] == nil
assert res["data"]["registerPush"] == "OK"
res =
conn
|> auth_conn(user)
|> AbsintheHelpers.graphql_query(
query: @register_push_mutation,
variables: %{
endpoint: "https://yolo.com/duplicate",
auth: "duplicate",
p256dh: "duplicate"
}
)
assert hd(res["errors"])["message"] ==
"The same push subscription has already been registered"
refute res["data"]["registerPush"] == "OK"
end
end
describe "unregister a push subscription" do
@unregister_push_mutation """
mutation UnRegisterPush($endpoint: String!) {
unregisterPush(endpoint: $endpoint)
}
"""
test "without auth", %{conn: conn} do
res =
AbsintheHelpers.graphql_query(conn,
query: @unregister_push_mutation,
variables: %{endpoint: "https://yolo.com/gfjgfd"}
)
assert hd(res["errors"])["status_code"] == 401
assert hd(res["errors"])["message"] == "You need to be logged in"
end
test "fails when not existing", %{conn: conn} do
user = insert(:user)
res =
conn
|> auth_conn(user)
|> AbsintheHelpers.graphql_query(
query: @unregister_push_mutation,
variables: %{
endpoint: "https://yolo.com/duplicate",
auth: "duplicate",
p256dh: "duplicate"
}
)
assert hd(res["errors"])["status_code"] == 404
assert hd(res["errors"])["message"] == "Resource not found"
refute res["data"]["registerPush"] == "OK"
end
test "fails when wrong user", %{conn: conn} do
user = insert(:user)
push_subscription = insert(:push_subscription)
res =
conn
|> auth_conn(user)
|> AbsintheHelpers.graphql_query(
query: @unregister_push_mutation,
variables: %{endpoint: push_subscription.endpoint}
)
assert hd(res["errors"])["status_code"] == 403
assert hd(res["errors"])["message"] == "You don't have permission to do this"
refute res["data"]["registerPush"] == "OK"
end
test "succeeds", %{conn: conn} do
user = insert(:user)
push_subscription = insert(:push_subscription, user: user)
res =
conn
|> auth_conn(user)
|> AbsintheHelpers.graphql_query(
query: @unregister_push_mutation,
variables: %{endpoint: push_subscription.endpoint}
)
assert res["errors"] == nil
assert res["data"]["unregisterPush"] == "OK"
end
end
end

View File

@ -5,24 +5,28 @@ defmodule Mobilizon.Service.Export.Participants.CommonTest do
alias Mobilizon.Actors.Actor alias Mobilizon.Actors.Actor
alias Mobilizon.Service.Export.Participants.Common alias Mobilizon.Service.Export.Participants.Common
import Mobilizon.Service.DateTime, only: [datetime_to_string: 1]
test "convert participants to list items" do test "convert participants to list items" do
participant = insert(:participant) participant = insert(:participant)
actor = insert(:actor) actor = insert(:actor)
name = Actor.display_name_and_username(actor) name = Actor.display_name_and_username(actor)
assert [^name, _, ""] = Common.to_list({participant, actor}) date = datetime_to_string(participant.inserted_at)
assert [^name, _, ^date, ""] = Common.to_list({participant, actor})
end end
test "convert participants with metadata to list items" do test "convert participants with metadata to list items" do
participant = insert(:participant, metadata: %{message: "a message"}) participant = insert(:participant, metadata: %{message: "a message"})
actor = insert(:actor) actor = insert(:actor)
name = Actor.display_name_and_username(actor) name = Actor.display_name_and_username(actor)
assert [^name, _, "a message"] = Common.to_list({participant, actor}) date = datetime_to_string(participant.inserted_at)
assert [^name, _, ^date, "a message"] = Common.to_list({participant, actor})
end end
test "convert anonymous participants to list items" do test "convert anonymous participants to list items" do
participant = insert(:participant) participant = insert(:participant)
actor = insert(:actor, domain: nil, preferred_username: "anonymous") actor = insert(:actor, domain: nil, preferred_username: "anonymous")
assert ["Anonymous participant", _, ""] = Common.to_list({participant, actor}) date = datetime_to_string(participant.inserted_at)
assert ["Anonymous participant", _, ^date, ""] = Common.to_list({participant, actor})
end end
end end