From fdf87ea991b1d406b28dbd0c8807908939070c8b Mon Sep 17 00:00:00 2001 From: Thomas Citharel Date: Wed, 23 Aug 2023 12:12:54 +0200 Subject: [PATCH 1/7] fix(push): fix push subscriptions registration Signed-off-by: Thomas Citharel --- js/src/services/push-subscription.ts | 5 ++--- lib/graphql/schema/users/push_subscription.ex | 11 +++++++---- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/js/src/services/push-subscription.ts b/js/src/services/push-subscription.ts index 0abb0d3c..437a5867 100644 --- a/js/src/services/push-subscription.ts +++ b/js/src/services/push-subscription.ts @@ -32,9 +32,8 @@ export async function subscribeUserToPush(): Promise { }; 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/lib/graphql/schema/users/push_subscription.ex b/lib/graphql/schema/users/push_subscription.ex index 9565199d..6824ea59 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) From 8617382af2cb972236313271b6858c9a0366753d Mon Sep 17 00:00:00 2001 From: Thomas Citharel Date: Wed, 23 Aug 2023 14:39:24 +0200 Subject: [PATCH 2/7] test: add tests for push notification registration GraphQL resolver Signed-off-by: Thomas Citharel --- lib/graphql/resolvers/push_subscription.ex | 14 ++ .../resolvers/push_subscription_test.exs | 149 ++++++++++++++++++ 2 files changed, 163 insertions(+) create mode 100644 test/graphql/resolvers/push_subscription_test.exs diff --git a/lib/graphql/resolvers/push_subscription.ex b/lib/graphql/resolvers/push_subscription.ex index 5501fa8b..5be49a7b 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/test/graphql/resolvers/push_subscription_test.exs b/test/graphql/resolvers/push_subscription_test.exs new file mode 100644 index 00000000..456aea44 --- /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 From fef60ed0f92fc4e09ee261ff03f1139aff2449c3 Mon Sep 17 00:00:00 2001 From: Thomas Citharel Date: Wed, 23 Aug 2023 16:10:32 +0200 Subject: [PATCH 3/7] feat(export): add date of participant creation in participant exports Closes #1343 Signed-off-by: Thomas Citharel --- lib/service/date_time/date_time.ex | 18 ++++++------ lib/service/export/participants/common.ex | 28 +++++++++++++++---- .../export/participants/common_test.exs | 10 +++++-- 3 files changed, 40 insertions(+), 16 deletions(-) diff --git a/lib/service/date_time/date_time.ex b/lib/service/date_time/date_time.ex index bccf9e7d..dbceb7cc 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 afc0b9d8..84c848e1 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/test/service/export/participants/common_test.exs b/test/service/export/participants/common_test.exs index 83077286..df0bb058 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 From 010a5e426def0a0b7f2658234f3c9d6eec46a68e Mon Sep 17 00:00:00 2001 From: Thomas Citharel Date: Wed, 23 Aug 2023 16:11:55 +0200 Subject: [PATCH 4/7] fix(front): fix changing language not being saved to the user's settings Signed-off-by: Thomas Citharel --- js/src/components/Settings/SettingsOnboarding.vue | 6 ++++-- js/src/composition/apollo/user.ts | 8 ++------ js/src/views/Settings/PreferencesView.vue | 9 ++++++++- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/js/src/components/Settings/SettingsOnboarding.vue b/js/src/components/Settings/SettingsOnboarding.vue index e9a489b7..383d7d25 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 4e8128eb..b1459ff2 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/views/Settings/PreferencesView.vue b/js/src/views/Settings/PreferencesView.vue index 91913197..2be472a9 100644 --- a/js/src/views/Settings/PreferencesView.vue +++ b/js/src/views/Settings/PreferencesView.vue @@ -47,6 +47,7 @@ @@ -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) { From beef3ff16d12f5d5710e302b739dd724ad4b0cb5 Mon Sep 17 00:00:00 2001 From: Thomas Citharel Date: Thu, 24 Aug 2023 09:50:22 +0200 Subject: [PATCH 5/7] fix(front): fix selecting all participants in participant view Signed-off-by: Thomas Citharel --- js/src/views/Event/ParticipantsView.vue | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/js/src/views/Event/ParticipantsView.vue b/js/src/views/Event/ParticipantsView.vue index 3b859bfa..f8956278 100644 --- a/js/src/views/Event/ParticipantsView.vue +++ b/js/src/views/Event/ParticipantsView.vue @@ -21,7 +21,7 @@
-