diff --git a/js/src/components/Activity/EventActivityItem.vue b/js/src/components/Activity/EventActivityItem.vue index 4c9f1a359..cdb13264e 100644 --- a/js/src/components/Activity/EventActivityItem.vue +++ b/js/src/components/Activity/EventActivityItem.vue @@ -42,6 +42,7 @@ import { usernameWithDomain } from "@/types/actor"; import { formatTimeString } from "@/filters/datetime"; import { ActivityEventCommentSubject, + ActivityEventParticipantSubject, ActivityEventSubject, } from "@/types/enums"; import { computed } from "vue"; @@ -90,6 +91,14 @@ const translation = computed((): string | undefined => { return "You 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: return undefined; } diff --git a/js/src/components/Participation/ConfirmParticipation.vue b/js/src/components/Participation/ConfirmParticipation.vue index e433a581c..223da0cc1 100644 --- a/js/src/components/Participation/ConfirmParticipation.vue +++ b/js/src/components/Participation/ConfirmParticipation.vue @@ -67,7 +67,7 @@ import { EventJoinOptions } from "@/types/enums"; import { IParticipant } from "../../types/participant.model"; import RouteName from "../../router/name"; import { CONFIRM_PARTICIPATION } from "../../graphql/event"; -import { computed, ref } from "vue"; +import { computed, ref, watchEffect } from "vue"; import { useMutation } from "@vue/apollo-composable"; import { useI18n } from "vue-i18n"; import { useHead } from "@vueuse/head"; @@ -90,9 +90,15 @@ const { onDone, onError, mutate } = useMutation<{ confirmParticipation: IParticipant; }>(CONFIRM_PARTICIPATION); -mutate(() => ({ - token: props.token, -})); +const participationToken = computed(() => props.token); + +watchEffect(() => { + if (participationToken.value) { + mutate({ + token: participationToken.value, + }); + } +}); onDone(async ({ data }) => { participation.value = data?.confirmParticipation; diff --git a/js/src/components/Settings/SettingsOnboarding.vue b/js/src/components/Settings/SettingsOnboarding.vue index e9a489b7c..383d7d25f 100644 --- a/js/src/components/Settings/SettingsOnboarding.vue +++ b/js/src/components/Settings/SettingsOnboarding.vue @@ -70,14 +70,16 @@ const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone; const { loggedUser } = useUserSettings(); +const { mutate: doUpdateLocale } = updateLocale(); + onMounted(() => { - updateLocale(locale as unknown as string); + doUpdateLocale({ locale: locale as unknown as string }); doUpdateSetting({ timezone }); }); watch(locale, () => { if (locale.value) { - updateLocale(locale.value as string); + doUpdateLocale({ locale: locale as unknown as string }); saveLocaleData(locale.value as string); } }); diff --git a/js/src/composition/apollo/user.ts b/js/src/composition/apollo/user.ts index 4e8128eb4..b1459ff2b 100644 --- a/js/src/composition/apollo/user.ts +++ b/js/src/composition/apollo/user.ts @@ -59,12 +59,8 @@ export async function doUpdateSetting( })); } -export async function updateLocale(locale: string) { - useMutation<{ id: string; locale: string }>(UPDATE_USER_LOCALE, () => ({ - variables: { - locale, - }, - })); +export function updateLocale() { + return useMutation<{ id: string; locale: string }>(UPDATE_USER_LOCALE); } export function registerAccount() { diff --git a/js/src/i18n/en_US.json b/js/src/i18n/en_US.json index dc52d2ec0..58d487f1f 100644 --- a/js/src/i18n/en_US.json +++ b/js/src/i18n/en_US.json @@ -1579,5 +1579,8 @@ "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", "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}." } \ No newline at end of file diff --git a/js/src/i18n/fr_FR.json b/js/src/i18n/fr_FR.json index 9552c8370..32fee266e 100644 --- a/js/src/i18n/fr_FR.json +++ b/js/src/i18n/fr_FR.json @@ -1575,5 +1575,8 @@ "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", "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}." } diff --git a/js/src/services/push-subscription.ts b/js/src/services/push-subscription.ts index 0abb0d3c4..437a5867d 100644 --- a/js/src/services/push-subscription.ts +++ b/js/src/services/push-subscription.ts @@ -32,9 +32,8 @@ export async function subscribeUserToPush(): Promise<PushSubscription | null> { }; const registration = await navigator.serviceWorker.ready; try { - const pushSubscription = await registration.pushManager.subscribe( - subscribeOptions - ); + const pushSubscription = + await registration.pushManager.subscribe(subscribeOptions); console.debug("Received PushSubscription: ", pushSubscription); resolve(pushSubscription); } catch (e) { diff --git a/js/src/types/activity.model.ts b/js/src/types/activity.model.ts index c5c218757..f3ecb913e 100644 --- a/js/src/types/activity.model.ts +++ b/js/src/types/activity.model.ts @@ -3,6 +3,7 @@ import { IMember } from "./actor/member.model"; import { ActivityDiscussionSubject, ActivityEventCommentSubject, + ActivityEventParticipantSubject, ActivityEventSubject, ActivityGroupSubject, ActivityMemberSubject, @@ -21,7 +22,8 @@ export type ActivitySubject = | ActivityResourceSubject | ActivityDiscussionSubject | ActivityGroupSubject - | ActivityEventCommentSubject; + | ActivityEventCommentSubject + | ActivityEventParticipantSubject; export interface IActivity { id: string; diff --git a/js/src/types/enums.ts b/js/src/types/enums.ts index 0eb3841bb..dc551f3f9 100644 --- a/js/src/types/enums.ts +++ b/js/src/types/enums.ts @@ -200,6 +200,10 @@ export enum ActivityEventCommentSubject { COMMENT_POSTED = "comment_posted", } +export enum ActivityEventParticipantSubject { + EVENT_NEW_PARTICIPATION = "event_new_participation", +} + export enum ActivityPostSubject { POST_CREATED = "post_created", POST_UPDATED = "post_updated", diff --git a/js/src/views/Event/ParticipantsView.vue b/js/src/views/Event/ParticipantsView.vue index 3b859bfa3..f89562787 100644 --- a/js/src/views/Event/ParticipantsView.vue +++ b/js/src/views/Event/ParticipantsView.vue @@ -21,7 +21,7 @@ <div class=""> <o-field :label="t('Status')" horizontal label-for="role-select"> <o-select v-model="role" id="role-select"> - <option :value="null"> + <option value="EVERYTHING"> {{ t("Everything") }} </option> <option :value="ParticipantRole.CREATOR"> @@ -303,17 +303,15 @@ const participantsExportFormats = useParticipantsExportFormats(); const ellipsize = (text?: string) => text && text.substring(0, MESSAGE_ELLIPSIS_LENGTH).concat("…"); -// metaInfo() { -// return { -// title: this.t("Participants") as string, -// }; -// }, +const eventId = computed(() => props.eventId); + +const ParticipantAllRoles = { ...ParticipantRole, EVERYTHING: "EVERYTHING" }; const page = useRouteQuery("page", 1, integerTransformer); const role = useRouteQuery( "role", - ParticipantRole.PARTICIPANT, - enumTransformer(ParticipantRole) + "EVERYTHING", + enumTransformer(ParticipantAllRoles) ); const checkedRows = ref<IParticipant[]>([]); @@ -325,10 +323,10 @@ const { result: participantsResult, loading: participantsLoading } = useQuery<{ }>( PARTICIPANTS, () => ({ - uuid: props.eventId, + uuid: eventId.value, page: page.value, limit: PARTICIPANTS_PER_PAGE, - roles: role.value, + roles: role.value === "EVERYTHING" ? undefined : role.value, }), () => ({ enabled: diff --git a/js/src/views/Settings/PreferencesView.vue b/js/src/views/Settings/PreferencesView.vue index 919131972..2be472a9e 100644 --- a/js/src/views/Settings/PreferencesView.vue +++ b/js/src/views/Settings/PreferencesView.vue @@ -47,6 +47,7 @@ <o-select :loading="loadingTimezones || loadingUserSettings" v-model="$i18n.locale" + @update:modelValue="updateLanguage" :placeholder="t('Select a language')" id="setting-language" > @@ -147,7 +148,7 @@ import RouteName from "../../router/name"; import { AddressSearchType } from "@/types/enums"; import { Address, IAddress } from "@/types/address.model"; 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 { computed, defineAsyncComponent, ref, watch } from "vue"; import { useI18n } from "vue-i18n"; @@ -172,6 +173,12 @@ useHead({ const theme = ref(localStorage.getItem("theme")); const systemTheme = ref(!("theme" in localStorage)); +const { mutate: doUpdateLocale } = updateLocale(); + +const updateLanguage = (newLocale: string) => { + doUpdateLocale({ locale: newLocale }); +}; + watch(systemTheme, (newSystemTheme) => { console.debug("changing system theme", newSystemTheme); if (newSystemTheme) { diff --git a/lib/federation/activity_pub/actions/accept.ex b/lib/federation/activity_pub/actions/accept.ex index 87912de97..1480a262f 100644 --- a/lib/federation/activity_pub/actions/accept.ex +++ b/lib/federation/activity_pub/actions/accept.ex @@ -82,6 +82,11 @@ defmodule Mobilizon.Federation.ActivityPub.Actions.Accept do ) 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) audience = Audience.get_audience(participant) diff --git a/lib/federation/activity_pub/types/events.ex b/lib/federation/activity_pub/types/events.ex index 13b3e2f12..94cd9d665 100644 --- a/lib/federation/activity_pub/types/events.ex +++ b/lib/federation/activity_pub/types/events.ex @@ -224,6 +224,10 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Events do cond do Mobilizon.Events.get_default_participant_role(event) == :participant && role == :participant -> + Mobilizon.Service.Activity.Participant.insert_activity(participant, + subject: "event_new_participation" + ) + {:accept, Actions.Accept.accept( :join, diff --git a/lib/graphql/resolvers/push_subscription.ex b/lib/graphql/resolvers/push_subscription.ex index 5501fa8b2..5be49a7b5 100644 --- a/lib/graphql/resolvers/push_subscription.ex +++ b/lib/graphql/resolvers/push_subscription.ex @@ -6,6 +6,7 @@ defmodule Mobilizon.GraphQL.Resolvers.PushSubscription do alias Mobilizon.Storage.Page alias Mobilizon.Users alias Mobilizon.Users.{PushSubscription, User} + import Mobilizon.Web.Gettext @doc """ List all of an user's registered push subscriptions @@ -33,6 +34,19 @@ defmodule Mobilizon.GraphQL.Resolvers.PushSubscription do {:ok, %PushSubscription{}} -> {: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} -> require Logger Logger.error(inspect(err)) diff --git a/lib/graphql/schema/users/push_subscription.ex b/lib/graphql/schema/users/push_subscription.ex index 9565199de..6824ea59d 100644 --- a/lib/graphql/schema/users/push_subscription.ex +++ b/lib/graphql/schema/users/push_subscription.ex @@ -4,6 +4,7 @@ defmodule Mobilizon.GraphQL.Schema.Users.PushSubscription do """ use Absinthe.Schema.Notation alias Mobilizon.GraphQL.Resolvers.PushSubscription + alias Mobilizon.Users.User # object :push_subscription do # field(:id, :id) @@ -29,8 +30,9 @@ defmodule Mobilizon.GraphQL.Schema.Users.PushSubscription do middleware(Rajska.QueryAuthorization, permit: :user, - scope: false, - rule: :"write:user:setting:push" + scope: User, + rule: :"write:user:setting:push", + args: %{} ) resolve(&PushSubscription.register_push_subscription/3) @@ -41,8 +43,9 @@ defmodule Mobilizon.GraphQL.Schema.Users.PushSubscription do middleware(Rajska.QueryAuthorization, permit: :user, - scope: false, - rule: :"write:user:setting:push" + scope: User, + rule: :"write:user:setting:push", + args: %{} ) resolve(&PushSubscription.unregister_push_subscription/3) diff --git a/lib/mobilizon/activities/activities.ex b/lib/mobilizon/activities/activities.ex index 52b7683ed..d851f19fb 100644 --- a/lib/mobilizon/activities/activities.ex +++ b/lib/mobilizon/activities/activities.ex @@ -19,6 +19,7 @@ defmodule Mobilizon.Activities do @activity_types ["event", "post", "discussion", "resource", "group", "member", "comment"] @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"] @discussion_activity_subjects [ "discussion_created", @@ -48,12 +49,23 @@ defmodule Mobilizon.Activities do @settings_activity_subjects ["group_created", "group_updated"] @subjects @event_activity_subjects ++ + @participant_activity_subjects ++ @post_activity_subjects ++ @discussion_activity_subjects ++ @resource_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(Subject, @subjects) diff --git a/lib/service/activity/activity.ex b/lib/service/activity/activity.ex index 9c436fecf..1262102c6 100644 --- a/lib/service/activity/activity.ex +++ b/lib/service/activity/activity.ex @@ -4,7 +4,17 @@ defmodule Mobilizon.Service.Activity do """ 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()) :: {: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 Comment.get_object(comment_id) end + + defp do_get_object(:participant, participant_id) do + Participant.get_object(participant_id) + end end diff --git a/lib/service/activity/participant.ex b/lib/service/activity/participant.ex new file mode 100644 index 000000000..791e67fff --- /dev/null +++ b/lib/service/activity/participant.ex @@ -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 diff --git a/lib/service/activity/renderer/event.ex b/lib/service/activity/renderer/event.ex index 809583bb0..3bf07232f 100644 --- a/lib/service/activity/renderer/event.ex +++ b/lib/service/activity/renderer/event.ex @@ -1,6 +1,6 @@ defmodule Mobilizon.Service.Activity.Renderer.Event do @moduledoc """ - Insert a comment activity + Insert an event activity """ alias Mobilizon.Activities.Activity alias Mobilizon.Actors.Actor @@ -67,6 +67,16 @@ defmodule Mobilizon.Service.Activity.Renderer.Event do url: event_url(activity) } end + + :event_new_participation -> + %{ + body: + dgettext("activity", "%{profile} joined your event %{event}.", %{ + profile: profile(activity), + event: title(activity) + }), + url: event_url(activity) + } end end diff --git a/lib/service/date_time/date_time.ex b/lib/service/date_time/date_time.ex index bccf9e7d0..dbceb7cc4 100644 --- a/lib/service/date_time/date_time.ex +++ b/lib/service/date_time/date_time.ex @@ -3,6 +3,8 @@ defmodule Mobilizon.Service.DateTime do Module to represent a datetime in a given locale """ 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 @@ -10,25 +12,25 @@ defmodule Mobilizon.Service.DateTime do @spec datetime_to_string(DateTime.t(), String.t(), to_string_format()) :: String.t() def datetime_to_string(%DateTime{} = datetime, locale \\ "en", format \\ :medium) do - Mobilizon.Cldr.DateTime.to_string!(datetime, + MobilizonCldr.DateTime.to_string!(datetime, format: format, - locale: Mobilizon.Cldr.locale_or_default(locale) + locale: locale_or_default(locale) ) end @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 - Mobilizon.Cldr.Time.to_string!(datetime, + MobilizonCldr.Time.to_string!(datetime, format: format, - locale: Mobilizon.Cldr.locale_or_default(locale) + locale: locale_or_default(locale) ) end @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 - Mobilizon.Cldr.Date.to_string!(datetime, + MobilizonCldr.Date.to_string!(datetime, format: format, - locale: Mobilizon.Cldr.locale_or_default(locale) + locale: locale_or_default(locale) ) end @@ -47,9 +49,9 @@ defmodule Mobilizon.Service.DateTime do @spec datetime_relative(DateTime.t(), String.t()) :: String.t() def datetime_relative(%DateTime{} = datetime, locale \\ "en") do - Relative.to_string!(datetime, Mobilizon.Cldr, + Relative.to_string!(datetime, MobilizonCldr, relative_to: DateTime.utc_now(), - locale: Mobilizon.Cldr.locale_or_default(locale) + locale: locale_or_default(locale) ) end diff --git a/lib/service/export/participants/common.ex b/lib/service/export/participants/common.ex index afc0b9d88..84c848e1e 100644 --- a/lib/service/export/participants/common.ex +++ b/lib/service/export/participants/common.ex @@ -9,6 +9,7 @@ defmodule Mobilizon.Service.Export.Participants.Common do alias Mobilizon.Events.Participant.Metadata alias Mobilizon.Storage.Repo 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()) :: {:ok, Export.t()} | {:error, atom() | Ecto.Changeset.t()} @@ -58,7 +59,12 @@ defmodule Mobilizon.Service.Export.Participants.Common do @spec columns :: list(String.t()) 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 # One hour @@ -82,14 +88,26 @@ defmodule Mobilizon.Service.Export.Participants.Common do @spec to_list({Participant.t(), Actor.t()}) :: list(String.t()) def to_list( - {%Participant{role: role, metadata: metadata}, + {%Participant{role: role, metadata: metadata, inserted_at: inserted_at}, %Actor{domain: nil, preferred_username: "anonymous"}} ) 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 - def to_list({%Participant{role: role, metadata: metadata}, %Actor{} = actor}) do - [Actor.display_name_and_username(actor), translate_role(role), convert_metadata(metadata)] + def to_list( + {%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 @spec convert_metadata(Metadata.t() | nil) :: String.t() diff --git a/lib/service/notifier/email.ex b/lib/service/notifier/email.ex index acd213d2e..750c70706 100644 --- a/lib/service/notifier/email.ex +++ b/lib/service/notifier/email.ex @@ -96,7 +96,7 @@ defmodule Mobilizon.Service.Notifier.Email do defp can_send_activity?(activity, user, options) do 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 diff --git a/lib/web/templates/email/activity/_event_activity_item.html.heex b/lib/web/templates/email/activity/_event_activity_item.html.heex index 9af392741..0497d658d 100644 --- a/lib/web/templates/email/activity/_event_activity_item.html.heex +++ b/lib/web/templates/email/activity/_event_activity_item.html.heex @@ -51,4 +51,13 @@ }) |> raw %> <% 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 %> diff --git a/lib/web/templates/email/activity/_event_activity_item.text.eex b/lib/web/templates/email/activity/_event_activity_item.text.eex index 390bd2611..30b05abc0 100644 --- a/lib/web/templates/email/activity/_event_activity_item.text.eex +++ b/lib/web/templates/email/activity/_event_activity_item.text.eex @@ -1,31 +1,37 @@ <%= 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"] } ) %> <%= 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"] } ) %> <%= 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"] } ) %> <% :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"] } ) %> <%= 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"] } ) %> -<%= Routes.page_url(Mobilizon.Web.Endpoint, :event, @activity.subject_params["event_uuid"]) |> URI.decode() %><% end %><% end %> \ No newline at end of file +<%= 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 %> \ No newline at end of file diff --git a/lib/web/views/email_view.ex b/lib/web/views/email_view.ex index c103e4fcf..82b0e74d9 100644 --- a/lib/web/views/email_view.ex +++ b/lib/web/views/email_view.ex @@ -25,7 +25,6 @@ defmodule Mobilizon.Web.EmailView do defdelegate datetime_relative(datetime, locale \\ "en"), to: DateTimeRenderer defdelegate render_address(address), to: Address defdelegate is_same_day?(one, two), to: DateTimeRenderer - defdelegate display_name_and_username(actor), to: Actor defdelegate display_name(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 actor - |> Actor.display_name_and_username() + |> display_name_and_username() |> escape_html() 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 diff --git a/test/graphql/resolvers/push_subscription_test.exs b/test/graphql/resolvers/push_subscription_test.exs new file mode 100644 index 000000000..456aea443 --- /dev/null +++ b/test/graphql/resolvers/push_subscription_test.exs @@ -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 diff --git a/test/service/export/participants/common_test.exs b/test/service/export/participants/common_test.exs index 830772866..df0bb0581 100644 --- a/test/service/export/participants/common_test.exs +++ b/test/service/export/participants/common_test.exs @@ -5,24 +5,28 @@ defmodule Mobilizon.Service.Export.Participants.CommonTest do alias Mobilizon.Actors.Actor alias Mobilizon.Service.Export.Participants.Common + import Mobilizon.Service.DateTime, only: [datetime_to_string: 1] test "convert participants to list items" do participant = insert(:participant) actor = insert(: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 test "convert participants with metadata to list items" do participant = insert(:participant, metadata: %{message: "a message"}) actor = insert(: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 test "convert anonymous participants to list items" do participant = insert(:participant) 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