Merge remote-tracking branch 'origin/main'

This commit is contained in:
778a69cd 2024-02-08 18:53:07 +01:00
commit 10d3d03da1
70 changed files with 3694 additions and 1410 deletions

View file

@ -108,7 +108,8 @@
{Credo.Check.Refactor.MatchInCondition, []},
{Credo.Check.Refactor.NegatedConditionsInUnless, []},
{Credo.Check.Refactor.NegatedConditionsWithElse, []},
{Credo.Check.Refactor.Nesting, [
{Credo.Check.Refactor.Nesting,
[
max_nesting: 3
]},
{Credo.Check.Refactor.PipeChainStart,
@ -159,8 +160,7 @@
# Removed checks
#
{Credo.Check.Warning.LazyLogging, false},
{Credo.Check.Refactor.MapInto, false},
{Credo.Check.Warning.MissedMetadataKeyInLoggerConfig, false}
{Credo.Check.Refactor.MapInto, false}
]
}
]

View file

@ -1,3 +1 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npm run pre-commit

View file

@ -36,7 +36,7 @@ config :mobilizon, :instance,
unconfirmed_user_grace_period_hours: 48,
activity_expire_days: 365,
activity_keep_number: 100,
enable_instance_feeds: false,
enable_instance_feeds: true,
email_from: "noreply@localhost",
email_reply_to: "noreply@localhost"

View file

@ -1,6 +1,7 @@
# Mobilizon instance configuration
import Config
import Mobilizon.Service.Config.Helpers
{:ok, _} = Application.ensure_all_started(:tls_certificate_check)
@ -49,9 +50,20 @@ config :mobilizon, :instance,
description: "Change this to a proper description of your instance",
hostname: System.get_env("MOBILIZON_INSTANCE_HOST", "mobilizon.lan"),
registrations_open: System.get_env("MOBILIZON_INSTANCE_REGISTRATIONS_OPEN", "false") == "true",
demo: false,
allow_relay: true,
federating: true,
registration_email_allowlist:
System.get_env("MOBILIZON_INSTANCE_REGISTRATIONS_EMAIL_ALLOWLIST", "")
|> String.split(",", trim: true),
registration_email_denylist:
System.get_env("MOBILIZON_INSTANCE_REGISTRATIONS_EMAIL_DENYLIST", "")
|> String.split(",", trim: true),
disable_database_login:
System.get_env("MOBILIZON_INSTANCE_DISABLE_DATABASE_LOGIN", "false") == "true",
default_language: System.get_env("MOBILIZON_INSTANCE_DEFAULT_LANGUAGE", "en"),
demo: System.get_env("MOBILIZON_INSTANCE_DEMO", "false") == "true",
allow_relay: System.get_env("MOBILIZON_INSTANCE_ALLOW_RELAY", "true") == "true",
federating: System.get_env("MOBILIZON_INSTANCE_FEDERATING", "true") == "true",
enable_instance_feeds:
System.get_env("MOBILIZON_INSTANCE_ENABLE_INSTANCE_FEEDS", "true") == "true",
email_from: System.get_env("MOBILIZON_INSTANCE_EMAIL", "noreply@mobilizon.lan"),
email_reply_to: System.get_env("MOBILIZON_REPLY_EMAIL", "noreply@mobilizon.lan")
@ -79,7 +91,7 @@ config :mobilizon, Mobilizon.Web.Email.Mailer,
ssl: System.get_env("MOBILIZON_SMTP_SSL", "false"),
retries: 1,
no_mx_lookups: false,
auth: :if_available
auth: System.get_env("MOBILIZON_SMTP_AUTH", "if_available")
config :geolix,
databases: [
@ -93,13 +105,30 @@ config :geolix,
config :mobilizon, Mobilizon.Web.Upload.Uploader.Local,
uploads: System.get_env("MOBILIZON_UPLOADS", "/var/lib/mobilizon/uploads")
formats =
if System.get_env("MOBILIZON_EXPORTS_FORMAT_CSV_ENABLED", "true") == "true" do
[Mobilizon.Service.Export.Participants.CSV]
else
[]
end
formats =
if System.get_env("MOBILIZON_EXPORTS_FORMAT_PDF_ENABLED", "true") == "true" do
formats ++ [Mobilizon.Service.Export.Participants.PDF]
else
formats
end
formats =
if System.get_env("MOBILIZON_EXPORTS_FORMAT_ODS_ENABLED", "true") == "true" do
formats ++ [Mobilizon.Service.Export.Participants.ODS]
else
formats
end
config :mobilizon, :exports,
path: System.get_env("MOBILIZON_UPLOADS_EXPORTS", "/var/lib/mobilizon/uploads/exports"),
formats: [
Mobilizon.Service.Export.Participants.CSV,
Mobilizon.Service.Export.Participants.PDF,
Mobilizon.Service.Export.Participants.ODS
]
formats: formats
config :tz_world,
data_dir: System.get_env("MOBILIZON_TIMEZONES_DIR", "/var/lib/mobilizon/timezones")
@ -110,3 +139,131 @@ config :web_push_encryption, :vapid_details,
subject: System.get_env("MOBILIZON_WEB_PUSH_ENCRYPTION_SUBJECT", nil),
public_key: System.get_env("MOBILIZON_WEB_PUSH_ENCRYPTION_PUBLIC_KEY", nil),
private_key: System.get_env("MOBILIZON_WEB_PUSH_ENCRYPTION_PRIVATE_KEY", nil)
geospatial_service =
case System.get_env("MOBILIZON_GEOSPATIAL_SERVICE", "Nominatim") do
"Nominatim" -> Mobilizon.Service.Geospatial.Nominatim
"Addok" -> Mobilizon.Service.Geospatial.Addok
"Photon" -> Mobilizon.Service.Geospatial.Photon
"GoogleMaps" -> Mobilizon.Service.Geospatial.GoogleMaps
"MapQuest" -> Mobilizon.Service.Geospatial.MapQuest
"Mimirsbrunn" -> Mobilizon.Service.Geospatial.Mimirsbrunn
"Pelias" -> Mobilizon.Service.Geospatial.Pelias
end
config :mobilizon, Mobilizon.Service.Geospatial, service: geospatial_service
if System.get_env("MOBILIZON_GEOSPATIAL_SERVICE", "Nominatim") == "Nominatim" do
config :mobilizon, Mobilizon.Service.Geospatial.Nominatim,
endpoint:
System.get_env(
"MOBILIZON_GEOSPATIAL_NOMINATIM_ENDPOINT",
"https://nominatim.openstreetmap.org"
),
api_key: System.get_env("MOBILIZON_GEOSPATIAL_NOMINATIM_API_KEY", nil)
end
if System.get_env("MOBILIZON_GEOSPATIAL_SERVICE", "Nominatim") == "Addok" do
config :mobilizon, Mobilizon.Service.Geospatial.Addok,
endpoint:
System.get_env("MOBILIZON_GEOSPATIAL_ADDOK_ENDPOINT", "https://api-adresse.data.gouv.fr")
end
if System.get_env("MOBILIZON_GEOSPATIAL_SERVICE", "Nominatim") == "Photon" do
config :mobilizon, Mobilizon.Service.Geospatial.Photon,
endpoint: System.get_env("MOBILIZON_GEOSPATIAL_PHOTON_ENDPOINT", "https://photon.komoot.de")
end
if System.get_env("MOBILIZON_GEOSPATIAL_SERVICE", "Nominatim") == "GoogleMaps" do
config :mobilizon, Mobilizon.Service.Geospatial.GoogleMaps,
api_key: System.get_env("MOBILIZON_GEOSPATIAL_GOOGLE_MAPS_API_KEY", nil),
fetch_place_details: true
end
if System.get_env("MOBILIZON_GEOSPATIAL_SERVICE", "Nominatim") == "MapQuest" do
config :mobilizon, Mobilizon.Service.Geospatial.MapQuest,
api_key: System.get_env("MOBILIZON_GEOSPATIAL_MAP_QUEST_API_KEY", nil)
end
if System.get_env("MOBILIZON_GEOSPATIAL_SERVICE", "Nominatim") == "Mimirsbrunn" do
config :mobilizon, Mobilizon.Service.Geospatial.Mimirsbrunn,
endpoint: System.get_env("MOBILIZON_GEOSPATIAL_MIMIRSBRUNN_ENDPOINT", nil)
end
if System.get_env("MOBILIZON_GEOSPATIAL_SERVICE", "Nominatim") == "Pelias" do
config :mobilizon, Mobilizon.Service.Geospatial.Pelias,
endpoint: System.get_env("MOBILIZON_GEOSPATIAL_PELIAS_ENDPOINT", nil)
end
sentry_dsn = System.get_env("MOBILIZON_ERROR_REPORTING_SENTRY_DSN", nil)
included_environments = if sentry_dsn, do: ["prod"], else: []
config :sentry,
dsn: sentry_dsn,
included_environments: included_environments,
release: to_string(Application.spec(:mobilizon, :vsn))
config :logger, Sentry.LoggerBackend,
capture_log_messages: true,
level: :error
if sentry_dsn != nil do
config :mobilizon, Mobilizon.Service.ErrorReporting,
adapter: Mobilizon.Service.ErrorReporting.Sentry
end
matomo_enabled = System.get_env("MOBILIZON_FRONT_END_ANALYTICS_MATOMO_ENABLED", "false") == "true"
matomo_endpoint = System.get_env("MOBILIZON_FRONT_END_ANALYTICS_MATOMO_ENDPOINT", nil)
matomo_site_id = System.get_env("MOBILIZON_FRONT_END_ANALYTICS_MATOMO_SITE_ID", nil)
matomo_tracker_file_name =
System.get_env("MOBILIZON_FRONT_END_ANALYTICS_MATOMO_TRACKER_FILE_NAME", "matomo")
matomo_host = host_from_uri(matomo_endpoint)
analytics_providers =
if matomo_enabled do
[Mobilizon.Service.FrontEndAnalytics.Matomo]
else
[]
end
analytics_providers =
if sentry_dsn != nil do
analytics_providers ++ [Mobilizon.Service.FrontEndAnalytics.Sentry]
else
analytics_providers
end
config :mobilizon, :analytics, providers: analytics_providers
matomo_csp =
if matomo_enabled and matomo_host do
[
connect_src: [matomo_host],
script_src: [matomo_host],
img_src: [matomo_host]
]
else
[]
end
config :mobilizon, Mobilizon.Service.FrontEndAnalytics.Matomo,
enabled: matomo_enabled,
host: matomo_endpoint,
siteId: matomo_site_id,
trackerFileName: matomo_tracker_file_name,
csp: matomo_csp
config :mobilizon, Mobilizon.Service.FrontEndAnalytics.Sentry,
enabled: sentry_dsn != nil,
dsn: sentry_dsn,
tracesSampleRate: 1.0,
organization: System.get_env("MOBILIZON_ERROR_REPORTING_SENTRY_ORGANISATION", nil),
project: System.get_env("MOBILIZON_ERROR_REPORTING_SENTRY_PROJECT", nil),
host: System.get_env("MOBILIZON_ERROR_REPORTING_SENTRY_HOST", nil),
csp: [
connect_src:
System.get_env("MOBILIZON_ERROR_REPORTING_SENTRY_HOST", "") |> String.split(" ", trim: true)
]

View file

@ -61,8 +61,8 @@ mixRelease rec {
src = fetchFromGitHub {
owner = "danhper";
repo = "elixir-web-push-encryption";
rev = "70f00d06cbd88c9ac382e0ad2539e54448e9d8da";
sha256 = "sha256-b4ZMrt/8n2sPUFtCDRTwXS1qWm5VlYdbx8qC0R0boOA=";
rev = "6e143dcde0a2854c4f0d72816b7ecab696432779";
sha256 = "sha256-Da+/28SPZuUQBi8fQj31zmMvhMrYUaQIW4U4E+mRtMg=";
};
beamDeps = with final; [ httpoison jose ];
};

View file

@ -71,8 +71,10 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
case Discussions.get_comment_from_url_with_preload(object["id"]) do
{:error, :comment_not_found} ->
case Converter.Comment.as_to_model_data(object) do
%{visibility: visibility, attributed_to_id: attributed_to_id} = object_data
when visibility === :private and is_nil(attributed_to_id) ->
%{visibility: visibility, attributed_to_id: attributed_to_id, actor_id: actor_id} =
object_data
when visibility === :private and
(is_nil(attributed_to_id) or actor_id == attributed_to_id) ->
Actions.Create.create(:conversation, object_data, false)
object_data when is_map(object_data) ->

View file

@ -6,10 +6,13 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Event do
internal one, and back.
"""
alias Cldr.DateTime.Formatter
alias Mobilizon.Actors.Actor
alias Mobilizon.Addresses.Address
alias Mobilizon.Events.Categories
alias Mobilizon.Events.Event, as: EventModel
alias Mobilizon.Events.EventOptions
alias Mobilizon.Medias.Media
alias Mobilizon.Federation.ActivityStream.{Converter, Convertible}
@ -29,7 +32,14 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Event do
maybe_fetch_actor_and_attributed_to_id: 1,
process_pictures: 2,
get_address: 1,
fetch_actor: 1
fetch_actor: 1,
visibility_public?: 1
]
import Mobilizon.Service.Metadata.Utils,
only: [
datetime_to_string: 3,
render_address!: 1
]
require Logger
@ -146,7 +156,7 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Event do
"anonymousParticipationEnabled" => event.options.anonymous_participation,
"attachment" => Enum.map(event.metadata, &EventMetadataConverter.metadata_to_as/1),
"draft" => event.draft,
# Remove me in MBZ 5.x
# TODO: Remove me in MBZ 5.x
"ical:status" => event.status |> to_string |> String.upcase(),
"status" => event.status |> to_string |> String.upcase(),
"id" => event.url,
@ -154,7 +164,8 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Event do
"inLanguage" => event.language,
"timezone" => event.options.timezone,
"contacts" => Enum.map(event.contacts, & &1.url),
"isOnline" => event.options.is_online
"isOnline" => event.options.is_online,
"summary" => event_summary(event)
}
|> maybe_add_physical_address(event)
|> maybe_add_event_picture(event)
@ -216,7 +227,8 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Event do
defp get_metdata(_), do: []
defp get_visibility(object), do: if(@ap_public in object["to"], do: :public, else: :unlisted)
defp get_visibility(object),
do: if(visibility_public?(object["to"]), do: :public, else: :unlisted)
@spec date_to_string(DateTime.t() | nil) :: String.t()
defp date_to_string(nil), do: nil
@ -341,4 +353,47 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Event do
_participant_count
),
do: nil
def event_summary(%EventModel{
begins_on: begins_on,
physical_address: address,
options: %EventOptions{timezone: timezone},
language: language
}) do
begins_on = build_begins_on(begins_on, timezone)
begins_on
|> datetime_to_string(language || "en", :long)
|> (&[&1]).()
|> add_timezone(begins_on)
|> maybe_build_address(address)
|> Enum.join(" - ")
end
@spec build_begins_on(DateTime.t(), String.t() | nil) :: DateTime.t()
defp build_begins_on(begins_on, nil), do: begins_on
defp build_begins_on(begins_on, timezone) do
case DateTime.shift_zone(begins_on, timezone) do
{:ok, begins_on} -> begins_on
{:error, _err} -> begins_on
end
end
defp add_timezone(elements, %DateTime{} = begins_on) do
elements ++ [Formatter.zone_gmt(begins_on)]
end
@spec maybe_build_address(list(String.t()), Address.t() | nil) :: list(String.t())
defp maybe_build_address(elements, %Address{} = address) do
elements ++ [render_address!(address)]
rescue
# If the address is not renderable
e in ArgumentError ->
require Logger
Logger.error(Exception.format(:error, e, __STACKTRACE__))
elements
end
defp maybe_build_address(elements, _address), do: elements
end

View file

@ -15,7 +15,8 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Post do
import Mobilizon.Federation.ActivityStream.Converter.Utils,
only: [
process_pictures: 2
process_pictures: 2,
visibility_public?: 1
]
import Mobilizon.Service.Guards, only: [is_valid_string: 1]
@ -134,14 +135,12 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Post do
)
end
@ap_public "https://www.w3.org/ns/activitystreams#Public"
defp get_visibility(%{"to" => to}, %Actor{
followers_url: followers_url,
members_url: members_url
}) do
cond do
@ap_public in to -> :public
visibility_public?(to) -> :public
followers_url in to -> :unlisted
members_url in to -> :private
end

View file

@ -205,9 +205,7 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Utils do
@spec process_pictures(map(), integer()) :: Keyword.t()
def process_pictures(object, actor_id) do
attachements = Map.get(object, "attachment", [])
{banner, media_attachements} = get_medias(attachements)
{banner, media_attachements} = get_medias(object)
media_attachements_map =
media_attachements
@ -259,24 +257,46 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Utils do
do: String.replace(body, old_url, new_url)
@spec get_medias(list(map())) :: {map(), list(map())}
defp get_medias(attachments) do
banner = get_banner_picture(attachments)
{banner, Enum.filter(attachments, &(&1["type"] == "Document" && &1["url"] != banner["url"]))}
def get_medias(object) do
banner = get_banner_picture(object)
attachments = Map.get(object, "attachment", [])
{banner, Enum.filter(attachments, &(valid_banner_media?(&1) && &1["url"] != banner["url"]))}
end
@spec get_banner_picture(list(map())) :: map()
defp get_banner_picture(attachments) do
# Prefer media with
@spec get_banner_picture(map()) :: map()
defp get_banner_picture(object) do
attachments = Map.get(object, "attachment", [])
image = Map.get(object, "image", %{})
media_with_picture_name =
Enum.find(attachments, &(&1["type"] == "Document" && &1["name"] == @banner_picture_name))
Enum.find(attachments, &(valid_banner_media?(&1) && &1["name"] == @banner_picture_name))
case media_with_picture_name do
# If no banner found, use the first media
nil -> Enum.find(attachments, &(&1["type"] == "Document"))
media_with_picture_name -> media_with_picture_name
cond do
# Check if the "image" key is set and of type "Document" or "Image"
is_nil(media_with_picture_name) and valid_banner_media?(image) ->
image
is_nil(media_with_picture_name) and Enum.find(attachments, &valid_banner_media?/1) ->
Enum.find(attachments, &valid_banner_media?/1)
!is_nil(media_with_picture_name) ->
media_with_picture_name
true ->
nil
end
end
@spec valid_banner_media?(map()) :: boolean()
defp valid_banner_media?(media) do
media |> Map.get("type") |> valid_attachment_type?()
end
@spec valid_attachment_type?(any()) :: boolean()
defp valid_attachment_type?(type) do
type in ["Document", "Image"]
end
@spec get_address(map | binary | nil) :: Address.t() | nil
def get_address(text_address) when is_binary(text_address) do
get_address(%{"type" => "Place", "name" => text_address})
@ -315,4 +335,13 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Utils do
nil
end
end
@ap_public "https://www.w3.org/ns/activitystreams#Public"
@spec visibility_public?(String.t() | list(String.t())) :: boolean()
def visibility_public?(to) when is_binary(to), do: visibility_public?([to])
def visibility_public?(to) when is_list(to) do
!MapSet.disjoint?(MapSet.new(to), MapSet.new([@ap_public, "as:Public", "Public"]))
end
end

View file

@ -156,6 +156,9 @@ defmodule Mobilizon.GraphQL.Resolvers.Conversation do
{:ok, conversation_to_view(conversation, conversation_participant_actor)}
{:error, %Ecto.Changeset{} = changeset} ->
{:error, changeset}
{:error, :empty_participants} ->
{:error,
dgettext(

View file

@ -381,10 +381,15 @@ defmodule Mobilizon.GraphQL.Resolvers.Participant do
visibility: :private
})
with {:member, true} <-
with {:ok,
%Event{organizer_actor_id: organizer_actor_id, attributed_to_id: attributed_to_id} =
_event} <- Mobilizon.Events.get_event(event_id),
{:member, true} <-
{:member,
to_string(current_actor_id) == to_string(actor_id) or
Actors.member?(current_actor_id, actor_id)},
(to_string(current_actor_id) == to_string(organizer_actor_id) and
to_string(current_actor_id) == to_string(actor_id)) or
(!is_nil(attributed_to_id) and Actors.member?(current_actor_id, attributed_to_id) and
to_string(attributed_to_id) == to_string(actor_id))},
{:ok, _activity, %Conversation{} = conversation} <- Comments.create_conversation(args) do
{:ok, conversation_to_view(conversation, Actors.get_actor(actor_id))}
else

View file

@ -20,6 +20,7 @@ defmodule Mobilizon.GraphQL.Schema.ConversationType do
)
field(:last_comment, :comment, description: "The last comment of the conversation")
field(:origin_comment, :comment, description: "The first comment of the conversation")
field :comments, :paginated_comment_list do
arg(:page, :integer, default_value: 1)

View file

@ -4,7 +4,7 @@ defmodule Mobilizon.GraphQL.Schema.Discussions.CommentType do
"""
use Absinthe.Schema.Notation
import Absinthe.Resolution.Helpers, only: [dataloader: 1]
import Absinthe.Resolution.Helpers, only: [dataloader: 1, dataloader: 2]
alias Mobilizon.{Actors, Discussions, Events}
alias Mobilizon.GraphQL.Resolvers.Comment
@ -23,7 +23,7 @@ defmodule Mobilizon.GraphQL.Schema.Discussions.CommentType do
field(:replies, list_of(:comment)) do
description("A list of replies to the comment")
resolve(dataloader(Discussions))
resolve(dataloader(Discussions, args: %{replies: true}))
end
field(:total_replies, :integer,
@ -47,6 +47,12 @@ defmodule Mobilizon.GraphQL.Schema.Discussions.CommentType do
field(:threadLanguages, non_null(list_of(:string)), description: "The thread languages")
field(:actor, :person, resolve: dataloader(Actors), description: "The comment's author")
field(:attributed_to, :actor,
resolve: dataloader(Actors),
description: "The comment's attributed to actor"
)
field(:inserted_at, :datetime, description: "When was the comment inserted in database")
field(:updated_at, :datetime, description: "When was the comment updated")
field(:deleted_at, :datetime, description: "When was the comment deleted")

View file

@ -104,6 +104,10 @@ defmodule Mobilizon.Conversations do
|> join(:inner, [_cp, _c, _e, _a, _lc, _oc, p], ap in Actor, on: p.actor_id == ap.id)
|> where([_cp, c], c.event_id == ^event_id)
|> where([cp], cp.actor_id == ^actor_id)
|> where(
[_cp, _c, _e, _a, _lc, oc],
oc.actor_id == ^actor_id or oc.attributed_to_id == ^actor_id
)
|> order_by([cp], desc: cp.unread, desc: cp.updated_at)
|> preload([_cp, c, e, a, lc, oc, p, ap],
actor: a,
@ -113,6 +117,14 @@ defmodule Mobilizon.Conversations do
|> Page.build_page(page, limit)
end
def find_all_conversations_for_event(event_id) do
ConversationParticipant
|> join(:inner, [cp], c in Conversation, on: cp.conversation_id == c.id)
|> join(:left, [_cp, c], e in Event, on: c.event_id == e.id)
|> where([_cp, c], c.event_id == ^event_id)
|> Repo.all()
end
@spec list_conversation_participants_for_actor(
integer | String.t(),
integer | nil,
@ -133,7 +145,7 @@ defmodule Mobilizon.Conversations do
subquery
|> subquery()
|> order_by([cp], desc: cp.unread, desc: cp.updated_at)
|> preload([:actor, conversation: [:last_comment, :participants]])
|> preload([:actor, conversation: [:last_comment, :origin_comment, :participants, :event]])
|> Page.build_page(page, limit)
end
@ -147,7 +159,7 @@ defmodule Mobilizon.Conversations do
ConversationParticipant
|> join(:inner, [cp], a in Actor, on: cp.actor_id == a.id)
|> where([_cp, a], a.user_id == ^user_id)
|> preload([:actor, conversation: [:last_comment, :participants]])
|> preload([:actor, conversation: [:last_comment, :origin_comment, :participants, :event]])
|> Page.build_page(page, limit)
end

View file

@ -85,6 +85,13 @@ defmodule Mobilizon.Discussions do
|> select([c, r], %{c | total_replies: count(r.id)})
end
# Replies are only used on event comments, so we always use public visibily here
def query(Comment, %{replies: true}) do
Comment
|> where([c], c.visibility in ^@public_visibility)
|> order_by([c], asc: :is_announcement, asc: :published_at)
end
def query(Comment, _) do
order_by(Comment, [c], asc: :is_announcement, asc: :published_at)
end

View file

@ -792,7 +792,7 @@ defmodule Mobilizon.Events do
end
end
def get_participant(event_id, actor_id, %{}) do
def get_participant(event_id, actor_id, _params) do
case Participant
|> Repo.get_by(event_id: event_id, actor_id: actor_id)
|> Repo.preload(@participant_preloads) do

View file

@ -2,7 +2,7 @@ defmodule Mobilizon.Service.Activity.Conversation do
@moduledoc """
Insert a conversation activity
"""
alias Mobilizon.Conversations
alias Mobilizon.{Actors, Conversations}
alias Mobilizon.Conversations.{Conversation, ConversationParticipant}
alias Mobilizon.Discussions.Comment
alias Mobilizon.Events.Event
@ -38,7 +38,7 @@ defmodule Mobilizon.Service.Activity.Conversation do
%Conversation{
id: conversation_id
} = conversation,
%Comment{actor_id: actor_id, text: last_comment_text},
%Comment{actor_id: actor_id, text: last_comment_text} = comment,
_options
)
when subject in [
@ -55,7 +55,8 @@ defmodule Mobilizon.Service.Activity.Conversation do
actor_id: conversation_participant_actor_id
} =
conversation_participant ->
if actor_id != conversation_participant_actor_id do
if actor_id != conversation_participant_actor_id and
can_send_event_announcement?(conversation, comment) do
LegacyNotifierBuilder.enqueue(
:legacy_notify,
%{
@ -98,4 +99,31 @@ defmodule Mobilizon.Service.Activity.Conversation do
}
defp event_subject_params(_), do: %{}
@spec can_send_event_announcement?(Conversation.t(), Comment.t()) :: boolean()
defp can_send_event_announcement?(
%Conversation{
event: %Event{
attributed_to_id: attributed_to_id
}
},
%Comment{actor_id: actor_id}
)
when not is_nil(attributed_to_id) do
attributed_to_id == actor_id or Actors.member?(actor_id, attributed_to_id)
end
defp can_send_event_announcement?(
%Conversation{
event: %Event{
organizer_actor_id: organizer_actor_id
}
},
%Comment{actor_id: actor_id}
)
when not is_nil(organizer_actor_id) do
organizer_actor_id == actor_id
end
defp can_send_event_announcement?(_, _), do: false
end

View file

@ -85,7 +85,7 @@ defmodule Mobilizon.Service.Address do
defined?(street) ->
if defined?(locality), do: "#{street} (#{locality})", else: street
defined?(locality) ->
defined?(locality) and locality != region ->
"#{locality}, #{region}, #{country}"
defined?(region) ->

View file

@ -0,0 +1,12 @@
defmodule Mobilizon.Service.Config.Helpers do
@moduledoc """
Provide some helpers to configuration files
"""
@spec host_from_uri(String.t() | nil) :: String.t() | nil
def host_from_uri(nil), do: nil
def host_from_uri(uri) when is_binary(uri) do
URI.parse(uri).host
end
end

View file

@ -136,15 +136,13 @@ defimpl Mobilizon.Service.Metadata, for: Mobilizon.Events.Event do
defp build_language(language, locale), do: language || locale
@spec build_begins_on(DateTime.t(), String.t() | nil) :: DateTime.t()
defp build_begins_on(begins_on, nil), do: begins_on
defp build_begins_on(begins_on, timezone) do
if timezone do
case DateTime.shift_zone(begins_on, timezone) do
{:ok, begins_on} -> begins_on
{:error, _err} -> begins_on
end
else
begins_on
end
end
defp add_timezone(elements, %DateTime{} = begins_on) do

View file

@ -56,7 +56,6 @@ defmodule Mobilizon.Service.SiteMap do
end)
|> Sitemapper.generate(config)
|> Sitemapper.persist(config)
|> Sitemapper.ping(config)
|> Stream.run()
end,
timeout: :infinity

View file

@ -4,7 +4,7 @@ defmodule Mobilizon.Service.Workers.LegacyNotifierBuilder do
"""
alias Mobilizon.Activities.Activity
alias Mobilizon.{Actors, Events, Users}
alias Mobilizon.{Actors, Config, Events, Users}
alias Mobilizon.Actors.Actor
alias Mobilizon.Events.{Event, Participant}
alias Mobilizon.Service.Notifier
@ -37,9 +37,10 @@ defmodule Mobilizon.Service.Workers.LegacyNotifierBuilder do
end
defp special_handling("conversation_created", args, activity) do
notify_participants(
notify_participant(
get_in(args, ["subject_params", "conversation_event_id"]),
activity,
get_in(args, ["participant", "actor_id"]),
args["author_id"]
)
end
@ -143,6 +144,24 @@ defmodule Mobilizon.Service.Workers.LegacyNotifierBuilder do
notify_anonymous_participants(event_id, activity)
end
defp notify_participant(nil, _activity, _conversation_participant_actor_id, _author_id),
do: :ok
defp notify_participant(event_id, activity, conversation_participant_actor_id, author_id) do
# Anonymous participation
if conversation_participant_actor_id == Config.anonymous_actor_id() do
notify_anonymous_participants(event_id, activity)
else
[conversation_participant_actor_id]
|> users_from_actor_ids(author_id)
|> Enum.each(fn user ->
Notifier.Email.send_anonymous_activity(user.email, activity,
locale: Map.get(user, :locale, "en")
)
end)
end
end
defp notify_anonymous_participants(nil, _activity), do: :ok
defp notify_anonymous_participants(event_id, activity) do
@ -154,7 +173,7 @@ defmodule Mobilizon.Service.Workers.LegacyNotifierBuilder do
|> Enum.map(fn %Participant{metadata: metadata} -> metadata end)
|> Enum.map(fn %{email: email} = metadata ->
Notifier.Email.send_anonymous_activity(email, activity,
locale: Map.get(metadata, :locale, "en")
locale: Map.get(metadata, :locale, "en") || "en"
)
end)
end

View file

@ -95,7 +95,7 @@ defmodule Mobilizon.Web.Router do
forward("/", Absinthe.Plug,
schema: Mobilizon.GraphQL.Schema,
analyze_complexity: true,
max_complexity: 250
max_complexity: 300
)
end

14
mix.exs
View file

@ -121,10 +121,12 @@ defmodule Mobilizon.Mixfile do
|> to_string()
|> String.split()
|> Enum.map(fn strategy_entry ->
with [_strategy, dependency] <- String.split(strategy_entry, ":") do
case String.split(strategy_entry, ":") do
[_strategy, dependency] ->
dependency
else
[strategy] -> "ueberauth_#{strategy}"
[strategy] ->
"ueberauth_#{strategy}"
end
end)
@ -185,7 +187,7 @@ defmodule Mobilizon.Mixfile do
{:floki, "~> 0.31"},
{:ip_reserved, "~> 0.1.0"},
{:fast_sanitize, "~> 0.1"},
{:ueberauth, "0.10.5", override: true},
{:ueberauth, "0.10.7", override: true},
{:ueberauth_twitter, "~> 0.4"},
{:ueberauth_discord, "~> 0.7"},
{:ueberauth_github, "~> 0.8.1"},
@ -283,7 +285,7 @@ defmodule Mobilizon.Mixfile do
File.rm_rf!("test/uploads")
end
defp docs() do
defp docs do
[
source_ref: "v#{@version}",
groups_for_modules: groups_for_modules(),
@ -323,7 +325,7 @@ defmodule Mobilizon.Mixfile do
]
end
defp groups_for_modules() do
defp groups_for_modules do
[
Models: [
~r/Mobilizon.Actors~r/,

View file

@ -14,10 +14,10 @@
"comeonin": {:hex, :comeonin, "5.4.0", "246a56ca3f41d404380fc6465650ddaa532c7f98be4bda1b4656b3a37cc13abe", [:mix], [], "hexpm", "796393a9e50d01999d56b7b8420ab0481a7538d0caf80919da493b4a6e51faf1"},
"connection": {:hex, :connection, "1.1.0", "ff2a49c4b75b6fb3e674bfc5536451607270aac754ffd1bdfe175abe4a6d7a68", [:mix], [], "hexpm", "722c1eb0a418fbe91ba7bd59a47e28008a189d47e37e0e7bb85585a016b2869c"},
"cors_plug": {:hex, :cors_plug, "3.0.3", "7c3ac52b39624bc616db2e937c282f3f623f25f8d550068b6710e58d04a0e330", [:mix], [{:plug, "~> 1.13", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "3f2d759e8c272ed3835fab2ef11b46bddab8c1ab9528167bd463b6452edf830d"},
"cowboy": {:hex, :cowboy, "2.10.0", "ff9ffeff91dae4ae270dd975642997afe2a1179d94b1887863e43f681a203e26", [:make, :rebar3], [{:cowlib, "2.12.1", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "3afdccb7183cc6f143cb14d3cf51fa00e53db9ec80cdcd525482f5e99bc41d6b"},
"cowboy": {:hex, :cowboy, "2.11.0", "356bf784599cf6f2cdc6ad12fdcfb8413c2d35dab58404cf000e1feaed3f5645", [:make, :rebar3], [{:cowlib, "2.12.1", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "0fa395437f1b0e104e0e00999f39d2ac5f4082ac5049b67a5b6d56ecc31b1403"},
"cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"},
"cowlib": {:hex, :cowlib, "2.12.1", "a9fa9a625f1d2025fe6b462cb865881329b5caff8f1854d1cbc9f9533f00e1e1", [:make, :rebar3], [], "hexpm", "163b73f6367a7341b33c794c4e88e7dbfe6498ac42dcd69ef44c5bc5507c8db0"},
"credo": {:hex, :credo, "1.7.2", "fdee3a7cb553d8f2e773569181f0a4a2bb7d192e27e325404cc31b354f59d68c", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "dd15d6fbc280f6cf9b269f41df4e4992dee6615939653b164ef951f60afcb68e"},
"credo": {:hex, :credo, "1.7.3", "05bb11eaf2f2b8db370ecaa6a6bda2ec49b2acd5e0418bc106b73b07128c0436", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "35ea675a094c934c22fb1dca3696f3c31f2728ae6ef5a53b5d648c11180a4535"},
"credo_code_climate": {:hex, :credo_code_climate, "0.1.0", "1c4efbd11cb0244622ed5f09246b9afbbf796316ce03e78f67db6d81271d2978", [:mix], [{:credo, "~> 1.5", [hex: :credo, repo: "hexpm", optional: false]}, {:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "75529fe38056f4e229821d604758282838b8397c82e2c12e409fda16b16821ca"},
"dataloader": {:hex, :dataloader, "2.0.0", "49b42d60b9bb06d761a71d7b034c4b34787957e713d4fae15387a25fcd639112", [:mix], [{:ecto, ">= 3.4.3 and < 4.0.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:opentelemetry_process_propagator, "~> 0.2.1", [hex: :opentelemetry_process_propagator, repo: "hexpm", optional: true]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "09d61781b76ce216e395cdbc883ff00d00f46a503e215c22722dba82507dfef0"},
"db_connection": {:hex, :db_connection, "2.6.0", "77d835c472b5b67fc4f29556dee74bf511bbafecdcaf98c27d27fa5918152086", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c2f992d15725e721ec7fbc1189d4ecdb8afef76648c746a8e1cad35e3b8a35f3"},
@ -34,7 +34,7 @@
"ecto_shortuuid": {:hex, :ecto_shortuuid, "0.2.0", "57cae7b6016cc56a04457b4fc8f63957398dfd9023ff3e900eaf6805a40f8043", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:shortuuid, "~> 2.1 or ~> 3.0", [hex: :shortuuid, repo: "hexpm", optional: false]}], "hexpm", "b92e3b71e86be92f5a7ef6f3de170e7864454e630f7b01dd930414baf38efb65"},
"ecto_sql": {:hex, :ecto_sql, "3.11.1", "e9abf28ae27ef3916b43545f9578b4750956ccea444853606472089e7d169470", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.11.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ce14063ab3514424276e7e360108ad6c2308f6d88164a076aac8a387e1fea634"},
"elixir_feed_parser": {:hex, :elixir_feed_parser, "2.1.0", "bb96fb6422158dc7ad59de62ef211cc69d264acbbe63941a64a5dce97bbbc2e6", [:mix], [{:timex, "~> 3.4", [hex: :timex, repo: "hexpm", optional: false]}], "hexpm", "2d3c62fe7b396ee3b73d7160bc8fadbd78bfe9597c98c7d79b3f1038d9cba28f"},
"elixir_make": {:hex, :elixir_make, "0.7.7", "7128c60c2476019ed978210c245badf08b03dbec4f24d05790ef791da11aa17c", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}], "hexpm", "5bc19fff950fad52bbe5f211b12db9ec82c6b34a9647da0c2224b8b8464c7e6c"},
"elixir_make": {:hex, :elixir_make, "0.7.8", "505026f266552ee5aabca0b9f9c229cbb496c689537c9f922f3eb5431157efc7", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:certifi, "~> 2.0", [hex: :certifi, repo: "hexpm", optional: true]}], "hexpm", "7a71945b913d37ea89b06966e1342c85cfe549b15e6d6d081e8081c493062c07"},
"erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"},
"erlport": {:hex, :erlport, "0.11.0", "8bb46a520e6eb9146e655fbf9b824433d9d532194667069d9aa45696aae9684b", [:rebar3], [], "hexpm", "8eb136ccaf3948d329b8d1c3278ad2e17e2a7319801bc4cc2da6db278204eee4"},
"eternal": {:hex, :eternal, "1.2.2", "d1641c86368de99375b98d183042dd6c2b234262b8d08dfd72b9eeaafc2a1abd", [:mix], [], "hexpm", "2c9fe32b9c3726703ba5e1d43a1d255a4f3f2d8f8f9bc19f094c7cb1a7a9e782"},
@ -43,9 +43,9 @@
"ex_cldr_currencies": {:hex, :ex_cldr_currencies, "2.15.1", "e92ba17c41e7405b7784e0e65f406b5f17cfe313e0e70de9befd653e12854822", [:mix], [{:ex_cldr, "~> 2.34", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "31df8bd37688340f8819bdd770eb17d659652078d34db632b85d4a32864d6a25"},
"ex_cldr_dates_times": {:hex, :ex_cldr_dates_times, "2.16.0", "d9848a5de83b6f1bcba151cc43d63b5c6311813cd605b1df1afd896dfdd21001", [:mix], [{:calendar_interval, "~> 0.2", [hex: :calendar_interval, repo: "hexpm", optional: true]}, {:ex_cldr_calendars, "~> 1.22", [hex: :ex_cldr_calendars, repo: "hexpm", optional: false]}, {:ex_cldr_numbers, "~> 2.31", [hex: :ex_cldr_numbers, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:tz, "~> 0.26", [hex: :tz, repo: "hexpm", optional: true]}], "hexpm", "0f2f250d479cadda4e0ef3a5e3d936ae7ba1a3f1199db6791e284e86203495b1"},
"ex_cldr_languages": {:hex, :ex_cldr_languages, "0.3.3", "9787002803552b15a7ade19496c9e46fc921baca992ea80d0394e11fe3acea45", [:mix], [{:ex_cldr, "~> 2.25", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "22fb1fef72b7b4b4872d243b34e7b83734247a78ad87377986bf719089cc447a"},
"ex_cldr_numbers": {:hex, :ex_cldr_numbers, "2.32.3", "b631ff94c982ec518e46bf4736000a30a33d6b58facc085d5f240305f512ad4a", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:digital_token, "~> 0.3 or ~> 1.0", [hex: :digital_token, repo: "hexpm", optional: false]}, {:ex_cldr, "~> 2.37", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:ex_cldr_currencies, ">= 2.14.2", [hex: :ex_cldr_currencies, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "7b626ff1e59a0ec9c3c5db5ce9ca91a6995e2ab56426b71f3cbf67181ea225f5"},
"ex_cldr_numbers": {:hex, :ex_cldr_numbers, "2.32.4", "5562148dfc631b04712983975093d2aac29df30b3bf2f7257e0c94b85b72e91b", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:digital_token, "~> 0.3 or ~> 1.0", [hex: :digital_token, repo: "hexpm", optional: false]}, {:ex_cldr, "~> 2.37", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:ex_cldr_currencies, ">= 2.14.2", [hex: :ex_cldr_currencies, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "6fd5a82f0785418fa8b698c0be2b1845dff92b77f1b3172c763d37868fb503d2"},
"ex_cldr_plugs": {:hex, :ex_cldr_plugs, "1.3.1", "ae58748df815ad21b8618830374a28b2ab593230e5df70ed9f647e953a884bec", [:mix], [{:ex_cldr, "~> 2.37", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:gettext, "~> 0.19", [hex: :gettext, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "4f7b4a5fe061734cef7b62ff29118ed6ac72698cdd7bcfc97495db73611fe0fe"},
"ex_doc": {:hex, :ex_doc, "0.31.0", "06eb1dfd787445d9cab9a45088405593dd3bb7fe99e097eaa71f37ba80c7a676", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.1", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "5350cafa6b7f77bdd107aa2199fe277acf29d739aba5aee7e865fc680c62a110"},
"ex_doc": {:hex, :ex_doc, "0.31.1", "8a2355ac42b1cc7b2379da9e40243f2670143721dd50748bf6c3b1184dae2089", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.1", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "3178c3a407c557d8343479e1ff117a96fd31bafe52a039079593fb0524ef61b0"},
"ex_ical": {:hex, :ex_ical, "0.2.0", "4b928b554614704016cc0c9ee226eb854da9327a1cc460457621ceacb1ac29a6", [:mix], [{:timex, "~> 3.1", [hex: :timex, repo: "hexpm", optional: false]}], "hexpm", "db76473b2ae0259e6633c6c479a5a4d8603f09497f55c88f9ef4d53d2b75befb"},
"ex_machina": {:hex, :ex_machina, "2.7.0", "b792cc3127fd0680fecdb6299235b4727a4944a09ff0fa904cc639272cd92dc7", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm", "419aa7a39bde11894c87a615c4ecaa52d8f107bbdd81d810465186f783245bf8"},
"ex_optimizer": {:hex, :ex_optimizer, "0.1.1", "62da37e206fc2233ff7a4e54e40eae365c40f96c81992fcd15b782eb25169b80", [:mix], [{:file_info, "~> 0.0.4", [hex: :file_info, repo: "hexpm", optional: false]}], "hexpm", "e6f5c059bcd58b66be2f6f257fdc4f69b74b0fa5c9ddd669486af012e4b52286"},
@ -55,11 +55,11 @@
"exkismet": {:git, "https://github.com/tcitworld/exkismet.git", "8b5485fde00fafbde20f315bec387a77f7358334", []},
"expo": {:hex, :expo, "0.5.1", "249e826a897cac48f591deba863b26c16682b43711dd15ee86b92f25eafd96d9", [:mix], [], "hexpm", "68a4233b0658a3d12ee00d27d37d856b1ba48607e7ce20fd376958d0ba6ce92b"},
"export": {:hex, :export, "0.1.1", "6dfd268b0692428f89b9285859a2dc02b6dcd2e8fdfbca34ac6e6a331351df91", [:mix], [{:erlport, "~> 0.9", [hex: :erlport, repo: "hexpm", optional: false]}], "hexpm", "3da7444ff4053f1824352f4bdb13fbd2c28c93c2011786fb686b649fdca1021f"},
"fast_html": {:hex, :fast_html, "2.2.0", "6c5ef1be087a4ed613b0379c13f815c4d11742b36b67bb52cee7859847c84520", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}], "hexpm", "064c4f23b4a6168f9187dac8984b056f2c531bb0787f559fd6a8b34b38aefbae"},
"fast_html": {:hex, :fast_html, "2.3.0", "08c1d8ead840dd3060ba02c761bed9f37f456a1ddfe30bcdcfee8f651cec06a6", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}], "hexpm", "f18e3c7668f82d3ae0b15f48d48feeb257e28aa5ab1b0dbf781c7312e5da029d"},
"fast_sanitize": {:hex, :fast_sanitize, "0.2.3", "67b93dfb34e302bef49fec3aaab74951e0f0602fd9fa99085987af05bd91c7a5", [:mix], [{:fast_html, "~> 2.0", [hex: :fast_html, repo: "hexpm", optional: false]}, {:plug, "~> 1.8", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "e8ad286d10d0386e15d67d0ee125245ebcfbc7d7290b08712ba9013c8c5e56e2"},
"file_info": {:hex, :file_info, "0.0.4", "2e0e77f211e833f38ead22cb29ce53761d457d80b3ffe0ffe0eb93880b0963b2", [:mix], [{:mimetype_parser, "~> 0.1.2", [hex: :mimetype_parser, repo: "hexpm", optional: false]}], "hexpm", "50e7ad01c2c8b9339010675fe4dc4a113b8d6ca7eddce24d1d74fd0e762781a5"},
"file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"},
"floki": {:hex, :floki, "0.35.2", "87f8c75ed8654b9635b311774308b2760b47e9a579dabf2e4d5f1e1d42c39e0b", [:mix], [], "hexpm", "6b05289a8e9eac475f644f09c2e4ba7e19201fd002b89c28c1293e7bd16773d9"},
"floki": {:hex, :floki, "0.35.3", "0c8c6234aa71cb2b069cf801e8f8f30f8d096eb452c3dae2ccc409510ec32720", [:mix], [], "hexpm", "6d9f07f3fc76599f3b66c39f4a81ac62c8f4d9631140268db92aacad5d0e56d4"},
"gen_smtp": {:hex, :gen_smtp, "1.2.0", "9cfc75c72a8821588b9b9fe947ae5ab2aed95a052b81237e0928633a13276fd3", [:rebar3], [{:ranch, ">= 1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "5ee0375680bca8f20c4d85f58c2894441443a743355430ff33a783fe03296779"},
"geo": {:hex, :geo, "3.6.0", "00c9c6338579f67e91cd5950af4ae2eb25cdce0c3398718c232539f61625d0bd", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "1dbdebf617183b54bc3c8ad7a36531a9a76ada8ca93f75f573b0ae94006168da"},
"geo_postgis": {:hex, :geo_postgis, "3.5.0", "e3675b6276b8c2166dc20a6fa9d992eb73c665de2b09b666d09c7824dc8a8300", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:geo, "~> 3.5", [hex: :geo, repo: "hexpm", optional: false]}, {:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: true]}, {:poison, "~> 2.2 or ~> 3.0 or ~> 4.0 or ~> 5.0", [hex: :poison, repo: "hexpm", optional: true]}, {:postgrex, ">= 0.0.0", [hex: :postgrex, repo: "hexpm", optional: false]}], "hexpm", "0bebc5b00f8b11835066bd6213fbeeec03704b4a1c206920b81c1ec2201d185f"},
@ -71,14 +71,14 @@
"guardian_db": {:hex, :guardian_db, "3.0.0", "c42902e3f1af1ba1e2d0c10913b926a1421f3a7e38eb4fc382b715c17489abdb", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.1", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:guardian, "~> 1.0 or ~> 2.0", [hex: :guardian, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.13", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "9c2ec4278efa34f9f1cc6ba795e552d41fdc7ffba5319d67eeb533b89392d183"},
"guardian_phoenix": {:hex, :guardian_phoenix, "2.0.1", "89a817265af09a6ddf7cb1e77f17ffca90cea2db10ff888375ef34502b2731b1", [:mix], [{:guardian, "~> 2.0", [hex: :guardian, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.3", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "21f439246715192b231f228680465d1ed5fbdf01555a4a3b17165532f5f9a08c"},
"hackney": {:hex, :hackney, "1.20.1", "8d97aec62ddddd757d128bfd1df6c5861093419f8f7a4223823537bad5d064e2", [:rebar3], [{:certifi, "~> 2.12.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "fe9094e5f1a2a2c0a7d10918fee36bfec0ec2a979994cff8cfe8058cd9af38e3"},
"hammer": {:hex, :hammer, "6.1.0", "f263e3c3e9946bd410ea0336b2abe0cb6260af4afb3a221e1027540706e76c55", [:make, :mix], [{:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: false]}], "hexpm", "b47e415a562a6d072392deabcd58090d8a41182cf9044cdd6b0d0faaaf68ba57"},
"hammer": {:hex, :hammer, "6.2.0", "956e578f210ee67f7801caf7109b0e1145d2dad77ed5a0e5c0041a04739ede36", [:mix], [{:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: false]}], "hexpm", "1431a30e1f9c816e0fc58d2587de2d5f4c709b74bf81be77515dc902e35bb3a7"},
"haversine": {:hex, :haversine, "0.1.0", "14240e90dae07c9459f538d12a811492f655d95fc68f999403503b4f6c4ec522", [:mix], [], "hexpm", "54dc48e895bc18a59437a37026c873634e17b648a64cb87bfafb96f64d607060"},
"html_entities": {:hex, :html_entities, "0.5.2", "9e47e70598da7de2a9ff6af8758399251db6dbb7eebe2b013f2bbd2515895c3c", [:mix], [], "hexpm", "c53ba390403485615623b9531e97696f076ed415e8d8058b1dbaa28181f4fdcc"},
"http_signatures": {:hex, :http_signatures, "0.1.2", "ed1cc7043abcf5bb4f30d68fb7bad9d618ec1a45c4ff6c023664e78b67d9c406", [:mix], [], "hexpm", "f08aa9ac121829dae109d608d83c84b940ef2f183ae50f2dd1e9a8bc619d8be7"},
"httpoison": {:hex, :httpoison, "1.8.2", "9eb9c63ae289296a544842ef816a85d881d4a31f518a0fec089aaa744beae290", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "2bb350d26972e30c96e2ca74a1aaf8293d61d0742ff17f01e0279fef11599921"},
"icalendar": {:git, "https://github.com/tcitworld/icalendar.git", "1033d922c82a7223db0ec138e2316557b70ff49f", []},
"idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"},
"inet_cidr": {:hex, :inet_cidr, "1.0.4", "a05744ab7c221ca8e395c926c3919a821eb512e8f36547c062f62c4ca0cf3d6e", [:mix], [], "hexpm", "64a2d30189704ae41ca7dbdd587f5291db5d1dda1414e0774c29ffc81088c1bc"},
"inet_cidr": {:hex, :inet_cidr, "1.0.8", "d26bb7bdbdf21ae401ead2092bf2bb4bf57fe44a62f5eaa5025280720ace8a40", [:mix], [], "hexpm", "d5b26da66603bb56c933c65214c72152f0de9a6ea53618b56d63302a68f6a90e"},
"ip_reserved": {:hex, :ip_reserved, "0.1.1", "e5112d71f1abf05207f82fd9597d369a5fde1e0b6d1bbe77c02a99bb26ecdc33", [:mix], [{:inet_cidr, "~> 1.0.0", [hex: :inet_cidr, repo: "hexpm", optional: false]}], "hexpm", "55fcd2b6e211caef09ea3f54ef37d43030bec486325d12fe865ab5ed8140a4fe"},
"jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"},
"jose": {:hex, :jose, "1.11.6", "613fda82552128aa6fb804682e3a616f4bc15565a048dabd05b1ebd5827ed965", [:mix, :rebar3], [], "hexpm", "6275cb75504f9c1e60eeacb771adfeee4905a9e182103aa59b53fed651ff9738"},
@ -87,7 +87,7 @@
"linkify": {:hex, :linkify, "0.5.3", "5f8143d8f61f5ff08d3aeeff47ef6509492b4948d8f08007fbf66e4d2246a7f2", [:mix], [], "hexpm", "3ef35a1377d47c25506e07c1c005ea9d38d700699d92ee92825f024434258177"},
"makeup": {:hex, :makeup, "1.1.1", "fa0bc768698053b2b3869fa8a62616501ff9d11a562f3ce39580d60860c3a55e", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "5dc62fbdd0de44de194898b6710692490be74baa02d9d108bc29f007783b0b48"},
"makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"},
"makeup_erlang": {:hex, :makeup_erlang, "0.1.3", "d684f4bac8690e70b06eb52dad65d26de2eefa44cd19d64a8095e1417df7c8fd", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "b78dc853d2e670ff6390b605d807263bf606da3c82be37f9d7f68635bd886fc9"},
"makeup_erlang": {:hex, :makeup_erlang, "0.1.4", "29563475afa9b8a2add1b7a9c8fb68d06ca7737648f28398e04461f008b69521", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f4ed47ecda66de70dd817698a703f8816daa91272e7e45812469498614ae8b29"},
"meck": {:hex, :meck, "0.9.2", "85ccbab053f1db86c7ca240e9fc718170ee5bda03810a6292b5306bf31bae5f5", [:rebar3], [], "hexpm", "81344f561357dc40a8344afa53767c32669153355b626ea9fcbc8da6b3045826"},
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"},
"mime": {:hex, :mime, "2.0.5", "dc34c8efd439abe6ae0343edbb8556f4d63f178594894720607772a041b04b02", [:mix], [], "hexpm", "da0d64a365c45bc9935cc5c8a7fc5e49a0e0f9932a761c55d6c52b142780a05c"},
@ -103,20 +103,20 @@
"nimble_pool": {:hex, :nimble_pool, "0.2.6", "91f2f4c357da4c4a0a548286c84a3a28004f68f05609b4534526871a22053cde", [:mix], [], "hexpm", "1c715055095d3f2705c4e236c18b618420a35490da94149ff8b580a2144f653f"},
"oauth2": {:hex, :oauth2, "2.1.0", "beb657f393814a3a7a8a15bd5e5776ecae341fd344df425342a3b6f1904c2989", [:mix], [{:tesla, "~> 1.5", [hex: :tesla, repo: "hexpm", optional: false]}], "hexpm", "8ac07f85b3307dd1acfeb0ec852f64161b22f57d0ce0c15e616a1dfc8ebe2b41"},
"oauther": {:hex, :oauther, "1.3.0", "82b399607f0ca9d01c640438b34d74ebd9e4acd716508f868e864537ecdb1f76", [:mix], [], "hexpm", "78eb888ea875c72ca27b0864a6f550bc6ee84f2eeca37b093d3d833fbcaec04e"},
"oban": {:hex, :oban, "2.17.1", "42d6221a1c17b63d81c19e3bad9ea82b59e39c47c1f9b7670ee33628569a449b", [:mix], [{:ecto_sql, "~> 3.6", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:ecto_sqlite3, "~> 0.9", [hex: :ecto_sqlite3, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c02686ada7979b00e259c0efbafeae2749f8209747b3460001fe695c5bdbeee6"},
"oban": {:hex, :oban, "2.17.3", "ddfd5710aadcd550d2e174c8d73ce5f1865601418cf54a91775f20443fb832b7", [:mix], [{:ecto_sql, "~> 3.6", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:ecto_sqlite3, "~> 0.9", [hex: :ecto_sqlite3, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "452eada8bfe0d0fefd0740ab5fa8cf3ef6c375df0b4a3c3805d179022a04738a"},
"paasaa": {:hex, :paasaa, "0.6.0", "07c8ed81010caa25db351d474f0c053072c809821c60f9646f7b1547bec52f6d", [:mix], [], "hexpm", "732ddfc21bac0831edb26aec468af3ec2b8997d74f6209810b1cc53199c29f2e"},
"parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"},
"phoenix": {:hex, :phoenix, "1.7.10", "02189140a61b2ce85bb633a9b6fd02dff705a5f1596869547aeb2b2b95edd729", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "cf784932e010fd736d656d7fead6a584a4498efefe5b8227e9f383bf15bb79d0"},
"phoenix_ecto": {:hex, :phoenix_ecto, "4.4.3", "86e9878f833829c3f66da03d75254c155d91d72a201eb56ae83482328dc7ca93", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "d36c401206f3011fefd63d04e8ef626ec8791975d9d107f9a0817d426f61ac07"},
"phoenix_html": {:hex, :phoenix_html, "3.3.3", "380b8fb45912b5638d2f1d925a3771b4516b9a78587249cabe394e0a5d579dc9", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "923ebe6fec6e2e3b3e569dfbdc6560de932cd54b000ada0208b5f45024bdd76c"},
"phoenix_live_reload": {:hex, :phoenix_live_reload, "1.4.1", "2aff698f5e47369decde4357ba91fc9c37c6487a512b41732818f2204a8ef1d3", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "9bffb834e7ddf08467fe54ae58b5785507aaba6255568ae22b4d46e2bb3615ab"},
"phoenix_live_view": {:hex, :phoenix_live_view, "0.20.3", "8b6406bc0a451f295407d7acff7f234a6314be5bbe0b3f90ed82b07f50049878", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.15", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a8e4385e05618b424779f894ed2df97d3c7518b7285fcd11979077ae6226466b"},
"phoenix_live_view": {:hex, :phoenix_live_view, "0.20.4", "0dc21e89dbf5b1f3a69090a92d1a2724bfa951d5cbccff6c5b318e12eac107e3", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.15", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d8930c9c79dd25775646874abdf3d8d24356b88d58fa14f637c8e3418d36bce3"},
"phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.3", "3168d78ba41835aecad272d5e8cd51aa87a7ac9eb836eabc42f6e57538e3731d", [:mix], [], "hexpm", "bba06bc1dcfd8cb086759f0edc94a8ba2bc8896d5331a1e2c2902bf8e36ee502"},
"phoenix_swoosh": {:hex, :phoenix_swoosh, "1.2.0", "a544d83fde4a767efb78f45404a74c9e37b2a9c5ea3339692e65a6966731f935", [:mix], [{:finch, "~> 0.8", [hex: :finch, repo: "hexpm", optional: true]}, {:hackney, "~> 1.10", [hex: :hackney, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6", [hex: :phoenix, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_view, "~> 1.0 or ~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:swoosh, "~> 1.5", [hex: :swoosh, repo: "hexpm", optional: false]}], "hexpm", "e88d117251e89a16b92222415a6d87b99a96747ddf674fc5c7631de734811dba"},
"phoenix_swoosh": {:hex, :phoenix_swoosh, "1.2.1", "b74ccaa8046fbc388a62134360ee7d9742d5a8ae74063f34eb050279de7a99e1", [:mix], [{:finch, "~> 0.8", [hex: :finch, repo: "hexpm", optional: true]}, {:hackney, "~> 1.10", [hex: :hackney, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6", [hex: :phoenix, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_view, "~> 1.0 or ~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:swoosh, "~> 1.5", [hex: :swoosh, repo: "hexpm", optional: false]}], "hexpm", "4000eeba3f9d7d1a6bf56d2bd56733d5cadf41a7f0d8ffe5bb67e7d667e204a2"},
"phoenix_template": {:hex, :phoenix_template, "1.0.4", "e2092c132f3b5e5b2d49c96695342eb36d0ed514c5b252a77048d5969330d639", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "2c0c81f0e5c6753faf5cca2f229c9709919aba34fab866d3bc05060c9c444206"},
"phoenix_view": {:hex, :phoenix_view, "2.0.3", "4d32c4817fce933693741deeb99ef1392619f942633dde834a5163124813aad3", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}], "hexpm", "cd34049af41be2c627df99cd4eaa71fc52a328c0c3d8e7d4aa28f880c30e7f64"},
"plug": {:hex, :plug, "1.15.2", "94cf1fa375526f30ff8770837cb804798e0045fd97185f0bb9e5fcd858c792a3", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "02731fa0c2dcb03d8d21a1d941bdbbe99c2946c0db098eee31008e04c6283615"},
"plug_cowboy": {:hex, :plug_cowboy, "2.6.1", "9a3bbfceeb65eff5f39dab529e5cd79137ac36e913c02067dba3963a26efe9b2", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "de36e1a21f451a18b790f37765db198075c25875c64834bcc82d90b309eb6613"},
"plug": {:hex, :plug, "1.15.3", "712976f504418f6dff0a3e554c40d705a9bcf89a7ccef92fc6a5ef8f16a30a97", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "cc4365a3c010a56af402e0809208873d113e9c38c401cabd88027ef4f5c01fd2"},
"plug_cowboy": {:hex, :plug_cowboy, "2.6.2", "753611b23b29231fb916b0cdd96028084b12aff57bfd7b71781bd04b1dbeb5c9", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "951ed2433df22f4c97b85fdb145d4cee561f36b74854d64c06d896d7cd2921a7"},
"plug_crypto": {:hex, :plug_crypto, "2.0.0", "77515cc10af06645abbfb5e6ad7a3e9714f805ae118fa1a70205f80d2d70fe73", [:mix], [], "hexpm", "53695bae57cc4e54566d993eb01074e4d894b65a3766f1c43e2c61a1b0f45ea9"},
"poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm", "dad79704ce5440f3d5a3681c8590b9dc25d1a561e8f5a9c995281012860901e3"},
"postgrex": {:hex, :postgrex, "0.17.4", "5777781f80f53b7c431a001c8dad83ee167bcebcf3a793e3906efff680ab62b3", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "6458f7d5b70652bc81c3ea759f91736c16a31be000f306d3c64bcdfe9a18b3cc"},
@ -127,7 +127,7 @@
"replug": {:hex, :replug, "0.1.0", "61d35f8c873c0078a23c49579a48f36e45789414b1ec0daee3fd5f4e34221f23", [:mix], [{:plug, "~> 1.8", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "f71f7a57e944e854fe4946060c6964098e53958074c69fb844b96e0bd58cfa60"},
"sentry": {:hex, :sentry, "8.1.0", "8d235b62fce5f8e067ea1644e30939405b71a5e1599d9529ff82899d11d03f2b", [:mix], [{:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.6", [hex: :plug, repo: "hexpm", optional: true]}, {:plug_cowboy, "~> 2.3", [hex: :plug_cowboy, repo: "hexpm", optional: true]}], "hexpm", "f9fc7641ef61e885510f5e5963c2948b9de1de597c63f781e9d3d6c9c8681ab4"},
"shortuuid": {:hex, :shortuuid, "3.0.0", "028684d9eeed0ad4b800e8481afd854e1a61c526f35952455b2ee4248601e7b8", [:mix], [], "hexpm", "dfd8f80f514cbb91622cb83f4ac0d6e2f06d98cc6d4aeba94444a212289d0d39"},
"sitemapper": {:hex, :sitemapper, "0.7.0", "4aee7930327a9a01b1c9b81d1d42f60c1a295e9f420108eb2d130c317415abd7", [:mix], [{:ex_aws_s3, "~> 2.0", [hex: :ex_aws_s3, repo: "hexpm", optional: true]}, {:xml_builder, "~> 2.1", [hex: :xml_builder, repo: "hexpm", optional: false]}], "hexpm", "60f7a684e5e9fe7f10ac5b69f48b0be2bcbba995afafcb3c143fc0c8ef1f223f"},
"sitemapper": {:hex, :sitemapper, "0.8.0", "50c8c85ed38c013829ce700e8a8d195a2faf4aed8685659b14529dcb6f91fee0", [:mix], [{:ex_aws_s3, "~> 2.0", [hex: :ex_aws_s3, repo: "hexpm", optional: true]}, {:xml_builder, "~> 2.1", [hex: :xml_builder, repo: "hexpm", optional: false]}], "hexpm", "7cd42b454035da457151c9b6a314b688b5bbe5383add95badc65d013c25989c5"},
"sleeplocks": {:hex, :sleeplocks, "1.1.2", "d45aa1c5513da48c888715e3381211c859af34bee9b8290490e10c90bb6ff0ca", [:rebar3], [], "hexpm", "9fe5d048c5b781d6305c1a3a0f40bb3dfc06f49bf40571f3d2d0c57eaa7f59a5"},
"slugger": {:hex, :slugger, "0.3.0", "efc667ab99eee19a48913ccf3d038b1fb9f165fa4fbf093be898b8099e61b6ed", [:mix], [], "hexpm", "20d0ded0e712605d1eae6c5b4889581c3460d92623a930ddda91e0e609b5afba"},
"slugify": {:hex, :slugify, "1.3.1", "0d3b8b7e5c1eeaa960e44dce94382bee34a39b3ea239293e457a9c5b47cc6fd3", [:mix], [], "hexpm", "cb090bbeb056b312da3125e681d98933a360a70d327820e4b7f91645c4d8be76"},
@ -135,14 +135,14 @@
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"},
"struct_access": {:hex, :struct_access, "1.1.2", "a42e6ceedd9b9ea090ee94a6da089d56e16f374dbbc010c3eebdf8be17df286f", [:mix], [], "hexpm", "e4c411dcc0226081b95709909551fc92b8feb1a3476108348ea7e3f6c12e586a"},
"sweet_xml": {:hex, :sweet_xml, "0.7.4", "a8b7e1ce7ecd775c7e8a65d501bc2cd933bff3a9c41ab763f5105688ef485d08", [:mix], [], "hexpm", "e7c4b0bdbf460c928234951def54fe87edf1a170f6896675443279e2dbeba167"},
"swoosh": {:hex, :swoosh, "1.14.3", "949e6bf6dd469449238a94ec6f19ec10b63fc8753de7f3ebe3d3aeaf772f4c6b", [:mix], [{:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: true]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:req, "~> 0.4 or ~> 1.0", [hex: :req, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "6c565103fc8f086bdd96e5c948660af8e20922b7a90a75db261f06a34f805c8b"},
"swoosh": {:hex, :swoosh, "1.15.2", "490ea85a98e8fb5178c07039e0d8519839e38127724a58947a668c00db7574ee", [:mix], [{:bandit, ">= 1.0.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: true]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:req, "~> 0.4 or ~> 1.0", [hex: :req, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9f7739c02f6c7c0ca82ee397f3bfe0465dbe4c8a65372ac2a5584bf147dd5831"},
"telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"},
"tesla": {:hex, :tesla, "1.8.0", "d511a4f5c5e42538d97eef7c40ec4f3e44effdc5068206f42ed859e09e51d1fd", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.13", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, ">= 1.0.0", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.2", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:msgpax, "~> 2.3", [hex: :msgpax, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "10501f360cd926a309501287470372af1a6e1cbed0f43949203a4c13300bc79f"},
"timex": {:hex, :timex, "3.7.11", "bb95cb4eb1d06e27346325de506bcc6c30f9c6dea40d1ebe390b262fad1862d1", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.20", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 1.1", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "8b9024f7efbabaf9bd7aa04f65cf8dcd7c9818ca5737677c7b76acbc6a94d1aa"},
"tls_certificate_check": {:hex, :tls_certificate_check, "1.21.0", "042ab2c0c860652bc5cf69c94e3a31f96676d14682e22ec7813bd173ceff1788", [:rebar3], [{:ssl_verify_fun, "~> 1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "6cee6cffc35a390840d48d463541d50746a7b0e421acaadb833cfc7961e490e7"},
"tz_world": {:hex, :tz_world, "1.3.2", "15d331ad1ff22735dfcc8c98bfc7b2a9fdc17f1f071e31e21cdafe2d9318a300", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:certifi, "~> 2.5", [hex: :certifi, repo: "hexpm", optional: true]}, {:geo, "~> 1.0 or ~> 2.0 or ~> 3.3", [hex: :geo, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "d1a345e07b3378c4c902ad54fbd5d54c8c3dd55dba883b7407fe57bcec45ff2a"},
"tzdata": {:hex, :tzdata, "1.1.1", "20c8043476dfda8504952d00adac41c6eda23912278add38edc140ae0c5bcc46", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "a69cec8352eafcd2e198dea28a34113b60fdc6cb57eb5ad65c10292a6ba89787"},
"ueberauth": {:hex, :ueberauth, "0.10.5", "806adb703df87e55b5615cf365e809f84c20c68aa8c08ff8a416a5a6644c4b02", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "3efd1f31d490a125c7ed453b926f7c31d78b97b8a854c755f5c40064bf3ac9e1"},
"ueberauth": {:hex, :ueberauth, "0.10.7", "5a31cbe11e7ce5c7484d745dc9e1f11948e89662f8510d03c616de03df581ebd", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "0bccf73e2ffd6337971340832947ba232877aa8122dba4c95be9f729c8987377"},
"ueberauth_cas": {:hex, :ueberauth_cas, "2.3.1", "df45a1f2c5df8bc80191cbca4baeeed808d697702ec5ebe5bd5d5a264481752f", [:mix], [{:httpoison, "~> 1.8", [hex: :httpoison, repo: "hexpm", optional: false]}, {:sweet_xml, "~> 0.7", [hex: :sweet_xml, repo: "hexpm", optional: false]}, {:ueberauth, "~> 0.6", [hex: :ueberauth, repo: "hexpm", optional: false]}], "hexpm", "5068ae2b9e217c2f05aa9a67483a6531e21ba0be9a6f6c8749bb7fd1599be321"},
"ueberauth_discord": {:hex, :ueberauth_discord, "0.7.0", "463f6dfe1ed10a76739331ce8e1dd3600ab611f10524dd828eb3aa50e76e9d43", [:mix], [{:oauth2, "~> 1.0 or ~> 2.0", [hex: :oauth2, repo: "hexpm", optional: false]}, {:ueberauth, "~> 0.7", [hex: :ueberauth, repo: "hexpm", optional: false]}], "hexpm", "d6f98ef91abb4ddceada4b7acba470e0e68c4d2de9735ff2f24172a8e19896b4"},
"ueberauth_facebook": {:hex, :ueberauth_facebook, "0.10.0", "0d607fbd1b7c6e0449981571027d869c2d156b8ad20c42e3672346678c05ccf1", [:mix], [{:oauth2, "~> 1.0 or ~> 2.0", [hex: :oauth2, repo: "hexpm", optional: false]}, {:ueberauth, "~> 0.7", [hex: :ueberauth, repo: "hexpm", optional: false]}], "hexpm", "bf8ce5d66b1c50da8abff77e8086c1b710bdde63f4acaef19a651ba43a9537a8"},

74
mix.nix
View file

@ -205,12 +205,12 @@ let
cowboy = buildErlangMk rec {
name = "cowboy";
version = "2.10.0";
version = "2.11.0";
src = fetchHex {
pkg = "cowboy";
version = "${version}";
sha256 = "3afdccb7183cc6f143cb14d3cf51fa00e53db9ec80cdcd525482f5e99bc41d6b";
sha256 = "0fa395437f1b0e104e0e00999f39d2ac5f4082ac5049b67a5b6d56ecc31b1403";
};
beamDeps = [ cowlib ranch ];
@ -244,12 +244,12 @@ let
credo = buildMix rec {
name = "credo";
version = "1.7.2";
version = "1.7.3";
src = fetchHex {
pkg = "credo";
version = "${version}";
sha256 = "dd15d6fbc280f6cf9b269f41df4e4992dee6615939653b164ef951f60afcb68e";
sha256 = "35ea675a094c934c22fb1dca3696f3c31f2728ae6ef5a53b5d648c11180a4535";
};
beamDeps = [ bunt file_system jason ];
@ -465,15 +465,15 @@ let
elixir_make = buildMix rec {
name = "elixir_make";
version = "0.7.7";
version = "0.7.8";
src = fetchHex {
pkg = "elixir_make";
version = "${version}";
sha256 = "5bc19fff950fad52bbe5f211b12db9ec82c6b34a9647da0c2224b8b8464c7e6c";
sha256 = "7a71945b913d37ea89b06966e1342c85cfe549b15e6d6d081e8081c493062c07";
};
beamDeps = [ castore ];
beamDeps = [ castore certifi ];
};
erlex = buildMix rec {
@ -582,12 +582,12 @@ let
ex_cldr_numbers = buildMix rec {
name = "ex_cldr_numbers";
version = "2.32.3";
version = "2.32.4";
src = fetchHex {
pkg = "ex_cldr_numbers";
version = "${version}";
sha256 = "7b626ff1e59a0ec9c3c5db5ce9ca91a6995e2ab56426b71f3cbf67181ea225f5";
sha256 = "6fd5a82f0785418fa8b698c0be2b1845dff92b77f1b3172c763d37868fb503d2";
};
beamDeps = [ decimal digital_token ex_cldr ex_cldr_currencies jason ];
@ -608,12 +608,12 @@ let
ex_doc = buildMix rec {
name = "ex_doc";
version = "0.31.0";
version = "0.31.1";
src = fetchHex {
pkg = "ex_doc";
version = "${version}";
sha256 = "5350cafa6b7f77bdd107aa2199fe277acf29d739aba5aee7e865fc680c62a110";
sha256 = "3178c3a407c557d8343479e1ff117a96fd31bafe52a039079593fb0524ef61b0";
};
beamDeps = [ earmark_parser makeup_elixir makeup_erlang ];
@ -725,12 +725,12 @@ let
fast_html = buildMix rec {
name = "fast_html";
version = "2.2.0";
version = "2.3.0";
src = fetchHex {
pkg = "fast_html";
version = "${version}";
sha256 = "064c4f23b4a6168f9187dac8984b056f2c531bb0787f559fd6a8b34b38aefbae";
sha256 = "f18e3c7668f82d3ae0b15f48d48feeb257e28aa5ab1b0dbf781c7312e5da029d";
};
beamDeps = [ elixir_make nimble_pool ];
@ -777,12 +777,12 @@ let
floki = buildMix rec {
name = "floki";
version = "0.35.2";
version = "0.35.3";
src = fetchHex {
pkg = "floki";
version = "${version}";
sha256 = "6b05289a8e9eac475f644f09c2e4ba7e19201fd002b89c28c1293e7bd16773d9";
sha256 = "6d9f07f3fc76599f3b66c39f4a81ac62c8f4d9631140268db92aacad5d0e56d4";
};
beamDeps = [];
@ -933,12 +933,12 @@ let
hammer = buildMix rec {
name = "hammer";
version = "6.1.0";
version = "6.2.0";
src = fetchHex {
pkg = "hammer";
version = "${version}";
sha256 = "b47e415a562a6d072392deabcd58090d8a41182cf9044cdd6b0d0faaaf68ba57";
sha256 = "1431a30e1f9c816e0fc58d2587de2d5f4c709b74bf81be77515dc902e35bb3a7";
};
beamDeps = [ poolboy ];
@ -1011,12 +1011,12 @@ let
inet_cidr = buildMix rec {
name = "inet_cidr";
version = "1.0.4";
version = "1.0.8";
src = fetchHex {
pkg = "inet_cidr";
version = "${version}";
sha256 = "64a2d30189704ae41ca7dbdd587f5291db5d1dda1414e0774c29ffc81088c1bc";
sha256 = "d5b26da66603bb56c933c65214c72152f0de9a6ea53618b56d63302a68f6a90e";
};
beamDeps = [];
@ -1128,12 +1128,12 @@ let
makeup_erlang = buildMix rec {
name = "makeup_erlang";
version = "0.1.3";
version = "0.1.4";
src = fetchHex {
pkg = "makeup_erlang";
version = "${version}";
sha256 = "b78dc853d2e670ff6390b605d807263bf606da3c82be37f9d7f68635bd886fc9";
sha256 = "f4ed47ecda66de70dd817698a703f8816daa91272e7e45812469498614ae8b29";
};
beamDeps = [ makeup ];
@ -1336,12 +1336,12 @@ let
oban = buildMix rec {
name = "oban";
version = "2.17.1";
version = "2.17.3";
src = fetchHex {
pkg = "oban";
version = "${version}";
sha256 = "c02686ada7979b00e259c0efbafeae2749f8209747b3460001fe695c5bdbeee6";
sha256 = "452eada8bfe0d0fefd0740ab5fa8cf3ef6c375df0b4a3c3805d179022a04738a";
};
beamDeps = [ ecto_sql jason postgrex telemetry ];
@ -1427,12 +1427,12 @@ let
phoenix_live_view = buildMix rec {
name = "phoenix_live_view";
version = "0.20.3";
version = "0.20.4";
src = fetchHex {
pkg = "phoenix_live_view";
version = "${version}";
sha256 = "a8e4385e05618b424779f894ed2df97d3c7518b7285fcd11979077ae6226466b";
sha256 = "d8930c9c79dd25775646874abdf3d8d24356b88d58fa14f637c8e3418d36bce3";
};
beamDeps = [ jason phoenix phoenix_html phoenix_template phoenix_view plug telemetry ];
@ -1453,12 +1453,12 @@ let
phoenix_swoosh = buildMix rec {
name = "phoenix_swoosh";
version = "1.2.0";
version = "1.2.1";
src = fetchHex {
pkg = "phoenix_swoosh";
version = "${version}";
sha256 = "e88d117251e89a16b92222415a6d87b99a96747ddf674fc5c7631de734811dba";
sha256 = "4000eeba3f9d7d1a6bf56d2bd56733d5cadf41a7f0d8ffe5bb67e7d667e204a2";
};
beamDeps = [ hackney phoenix phoenix_html phoenix_view swoosh ];
@ -1492,12 +1492,12 @@ let
plug = buildMix rec {
name = "plug";
version = "1.15.2";
version = "1.15.3";
src = fetchHex {
pkg = "plug";
version = "${version}";
sha256 = "02731fa0c2dcb03d8d21a1d941bdbbe99c2946c0db098eee31008e04c6283615";
sha256 = "cc4365a3c010a56af402e0809208873d113e9c38c401cabd88027ef4f5c01fd2";
};
beamDeps = [ mime plug_crypto telemetry ];
@ -1505,12 +1505,12 @@ let
plug_cowboy = buildMix rec {
name = "plug_cowboy";
version = "2.6.1";
version = "2.6.2";
src = fetchHex {
pkg = "plug_cowboy";
version = "${version}";
sha256 = "de36e1a21f451a18b790f37765db198075c25875c64834bcc82d90b309eb6613";
sha256 = "951ed2433df22f4c97b85fdb145d4cee561f36b74854d64c06d896d7cd2921a7";
};
beamDeps = [ cowboy cowboy_telemetry plug ];
@ -1635,12 +1635,12 @@ let
sitemapper = buildMix rec {
name = "sitemapper";
version = "0.7.0";
version = "0.8.0";
src = fetchHex {
pkg = "sitemapper";
version = "${version}";
sha256 = "60f7a684e5e9fe7f10ac5b69f48b0be2bcbba995afafcb3c143fc0c8ef1f223f";
sha256 = "7cd42b454035da457151c9b6a314b688b5bbe5383add95badc65d013c25989c5";
};
beamDeps = [ xml_builder ];
@ -1739,12 +1739,12 @@ let
swoosh = buildMix rec {
name = "swoosh";
version = "1.14.3";
version = "1.15.2";
src = fetchHex {
pkg = "swoosh";
version = "${version}";
sha256 = "6c565103fc8f086bdd96e5c948660af8e20922b7a90a75db261f06a34f805c8b";
sha256 = "9f7739c02f6c7c0ca82ee397f3bfe0465dbe4c8a65372ac2a5584bf147dd5831";
};
beamDeps = [ cowboy gen_smtp hackney jason mime plug plug_cowboy telemetry ];
@ -1830,12 +1830,12 @@ let
ueberauth = buildMix rec {
name = "ueberauth";
version = "0.10.5";
version = "0.10.7";
src = fetchHex {
pkg = "ueberauth";
version = "${version}";
sha256 = "3efd1f31d490a125c7ed453b926f7c31d78b97b8a854c755f5c40064bf3ac9e1";
sha256 = "0bccf73e2ffd6337971340832947ba232877aa8122dba4c95be9f729c8987377";
};
beamDeps = [ plug ];

2808
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -15,7 +15,8 @@
"story:preview": "histoire preview",
"test": "vitest",
"coverage": "vitest run --coverage",
"pre-commit": "lint-staged"
"pre-commit": "lint-staged",
"postinstall": "patch-package"
},
"lint-staged": {
"**/*.{js,ts,vue}": [
@ -27,6 +28,7 @@
"mix credo"
]
},
"type": "module",
"dependencies": {
"@apollo/client": "^3.3.16",
"@framasoft/socket": "^1.0.0",
@ -77,7 +79,7 @@
"blurhash": "^2.0.0",
"date-fns": "^2.16.0",
"date-fns-tz": "^2.0.0",
"floating-vue": "^2.0.0-beta.24",
"floating-vue": "^5.0.0",
"graphql": "^16.8.1",
"graphql-tag": "^2.10.3",
"hammerjs": "^2.0.8",
@ -90,6 +92,7 @@
"lodash": "^4.17.11",
"ngeohash": "^0.6.3",
"p-debounce": "^4.0.0",
"patch-package": "^8.0.0",
"phoenix": "^1.6",
"postcss": "^8",
"register-service-worker": "^1.7.2",
@ -97,7 +100,7 @@
"tailwindcss": "^3",
"tippy.js": "^6.2.3",
"unfetch": "^5.0.0",
"vue": "^3.2.37",
"vue": "3.4.16",
"vue-i18n": "9",
"vue-material-design-icons": "^5.1.2",
"vue-matomo": "^4.1.0",
@ -121,9 +124,9 @@
"@types/ngeohash": "^0.6.2",
"@types/phoenix": "^1.5.2",
"@types/sanitize-html": "^2.5.0",
"@vitejs/plugin-vue": "^4.0.0",
"@vitest/coverage-v8": "^0.34.1",
"@vitest/ui": "^0.34.1",
"@vitejs/plugin-vue": "^5.0.0",
"@vitest/coverage-v8": "^1.2.2",
"@vitest/ui": "^1.2.2",
"@vue/eslint-config-prettier": "^9.0.0",
"@vue/eslint-config-typescript": "^12.0.0",
"@vue/test-utils": "^2.0.2",
@ -134,8 +137,8 @@
"eslint-plugin-vue": "^9.3.0",
"flush-promises": "^1.0.2",
"histoire": "^0.17.0",
"husky": "^8.0.3",
"jsdom": "^22.0.0",
"husky": "^9.0.10",
"jsdom": "^24.0.0",
"lint-staged": "^15.1.0",
"mock-apollo-client": "^1.1.0",
"prettier": "^3.0.0",
@ -143,10 +146,10 @@
"rollup-plugin-visualizer": "^5.7.1",
"sass": "^1.34.1",
"typescript": "~5.3.2",
"vite": "^4.5.0",
"vite": "^5.0.12",
"vite-plugin-pwa": "^0.17.0",
"vite-svg-loader": "^4.0.0",
"vitest": "^0.34.1",
"vitest": "^1.2.2",
"vue-i18n-extract": "^2.0.4",
"vue-router-mock": "^1.0.0"
}

View file

@ -0,0 +1,66 @@
diff --git a/node_modules/vue-i18n-extract/dist/vue-i18n-extract.modern.mjs b/node_modules/vue-i18n-extract/dist/vue-i18n-extract.modern.mjs
index 670733e..872d1af 100644
--- a/node_modules/vue-i18n-extract/dist/vue-i18n-extract.modern.mjs
+++ b/node_modules/vue-i18n-extract/dist/vue-i18n-extract.modern.mjs
@@ -38,7 +38,7 @@ var defaultConfig = {
};
function initCommand() {
- fs.writeFileSync(path.resolve(process.cwd(), './vue-i18n-extract.config.js'), `module.exports = ${JSON.stringify(defaultConfig, null, 2)}`);
+ fs.writeFileSync(path.resolve(process.cwd(), './vue-i18n-extract.config.cjs'), `module.exports = ${JSON.stringify(defaultConfig, null, 2)}`);
}
function resolveConfig() {
const argvOptions = cac().parse(process.argv, {
@@ -47,7 +47,7 @@ function resolveConfig() {
let options;
try {
- const pathToConfigFile = path.resolve(process.cwd(), './vue-i18n-extract.config.js'); // eslint-disable-next-line @typescript-eslint/no-var-requires
+ const pathToConfigFile = path.resolve(process.cwd(), './vue-i18n-extract.config.cjs'); // eslint-disable-next-line @typescript-eslint/no-var-requires
const configOptions = require(pathToConfigFile);
diff --git a/node_modules/vue-i18n-extract/dist/vue-i18n-extract.umd.js b/node_modules/vue-i18n-extract/dist/vue-i18n-extract.umd.js
index ca19c7a..11cb846 100644
--- a/node_modules/vue-i18n-extract/dist/vue-i18n-extract.umd.js
+++ b/node_modules/vue-i18n-extract/dist/vue-i18n-extract.umd.js
@@ -45,7 +45,7 @@
};
function initCommand() {
- fs__default["default"].writeFileSync(path__default["default"].resolve(process.cwd(), './vue-i18n-extract.config.js'), `module.exports = ${JSON.stringify(defaultConfig, null, 2)}`);
+ fs__default["default"].writeFileSync(path__default["default"].resolve(process.cwd(), './vue-i18n-extract.config.cjs'), `module.exports = ${JSON.stringify(defaultConfig, null, 2)}`);
}
function resolveConfig() {
const argvOptions = cac__default["default"]().parse(process.argv, {
@@ -54,7 +54,7 @@
let options;
try {
- const pathToConfigFile = path__default["default"].resolve(process.cwd(), './vue-i18n-extract.config.js'); // eslint-disable-next-line @typescript-eslint/no-var-requires
+ const pathToConfigFile = path__default["default"].resolve(process.cwd(), './vue-i18n-extract.config.cjs'); // eslint-disable-next-line @typescript-eslint/no-var-requires
const configOptions = require(pathToConfigFile);
diff --git a/node_modules/vue-i18n-extract/src/config-file/index.ts b/node_modules/vue-i18n-extract/src/config-file/index.ts
index 3db836f..744bd74 100644
--- a/node_modules/vue-i18n-extract/src/config-file/index.ts
+++ b/node_modules/vue-i18n-extract/src/config-file/index.ts
@@ -5,7 +5,7 @@ import defaultConfig from './vue-i18n-extract.config';
export function initCommand(): void {
fs.writeFileSync(
- path.resolve(process.cwd(), './vue-i18n-extract.config.js'),
+ path.resolve(process.cwd(), './vue-i18n-extract.config.cjs'),
`module.exports = ${JSON.stringify(defaultConfig, null, 2)}`,
);
}
@@ -16,7 +16,7 @@ export function resolveConfig (): Record<string, string> {
let options;
try {
- const pathToConfigFile = path.resolve(process.cwd(), './vue-i18n-extract.config.js');
+ const pathToConfigFile = path.resolve(process.cwd(), './vue-i18n-extract.config.cjs');
// eslint-disable-next-line @typescript-eslint/no-var-requires
const configOptions = require(pathToConfigFile);

View file

@ -1,4 +1,4 @@
module.exports = {
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},

View file

@ -146,22 +146,37 @@ body {
.taginput-item {
@apply bg-primary mr-2;
}
.taginput-autocomplete {
@apply flex-1 !drop-shadow-none;
}
.taginput-expanded {
@apply w-full;
}
.taginput .autocomplete .dropdown-menu {
@apply w-full;
}
.taginput-container {
@apply border-none;
}
.taginput-item:first-child {
@apply ml-2;
}
.taginput-input-wrapper {
@apply block;
}
/* Autocomplete */
.autocomplete {
@apply max-h-[200px] drop-shadow-md text-black z-10;
}
.autocomplete-item {
@apply py-1.5 px-4 text-start;
.autocomplete .autocomplete-item {
@apply text-start p-2;
}
.autocomplete-item-group-title {
@apply opacity-50 py-0 px-2;
.autocomplete .autocomplete-item-group-title {
@apply opacity-50 py-1.5 px-2 dark:text-white dark:opacity-75;
}
/* Dropdown */
@ -173,7 +188,7 @@ body {
@apply bg-white dark:bg-zinc-700 shadow-lg rounded text-start py-2;
}
.dropdown-item {
@apply relative inline-flex gap-1 no-underline p-2 cursor-pointer w-full;
@apply relative inline-flex gap-1 no-underline p-2 cursor-pointer w-full hover:bg-[#f5f5f5] hover:text-black;
}
.dropdown-item-active {
@ -343,8 +358,8 @@ button.menubar__button {
.o-drop__menu--active {
@apply z-50;
}
.o-dpck__box {
@apply px-4 py-1;
.datepicker-box {
@apply block px-4 py-1 hover:bg-transparent;
}
.o-dpck__header {
@apply pb-2 mb-2;
@ -352,7 +367,7 @@ button.menubar__button {
}
.o-dpck__header__next,
.o-dpck__header__previous {
@apply justify-center text-center no-underline cursor-pointer items-center shadow-none inline-flex relative select-none leading-6 border rounded h-10 p-2 m-1 dark:text-white;
@apply justify-center text-center no-underline cursor-pointer items-center shadow-none inline-flex relative select-none leading-6 border rounded h-10 p-2 m-1 dark:text-white hover:px-2;
min-width: 2.25em;
}
.o-dpck__header__list {

View file

@ -30,7 +30,9 @@
class="flex-1 min-w-[200px]"
>
<template v-slot="props">
<div class="dark:bg-violet-3 p-1 flex items-center gap-1">
<div
class="dark:bg-violet-3 p-1 flex items-center gap-1 flex-1 dark:text-white"
>
<div class="">
<img
v-if="
@ -41,6 +43,7 @@
width="24"
height="24"
alt=""
class="dark:fill-white"
/>
<o-icon v-else-if="props.option.icon" :icon="props.option.icon" />
<o-icon v-else icon="help-circle" />

View file

@ -44,7 +44,9 @@
<o-icon :icon="addressToPoiInfos(option).poiIcon.icon" />
<b>{{ addressToPoiInfos(option).name }}</b>
</p>
<small>{{ addressToPoiInfos(option).alternativeName }}</small>
<p class="text-small">
{{ addressToPoiInfos(option).alternativeName }}
</p>
</template>
<template #empty>
<template v-if="isFetching">{{ t("Searching") }}</template>

View file

@ -1,15 +1,17 @@
<template>
<o-field :label-for="id">
<o-field :label-for="id" class="taginput-field">
<template #label>
{{ $t("Add some tags") }}
<p class="inline-flex items-center gap-0.5">
{{ t("Add some tags") }}
<o-tooltip
variant="dark"
:label="
$t('You can add tags by hitting the Enter key or by adding a comma')
t('You can add tags by hitting the Enter key or by adding a comma')
"
>
<HelpCircleOutline :size="16" />
</o-tooltip>
</p>
</template>
<o-taginput
v-model="tagsStrings"
@ -20,10 +22,11 @@
icon="label"
:maxlength="20"
:maxitems="10"
:placeholder="$t('Eg: Stockholm, Dance, Chess…')"
@input="debouncedGetFilteredTags"
:placeholder="t('Eg: Stockholm, Dance, Chess…')"
@input="getFilteredTags"
:id="id"
dir="auto"
expanded
>
</o-taginput>
</o-field>
@ -31,11 +34,11 @@
<script lang="ts" setup>
import differenceBy from "lodash/differenceBy";
import { ITag } from "../../types/tag.model";
import debounce from "lodash/debounce";
import { computed, onBeforeMount, ref } from "vue";
import HelpCircleOutline from "vue-material-design-icons/HelpCircleOutline.vue";
import { useFetchTags } from "@/composition/apollo/tags";
import { FILTER_TAGS } from "@/graphql/tags";
import { useI18n } from "vue-i18n";
const props = defineProps<{
modelValue: ITag[];
@ -47,6 +50,8 @@ const text = ref("");
const tags = ref<ITag[]>([]);
const { t } = useI18n({ useScope: "global" });
let componentId = 0;
onBeforeMount(() => {
@ -61,14 +66,16 @@ const { load: fetchTags } = useFetchTags();
const getFilteredTags = async (newText: string): Promise<void> => {
text.value = newText;
const res = await fetchTags(FILTER_TAGS, { filter: newText });
const res = await fetchTags(
FILTER_TAGS,
{ filter: newText },
{ debounce: 200 }
);
if (res) {
tags.value = res.tags;
}
};
const debouncedGetFilteredTags = debounce(getFilteredTags, 200);
const filteredTags = computed((): ITag[] => {
return differenceBy(tags.value, props.modelValue, "id").filter(
(option) =>

View file

@ -32,6 +32,7 @@
v-if="currentActor"
:currentActor="currentActor"
:placeholder="t('Write a new message')"
:required="true"
/>
<o-notification
class="my-2"
@ -133,6 +134,7 @@ const sendForm = (e: Event) => {
e.preventDefault();
console.debug("Sending new private message");
if (!currentActor.value?.id || !event.value.id) return;
errors.value = [];
eventPrivateMessageMutate({
text: text.value,
actorId:
@ -150,7 +152,10 @@ onEventPrivateMessageMutated(() => {
onEventPrivateMessageError((err) => {
err.graphQLErrors.forEach((error) => {
errors.value.push(error.message);
const message = Array.isArray(error.message)
? error.message
: [error.message];
errors.value.push(...message);
});
});

View file

@ -6,7 +6,7 @@
<section>
<div
class="flex gap-1 flex-row mb-3 bg-mbz-yellow p-3 rounded items-center"
class="flex gap-1 flex-row mb-3 bg-mbz-yellow dark:text-black p-3 rounded items-center"
>
<o-icon
icon="alert"

View file

@ -1,6 +1,6 @@
<template>
<span
class="rounded-md truncate text-sm text-violet-title px-2 py-1"
class="rounded-md truncate text-sm text-black px-2 py-1"
:class="[
typeClasses,
capitalize,

View file

@ -217,7 +217,16 @@
</button>
</bubble-menu>
<editor-content class="editor__content" :editor="editor" v-if="editor" />
<editor-content
class="editor__content"
:class="{ editorErrorStatus, editorIsFocused: focused }"
:editor="editor"
v-if="editor"
ref="editorContentRef"
/>
<p v-if="editorErrorMessage" class="text-sm text-mbz-danger">
{{ editorErrorMessage }}
</p>
</div>
</div>
</template>
@ -249,7 +258,7 @@ import Underline from "@tiptap/extension-underline";
import Link from "@tiptap/extension-link";
import { AutoDir } from "./Editor/Autodir";
// import sanitizeHtml from "sanitize-html";
import { computed, inject, onBeforeUnmount, watch } from "vue";
import { computed, inject, onBeforeUnmount, ref, watch } from "vue";
import { Dialog } from "@/plugins/dialog";
import { useI18n } from "vue-i18n";
import { useMutation } from "@vue/apollo-composable";
@ -269,6 +278,7 @@ import FormatQuoteClose from "vue-material-design-icons/FormatQuoteClose.vue";
import Undo from "vue-material-design-icons/Undo.vue";
import Redo from "vue-material-design-icons/Redo.vue";
import Placeholder from "@tiptap/extension-placeholder";
import { useFocusWithin } from "@vueuse/core";
const props = withDefaults(
defineProps<{
@ -279,11 +289,13 @@ const props = withDefaults(
currentActor: IPerson;
placeholder?: string;
headingLevel?: Level[];
required?: boolean;
}>(),
{
mode: "description",
maxSize: 100_000_000,
headingLevel: () => [3, 4, 5],
required: false,
}
);
@ -333,7 +345,7 @@ const editor = useEditor({
"aria-label": ariaLabel.value ?? "",
role: "textbox",
class:
"prose dark:prose-invert prose-sm lg:prose-lg xl:prose-xl bg-zinc-50 dark:bg-zinc-700 focus:outline-none !max-w-full",
"prose dark:prose-invert prose-sm lg:prose-lg xl:prose-xl bg-white dark:bg-zinc-700 !max-w-full",
},
transformPastedHTML: transformPastedHTML,
},
@ -373,8 +385,18 @@ const editor = useEditor({
onUpdate: () => {
emit("update:modelValue", editor.value?.getHTML());
},
onBlur: () => {
checkEditorEmpty();
},
onFocus: () => {
editorErrorStatus.value = false;
editorErrorMessage.value = "";
},
});
const editorContentRef = ref(null);
const { focused } = useFocusWithin(editorContentRef);
watch(value, (val: string) => {
if (!editor.value) return;
if (val !== editor.value.getHTML()) {
@ -470,6 +492,18 @@ defineExpose({ replyToComment, focus });
onBeforeUnmount(() => {
editor.value?.destroy();
});
const editorErrorStatus = ref(false);
const editorErrorMessage = ref("");
const isEmpty = computed(
() => props.required === true && editor.value?.isEmpty === true
);
const checkEditorEmpty = () => {
editorErrorStatus.value = isEmpty.value;
editorErrorMessage.value = isEmpty.value ? t("You need to enter a text") : "";
};
</script>
<style lang="scss">
@use "@/styles/_mixins" as *;
@ -523,14 +557,8 @@ onBeforeUnmount(() => {
&__content {
div.ProseMirror {
min-height: 2.5rem;
box-shadow: inset 0 1px 2px rgba(10, 10, 10, 0.1);
border-radius: 4px;
border: 1px solid #dbdbdb;
padding: 12px 6px;
&:focus {
outline: none;
}
}
h1 {
@ -655,4 +683,19 @@ onBeforeUnmount(() => {
.mention[data-id] {
@apply inline-block border border-zinc-600 dark:border-zinc-300 rounded py-0.5 px-1;
}
.editor__content > div {
@apply border rounded border-[#6b7280];
}
.editorIsFocused > div {
@apply ring-2 ring-[#2563eb] outline-2 outline outline-offset-2 outline-transparent;
}
.editorErrorStatus {
@apply border-red-500;
}
.editor__content p.is-editor-empty:first-child::before {
@apply text-slate-300;
}
</style>

View file

@ -17,7 +17,6 @@ export function useGroupMembers(
groupName: Ref<string>,
options: useGroupMembersOptions = {}
) {
console.debug("useGroupMembers", options);
const { result, error, loading, onResult, onError, refetch, fetchMore } =
useQuery<
{

View file

@ -13,6 +13,9 @@ export const COMMENT_FIELDS_FRAGMENT = gql`
actor {
...ActorFragment
}
attributedTo {
...ActorFragment
}
totalReplies
insertedAt
updatedAt

View file

@ -12,6 +12,9 @@ export const CONVERSATION_QUERY_FRAGMENT = gql`
lastComment {
...CommentFields
}
originComment {
...CommentFields
}
participants {
...ActorFragment
}
@ -19,6 +22,12 @@ export const CONVERSATION_QUERY_FRAGMENT = gql`
id
uuid
title
organizerActor {
id
}
attributedTo {
id
}
picture {
id
url

View file

@ -1643,5 +1643,7 @@
"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",
"Domain or instance name": "Domain or instance name"
"Domain or instance name": "Domain or instance name",
"You need to enter a text": "You need to enter a text",
"Error while adding tag: {error}": "Error while adding tag: {error}"
}

View file

@ -1636,5 +1636,7 @@
"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",
"Domain or instance name": "Domaine ou nom de l'instance"
"Domain or instance name": "Domaine ou nom de l'instance",
"You need to enter a text": "Vous devez entrer un texte",
"Error while adding tag: {error}": "Erreur lors de l'ajout d'un tag : {error}"
}

View file

@ -16,7 +16,7 @@
"A cookie is a small file containing information that is sent to your computer when you visit a website. When you visit the site again, the cookie allows that site to recognize your browser. Cookies may store user preferences and other information. You can configure your browser to refuse all cookies. However, this may result in some website features or services partially working. Local storage works the same way but allows you to store more data.": "A süti egy információt tartalmazó kis fájl, amelyet akkor küld el a számítógépe, ha meglátogat egy weboldalt. Amikor újra meglátogatja a weboldalt, akkor a süti lehetővé teszi annak az oldalnak, hogy felismerje a böngészőjét. A sütik tárolhatnak felhasználói beállításokat vagy egyéb információkat. Beállíthatja a böngészőjét úgy, hogy utasítson vissza minden sütit. Azonban ez azt eredményezheti, hogy néhány weboldalon a funkciók vagy a szolgáltatások csak részlegesen működnek. A helyi tároló hasonlóan működik, de több adat tárolását teszi lehetővé.",
"A discussion has been created or updated": "Egy megbeszélés létre lett hozva vagy frissítve lett",
"A federated software": "Egy föderált szoftver",
"A fediverse account URL to follow for event updates": "Egy födiverzum-fiók URL az esemény frissítéseinek követéséhez",
"A fediverse account URL to follow for event updates": "Egy Födiverzum-fiók webcíme az esemény frissítéseinek követéséhez",
"A few lines about your group": "Néhány sor a csoportjáról",
"A link to a page presenting the event schedule": "Az esemény ütemtervét bemutató oldalra mutató hivatkozás",
"A link to a page presenting the price options": "Az árválasztékot bemutató oldalra mutató hivatkozás",
@ -41,8 +41,8 @@
"About Mobilizon": "A Mobilizon névjegye",
"About anonymous participation": "A névtelen részvételről",
"About instance": "A példány névjegye",
"About this event": "Erről az eseményről",
"About this instance": "Erről a példányról",
"About this event": "Az esemény nébjegye",
"About this instance": "A példány névjegye",
"About {instance}": "A(z) {instance} névjegye",
"Accept": "Elfogadás",
"Accept follow": "Követés elfogadása",
@ -57,7 +57,7 @@
"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",
"Access your group's resources": "A csoport erőforrásainak 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",
@ -84,7 +84,7 @@
"Add new…": "Új hozzáadása…",
"Add picture": "Kép hozzáadása",
"Add some tags": "Címkék hozzáadása",
"Add to my calendar": "Hozzáadás a naptáramhoz",
"Add to my calendar": "Hozzáadás a saját naptárhoz",
"Additional comments": "További hozzászólások",
"Admin": "Adminisztrátor",
"Admin dashboard": "Adminisztrátori vezérlőpult",
@ -98,7 +98,7 @@
"All the places have already been taken": "Már az összes helyet elfoglalták",
"Allow all comments from users with accounts": "Az összes hozzászólás engedélyezése a bejelentkezett felhasználóktól",
"Allow registrations": "Regisztrációk engedélyezése",
"An URL to an external ticketing platform": "Egy URL egy külső jegyértékesítő platformhoz",
"An URL to an external ticketing platform": "Egy külső jegyértékesítő platform webcíme",
"An error has occured while refreshing the page.": "Hiba történt az oldal frissítésekor.",
"An error has occured. Sorry about that. You may try to reload the page.": "Hiba történt. Sajnáljuk. Megpróbálhatja újratölteni az oldalt.",
"An ethical alternative": "Egy etikus alternatíva",
@ -146,11 +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észt vesz",
"Attending": "Részvétel",
"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?",
"Autorize this application to access your account?": "Engedélyezi, hogy 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",
@ -183,8 +183,8 @@
"Cancel edition": "Szerkesztés megszakítása",
"Cancel follow request": "Követési kérés megszakítása",
"Cancel membership request": "Tagsági kérés megszakítása",
"Cancel my participation request…": "Részvételi kérésem megszakítása…",
"Cancel my participation…": "Részvételem megszakítása…",
"Cancel my participation request…": "Saját részvételi kérés visszavonása…",
"Cancel my participation…": "Saját részvétel visszavonása…",
"Cancelled": "Törölve",
"Cancelled: Won't happen": "Törölve: nem fog megtörténni",
"Categories": "Kategóriák",
@ -193,9 +193,9 @@
"Category list": "Kategóriák",
"Change": "Változtatás",
"Change email": "E-mail-cím megváltoztatása",
"Change my email": "E-mail-címem megváltoztatása",
"Change my identity…": "Személyazonosságom megváltoztatása…",
"Change my password": "Jelszavam megváltoztatása",
"Change my email": "Saját e-mail-cím megváltoztatása",
"Change my identity…": "Saját személyazonosság megváltoztatása…",
"Change my password": "Saját jelszó megváltoztatása",
"Change role": "Szerep megváltoztatása",
"Change the filters.": "Módosítsa a szűrőket.",
"Change timezone": "Időzóna megváltoztatása",
@ -224,8 +224,8 @@
"Comments": "Hozzászólások",
"Comments are closed for everybody else.": "A hozzászólások le vannak zárva mindenki más számára.",
"Confirm": "Megerősítés",
"Confirm my participation": "Részvételem megerősítése",
"Confirm my particpation": "Részvételem megerősítése",
"Confirm my participation": "Részvétel megerősítése",
"Confirm my particpation": "Részvétel megerősítése",
"Confirm participation": "Részvétel megerősítése",
"Confirm user": "Felhasználó megerősítése",
"Confirmed": "Megerősítve",
@ -236,7 +236,7 @@
"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",
"Copy URL to clipboard": "Webcím másolás a vágólapra",
"Copy details to clipboard": "Részletek másolása a vágólapra",
"Country": "Ország",
"Create": "Létrehozás",
@ -272,7 +272,7 @@
"Current identity has been changed to {identityName} in order to manage this event.": "A jelenlegi személyazonosság megváltozott {identityName} személyazonosságra az esemény kezelése érdekében.",
"Current page": "Jelenlegi oldal",
"Custom": "Egyéni",
"Custom URL": "Egyéni URL",
"Custom URL": "Egyéni webcím",
"Custom text": "Egyéni szöveg",
"Daily email summary": "Napi e-mailes összegzés",
"Dark": "Sötét",
@ -297,7 +297,7 @@
"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 posts": "Csoportbejegyzések 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",
@ -310,7 +310,7 @@
"Deleting comment": "Hozzászólás törlése",
"Deleting event": "Esemény törlése",
"Deleting my account will delete all of my identities.": "A saját fiókom törlése törölni fogja az összes személyazonosságomat is.",
"Deleting your Mobilizon account": "Az Ön Mobilizon-fiókjának törlése",
"Deleting your Mobilizon account": "A saját Mobilizon-fiókjának törlése",
"Demote": "Lefokozás",
"Describe your event": "Írja le az eseményt",
"Description": "Leírás",
@ -355,7 +355,7 @@
"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 the link URL": "Adja meg a hivatkozás webcímé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 terms. HTML tags allowed. The {mobilizon_terms} are provided as template.": "Adja meg a saját használati feltételeit. A HTML címkék engedélyezettek. A {mobilizon_terms} meg van adva sablonként.",
@ -377,7 +377,7 @@
"Ethical alternative to Facebook events, groups and pages, Mobilizon is a <b>tool designed to serve you</b>. Period.": "Etikus alternatíva a Facebook eseményekre, csoportokra és oldalakra. A Mobilizon egy olyan <b>eszköz, amelyet úgy terveztek, hogy Önt szolgálja</b>. És pont.",
"Ethical alternative to Facebook events, groups and pages, Mobilizon is a {tool_designed_to_serve_you}. Period.": "A facebookos események, csoportok és oldalak etikus alternatívája, a Mobilizon egy olyan {tool_designed_to_serve_you}. Pont.",
"Event": "Esemény",
"Event URL": "Esemény URL",
"Event URL": "Esemény webcíme",
"Event already passed": "Az esemény már elmúlt",
"Event cancelled": "Esemény törölve",
"Event creation": "Eseménylétrehozás",
@ -464,7 +464,7 @@
"Group": "Csoport",
"Group Followers": "Követők csoportosítása",
"Group Members": "Csoporttagok",
"Group URL": "Csoport URL",
"Group URL": "Csoport webcíme",
"Group activity": "Csoporttevékenység",
"Group address": "Csoport címe",
"Group description body": "Csoport leírásának törzse",
@ -528,13 +528,13 @@
"Instance Name": "Példány neve",
"Instance Privacy Policy": "Példány adatvédelmi irányelve",
"Instance Privacy Policy Source": "Példány adatvédelmi irányelvének forrása",
"Instance Privacy Policy URL": "Példány adatvédelmi irányelvének URL-je",
"Instance Privacy Policy URL": "Példány adatvédelmi irányelvének webcíme",
"Instance Rules": "Példány szabályai",
"Instance Short Description": "Példány rövid leírása",
"Instance Slogan": "Példány szlogenje",
"Instance Terms": "Példány használati feltételei",
"Instance Terms Source": "Példány használati feltételeinek forrása",
"Instance Terms URL": "Példány használati feltételeinek URL-e",
"Instance Terms URL": "Példány használati feltételeinek webcíme",
"Instance administrator": "Példány adminisztrátora",
"Instance configuration": "Példány beállítása",
"Instance feeds": "Példány hírforrásai",
@ -759,7 +759,7 @@
"Only group members can access discussions": "Csak csoporttagok férhetnek hozzá a megbeszélésekhez",
"Only group moderators can create, edit and delete events.": "Csak a csoport moderátorai hozhatnak létre, szerkeszthetnek és törölhetnek eseményeket.",
"Only group moderators can create, edit and delete posts.": "Csak a csoport moderátorai hozhatnak létre, szerkeszthetnek és törölhetnek bejegyzéseket.",
"Only registered users may fetch remote events from their URL.": "Csak regisztrált felhasználók kérhetik le a távoli eseményeket az URL-ükről.",
"Only registered users may fetch remote events from their URL.": "Csak regisztrált felhasználók kérhetik le a távoli eseményeket a webcímükről.",
"Open": "Megnyitás",
"Open a topic on our forum": "Téma nyitása a fórumunkon",
"Open an issue on our bug tracker (advanced users)": "Jegy nyitása a hibakövetőnkben (haladó felhasználóknak)",
@ -784,7 +784,7 @@
"Otherwise this identity will just be removed from the group administrators.": "Egyébként ez a személyazonosság el lesz távolítva a csoport adminisztrátoraiból.",
"Owncast": "Owncast",
"Page": "Oldal",
"Page limited to my group (asks for auth)": "Az oldal korlátozva van a csoportomra (hitelesítést kér)",
"Page limited to my group (asks for auth)": "Az oldal korlátozva van a saját csoportomra (hitelesítést kér)",
"Page not found": "Az oldal nem található",
"Parent folder": "Szülőmappa",
"Partially accessible with a wheelchair": "Részlegesen közelíthető meg kerekesszékkel",
@ -822,7 +822,7 @@
"Popular groups close to you": "Önhöz közeli népszerű csoportok",
"Popular groups nearby {position}": "Népszerű csoportok {position} közelében",
"Post": "Bejegyzés",
"Post URL": "Bejegyzés URL-e",
"Post URL": "Bejegyzés webcíme",
"Post a comment": "Hozzászólás beküldése",
"Post a reply": "Válasz beküldése",
"Post body": "Bejegyzés törzse",
@ -858,7 +858,7 @@
"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",
"Publish group posts": "Csoportbejegyzések 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",
@ -892,7 +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",
"Remove uploaded media": "Feltöltött média törlése",
"Rename": "Átnevezés",
"Rename resource": "Erőforrás átnevezése",
"Reopen": "Újranyitás",
@ -900,8 +900,8 @@
"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 ham": "Jelentés hasznosként",
"Report as spam": "Jelentés kéretlen tartalomként",
"Report as undetected spam": "Jelentés nem észlelt kéretlen tartalomként",
"Report reason": "Jelentés oka",
"Report status": "Állapotjelentés",
@ -924,10 +924,10 @@
"Resent confirmation email": "Megerősítő e-mail újraküldve",
"Reset": "Visszaállítás",
"Reset filters": "Szűrők alaphelyzetbe állítása",
"Reset my password": "Jelszavam visszaállítása",
"Reset my password": "Saját jelszó visszaállítása",
"Reset password": "Jelszó visszaállítása",
"Resolved": "Megoldva",
"Resource provided is not an URL": "A megadott erőforrás nem URL",
"Resource provided is not an URL": "A megadott erőforrás nem webcím",
"Resources": "Erőforrások",
"Restricted": "Korlátozott",
"Return to the group page": "Visszatérés a csoport oldalára",
@ -935,13 +935,13 @@
"Right now": "Épp most",
"Role": "Szerep",
"Rules": "Szabályok",
"SSL and it's successor TLS are encryption technologies to secure data communications when using the service. You can recognize an encrypted connection in your browser's address line when the URL begins with {https} and the lock icon is displayed in your browser's address bar.": "Az SSL és utódja, a TLS, titkosítási technológiák a szolgáltatás használatakor történő adatkommunikációk biztonságossá tételéhez. Arról ismerhet fel egy titkosított kapcsolatot a böngészője címsorában, hogy az URL {https} kezdetű, és lakat ikon jelenik meg a böngészője címsávjában.",
"SSL and it's successor TLS are encryption technologies to secure data communications when using the service. You can recognize an encrypted connection in your browser's address line when the URL begins with {https} and the lock icon is displayed in your browser's address bar.": "Az SSL és utódja, a TLS, titkosítási technológiák a szolgáltatás használatakor történő adatkommunikációk biztonságossá tételéhez. Arról ismerhet fel egy titkosított kapcsolatot a böngészője címsorában, hogy a webcím {https} kezdetű, és lakat ikon jelenik meg a böngészője címsávjában.",
"SSL/TLS": "SSL/TLS",
"Save": "Mentés",
"Save draft": "Piszkozat mentése",
"Schedule": "Ütemterv",
"Search": "Keresés",
"Search events, groups, etc.": "Események, csoportok és egyebek keresése",
"Search events, groups, etc.": "Események, csoportok stb. keresése",
"Search target": "Cél keresése",
"Searching…": "Keresés…",
"Select a category": "Válasszon egy kategóriát",
@ -959,8 +959,8 @@
"Send password reset": "Jelszó-visszaállítás küldése",
"Send the confirmation email again": "A megerősítő e-mail újraküldése",
"Send the report": "A jelentés küldése",
"Set an URL to a page with your own privacy policy.": "URL beállítása a saját adatvédelmi irányelveit tartalmazó oldalra.",
"Set an URL to a page with your own terms.": "URL beállítása a saját használati feltételeit tartalmazó oldalra.",
"Set an URL to a page with your own privacy policy.": "Állítsa be a webcímet a saját adatvédelmi irányelveit tartalmazó oldalra.",
"Set an URL to a page with your own terms.": "Állítsa be a webcímet a saját felhasználási feltételeit tartalmazó oldalra.",
"Settings": "Beállítások",
"Share": "Megosztás",
"Share this event": "Az esemény megosztása",
@ -1010,15 +1010,15 @@
"Text": "Szöveg",
"Thanks a lot, your feedback was submitted!": "Köszönjük, beküldte a visszajelzést!",
"That you follow or of which you are a member": "Amelyet Ön követ, vagy amelynek Ön tagja",
"The Big Blue Button video teleconference URL": "A Big Blue Button videótelefon-konferencia URL-e",
"The Google Meet video teleconference URL": "A Google Meet videótelefon-konferencia URL-e",
"The Jitsi Meet video teleconference URL": "A Jitsi Meet videótelefon-konferencia URL-e",
"The Microsoft Teams video teleconference URL": "A Microsoft Teams videótelefon-konferencia URL-e",
"The URL of a pad where notes are being taken collaboratively": "A jegyzettömb URL-e, ahol a jegyzeteket közösen készítik el",
"The URL of a poll where the choice for the event date is happening": "Egy szavazás URL-e, ahol az esemény dátumának kiválasztása történik",
"The URL where the event can be watched live": "Az URL, ahol az esemény élőben nézhető",
"The URL where the event live can be watched again after it has ended": "Az URL, ahol az élő esemény újra megnézhető, miután befejeződött",
"The Zoom video teleconference URL": "A Zoom videótelefon-konferencia URL-e",
"The Big Blue Button video teleconference URL": "A Big Blue Button videókonferencia webcíme",
"The Google Meet video teleconference URL": "A Google Meet videókonferencia webcíme",
"The Jitsi Meet video teleconference URL": "A Jitsi Meet videókonferencia webcíme",
"The Microsoft Teams video teleconference URL": "A Microsoft Teams videókonferencia webcíme",
"The URL of a pad where notes are being taken collaboratively": "A jegyzettömb webcíme, ahol a jegyzeteket közösen készítik el",
"The URL of a poll where the choice for the event date is happening": "Egy szavazás webcíme, ahol az esemény dátumának kiválasztása történik",
"The URL where the event can be watched live": "A webcím, ahol az esemény élőben nézhető",
"The URL where the event live can be watched again after it has ended": "A webcím, ahol az élő esemény újra megnézhető, miután befejeződött",
"The Zoom video teleconference URL": "A Zoom videókonferencia webcíme",
"The account's email address was changed. Check your emails to verify it.": "A fiók e-mail-címe megváltozott. Nézze meg az e-mailjeit az ellenőrzéséhez.",
"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}",
@ -1067,7 +1067,7 @@
"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 contents (eventual comments and event) and the reported profile details will be transmitted to Akismet.": "A jelentés tartalma (az esetleges hozzászólások és az esemény), valamint 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.",
@ -1084,17 +1084,17 @@
"These feeds contain event data for the events for which any of your profiles is a participant or creator. You should keep these private. You can find feeds for specific profiles on each profile edition page.": "Ezek a hírforrások eseményadatokat tartalmaznak azon eseményekhez, amelyeknél a profiljai bármelyike résztvevő vagy létrehozó. Ezeket bizalmasan kell tartania. Adott profilokhoz találhat hírforrásokat az egyes profilszerkesztő oldalakon.",
"These feeds contain event data for the events for which this specific profile is a participant or creator. You should keep these private. You can find feeds for all of your profiles into your notification settings.": "Ezek a hírforrások eseményadatokat tartalmaznak azon eseményekhez, amelyeknél ez a bizonyos profil résztvevő vagy létrehozó. Ezeket bizalmasan kell tartania. Az összes profiljához találhat hírforrásokat az értesítési beállításaiban.",
"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 URL doesn't seem to be valid": "Ez a webcím nem tűnik érvényesnek",
"This URL is not supported": "Ez a webcím 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.": "Az alkalmazás eléri az összes információját és bejegyzését. Csak azokat az alkalmazásokat engedélyezze, melyekben megbízik.",
"This application will be allowed to delete events": "Az alkalmazás jogosult lesz az események törlésére",
"This application will be allowed to delete group posts": "Az alkalmazás jogosult lesz a csoportbejegyzések törlésére",
"This application will be allowed to publish events": "Az alkalmazás jogosult lesz az események közzétételére",
"This application will be allowed to publish group posts": "Az alkalmazás jogosult lesz csoportbejegyzések közzétételére",
"This application will be allowed to remove uploaded media": "Az alkalmazás jogosult lesz a feltöltött médiatartalmak törlésére",
"This application will be allowed to update events": "Az alkalmazás jogosult lesz az események frissítésére",
"This application will be allowed to update group posts": "Az alkalmazás jogosult lesz a csoportbejegyzések frissítésére",
"This application will be allowed to upload media": "Az alkalmazás jogosult lesz médiatartalmak feltöltésére",
"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.",
@ -1123,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": "Ezen a hétvégén",
"This weekend": "Ez a hétvége",
"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})",
@ -1152,8 +1152,8 @@
"Twitter account": "Twitter-fiók",
"Type": "Típus",
"Type or select a date…": "Gépeljen be vagy válasszon egy dátumot…",
"URL": "URL",
"URL copied to clipboard": "URL másolva a vágólapra",
"URL": "Webcím",
"URL copied to clipboard": "Webcím a vágólapra másolva",
"Unable to copy to clipboard": "Nem lehet a vágólapra másolni",
"Unable to create the group. One of the pictures may be too heavy.": "Nem lehet létrehozni a csoportot. A képek egyike esetleg túl nagy.",
"Unable to create the profile. The avatar picture may be too heavy.": "Nem lehet létrehozni a profilt. A profilkép esetleg túl nagy.",
@ -1184,7 +1184,7 @@
"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 posts": "Csoportbejegyzések 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",
@ -1236,7 +1236,7 @@
"We'll send you an email one hour before the event begins, to be sure you won't forget about it.": "Egy e-mailt fogunk küldeni Önnek az esemény kezdete előtt egy órával, hogy biztosan ne felejtse el azt.",
"We'll use your timezone settings to send a recap of the morning of the event.": "Az Ön időzóna-beállításait fogjuk használni az esemény reggeli rövid összegzésének küldéséhez.",
"Website": "Weboldal",
"Website / URL": "Weboldal vagy URL",
"Website / URL": "Weboldal vagy webcím",
"Weekly email summary": "Heti e-mailes összegzés",
"Welcome back {username}!": "Üdvözöljük, {username}!",
"Welcome back!": "Üdvözöljük!",
@ -1346,10 +1346,10 @@
"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 be able to revoke access for this application in your account settings.": "Az alkalmazás hozzáférése a fiókbeállításokban vonható 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.",
"You'll need to change the URLs where there were previously entered.": "Meg kell majd változtatnia az webcímeket, 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 webcímé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 receive a confirmation email.": "Kapni fog egy megerősítő e-mailt.",
"YouTube live": "YouTube élő",
"YouTube replay": "YouTube ismétlés",

View file

@ -27,6 +27,16 @@ export const orugaConfig = {
taginput: {
itemClass: "taginput-item",
rootClass: "taginput",
containerClass: "taginput-container",
expandedClass: "taginput-expanded",
autocompleteClasses: {
rootClass: "taginput-autocomplete",
itemClass: "taginput-autocomplete-item",
inputClasses: {
rootClass: "taginput-input-wrapper",
inputClass: "taginput-input",
},
},
},
autocomplete: {
rootClass: "autocomplete",
@ -57,6 +67,7 @@ export const orugaConfig = {
datepicker: {
iconNext: "ChevronRight",
iconPrev: "ChevronLeft",
boxClass: "datepicker-box",
},
modal: {
rootClass: "modal",

View file

@ -155,6 +155,7 @@ export function iconForAddress(address: IAddress): IPOIIcon {
}
export function addressFullName(address: IAddress): string {
if (!address) return "";
const { name, alternativeName } = addressToPoiInfos(address);
if (name && alternativeName) {
return `${name}, ${alternativeName}`;

View file

@ -8,6 +8,7 @@ export interface IConversation {
id?: string;
actor?: IActor;
lastComment?: IComment;
originComment?: IComment;
comments: Paginate<IComment>;
participants: IActor[];
updatedAt: string;

View file

@ -25,7 +25,7 @@
aria-required="true"
required
v-model="identity.name"
@input="(event: any) => updateUsername(event.target.value)"
@update:modelValue="(value: string) => updateUsername(value)"
id="identity-display-name"
dir="auto"
expanded
@ -740,6 +740,7 @@ const breadcrumbsLinks = computed(
);
const updateUsername = (value: string) => {
if (props.isUpdate) return;
identity.value.preferredUsername = convertToUsername(value);
};

View file

@ -233,7 +233,7 @@ import {
useRouteQuery,
} from "vue-use-route-query";
import { useMutation, useQuery } from "@vue/apollo-composable";
import { computed, inject, ref } from "vue";
import { computed, inject, ref, watch } from "vue";
import { useRouter } from "vue-router";
import { useHead } from "@unhead/vue";
import CloudQuestion from "../../../node_modules/vue-material-design-icons/CloudQuestion.vue";
@ -263,8 +263,26 @@ const { result: instancesResult } = useQuery<{
{ debounce: 500 }
);
watch([filterDomain, followStatus], () => {
instancePage.value = 1;
});
const instances = computed(() => instancesResult.value?.instances);
const instancesTotal = computed(() => instancesResult.value?.instances.total);
const currentPageInstancesNumber = computed(
() => instancesResult.value?.instances.elements.length
);
// If we didn't found any instances on this page
watch(instancesTotal, (newInstancesTotal) => {
if (newInstancesTotal === 0) {
instancePage.value = 1;
} else if (currentPageInstancesNumber.value === 0) {
instancePage.value = instancePage.value - 1;
}
});
const { t } = useI18n({ useScope: "global" });
useHead({
title: computed(() => t("Federation")),

View file

@ -14,7 +14,11 @@
]"
/>
<div
v-if="conversation.event && !isCurrentActorAuthor"
v-if="
conversation.event &&
!isCurrentActorAuthor &&
isOriginCommentAuthorEventOrganizer
"
class="bg-mbz-yellow p-6 mb-3 rounded flex gap-2 items-center"
>
<Calendar :size="36" />
@ -132,7 +136,11 @@
>
</form>
<div
v-else-if="conversation.event"
v-else-if="
conversation.event &&
!isCurrentActorAuthor &&
isOriginCommentAuthorEventOrganizer
"
class="bg-mbz-yellow p-6 rounded flex gap-2 items-center mt-3"
>
<Calendar :size="36" />
@ -292,6 +300,16 @@ const isCurrentActorAuthor = computed(
currentActor.value.id !== conversation.value?.actor?.id
);
const isOriginCommentAuthorEventOrganizer = computed(
() =>
conversation.value?.originComment?.actor &&
conversation.value?.event &&
[
conversation.value?.event?.organizerActor?.id,
conversation.value?.event?.attributedTo?.id,
].includes(conversation.value?.originComment?.actor.id)
);
useHead({
title: () => title.value,
});

View file

@ -924,9 +924,28 @@ const handleError = (err: any) => {
console.error(err);
if (err.graphQLErrors !== undefined) {
err.graphQLErrors.forEach(({ message }: { message: string }) => {
err.graphQLErrors.forEach(
({
message,
field,
}: {
message: string | { slug?: string[] }[];
field: string;
}) => {
if (
field === "tags" &&
Array.isArray(message) &&
message.some((msg) => msg.slug)
) {
const finalMsg = message.find((msg) => msg.slug?.[0]);
notifier?.error(
t("Error while adding tag: {error}", { error: finalMsg?.slug?.[0] })
);
} else if (typeof message === "string") {
notifier?.error(message);
});
}
}
);
}
};

View file

@ -349,8 +349,7 @@ const { result: loggedUserResult } = useQuery<{ loggedUser: IUser }>(
USER_NOTIFICATIONS
);
const loggedUser = computed(() => loggedUserResult.value?.loggedUser);
const feedTokens = computed(
() =>
const feedTokens = computed(() =>
loggedUser.value?.feedTokens.filter(
(token: IFeedToken) => token.actor === null
)

View file

@ -94,7 +94,6 @@
<o-field grouped>
<o-field :label="t('City or region')" expanded label-for="setting-city">
<full-address-auto-complete
v-if="loggedUser?.settings"
:resultType="AddressSearchType.ADMINISTRATIVE"
v-model="address"
:default-text="address?.description"

View file

@ -6,6 +6,7 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier.CreateTest do
alias Mobilizon.Actors.Actor
alias Mobilizon.Conversations.Conversation
alias Mobilizon.Discussions.Comment
alias Mobilizon.Events.Event
alias Mobilizon.Federation.ActivityPub.Transmogrifier
alias Mobilizon.Service.HTTP.ActivityPub.Mock
@ -103,5 +104,57 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier.CreateTest do
{:ok, admin} = Mobilizon.Actors.get_actor_by_url("https://framapiaf.org/users/admin")
assert participant_ids == MapSet.new([actor.id, admin.id])
end
test "it creates conversations for received comments if we're concerned even with reply to an event" do
actor_data = File.read!("test/fixtures/mastodon-actor.json") |> Jason.decode!()
Mock
|> expect(:call, 1, fn
%{method: :get, url: "https://framapiaf.org/users/admin"}, _opts ->
{:ok,
%Tesla.Env{
status: 200,
body:
actor_data
|> Map.put("id", "https://framapiaf.org/users/admin")
|> Map.put("preferredUsername", "admin")
}}
end)
actor = insert(:actor)
data = File.read!("test/fixtures/mastodon-post-activity-private.json") |> Jason.decode!()
data = Map.put(data, "to", [actor.url])
%Event{id: event_id, organizer_actor_id: organizer_actor_id} = event = insert(:event)
%Comment{url: reply_to_url, id: first_reply_comment_id} =
insert(:comment, visibility: :private, event: event)
object =
data["object"]
|> Map.put("to", [actor.url])
|> Map.put("inReplyTo", reply_to_url)
|> Map.put("tag", [
data["object"]["tag"]
|> hd()
|> Map.put("href", actor.url)
|> Map.put("name", Actor.preferred_username_and_domain(actor))
])
data = Map.put(data, "object", object)
{:ok, _activity,
%Conversation{
origin_comment: %Comment{visibility: :private, id: origin_comment_id},
last_comment: %Comment{visibility: :private, id: _last_comment_id},
participants: participants,
event: %Event{id: ^event_id}
}} = Transmogrifier.handle_incoming(data)
assert origin_comment_id == first_reply_comment_id
participant_ids = participants |> Enum.map(& &1.id) |> MapSet.new()
{:ok, admin} = Mobilizon.Actors.get_actor_by_url("https://framapiaf.org/users/admin")
assert participant_ids == MapSet.new([actor.id, organizer_actor_id, admin.id])
end
end
end

View file

@ -0,0 +1,76 @@
defmodule Mobilizon.Federation.ActivityStream.Converter.UtilsTest do
@moduledoc """
Module to test converting from EventMetadata to AS
"""
use Mobilizon.DataCase
import Mobilizon.Factory
alias Mobilizon.Federation.ActivityStream.Converter.Utils
describe "get_medias/1" do
test "getting banner from Document attachment" do
data =
File.read!("test/fixtures/mobilizon-post-activity-media.json")
|> Jason.decode!()
|> Map.get("object")
assert Utils.get_medias(data) ==
{%{
"blurhash" => "U5SY?Z00nOxu7ORP.8-pU^kVS#NGXyxbMxM{",
"mediaType" => "image/png",
"name" => nil,
"type" => "Document",
"url" => "https://mobilizon.fr/some-image"
}, []}
end
test "getting banner from image property" do
data =
File.read!("test/fixtures/mobilizon-post-activity-media-1.json")
|> Jason.decode!()
|> Map.get("object")
assert Utils.get_medias(data) ==
{%{
"blurhash" => "U5SY?Z00nOxu7ORP.8-pU^kVS#NGXyxbMxM{",
"mediaType" => "image/png",
"name" => nil,
"type" => "Image",
"url" => "https://mobilizon.fr/some-image-1"
},
[
%{
"blurhash" => "U5SY?Z00nOxu7ORP.8-pU^kVS#NGXyxbMxM{",
"mediaType" => "image/png",
"name" => nil,
"type" => "Document",
"url" => "https://mobilizon.fr/some-image"
}
]}
end
test "getting banner from attachment named \"Banner\"" do
data =
File.read!("test/fixtures/mobilizon-post-activity-media-2.json")
|> Jason.decode!()
|> Map.get("object")
assert Utils.get_medias(data) ==
{%{
"blurhash" => "U5SY?Z00nOxu7ORP.8-pU^kVS#NGXyxbMxM{",
"mediaType" => "image/png",
"name" => "Banner",
"type" => "Document",
"url" => "https://mobilizon.fr/some-image-2"
},
[
%{
"blurhash" => "U5SY?Z00nOxu7ORP.8-pU^kVS#NGXyxbMxM{",
"mediaType" => "image/png",
"name" => nil,
"type" => "Document",
"url" => "https://mobilizon.fr/some-image-1"
}
]}
end
end
end

View file

@ -0,0 +1,106 @@
{
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://litepub.social/litepub/context.jsonld",
{
"Hashtag": "as:Hashtag",
"category": "sc:category",
"ical": "http://www.w3.org/2002/12/cal/ical#",
"joinMode": {
"@id": "mz:joinMode",
"@type": "mz:joinModeType"
},
"joinModeType": {
"@id": "mz:joinModeType",
"@type": "rdfs:Class"
},
"maximumAttendeeCapacity": "sc:maximumAttendeeCapacity",
"mz": "https://joinmobilizon.org/ns#",
"repliesModerationOption": {
"@id": "mz:repliesModerationOption",
"@type": "mz:repliesModerationOptionType"
},
"repliesModerationOptionType": {
"@id": "mz:repliesModerationOptionType",
"@type": "rdfs:Class"
},
"sc": "http://schema.org#",
"uuid": "sc:identifier"
}
],
"actor": "https://mobilizon.fr/@metacartes",
"cc": [
"https://framapiaf.org/users/admin/followers",
"https://framapiaf.org/users/tcit"
],
"id": "https://mobilizon.fr/events/39a0c4a6-f2b6-41dc-bbe2-fc5bff76cc93/activity",
"object": {
"attachment": [
{
"href": "https://something.org",
"mediaType": "text/html",
"name": "Another link",
"type": "Link"
},
{
"href": "https://google.com",
"mediaType": "text/html",
"name": "Website",
"type": "Link"
},
{
"type": "Document",
"mediaType": "image/png",
"url": "https://mobilizon.fr/some-image",
"name": null,
"blurhash": "U5SY?Z00nOxu7ORP.8-pU^kVS#NGXyxbMxM{"
}
],
"attributedTo": "https://mobilizon.fr/@metacartes",
"startTime": "2018-02-12T14:08:20Z",
"cc": [
"https://framapiaf.org/users/admin/followers",
"https://framapiaf.org/users/tcit"
],
"content": "<p><span class=\"h-card\"><a href=\"https://framapiaf.org/users/tcit\" class=\"u-url mention\">@<span>tcit</span></a></span></p>",
"category": "TODO remove me",
"id": "https://mobilizon.fr/events/39a0c4a6-f2b6-41dc-bbe2-fc5bff76cc93",
"image": {
"type": "Image",
"mediaType": "image/png",
"url": "https://mobilizon.fr/some-image-1",
"name": null,
"blurhash": "U5SY?Z00nOxu7ORP.8-pU^kVS#NGXyxbMxM{"
},
"inReplyTo": null,
"location": {
"type": "Place",
"name": "Locaux de Framasoft",
"id": "https://event1.tcit.fr/address/eeecc11d-0030-43e8-a897-6422876372jd",
"address": {
"type": "PostalAddress",
"streetAddress": "10 Rue Jangot",
"postalCode": "69007",
"addressLocality": "Lyon",
"addressRegion": "Auvergne Rhône Alpes",
"addressCountry": "France"
}
},
"name": "My first event",
"published": "2018-02-12T14:08:20Z",
"tag": [
{
"href": "https://framapiaf.org/users/tcit",
"name": "@tcit@framapiaf.org",
"type": "Mention"
}
],
"to": ["https://www.w3.org/ns/activitystreams#Public"],
"type": "Event",
"url": "https://mobilizon.fr/events/39a0c4a6-f2b6-41dc-bbe2-fc5bff76cc93",
"uuid": "109ccdfd-ee3e-46e1-a877-6c228763df0c"
},
"published": "2018-02-12T14:08:20Z",
"to": ["https://www.w3.org/ns/activitystreams#Public"],
"type": "Create"
}

View file

@ -0,0 +1,106 @@
{
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://litepub.social/litepub/context.jsonld",
{
"Hashtag": "as:Hashtag",
"category": "sc:category",
"ical": "http://www.w3.org/2002/12/cal/ical#",
"joinMode": {
"@id": "mz:joinMode",
"@type": "mz:joinModeType"
},
"joinModeType": {
"@id": "mz:joinModeType",
"@type": "rdfs:Class"
},
"maximumAttendeeCapacity": "sc:maximumAttendeeCapacity",
"mz": "https://joinmobilizon.org/ns#",
"repliesModerationOption": {
"@id": "mz:repliesModerationOption",
"@type": "mz:repliesModerationOptionType"
},
"repliesModerationOptionType": {
"@id": "mz:repliesModerationOptionType",
"@type": "rdfs:Class"
},
"sc": "http://schema.org#",
"uuid": "sc:identifier"
}
],
"actor": "https://mobilizon.fr/@metacartes",
"cc": [
"https://framapiaf.org/users/admin/followers",
"https://framapiaf.org/users/tcit"
],
"id": "https://mobilizon.fr/events/39a0c4a6-f2b6-41dc-bbe2-fc5bff76cc93/activity",
"object": {
"attachment": [
{
"href": "https://something.org",
"mediaType": "text/html",
"name": "Another link",
"type": "Link"
},
{
"href": "https://google.com",
"mediaType": "text/html",
"name": "Website",
"type": "Link"
},
{
"type": "Document",
"mediaType": "image/png",
"url": "https://mobilizon.fr/some-image-1",
"name": null,
"blurhash": "U5SY?Z00nOxu7ORP.8-pU^kVS#NGXyxbMxM{"
},
{
"type": "Document",
"mediaType": "image/png",
"url": "https://mobilizon.fr/some-image-2",
"name": "Banner",
"blurhash": "U5SY?Z00nOxu7ORP.8-pU^kVS#NGXyxbMxM{"
}
],
"attributedTo": "https://mobilizon.fr/@metacartes",
"startTime": "2018-02-12T14:08:20Z",
"cc": [
"https://framapiaf.org/users/admin/followers",
"https://framapiaf.org/users/tcit"
],
"content": "<p><span class=\"h-card\"><a href=\"https://framapiaf.org/users/tcit\" class=\"u-url mention\">@<span>tcit</span></a></span></p>",
"category": "TODO remove me",
"id": "https://mobilizon.fr/events/39a0c4a6-f2b6-41dc-bbe2-fc5bff76cc93",
"inReplyTo": null,
"location": {
"type": "Place",
"name": "Locaux de Framasoft",
"id": "https://event1.tcit.fr/address/eeecc11d-0030-43e8-a897-6422876372jd",
"address": {
"type": "PostalAddress",
"streetAddress": "10 Rue Jangot",
"postalCode": "69007",
"addressLocality": "Lyon",
"addressRegion": "Auvergne Rhône Alpes",
"addressCountry": "France"
}
},
"name": "My first event",
"published": "2018-02-12T14:08:20Z",
"tag": [
{
"href": "https://framapiaf.org/users/tcit",
"name": "@tcit@framapiaf.org",
"type": "Mention"
}
],
"to": ["https://www.w3.org/ns/activitystreams#Public"],
"type": "Event",
"url": "https://mobilizon.fr/events/39a0c4a6-f2b6-41dc-bbe2-fc5bff76cc93",
"uuid": "109ccdfd-ee3e-46e1-a877-6c228763df0c"
},
"published": "2018-02-12T14:08:20Z",
"to": ["https://www.w3.org/ns/activitystreams#Public"],
"type": "Create"
}

View file

@ -0,0 +1,99 @@
{
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://litepub.social/litepub/context.jsonld",
{
"Hashtag": "as:Hashtag",
"category": "sc:category",
"ical": "http://www.w3.org/2002/12/cal/ical#",
"joinMode": {
"@id": "mz:joinMode",
"@type": "mz:joinModeType"
},
"joinModeType": {
"@id": "mz:joinModeType",
"@type": "rdfs:Class"
},
"maximumAttendeeCapacity": "sc:maximumAttendeeCapacity",
"mz": "https://joinmobilizon.org/ns#",
"repliesModerationOption": {
"@id": "mz:repliesModerationOption",
"@type": "mz:repliesModerationOptionType"
},
"repliesModerationOptionType": {
"@id": "mz:repliesModerationOptionType",
"@type": "rdfs:Class"
},
"sc": "http://schema.org#",
"uuid": "sc:identifier"
}
],
"actor": "https://mobilizon.fr/@metacartes",
"cc": [
"https://framapiaf.org/users/admin/followers",
"https://framapiaf.org/users/tcit"
],
"id": "https://mobilizon.fr/events/39a0c4a6-f2b6-41dc-bbe2-fc5bff76cc93/activity",
"object": {
"attachment": [
{
"href": "https://something.org",
"mediaType": "text/html",
"name": "Another link",
"type": "Link"
},
{
"href": "https://google.com",
"mediaType": "text/html",
"name": "Website",
"type": "Link"
},
{
"type": "Document",
"mediaType": "image/png",
"url": "https://mobilizon.fr/some-image",
"name": null,
"blurhash": "U5SY?Z00nOxu7ORP.8-pU^kVS#NGXyxbMxM{"
}
],
"attributedTo": "https://mobilizon.fr/@metacartes",
"startTime": "2018-02-12T14:08:20Z",
"cc": [
"https://framapiaf.org/users/admin/followers",
"https://framapiaf.org/users/tcit"
],
"content": "<p><span class=\"h-card\"><a href=\"https://framapiaf.org/users/tcit\" class=\"u-url mention\">@<span>tcit</span></a></span></p>",
"category": "TODO remove me",
"id": "https://mobilizon.fr/events/39a0c4a6-f2b6-41dc-bbe2-fc5bff76cc93",
"inReplyTo": null,
"location": {
"type": "Place",
"name": "Locaux de Framasoft",
"id": "https://event1.tcit.fr/address/eeecc11d-0030-43e8-a897-6422876372jd",
"address": {
"type": "PostalAddress",
"streetAddress": "10 Rue Jangot",
"postalCode": "69007",
"addressLocality": "Lyon",
"addressRegion": "Auvergne Rhône Alpes",
"addressCountry": "France"
}
},
"name": "My first event",
"published": "2018-02-12T14:08:20Z",
"tag": [
{
"href": "https://framapiaf.org/users/tcit",
"name": "@tcit@framapiaf.org",
"type": "Mention"
}
],
"to": ["https://www.w3.org/ns/activitystreams#Public"],
"type": "Event",
"url": "https://mobilizon.fr/events/39a0c4a6-f2b6-41dc-bbe2-fc5bff76cc93",
"uuid": "109ccdfd-ee3e-46e1-a877-6c228763df0c"
},
"published": "2018-02-12T14:08:20Z",
"to": ["https://www.w3.org/ns/activitystreams#Public"],
"type": "Create"
}

View file

@ -44,7 +44,8 @@ defmodule Mobilizon.GraphQL.Resolvers.ConversationTest do
describe "Find conversations for event" do
test "for a given event", %{conn: conn, user: user, actor: actor} do
event = insert(:event, organizer_actor: actor)
conversation = insert(:conversation, event: event)
origin_comment = insert(:comment, actor: actor)
conversation = insert(:conversation, event: event, origin_comment: origin_comment)
another_comment = insert(:comment, origin_comment: conversation.origin_comment)
Discussions.update_comment(conversation.origin_comment, %{conversation_id: conversation.id})

View file

@ -1,11 +1,13 @@
defmodule Mobilizon.GraphQL.Resolvers.ParticipantTest do
use Mobilizon.Web.ConnCase
use Mobilizon.Tests.Helpers
use Oban.Testing, repo: Mobilizon.Storage.Repo
alias Mobilizon.Config
alias Mobilizon.Events
alias Mobilizon.Actors.Actor
alias Mobilizon.{Actors, Config, Conversations, Events}
alias Mobilizon.Events.{Event, EventParticipantStats, Participant}
alias Mobilizon.GraphQL.AbsintheHelpers
alias Mobilizon.Service.Workers.LegacyNotifierBuilder
alias Mobilizon.Storage.Page
import Mobilizon.Factory
@ -1381,4 +1383,393 @@ defmodule Mobilizon.GraphQL.Resolvers.ParticipantTest do
assert_email_sent(to: @email)
end
end
describe "Send private messages to participants" do
@send_event_private_message_mutation """
mutation SendEventPrivateMessageMutation(
$text: String!
$actorId: ID!
$eventId: ID!
$roles: [ParticipantRoleEnum]
$language: String
) {
sendEventPrivateMessage(
text: $text
actorId: $actorId
eventId: $eventId
roles: $roles
language: $language
) {
id
conversationParticipantId
actor {
id
}
lastComment {
id
text
}
originComment {
id
text
}
participants {
id
}
event {
id
uuid
title
organizerActor {
id
}
attributedTo {
id
}
}
unread
insertedAt
updatedAt
}
}
"""
setup %{conn: conn} do
user = insert(:user)
actor = insert(:actor, user: user, preferred_username: "test")
{:ok, conn: conn, actor: actor, user: user}
end
test "Without being logged-in", %{conn: conn} do
%Actor{id: actor_id} = insert(:actor)
%Event{id: event_id} = insert(:event)
res =
conn
|> AbsintheHelpers.graphql_query(
query: @send_event_private_message_mutation,
variables: %{actorId: actor_id, eventId: event_id, text: "Hello dear participants"}
)
assert hd(res["errors"])["message"] == "You need to be logged in"
end
test "With actor not allowed", %{conn: conn, actor: actor, user: user} do
%Event{id: event_id} = insert(:event)
res =
conn
|> auth_conn(user)
|> AbsintheHelpers.graphql_query(
query: @send_event_private_message_mutation,
variables: %{actorId: actor.id, eventId: event_id, text: "Hello dear participants"}
)
assert hd(res["errors"])["message"] == "You don't have permission to do this"
end
test "With actor as event organizer", %{conn: conn, actor: actor, user: user} do
%Event{id: event_id, title: event_title, uuid: event_uuid} =
event = insert(:event, organizer_actor: actor)
%Participant{actor_id: participant_actor_id} = insert(:participant, event: event)
%Participant{actor_id: participant_actor_id_2} = insert(:participant, event: event)
res =
conn
|> auth_conn(user)
|> AbsintheHelpers.graphql_query(
query: @send_event_private_message_mutation,
variables: %{actorId: actor.id, eventId: event_id, text: "Hello dear participants"}
)
assert res["errors"] == nil
assert res["data"]["sendEventPrivateMessage"]["lastComment"]["id"] ==
res["data"]["sendEventPrivateMessage"]["originComment"]["id"]
assert res["data"]["sendEventPrivateMessage"]["lastComment"]["text"] ==
"Hello dear participants"
participants_ids =
Enum.map(res["data"]["sendEventPrivateMessage"]["participants"], fn participant ->
String.to_integer(participant["id"])
end)
assert length(participants_ids) == 3
assert MapSet.new(participants_ids) ==
MapSet.new([actor.id, participant_actor_id, participant_actor_id_2])
assert res["data"]["sendEventPrivateMessage"]["actor"]["id"] == to_string(actor.id)
conversation_id = res["data"]["sendEventPrivateMessage"]["id"]
all_conversation_participants_ids = Conversations.find_all_conversations_for_event(event_id)
notified_conversation_participant_ids =
all_conversation_participants_ids
|> Enum.filter(&(&1.actor_id != actor.id))
|> Enum.map(&[&1.id, &1.actor_id])
Enum.each(notified_conversation_participant_ids, fn [pa_id, participant_actor_id] ->
assert_enqueued(
worker: LegacyNotifierBuilder,
args: %{
"author_id" => actor.id,
"object_id" => to_string(conversation_id),
"object_type" => "conversation",
"op" => "legacy_notify",
"subject" => "conversation_created",
"subject_params" => %{
"conversation_id" => String.to_integer(conversation_id),
"conversation_participant_id" => pa_id,
"conversation_text" => "Hello dear participants",
"conversation_event_id" => event_id,
"conversation_event_title" => event_title,
"conversation_event_uuid" => event_uuid
},
"type" => "conversation",
"participant" => %{
"actor_id" => participant_actor_id,
"id" => pa_id
}
}
)
end)
ignored_conversation_participant_id =
all_conversation_participants_ids
|> Enum.filter(&(&1.actor_id == actor.id))
|> Enum.map(& &1.id)
refute_enqueued(
worker: LegacyNotifierBuilder,
args: %{
"author_id" => actor.id,
"object_id" => to_string(conversation_id),
"object_type" => "conversation",
"op" => "legacy_notify",
"subject" => "conversation_created",
"subject_params" => %{
"conversation_id" => String.to_integer(conversation_id),
"conversation_participant_id" => ignored_conversation_participant_id,
"conversation_text" => "Hello dear participants",
"conversation_event_id" => event_id,
"conversation_event_title" => event_title,
"conversation_event_uuid" => event_uuid
},
"type" => "conversation",
"participant" => %{
"actor_id" => actor.id,
"id" => ignored_conversation_participant_id
}
}
)
end
test "With actor as event organizer with customized roles", %{
conn: conn,
actor: actor,
user: user
} do
%Event{id: event_id, title: event_title, uuid: event_uuid} =
event = insert(:event, organizer_actor: actor)
%Participant{actor_id: _participant_actor_id} = insert(:participant, event: event)
%Participant{actor_id: participant_actor_id_2} =
insert(:participant, event: event, role: :not_approved)
res =
conn
|> auth_conn(user)
|> AbsintheHelpers.graphql_query(
query: @send_event_private_message_mutation,
variables: %{
actorId: actor.id,
eventId: event_id,
text: "Hello dear participants",
roles: ["NOT_APPROVED"]
}
)
assert res["errors"] == nil
assert res["data"]["sendEventPrivateMessage"]["lastComment"]["id"] ==
res["data"]["sendEventPrivateMessage"]["originComment"]["id"]
assert res["data"]["sendEventPrivateMessage"]["lastComment"]["text"] ==
"Hello dear participants"
participants_ids =
Enum.map(res["data"]["sendEventPrivateMessage"]["participants"], fn participant ->
String.to_integer(participant["id"])
end)
assert length(participants_ids) == 2
assert MapSet.new(participants_ids) ==
MapSet.new([actor.id, participant_actor_id_2])
conversation_id = res["data"]["sendEventPrivateMessage"]["id"]
all_conversation_participants_ids = Conversations.find_all_conversations_for_event(event_id)
notified_conversation_participant_ids =
all_conversation_participants_ids
|> Enum.filter(&(&1.actor_id == participant_actor_id_2))
|> Enum.map(&[&1.id, &1.actor_id])
Enum.each(notified_conversation_participant_ids, fn [pa_id, participant_actor_id] ->
assert_enqueued(
worker: LegacyNotifierBuilder,
args: %{
"author_id" => actor.id,
"object_id" => to_string(conversation_id),
"object_type" => "conversation",
"op" => "legacy_notify",
"subject" => "conversation_created",
"subject_params" => %{
"conversation_id" => String.to_integer(conversation_id),
"conversation_participant_id" => pa_id,
"conversation_text" => "Hello dear participants",
"conversation_event_id" => event_id,
"conversation_event_title" => event_title,
"conversation_event_uuid" => event_uuid
},
"type" => "conversation",
"participant" => %{
"actor_id" => participant_actor_id,
"id" => pa_id
}
}
)
end)
ignored_conversation_participant_id =
all_conversation_participants_ids
|> Enum.filter(&(&1.actor_id != participant_actor_id_2))
|> Enum.map(&[&1.id, &1.actor_id])
Enum.each(ignored_conversation_participant_id, fn [pa_id, participant_actor_id] ->
refute_enqueued(
worker: LegacyNotifierBuilder,
args: %{
"author_id" => participant_actor_id,
"object_id" => to_string(conversation_id),
"object_type" => "conversation",
"op" => "legacy_notify",
"subject" => "conversation_created",
"subject_params" => %{
"conversation_id" => String.to_integer(conversation_id),
"conversation_participant_id" => pa_id,
"conversation_text" => "Hello dear participants",
"conversation_event_id" => event_id,
"conversation_event_title" => event_title,
"conversation_event_uuid" => event_uuid
},
"type" => "conversation",
"participant" => %{
"actor_id" => participant_actor_id,
"id" => pa_id
}
}
)
end)
end
test "With actor as member of group event organizer", %{conn: conn, actor: actor, user: user} do
%Actor{id: group_id} = group = insert(:group)
insert(:member, parent: group, actor: actor, role: :moderator)
%Event{id: event_id} = event = insert(:event, organizer_actor: actor, attributed_to: group)
%Participant{actor_id: participant_actor_id} = insert(:participant, event: event)
%Participant{actor_id: participant_actor_id_2} = insert(:participant, event: event)
res =
conn
|> auth_conn(user)
|> AbsintheHelpers.graphql_query(
query: @send_event_private_message_mutation,
variables: %{actorId: group_id, eventId: event_id, text: "Hello dear participants"}
)
assert res["errors"] == nil
participants_ids =
Enum.map(res["data"]["sendEventPrivateMessage"]["participants"], fn participant ->
String.to_integer(participant["id"])
end)
assert MapSet.new(participants_ids) ==
MapSet.new([group_id, participant_actor_id, participant_actor_id_2])
assert res["data"]["sendEventPrivateMessage"]["actor"]["id"] == to_string(group_id)
end
test "With event not found", %{conn: conn, actor: actor, user: user} do
res =
conn
|> auth_conn(user)
|> AbsintheHelpers.graphql_query(
query: @send_event_private_message_mutation,
variables: %{actorId: actor.id, eventId: "5019438457", text: "Hello dear participants"}
)
assert hd(res["errors"])["message"] ==
"Event not found"
end
test "With no participants matching the audience", %{conn: conn, actor: actor, user: user} do
%Event{id: event_id} = insert(:event, organizer_actor: actor)
res =
conn
|> auth_conn(user)
|> AbsintheHelpers.graphql_query(
query: @send_event_private_message_mutation,
variables: %{actorId: actor.id, eventId: event_id, text: "Hello dear participants"}
)
assert hd(res["errors"])["message"] ==
"There are no participants matching the audience you've selected."
end
end
test "With several anonymous participants", %{conn: conn, actor: actor, user: user} do
%Event{id: event_id} =
event = insert(:event, organizer_actor: actor)
{:ok, anonymous_actor} = Actors.get_or_create_internal_actor("anonymous")
refute is_nil(anonymous_actor)
insert(:participant, event: event, actor: anonymous_actor, metadata: %{email: "anon@mou.se"})
insert(:participant, event: event, actor: anonymous_actor, metadata: %{email: "other@mou.se"})
res =
conn
|> auth_conn(user)
|> AbsintheHelpers.graphql_query(
query: @send_event_private_message_mutation,
variables: %{actorId: actor.id, eventId: event_id, text: "Hello dear participants"}
)
assert res["errors"] == nil
assert res["data"]["sendEventPrivateMessage"]["lastComment"]["id"] ==
res["data"]["sendEventPrivateMessage"]["originComment"]["id"]
assert res["data"]["sendEventPrivateMessage"]["lastComment"]["text"] ==
"Hello dear participants"
participants_ids =
Enum.map(res["data"]["sendEventPrivateMessage"]["participants"], fn participant ->
String.to_integer(participant["id"])
end)
# Anonymous actor is only added once
assert length(participants_ids) == 2
assert Enum.sort(participants_ids) == Enum.sort([actor.id, anonymous_actor.id])
end
end

View file

@ -7,6 +7,7 @@ defmodule Mobilizon.Service.Activity.ConversationTest do
alias Mobilizon.Conversations
alias Mobilizon.Conversations.{Conversation, ConversationParticipant}
alias Mobilizon.Discussions.Comment
alias Mobilizon.Events.Event
alias Mobilizon.Service.Activity.Conversation, as: ConversationActivity
alias Mobilizon.Service.Workers.LegacyNotifierBuilder
alias Mobilizon.Users.User
@ -15,16 +16,93 @@ defmodule Mobilizon.Service.Activity.ConversationTest do
use Oban.Testing, repo: Mobilizon.Storage.Repo
import Mobilizon.Factory
describe "handle conversation" do
test "with participants" do
describe "handle activity from event private announcement conversation" do
test "when conversation initial comment author is not an organizer" do
%User{} = user = insert(:user)
%Actor{id: actor_id} = actor = insert(:actor, user: user)
%Conversation{
id: conversation_id,
last_comment: %Comment{actor_id: last_comment_actor_id}
} =
conversation = insert(:conversation, event: nil)
%Actor{} = organizer_actor = insert(:actor)
%Event{} = event = insert(:event)
%Comment{} = comment = insert(:comment, actor: organizer_actor)
%Conversation{id: conversation_id} =
conversation =
insert(:conversation, event: event, last_comment: comment, origin_comment: comment)
%ConversationParticipant{id: conversation_participant_actor_id} =
insert(:conversation_participant, actor: actor, conversation: conversation)
%ConversationParticipant{
id: conversation_participant_id,
actor: %Actor{id: conversation_other_participant_actor_id}
} = insert(:conversation_participant, conversation: conversation)
conversation = Conversations.get_conversation(conversation_id)
assert {:ok, _} =
ConversationActivity.insert_activity(conversation, subject: "conversation_created")
refute_enqueued(
worker: LegacyNotifierBuilder,
args: %{
"author_id" => organizer_actor.id,
"participant" => %{"actor_id" => actor_id, "id" => conversation_participant_actor_id},
"object_id" => to_string(conversation_id),
"object_type" => "conversation",
"op" => "legacy_notify",
"subject" => "conversation_created",
"subject_params" => %{
"conversation_id" => conversation_id,
"conversation_participant_id" => conversation_participant_actor_id,
"conversation_event_id" => event.id,
"conversation_event_title" => event.title,
"conversation_event_uuid" => event.uuid
},
"type" => "conversation"
}
)
refute_enqueued(
worker: LegacyNotifierBuilder,
args: %{
"author_id" => organizer_actor.id,
"participant" => %{
"actor_id" => conversation_other_participant_actor_id,
"id" => conversation_participant_id
},
"object_id" => to_string(conversation_id),
"object_type" => "conversation",
"op" => "legacy_notify",
"subject" => "conversation_created",
"subject_params" => %{
"conversation_id" => conversation_id,
"conversation_participant_id" => conversation_participant_id,
"conversation_event_id" => event.id,
"conversation_event_title" => event.title,
"conversation_event_uuid" => event.uuid
},
"type" => "conversation"
}
)
assert [] = all_enqueued()
end
test "an author who is the event organizer" do
%User{} = user = insert(:user)
%Actor{id: actor_id} = actor = insert(:actor, user: user)
%Actor{} = organizer_actor = insert(:actor)
%Event{} = event = insert(:event, organizer_actor: organizer_actor)
%Comment{} = comment = insert(:comment, actor: organizer_actor)
%Conversation{id: conversation_id} =
conversation =
insert(:conversation, event: event, last_comment: comment, origin_comment: comment)
%ConversationParticipant{id: conversation_participant_actor_id} =
insert(:conversation_participant, actor: actor, conversation: conversation)
@ -42,7 +120,7 @@ defmodule Mobilizon.Service.Activity.ConversationTest do
assert_enqueued(
worker: LegacyNotifierBuilder,
args: %{
"author_id" => last_comment_actor_id,
"author_id" => organizer_actor.id,
"participant" => %{"actor_id" => actor_id, "id" => conversation_participant_actor_id},
"object_id" => to_string(conversation_id),
"object_type" => "conversation",
@ -50,7 +128,10 @@ defmodule Mobilizon.Service.Activity.ConversationTest do
"subject" => "conversation_created",
"subject_params" => %{
"conversation_id" => conversation_id,
"conversation_participant_id" => conversation_participant_actor_id
"conversation_participant_id" => conversation_participant_actor_id,
"conversation_event_id" => event.id,
"conversation_event_title" => event.title,
"conversation_event_uuid" => event.uuid
},
"type" => "conversation"
}
@ -59,7 +140,7 @@ defmodule Mobilizon.Service.Activity.ConversationTest do
assert_enqueued(
worker: LegacyNotifierBuilder,
args: %{
"author_id" => last_comment_actor_id,
"author_id" => organizer_actor.id,
"participant" => %{
"actor_id" => conversation_other_participant_actor_id,
"id" => conversation_participant_id
@ -70,7 +151,81 @@ defmodule Mobilizon.Service.Activity.ConversationTest do
"subject" => "conversation_created",
"subject_params" => %{
"conversation_id" => conversation_id,
"conversation_participant_id" => conversation_participant_id
"conversation_participant_id" => conversation_participant_id,
"conversation_event_id" => event.id,
"conversation_event_title" => event.title,
"conversation_event_uuid" => event.uuid
},
"type" => "conversation"
}
)
end
test "an author who is member of the event organizer group" do
%User{} = user = insert(:user)
%Actor{id: actor_id} = actor = insert(:actor, user: user)
%Actor{} = organizer_group = insert(:group)
%Event{} = event = insert(:event, attributed_to: organizer_group)
%Comment{} = comment = insert(:comment, actor: organizer_group)
%Conversation{id: conversation_id} =
conversation =
insert(:conversation, event: event, last_comment: comment, origin_comment: comment)
%ConversationParticipant{id: conversation_participant_actor_id} =
insert(:conversation_participant, actor: actor, conversation: conversation)
%ConversationParticipant{
id: conversation_participant_id,
actor: %Actor{id: conversation_other_participant_actor_id}
} = insert(:conversation_participant, conversation: conversation)
conversation = Conversations.get_conversation(conversation_id)
assert {:ok, _} =
ConversationActivity.insert_activity(conversation, subject: "conversation_created")
assert_enqueued(
worker: LegacyNotifierBuilder,
args: %{
"author_id" => organizer_group.id,
"participant" => %{"actor_id" => actor_id, "id" => conversation_participant_actor_id},
"object_id" => to_string(conversation_id),
"object_type" => "conversation",
"op" => "legacy_notify",
"subject" => "conversation_created",
"subject_params" => %{
"conversation_id" => conversation_id,
"conversation_participant_id" => conversation_participant_actor_id,
"conversation_event_id" => event.id,
"conversation_event_title" => event.title,
"conversation_event_uuid" => event.uuid
},
"type" => "conversation"
}
)
assert_enqueued(
worker: LegacyNotifierBuilder,
args: %{
"author_id" => organizer_group.id,
"participant" => %{
"actor_id" => conversation_other_participant_actor_id,
"id" => conversation_participant_id
},
"object_id" => to_string(conversation_id),
"object_type" => "conversation",
"op" => "legacy_notify",
"subject" => "conversation_created",
"subject_params" => %{
"conversation_id" => conversation_id,
"conversation_participant_id" => conversation_participant_id,
"conversation_event_id" => event.id,
"conversation_event_title" => event.title,
"conversation_event_uuid" => event.uuid
},
"type" => "conversation"
}

View file

@ -11,7 +11,7 @@ defmodule Mobilizon.Service.Metadata.InstanceTest do
assert Instance.build_tags() |> Utils.stringify_tags() ==
"""
<title>#{title}</title><meta content="#{description}" name="description"><meta content="#{title}" property="og:title"><meta content="#{Endpoint.url()}" property="og:url"><meta content="#{description}" property="og:description"><meta content="website" property="og:type"><script type="application/ld+json">{"@context":"http://schema.org","@type":"WebSite","name":"#{title}","potentialAction":{"@type":"SearchAction","query-input":"required name=search_term","target":"#{Endpoint.url()}/search?term={search_term}"},"url":"#{Endpoint.url()}"}</script>
<title>#{title}</title><meta content="#{description}" name="description"><meta content="#{title}" property="og:title"><meta content="#{Endpoint.url()}" property="og:url"><meta content="#{description}" property="og:description"><meta content="website" property="og:type"><script type="application/ld+json">{"@context":"http://schema.org","@type":"WebSite","name":"#{title}","potentialAction":{"@type":"SearchAction","query-input":"required name=search_term","target":"#{Endpoint.url()}/search?term={search_term}"},"url":"#{Endpoint.url()}"}</script>\n<link href=\"#{Endpoint.url()}/feed/instance/atom\" rel=\"alternate\" title=\"Test instance's feed\" type=\"application/atom+xml\"><link href=\"#{Endpoint.url()}/feed/instance/ics\" rel=\"alternate\" title=\"Test instance's feed\" type=\"text/calendar\">\
"""
end
end

View file

@ -4,6 +4,7 @@ defmodule Mobilizon.Service.Workers.LegacyNotifierBuilderTest do
"""
alias Mobilizon.Activities.Activity
alias Mobilizon.Actors
alias Mobilizon.Actors.Actor
alias Mobilizon.Discussions.{Comment, Discussion}
alias Mobilizon.Events.Event
@ -15,6 +16,7 @@ defmodule Mobilizon.Service.Workers.LegacyNotifierBuilderTest do
use Mobilizon.Tests.Helpers
import Mox
import Mobilizon.Factory
import Mobilizon.Tests.SwooshAssertions
setup_all do
Mox.defmock(NotifierMock, for: Mobilizon.Service.Notifier)
@ -42,7 +44,7 @@ defmodule Mobilizon.Service.Workers.LegacyNotifierBuilderTest do
"op" => "legacy_notify"
}
@announcement %{
@public_announcement %{
"type" => "comment",
"subject" => "participation_event_comment",
"object_type" => "comment",
@ -50,6 +52,14 @@ defmodule Mobilizon.Service.Workers.LegacyNotifierBuilderTest do
"op" => "legacy_notify"
}
@private_announcement %{
"type" => "conversation",
"subject" => "conversation_created",
"object_type" => "conversation",
"inserted_at" => DateTime.utc_now(),
"op" => "legacy_notify"
}
setup :verify_on_exit!
describe "Generates a comment mention notification " do
@ -138,7 +148,7 @@ defmodule Mobilizon.Service.Workers.LegacyNotifierBuilderTest do
%Comment{id: comment_id} = insert(:comment, event: event, actor: actor)
args =
Map.merge(@announcement, %{
Map.merge(@public_announcement, %{
"subject_params" => %{
"event_uuid" => uuid,
"event_title" => title,
@ -177,7 +187,7 @@ defmodule Mobilizon.Service.Workers.LegacyNotifierBuilderTest do
insert(:participant, event: event, actor: actor2)
args =
Map.merge(@announcement, %{
Map.merge(@public_announcement, %{
"subject_params" => %{
"event_uuid" => uuid,
"event_title" => title,
@ -334,4 +344,91 @@ defmodule Mobilizon.Service.Workers.LegacyNotifierBuilderTest do
assert :ok == LegacyNotifierBuilder.perform(%Oban.Job{args: args})
end
end
describe "Generates a private event announcement notification" do
test "sends emails to target users" do
user1 = insert(:user, email: "user1@do.main")
actor1 = insert(:actor, user: user1)
user2 = insert(:user, email: "user2@do.main")
actor2 = insert(:actor, user: user2)
event = insert(:event)
comment = insert(:comment, actor: actor2, visibility: :private)
conversation =
insert(:conversation, event: event, last_comment: comment, origin_comment: comment)
conversation_participant =
insert(:conversation_participant, conversation: conversation, actor: actor1)
args =
Map.merge(@private_announcement, %{
"subject_params" => %{
"conversation_id" => conversation.id,
"conversation_participant_id" => conversation_participant.id,
"conversation_text" => conversation.last_comment.text,
"conversation_event_id" => event.id,
"conversation_event_title" => event.title,
"conversation_event_uuid" => event.uuid
},
"author_id" => conversation.last_comment.actor.id,
"object_id" => conversation.last_comment.id,
"participant" => %{
"actor_id" => actor1.id,
"id" => conversation_participant.id
}
})
LegacyNotifierBuilder.perform(%Oban.Job{args: args})
assert_email_sent(%Swoosh.Email{to: [{"", "user1@do.main"}]})
refute_email_sent(%Swoosh.Email{to: [{"", "user2@do.main"}]})
refute_email_sent(%Swoosh.Email{to: [{"", "user1@do.main"}]})
end
test "sends emails to anonymous participants" do
{:ok, anonymous_actor} = Actors.get_or_create_internal_actor("anonymous")
refute is_nil(anonymous_actor)
user2 = insert(:user, email: "user2@do.main")
actor2 = insert(:actor, user: user2)
event = insert(:event)
comment = insert(:comment, actor: actor2, visibility: :private)
insert(:participant,
event: event,
actor: anonymous_actor,
metadata: %{email: "anon@mou.se"}
)
conversation =
insert(:conversation, event: event, last_comment: comment, origin_comment: comment)
conversation_participant =
insert(:conversation_participant, conversation: conversation, actor: anonymous_actor)
args =
Map.merge(@private_announcement, %{
"subject_params" => %{
"conversation_id" => conversation.id,
"conversation_participant_id" => conversation_participant.id,
"conversation_text" => conversation.last_comment.text,
"conversation_event_id" => event.id,
"conversation_event_title" => event.title,
"conversation_event_uuid" => event.uuid
},
"author_id" => conversation.last_comment.actor.id,
"object_id" => conversation.last_comment.id,
"participant" => %{
"actor_id" => anonymous_actor.id,
"id" => conversation_participant.id
}
})
LegacyNotifierBuilder.perform(%Oban.Job{args: args})
assert_email_sending(%Swoosh.Email{to: [{"", "anon@mou.se"}]}, 10_000)
refute_email_sent(%Swoosh.Email{to: [{"", "user2@do.main"}]})
# Because of timeouts, can't do that currently
# refute_email_sent(%Swoosh.Email{to: [{"", "anon@mou.se"}]})
end
end
end

View file

@ -0,0 +1,91 @@
# The following module is taken from this issue
# https://github.com/swoosh/swoosh/issues/488#issuecomment-1671224765
defmodule Mobilizon.Tests.SwooshAssertions do
@moduledoc ~S"""
Assertions for emails.
The assertions provided by this module work by pattern matching
against all emails received by the test process against the
`Swoosh.Email` struct. For example:
assert_email_sent %{subject: "You got a message"}
If you want to be additionally explicit, you might:
assert_email_sent %Swoosh.Email{subject: "You got a message"}
If emails are being sent concurrently, you can use `assert_email_sending/2`:
assert_email_sending %{subject: "You got a message"}
Both functions will return the matched email if the assertion succeeds.
You can then perform further matches on it:
email = assert_email_sent %Swoosh.Email{subject: "You got a message"}
assert email.from == {"MyApp", "no-reply@example.com"}
Using pattern matching imposes two limitations. The first one is that you
must match precisely the Swoosh.Email structure. For example, the following
will not work:
assert_email_sent %{to: "foobar@example.com"}
That's because `Swoosh.Email` keeps the field as a list. This will work:
assert_email_sent %{to: [{"FooBar", "foobar@example.com"}]}
You are also not allowed to have interpolations. For example, the following
will not work:
assert_email_sent %{
subject: "You have been invited to #{org.name}",
to: [{user.name, user.email}]
}
However, you can rely on pattern matching and rewrite it as:
email = assert_email_sent %{subject: "You have been invited to " <> org_name}
assert org_name == org.name
assert email.to == [{user.name, user.email}]
"""
@doc """
Matches an email has been sent.
See moduledoc for more information.
"""
defmacro assert_email_sent(pattern) do
quote do
{:email, email} = assert_received({:email, unquote(pattern)})
email
end
end
@doc """
Matches an email is sending (within a timeout).
See moduledoc for more information.
"""
defmacro assert_email_sending(
pattern,
timeout \\ Application.fetch_env!(:ex_unit, :assert_receive_timeout)
) do
quote do
{:email, email} = assert_receive({:email, unquote(pattern)}, unquote(timeout))
email
end
end
@doc """
Refutes an email matching pattern has been sent.
The opposite of `assert_email_sent`.
"""
defmacro refute_email_sent(pattern) do
quote do
refute_received({:email, unquote(pattern)})
end
end
end

View file

@ -1,5 +0,0 @@
// vetur.config.js
/** @type {import('vls').VeturConfig} */
module.exports = {
projects: ["./js"],
};