Merge branch 'fixes' into 'main'
Various fixes Closes #1384, #1382 et #1413 See merge request framasoft/mobilizon!1523
This commit is contained in:
commit
5429afba21
10
.credo.exs
10
.credo.exs
|
@ -108,9 +108,10 @@
|
|||
{Credo.Check.Refactor.MatchInCondition, []},
|
||||
{Credo.Check.Refactor.NegatedConditionsInUnless, []},
|
||||
{Credo.Check.Refactor.NegatedConditionsWithElse, []},
|
||||
{Credo.Check.Refactor.Nesting, [
|
||||
max_nesting: 3
|
||||
]},
|
||||
{Credo.Check.Refactor.Nesting,
|
||||
[
|
||||
max_nesting: 3
|
||||
]},
|
||||
{Credo.Check.Refactor.PipeChainStart,
|
||||
[
|
||||
excluded_argument_types: [:atom, :binary, :fn, :keyword, :number],
|
||||
|
@ -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}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
|
@ -1,3 +1 @@
|
|||
#!/usr/bin/env sh
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
npm run pre-commit
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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)
|
||||
]
|
||||
|
|
|
@ -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) ->
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -335,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
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) ->
|
||||
|
|
12
lib/service/config/helpers.ex
Normal file
12
lib/service/config/helpers.ex
Normal 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
|
|
@ -136,14 +136,12 @@ 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
|
||||
case DateTime.shift_zone(begins_on, timezone) do
|
||||
{:ok, begins_on} -> begins_on
|
||||
{:error, _err} -> begins_on
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -56,7 +56,6 @@ defmodule Mobilizon.Service.SiteMap do
|
|||
end)
|
||||
|> Sitemapper.generate(config)
|
||||
|> Sitemapper.persist(config)
|
||||
|> Sitemapper.ping(config)
|
||||
|> Stream.run()
|
||||
end,
|
||||
timeout: :infinity
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
16
mix.exs
16
mix.exs
|
@ -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
|
||||
dependency
|
||||
else
|
||||
[strategy] -> "ueberauth_#{strategy}"
|
||||
case String.split(strategy_entry, ":") do
|
||||
[_strategy, dependency] ->
|
||||
dependency
|
||||
|
||||
[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/,
|
||||
|
|
36
mix.lock
36
mix.lock
|
@ -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"},
|
||||
|
|
2823
package-lock.json
generated
2823
package-lock.json
generated
File diff suppressed because it is too large
Load diff
25
package.json
25
package.json
|
@ -15,8 +15,9 @@
|
|||
"story:preview": "histoire preview",
|
||||
"test": "vitest",
|
||||
"coverage": "vitest run --coverage",
|
||||
"prepare": "husky install",
|
||||
"pre-commit": "lint-staged"
|
||||
"prepare": "husky",
|
||||
"pre-commit": "lint-staged",
|
||||
"postinstall": "patch-package"
|
||||
},
|
||||
"lint-staged": {
|
||||
"**/*.{js,ts,vue}": [
|
||||
|
@ -28,6 +29,7 @@
|
|||
"mix credo"
|
||||
]
|
||||
},
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@apollo/client": "^3.3.16",
|
||||
"@framasoft/socket": "^1.0.0",
|
||||
|
@ -73,7 +75,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",
|
||||
|
@ -85,6 +87,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",
|
||||
|
@ -92,7 +95,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",
|
||||
|
@ -116,9 +119,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",
|
||||
|
@ -129,8 +132,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",
|
||||
|
@ -138,9 +141,9 @@
|
|||
"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",
|
||||
"vitest": "^0.34.1",
|
||||
"vitest": "^1.2.2",
|
||||
"vue-i18n-extract": "^2.0.4",
|
||||
"vue-router-mock": "^1.0.0"
|
||||
}
|
||||
|
|
66
patches/vue-i18n-extract+2.0.7.patch
Normal file
66
patches/vue-i18n-extract+2.0.7.patch
Normal 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);
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
module.exports = {
|
||||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -39,7 +39,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>
|
||||
|
|
|
@ -1,15 +1,17 @@
|
|||
<template>
|
||||
<o-field :label-for="id">
|
||||
<o-field :label-for="id" class="taginput-field">
|
||||
<template #label>
|
||||
{{ $t("Add some tags") }}
|
||||
<o-tooltip
|
||||
variant="dark"
|
||||
:label="
|
||||
$t('You can add tags by hitting the Enter key or by adding a comma')
|
||||
"
|
||||
>
|
||||
<HelpCircleOutline :size="16" />
|
||||
</o-tooltip>
|
||||
<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')
|
||||
"
|
||||
>
|
||||
<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) =>
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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<
|
||||
{
|
||||
|
|
|
@ -13,6 +13,9 @@ export const COMMENT_FIELDS_FRAGMENT = gql`
|
|||
actor {
|
||||
...ActorFragment
|
||||
}
|
||||
attributedTo {
|
||||
...ActorFragment
|
||||
}
|
||||
totalReplies
|
||||
insertedAt
|
||||
updatedAt
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1642,5 +1642,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}"
|
||||
}
|
|
@ -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}"
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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}`;
|
||||
|
|
|
@ -8,6 +8,7 @@ export interface IConversation {
|
|||
id?: string;
|
||||
actor?: IActor;
|
||||
lastComment?: IComment;
|
||||
originComment?: IComment;
|
||||
comments: Paginate<IComment>;
|
||||
participants: IActor[];
|
||||
updatedAt: string;
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
||||
|
|
|
@ -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")),
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
|
|
|
@ -924,9 +924,28 @@ const handleError = (err: any) => {
|
|||
console.error(err);
|
||||
|
||||
if (err.graphQLErrors !== undefined) {
|
||||
err.graphQLErrors.forEach(({ message }: { message: string }) => {
|
||||
notifier?.error(message);
|
||||
});
|
||||
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);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -349,11 +349,10 @@ const { result: loggedUserResult } = useQuery<{ loggedUser: IUser }>(
|
|||
USER_NOTIFICATIONS
|
||||
);
|
||||
const loggedUser = computed(() => loggedUserResult.value?.loggedUser);
|
||||
const feedTokens = computed(
|
||||
() =>
|
||||
loggedUser.value?.feedTokens.filter(
|
||||
(token: IFeedToken) => token.actor === null
|
||||
)
|
||||
const feedTokens = computed(() =>
|
||||
loggedUser.value?.feedTokens.filter(
|
||||
(token: IFeedToken) => token.actor === null
|
||||
)
|
||||
);
|
||||
|
||||
const { result: webPushEnabledResult } = useQuery<{
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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})
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
91
test/support/swoosh_assertions.ex
Normal file
91
test/support/swoosh_assertions.ex
Normal 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
|
|
@ -1,5 +0,0 @@
|
|||
// vetur.config.js
|
||||
/** @type {import('vls').VeturConfig} */
|
||||
module.exports = {
|
||||
projects: ["./js"],
|
||||
};
|
Loading…
Reference in a new issue