Merge remote-tracking branch 'origin/main'

This commit is contained in:
778a69cd 2024-01-31 17:03:11 +01:00
commit 81ae56d850
66 changed files with 1399 additions and 902 deletions

View file

@ -33,7 +33,7 @@
# If you want to enforce a style guide and need a more traditional linting # If you want to enforce a style guide and need a more traditional linting
# experience, you can change `strict` to `true` below: # experience, you can change `strict` to `true` below:
# #
strict: false, strict: true,
# #
# If you want to use uncolored output by default, you can change `color` # If you want to use uncolored output by default, you can change `color`
# to `false` below: # to `false` below:
@ -160,6 +160,7 @@
# #
{Credo.Check.Warning.LazyLogging, false}, {Credo.Check.Warning.LazyLogging, false},
{Credo.Check.Refactor.MapInto, false}, {Credo.Check.Refactor.MapInto, false},
{Credo.Check.Warning.MissedMetadataKeyInLoggerConfig, false}
] ]
} }
] ]

View file

@ -4,25 +4,25 @@ init:
setup: stop setup: stop
@bash docker/message.sh "Compiling everything" @bash docker/message.sh "Compiling everything"
docker-compose run --rm api bash -c 'mix deps.get; npm ci; npm run build:pictures; mix ecto.create; mix ecto.migrate' docker compose run --rm api bash -c 'mix deps.get; npm ci; npm run build:pictures; mix ecto.create; mix ecto.migrate'
migrate: migrate:
docker-compose run --rm api mix ecto.migrate docker compose run --rm api mix ecto.migrate
logs: logs:
docker-compose logs -f docker compose logs -f
start: stop start: stop
@bash docker/message.sh "Starting Mobilizon with Docker" @bash docker/message.sh "Starting Mobilizon with Docker"
docker-compose up -d api docker compose up -d api
@bash docker/message.sh "Docker server started" @bash docker/message.sh "Docker server started"
stop: stop:
@bash docker/message.sh "Stopping Mobilizon" @bash docker/message.sh "Stopping Mobilizon"
docker-compose down docker compose down
@bash docker/message.sh "Mobilizon is stopped" @bash docker/message.sh "Mobilizon is stopped"
test: stop test: stop
@bash docker/message.sh "Running tests" @bash docker/message.sh "Running tests"
docker-compose -f docker-compose.yml -f docker-compose.test.yml run api mix prepare_test docker compose -f docker compose.yml -f docker compose.test.yml run api mix prepare_test
docker-compose -f docker-compose.yml -f docker-compose.test.yml run api mix test $(only) docker compose -f docker compose.yml -f docker compose.test.yml run api mix test $(only)
@bash docker/message.sh "Done running tests" @bash docker/message.sh "Done running tests"
format: format:
docker-compose run --rm api bash -c "mix format && mix credo --strict" docker compose run --rm api bash -c "mix format && mix credo --strict"
@bash docker/message.sh "Code is now ready to commit :)" @bash docker/message.sh "Code is now ready to commit :)"
target: init target: init

View file

@ -25,7 +25,7 @@ defmodule Mobilizon.Federation.ActivityPub.Actions.Invite do
) do ) do
Logger.debug("Handling #{actor_url} invite to #{group_url} sent to #{target_actor_url}") Logger.debug("Handling #{actor_url} invite to #{group_url} sent to #{target_actor_url}")
if is_able_to_invite?(actor, group) do if able_to_invite?(actor, group) do
with {:ok, %Member{url: member_url} = member} <- with {:ok, %Member{url: member_url} = member} <-
Actors.create_member(%{ Actors.create_member(%{
parent_id: group_id, parent_id: group_id,
@ -64,8 +64,8 @@ defmodule Mobilizon.Federation.ActivityPub.Actions.Invite do
end end
end end
@spec is_able_to_invite?(Actor.t(), Actor.t()) :: boolean @spec able_to_invite?(Actor.t(), Actor.t()) :: boolean
defp is_able_to_invite?(%Actor{domain: actor_domain, id: actor_id}, %Actor{ defp able_to_invite?(%Actor{domain: actor_domain, id: actor_id}, %Actor{
domain: group_domain, domain: group_domain,
id: group_id id: group_id
}) do }) do
@ -76,7 +76,7 @@ defmodule Mobilizon.Federation.ActivityPub.Actions.Invite do
# If local group, we'll send the invite # If local group, we'll send the invite
case Actors.get_member(actor_id, group_id) do case Actors.get_member(actor_id, group_id) do
{:ok, %Member{} = admin_member} -> {:ok, %Member{} = admin_member} ->
Member.is_administrator(admin_member) Member.administrator?(admin_member)
_ -> _ ->
false false

View file

@ -34,7 +34,7 @@ defmodule Mobilizon.Federation.ActivityPub.Actions.Leave do
local, local,
additional additional
) do ) do
if Participant.is_not_only_organizer(event_id, actor_id) do if Participant.not_only_organizer?(event_id, actor_id) do
{:error, :is_only_organizer} {:error, :is_only_organizer}
else else
case Mobilizon.Events.get_participant( case Mobilizon.Events.get_participant(
@ -83,7 +83,7 @@ defmodule Mobilizon.Federation.ActivityPub.Actions.Leave do
case Actors.get_member(actor_id, group_id) do case Actors.get_member(actor_id, group_id) do
{:ok, %Member{id: member_id} = member} -> {:ok, %Member{id: member_id} = member} ->
if Map.get(additional, :force_member_removal, false) || group_domain != actor_domain || if Map.get(additional, :force_member_removal, false) || group_domain != actor_domain ||
!Actors.is_only_administrator?(member_id, group_id) do !Actors.only_administrator?(member_id, group_id) do
with {:ok, %Member{} = member} <- Actors.delete_member(member) do with {:ok, %Member{} = member} <- Actors.delete_member(member) do
Mobilizon.Service.Activity.Member.insert_activity(member, subject: "member_quit") Mobilizon.Service.Activity.Member.insert_activity(member, subject: "member_quit")

View file

@ -44,13 +44,13 @@ defmodule Mobilizon.Federation.ActivityPub.Permission do
) do ) do
case object |> Ownable.permissions() |> get_in([:create]) do case object |> Ownable.permissions() |> get_in([:create]) do
:member -> :member ->
Actors.is_member?(actor_id, group_id) Actors.member?(actor_id, group_id)
:moderator -> :moderator ->
Actors.is_moderator?(actor_id, group_id) Actors.moderator?(actor_id, group_id)
:administrator -> :administrator ->
Actors.is_administrator?(actor_id, group_id) Actors.administrator?(actor_id, group_id)
_ -> _ ->
false false
@ -122,21 +122,21 @@ defmodule Mobilizon.Federation.ActivityPub.Permission do
"Checking if activity actor #{actor_url} is a moderator from group from #{object.url}" "Checking if activity actor #{actor_url} is a moderator from group from #{object.url}"
) )
Actors.is_moderator?(actor_id, group_id) Actors.moderator?(actor_id, group_id)
:administrator -> :administrator ->
Logger.debug( Logger.debug(
"Checking if activity actor #{actor_url} is an administrator from group from #{object.url}" "Checking if activity actor #{actor_url} is an administrator from group from #{object.url}"
) )
Actors.is_administrator?(actor_id, group_id) Actors.administrator?(actor_id, group_id)
_ -> _ ->
Logger.debug( Logger.debug(
"Checking if activity actor #{actor_url} is a member from group from #{object.url}" "Checking if activity actor #{actor_url} is a member from group from #{object.url}"
) )
Actors.is_member?(actor_id, group_id) Actors.member?(actor_id, group_id)
end end
_ -> _ ->

View file

@ -21,10 +21,10 @@ defmodule Mobilizon.Federation.ActivityPub.Publisher do
Logger.debug("Publishing an activity") Logger.debug("Publishing an activity")
Logger.debug(inspect(activity, pretty: true)) Logger.debug(inspect(activity, pretty: true))
public = Visibility.is_public?(activity) public = Visibility.public?(activity)
Logger.debug("is public ? #{public}") Logger.debug("is public ? #{public}")
if public && is_create_activity?(activity) && Config.get([:instance, :allow_relay]) do if public && create_activity?(activity) && Config.get([:instance, :allow_relay]) do
Logger.info(fn -> "Relaying #{activity.data["id"]} out" end) Logger.info(fn -> "Relaying #{activity.data["id"]} out" end)
Relay.publish(activity) Relay.publish(activity)
@ -125,9 +125,9 @@ defmodule Mobilizon.Federation.ActivityPub.Publisher do
end) end)
end end
@spec is_create_activity?(Activity.t()) :: boolean @spec create_activity?(Activity.t()) :: boolean
defp is_create_activity?(%Activity{data: %{"type" => "Create"}}), do: true defp create_activity?(%Activity{data: %{"type" => "Create"}}), do: true
defp is_create_activity?(_), do: false defp create_activity?(_), do: false
@spec convert_members_in_recipients(list(String.t())) :: {list(String.t()), list(Actor.t())} @spec convert_members_in_recipients(list(String.t())) :: {list(String.t()), list(Actor.t())}
defp convert_members_in_recipients(recipients) do defp convert_members_in_recipients(recipients) do

View file

@ -285,7 +285,7 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
object_data when is_map(object_data) <- object_data when is_map(object_data) <-
object |> Converter.Resource.as_to_model_data(), object |> Converter.Resource.as_to_model_data(),
{:member, true} <- {:member, true} <-
{:member, Actors.is_member?(object_data.creator_id, object_data.actor_id)}, {:member, Actors.member?(object_data.creator_id, object_data.actor_id)},
{:ok, %Activity{} = activity, %Resource{} = resource} <- {:ok, %Activity{} = activity, %Resource{} = resource} <-
Actions.Create.create(:resource, object_data, false) do Actions.Create.create(:resource, object_data, false) do
{:ok, activity, resource} {:ok, activity, resource}
@ -1005,14 +1005,14 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
end end
# Comment initiates a whole discussion only if it has full title # Comment initiates a whole discussion only if it has full title
@spec is_data_for_comment_or_discussion?(map()) :: boolean() @spec data_for_comment_or_discussion?(map()) :: boolean()
defp is_data_for_comment_or_discussion?(object_data) do defp data_for_comment_or_discussion?(object_data) do
is_data_a_discussion_initialization?(object_data) and data_a_discussion_initialization?(object_data) and
is_nil(object_data.discussion_id) is_nil(object_data.discussion_id)
end end
# Comment initiates a whole discussion only if it has full title # Comment initiates a whole discussion only if it has full title
defp is_data_a_discussion_initialization?(object_data) do defp data_a_discussion_initialization?(object_data) do
not Map.has_key?(object_data, :title) or not Map.has_key?(object_data, :title) or
is_nil(object_data.title) or object_data.title == "" is_nil(object_data.title) or object_data.title == ""
end end
@ -1034,7 +1034,7 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
@spec transform_object_data_for_discussion(map()) :: map() @spec transform_object_data_for_discussion(map()) :: map()
defp transform_object_data_for_discussion(object_data) do defp transform_object_data_for_discussion(object_data) do
# Basic comment # Basic comment
if is_data_a_discussion_initialization?(object_data) do if data_a_discussion_initialization?(object_data) do
object_data object_data
else else
# Conversation # Conversation
@ -1138,8 +1138,8 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
end end
end end
defp is_group_object_gone(object_id) do defp group_object_gone_check(object_id) do
Logger.debug("is_group_object_gone #{object_id}") Logger.debug("Checking if group object #{object_id} is gone")
case ActivityPub.fetch_object_from_url(object_id, force: true) do case ActivityPub.fetch_object_from_url(object_id, force: true) do
# comments are just emptied # comments are just emptied
@ -1163,14 +1163,10 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
end end
end end
# Before 1.0.4 the object of a "Remove" activity was an actor's URL
# instead of the member's URL.
# TODO: Remove in 1.2
@spec get_remove_object(map() | String.t()) :: {:ok, integer()} @spec get_remove_object(map() | String.t()) :: {:ok, integer()}
defp get_remove_object(object) do defp get_remove_object(object) do
case object |> Utils.get_url() |> ActivityPub.fetch_object_from_url() do case object |> Utils.get_url() |> ActivityPub.fetch_object_from_url() do
{:ok, %Member{actor: %Actor{id: person_id}}} -> {:ok, person_id} {:ok, %Member{actor: %Actor{id: person_id}}} -> {:ok, person_id}
{:ok, %Actor{id: person_id}} -> {:ok, person_id}
_ -> {:error, :remove_object_not_found} _ -> {:error, :remove_object_not_found}
end end
end end
@ -1196,7 +1192,7 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
@spec create_comment_or_discussion(map()) :: @spec create_comment_or_discussion(map()) ::
{:ok, Activity.t(), struct()} | {:error, atom() | Ecto.Changeset.t()} {:ok, Activity.t(), struct()} | {:error, atom() | Ecto.Changeset.t()}
defp create_comment_or_discussion(object_data) do defp create_comment_or_discussion(object_data) do
if is_data_for_comment_or_discussion?(object_data) do if data_for_comment_or_discussion?(object_data) do
Logger.debug("Chosing to create a regular comment") Logger.debug("Chosing to create a regular comment")
Actions.Create.create(:comment, object_data, false) Actions.Create.create(:comment, object_data, false)
else else
@ -1248,7 +1244,7 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
end end
defp handle_group_being_gone(actor, actor_url, object_id) do defp handle_group_being_gone(actor, actor_url, object_id) do
case is_group_object_gone(object_id) do case group_object_gone_check(object_id) do
# The group object is no longer there, we can remove the element # The group object is no longer there, we can remove the element
{:ok, entity} -> {:ok, entity} ->
if Utils.origin_check_from_id?(actor_url, object_id) || if Utils.origin_check_from_id?(actor_url, object_id) ||

View file

@ -93,7 +93,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Members do
atom() atom()
) :: boolean ) :: boolean
defp check_admins_left?(member_id, group_id, current_role, updated_role) do defp check_admins_left?(member_id, group_id, current_role, updated_role) do
Actors.is_only_administrator?(member_id, group_id) && current_role == :administrator && Actors.only_administrator?(member_id, group_id) && current_role == :administrator &&
updated_role != :administrator updated_role != :administrator
end end
end end

View file

@ -14,17 +14,17 @@ defmodule Mobilizon.Federation.ActivityPub.Visibility do
@public "https://www.w3.org/ns/activitystreams#Public" @public "https://www.w3.org/ns/activitystreams#Public"
@spec is_public?(Activity.t() | map()) :: boolean() @spec public?(Activity.t() | map()) :: boolean()
def is_public?(%{data: %{"type" => "Tombstone"}}), do: false def public?(%{data: %{"type" => "Tombstone"}}), do: false
def is_public?(%{data: data}), do: is_public?(data) def public?(%{data: data}), do: public?(data)
def is_public?(%Activity{data: data}), do: is_public?(data) def public?(%Activity{data: data}), do: public?(data)
def is_public?(data) when is_map(data) do def public?(data) when is_map(data) do
@public in make_list(Map.get(data, "to", [])) @public in make_list(Map.get(data, "to", []))
end end
def is_public?(%Comment{deleted_at: deleted_at}), do: !is_nil(deleted_at) def public?(%Comment{deleted_at: deleted_at}), do: !is_nil(deleted_at)
def is_public?(err), do: raise(ArgumentError, message: "Invalid argument #{inspect(err)}") def public?(err), do: raise(ArgumentError, message: "Invalid argument #{inspect(err)}")
defp make_list(data) when is_list(data), do: data defp make_list(data) when is_list(data), do: data
defp make_list(data), do: [data] defp make_list(data), do: [data]

View file

@ -76,14 +76,13 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Actor do
def as_to_model_data(_), do: {:error, :actor_not_allowed_type} def as_to_model_data(_), do: {:error, :actor_not_allowed_type}
defp add_endpoints_to_model(actor, data) do defp add_endpoints_to_model(actor, data) do
# TODO: Remove fallbacks in 3.0
endpoints = %{ endpoints = %{
members_url: get_in(data, ["endpoints", "members"]) || data["members"], members_url: get_in(data, ["endpoints", "members"]),
resources_url: get_in(data, ["endpoints", "resources"]) || data["resources"], resources_url: get_in(data, ["endpoints", "resources"]),
todos_url: get_in(data, ["endpoints", "todos"]) || data["todos"], todos_url: get_in(data, ["endpoints", "todos"]),
events_url: get_in(data, ["endpoints", "events"]) || data["events"], events_url: get_in(data, ["endpoints", "events"]),
posts_url: get_in(data, ["endpoints", "posts"]) || data["posts"], posts_url: get_in(data, ["endpoints", "posts"]),
discussions_url: get_in(data, ["endpoints", "discussions"]) || data["discussions"], discussions_url: get_in(data, ["endpoints", "discussions"]),
shared_inbox_url: data["endpoints"]["sharedInbox"] shared_inbox_url: data["endpoints"]["sharedInbox"]
} }

View file

@ -62,7 +62,7 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Comment do
tags: fetch_tags(tag_object), tags: fetch_tags(tag_object),
mentions: fetch_mentions(tag_object), mentions: fetch_mentions(tag_object),
local: is_nil(actor_domain), local: is_nil(actor_domain),
visibility: if(Visibility.is_public?(object), do: :public, else: :private), visibility: if(Visibility.public?(object), do: :public, else: :private),
published_at: object["published"], published_at: object["published"],
is_announcement: Map.get(object, "isAnnouncement", false) is_announcement: Map.get(object, "isAnnouncement", false)
} }

View file

@ -77,7 +77,7 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Event do
category: Categories.get_category(object["category"]), category: Categories.get_category(object["category"]),
visibility: visibility, visibility: visibility,
join_options: Map.get(object, "joinMode", "free"), join_options: Map.get(object, "joinMode", "free"),
local: is_local?(object["id"]), local: local?(object["id"]),
external_participation_url: object["externalParticipationUrl"], external_participation_url: object["externalParticipationUrl"],
options: options, options: options,
metadata: metadata, metadata: metadata,
@ -305,8 +305,8 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Event do
) )
end end
@spec is_local?(String.t()) :: boolean() @spec local?(String.t()) :: boolean()
defp is_local?(url) do defp local?(url) do
%URI{host: url_domain} = URI.parse(url) %URI{host: url_domain} = URI.parse(url)
%URI{host: local_domain} = URI.parse(Endpoint.url()) %URI{host: local_domain} = URI.parse(Endpoint.url())
url_domain == local_domain url_domain == local_domain

View file

@ -5,7 +5,7 @@ defmodule Mobilizon.Federation.NodeInfo do
alias Mobilizon.Service.HTTP.WebfingerClient alias Mobilizon.Service.HTTP.WebfingerClient
require Logger require Logger
import Mobilizon.Service.HTTP.Utils, only: [is_content_type?: 2] import Mobilizon.Service.HTTP.Utils, only: [content_type_matches?: 2]
@application_uri "https://www.w3.org/ns/activitystreams#Application" @application_uri "https://www.w3.org/ns/activitystreams#Application"
@nodeinfo_rel_2_0 "http://nodeinfo.diaspora.software/ns/schema/2.0" @nodeinfo_rel_2_0 "http://nodeinfo.diaspora.software/ns/schema/2.0"
@ -110,7 +110,7 @@ defmodule Mobilizon.Federation.NodeInfo do
{:ok, String.t()} | {:error, :bad_content_type | :body_not_json} {:ok, String.t()} | {:error, :bad_content_type | :body_not_json}
defp validate_json_response(body, headers) do defp validate_json_response(body, headers) do
cond do cond do
!is_content_type?(headers, "application/json") -> !content_type_matches?(headers, "application/json") ->
{:error, :bad_content_type} {:error, :bad_content_type}
!is_map(body) -> !is_map(body) ->

View file

@ -25,8 +25,8 @@ defmodule Mobilizon.GraphQL.API.Search do
cond do cond do
# Some URLs could be domain.tld/@username, so keep this condition above # Some URLs could be domain.tld/@username, so keep this condition above
# the `is_handle` function # the `handle?` function
is_url(term) -> url?(term) ->
# skip, if it's not an actor # skip, if it's not an actor
case process_from_url(term) do case process_from_url(term) do
%Page{total: _total, elements: [%Actor{} = _actor]} = page -> %Page{total: _total, elements: [%Actor{} = _actor]} = page ->
@ -36,11 +36,11 @@ defmodule Mobilizon.GraphQL.API.Search do
{:ok, %{total: 0, elements: []}} {:ok, %{total: 0, elements: []}}
end end
is_handle(term) -> handle?(term) ->
{:ok, process_from_username(term)} {:ok, process_from_username(term)}
true -> true ->
if is_global_search(args) do if global_search?(args) do
service = GlobalSearch.service() service = GlobalSearch.service()
{:ok, service.search_groups(Keyword.new(args, fn {k, v} -> {k, v} end))} {:ok, service.search_groups(Keyword.new(args, fn {k, v} -> {k, v} end))}
@ -75,7 +75,7 @@ defmodule Mobilizon.GraphQL.API.Search do
def search_events(%{term: term} = args, page \\ 1, limit \\ 10) do def search_events(%{term: term} = args, page \\ 1, limit \\ 10) do
term = String.trim(term) term = String.trim(term)
if is_url(term) do if url?(term) do
# skip, if it's not an event # skip, if it's not an event
case process_from_url(term) do case process_from_url(term) do
%Page{total: _total, elements: [%Event{} = event]} = page -> %Page{total: _total, elements: [%Event{} = event]} = page ->
@ -89,7 +89,7 @@ defmodule Mobilizon.GraphQL.API.Search do
{:ok, %{total: 0, elements: []}} {:ok, %{total: 0, elements: []}}
end end
else else
if is_global_search(args) do if global_search?(args) do
service = GlobalSearch.service() service = GlobalSearch.service()
{:ok, service.search_events(Keyword.new(args, fn {k, v} -> {k, v} end))} {:ok, service.search_events(Keyword.new(args, fn {k, v} -> {k, v} end))}
@ -140,17 +140,17 @@ defmodule Mobilizon.GraphQL.API.Search do
end end
end end
@spec is_url(String.t()) :: boolean @spec url?(String.t()) :: boolean
defp is_url(search), do: String.starts_with?(search, ["http://", "https://"]) defp url?(search), do: String.starts_with?(search, ["http://", "https://"])
@spec is_handle(String.t()) :: boolean @spec handle?(String.t()) :: boolean
defp is_handle(search), do: String.match?(search, ~r/@/) defp handle?(search), do: String.match?(search, ~r/@/)
defp is_global_search(%{search_target: :global}) do defp global_search?(%{search_target: :global}) do
global_search_enabled?() global_search_enabled?()
end end
defp is_global_search(_), do: global_search_enabled?() && global_search_default?() defp global_search?(_), do: global_search_enabled?() && global_search_default?()
defp global_search_enabled? do defp global_search_enabled? do
Application.get_env(:mobilizon, :search) |> get_in([:global]) |> get_in([:is_enabled]) Application.get_env(:mobilizon, :search) |> get_in([:global]) |> get_in([:is_enabled])

View file

@ -68,6 +68,6 @@ defmodule Mobilizon.GraphQL.API.Utils do
@spec check_actor_owns_media?(integer() | String.t(), integer() | String.t()) :: boolean() @spec check_actor_owns_media?(integer() | String.t(), integer() | String.t()) :: boolean()
defp check_actor_owns_media?(actor_id, media_actor_id) do defp check_actor_owns_media?(actor_id, media_actor_id) do
actor_id == media_actor_id || Mobilizon.Actors.is_member?(media_actor_id, actor_id) actor_id == media_actor_id || Mobilizon.Actors.member?(media_actor_id, actor_id)
end end
end end

View file

@ -5,8 +5,8 @@ defmodule Mobilizon.GraphQL.Error do
require Logger require Logger
alias __MODULE__ alias __MODULE__
alias Mobilizon.Web.Gettext, as: GettextBackend
import Mobilizon.Web.Gettext, only: [dgettext: 2] import Mobilizon.Web.Gettext, only: [dgettext: 2]
import Mobilizon.Storage.Ecto, only: [convert_ecto_errors: 1]
@type t :: %{code: atom(), message: String.t(), status_code: pos_integer(), field: atom()} @type t :: %{code: atom(), message: String.t(), status_code: pos_integer(), field: atom()}
@ -64,7 +64,7 @@ defmodule Mobilizon.GraphQL.Error do
defp handle(%Ecto.Changeset{} = changeset) do defp handle(%Ecto.Changeset{} = changeset) do
changeset changeset
|> Ecto.Changeset.traverse_errors(&translate_error/1) |> convert_ecto_errors()
|> Enum.map(fn {k, v} -> |> Enum.map(fn {k, v} ->
%Error{ %Error{
code: :validation, code: :validation,
@ -126,27 +126,4 @@ defmodule Mobilizon.GraphQL.Error do
Logger.warning("Unhandled error code: #{inspect(code)}") Logger.warning("Unhandled error code: #{inspect(code)}")
{422, to_string(code)} {422, to_string(code)}
end end
# Translates an error message using gettext.
defp translate_error({msg, opts}) do
# Because error messages were defined within Ecto, we must
# call the Gettext module passing our Gettext backend. We
# also use the "errors" domain as translations are placed
# in the errors.po file.
# Ecto will pass the :count keyword if the error message is
# meant to be pluralized.
# On your own code and templates, depending on whether you
# need the message to be pluralized or not, this could be
# written simply as:
#
# dngettext "errors", "1 file", "%{count} files", count
# dgettext "errors", "is invalid"
#
if count = opts[:count] do
Gettext.dngettext(GettextBackend, "errors", msg, msg, count, opts)
else
Gettext.dgettext(GettextBackend, "errors", msg, opts)
end
end
end end

View file

@ -18,7 +18,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Activity do
def group_activity(%Actor{type: :Group, id: group_id}, %{page: page, limit: limit} = args, %{ def group_activity(%Actor{type: :Group, id: group_id}, %{page: page, limit: limit} = args, %{
context: %{current_user: %User{role: role}, current_actor: %Actor{id: actor_id}} context: %{current_user: %User{role: role}, current_actor: %Actor{id: actor_id}}
}) do }) do
if Actors.is_member?(actor_id, group_id) or is_moderator(role) do if Actors.member?(actor_id, group_id) or is_moderator(role) do
%Page{total: total, elements: elements} = %Page{total: total, elements: elements} =
Activities.list_group_activities_for_member( Activities.list_group_activities_for_member(
group_id, group_id,

View file

@ -26,7 +26,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Conversation do
} }
) )
when not is_nil(attributed_to_id) do when not is_nil(attributed_to_id) do
if Actors.is_member?(actor_id, attributed_to_id) do if Actors.member?(actor_id, attributed_to_id) do
{:ok, {:ok,
event_id event_id
|> Conversations.find_conversations_for_event(attributed_to_id, page, limit) |> Conversations.find_conversations_for_event(attributed_to_id, page, limit)
@ -103,7 +103,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Conversation do
{:error, :not_found} {:error, :not_found}
%ConversationParticipant{actor_id: actor_id} = conversation_participant -> %ConversationParticipant{actor_id: actor_id} = conversation_participant ->
if actor_id == performing_actor_id or Actors.is_member?(performing_actor_id, actor_id) do if actor_id == performing_actor_id or Actors.member?(performing_actor_id, actor_id) do
{:ok, conversation_participant_to_view(conversation_participant)} {:ok, conversation_participant_to_view(conversation_participant)}
else else
{:error, :not_found} {:error, :not_found}
@ -121,7 +121,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Conversation do
} }
) do ) do
if conversation_actor_id == performing_actor_id or if conversation_actor_id == performing_actor_id or
Actors.is_member?(performing_actor_id, conversation_actor_id) do Actors.member?(performing_actor_id, conversation_actor_id) do
{:ok, {:ok,
Mobilizon.Discussions.get_comments_in_reply_to_comment_id(origin_comment_id, page, limit)} Mobilizon.Discussions.get_comments_in_reply_to_comment_id(origin_comment_id, page, limit)}
else else
@ -184,7 +184,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Conversation do
{:valid_actor, true} <- {:valid_actor, true} <-
{:valid_actor, {:valid_actor,
actor_id == current_actor_id or actor_id == current_actor_id or
Actors.is_member?(current_actor_id, actor_id)}, Actors.member?(current_actor_id, actor_id)},
{:ok, %ConversationParticipant{} = conversation_participant} <- {:ok, %ConversationParticipant{} = conversation_participant} <-
Conversations.update_conversation_participant(conversation_participant, %{ Conversations.update_conversation_participant(conversation_participant, %{
unread: !read unread: !read
@ -269,7 +269,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Conversation do
to_string(current_actor_id) in participant_ids or to_string(current_actor_id) in participant_ids or
Enum.any?(participant_ids, fn participant_id -> Enum.any?(participant_ids, fn participant_id ->
Actors.is_member?(current_actor_id, participant_id) and Actors.member?(current_actor_id, participant_id) and
attributed_to_id == participant_id attributed_to_id == participant_id
end) end)
end end

View file

@ -23,7 +23,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Discussion do
} }
} }
) do ) do
with {:member, true} <- {:member, Actors.is_member?(actor_id, group_id)}, with {:member, true} <- {:member, Actors.member?(actor_id, group_id)},
{:ok, %Actor{type: :Group} = group} <- Actors.get_group_by_actor_id(group_id) do {:ok, %Actor{type: :Group} = group} <- Actors.get_group_by_actor_id(group_id) do
{:ok, Discussions.find_discussions_for_actor(group, page, limit)} {:ok, Discussions.find_discussions_for_actor(group, page, limit)}
else else
@ -45,7 +45,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Discussion do
}) do }) do
case Discussions.get_discussion(id) do case Discussions.get_discussion(id) do
%Discussion{actor_id: actor_id} = discussion -> %Discussion{actor_id: actor_id} = discussion ->
if Actors.is_member?(creator_id, actor_id) do if Actors.member?(creator_id, actor_id) do
{:ok, discussion} {:ok, discussion}
else else
{:error, :unauthorized} {:error, :unauthorized}
@ -63,7 +63,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Discussion do
}) do }) do
with %Discussion{actor_id: actor_id} = discussion <- with %Discussion{actor_id: actor_id} = discussion <-
Discussions.get_discussion_by_slug(slug), Discussions.get_discussion_by_slug(slug),
{:member, true} <- {:member, Actors.is_member?(creator_id, actor_id)} do {:member, true} <- {:member, Actors.member?(creator_id, actor_id)} do
{:ok, discussion} {:ok, discussion}
else else
nil -> {:error, dgettext("errors", "Discussion not found")} nil -> {:error, dgettext("errors", "Discussion not found")}
@ -105,7 +105,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Discussion do
} }
} }
) do ) do
if Actors.is_member?(creator_id, group_id) do if Actors.member?(creator_id, group_id) do
case Comments.create_discussion(%{ case Comments.create_discussion(%{
title: title, title: title,
text: text, text: text,
@ -150,7 +150,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Discussion do
} }
} = _discussion} <- } = _discussion} <-
{:no_discussion, Discussions.get_discussion(discussion_id)}, {:no_discussion, Discussions.get_discussion(discussion_id)},
{:member, true} <- {:member, Actors.is_member?(creator_id, actor_id)}, {:member, true} <- {:member, Actors.member?(creator_id, actor_id)},
{:ok, _activity, %Discussion{} = discussion} <- {:ok, _activity, %Discussion{} = discussion} <-
Comments.create_discussion(%{ Comments.create_discussion(%{
text: text, text: text,
@ -183,7 +183,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Discussion do
) do ) do
with {:no_discussion, %Discussion{actor_id: actor_id} = discussion} <- with {:no_discussion, %Discussion{actor_id: actor_id} = discussion} <-
{:no_discussion, Discussions.get_discussion(discussion_id)}, {:no_discussion, Discussions.get_discussion(discussion_id)},
{:member, true} <- {:member, Actors.is_member?(creator_id, actor_id)}, {:member, true} <- {:member, Actors.member?(creator_id, actor_id)},
{:ok, _activity, %Discussion{} = discussion} <- {:ok, _activity, %Discussion{} = discussion} <-
Actions.Update.update( Actions.Update.update(
discussion, discussion,
@ -213,7 +213,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Discussion do
}) do }) do
with {:no_discussion, %Discussion{actor_id: actor_id} = discussion} <- with {:no_discussion, %Discussion{actor_id: actor_id} = discussion} <-
{:no_discussion, Discussions.get_discussion(discussion_id)}, {:no_discussion, Discussions.get_discussion(discussion_id)},
{:member, true} <- {:member, Actors.is_member?(creator_id, actor_id)}, {:member, true} <- {:member, Actors.member?(creator_id, actor_id)},
{:ok, _activity, %Discussion{} = discussion} <- {:ok, _activity, %Discussion{} = discussion} <-
Actions.Delete.delete(discussion, actor) do Actions.Delete.delete(discussion, actor) do
{:ok, discussion} {:ok, discussion}

View file

@ -36,7 +36,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Event do
when not is_nil(attributed_to_id) do when not is_nil(attributed_to_id) do
with %Actor{id: group_id} <- Actors.get_actor(attributed_to_id), with %Actor{id: group_id} <- Actors.get_actor(attributed_to_id),
{:member, true} <- {:member, true} <-
{:member, Actors.is_member?(actor_id, group_id) or is_moderator(user_role)}, {:member, Actors.member?(actor_id, group_id) or is_moderator(user_role)},
%Actor{} = actor <- Actors.get_actor(organizer_actor_id) do %Actor{} = actor <- Actors.get_actor(organizer_actor_id) do
{:ok, actor} {:ok, actor}
else else
@ -176,7 +176,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Event do
_args, _args,
%{context: %{current_user: %User{id: user_id} = _user}} = _resolution %{context: %{current_user: %User{id: user_id} = _user}} = _resolution
) do ) do
if Events.is_user_moderator_for_event?(user_id, event_id) do if Events.user_moderator_for_event?(user_id, event_id) do
{:ok, {:ok,
Map.put( Map.put(
stats, stats,
@ -256,7 +256,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Event do
{:can_create_event, true} <- can_create_event(args), {:can_create_event, true} <- can_create_event(args),
{:event_external, true} <- edit_event_external_checker(args), {:event_external, true} <- edit_event_external_checker(args),
{:organizer_group_member, true} <- {:organizer_group_member, true} <-
{:organizer_group_member, is_organizer_group_member?(args)}, {:organizer_group_member, organizer_group_member?(args)},
args_with_organizer <- args_with_organizer <-
args |> Map.put(:organizer_actor, organizer_actor) |> extract_timezone(user.id), args |> Map.put(:organizer_actor, organizer_actor) |> extract_timezone(user.id),
{:askismet, :ham} <- {:askismet, :ham} <-
@ -447,17 +447,17 @@ defmodule Mobilizon.GraphQL.Resolvers.Event do
end end
end end
@spec is_organizer_group_member?(map()) :: boolean() @spec organizer_group_member?(map()) :: boolean()
defp is_organizer_group_member?(%{ defp organizer_group_member?(%{
attributed_to_id: attributed_to_id, attributed_to_id: attributed_to_id,
organizer_actor_id: organizer_actor_id organizer_actor_id: organizer_actor_id
}) })
when not is_nil(attributed_to_id) do when not is_nil(attributed_to_id) do
Actors.is_member?(organizer_actor_id, attributed_to_id) && Actors.member?(organizer_actor_id, attributed_to_id) &&
Permission.can_create_group_object?(organizer_actor_id, attributed_to_id, %Event{}) Permission.can_create_group_object?(organizer_actor_id, attributed_to_id, %Event{})
end end
defp is_organizer_group_member?(_), do: true defp organizer_group_member?(_), do: true
@spec verify_profile_change(map(), Event.t(), User.t(), Actor.t()) :: {:ok, map()} @spec verify_profile_change(map(), Event.t(), User.t(), Actor.t()) :: {:ok, map()}
defp verify_profile_change( defp verify_profile_change(

View file

@ -23,7 +23,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Followers do
) do ) do
followers = group_followers(group, args) followers = group_followers(group, args)
if Actors.is_moderator?(actor_id, group_id) or is_moderator(user_role) do if Actors.moderator?(actor_id, group_id) or is_moderator(user_role) do
{:ok, followers} {:ok, followers}
else else
{:ok, %Page{followers | elements: []}} {:ok, %Page{followers | elements: []}}
@ -48,7 +48,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Followers do
with %Follower{target_actor: %Actor{type: :Group, id: group_id}} = follower <- with %Follower{target_actor: %Actor{type: :Group, id: group_id}} = follower <-
Actors.get_follower(follower_id), Actors.get_follower(follower_id),
{:member, true} <- {:member, true} <-
{:member, Actors.is_moderator?(actor_id, group_id)}, {:member, Actors.moderator?(actor_id, group_id)},
{:ok, _activity, %Follower{} = follower} <- {:ok, _activity, %Follower{} = follower} <-
(if approved do (if approved do
Actions.Accept.accept(:follow, follower) Actions.Accept.accept(:follow, follower)

View file

@ -36,7 +36,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Group do
) do ) do
case ActivityPubActor.find_or_make_group_from_nickname(name) do case ActivityPubActor.find_or_make_group_from_nickname(name) do
{:ok, %Actor{id: group_id, suspended: false} = group} -> {:ok, %Actor{id: group_id, suspended: false} = group} ->
if Actors.is_member?(actor_id, group_id) do if Actors.member?(actor_id, group_id) do
{:ok, group} {:ok, group}
else else
find_group(parent, args, nil) find_group(parent, args, nil)
@ -72,7 +72,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Group do
} }
}) do }) do
with %Actor{suspended: false, id: group_id} = group <- Actors.get_actor_with_preload(id), with %Actor{suspended: false, id: group_id} = group <- Actors.get_actor_with_preload(id),
true <- Actors.is_member?(actor_id, group_id) do true <- Actors.member?(actor_id, group_id) do
{:ok, group} {:ok, group}
else else
_ -> _ ->
@ -215,7 +215,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Group do
} }
} }
) do ) do
if Actors.is_administrator?(updater_actor.id, group_id) do if Actors.administrator?(updater_actor.id, group_id) do
args = Map.put(args, :updater_actor, updater_actor) args = Map.put(args, :updater_actor, updater_actor)
case save_attached_pictures(args) do case save_attached_pictures(args) do
@ -265,7 +265,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Group do
) do ) do
with {:ok, %Actor{} = group} <- Actors.get_group_by_actor_id(group_id), with {:ok, %Actor{} = group} <- Actors.get_group_by_actor_id(group_id),
{:ok, %Member{} = member} <- Actors.get_member(actor_id, group.id), {:ok, %Member{} = member} <- Actors.get_member(actor_id, group.id),
{:is_admin, true} <- {:is_admin, Member.is_administrator(member)}, {:is_admin, true} <- {:is_admin, Member.administrator?(member)},
{:ok, _activity, group} <- Actions.Delete.delete(group, actor, true) do {:ok, _activity, group} <- Actions.Delete.delete(group, actor, true) do
{:ok, %{id: group.id}} {:ok, %{id: group.id}}
else else
@ -448,7 +448,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Group do
} }
} }
) do ) do
if Actors.is_member?(actor_id, group_id) do if Actors.member?(actor_id, group_id) do
{:ok, {:ok,
Events.list_organized_events_for_group( Events.list_organized_events_for_group(
group, group,

View file

@ -26,7 +26,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Member do
context: %{current_user: %User{role: user_role}, current_actor: %Actor{id: actor_id}} context: %{current_user: %User{role: user_role}, current_actor: %Actor{id: actor_id}}
} = _resolution } = _resolution
) do ) do
if Actors.is_member?(actor_id, group_id) or is_moderator(user_role) do if Actors.member?(actor_id, group_id) or is_moderator(user_role) do
roles = roles =
case roles do case roles do
"" -> "" ->

View file

@ -384,7 +384,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Participant do
with {:member, true} <- with {:member, true} <-
{:member, {:member,
to_string(current_actor_id) == to_string(actor_id) or to_string(current_actor_id) == to_string(actor_id) or
Actors.is_member?(current_actor_id, actor_id)}, Actors.member?(current_actor_id, actor_id)},
{:ok, _activity, %Conversation{} = conversation} <- Comments.create_conversation(args) do {:ok, _activity, %Conversation{} = conversation} <- Comments.create_conversation(args) do
{:ok, conversation_to_view(conversation, Actors.get_actor(actor_id))} {:ok, conversation_to_view(conversation, Actors.get_actor(actor_id))}
else else

View file

@ -32,7 +32,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Post do
} }
} = _resolution } = _resolution
) do ) do
if Actors.is_member?(actor_id, group_id) or is_moderator(user_role) do if Actors.member?(actor_id, group_id) or is_moderator(user_role) do
%Page{} = page = Posts.get_posts_for_group(group, page, limit) %Page{} = page = Posts.get_posts_for_group(group, page, limit)
{:ok, page} {:ok, page}
else else
@ -111,7 +111,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Post do
} }
} = _resolution } = _resolution
) do ) do
with {:member, true} <- {:member, Actors.is_member?(actor_id, group_id)}, with {:member, true} <- {:member, Actors.member?(actor_id, group_id)},
%Actor{} = group <- Actors.get_actor(group_id), %Actor{} = group <- Actors.get_actor(group_id),
args <- args <-
Map.update(args, :picture, nil, fn picture -> Map.update(args, :picture, nil, fn picture ->
@ -160,7 +160,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Post do
process_picture(picture, group) process_picture(picture, group)
end), end),
args <- extract_pictures_from_post_body(args, actor_id), args <- extract_pictures_from_post_body(args, actor_id),
{:member, true} <- {:member, Actors.is_member?(actor_id, group_id)}, {:member, true} <- {:member, Actors.member?(actor_id, group_id)},
{:ok, _, %Post{} = post} <- {:ok, _, %Post{} = post} <-
Actions.Update.update(post, args, true, %{"actor" => actor_url}) do Actions.Update.update(post, args, true, %{"actor" => actor_url}) do
{:ok, post} {:ok, post}
@ -194,7 +194,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Post do
with {:uuid, {:ok, _uuid}} <- {:uuid, Ecto.UUID.cast(post_id)}, with {:uuid, {:ok, _uuid}} <- {:uuid, Ecto.UUID.cast(post_id)},
{:post, %Post{attributed_to: %Actor{id: group_id}} = post} <- {:post, %Post{attributed_to: %Actor{id: group_id}} = post} <-
{:post, Posts.get_post_with_preloads(post_id)}, {:post, Posts.get_post_with_preloads(post_id)},
{:member, true} <- {:member, Actors.is_member?(actor_id, group_id)}, {:member, true} <- {:member, Actors.member?(actor_id, group_id)},
{:ok, _, %Post{} = post} <- {:ok, _, %Post{} = post} <-
Actions.Delete.delete(post, actor) do Actions.Delete.delete(post, actor) do
{:ok, post} {:ok, post}

View file

@ -32,7 +32,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Resource do
} }
} = _resolution } = _resolution
) do ) do
with {:member, true} <- {:member, Actors.is_member?(actor_id, group_id)}, with {:member, true} <- {:member, Actors.member?(actor_id, group_id)},
%Page{} = page <- Resources.get_resources_for_group(group, page, limit) do %Page{} = page <- Resources.get_resources_for_group(group, page, limit) do
{:ok, page} {:ok, page}
else else
@ -60,7 +60,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Resource do
} }
} = _resolution } = _resolution
) do ) do
with {:member, true} <- {:member, Actors.is_member?(actor_id, group_id)}, with {:member, true} <- {:member, Actors.member?(actor_id, group_id)},
%Page{} = page <- Resources.get_resources_for_folder(parent, page, limit) do %Page{} = page <- Resources.get_resources_for_folder(parent, page, limit) do
{:ok, page} {:ok, page}
end end
@ -83,7 +83,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Resource do
Logger.debug("Getting resource for group with username #{username}") Logger.debug("Getting resource for group with username #{username}")
with {:group, %Actor{id: group_id}} <- {:group, Actors.get_actor_by_name(username, :Group)}, with {:group, %Actor{id: group_id}} <- {:group, Actors.get_actor_by_name(username, :Group)},
{:member, true} <- {:member, Actors.is_member?(actor_id, group_id)}, {:member, true} <- {:member, Actors.member?(actor_id, group_id)},
{:resource, %Resource{} = resource} <- {:resource, %Resource{} = resource} <-
{:resource, Resources.get_resource_by_group_and_path_with_preloads(group_id, path)} do {:resource, Resources.get_resource_by_group_and_path_with_preloads(group_id, path)} do
{:ok, resource} {:ok, resource}
@ -109,7 +109,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Resource do
} }
} = _resolution } = _resolution
) do ) do
if Actors.is_member?(actor_id, group_id) do if Actors.member?(actor_id, group_id) do
parent = get_eventual_parent(args) parent = get_eventual_parent(args)
if check_resource_owned_by_group(parent, group_id) do if check_resource_owned_by_group(parent, group_id) do
@ -155,7 +155,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Resource do
) do ) do
case Resources.get_resource_with_preloads(resource_id) do case Resources.get_resource_with_preloads(resource_id) do
%Resource{actor_id: group_id} = resource -> %Resource{actor_id: group_id} = resource ->
if Actors.is_member?(actor_id, group_id) do if Actors.member?(actor_id, group_id) do
case Actions.Update.update(resource, args, true, %{"actor" => actor_url}) do case Actions.Update.update(resource, args, true, %{"actor" => actor_url}) do
{:ok, _, %Resource{} = resource} -> {:ok, _, %Resource{} = resource} ->
{:ok, resource} {:ok, resource}
@ -192,7 +192,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Resource do
) do ) do
with {:resource, %Resource{parent_id: _parent_id, actor_id: group_id} = resource} <- with {:resource, %Resource{parent_id: _parent_id, actor_id: group_id} = resource} <-
{:resource, Resources.get_resource_with_preloads(resource_id)}, {:resource, Resources.get_resource_with_preloads(resource_id)},
{:member, true} <- {:member, Actors.is_member?(actor_id, group_id)}, {:member, true} <- {:member, Actors.member?(actor_id, group_id)},
{:ok, _, %Resource{} = resource} <- {:ok, _, %Resource{} = resource} <-
Actions.Delete.delete(resource, actor) do Actions.Delete.delete(resource, actor) do
{:ok, resource} {:ok, resource}

View file

@ -26,7 +26,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Todos do
context: %{current_actor: %Actor{id: actor_id}} context: %{current_actor: %Actor{id: actor_id}}
} = _resolution } = _resolution
) do ) do
with {:member, true} <- {:member, Actors.is_member?(actor_id, group_id)}, with {:member, true} <- {:member, Actors.member?(actor_id, group_id)},
%Page{} = page <- Todos.get_todo_lists_for_group(group, page, limit) do %Page{} = page <- Todos.get_todo_lists_for_group(group, page, limit) do
{:ok, page} {:ok, page}
else else
@ -50,7 +50,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Todos do
context: %{current_actor: %Actor{id: actor_id}} context: %{current_actor: %Actor{id: actor_id}}
} = _resolution } = _resolution
) do ) do
with {:member, true} <- {:member, Actors.is_member?(actor_id, group_id)}, with {:member, true} <- {:member, Actors.member?(actor_id, group_id)},
%Page{} = page <- Todos.get_todos_for_todo_list(todo_list, page, limit) do %Page{} = page <- Todos.get_todos_for_todo_list(todo_list, page, limit) do
{:ok, page} {:ok, page}
else else
@ -70,7 +70,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Todos do
) do ) do
with {:todo, %TodoList{actor_id: group_id} = todo} <- with {:todo, %TodoList{actor_id: group_id} = todo} <-
{:todo, Todos.get_todo_list(todo_list_id)}, {:todo, Todos.get_todo_list(todo_list_id)},
{:member, true} <- {:member, Actors.is_member?(actor_id, group_id)} do {:member, true} <- {:member, Actors.member?(actor_id, group_id)} do
{:ok, todo} {:ok, todo}
else else
{:todo, nil} -> {:todo, nil} ->
@ -93,7 +93,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Todos do
context: %{current_actor: %Actor{id: actor_id}} context: %{current_actor: %Actor{id: actor_id}}
} = _resolution } = _resolution
) do ) do
with {:member, true} <- {:member, Actors.is_member?(actor_id, group_id)}, with {:member, true} <- {:member, Actors.member?(actor_id, group_id)},
{:ok, _, %TodoList{} = todo_list} <- {:ok, _, %TodoList{} = todo_list} <-
Actions.Create.create( Actions.Create.create(
:todo_list, :todo_list,
@ -121,7 +121,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Todos do
# with {:is_owned, %Actor{} = actor} <- User.owns_actor(user, actor_id), # with {:is_owned, %Actor{} = actor} <- User.owns_actor(user, actor_id),
# {:todo_list, %TodoList{actor_id: group_id} = todo_list} <- # {:todo_list, %TodoList{actor_id: group_id} = todo_list} <-
# {:todo_list, Todos.get_todo_list(todo_list_id)}, # {:todo_list, Todos.get_todo_list(todo_list_id)},
# {:member, true} <- {:member, Actors.is_member?(actor_id, group_id)}, # {:member, true} <- {:member, Actors.member?(actor_id, group_id)},
# {:ok, _, %TodoList{} = todo} <- # {:ok, _, %TodoList{} = todo} <-
# Actions.Update.update_todo_list(todo_list, actor, true, %{}) do # Actions.Update.update_todo_list(todo_list, actor, true, %{}) do
# {:ok, todo} # {:ok, todo}
@ -144,7 +144,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Todos do
# with {:is_owned, %Actor{} = actor} <- User.owns_actor(user, actor_id), # with {:is_owned, %Actor{} = actor} <- User.owns_actor(user, actor_id),
# {:todo_list, %TodoList{actor_id: group_id} = todo_list} <- # {:todo_list, %TodoList{actor_id: group_id} = todo_list} <-
# {:todo_list, Todos.get_todo_list(todo_list_id)}, # {:todo_list, Todos.get_todo_list(todo_list_id)},
# {:member, true} <- {:member, Actors.is_member?(actor_id, group_id)}, # {:member, true} <- {:member, Actors.member?(actor_id, group_id)},
# {:ok, _, %TodoList{} = todo} <- # {:ok, _, %TodoList{} = todo} <-
# Actions.Delete.delete_todo_list(todo_list, actor, true, %{}) do # Actions.Delete.delete_todo_list(todo_list, actor, true, %{}) do
# {:ok, todo} # {:ok, todo}
@ -169,7 +169,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Todos do
{:todo, Todos.get_todo(todo_id)}, {:todo, Todos.get_todo(todo_id)},
{:todo_list, %TodoList{actor_id: group_id}} <- {:todo_list, %TodoList{actor_id: group_id}} <-
{:todo_list, Todos.get_todo_list(todo_list_id)}, {:todo_list, Todos.get_todo_list(todo_list_id)},
{:member, true} <- {:member, Actors.is_member?(actor_id, group_id)} do {:member, true} <- {:member, Actors.member?(actor_id, group_id)} do
{:ok, todo} {:ok, todo}
else else
{:todo, nil} -> {:todo, nil} ->
@ -194,7 +194,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Todos do
) do ) do
with {:todo_list, %TodoList{actor_id: group_id} = _todo_list} <- with {:todo_list, %TodoList{actor_id: group_id} = _todo_list} <-
{:todo_list, Todos.get_todo_list(todo_list_id)}, {:todo_list, Todos.get_todo_list(todo_list_id)},
{:member, true} <- {:member, Actors.is_member?(actor_id, group_id)}, {:member, true} <- {:member, Actors.member?(actor_id, group_id)},
{:ok, _, %Todo{} = todo} <- {:ok, _, %Todo{} = todo} <-
Actions.Create.create( Actions.Create.create(
:todo, :todo,
@ -228,7 +228,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Todos do
{:todo, Todos.get_todo(todo_id)}, {:todo, Todos.get_todo(todo_id)},
{:todo_list, %TodoList{actor_id: group_id}} <- {:todo_list, %TodoList{actor_id: group_id}} <-
{:todo_list, Todos.get_todo_list(todo_list_id)}, {:todo_list, Todos.get_todo_list(todo_list_id)},
{:member, true} <- {:member, Actors.is_member?(actor_id, group_id)}, {:member, true} <- {:member, Actors.member?(actor_id, group_id)},
{:ok, _, %Todo{} = todo} <- {:ok, _, %Todo{} = todo} <-
Actions.Update.update(todo, args, true, %{}) do Actions.Update.update(todo, args, true, %{}) do
{:ok, todo} {:ok, todo}
@ -259,7 +259,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Todos do
# {:todo, Todos.get_todo(todo_id)}, # {:todo, Todos.get_todo(todo_id)},
# {:todo_list, %TodoList{actor_id: group_id}} <- # {:todo_list, %TodoList{actor_id: group_id}} <-
# {:todo_list, Todos.get_todo_list(todo_list_id)}, # {:todo_list, Todos.get_todo_list(todo_list_id)},
# {:member, true} <- {:member, Actors.is_member?(actor_id, group_id)}, # {:member, true} <- {:member, Actors.member?(actor_id, group_id)},
# {:ok, _, %Todo{} = todo} <- # {:ok, _, %Todo{} = todo} <-
# Actions.Delete.delete_todo(todo, actor, true, %{}) do # Actions.Delete.delete_todo(todo, actor, true, %{}) do
# {:ok, todo} # {:ok, todo}

View file

@ -209,8 +209,8 @@ defmodule Mobilizon.Actors.Actor do
@doc """ @doc """
Checks whether actor visibility is public. Checks whether actor visibility is public.
""" """
@spec is_public_visibility?(t) :: boolean @spec public_visibility?(t) :: boolean
def is_public_visibility?(%__MODULE__{visibility: visibility}) do def public_visibility?(%__MODULE__{visibility: visibility}) do
visibility in [:public, :unlisted] visibility in [:public, :unlisted]
end end

View file

@ -710,8 +710,8 @@ defmodule Mobilizon.Actors do
@doc """ @doc """
Returns whether the `actor_id` is a confirmed member for the group `parent_id` Returns whether the `actor_id` is a confirmed member for the group `parent_id`
""" """
@spec is_member?(integer | String.t(), integer | String.t()) :: boolean() @spec member?(integer | String.t(), integer | String.t()) :: boolean()
def is_member?(actor_id, parent_id) do def member?(actor_id, parent_id) do
match?( match?(
{:ok, %Member{}}, {:ok, %Member{}},
get_member(actor_id, parent_id, @member_roles) get_member(actor_id, parent_id, @member_roles)
@ -721,8 +721,8 @@ defmodule Mobilizon.Actors do
@doc """ @doc """
Returns whether the `actor_id` is a moderator for the group `parent_id` Returns whether the `actor_id` is a moderator for the group `parent_id`
""" """
@spec is_moderator?(integer | String.t(), integer | String.t()) :: boolean() @spec moderator?(integer | String.t(), integer | String.t()) :: boolean()
def is_moderator?(actor_id, parent_id) do def moderator?(actor_id, parent_id) do
match?( match?(
{:ok, %Member{}}, {:ok, %Member{}},
get_member(actor_id, parent_id, @moderator_roles) get_member(actor_id, parent_id, @moderator_roles)
@ -732,8 +732,8 @@ defmodule Mobilizon.Actors do
@doc """ @doc """
Returns whether the `actor_id` is an administrator for the group `parent_id` Returns whether the `actor_id` is an administrator for the group `parent_id`
""" """
@spec is_administrator?(integer | String.t(), integer | String.t()) :: boolean() @spec administrator?(integer | String.t(), integer | String.t()) :: boolean()
def is_administrator?(actor_id, parent_id) do def administrator?(actor_id, parent_id) do
match?( match?(
{:ok, %Member{}}, {:ok, %Member{}},
get_member(actor_id, parent_id, @administrator_roles) get_member(actor_id, parent_id, @administrator_roles)
@ -922,8 +922,8 @@ defmodule Mobilizon.Actors do
@doc """ @doc """
Returns whether the member is the last administrator for a group Returns whether the member is the last administrator for a group
""" """
@spec is_only_administrator?(integer | String.t(), integer | String.t()) :: boolean() @spec only_administrator?(integer | String.t(), integer | String.t()) :: boolean()
def is_only_administrator?(member_id, group_id) do def only_administrator?(member_id, group_id) do
Member Member
|> where( |> where(
[m], [m],

View file

@ -55,9 +55,10 @@ defmodule Mobilizon.Actors.Member do
@doc """ @doc """
Checks whether the member is an administrator (admin or creator) of the group. Checks whether the member is an administrator (admin or creator) of the group.
""" """
def is_administrator(%__MODULE__{role: :administrator}), do: true @spec administrator?(t()) :: boolean()
def is_administrator(%__MODULE__{role: :creator}), do: true def administrator?(%__MODULE__{role: :administrator}), do: true
def is_administrator(%__MODULE__{}), do: false def administrator?(%__MODULE__{role: :creator}), do: true
def administrator?(%__MODULE__{}), do: false
@doc false @doc false
@spec changeset(t | Ecto.Schema.t(), map) :: Ecto.Changeset.t() @spec changeset(t | Ecto.Schema.t(), map) :: Ecto.Changeset.t()

View file

@ -77,7 +77,7 @@ defmodule Mobilizon.Discussions do
|> join(:left, [c], r in Comment, on: r.origin_comment_id == c.id) |> join(:left, [c], r in Comment, on: r.origin_comment_id == c.id)
|> where([c, _], is_nil(c.in_reply_to_comment_id)) |> where([c, _], is_nil(c.in_reply_to_comment_id))
|> where([c], c.visibility in ^@public_visibility) |> where([c], c.visibility in ^@public_visibility)
# TODO: This was added because we don't want to count deleted comments in total_replies. # This was added because we don't want to count deleted comments in total_replies.
# However, it also excludes all top-level comments with deleted replies from being selected # However, it also excludes all top-level comments with deleted replies from being selected
# |> where([_, r], is_nil(r.deleted_at)) # |> where([_, r], is_nil(r.deleted_at))
|> group_by([c], c.id) |> group_by([c], c.id)

View file

@ -515,8 +515,8 @@ defmodule Mobilizon.Events do
|> Page.build_page(page, limit) |> Page.build_page(page, limit)
end end
@spec is_user_moderator_for_event?(integer | String.t(), integer | String.t()) :: boolean @spec user_moderator_for_event?(integer | String.t(), integer | String.t()) :: boolean
def is_user_moderator_for_event?(user_id, event_id) do def user_moderator_for_event?(user_id, event_id) do
Participant Participant
|> join(:inner, [p], a in Actor, on: p.actor_id == a.id) |> join(:inner, [p], a in Actor, on: p.actor_id == a.id)
|> where([p, _a], p.event_id == ^event_id) |> where([p, _a], p.event_id == ^event_id)
@ -1492,14 +1492,14 @@ defmodule Mobilizon.Events do
end end
@spec filter_online(Ecto.Query.t(), map()) :: Ecto.Query.t() @spec filter_online(Ecto.Query.t(), map()) :: Ecto.Query.t()
defp filter_online(query, %{type: :online}), do: is_online_fragment(query, true) defp filter_online(query, %{type: :online}), do: online_fragment_check(query, true)
defp filter_online(query, %{type: :in_person}), do: is_online_fragment(query, false) defp filter_online(query, %{type: :in_person}), do: online_fragment_check(query, false)
defp filter_online(query, _), do: query defp filter_online(query, _), do: query
@spec is_online_fragment(Ecto.Query.t(), boolean()) :: Ecto.Query.t() @spec online_fragment_check(Ecto.Query.t(), boolean()) :: Ecto.Query.t()
defp is_online_fragment(query, value) do defp online_fragment_check(query, value) do
where(query, [q], fragment("(?->>'is_online')::bool = ?", q.options, ^value)) where(query, [q], fragment("(?->>'is_online')::bool = ?", q.options, ^value))
end end

View file

@ -49,8 +49,8 @@ defmodule Mobilizon.Events.Participant do
We start by fetching the list of organizers and if there's only one of them We start by fetching the list of organizers and if there's only one of them
and that it's the actor requesting leaving the event we return true. and that it's the actor requesting leaving the event we return true.
""" """
@spec is_not_only_organizer(integer | String.t(), integer | String.t()) :: boolean @spec not_only_organizer?(integer | String.t(), integer | String.t()) :: boolean
def is_not_only_organizer(event_id, actor_id) do def not_only_organizer?(event_id, actor_id) do
case Events.list_organizers_participants_for_event(event_id) do case Events.list_organizers_participants_for_event(event_id) do
[%__MODULE__{actor: %Actor{id: participant_actor_id}}] -> [%__MODULE__{actor: %Actor{id: participant_actor_id}}] ->
participant_actor_id == actor_id participant_actor_id == actor_id

View file

@ -34,6 +34,10 @@ defmodule Mobilizon.Instances.InstanceActor do
instance_actor instance_actor
|> cast(attrs, @attrs) |> cast(attrs, @attrs)
|> validate_required(@required_attrs) |> validate_required(@required_attrs)
|> validate_length(:domain, max: 254)
|> validate_length(:instance_name, max: 254)
|> validate_length(:software, max: 254)
|> validate_length(:software_version, max: 254)
|> unique_constraint(:domain) |> unique_constraint(:domain)
end end
end end

View file

@ -4,6 +4,7 @@ defmodule Mobilizon.Instances do
""" """
alias Ecto.Adapters.SQL alias Ecto.Adapters.SQL
alias Mobilizon.Actors.{Actor, Follower} alias Mobilizon.Actors.{Actor, Follower}
alias Mobilizon.Federation.ActivityPub.Relay
alias Mobilizon.Instances.{Instance, InstanceActor} alias Mobilizon.Instances.{Instance, InstanceActor}
alias Mobilizon.Storage.{Page, Repo} alias Mobilizon.Storage.{Page, Repo}
import Ecto.Query import Ecto.Query
@ -22,11 +23,15 @@ defmodule Mobilizon.Instances do
order_by_options = Keyword.new([{direction, order_by}]) order_by_options = Keyword.new([{direction, order_by}])
%Actor{id: relay_id} = Relay.get_actor()
query = query =
Instance Instance
|> join(:left, [i], ia in InstanceActor, on: i.domain == ia.domain) |> join(:left, [i], ia in InstanceActor, on: i.domain == ia.domain)
|> join(:left, [_i, ia], a in Actor, on: ia.actor_id == a.id) |> join(:left, [_i, ia], a in Actor, on: ia.actor_id == a.id)
# following
|> join(:left, [_i, _ia, a], f1 in Follower, on: f1.target_actor_id == a.id) |> join(:left, [_i, _ia, a], f1 in Follower, on: f1.target_actor_id == a.id)
# followed
|> join(:left, [_i, _ia, a], f2 in Follower, on: f2.actor_id == a.id) |> join(:left, [_i, _ia, a], f2 in Follower, on: f2.actor_id == a.id)
|> select([i, ia, a, f1, f2], %{ |> select([i, ia, a, f1, f2], %{
instance: i, instance: i,
@ -45,14 +50,27 @@ defmodule Mobilizon.Instances do
if is_nil(filter_domain) or filter_domain == "" do if is_nil(filter_domain) or filter_domain == "" do
query query
else else
where(query, [i], like(i.domain, ^"%#{filter_domain}%")) where(
query,
[i, ia],
like(i.domain, ^"%#{filter_domain}%") or like(ia.instance_name, ^"%#{filter_domain}%")
)
end end
query = query =
case follow_status do case follow_status do
:following -> where(query, [i, s], s.following == true) :following ->
:followed -> where(query, [i, s], s.follower == true) where(query, [_i, _ia, _a, f1], f1.actor_id == ^relay_id and f1.approved == true)
:all -> query
:followed ->
where(
query,
[_i, _ia, _a, _f1, f2],
f2.target_actor_id == ^relay_id and f2.approved == true
)
:all ->
query
end end
%Page{elements: elements} = paged_instances = Page.build_page(query, page, limit, :domain) %Page{elements: elements} = paged_instances = Page.build_page(query, page, limit, :domain)

View file

@ -214,7 +214,7 @@ defmodule Mobilizon.Medias do
query query
|> Repo.all(timeout: :infinity) |> Repo.all(timeout: :infinity)
|> Enum.filter(fn %Media{file: %File{url: url}} -> |> Enum.filter(fn %Media{file: %File{url: url}} ->
!url_is_also_a_profile_file?(url) && is_all_media_orphan?(url, expiration_date) !url_is_also_a_profile_file?(url) && all_media_orphan?(url, expiration_date)
end) end)
|> Enum.chunk_by(fn %Media{file: %File{url: url}} -> |> Enum.chunk_by(fn %Media{file: %File{url: url}} ->
url url
@ -223,14 +223,14 @@ defmodule Mobilizon.Medias do
end) end)
end end
defp is_all_media_orphan?(url, expiration_date) do defp all_media_orphan?(url, expiration_date) do
url url
|> get_all_media_by_url() |> get_all_media_by_url()
|> Enum.all?(&is_media_orphan?(&1, expiration_date)) |> Enum.all?(&media_orphan?(&1, expiration_date))
end end
@spec is_media_orphan?(Media.t(), DateTime.t()) :: boolean() @spec media_orphan?(Media.t(), DateTime.t()) :: boolean()
defp is_media_orphan?(%Media{id: media_id}, expiration_date) do defp media_orphan?(%Media{id: media_id}, expiration_date) do
media_query = media_query =
from(m in Media, from(m in Media,
as: :media, as: :media,

View file

@ -7,6 +7,7 @@ defmodule Mobilizon.Storage.Ecto do
import Ecto.Changeset, only: [fetch_change: 2, put_change: 3, get_field: 2] import Ecto.Changeset, only: [fetch_change: 2, put_change: 3, get_field: 2]
alias Ecto.{Changeset, Query} alias Ecto.{Changeset, Query}
alias Mobilizon.Web.Endpoint alias Mobilizon.Web.Endpoint
alias Mobilizon.Web.Gettext, as: GettextBackend
alias Mobilizon.Web.Router.Helpers, as: Routes alias Mobilizon.Web.Router.Helpers, as: Routes
@doc """ @doc """
@ -56,4 +57,30 @@ defmodule Mobilizon.Storage.Ecto do
changeset changeset
end end
end end
def convert_ecto_errors(%Ecto.Changeset{} = changeset),
do: Ecto.Changeset.traverse_errors(changeset, &translate_error/1)
# Translates an error message using gettext.
defp translate_error({msg, opts}) do
# Because error messages were defined within Ecto, we must
# call the Gettext module passing our Gettext backend. We
# also use the "errors" domain as translations are placed
# in the errors.po file.
# Ecto will pass the :count keyword if the error message is
# meant to be pluralized.
# On your own code and templates, depending on whether you
# need the message to be pluralized or not, this could be
# written simply as:
#
# dngettext "errors", "1 file", "%{count} files", count
# dgettext "errors", "is invalid"
#
if count = opts[:count] do
Gettext.dngettext(GettextBackend, "errors", msg, msg, count, opts)
else
Gettext.dgettext(GettextBackend, "errors", msg, opts)
end
end
end end

View file

@ -55,14 +55,14 @@ defmodule Mobilizon.Service.DateTime do
) )
end end
@spec is_first_day_of_week(Date.t(), String.t()) :: boolean() @spec first_day_of_week?(Date.t(), String.t()) :: boolean()
defp is_first_day_of_week(%Date{} = date, locale) do defp first_day_of_week?(%Date{} = date, locale) do
Date.day_of_week(date) == Cldr.Calendar.first_day_for_locale(locale) Date.day_of_week(date) == Cldr.Calendar.first_day_for_locale(locale)
end end
@spec calculate_first_day_of_week(Date.t(), String.t()) :: Date.t() @spec calculate_first_day_of_week(Date.t(), String.t()) :: Date.t()
def calculate_first_day_of_week(%Date{} = date, locale \\ "en") do def calculate_first_day_of_week(%Date{} = date, locale \\ "en") do
if is_first_day_of_week(date, locale), if first_day_of_week?(date, locale),
do: date, do: date,
else: calculate_first_day_of_week(Date.add(date, -1), locale) else: calculate_first_day_of_week(Date.add(date, -1), locale)
end end
@ -204,11 +204,11 @@ defmodule Mobilizon.Service.DateTime do
compare_to_day = Keyword.get(options, :compare_to_day, Date.utc_today()) compare_to_day = Keyword.get(options, :compare_to_day, Date.utc_today())
locale = Keyword.get(options, :locale, "en") locale = Keyword.get(options, :locale, "en")
is_first_day_of_week(compare_to_day, locale) && is_between_hours?(options) first_day_of_week?(compare_to_day, locale) && is_between_hours?(options)
end end
@spec is_delay_ok_since_last_notification_sent?(DateTime.t(), pos_integer()) :: boolean() @spec delay_ok_since_last_notification_sent?(DateTime.t(), pos_integer()) :: boolean()
def is_delay_ok_since_last_notification_sent?( def delay_ok_since_last_notification_sent?(
%DateTime{} = last_notification_sent, %DateTime{} = last_notification_sent,
delay \\ 3_600 delay \\ 3_600
) do ) do
@ -216,8 +216,8 @@ defmodule Mobilizon.Service.DateTime do
:lt :lt
end end
@spec is_same_day?(DateTime.t(), DateTime.t()) :: boolean() @spec same_day?(DateTime.t(), DateTime.t()) :: boolean()
def is_same_day?(%DateTime{} = one, %DateTime{} = two) do def same_day?(%DateTime{} = one, %DateTime{} = two) do
DateTime.to_date(one) == DateTime.to_date(two) DateTime.to_date(one) == DateTime.to_date(two)
end end

View file

@ -16,7 +16,7 @@ defmodule Mobilizon.Service.Export.Common do
def fetch_actor_event_feed(name, limit) do def fetch_actor_event_feed(name, limit) do
case Actors.get_actor_by_name(name) do case Actors.get_actor_by_name(name) do
%Actor{} = actor -> %Actor{} = actor ->
if Actor.is_public_visibility?(actor) do if Actor.public_visibility?(actor) do
%Page{elements: events} = Events.list_public_upcoming_events_for_actor(actor, 1, limit) %Page{elements: events} = Events.list_public_upcoming_events_for_actor(actor, 1, limit)
%Page{elements: posts} = Posts.get_public_posts_for_group(actor, 1, limit) %Page{elements: posts} = Posts.get_public_posts_for_group(actor, 1, limit)
{:ok, actor, events, posts} {:ok, actor, events, posts}

View file

@ -265,7 +265,7 @@ defmodule Mobilizon.Service.Export.Feed do
end end
defp clear_actor_feed(%Actor{preferred_username: preferred_username} = actor) do defp clear_actor_feed(%Actor{preferred_username: preferred_username} = actor) do
if Actor.is_public_visibility?(actor) do if Actor.public_visibility?(actor) do
Cachex.del(:feed, "actor_#{preferred_username}") Cachex.del(:feed, "actor_#{preferred_username}")
end end
end end

View file

@ -216,7 +216,7 @@ defmodule Mobilizon.Service.Export.ICalendar do
end end
defp clear_actor_feed(%Actor{preferred_username: preferred_username} = actor) do defp clear_actor_feed(%Actor{preferred_username: preferred_username} = actor) do
if Actor.is_public_visibility?(actor) do if Actor.public_visibility?(actor) do
Cachex.del(:ics, "actor_#{preferred_username}") Cachex.del(:ics, "actor_#{preferred_username}")
end end
end end

View file

@ -13,8 +13,8 @@ defmodule Mobilizon.Service.HTTP.Utils do
end end
end end
@spec is_content_type?(Enum.t(), String.t() | list(String.t())) :: boolean @spec content_type_matches?(Enum.t(), String.t() | list(String.t())) :: boolean
def is_content_type?(headers, content_type) do def content_type_matches?(headers, content_type) do
headers headers
|> get_header("Content-Type") |> get_header("Content-Type")
|> content_type_header_matches(content_type) |> content_type_header_matches(content_type)

View file

@ -12,8 +12,8 @@ defmodule Mobilizon.Service.Notifier.Email do
import Mobilizon.Service.DateTime, import Mobilizon.Service.DateTime,
only: [ only: [
is_delay_ok_since_last_notification_sent?: 1, delay_ok_since_last_notification_sent?: 1,
is_delay_ok_since_last_notification_sent?: 2 delay_ok_since_last_notification_sent?: 2
] ]
require Logger require Logger
@ -129,7 +129,7 @@ defmodule Mobilizon.Service.Notifier.Email do
# Delay ok since last notification # Delay ok since last notification
defp match_group_notifications_setting(:one_hour, _, %DateTime{} = last_notification_sent, _) do defp match_group_notifications_setting(:one_hour, _, %DateTime{} = last_notification_sent, _) do
is_delay_ok_since_last_notification_sent?(last_notification_sent) delay_ok_since_last_notification_sent?(last_notification_sent)
end end
# Delay ok since last notification # Delay ok since last notification
@ -139,7 +139,7 @@ defmodule Mobilizon.Service.Notifier.Email do
%DateTime{} = last_notification_sent, %DateTime{} = last_notification_sent,
options options
) do ) do
is_delay_ok_since_last_notification_sent?(last_notification_sent, 3_600 * 23) and delay_ok_since_last_notification_sent?(last_notification_sent, 3_600 * 23) and
Keyword.get(options, :recap, false) != false Keyword.get(options, :recap, false) != false
end end
@ -149,7 +149,7 @@ defmodule Mobilizon.Service.Notifier.Email do
%DateTime{} = last_notification_sent, %DateTime{} = last_notification_sent,
options options
) do ) do
is_delay_ok_since_last_notification_sent?(last_notification_sent, 3_600 * 24 * 6) and delay_ok_since_last_notification_sent?(last_notification_sent, 3_600 * 24 * 6) and
Keyword.get(options, :recap, false) != false Keyword.get(options, :recap, false) != false
end end

View file

@ -11,9 +11,7 @@ defmodule Mobilizon.Service.RichMedia.Parser do
max_body: 2_000_000, max_body: 2_000_000,
timeout: 10_000, timeout: 10_000,
recv_timeout: 20_000, recv_timeout: 20_000,
follow_redirect: true, follow_redirect: true
# TODO: Remove me once Hackney/HTTPoison fixes their issue with TLS1.3 and OTP 23
ssl: [{:versions, [:"tlsv1.2"]}]
] ]
alias Mobilizon.Config alias Mobilizon.Config
@ -75,7 +73,7 @@ defmodule Mobilizon.Service.RichMedia.Parser do
opts: @options opts: @options
)}, )},
{:is_html, _response_headers, true} <- {:is_html, _response_headers, true} <-
{:is_html, response_headers, is_html?(response_headers)} do {:is_html, response_headers, html?(response_headers)} do
body body
|> convert_utf8(response_headers) |> convert_utf8(response_headers)
|> maybe_parse() |> maybe_parse()
@ -108,21 +106,21 @@ defmodule Mobilizon.Service.RichMedia.Parser do
defp get_data_for_media(response_headers, url) do defp get_data_for_media(response_headers, url) do
data = %{title: get_filename_from_headers(response_headers) || get_filename_from_url(url)} data = %{title: get_filename_from_headers(response_headers) || get_filename_from_url(url)}
if is_image?(response_headers) do if image?(response_headers) do
Map.put(data, :image_remote_url, url) Map.put(data, :image_remote_url, url)
else else
data data
end end
end end
@spec is_html?(Enum.t()) :: boolean @spec html?(Enum.t()) :: boolean
defp is_html?(headers) do defp html?(headers) do
is_content_type?(headers, ["text/html", "application/xhtml"]) content_type_matches?(headers, ["text/html", "application/xhtml"])
end end
@spec is_image?(Enum.t()) :: boolean @spec image?(Enum.t()) :: boolean
defp is_image?(headers) do defp image?(headers) do
is_content_type?(headers, ["image/"]) content_type_matches?(headers, ["image/"])
end end
@spec get_filename_from_headers(Enum.t()) :: String.t() | nil @spec get_filename_from_headers(Enum.t()) :: String.t() | nil

View file

@ -81,7 +81,7 @@ defmodule Mobilizon.Service.Workers.LegacyNotifierBuilder do
options options
) do ) do
mentionned_actor_ids mentionned_actor_ids
|> Enum.filter(&Actors.is_member?(&1, Keyword.fetch!(options, :group_id))) |> Enum.filter(&Actors.member?(&1, Keyword.fetch!(options, :group_id)))
|> users_from_actor_ids(Keyword.fetch!(options, :author_id)) |> users_from_actor_ids(Keyword.fetch!(options, :author_id))
end end

View file

@ -13,6 +13,7 @@ defmodule Mobilizon.Service.Workers.RefreshInstances do
alias Mobilizon.Instances.{Instance, InstanceActor} alias Mobilizon.Instances.{Instance, InstanceActor}
alias Oban.Job alias Oban.Job
require Logger require Logger
import Mobilizon.Storage.Ecto, only: [convert_ecto_errors: 1]
@impl Oban.Worker @impl Oban.Worker
@spec perform(Oban.Job.t()) :: :ok @spec perform(Oban.Job.t()) :: :ok
@ -56,6 +57,10 @@ defmodule Mobilizon.Service.Workers.RefreshInstances do
Instances.create_instance_actor(args) do Instances.create_instance_actor(args) do
Logger.info("Saved instance actor details for domain #{host}") Logger.info("Saved instance actor details for domain #{host}")
else else
{:error, %Ecto.Changeset{} = changeset} ->
Logger.error("Unable to save instance \"#{domain}\" metadata")
Logger.debug(convert_ecto_errors(changeset))
err -> err ->
Logger.error(inspect(err)) Logger.error(inspect(err))
end end

View file

@ -16,7 +16,7 @@ defmodule Mobilizon.Service.Workers.SendActivityRecapWorker do
only: [ only: [
is_between_hours?: 1, is_between_hours?: 1,
is_between_hours_on_first_day?: 1, is_between_hours_on_first_day?: 1,
is_delay_ok_since_last_notification_sent?: 1 delay_ok_since_last_notification_sent?: 1
] ]
@impl Oban.Worker @impl Oban.Worker
@ -108,7 +108,7 @@ defmodule Mobilizon.Service.Workers.SendActivityRecapWorker do
"Testing if it's less than an hour since the last time we sent an activity recap" "Testing if it's less than an hour since the last time we sent an activity recap"
) )
is_delay_ok_since_last_notification_sent?(last_notification_sent) delay_ok_since_last_notification_sent?(last_notification_sent)
end end
# If we're between notification hours # If we're between notification hours

View file

@ -173,26 +173,26 @@ defmodule Mobilizon.Web.PageController do
end end
end end
@spec is_visible?(map) :: boolean() @spec visible?(map) :: boolean()
defp is_visible?(%{visibility: v}), do: v in [:public, :unlisted] defp visible?(%{visibility: v}), do: v in [:public, :unlisted]
defp is_visible?(%Tombstone{}), do: true defp visible?(%Tombstone{}), do: true
defp is_visible?(_), do: true defp visible?(_), do: true
@spec ok_status?(cache_status) :: boolean() @spec ok_status?(cache_status) :: boolean()
defp ok_status?(status), do: status in [:ok, :commit] defp ok_status?(status), do: status in [:ok, :commit]
@typep cache_status :: :ok | :commit | :ignore @typep cache_status :: :ok | :commit | :ignore
@spec ok_status_and_is_visible?(Plug.Conn.t(), cache_status, map()) :: boolean() @spec ok_status_and_visible?(Plug.Conn.t(), cache_status, map()) :: boolean()
defp ok_status_and_is_visible?(_conn, status, o), defp ok_status_and_visible?(_conn, status, o),
do: ok_status?(status) and is_visible?(o) do: ok_status?(status) and visible?(o)
defp checks?(conn, status, o) do defp checks?(conn, status, o) do
cond do cond do
ok_status_and_is_visible?(conn, status, o) -> ok_status_and_visible?(conn, status, o) ->
if is_local?(o) == :remote && get_format(conn) == "activity-json", do: :remote, else: true if local?(o) == :remote && get_format(conn) == "activity-json", do: :remote, else: true
is_person?(o) && get_format(conn) == "activity-json" -> person?(o) && get_format(conn) == "activity-json" ->
true true
true -> true ->
@ -200,9 +200,9 @@ defmodule Mobilizon.Web.PageController do
end end
end end
@spec is_local?(map()) :: boolean | :remote @spec local?(map()) :: boolean | :remote
defp is_local?(%{local: local}), do: if(local, do: true, else: :remote) defp local?(%{local: local}), do: if(local, do: true, else: :remote)
defp is_local?(_), do: false defp local?(_), do: false
@spec maybe_add_noindex_header(Plug.Conn.t(), map()) :: Plug.Conn.t() @spec maybe_add_noindex_header(Plug.Conn.t(), map()) :: Plug.Conn.t()
defp maybe_add_noindex_header(conn, %{visibility: visibility}) defp maybe_add_noindex_header(conn, %{visibility: visibility})
@ -212,9 +212,9 @@ defmodule Mobilizon.Web.PageController do
defp maybe_add_noindex_header(conn, _), do: conn defp maybe_add_noindex_header(conn, _), do: conn
@spec is_person?(Actor.t()) :: boolean() @spec person?(Actor.t()) :: boolean()
defp is_person?(%Actor{type: :Person}), do: true defp person?(%Actor{type: :Person}), do: true
defp is_person?(_), do: false defp person?(_), do: false
defp maybe_add_content_type_header(conn) do defp maybe_add_content_type_header(conn) do
case get_format(conn) do case get_format(conn) do

View file

@ -87,8 +87,6 @@ defmodule Mobilizon.Web.Email.Group do
end end
end end
# TODO : def send_confirmation_to_inviter()
@member_roles [:administrator, :moderator, :member] @member_roles [:administrator, :moderator, :member]
@spec send_group_suspension_notification(Member.t()) :: :ok @spec send_group_suspension_notification(Member.t()) :: :ok
def send_group_suspension_notification(%Member{actor: %Actor{user_id: nil}}), do: :ok def send_group_suspension_notification(%Member{actor: %Actor{user_id: nil}}), do: :ok

View file

@ -124,6 +124,4 @@ defmodule Mobilizon.Web.Email.Member do
:ok :ok
end end
end end
# TODO : def send_confirmation_to_inviter()
end end

View file

@ -71,13 +71,7 @@ defmodule Mobilizon.Web.MediaProxy do
@compile {:no_warn_undefined, {:crypto, :mac, 4}} @compile {:no_warn_undefined, {:crypto, :mac, 4}}
@compile {:no_warn_undefined, {:crypto, :hmac, 3}} @compile {:no_warn_undefined, {:crypto, :hmac, 3}}
defp sha_hmac(key, url) do defp sha_hmac(key, url) do
# :crypto.hmac was removed in OTP24, but :crypto.mac was added in OTP 22.1
# TODO: Remove me when we don't support OTP 21/22 anymore
if function_exported?(:crypto, :mac, 4) do
:crypto.mac(:hmac, :sha, key, url) :crypto.mac(:hmac, :sha, key, url)
else
:crypto.hmac(:sha, key, url)
end
end end
@spec filename(String.t()) :: String.t() | nil @spec filename(String.t()) :: String.t() | nil

View file

@ -6,7 +6,7 @@
timezone: @timezone, timezone: @timezone,
locale: @locale locale: @locale
) %> ) %>
<% is_same_day?(@start_date, @end_date) -> %> <% same_day?(@start_date, @end_date) -> %>
<strong> <strong>
<%= gettext("On %{date} from %{start_time} to %{end_time}", <%= gettext("On %{date} from %{start_time} to %{end_time}",
date: datetime_to_date_string(@start_date, @locale), date: datetime_to_date_string(@start_date, @locale),

View file

@ -1 +1 @@
<%= cond do %><% @end_date == nil -> %><%= render("date/event_tz_date.text", date: @start_date, event: @event, timezone: @timezone, locale: @locale) %><% is_same_day?(@start_date, @end_date) -> %><%= gettext "On %{date} from %{start_time} to %{end_time}", date: datetime_to_date_string(@start_date, @locale), start_time: datetime_to_time_string(@start_date, @locale), end_time: datetime_to_time_string(@end_date, @locale) %><%= if @event.options.timezone != @timezone do %> <%= gettext "🌐 %{timezone} %{offset}", timezone: @event.options.timezone, offset: Cldr.DateTime.Formatter.zone_gmt(@start_date) %><% end %><% true -> %><%= gettext "From the %{start} to the %{end}", start: datetime_to_string(@start_date, @locale, :short), end: datetime_to_string(@end_date, @locale, :short) %><%= if @event.options.timezone != @timezone do %> <%= gettext "🌐 %{timezone} %{offset}", timezone: @event.options.timezone, offset: Cldr.DateTime.Formatter.zone_gmt(@start_date) %><% end %><% end %> <%= cond do %><% @end_date == nil -> %><%= render("date/event_tz_date.text", date: @start_date, event: @event, timezone: @timezone, locale: @locale) %><% same_day?(@start_date, @end_date) -> %><%= gettext "On %{date} from %{start_time} to %{end_time}", date: datetime_to_date_string(@start_date, @locale), start_time: datetime_to_time_string(@start_date, @locale), end_time: datetime_to_time_string(@end_date, @locale) %><%= if @event.options.timezone != @timezone do %> <%= gettext "🌐 %{timezone} %{offset}", timezone: @event.options.timezone, offset: Cldr.DateTime.Formatter.zone_gmt(@start_date) %><% end %><% true -> %><%= gettext "From the %{start} to the %{end}", start: datetime_to_string(@start_date, @locale, :short), end: datetime_to_string(@end_date, @locale, :short) %><%= if @event.options.timezone != @timezone do %> <%= gettext "🌐 %{timezone} %{offset}", timezone: @event.options.timezone, offset: Cldr.DateTime.Formatter.zone_gmt(@start_date) %><% end %><% end %>

View file

@ -15,7 +15,7 @@
document.documentElement.classList.remove('dark') document.documentElement.classList.remove('dark')
} }
</script> </script>
<%= if is_root(assigns) do %> <%= if root?(assigns) do %>
<link rel="preload" href="/img/shape-1.svg" as="image" /> <link rel="preload" href="/img/shape-1.svg" as="image" />
<link rel="preload" href="/img/shape-2.svg" as="image" /> <link rel="preload" href="/img/shape-2.svg" as="image" />
<link rel="preload" href="/img/shape-3.svg" as="image" /> <link rel="preload" href="/img/shape-3.svg" as="image" />

View file

@ -25,7 +25,7 @@ defmodule Mobilizon.Web.EmailView do
defdelegate datetime_tz_convert(datetime, timezone), to: DateTimeRenderer defdelegate datetime_tz_convert(datetime, timezone), to: DateTimeRenderer
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 same_day?(one, two), to: DateTimeRenderer
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

View file

@ -87,8 +87,8 @@ defmodule Mobilizon.Web.PageView do
assigns |> Map.get(:locale, "en") |> get_language_direction() assigns |> Map.get(:locale, "en") |> get_language_direction()
end end
@spec is_root(map()) :: boolean() @spec root?(map()) :: boolean()
def is_root(assigns) do def root?(assigns) do
assigns |> Map.get(:conn, %{request_path: "/"}) |> Map.get(:request_path, "/") == "/" assigns |> Map.get(:conn, %{request_path: "/"}) |> Map.get(:request_path, "/") == "/"
end end
end end

View file

@ -21,6 +21,10 @@
"**/*.{js,ts,vue}": [ "**/*.{js,ts,vue}": [
"eslint --fix", "eslint --fix",
"prettier --write" "prettier --write"
],
"**/*.{ex,exs,eex,heex}": [
"mix format",
"mix credo"
] ]
}, },
"dependencies": { "dependencies": {

View file

@ -8,15 +8,15 @@
## to merge POT files into PO files. ## to merge POT files into PO files.
msgid "" msgid ""
msgstr "" msgstr ""
"PO-Revision-Date: 2022-12-19 04:14+0000\n" "PO-Revision-Date: 2024-01-19 09:47+0000\n"
"Last-Translator: Kristoffer Grundström <swedishsailfishosuser@tutanota.com>\n" "Last-Translator: drkfrd <drkfrd@users.noreply.weblate.framasoft.org>\n"
"Language-Team: Swedish <https://weblate.framasoft.org/projects/mobilizon/" "Language-Team: Swedish <https://weblate.framasoft.org/projects/mobilizon/"
"activity/sv/>\n" "activity/sv/>\n"
"Language: sv\n" "Language: sv\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n" "Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 4.14.1\n" "X-Generator: Weblate 5.3.1\n"
#: lib/web/templates/email/activity/_member_activity_item.html.heex:14 #: lib/web/templates/email/activity/_member_activity_item.html.heex:14
#: lib/web/templates/email/activity/_member_activity_item.text.eex:12 #: lib/web/templates/email/activity/_member_activity_item.text.eex:12
@ -478,12 +478,12 @@ msgstr "%{profile} lade till medlemmen %{member}."
#: lib/web/templates/email/activity/_event_activity_item.text.eex:31 #: lib/web/templates/email/activity/_event_activity_item.text.eex:31
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "%{profile} joined your event %{event}." msgid "%{profile} joined your event %{event}."
msgstr "" msgstr "%{profile} gick med i ditt evenemang %{event}."
#: lib/web/views/email_view.ex:61 #: lib/web/views/email_view.ex:61
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "An anonymous profile" msgid "An anonymous profile"
msgstr "" msgstr "En anonym profil"
#: lib/web/templates/email/email_anonymous_activity.html.heex:107 #: lib/web/templates/email/email_anonymous_activity.html.heex:107
#: lib/web/templates/email/email_anonymous_activity.text.eex:14 #: lib/web/templates/email/email_anonymous_activity.text.eex:14
@ -510,7 +510,7 @@ msgstr "%{profile} nämnde dig i en kommentar under händelsen %{event}."
#: lib/service/activity/renderer/conversation.ex:36 #: lib/service/activity/renderer/conversation.ex:36
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "%{profile} replied to your message" msgid "%{profile} replied to your message"
msgstr "" msgstr "%{profil} svarade på ditt meddelande"
#: lib/web/templates/email/activity/_conversation_activity_item.html.heex:10 #: lib/web/templates/email/activity/_conversation_activity_item.html.heex:10
#, elixir-autogen, elixir-format, fuzzy #, elixir-autogen, elixir-format, fuzzy
@ -520,31 +520,35 @@ msgstr "%{profile} svarade på diskussionen %{discussion}."
#: lib/web/templates/email/activity/_conversation_activity_item.text.eex:6 #: lib/web/templates/email/activity/_conversation_activity_item.text.eex:6
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "%{profile} replied you in a conversation." msgid "%{profile} replied you in a conversation."
msgstr "" msgstr "%{profile} svarade dig i en konversation."
#: lib/service/activity/renderer/conversation.ex:49 #: lib/service/activity/renderer/conversation.ex:49
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "%{profile} sent a private message about event %{event}" msgid "%{profile} sent a private message about event %{event}"
msgstr "" msgstr "%{profile} skickade ett privat meddelande om händelsen %{event}"
#: lib/service/activity/renderer/conversation.ex:23 #: lib/service/activity/renderer/conversation.ex:23
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "%{profile} sent you a message" msgid "%{profile} sent you a message"
msgstr "" msgstr "%{profil} skickade ett meddelande till dig"
#: lib/web/email/activity.ex:52 #: lib/web/email/activity.ex:52
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Informations about your event %{event}" msgid "Informations about your event %{event}"
msgstr "" msgstr "Information om ditt evenemang %{event}"
#: lib/web/templates/email/email_anonymous_activity.html.heex:118 #: lib/web/templates/email/email_anonymous_activity.html.heex:118
#: lib/web/templates/email/email_anonymous_activity.text.eex:20 #: lib/web/templates/email/email_anonymous_activity.text.eex:20
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "It might give details on how to join the event, so make sure to read it appropriately." msgid "It might give details on how to join the event, so make sure to read it appropriately."
msgstr "" msgstr ""
"Den kan innehålla information om hur du går med i evenemanget, så se till "
"att läsa den ordentligt."
#: lib/web/templates/email/email_anonymous_activity.html.heex:156 #: lib/web/templates/email/email_anonymous_activity.html.heex:156
#: lib/web/templates/email/email_anonymous_activity.text.eex:28 #: lib/web/templates/email/email_anonymous_activity.text.eex:28
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "This information is sent privately to you as a person who registered for this event. Share the informations above with other people with caution." msgid "This information is sent privately to you as a person who registered for this event. Share the informations above with other people with caution."
msgstr "" msgstr ""
"Denna information skickas privat till dig som registrerat dig för detta "
"evenemang. Dela informationen ovan med andra personer med försiktighet."

View file

@ -0,0 +1,15 @@
defmodule Mobilizon.Storage.Repo.Migrations.ChangeActorInstanceDescriptionTypeToText do
use Ecto.Migration
def up do
alter table(:instance_actors) do
modify(:instance_description, :text)
end
end
def down do
alter table(:instance_actors) do
modify(:instance_description, :string)
end
end
end

View file

@ -1641,5 +1641,6 @@
"Announcements for {eventTitle}": "Announcements for {eventTitle}", "Announcements for {eventTitle}": "Announcements for {eventTitle}",
"Visit {instance_domain}": "Visit {instance_domain}", "Visit {instance_domain}": "Visit {instance_domain}",
"Software details: {software_details}": "Software details: {software_details}", "Software details: {software_details}": "Software details: {software_details}",
"Only instances with an application actor can be followed": "Only instances with an application actor can be followed" "Only instances with an application actor can be followed": "Only instances with an application actor can be followed",
"Domain or instance name": "Domain or instance name"
} }

View file

@ -1635,5 +1635,6 @@
"Announcements for {eventTitle}": "Annonces pour {eventTitle}", "Announcements for {eventTitle}": "Annonces pour {eventTitle}",
"Visit {instance_domain}": "Visiter {instance_domain}", "Visit {instance_domain}": "Visiter {instance_domain}",
"Software details: {software_details}": "Détails du logiciel : {software_details}", "Software details: {software_details}": "Détails du logiciel : {software_details}",
"Only instances with an application actor can be followed": "Seules les instances avec un acteur application peuvent être suivies" "Only instances with an application actor can be followed": "Seules les instances avec un acteur application peuvent être suivies",
"Domain or instance name": "Domaine ou nom de l'instance"
} }

View file

@ -47,6 +47,17 @@
"Accept": "Elfogadás", "Accept": "Elfogadás",
"Accept follow": "Követés elfogadása", "Accept follow": "Követés elfogadása",
"Accepted": "Elfogadva", "Accepted": "Elfogadva",
"Access followed groups": "Követett csoportok elérése",
"Access group activities": "Csoport tevékenységeinek elérése",
"Access group discussions": "Csoport témáinak elérése",
"Access group events": "Csoport eseményeinek elérése",
"Access group followers": "Csoport követőinek elérése",
"Access group members": "Csoport tagjainak elérése",
"Access group memberships": "Csoporttagságok elérése",
"Access group todo-lists": "Csoport teendőlistáinak elérése",
"Access organized events": "Szervezett események elérése",
"Access participations": "Résztvevők elérése",
"Access your group's resources": "Csoport bejegyzéseinek elérése",
"Accessibility": "Akadálymentesítés", "Accessibility": "Akadálymentesítés",
"Accessible only by link": "Csak hivatkozáson keresztül érhető el", "Accessible only by link": "Csak hivatkozáson keresztül érhető el",
"Accessible only to members": "Csak tagoknak érhető el", "Accessible only to members": "Csak tagoknak érhető el",
@ -99,6 +110,7 @@
"An event from one of my groups has been published": "Az egyik csoportomtól származó esemény közzé lett téve", "An event from one of my groups has been published": "Az egyik csoportomtól származó esemény közzé lett téve",
"An event from one of my groups has been updated or deleted": "Az egyik csoportomtól származó esemény frissítve vagy törölve lett", "An event from one of my groups has been updated or deleted": "Az egyik csoportomtól származó esemény frissítve vagy törölve lett",
"An instance is an installed version of the Mobilizon software running on a server. An instance can be run by anyone using the {mobilizon_software} or other federated apps, aka the “fediverse”. This instance's name is {instance_name}. Mobilizon is a federated network of multiple instances (just like email servers), users registered on different instances may communicate even though they didn't register on the same instance.": "A példány a kiszolgálón futó Mobilizon szoftvernek egy telepített verziója. Egy példányt bárki futtathat a {mobilizon_software} vagy egyéb föderált alkalmazások (vagy más néven „födiverzum”) használatával. Ennek a példánynak a neve {instance_name}. A Mobilizon több példány föderált hálózata (hasonlóan a levelezési kiszolgálókhoz). A különböző példányokon regisztrált felhasználók még akkor is kommunikálhatnak egymással, ha nem regisztráltak ugyanarra a példányra.", "An instance is an installed version of the Mobilizon software running on a server. An instance can be run by anyone using the {mobilizon_software} or other federated apps, aka the “fediverse”. This instance's name is {instance_name}. Mobilizon is a federated network of multiple instances (just like email servers), users registered on different instances may communicate even though they didn't register on the same instance.": "A példány a kiszolgálón futó Mobilizon szoftvernek egy telepített verziója. Egy példányt bárki futtathat a {mobilizon_software} vagy egyéb föderált alkalmazások (vagy más néven „födiverzum”) használatával. Ennek a példánynak a neve {instance_name}. A Mobilizon több példány föderált hálózata (hasonlóan a levelezési kiszolgálókhoz). A különböző példányokon regisztrált felhasználók még akkor is kommunikálhatnak egymással, ha nem regisztráltak ugyanarra a példányra.",
"An “application programming interface” or “API” is a communication protocol that allows software components to communicate with each other. The Mobilizon API, for example, can allow third-party software tools to communicate with Mobilizon instances to carry out certain actions, such as posting events, automatically and remotely.": "Egy „alkalmazásprogramozási interfész” vagy „API” egy kommunikációs protokoll, amellyel a szoftverösszetevők kommunikálhatnak egymással. A Mobilizon API-val, például, harmadik féltől származó szoftvereszközök is kommunikálhatnak a Mobilizon példányokkal, és így végrehajthatnak bizonyos műveleteket, mint az események automatikus létrehozása távolról.",
"And {number} comments": "és {number} hozzászólást tettek közzé", "And {number} comments": "és {number} hozzászólást tettek közzé",
"Announcements and mentions notifications are always sent straight away.": "A közlemények és az említések értesítései mindig azonnal elküldésre kerülnek.", "Announcements and mentions notifications are always sent straight away.": "A közlemények és az említések értesítései mindig azonnal elküldésre kerülnek.",
"Anonymous participant": "Névtelen részvétel", "Anonymous participant": "Névtelen részvétel",
@ -112,8 +124,11 @@
"Anyone can request being a member, but an administrator needs to approve the membership.": "Bárki kérheti, hogy tag lehessen, de egy adminisztrátornak jóvá kell hagynia a tagságot.", "Anyone can request being a member, but an administrator needs to approve the membership.": "Bárki kérheti, hogy tag lehessen, de egy adminisztrátornak jóvá kell hagynia a tagságot.",
"Anyone wanting to be a member from your group will be able to from your group page.": "Bárki, aki a csoportja tagja szeretne lenni, a csoportja oldaláról lesz képes csatlakozni.", "Anyone wanting to be a member from your group will be able to from your group page.": "Bárki, aki a csoportja tagja szeretne lenni, a csoportja oldaláról lesz képes csatlakozni.",
"Application": "Alkalmazás", "Application": "Alkalmazás",
"Application not found": "Az alkalmazás nem található",
"Application was revoked": "Az alkalmazás vissza lett vonva",
"Apply filters": "Szűrők alkalmazása", "Apply filters": "Szűrők alkalmazása",
"Approve member": "Tag jóváhagyása", "Approve member": "Tag jóváhagyása",
"Apps": "Alkalmazások",
"Are you really sure you want to delete your whole account? You'll lose everything. Identities, settings, events created, messages and participations will be gone forever.": "Egészen biztos abban, hogy a teljes fiókot törölni szeretné? Mindent el fog veszíteni! A személyazonosságok, a beállítások, a létrehozott események, az üzenetek és a részvételek örökre eltűnnek.", "Are you really sure you want to delete your whole account? You'll lose everything. Identities, settings, events created, messages and participations will be gone forever.": "Egészen biztos abban, hogy a teljes fiókot törölni szeretné? Mindent el fog veszíteni! A személyazonosságok, a beállítások, a létrehozott események, az üzenetek és a részvételek örökre eltűnnek.",
"Are you sure you want to <b>completely delete</b> this group? All members - including remote ones - will be notified and removed from the group, and <b>all of the group data (events, posts, discussions, todos…) will be irretrievably destroyed</b>.": "Biztosan <b>teljesen törölni</b> szeretné ezt a csoportot? Az összes tag beleértve a távoliakat is értesítve lesz, és eltávolításra kerül a csoportból, valamint <b>az összes csoportadat (események, bejegyzések, megbeszélések, tennivalók…) visszavonhatatlanul meg lesznek semmisítve</b>.", "Are you sure you want to <b>completely delete</b> this group? All members - including remote ones - will be notified and removed from the group, and <b>all of the group data (events, posts, discussions, todos…) will be irretrievably destroyed</b>.": "Biztosan <b>teljesen törölni</b> szeretné ezt a csoportot? Az összes tag beleértve a távoliakat is értesítve lesz, és eltávolításra kerül a csoportból, valamint <b>az összes csoportadat (események, bejegyzések, megbeszélések, tennivalók…) visszavonhatatlanul meg lesznek semmisítve</b>.",
"Are you sure you want to <b>delete</b> this comment? This action cannot be undone.": "Biztosan <b>törölni</b> szeretné ezt a hozzászólást? Ezt a műveletet nem lehet visszavonni.", "Are you sure you want to <b>delete</b> this comment? This action cannot be undone.": "Biztosan <b>törölni</b> szeretné ezt a hozzászólást? Ezt a műveletet nem lehet visszavonni.",
@ -131,7 +146,11 @@
"Ask your instance admin to {enable_feature}.": "Kérje meg a példány adminisztrátorát, hogy {enable_feature}.", "Ask your instance admin to {enable_feature}.": "Kérje meg a példány adminisztrátorát, hogy {enable_feature}.",
"Assigned to": "Hozzárendelve ehhez", "Assigned to": "Hozzárendelve ehhez",
"Atom feed for events and posts": "Atom hírforrás az eseményekhez és a bejegyzésekhez", "Atom feed for events and posts": "Atom hírforrás az eseményekhez és a bejegyzésekhez",
"Attending": "Részvétel", "Attending": "Részt vesz",
"Authorize": "Engedélyezés",
"Authorize application": "Alkalmazás engedélyezése",
"Authorized on {authorization_date}": "Engedélyezve: {authorization_date}",
"Autorize this application to access your account?": "Engedélyezi, hogy ez az alkalmazás hozzáférjen a fiókjához?",
"Avatar": "Profilkép", "Avatar": "Profilkép",
"Back to group list": "Vissza a csoportokhoz", "Back to group list": "Vissza a csoportokhoz",
"Back to homepage": "Vissza a kezdőlapra", "Back to homepage": "Vissza a kezdőlapra",
@ -214,6 +233,7 @@
"Confirmed: Will happen": "Megerősítve: meg fog történni", "Confirmed: Will happen": "Megerősítve: meg fog történni",
"Congratulations, your account is now created!": "Gratulálunk, a fiókja most létrejött!", "Congratulations, your account is now created!": "Gratulálunk, a fiókja most létrejött!",
"Contact": "Kapcsolat", "Contact": "Kapcsolat",
"Continue": "Folytatás",
"Continue editing": "Szerkesztés folytatása", "Continue editing": "Szerkesztés folytatása",
"Cookies and Local storage": "Sütik és helyi tároló", "Cookies and Local storage": "Sütik és helyi tároló",
"Copy URL to clipboard": "URL másolás a vágólapra", "Copy URL to clipboard": "URL másolás a vágólapra",
@ -227,6 +247,7 @@
"Create a new group": "Új csoport létrehozása", "Create a new group": "Új csoport létrehozása",
"Create a new identity": "Új személyazonosság létrehozása", "Create a new identity": "Új személyazonosság létrehozása",
"Create a new list": "Új lista létrehozása", "Create a new list": "Új lista létrehozása",
"Create a new metadata element": "Új metaadatelem létrehozása",
"Create a new profile": "Új profil létrehozása", "Create a new profile": "Új profil létrehozása",
"Create a pad": "Dokumentum létrehozása", "Create a pad": "Dokumentum létrehozása",
"Create a videoconference": "Videokonferencia létrehozása", "Create a videoconference": "Videokonferencia létrehozása",
@ -234,11 +255,14 @@
"Create discussion": "Megbeszélés létrehozása", "Create discussion": "Megbeszélés létrehozása",
"Create event": "Esemény létrehozása", "Create event": "Esemény létrehozása",
"Create group": "Csoport létrehozása", "Create group": "Csoport létrehozása",
"Create group discussions": "Csoport témáinak létrehozása",
"Create group resources": "Csoport bejegyzéseinek létrehozása",
"Create identity": "Személyazonosság létrehozása", "Create identity": "Személyazonosság létrehozása",
"Create my event": "Saját esemény létrehozása", "Create my event": "Saját esemény létrehozása",
"Create my group": "Saját csoport létrehozása", "Create my group": "Saját csoport létrehozása",
"Create my profile": "Saját profil létrehozása", "Create my profile": "Saját profil létrehozása",
"Create new links": "Új hivatkozások létrehozása", "Create new links": "Új hivatkozások létrehozása",
"Create new profiles": "Új profilok létrehozása",
"Create resource": "Erőforrás létrehozása", "Create resource": "Erőforrás létrehozása",
"Create the discussion": "A megbeszélés létrehozása", "Create the discussion": "A megbeszélés létrehozása",
"Create to-do lists for all the tasks you need to do, assign them and set due dates.": "Hozzon létre tennivalólistákat az összes elvégzendő feladathoz, rendelje hozzá őket, és állítson be a határidőket.", "Create to-do lists for all the tasks you need to do, assign them and set due dates.": "Hozzon létre tennivalólistákat az összes elvégzendő feladathoz, rendelje hozzá őket, és állítson be a határidőket.",
@ -265,13 +289,19 @@
"Default Mobilizon terms": "Alapértelmezett Mobilizon használati feltételek", "Default Mobilizon terms": "Alapértelmezett Mobilizon használati feltételek",
"Delete": "Törlés", "Delete": "Törlés",
"Delete account": "Fiók törlése", "Delete account": "Fiók törlése",
"Delete comments": "Hozzászólások törlése",
"Delete conversation": "Beszélgetés törlése", "Delete conversation": "Beszélgetés törlése",
"Delete discussion": "Megbeszélés törlése", "Delete discussion": "Megbeszélés törlése",
"Delete event": "Esemény törlése", "Delete event": "Esemény törlése",
"Delete events": "Események törlése",
"Delete everything": "Minden törlése", "Delete everything": "Minden törlése",
"Delete group": "Csoport törlése", "Delete group": "Csoport törlése",
"Delete group discussions": "Csoport témáinak törlése",
"Delete group posts": "Csoport bejegyzéseinek törlése",
"Delete group resources": "Csoport erőforrásainak törlése",
"Delete my account": "Saját fiók törlése", "Delete my account": "Saját fiók törlése",
"Delete post": "Bejegyzés törlése", "Delete post": "Bejegyzés törlése",
"Delete profiles": "Profilok törlése",
"Delete this discussion": "A megbeszélés törlése", "Delete this discussion": "A megbeszélés törlése",
"Delete this identity": "A személyazonosság törlése", "Delete this identity": "A személyazonosság törlése",
"Delete your identity": "Az Ön személyazonosságának törlése", "Delete your identity": "Az Ön személyazonosságának törlése",
@ -285,6 +315,7 @@
"Describe your event": "Írja le az eseményt", "Describe your event": "Írja le az eseményt",
"Description": "Leírás", "Description": "Leírás",
"Details": "Részletek", "Details": "Részletek",
"Device activation": "Eszköz aktiválása",
"Didn't receive the instructions?": "Nem kapta meg az utasításokat?", "Didn't receive the instructions?": "Nem kapta meg az utasításokat?",
"Disabled": "Letiltva", "Disabled": "Letiltva",
"Discussions": "Megbeszélések", "Discussions": "Megbeszélések",
@ -323,6 +354,7 @@
"Emails usually don't contain capitals, make sure you haven't made a typo.": "Az e-mail-címek általában nem tartalmaznak nagybetűt. Győződjön meg arról, hogy nem írta-e el.", "Emails usually don't contain capitals, make sure you haven't made a typo.": "Az e-mail-címek általában nem tartalmaznak nagybetűt. Győződjön meg arról, hogy nem írta-e el.",
"Enabled": "Engedélyezve", "Enabled": "Engedélyezve",
"Ends on…": "Befejeződik…", "Ends on…": "Befejeződik…",
"Enter the code displayed on your device": "Adja meg az eszközén megjelenített kódot",
"Enter the link URL": "Adja meg a hivatkozás URL-ét", "Enter the link URL": "Adja meg a hivatkozás URL-ét",
"Enter your email address below, and we'll email you instructions on how to change your password.": "Adja meg lent az e-mail-címét, és elküldjük e-mailben az utasításokat, hogy hogyan változtathatja meg a jelszavát.", "Enter your email address below, and we'll email you instructions on how to change your password.": "Adja meg lent az e-mail-címét, és elküldjük e-mailben az utasításokat, hogy hogyan változtathatja meg a jelszavát.",
"Enter your own privacy policy. HTML tags allowed. The {mobilizon_privacy_policy} is provided as template.": "Adja meg a saját adatvédelmi irányelveit. A HTML címkék engedélyezettek. A {mobilizon_privacy_policy} meg van adva sablonként.", "Enter your own privacy policy. HTML tags allowed. The {mobilizon_privacy_policy} is provided as template.": "Adja meg a saját adatvédelmi irányelveit. A HTML címkék engedélyezettek. A {mobilizon_privacy_policy} meg van adva sablonként.",
@ -537,6 +569,7 @@
"Last published events": "Legutóbb közzétett események", "Last published events": "Legutóbb közzétett események",
"Last seen on": "Legutóbb látva:", "Last seen on": "Legutóbb látva:",
"Last sign-in": "Utolsó bejelentkezés", "Last sign-in": "Utolsó bejelentkezés",
"Last used on {last_used_date}": "Legutóbb használva: {last_used_date}",
"Last week": "Múlt hét", "Last week": "Múlt hét",
"Latest posts": "Legutóbbi bejegyzések", "Latest posts": "Legutóbbi bejegyzések",
"Learn more": "Tudjon meg többet", "Learn more": "Tudjon meg többet",
@ -572,7 +605,12 @@
"Login status": "Bejelentkezési állapot", "Login status": "Bejelentkezési állapot",
"Main languages you/your moderators speak": "Fő nyelvek, amelyeken Ön vagy a moderátorai beszélnek", "Main languages you/your moderators speak": "Fő nyelvek, amelyeken Ön vagy a moderátorai beszélnek",
"Make sure that all words are spelled correctly.": "Győződjön meg róla, hogy minden szót helyesen írt le.", "Make sure that all words are spelled correctly.": "Győződjön meg róla, hogy minden szót helyesen írt le.",
"Manage activity settings": "Tevékenységbeállítások kezelése",
"Manage event participations": "Eseményrészvételek kezelése",
"Manage group members": "Csoport tagjainak kezelése",
"Manage group memberships": "Csoporttagságok kezelése",
"Manage participations": "Részvételek kezelése", "Manage participations": "Részvételek kezelése",
"Manage push notification settings": "Leküldéses értesítések beállításainak kezelése",
"Manually approve new followers": "Új követők kézi jóváhagyása", "Manually approve new followers": "Új követők kézi jóváhagyása",
"Manually invite new members": "Új tagok meghívása kézzel", "Manually invite new members": "Új tagok meghívása kézzel",
"Map": "Térkép", "Map": "Térkép",
@ -603,6 +641,7 @@
"Moderation log": "Moderálási napló", "Moderation log": "Moderálási napló",
"Moderation logs": "Moderálási naplók", "Moderation logs": "Moderálási naplók",
"Moderator": "Moderátor", "Moderator": "Moderátor",
"Modify all of your account's data": "Az összes fiókadatának módosítása",
"More options": "További lehetőségek", "More options": "További lehetőségek",
"Most recently published": "Legújabban közzétéve", "Most recently published": "Legújabban közzétéve",
"Move": "Áthelyezés", "Move": "Áthelyezés",
@ -617,6 +656,7 @@
"NOTE! The default terms have not been checked over by a lawyer and thus are unlikely to provide full legal protection for all situations for an instance admin using them. They are also not specific to all countries and jurisdictions. If you are unsure, please check with a lawyer.": "MEGJEGYZÉS! Az alapértelmezett használati feltételek nem lettek jogász által ellenőrizve, és ennélfogva nem valószínű, hogy minden helyzetben teljes jogi védelmet biztosít az azt használó példány adminisztrátorának. Továbbá nem tér ki az összes országra és igazságszolgáltatásra. Ha nem biztos a dolgában, akkor ellenőriztesse egy jogásszal.", "NOTE! The default terms have not been checked over by a lawyer and thus are unlikely to provide full legal protection for all situations for an instance admin using them. They are also not specific to all countries and jurisdictions. If you are unsure, please check with a lawyer.": "MEGJEGYZÉS! Az alapértelmezett használati feltételek nem lettek jogász által ellenőrizve, és ennélfogva nem valószínű, hogy minden helyzetben teljes jogi védelmet biztosít az azt használó példány adminisztrátorának. Továbbá nem tér ki az összes országra és igazságszolgáltatásra. Ha nem biztos a dolgában, akkor ellenőriztesse egy jogásszal.",
"Name": "Név", "Name": "Név",
"Navigated to {pageTitle}": "Navigálva ide: {pageTitle}", "Navigated to {pageTitle}": "Navigálva ide: {pageTitle}",
"Never used": "Sosem volt használva",
"New discussion": "Új megbeszélés", "New discussion": "Új megbeszélés",
"New email": "Új e-mail", "New email": "Új e-mail",
"New folder": "Új mappa", "New folder": "Új mappa",
@ -786,6 +826,7 @@
"Post a comment": "Hozzászólás beküldése", "Post a comment": "Hozzászólás beküldése",
"Post a reply": "Válasz beküldése", "Post a reply": "Válasz beküldése",
"Post body": "Bejegyzés törzse", "Post body": "Bejegyzés törzse",
"Post comments": "Hozzászólások közzététele",
"Post {eventTitle} reported": "A(z) {eventTitle} bejegyzés jelentve", "Post {eventTitle} reported": "A(z) {eventTitle} bejegyzés jelentve",
"Postal Code": "Irányítószám", "Postal Code": "Irányítószám",
"Posts": "Bejegyzések", "Posts": "Bejegyzések",
@ -816,6 +857,8 @@
"Public preview": "Nyilvános előnézet", "Public preview": "Nyilvános előnézet",
"Publication date": "Közzététel dátuma", "Publication date": "Közzététel dátuma",
"Publish": "Közzététel", "Publish": "Közzététel",
"Publish events": "Események közzététele",
"Publish group posts": "Csoport bejegyzéseinek közzététele",
"Published by {name}": "Közzétette: {name}", "Published by {name}": "Közzétette: {name}",
"Published events with <b>{comments}</b> comments and <b>{participations}</b> confirmed participations": "Közzétett események <b>{comments}</b> hozzászólással és <b>{participations}</b> megerősített részvétellel", "Published events with <b>{comments}</b> comments and <b>{participations}</b> confirmed participations": "Közzétett események <b>{comments}</b> hozzászólással és <b>{participations}</b> megerősített részvétellel",
"Published events with {comments} comments and {participations} confirmed participations": "Események közzétéve {comments} hozzászólással és {participations} megerősített résztvevővel", "Published events with {comments} comments and {participations} confirmed participations": "Események közzétéve {comments} hozzászólással és {participations} megerősített résztvevővel",
@ -823,6 +866,7 @@
"Quote": "Idézet", "Quote": "Idézet",
"RSS/Atom Feed": "RSS/Atom hírforrás", "RSS/Atom Feed": "RSS/Atom hírforrás",
"Radius": "Sugár", "Radius": "Sugár",
"Read all of your account's data": "Az összes fiókadatának olvasása",
"Recap every week": "Rövid összegzés minden héten", "Recap every week": "Rövid összegzés minden héten",
"Receive one email for each activity": "Egy e-mail fogadása minden tevékenységnél", "Receive one email for each activity": "Egy e-mail fogadása minden tevékenységnél",
"Receive one email per request": "Egy levél fogadása kérésenként", "Receive one email per request": "Egy levél fogadása kérésenként",
@ -848,6 +892,7 @@
"Remember my participation in this browser": "Emlékezzen a részvételemre ebben a böngészőben", "Remember my participation in this browser": "Emlékezzen a részvételemre ebben a böngészőben",
"Remove": "Eltávolítás", "Remove": "Eltávolítás",
"Remove link": "Hivatkozás eltávolítása", "Remove link": "Hivatkozás eltávolítása",
"Remove uploaded media": "Feltöltött média eltávolítása",
"Rename": "Átnevezés", "Rename": "Átnevezés",
"Rename resource": "Erőforrás átnevezése", "Rename resource": "Erőforrás átnevezése",
"Reopen": "Újranyitás", "Reopen": "Újranyitás",
@ -855,6 +900,9 @@
"Reply": "Válasz", "Reply": "Válasz",
"Report": "Jelentés", "Report": "Jelentés",
"Report #{reportNumber}": "#{reportNumber} jelentés", "Report #{reportNumber}": "#{reportNumber} jelentés",
"Report as ham": "Jelentés nem kéretlenként",
"Report as spam": "Jelentés kéretlenként",
"Report as undetected spam": "Jelentés nem észlelt kéretlen tartalomként",
"Report reason": "Jelentés oka", "Report reason": "Jelentés oka",
"Report status": "Állapotjelentés", "Report status": "Állapotjelentés",
"Report this comment": "Hozzászólás jelentése", "Report this comment": "Hozzászólás jelentése",
@ -883,6 +931,7 @@
"Resources": "Erőforrások", "Resources": "Erőforrások",
"Restricted": "Korlátozott", "Restricted": "Korlátozott",
"Return to the group page": "Visszatérés a csoport oldalára", "Return to the group page": "Visszatérés a csoport oldalára",
"Revoke": "Visszavonás",
"Right now": "Épp most", "Right now": "Épp most",
"Role": "Szerep", "Role": "Szerep",
"Rules": "Szabályok", "Rules": "Szabályok",
@ -892,7 +941,7 @@
"Save draft": "Piszkozat mentése", "Save draft": "Piszkozat mentése",
"Schedule": "Ütemterv", "Schedule": "Ütemterv",
"Search": "Keresés", "Search": "Keresés",
"Search events, groups, etc.": "Események, csoportok stb. keresése", "Search events, groups, etc.": "Események, csoportok és egyebek keresése",
"Search target": "Cél keresése", "Search target": "Cél keresése",
"Searching…": "Keresés…", "Searching…": "Keresés…",
"Select a category": "Válasszon egy kategóriát", "Select a category": "Válasszon egy kategóriát",
@ -943,6 +992,7 @@
"Stop following instance": "Példány követésének leállítása", "Stop following instance": "Példány követésének leállítása",
"Street": "Utca", "Street": "Utca",
"Submit": "Elküldés", "Submit": "Elküldés",
"Submit to Akismet": "Beküldés az Akismetnek",
"Subtitles": "Feliratok", "Subtitles": "Feliratok",
"Suggestions:": "Javaslatok:", "Suggestions:": "Javaslatok:",
"Suspend": "Felfüggesztés", "Suspend": "Felfüggesztés",
@ -973,6 +1023,7 @@
"The actual number of participants may differ, as this event is hosted on another instance.": "A résztvevők tényleges száma eltérhet, mivel ez az esemény egy másik példányon van kiszolgálva.", "The actual number of participants may differ, as this event is hosted on another instance.": "A résztvevők tényleges száma eltérhet, mivel ez az esemény egy másik példányon van kiszolgálva.",
"The calc will be created on {service}": "A táblázat itt lesz létrehozva: {service}", "The calc will be created on {service}": "A táblázat itt lesz létrehozva: {service}",
"The content came from another server. Transfer an anonymous copy of the report?": "A tartalom egy másik kiszolgálóról érkezik. Átviszi a jelentés egy névtelen másolatát?", "The content came from another server. Transfer an anonymous copy of the report?": "A tartalom egy másik kiszolgálóról érkezik. Átviszi a jelentés egy névtelen másolatát?",
"The device code is incorrect or no longer valid.": "Az eszköz kódja érvénytelen, vagy már nem érvényes.",
"The draft event has been updated": "A piszkozatesemény frissítve lett", "The draft event has been updated": "A piszkozatesemény frissítve lett",
"The event has a sign language interpreter": "Az eseménynek van jelnyelvi tolmácsa", "The event has a sign language interpreter": "Az eseménynek van jelnyelvi tolmácsa",
"The event has been created as a draft": "Az esemény létre lett hozva piszkozatként", "The event has been created as a draft": "Az esemény létre lett hozva piszkozatként",
@ -1015,6 +1066,8 @@
"The post {post} was created by {profile}.": "A(z) {post} bejegyzést {profile} hozta létre.", "The post {post} was created by {profile}.": "A(z) {post} bejegyzést {profile} hozta létre.",
"The post {post} was deleted by {profile}.": "A(z) {post} bejegyzést {profile} törölte.", "The post {post} was deleted by {profile}.": "A(z) {post} bejegyzést {profile} törölte.",
"The post {post} was updated by {profile}.": "A(z) {post} bejegyzést {profile} frissítette.", "The post {post} was updated by {profile}.": "A(z) {post} bejegyzést {profile} frissítette.",
"The provided application was not found.": "A megadott alkalmazás nem található.",
"The report contents (eventual comments and event) and the reported profile details will be transmitted to Akismet.": "A jelentés tartalma (a hozzászólások és az esemény) és a jelentett profil részletei el lesznek küldve az Akismetnek.",
"The report will be sent to the moderators of your instance. You can explain why you report this content below.": "A jelentés el lesz küldve a példánya moderátorainak. Elmagyarázhatja alább, hogy miért jelenti ezt a tartalmat.", "The report will be sent to the moderators of your instance. You can explain why you report this content below.": "A jelentés el lesz küldve a példánya moderátorainak. Elmagyarázhatja alább, hogy miért jelenti ezt a tartalmat.",
"The selected picture is too heavy. You need to select a file smaller than {size}.": "A kiválasztott kép túl nagy. Egy {size} méretűnél kisebb fájlt kell kiválasztania.", "The selected picture is too heavy. You need to select a file smaller than {size}.": "A kiválasztott kép túl nagy. Egy {size} méretűnél kisebb fájlt kell kiválasztania.",
"The technical details of the error can help developers solve the problem more easily. Please add them to your feedback.": "A hiba műszaki részletei segítenek a fejlesztőknek, hogy könnyebben megoldják a problémát. Adja hozzá a visszajelzéséhez.", "The technical details of the error can help developers solve the problem more easily. Please add them to your feedback.": "A hiba műszaki részletei segítenek a fejlesztőknek, hogy könnyebben megoldják a problémát. Adja hozzá a visszajelzéséhez.",
@ -1033,6 +1086,15 @@
"This Mobilizon instance and this event organizer allows anonymous participations, but requires validation through email confirmation.": "Ez a Mobilizon példány és ez az eseményszervező megengedi a névtelen részvételeket, de ellenőrzés szükséges e-mailen keresztüli megerősítéssel.", "This Mobilizon instance and this event organizer allows anonymous participations, but requires validation through email confirmation.": "Ez a Mobilizon példány és ez az eseményszervező megengedi a névtelen részvételeket, de ellenőrzés szükséges e-mailen keresztüli megerősítéssel.",
"This URL doesn't seem to be valid": "Ez az URL nem tűnik érvényesnek", "This URL doesn't seem to be valid": "Ez az URL nem tűnik érvényesnek",
"This URL is not supported": "Ez az URL nem támogatott", "This URL is not supported": "Ez az URL nem támogatott",
"This application will be able to access all of your informations and post content. Make sure you only approve applications you trust.": "Ez az alkalmazás hozzá fog férni az összes információjához és a bejegyzései tartalmához. Győződjön meg arról, hogy csak azokat az alkalmazásokat engedélyezi, melyekben megbízik.",
"This application will be allowed to delete events": "Az alkalmazás törölheti az eseményeket",
"This application will be allowed to delete group posts": "Az alkalmazás törölheti a bejegyzéseket a csoportokból",
"This application will be allowed to publish events": "Az alkalmazás közzétehet eseményeket",
"This application will be allowed to publish group posts": "Az alkalmazás közzétehet bejegyzéseket a csoportokban",
"This application will be allowed to remove uploaded media": "Az alkalmazás törölheti a feltöltött médiafájlokat",
"This application will be allowed to update events": "Az alkalmazás frissítheti az eseményeket",
"This application will be allowed to update group posts": "Az alkalmazás frissítheti a bejegyzéseket a csoportokban",
"This application will be allowed to upload media": "Az alkalmazás tölthet fel médiafájlokat",
"This event has been cancelled.": "Ezt az eseményt törölték.", "This event has been cancelled.": "Ezt az eseményt törölték.",
"This event is accessible only through it's link. Be careful where you post this link.": "Ez az esemény csak a hivatkozásán keresztül érhető el. Legyen óvatos, hogy hova küldi be ezt a hivatkozást.", "This event is accessible only through it's link. Be careful where you post this link.": "Ez az esemény csak a hivatkozásán keresztül érhető el. Legyen óvatos, hogy hova küldi be ezt a hivatkozást.",
"This group doesn't have a description yet.": "Ennek a csoportnak még nincs leírása.", "This group doesn't have a description yet.": "Ennek a csoportnak még nincs leírása.",
@ -1061,7 +1123,7 @@
"This user was not found": "Ez a felhasználó nem található", "This user was not found": "Ez a felhasználó nem található",
"This website isn't moderated and the data that you enter will be automatically destroyed every day at 00:01 (Paris timezone).": "Ez a weboldal nincs moderálva, és a beírt adatok automatikusan meg lesznek semmisítve minden nap 00:01-kor (Párizs időzóna).", "This website isn't moderated and the data that you enter will be automatically destroyed every day at 00:01 (Paris timezone).": "Ez a weboldal nincs moderálva, és a beírt adatok automatikusan meg lesznek semmisítve minden nap 00:01-kor (Párizs időzóna).",
"This week": "Ez a hét", "This week": "Ez a hét",
"This weekend": "Ez a hétvége", "This weekend": "Ezen a hétvégén",
"This will delete / anonymize all content (events, comments, messages, participations…) created from this identity.": "Ez törölni vagy névteleníteni fogja az ezzel a személyazonossággal létrehozott összes tartalmat (eseményeket, hozzászólásokat, üzeneteket, részvételeket…).", "This will delete / anonymize all content (events, comments, messages, participations…) created from this identity.": "Ez törölni vagy névteleníteni fogja az ezzel a személyazonossággal létrehozott összes tartalmat (eseményeket, hozzászólásokat, üzeneteket, részvételeket…).",
"Time in your timezone ({timezone})": "Az idő az Ön időzónájában ({timezone})", "Time in your timezone ({timezone})": "Az idő az Ön időzónájában ({timezone})",
"Times in your timezone ({timezone})": "Az idők az Ön időzónájában ({timezone})", "Times in your timezone ({timezone})": "Az idők az Ön időzónájában ({timezone})",
@ -1116,12 +1178,19 @@
"Upcoming events from your groups": "Közelgő események a csoportjaitól", "Upcoming events from your groups": "Közelgő események a csoportjaitól",
"Update": "Frissítés", "Update": "Frissítés",
"Update app": "Alkalmazás frissítése", "Update app": "Alkalmazás frissítése",
"Update comments": "Hozzászólások frissítése",
"Update discussion title": "Megbeszélés címének frissítése", "Update discussion title": "Megbeszélés címének frissítése",
"Update event {name}": "A(z) {name} esemény frissítése", "Update event {name}": "A(z) {name} esemény frissítése",
"Update events": "Események frissítése",
"Update group": "Csoport frissítése", "Update group": "Csoport frissítése",
"Update group discussions": "Csoport témáinak frissítése",
"Update group posts": "Csoport bejegyzéseinek frissítése",
"Update group resources": "Csoport erőforrásainak frissítése",
"Update my event": "Saját esemény frissítése", "Update my event": "Saját esemény frissítése",
"Update post": "Bejegyzés frissítése", "Update post": "Bejegyzés frissítése",
"Update profiles": "Profilok frissítése",
"Updated": "Frissítve", "Updated": "Frissítve",
"Upload media": "Média feltöltése",
"Uploaded media size": "Feltöltött média mérete", "Uploaded media size": "Feltöltött média mérete",
"Uploaded media total size": "Feltöltött média összmérete", "Uploaded media total size": "Feltöltött média összmérete",
"Use my location": "Saját hely használata", "Use my location": "Saját hely használata",
@ -1208,6 +1277,7 @@
"You can add resources by using the button above.": "A fenti gombbal adhat hozzá erőforrásokat.", "You can add resources by using the button above.": "A fenti gombbal adhat hozzá erőforrásokat.",
"You can add tags by hitting the Enter key or by adding a comma": "Hozzáadhat címkéket az Enter billentyű lenyomásával vagy egy vessző hozzáadásával", "You can add tags by hitting the Enter key or by adding a comma": "Hozzáadhat címkéket az Enter billentyű lenyomásával vagy egy vessző hozzáadásával",
"You can pick your timezone into your preferences.": "Kiválaszthatja az időzónát a beállításaiban.", "You can pick your timezone into your preferences.": "Kiválaszthatja az időzónát a beállításaiban.",
"You can put any arbitrary content in this element. URLs will be clickable.": "Tetszőleges tartalmat tehet ebbe az elembe. A webcím kattintható lesz.",
"You can try another search term or drag and drop the marker on the map": "Megpróbálhat egy másik keresési kifejezést, vagy fogd és vidd módon tegye a jelölőt a térképre", "You can try another search term or drag and drop the marker on the map": "Megpróbálhat egy másik keresési kifejezést, vagy fogd és vidd módon tegye a jelölőt a térképre",
"You can't change your password because you are registered through {provider}.": "Nem tudja megváltoztatni a jelszavát, mert {provider} használatával regisztrált.", "You can't change your password because you are registered through {provider}.": "Nem tudja megváltoztatni a jelszavát, mert {provider} használatával regisztrált.",
"You can't use push notifications in this browser.": "Nem tudja használni a leküldéses értesítéseket ebben a böngészőben.", "You can't use push notifications in this browser.": "Nem tudja használni a leküldéses értesítéseket ebben a böngészőben.",
@ -1249,6 +1319,7 @@
"You moved the resource {resource} into {new_path}.": "Ön áthelyezte a(z) {resource} erőforrást erre a helyre: {new_path}.", "You moved the resource {resource} into {new_path}.": "Ön áthelyezte a(z) {resource} erőforrást erre a helyre: {new_path}.",
"You moved the resource {resource} to the root folder.": "Ön áthelyezte a(z) {resource} erőforrást a gyökérmappába.", "You moved the resource {resource} to the root folder.": "Ön áthelyezte a(z) {resource} erőforrást a gyökérmappába.",
"You need to login.": "Be kell jelentkeznie.", "You need to login.": "Be kell jelentkeznie.",
"You need to provide the following code to your application. It will only be valid for a few minutes.": "Meg kell adnia a következő kódot az alkalmazásának. Csak néhány percig érvényes.",
"You posted a comment on the event {event}.": "Ön hozzászólást küldött a(z) {event} eseményhez.", "You posted a comment on the event {event}.": "Ön hozzászólást küldött a(z) {event} eseményhez.",
"You promoted the member {member} to an unknown role.": "Ön előléptette {member} tagot egy ismeretlen szerepre.", "You promoted the member {member} to an unknown role.": "Ön előléptette {member} tagot egy ismeretlen szerepre.",
"You promoted {member} to administrator.": "Ön előléptette {member} tagot adminisztrátorrá.", "You promoted {member} to administrator.": "Ön előléptette {member} tagot adminisztrátorrá.",
@ -1275,6 +1346,7 @@
"You will find here all the events you have created or of which you are a participant, as well as events organized by groups you follow or are a member of.": "Itt megtalálja az összes eseményt, amelyeket létrehozott vagy amelyeknél Ön résztvevő, illetve az olyan csoportok által szervezett eseményeket, amelyeket Ön követ vagy amelyeknek tagja.", "You will find here all the events you have created or of which you are a participant, as well as events organized by groups you follow or are a member of.": "Itt megtalálja az összes eseményt, amelyeket létrehozott vagy amelyeknél Ön résztvevő, illetve az olyan csoportok által szervezett eseményeket, amelyeket Ön követ vagy amelyeknek tagja.",
"You will receive notifications about this group's public activity depending on %{notification_settings}.": "Értesítéseket fog kapni ennek a csoportnak a nyilvános tevékenységéről az %{notification_settings} függően.", "You will receive notifications about this group's public activity depending on %{notification_settings}.": "Értesítéseket fog kapni ennek a csoportnak a nyilvános tevékenységéről az %{notification_settings} függően.",
"You wish to participate to the following event": "Részt kíván venni a következő eseményen", "You wish to participate to the following event": "Részt kíván venni a következő eseményen",
"You'll be able to revoke access for this application in your account settings.": "Az alkalmazás hozzáférését a fiókbeállításokban vonhatja vissza.",
"You'll get a weekly recap every Monday for upcoming events, if you have any.": "Heti rövid összegzést fog kapni minden hétfőn a közelgő eseményekről, ha van ilyen.", "You'll get a weekly recap every Monday for upcoming events, if you have any.": "Heti rövid összegzést fog kapni minden hétfőn a közelgő eseményekről, ha van ilyen.",
"You'll need to change the URLs where there were previously entered.": "Meg kell majd változtatnia az URL-eket, ahol korábban meg lettek adva.", "You'll need to change the URLs where there were previously entered.": "Meg kell majd változtatnia az URL-eket, ahol korábban meg lettek adva.",
"You'll need to transmit the group URL so people may access the group's profile. The group won't be findable in Mobilizon's search or regular search engines.": "Át kell küldenie a csoport URL-jét, hogy az emberek hozzáférhessenek a csoport profiljához. A csoport nem lesz megtalálható a Mobilizon keresőjében vagy a szokásos keresőmotorokban.", "You'll need to transmit the group URL so people may access the group's profile. The group won't be findable in Mobilizon's search or regular search engines.": "Át kell küldenie a csoport URL-jét, hogy az emberek hozzáférhessenek a csoport profiljához. A csoport nem lesz megtalálható a Mobilizon keresőjében vagy a szokásos keresőmotorokban.",
@ -1285,6 +1357,7 @@
"Your account has been validated": "A fiókja ellenőrizve lett", "Your account has been validated": "A fiókja ellenőrizve lett",
"Your account is being validated": "A fiókja ellenőrizés alatt van", "Your account is being validated": "A fiókja ellenőrizés alatt van",
"Your account is nearly ready, {username}": "A fiókja majdnem készen, {username}", "Your account is nearly ready, {username}": "A fiókja majdnem készen, {username}",
"Your application code": "Az alkalmazáskódja",
"Your city or region and the radius will only be used to suggest you events nearby. The event radius will consider the administrative center of the area.": "A települése vagy a régiója és a sugár csak a közeli események ajánlásához lesz használva. Az esemény sugara a terület adminisztratív középpontját veszi figyelembe.", "Your city or region and the radius will only be used to suggest you events nearby. The event radius will consider the administrative center of the area.": "A települése vagy a régiója és a sugár csak a közeli események ajánlásához lesz használva. Az esemény sugara a terület adminisztratív középpontját veszi figyelembe.",
"Your current email is {email}. You use it to log in.": "A jelenlegi e-mail-címe {email}. Használja ezt a bejelentkezéshez.", "Your current email is {email}. You use it to log in.": "A jelenlegi e-mail-címe {email}. Használja ezt a bejelentkezéshez.",
"Your email": "Az e-mail címe", "Your email": "Az e-mail címe",
@ -1328,6 +1401,7 @@
"create an event": "eseményt létrehozni", "create an event": "eseményt létrehozni",
"default Mobilizon privacy policy": "alapértelmezett Mobilizon adatvédelmi irányelv", "default Mobilizon privacy policy": "alapértelmezett Mobilizon adatvédelmi irányelv",
"default Mobilizon terms": "alapértelmezett Mobilizon használati feltételek", "default Mobilizon terms": "alapértelmezett Mobilizon használati feltételek",
"detail": " ",
"e.g. 10 Rue Jangot": "például Budapest, I. kerület", "e.g. 10 Rue Jangot": "például Budapest, I. kerület",
"e.g. Accessibility, Twitch, PeerTube": "például akadálymentesítés, Twitch, PeerTube", "e.g. Accessibility, Twitch, PeerTube": "például akadálymentesítés, Twitch, PeerTube",
"e.g. Nantes, Berlin, Cork, …": "például Nantes, Berlin, Cork…", "e.g. Nantes, Berlin, Cork, …": "például Nantes, Berlin, Cork…",
@ -1351,6 +1425,7 @@
"return to the homepage": "visszatérhet a kezdőlapra", "return to the homepage": "visszatérhet a kezdőlapra",
"terms of service": "szolgáltatás feltételeit", "terms of service": "szolgáltatás feltételeit",
"tool designed to serve you": "eszköz, amelyet arra terveztek, hogy Önt szolgálja", "tool designed to serve you": "eszköz, amelyet arra terveztek, hogy Önt szolgálja",
"translation": " ",
"with another identity…": "egy másik személyazonossággal…", "with another identity…": "egy másik személyazonossággal…",
"your notification settings": "értesítési beállításaitól", "your notification settings": "értesítési beállításaitól",
"{'@'}{username}": "{'@'}{username}", "{'@'}{username}": "{'@'}{username}",

File diff suppressed because it is too large Load diff

View file

@ -54,15 +54,14 @@
> >
</o-field> </o-field>
<o-field <o-field
:label="t('Domain')" :label="t('Domain or instance name')"
label-for="domain-filter" label-for="domain-filter"
class="flex-auto" class="flex-auto"
> >
<o-input <o-input
id="domain-filter" id="domain-filter"
:placeholder="t('mobilizon-instance.tld')" :placeholder="t('mobilizon-instance.tld')"
:value="filterDomain" v-model="filterDomain"
@input="debouncedUpdateDomainFilter"
/> />
</o-field> </o-field>
</div> </div>
@ -223,7 +222,6 @@ import { Paginate } from "@/types/paginate";
import RouteName from "../../router/name"; import RouteName from "../../router/name";
import { IInstance } from "@/types/instance.model"; import { IInstance } from "@/types/instance.model";
import EmptyContent from "@/components/Utils/EmptyContent.vue"; import EmptyContent from "@/components/Utils/EmptyContent.vue";
import debounce from "lodash/debounce";
import { import {
InstanceFilterFollowStatus, InstanceFilterFollowStatus,
InstanceFollowStatus, InstanceFollowStatus,
@ -254,12 +252,16 @@ const followStatus = useRouteQuery(
const { result: instancesResult } = useQuery<{ const { result: instancesResult } = useQuery<{
instances: Paginate<IInstance>; instances: Paginate<IInstance>;
}>(INSTANCES, () => ({ }>(
INSTANCES,
() => ({
page: instancePage.value, page: instancePage.value,
limit: INSTANCES_PAGE_LIMIT, limit: INSTANCES_PAGE_LIMIT,
filterDomain: filterDomain.value, filterDomain: filterDomain.value,
filterFollowStatus: followStatus.value, filterFollowStatus: followStatus.value,
})); }),
{ debounce: 500 }
);
const instances = computed(() => instancesResult.value?.instances); const instances = computed(() => instancesResult.value?.instances);
@ -276,13 +278,6 @@ const newRelayAddress = ref("");
// relayFollowers: Paginate<IFollower> = { elements: [], total: 0 }; // relayFollowers: Paginate<IFollower> = { elements: [], total: 0 };
const updateDomainFilter = (event: InputEvent) => {
const newValue = (event.target as HTMLInputElement).value;
filterDomain.value = newValue;
};
const debouncedUpdateDomainFilter = debounce(updateDomainFilter, 500);
const hasFilter = computed((): boolean => { const hasFilter = computed((): boolean => {
return ( return (
followStatus.value !== InstanceFilterFollowStatus.ALL || followStatus.value !== InstanceFilterFollowStatus.ALL ||

View file

@ -1087,7 +1087,10 @@ const physicalAddress = computed((): Address | null => {
}); });
const ableToReport = computed((): boolean => { const ableToReport = computed((): boolean => {
return anonymousReportsConfig.value?.allowed === true; return (
currentActor.value?.id !== undefined ||
anonymousReportsConfig.value?.allowed === true
);
}); });
const organizedEvents = computed((): Paginate<IEvent> => { const organizedEvents = computed((): Paginate<IEvent> => {

View file

@ -179,7 +179,7 @@ defmodule Mix.Tasks.Mobilizon.Actors.NewTest do
assert %Actor{name: @group_name, preferred_username: @group_username, id: group_id} = assert %Actor{name: @group_name, preferred_username: @group_username, id: group_id} =
Actors.get_group_by_title(@group_username) Actors.get_group_by_title(@group_username)
assert Actors.is_administrator?(admin_id, group_id) assert Actors.administrator?(admin_id, group_id)
end end
end end
end end