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
# 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`
# to `false` below:
@ -160,6 +160,7 @@
#
{Credo.Check.Warning.LazyLogging, false},
{Credo.Check.Refactor.MapInto, false},
{Credo.Check.Warning.MissedMetadataKeyInLoggerConfig, false}
]
}
]

View file

@ -4,25 +4,25 @@ init:
setup: stop
@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:
docker-compose run --rm api mix ecto.migrate
docker compose run --rm api mix ecto.migrate
logs:
docker-compose logs -f
docker compose logs -f
start: stop
@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"
stop:
@bash docker/message.sh "Stopping Mobilizon"
docker-compose down
docker compose down
@bash docker/message.sh "Mobilizon is stopped"
test: stop
@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 test $(only)
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)
@bash docker/message.sh "Done running tests"
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 :)"
target: init

View file

@ -25,7 +25,7 @@ defmodule Mobilizon.Federation.ActivityPub.Actions.Invite do
) do
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} <-
Actors.create_member(%{
parent_id: group_id,
@ -64,8 +64,8 @@ defmodule Mobilizon.Federation.ActivityPub.Actions.Invite do
end
end
@spec is_able_to_invite?(Actor.t(), Actor.t()) :: boolean
defp is_able_to_invite?(%Actor{domain: actor_domain, id: actor_id}, %Actor{
@spec able_to_invite?(Actor.t(), Actor.t()) :: boolean
defp able_to_invite?(%Actor{domain: actor_domain, id: actor_id}, %Actor{
domain: group_domain,
id: group_id
}) do
@ -76,7 +76,7 @@ defmodule Mobilizon.Federation.ActivityPub.Actions.Invite do
# If local group, we'll send the invite
case Actors.get_member(actor_id, group_id) do
{:ok, %Member{} = admin_member} ->
Member.is_administrator(admin_member)
Member.administrator?(admin_member)
_ ->
false

View file

@ -34,7 +34,7 @@ defmodule Mobilizon.Federation.ActivityPub.Actions.Leave do
local,
additional
) 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}
else
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
{:ok, %Member{id: member_id} = member} ->
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
Mobilizon.Service.Activity.Member.insert_activity(member, subject: "member_quit")

View file

@ -44,13 +44,13 @@ defmodule Mobilizon.Federation.ActivityPub.Permission do
) do
case object |> Ownable.permissions() |> get_in([:create]) do
:member ->
Actors.is_member?(actor_id, group_id)
Actors.member?(actor_id, group_id)
:moderator ->
Actors.is_moderator?(actor_id, group_id)
Actors.moderator?(actor_id, group_id)
:administrator ->
Actors.is_administrator?(actor_id, group_id)
Actors.administrator?(actor_id, group_id)
_ ->
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}"
)
Actors.is_moderator?(actor_id, group_id)
Actors.moderator?(actor_id, group_id)
:administrator ->
Logger.debug(
"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(
"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
_ ->

View file

@ -21,10 +21,10 @@ defmodule Mobilizon.Federation.ActivityPub.Publisher do
Logger.debug("Publishing an activity")
Logger.debug(inspect(activity, pretty: true))
public = Visibility.is_public?(activity)
public = Visibility.public?(activity)
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)
Relay.publish(activity)
@ -125,9 +125,9 @@ defmodule Mobilizon.Federation.ActivityPub.Publisher do
end)
end
@spec is_create_activity?(Activity.t()) :: boolean
defp is_create_activity?(%Activity{data: %{"type" => "Create"}}), do: true
defp is_create_activity?(_), do: false
@spec create_activity?(Activity.t()) :: boolean
defp create_activity?(%Activity{data: %{"type" => "Create"}}), do: true
defp create_activity?(_), do: false
@spec convert_members_in_recipients(list(String.t())) :: {list(String.t()), list(Actor.t())}
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 |> Converter.Resource.as_to_model_data(),
{: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} <-
Actions.Create.create(:resource, object_data, false) do
{:ok, activity, resource}
@ -1005,14 +1005,14 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
end
# Comment initiates a whole discussion only if it has full title
@spec is_data_for_comment_or_discussion?(map()) :: boolean()
defp is_data_for_comment_or_discussion?(object_data) do
is_data_a_discussion_initialization?(object_data) and
@spec data_for_comment_or_discussion?(map()) :: boolean()
defp data_for_comment_or_discussion?(object_data) do
data_a_discussion_initialization?(object_data) and
is_nil(object_data.discussion_id)
end
# 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
is_nil(object_data.title) or object_data.title == ""
end
@ -1034,7 +1034,7 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
@spec transform_object_data_for_discussion(map()) :: map()
defp transform_object_data_for_discussion(object_data) do
# Basic comment
if is_data_a_discussion_initialization?(object_data) do
if data_a_discussion_initialization?(object_data) do
object_data
else
# Conversation
@ -1138,8 +1138,8 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
end
end
defp is_group_object_gone(object_id) do
Logger.debug("is_group_object_gone #{object_id}")
defp group_object_gone_check(object_id) do
Logger.debug("Checking if group object #{object_id} is gone")
case ActivityPub.fetch_object_from_url(object_id, force: true) do
# comments are just emptied
@ -1163,14 +1163,10 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
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()}
defp get_remove_object(object) do
case object |> Utils.get_url() |> ActivityPub.fetch_object_from_url() do
{:ok, %Member{actor: %Actor{id: person_id}}} -> {:ok, person_id}
{:ok, %Actor{id: person_id}} -> {:ok, person_id}
_ -> {:error, :remove_object_not_found}
end
end
@ -1196,7 +1192,7 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
@spec create_comment_or_discussion(map()) ::
{:ok, Activity.t(), struct()} | {:error, atom() | Ecto.Changeset.t()}
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")
Actions.Create.create(:comment, object_data, false)
else
@ -1248,7 +1244,7 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
end
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
{:ok, entity} ->
if Utils.origin_check_from_id?(actor_url, object_id) ||

View file

@ -93,7 +93,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Members do
atom()
) :: boolean
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
end
end

View file

@ -14,17 +14,17 @@ defmodule Mobilizon.Federation.ActivityPub.Visibility do
@public "https://www.w3.org/ns/activitystreams#Public"
@spec is_public?(Activity.t() | map()) :: boolean()
def is_public?(%{data: %{"type" => "Tombstone"}}), do: false
def is_public?(%{data: data}), do: is_public?(data)
def is_public?(%Activity{data: data}), do: is_public?(data)
@spec public?(Activity.t() | map()) :: boolean()
def public?(%{data: %{"type" => "Tombstone"}}), do: false
def public?(%{data: data}), do: 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", []))
end
def is_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?(%Comment{deleted_at: deleted_at}), do: !is_nil(deleted_at)
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), 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}
defp add_endpoints_to_model(actor, data) do
# TODO: Remove fallbacks in 3.0
endpoints = %{
members_url: get_in(data, ["endpoints", "members"]) || data["members"],
resources_url: get_in(data, ["endpoints", "resources"]) || data["resources"],
todos_url: get_in(data, ["endpoints", "todos"]) || data["todos"],
events_url: get_in(data, ["endpoints", "events"]) || data["events"],
posts_url: get_in(data, ["endpoints", "posts"]) || data["posts"],
discussions_url: get_in(data, ["endpoints", "discussions"]) || data["discussions"],
members_url: get_in(data, ["endpoints", "members"]),
resources_url: get_in(data, ["endpoints", "resources"]),
todos_url: get_in(data, ["endpoints", "todos"]),
events_url: get_in(data, ["endpoints", "events"]),
posts_url: get_in(data, ["endpoints", "posts"]),
discussions_url: get_in(data, ["endpoints", "discussions"]),
shared_inbox_url: data["endpoints"]["sharedInbox"]
}

View file

@ -62,7 +62,7 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Comment do
tags: fetch_tags(tag_object),
mentions: fetch_mentions(tag_object),
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"],
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"]),
visibility: visibility,
join_options: Map.get(object, "joinMode", "free"),
local: is_local?(object["id"]),
local: local?(object["id"]),
external_participation_url: object["externalParticipationUrl"],
options: options,
metadata: metadata,
@ -305,8 +305,8 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Event do
)
end
@spec is_local?(String.t()) :: boolean()
defp is_local?(url) do
@spec local?(String.t()) :: boolean()
defp local?(url) do
%URI{host: url_domain} = URI.parse(url)
%URI{host: local_domain} = URI.parse(Endpoint.url())
url_domain == local_domain

View file

@ -5,7 +5,7 @@ defmodule Mobilizon.Federation.NodeInfo do
alias Mobilizon.Service.HTTP.WebfingerClient
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"
@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}
defp validate_json_response(body, headers) do
cond do
!is_content_type?(headers, "application/json") ->
!content_type_matches?(headers, "application/json") ->
{:error, :bad_content_type}
!is_map(body) ->

View file

@ -25,8 +25,8 @@ defmodule Mobilizon.GraphQL.API.Search do
cond do
# Some URLs could be domain.tld/@username, so keep this condition above
# the `is_handle` function
is_url(term) ->
# the `handle?` function
url?(term) ->
# skip, if it's not an actor
case process_from_url(term) do
%Page{total: _total, elements: [%Actor{} = _actor]} = page ->
@ -36,11 +36,11 @@ defmodule Mobilizon.GraphQL.API.Search do
{:ok, %{total: 0, elements: []}}
end
is_handle(term) ->
handle?(term) ->
{:ok, process_from_username(term)}
true ->
if is_global_search(args) do
if global_search?(args) do
service = GlobalSearch.service()
{: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
term = String.trim(term)
if is_url(term) do
if url?(term) do
# skip, if it's not an event
case process_from_url(term) do
%Page{total: _total, elements: [%Event{} = event]} = page ->
@ -89,7 +89,7 @@ defmodule Mobilizon.GraphQL.API.Search do
{:ok, %{total: 0, elements: []}}
end
else
if is_global_search(args) do
if global_search?(args) do
service = GlobalSearch.service()
{: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
@spec is_url(String.t()) :: boolean
defp is_url(search), do: String.starts_with?(search, ["http://", "https://"])
@spec url?(String.t()) :: boolean
defp url?(search), do: String.starts_with?(search, ["http://", "https://"])
@spec is_handle(String.t()) :: boolean
defp is_handle(search), do: String.match?(search, ~r/@/)
@spec handle?(String.t()) :: boolean
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?()
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
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()
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

View file

@ -5,8 +5,8 @@ defmodule Mobilizon.GraphQL.Error do
require Logger
alias __MODULE__
alias Mobilizon.Web.Gettext, as: GettextBackend
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()}
@ -64,7 +64,7 @@ defmodule Mobilizon.GraphQL.Error do
defp handle(%Ecto.Changeset{} = changeset) do
changeset
|> Ecto.Changeset.traverse_errors(&translate_error/1)
|> convert_ecto_errors()
|> Enum.map(fn {k, v} ->
%Error{
code: :validation,
@ -126,27 +126,4 @@ defmodule Mobilizon.GraphQL.Error do
Logger.warning("Unhandled error code: #{inspect(code)}")
{422, to_string(code)}
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

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, %{
context: %{current_user: %User{role: role}, current_actor: %Actor{id: actor_id}}
}) 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} =
Activities.list_group_activities_for_member(
group_id,

View file

@ -26,7 +26,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Conversation 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,
event_id
|> Conversations.find_conversations_for_event(attributed_to_id, page, limit)
@ -103,7 +103,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Conversation do
{:error, :not_found}
%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)}
else
{:error, :not_found}
@ -121,7 +121,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Conversation do
}
) do
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,
Mobilizon.Discussions.get_comments_in_reply_to_comment_id(origin_comment_id, page, limit)}
else
@ -184,7 +184,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Conversation do
{:valid_actor, true} <-
{:valid_actor,
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} <-
Conversations.update_conversation_participant(conversation_participant, %{
unread: !read
@ -269,7 +269,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Conversation do
to_string(current_actor_id) in participant_ids or
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
end)
end

View file

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

View file

@ -36,7 +36,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Event do
when not is_nil(attributed_to_id) do
with %Actor{id: group_id} <- Actors.get_actor(attributed_to_id),
{: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
{:ok, actor}
else
@ -176,7 +176,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Event do
_args,
%{context: %{current_user: %User{id: user_id} = _user}} = _resolution
) 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,
Map.put(
stats,
@ -256,7 +256,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Event do
{:can_create_event, true} <- can_create_event(args),
{:event_external, true} <- edit_event_external_checker(args),
{:organizer_group_member, true} <-
{:organizer_group_member, is_organizer_group_member?(args)},
{:organizer_group_member, organizer_group_member?(args)},
args_with_organizer <-
args |> Map.put(:organizer_actor, organizer_actor) |> extract_timezone(user.id),
{:askismet, :ham} <-
@ -447,17 +447,17 @@ defmodule Mobilizon.GraphQL.Resolvers.Event do
end
end
@spec is_organizer_group_member?(map()) :: boolean()
defp is_organizer_group_member?(%{
@spec organizer_group_member?(map()) :: boolean()
defp organizer_group_member?(%{
attributed_to_id: attributed_to_id,
organizer_actor_id: organizer_actor_id
})
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{})
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()}
defp verify_profile_change(

View file

@ -23,7 +23,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Followers do
) do
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}
else
{: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 <-
Actors.get_follower(follower_id),
{:member, true} <-
{:member, Actors.is_moderator?(actor_id, group_id)},
{:member, Actors.moderator?(actor_id, group_id)},
{:ok, _activity, %Follower{} = follower} <-
(if approved do
Actions.Accept.accept(:follow, follower)

View file

@ -36,7 +36,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Group do
) do
case ActivityPubActor.find_or_make_group_from_nickname(name) do
{: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}
else
find_group(parent, args, nil)
@ -72,7 +72,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Group do
}
}) do
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}
else
_ ->
@ -215,7 +215,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Group 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)
case save_attached_pictures(args) do
@ -265,7 +265,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Group do
) do
with {:ok, %Actor{} = group} <- Actors.get_group_by_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, %{id: group.id}}
else
@ -448,7 +448,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Group do
}
}
) do
if Actors.is_member?(actor_id, group_id) do
if Actors.member?(actor_id, group_id) do
{:ok,
Events.list_organized_events_for_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}}
} = _resolution
) 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 =
case roles do
"" ->

View file

@ -384,7 +384,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Participant do
with {:member, true} <-
{:member,
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, conversation_to_view(conversation, Actors.get_actor(actor_id))}
else

View file

@ -32,7 +32,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Post do
}
} = _resolution
) 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)
{:ok, page}
else
@ -111,7 +111,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Post do
}
} = _resolution
) 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),
args <-
Map.update(args, :picture, nil, fn picture ->
@ -160,7 +160,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Post do
process_picture(picture, group)
end),
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} <-
Actions.Update.update(post, args, true, %{"actor" => actor_url}) do
{:ok, post}
@ -194,7 +194,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Post do
with {:uuid, {:ok, _uuid}} <- {:uuid, Ecto.UUID.cast(post_id)},
{:post, %Post{attributed_to: %Actor{id: group_id}} = post} <-
{: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} <-
Actions.Delete.delete(post, actor) do
{:ok, post}

View file

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

View file

@ -26,7 +26,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Todos do
context: %{current_actor: %Actor{id: actor_id}}
} = _resolution
) 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
{:ok, page}
else
@ -50,7 +50,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Todos do
context: %{current_actor: %Actor{id: actor_id}}
} = _resolution
) 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
{:ok, page}
else
@ -70,7 +70,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Todos do
) do
with {:todo, %TodoList{actor_id: group_id} = todo} <-
{: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}
else
{:todo, nil} ->
@ -93,7 +93,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Todos do
context: %{current_actor: %Actor{id: actor_id}}
} = _resolution
) 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} <-
Actions.Create.create(
:todo_list,
@ -121,7 +121,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Todos do
# with {:is_owned, %Actor{} = actor} <- User.owns_actor(user, actor_id),
# {:todo_list, %TodoList{actor_id: group_id} = todo_list} <-
# {: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} <-
# Actions.Update.update_todo_list(todo_list, actor, true, %{}) do
# {:ok, todo}
@ -144,7 +144,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Todos do
# with {:is_owned, %Actor{} = actor} <- User.owns_actor(user, actor_id),
# {:todo_list, %TodoList{actor_id: group_id} = todo_list} <-
# {: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} <-
# Actions.Delete.delete_todo_list(todo_list, actor, true, %{}) do
# {:ok, todo}
@ -169,7 +169,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Todos do
{:todo, Todos.get_todo(todo_id)},
{:todo_list, %TodoList{actor_id: group_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}
else
{:todo, nil} ->
@ -194,7 +194,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Todos do
) do
with {:todo_list, %TodoList{actor_id: group_id} = _todo_list} <-
{: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} <-
Actions.Create.create(
:todo,
@ -228,7 +228,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Todos do
{:todo, Todos.get_todo(todo_id)},
{:todo_list, %TodoList{actor_id: group_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} <-
Actions.Update.update(todo, args, true, %{}) do
{:ok, todo}
@ -259,7 +259,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Todos do
# {:todo, Todos.get_todo(todo_id)},
# {:todo_list, %TodoList{actor_id: group_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} <-
# Actions.Delete.delete_todo(todo, actor, true, %{}) do
# {:ok, todo}

View file

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

View file

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

View file

@ -55,9 +55,10 @@ defmodule Mobilizon.Actors.Member do
@doc """
Checks whether the member is an administrator (admin or creator) of the group.
"""
def is_administrator(%__MODULE__{role: :administrator}), do: true
def is_administrator(%__MODULE__{role: :creator}), do: true
def is_administrator(%__MODULE__{}), do: false
@spec administrator?(t()) :: boolean()
def administrator?(%__MODULE__{role: :administrator}), do: true
def administrator?(%__MODULE__{role: :creator}), do: true
def administrator?(%__MODULE__{}), do: false
@doc false
@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)
|> where([c, _], is_nil(c.in_reply_to_comment_id))
|> 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
# |> where([_, r], is_nil(r.deleted_at))
|> group_by([c], c.id)

View file

@ -515,8 +515,8 @@ defmodule Mobilizon.Events do
|> Page.build_page(page, limit)
end
@spec is_user_moderator_for_event?(integer | String.t(), integer | String.t()) :: boolean
def is_user_moderator_for_event?(user_id, event_id) do
@spec user_moderator_for_event?(integer | String.t(), integer | String.t()) :: boolean
def user_moderator_for_event?(user_id, event_id) do
Participant
|> join(:inner, [p], a in Actor, on: p.actor_id == a.id)
|> where([p, _a], p.event_id == ^event_id)
@ -1492,14 +1492,14 @@ defmodule Mobilizon.Events do
end
@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
@spec is_online_fragment(Ecto.Query.t(), boolean()) :: Ecto.Query.t()
defp is_online_fragment(query, value) do
@spec online_fragment_check(Ecto.Query.t(), boolean()) :: Ecto.Query.t()
defp online_fragment_check(query, value) do
where(query, [q], fragment("(?->>'is_online')::bool = ?", q.options, ^value))
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
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
def is_not_only_organizer(event_id, actor_id) do
@spec not_only_organizer?(integer | String.t(), integer | String.t()) :: boolean
def not_only_organizer?(event_id, actor_id) do
case Events.list_organizers_participants_for_event(event_id) do
[%__MODULE__{actor: %Actor{id: participant_actor_id}}] ->
participant_actor_id == actor_id

View file

@ -34,6 +34,10 @@ defmodule Mobilizon.Instances.InstanceActor do
instance_actor
|> cast(attrs, @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)
end
end

View file

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

View file

@ -214,7 +214,7 @@ defmodule Mobilizon.Medias do
query
|> Repo.all(timeout: :infinity)
|> 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)
|> Enum.chunk_by(fn %Media{file: %File{url: url}} ->
url
@ -223,14 +223,14 @@ defmodule Mobilizon.Medias do
end)
end
defp is_all_media_orphan?(url, expiration_date) do
defp all_media_orphan?(url, expiration_date) do
url
|> get_all_media_by_url()
|> Enum.all?(&is_media_orphan?(&1, expiration_date))
|> Enum.all?(&media_orphan?(&1, expiration_date))
end
@spec is_media_orphan?(Media.t(), DateTime.t()) :: boolean()
defp is_media_orphan?(%Media{id: media_id}, expiration_date) do
@spec media_orphan?(Media.t(), DateTime.t()) :: boolean()
defp media_orphan?(%Media{id: media_id}, expiration_date) do
media_query =
from(m in 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]
alias Ecto.{Changeset, Query}
alias Mobilizon.Web.Endpoint
alias Mobilizon.Web.Gettext, as: GettextBackend
alias Mobilizon.Web.Router.Helpers, as: Routes
@doc """
@ -56,4 +57,30 @@ defmodule Mobilizon.Storage.Ecto do
changeset
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

View file

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

View file

@ -16,7 +16,7 @@ defmodule Mobilizon.Service.Export.Common do
def fetch_actor_event_feed(name, limit) do
case Actors.get_actor_by_name(name) do
%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: posts} = Posts.get_public_posts_for_group(actor, 1, limit)
{:ok, actor, events, posts}

View file

@ -265,7 +265,7 @@ defmodule Mobilizon.Service.Export.Feed do
end
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}")
end
end

View file

@ -216,7 +216,7 @@ defmodule Mobilizon.Service.Export.ICalendar do
end
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}")
end
end

View file

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

View file

@ -12,8 +12,8 @@ defmodule Mobilizon.Service.Notifier.Email do
import Mobilizon.Service.DateTime,
only: [
is_delay_ok_since_last_notification_sent?: 1,
is_delay_ok_since_last_notification_sent?: 2
delay_ok_since_last_notification_sent?: 1,
delay_ok_since_last_notification_sent?: 2
]
require Logger
@ -129,7 +129,7 @@ defmodule Mobilizon.Service.Notifier.Email do
# Delay ok since last notification
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
# Delay ok since last notification
@ -139,7 +139,7 @@ defmodule Mobilizon.Service.Notifier.Email do
%DateTime{} = last_notification_sent,
options
) 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
end
@ -149,7 +149,7 @@ defmodule Mobilizon.Service.Notifier.Email do
%DateTime{} = last_notification_sent,
options
) 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
end

View file

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

View file

@ -81,7 +81,7 @@ defmodule Mobilizon.Service.Workers.LegacyNotifierBuilder do
options
) do
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))
end

View file

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

View file

@ -16,7 +16,7 @@ defmodule Mobilizon.Service.Workers.SendActivityRecapWorker do
only: [
is_between_hours?: 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
@ -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"
)
is_delay_ok_since_last_notification_sent?(last_notification_sent)
delay_ok_since_last_notification_sent?(last_notification_sent)
end
# If we're between notification hours

View file

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

View file

@ -87,8 +87,6 @@ defmodule Mobilizon.Web.Email.Group do
end
end
# TODO : def send_confirmation_to_inviter()
@member_roles [:administrator, :moderator, :member]
@spec send_group_suspension_notification(Member.t()) :: :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
end
end
# TODO : def send_confirmation_to_inviter()
end

View file

@ -71,13 +71,7 @@ defmodule Mobilizon.Web.MediaProxy do
@compile {:no_warn_undefined, {:crypto, :mac, 4}}
@compile {:no_warn_undefined, {:crypto, :hmac, 3}}
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)
else
:crypto.hmac(:sha, key, url)
end
end
@spec filename(String.t()) :: String.t() | nil

View file

@ -6,7 +6,7 @@
timezone: @timezone,
locale: @locale
) %>
<% is_same_day?(@start_date, @end_date) -> %>
<% same_day?(@start_date, @end_date) -> %>
<strong>
<%= gettext("On %{date} from %{start_time} to %{end_time}",
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')
}
</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-2.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_relative(datetime, locale \\ "en"), to: DateTimeRenderer
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 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()
end
@spec is_root(map()) :: boolean()
def is_root(assigns) do
@spec root?(map()) :: boolean()
def root?(assigns) do
assigns |> Map.get(:conn, %{request_path: "/"}) |> Map.get(:request_path, "/") == "/"
end
end

View file

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

View file

@ -8,15 +8,15 @@
## to merge POT files into PO files.
msgid ""
msgstr ""
"PO-Revision-Date: 2022-12-19 04:14+0000\n"
"Last-Translator: Kristoffer Grundström <swedishsailfishosuser@tutanota.com>\n"
"PO-Revision-Date: 2024-01-19 09:47+0000\n"
"Last-Translator: drkfrd <drkfrd@users.noreply.weblate.framasoft.org>\n"
"Language-Team: Swedish <https://weblate.framasoft.org/projects/mobilizon/"
"activity/sv/>\n"
"Language: sv\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\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.text.eex:12
@ -478,12 +478,12 @@ msgstr "%{profile} lade till medlemmen %{member}."
#: lib/web/templates/email/activity/_event_activity_item.text.eex:31
#, elixir-autogen, elixir-format
msgid "%{profile} joined your event %{event}."
msgstr ""
msgstr "%{profile} gick med i ditt evenemang %{event}."
#: lib/web/views/email_view.ex:61
#, elixir-autogen, elixir-format
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.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
#, elixir-autogen, elixir-format
msgid "%{profile} replied to your message"
msgstr ""
msgstr "%{profil} svarade på ditt meddelande"
#: lib/web/templates/email/activity/_conversation_activity_item.html.heex:10
#, 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
#, elixir-autogen, elixir-format
msgid "%{profile} replied you in a conversation."
msgstr ""
msgstr "%{profile} svarade dig i en konversation."
#: lib/service/activity/renderer/conversation.ex:49
#, elixir-autogen, elixir-format
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
#, elixir-autogen, elixir-format
msgid "%{profile} sent you a message"
msgstr ""
msgstr "%{profil} skickade ett meddelande till dig"
#: lib/web/email/activity.ex:52
#, elixir-autogen, elixir-format
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.text.eex:20
#, elixir-autogen, elixir-format
msgid "It might give details on how to join the event, so make sure to read it appropriately."
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.text.eex:28
#, 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."
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}",
"Visit {instance_domain}": "Visit {instance_domain}",
"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}",
"Visit {instance_domain}": "Visiter {instance_domain}",
"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 follow": "Követés elfogadása",
"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",
"Accessible only by link": "Csak hivatkozáson keresztül é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 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 “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é",
"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",
@ -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 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 not found": "Az alkalmazás nem található",
"Application was revoked": "Az alkalmazás vissza lett vonva",
"Apply filters": "Szűrők alkalmazá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 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.",
@ -131,7 +146,11 @@
"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",
"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",
"Back to group list": "Vissza a csoportokhoz",
"Back to homepage": "Vissza a kezdőlapra",
@ -214,6 +233,7 @@
"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!",
"Contact": "Kapcsolat",
"Continue": "Folytatás",
"Continue editing": "Szerkesztés folytatása",
"Cookies and Local storage": "Sütik és helyi tároló",
"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 identity": "Új személyazonosság 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 pad": "Dokumentum létrehozása",
"Create a videoconference": "Videokonferencia létrehozása",
@ -234,11 +255,14 @@
"Create discussion": "Megbeszélés létrehozása",
"Create event": "Esemény 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 my event": "Saját esemény létrehozása",
"Create my group": "Saját csoport létrehozása",
"Create my profile": "Saját profil 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 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.",
@ -265,13 +289,19 @@
"Default Mobilizon terms": "Alapértelmezett Mobilizon használati feltételek",
"Delete": "Törlés",
"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 discussion": "Megbeszélés törlése",
"Delete event": "Esemény törlése",
"Delete events": "Események törlése",
"Delete everything": "Minden 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 post": "Bejegyzés törlése",
"Delete profiles": "Profilok törlése",
"Delete this discussion": "A megbeszélés 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",
@ -285,6 +315,7 @@
"Describe your event": "Írja le az eseményt",
"Description": "Leírás",
"Details": "Részletek",
"Device activation": "Eszköz aktiválása",
"Didn't receive the instructions?": "Nem kapta meg az utasításokat?",
"Disabled": "Letiltva",
"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.",
"Enabled": "Engedélyezve",
"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 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.",
@ -537,6 +569,7 @@
"Last published events": "Legutóbb közzétett események",
"Last seen on": "Legutóbb látva:",
"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",
"Latest posts": "Legutóbbi bejegyzések",
"Learn more": "Tudjon meg többet",
@ -572,7 +605,12 @@
"Login status": "Bejelentkezési állapot",
"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.",
"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 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 invite new members": "Új tagok meghívása kézzel",
"Map": "Térkép",
@ -603,6 +641,7 @@
"Moderation log": "Moderálási napló",
"Moderation logs": "Moderálási naplók",
"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",
"Most recently published": "Legújabban közzétéve",
"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.",
"Name": "Név",
"Navigated to {pageTitle}": "Navigálva ide: {pageTitle}",
"Never used": "Sosem volt használva",
"New discussion": "Új megbeszélés",
"New email": "Új e-mail",
"New folder": "Új mappa",
@ -786,6 +826,7 @@
"Post a comment": "Hozzászólás beküldése",
"Post a reply": "Válasz beküldése",
"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",
"Postal Code": "Irányítószám",
"Posts": "Bejegyzések",
@ -816,6 +857,8 @@
"Public preview": "Nyilvános előnézet",
"Publication date": "Közzététel dátuma",
"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 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",
@ -823,6 +866,7 @@
"Quote": "Idézet",
"RSS/Atom Feed": "RSS/Atom hírforrás",
"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",
"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",
@ -848,6 +892,7 @@
"Remember my participation in this browser": "Emlékezzen a részvételemre ebben a böngészőben",
"Remove": "Eltávolítás",
"Remove link": "Hivatkozás eltávolítása",
"Remove uploaded media": "Feltöltött média eltávolítása",
"Rename": "Átnevezés",
"Rename resource": "Erőforrás átnevezése",
"Reopen": "Újranyitás",
@ -855,6 +900,9 @@
"Reply": "Válasz",
"Report": "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 status": "Állapotjelentés",
"Report this comment": "Hozzászólás jelentése",
@ -883,6 +931,7 @@
"Resources": "Erőforrások",
"Restricted": "Korlátozott",
"Return to the group page": "Visszatérés a csoport oldalára",
"Revoke": "Visszavonás",
"Right now": "Épp most",
"Role": "Szerep",
"Rules": "Szabályok",
@ -892,7 +941,7 @@
"Save draft": "Piszkozat mentése",
"Schedule": "Ütemterv",
"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",
"Searching…": "Keresés…",
"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",
"Street": "Utca",
"Submit": "Elküldés",
"Submit to Akismet": "Beküldés az Akismetnek",
"Subtitles": "Feliratok",
"Suggestions:": "Javaslatok:",
"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 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 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 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",
@ -1015,6 +1066,8 @@
"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 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 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.",
@ -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 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 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 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.",
@ -1061,7 +1123,7 @@
"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 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…).",
"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})",
@ -1116,12 +1178,19 @@
"Upcoming events from your groups": "Közelgő események a csoportjaitól",
"Update": "Frissítés",
"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 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 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 post": "Bejegyzés frissítése",
"Update profiles": "Profilok frissítése",
"Updated": "Frissítve",
"Upload media": "Média feltöltése",
"Uploaded media size": "Feltöltött média mérete",
"Uploaded media total size": "Feltöltött média összmérete",
"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 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 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'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.",
@ -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} 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 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 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á.",
@ -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 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'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 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.",
@ -1285,6 +1357,7 @@
"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 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 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",
@ -1328,6 +1401,7 @@
"create an event": "eseményt létrehozni",
"default Mobilizon privacy policy": "alapértelmezett Mobilizon adatvédelmi irányelv",
"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. Accessibility, Twitch, PeerTube": "például akadálymentesítés, Twitch, PeerTube",
"e.g. Nantes, Berlin, Cork, …": "például Nantes, Berlin, Cork…",
@ -1351,6 +1425,7 @@
"return to the homepage": "visszatérhet a kezdőlapra",
"terms of service": "szolgáltatás feltételeit",
"tool designed to serve you": "eszköz, amelyet arra terveztek, hogy Önt szolgálja",
"translation": " ",
"with another identity…": "egy másik személyazonossággal…",
"your notification settings": "értesítési beállításaitól",
"{'@'}{username}": "{'@'}{username}",

File diff suppressed because it is too large Load diff

View file

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

View file

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