diff --git a/js/src/components/Settings/SettingsMenu.vue b/js/src/components/Settings/SettingsMenu.vue index e6aa18ba..bbf96cdc 100644 --- a/js/src/components/Settings/SettingsMenu.vue +++ b/js/src/components/Settings/SettingsMenu.vue @@ -14,7 +14,7 @@ :to="{ name: RouteName.PREFERENCES }" /> diff --git a/js/src/graphql/user.ts b/js/src/graphql/user.ts index b794ef1a..1eba90fb 100644 --- a/js/src/graphql/user.ts +++ b/js/src/graphql/user.ts @@ -241,3 +241,17 @@ export const UPDATE_USER_LOCALE = gql` } } `; + +export const FEED_TOKENS_LOGGED_USER = gql` + query { + loggedUser { + id + feedTokens { + token + actor { + id + } + } + } + } +`; diff --git a/js/src/i18n/en_US.json b/js/src/i18n/en_US.json index 0953e0c0..a5410a33 100644 --- a/js/src/i18n/en_US.json +++ b/js/src/i18n/en_US.json @@ -969,5 +969,13 @@ "You replied to a comment on the event {event}.": "You replied to a comment on the event {event}.", "{profile} replied to a comment on the event {event}.": "{profile} replied to a comment on the event {event}.", "New post": "New post", - "Comment text can't be empty": "Comment text can't be empty" + "Comment text can't be empty": "Comment text can't be empty", + "Notifications": "Notifications", + "Profile feeds": "Profile feeds", + "These feeds contain event data for the events for which this specific profile is a participant or creator. You should keep these private. You can find feeds for all of your profiles into your notification settings.": "These feeds contain event data for the events for which this specific profile is a participant or creator. You should keep these private. You can find feeds for all of your profiles into your notification settings.", + "Regenerate new links": "Regenerate new links", + "Create new links": "Create new links", + "You'll need to change the URLs where there were previously entered.": "You'll need to change the URLs where there were previously entered.", + "Personal feeds": "Personal feeds", + "These feeds contain event data for the events for which any of your profiles is a participant or creator. You should keep these private. You can find feeds for specific profiles on each profile edition page.": "These feeds contain event data for the events for which any of your profiles is a participant or creator. You should keep these private. You can find feeds for specific profiles on each profile edition page." } diff --git a/js/src/i18n/fr_FR.json b/js/src/i18n/fr_FR.json index 992fc10e..cdbfda87 100644 --- a/js/src/i18n/fr_FR.json +++ b/js/src/i18n/fr_FR.json @@ -1063,5 +1063,13 @@ "You replied to a comment on the event {event}.": "Vous avez répondu à un commentaire sur l'événement {event}.", "{profile} replied to a comment on the event {event}.": "{profile} a répondu à un commentaire sur l'événement {event}.", "New post": "Nouveau billet", - "Comment text can't be empty": "Le texte du commentaire ne peut être vide" + "Comment text can't be empty": "Le texte du commentaire ne peut être vide", + "Notifications": "Notifications", + "Profile feeds": "Flux du profil", + "These feeds contain event data for the events for which this specific profile is a participant or creator. You should keep these private. You can find feeds for all of your profiles into your notification settings.": "Ces flux contiennent des informations sur les événements pour lesquels ce profil spécifique est un⋅e participant⋅e ou un⋅e créateur⋅ice. Vous devriez les garder privés. Vous pouvez trouver des flux pour l'ensemble de vos profils dans vos paramètres de notification.", + "Regenerate new links": "Regénérer de nouveaux liens", + "Create new links": "Créer de nouveaux liens", + "You'll need to change the URLs where there were previously entered.": "Vous devrez changer les URLs là où vous les avez entrées précédemment.", + "Personal feeds": "Flux personnels", + "These feeds contain event data for the events for which any of your profiles is a participant or creator. You should keep these private. You can find feeds for specific profiles on each profile edition page.": "Ces flux contiennent des informations sur les événements pour lesquels n'importe lequel de vos profils est un⋅e participant⋅e ou un⋅e créateur⋅ice. Vous devriez les garder privés. Vous pouvez trouver des flux spécifiques à chaque profil sur la page d'édition des profils." } diff --git a/js/src/views/Account/children/EditIdentity.vue b/js/src/views/Account/children/EditIdentity.vue index bb6c44dc..5bf8670a 100644 --- a/js/src/views/Account/children/EditIdentity.vue +++ b/js/src/views/Account/children/EditIdentity.vue @@ -98,6 +98,77 @@ $t("Delete this identity") }} + +
+
+

{{ $t("Profile feeds") }}

+
+

+ {{ + $t( + "These feeds contain event data for the events for which this specific profile is a participant or creator. You should keep these private. You can find feeds for all of your profiles into your notification settings." + ) + }} +

+
+
+ + {{ $t("RSS/Atom Feed") }} + + + {{ $t("ICS/WebCal Feed") }} + + {{ $t("Regenerate new links") }} +
+
+
+ {{ $t("Create new links") }} +
+
@@ -131,6 +202,10 @@ h1 { .username-field + .field { margin-bottom: 0; } + +::v-deep .buttons > *:not(:last-child) .button { + margin-right: 0.5rem; +} @@ -193,4 +336,8 @@ export default class Notifications extends Vue { margin-left: 5px; } } + +::v-deep .buttons > *:not(:last-child) .button { + margin-right: 0.5rem; +} diff --git a/lib/graphql/resolvers/feed_token.ex b/lib/graphql/resolvers/feed_token.ex index ed545ed3..196da066 100644 --- a/lib/graphql/resolvers/feed_token.ex +++ b/lib/graphql/resolvers/feed_token.ex @@ -22,7 +22,7 @@ defmodule Mobilizon.GraphQL.Resolvers.FeedToken do ) do with {:is_owned, %Actor{}} <- User.owns_actor(user, actor_id), {:ok, feed_token} <- Events.create_feed_token(%{user_id: id, actor_id: actor_id}) do - {:ok, feed_token} + {:ok, to_short_uuid(feed_token)} else {:is_owned, nil} -> {:error, dgettext("errors", "Profile is not owned by authenticated user")} @@ -32,7 +32,7 @@ defmodule Mobilizon.GraphQL.Resolvers.FeedToken do @spec create_feed_token(any, map, map) :: {:ok, FeedToken.t()} def create_feed_token(_parent, %{}, %{context: %{current_user: %User{id: id}}}) do with {:ok, feed_token} <- Events.create_feed_token(%{user_id: id}) do - {:ok, feed_token} + {:ok, to_short_uuid(feed_token)} end end @@ -50,7 +50,8 @@ defmodule Mobilizon.GraphQL.Resolvers.FeedToken do %{token: token}, %{context: %{current_user: %User{id: id} = _user}} ) do - with {:ok, token} <- Ecto.UUID.cast(token), + with {:ok, token} <- ShortUUID.decode(token), + {:ok, token} <- Ecto.UUID.cast(token), {:no_token, %FeedToken{actor: actor, user: %User{} = user} = feed_token} <- {:no_token, Events.get_feed_token(token)}, {:token_from_user, true} <- {:token_from_user, id == user.id}, @@ -65,6 +66,9 @@ defmodule Mobilizon.GraphQL.Resolvers.FeedToken do :error -> {:error, dgettext("errors", "Token is not a valid UUID")} + {:error, "Invalid input"} -> + {:error, dgettext("errors", "Token is not a valid UUID")} + {:no_token, _} -> {:error, dgettext("errors", "Token does not exist")} @@ -77,4 +81,8 @@ defmodule Mobilizon.GraphQL.Resolvers.FeedToken do def delete_feed_token(_parent, _args, %{}) do {:error, dgettext("errors", "You are not allowed to delete a feed token if not connected")} end + + defp to_short_uuid(%FeedToken{token: token} = feed_token) do + %FeedToken{feed_token | token: ShortUUID.encode!(token)} + end end diff --git a/lib/graphql/schema/actors/person.ex b/lib/graphql/schema/actors/person.ex index fd8aedf0..42743316 100644 --- a/lib/graphql/schema/actors/person.ex +++ b/lib/graphql/schema/actors/person.ex @@ -4,7 +4,7 @@ defmodule Mobilizon.GraphQL.Schema.Actors.PersonType do """ use Absinthe.Schema.Notation - import Absinthe.Resolution.Helpers, only: [dataloader: 1] + import Absinthe.Resolution.Helpers, only: [dataloader: 2] alias Mobilizon.Events alias Mobilizon.GraphQL.Resolvers.{Media, Person} @@ -53,7 +53,13 @@ defmodule Mobilizon.GraphQL.Schema.Actors.PersonType do ) field(:feed_tokens, list_of(:feed_token), - resolve: dataloader(Events), + resolve: + dataloader( + Events, + callback: fn feed_tokens, _parent, _args -> + {:ok, Enum.map(feed_tokens, &Map.put(&1, :token, ShortUUID.encode!(&1.token)))} + end + ), description: "A list of the feed tokens for this person" ) diff --git a/lib/graphql/schema/events/feed_token.ex b/lib/graphql/schema/events/feed_token.ex index 44ea9628..c5088057 100644 --- a/lib/graphql/schema/events/feed_token.ex +++ b/lib/graphql/schema/events/feed_token.ex @@ -31,7 +31,7 @@ defmodule Mobilizon.GraphQL.Schema.Events.FeedTokenType do description: "The actor that participates to the event" ) - field(:token, :string, description: "The role of this actor at this event") + field(:token, :string, description: "A ShortUUID private token") end @desc "Represents a deleted feed_token" diff --git a/lib/graphql/schema/user.ex b/lib/graphql/schema/user.ex index af62f798..e8236c89 100644 --- a/lib/graphql/schema/user.ex +++ b/lib/graphql/schema/user.ex @@ -4,7 +4,7 @@ defmodule Mobilizon.GraphQL.Schema.UserType do """ use Absinthe.Schema.Notation - import Absinthe.Resolution.Helpers, only: [dataloader: 1] + import Absinthe.Resolution.Helpers, only: [dataloader: 2] alias Mobilizon.Events alias Mobilizon.GraphQL.Resolvers.{Media, User} @@ -43,7 +43,13 @@ defmodule Mobilizon.GraphQL.Schema.UserType do ) field(:feed_tokens, list_of(:feed_token), - resolve: dataloader(Events), + resolve: + dataloader( + Events, + callback: fn feed_tokens, _parent, _args -> + {:ok, Enum.map(feed_tokens, &Map.put(&1, :token, ShortUUID.encode!(&1.token)))} + end + ), description: "A list of the feed tokens for this user" ) diff --git a/lib/service/export/common.ex b/lib/service/export/common.ex index 26efe9b3..2b9a3569 100644 --- a/lib/service/export/common.ex +++ b/lib/service/export/common.ex @@ -25,8 +25,9 @@ defmodule Mobilizon.Service.Export.Common do # Only events, not posts @spec fetch_events_from_token(String.t()) :: String.t() def fetch_events_from_token(token) do - with {:ok, _uuid} <- Ecto.UUID.cast(token), - %FeedToken{actor: actor, user: %User{} = user} <- Events.get_feed_token(token) do + with {:ok, uuid} <- ShortUUID.decode(token), + {:ok, _uuid} <- Ecto.UUID.cast(uuid), + %FeedToken{actor: actor, user: %User{} = user} <- Events.get_feed_token(uuid) do case actor do %Actor{} = actor -> %{ diff --git a/lib/web/controllers/feed_controller.ex b/lib/web/controllers/feed_controller.ex index 87b8181d..4de8e61b 100644 --- a/lib/web/controllers/feed_controller.ex +++ b/lib/web/controllers/feed_controller.ex @@ -26,7 +26,7 @@ defmodule Mobilizon.Web.FeedController do end def event(conn, %{"uuid" => uuid, "format" => "ics"}) do - return_data(conn, "ics", "event_" <> uuid, "event.ics") + return_data(conn, "ics", "event_" <> uuid, "event") end def event(_conn, _) do @@ -34,7 +34,7 @@ defmodule Mobilizon.Web.FeedController do end def going(conn, %{"token" => token, "format" => format}) when format in @formats do - return_data(conn, format, "token_" <> token, "events.#{format}") + return_data(conn, format, "token_" <> token, "events") end def going(_conn, _) do diff --git a/test/graphql/resolvers/feed_token_test.exs b/test/graphql/resolvers/feed_token_test.exs index 931c8c34..bc2d353c 100644 --- a/test/graphql/resolvers/feed_token_test.exs +++ b/test/graphql/resolvers/feed_token_test.exs @@ -186,7 +186,7 @@ defmodule Mobilizon.GraphQL.Resolvers.FeedTokenTest do %{ "feedTokens" => [ %{ - "token" => feed_token.token + "token" => ShortUUID.encode!(feed_token.token) } ] } @@ -194,7 +194,7 @@ defmodule Mobilizon.GraphQL.Resolvers.FeedTokenTest do mutation = """ mutation { deleteFeedToken( - token: "#{feed_token.token}", + token: "#{ShortUUID.encode!(feed_token.token)}", ) { actor { id @@ -270,7 +270,7 @@ defmodule Mobilizon.GraphQL.Resolvers.FeedTokenTest do mutation = """ mutation { deleteFeedToken( - token: "#{feed_token.token}", + token: "#{ShortUUID.encode!(feed_token.token)}", ) { actor { id @@ -320,7 +320,7 @@ defmodule Mobilizon.GraphQL.Resolvers.FeedTokenTest do mutation = """ mutation { deleteFeedToken( - token: "#{uuid}" + token: "#{ShortUUID.encode!(uuid)}" ) { actor { id diff --git a/test/service/export/icalendar_test.exs b/test/service/export/icalendar_test.exs index c3aceed1..b50351e1 100644 --- a/test/service/export/icalendar_test.exs +++ b/test/service/export/icalendar_test.exs @@ -58,7 +58,7 @@ defmodule Mobilizon.Service.ICalendarTest do event = insert(:event) insert(:participant, event: event, actor: actor, role: :participant) - {:commit, ics} = ICalendarService.create_cache("token_#{token}") + {:commit, ics} = ICalendarService.create_cache("token_#{ShortUUID.encode!(token)}") assert ics =~ event.title end end diff --git a/test/web/controllers/feed_controller_test.exs b/test/web/controllers/feed_controller_test.exs index 1b56dabc..c4335dfb 100644 --- a/test/web/controllers/feed_controller_test.exs +++ b/test/web/controllers/feed_controller_test.exs @@ -225,7 +225,7 @@ defmodule Mobilizon.Web.FeedControllerTest do conn |> get( Endpoint - |> Routes.feed_url(:going, feed_token.token, "atom") + |> Routes.feed_url(:going, ShortUUID.encode!(feed_token.token), "atom") |> URI.decode() ) @@ -260,7 +260,7 @@ defmodule Mobilizon.Web.FeedControllerTest do |> put_req_header("accept", "application/atom+xml") |> get( Endpoint - |> Routes.feed_url(:going, feed_token.token, "atom") + |> Routes.feed_url(:going, ShortUUID.encode!(feed_token.token), "atom") |> URI.decode() ) @@ -307,7 +307,7 @@ defmodule Mobilizon.Web.FeedControllerTest do |> put_req_header("accept", "text/calendar") |> get( Endpoint - |> Routes.feed_url(:going, feed_token.token, "ics") + |> Routes.feed_url(:going, ShortUUID.encode!(feed_token.token), "ics") |> URI.decode() ) @@ -338,7 +338,7 @@ defmodule Mobilizon.Web.FeedControllerTest do |> put_req_header("accept", "text/calendar") |> get( Endpoint - |> Routes.feed_url(:going, feed_token.token, "ics") + |> Routes.feed_url(:going, ShortUUID.encode!(feed_token.token), "ics") |> URI.decode() )