all developments of milestone 1
This commit is contained in:
@@ -57,15 +57,25 @@ defmodule Mobilizon.GraphQL.API.Events do
|
||||
defp process_picture(%{media_id: _picture_id} = args, _), do: args
|
||||
|
||||
defp process_picture(%{media: media}, %Actor{id: actor_id}) do
|
||||
with uploaded when is_map(uploaded) <-
|
||||
media
|
||||
|> Map.get(:file)
|
||||
|> Utils.make_media_data(description: Map.get(media, :name)) do
|
||||
# case url
|
||||
if Map.has_key?(media, :url) do
|
||||
%{
|
||||
file: Map.take(uploaded, [:url, :name, :content_type, :size]),
|
||||
metadata: Map.take(uploaded, [:width, :height, :blurhash]),
|
||||
file: %{"url" => media.url, "name" => media.name},
|
||||
actor_id: actor_id
|
||||
}
|
||||
|
||||
# case upload
|
||||
else
|
||||
with uploaded when is_map(uploaded) <-
|
||||
media
|
||||
|> Map.get(:file)
|
||||
|> Utils.make_media_data(description: Map.get(media, :name)) do
|
||||
%{
|
||||
file: Map.take(uploaded, [:url, :name, :content_type, :size]),
|
||||
metadata: Map.take(uploaded, [:width, :height, :blurhash]),
|
||||
actor_id: actor_id
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -5,11 +5,10 @@ defmodule Mobilizon.GraphQL.Resolvers.Admin do
|
||||
|
||||
import Mobilizon.Users.Guards
|
||||
|
||||
alias Mobilizon.{Actors, Admin, Config, Events, Instances, Users}
|
||||
alias Mobilizon.{Actors, Admin, Config, Events, Instances, Media, Users}
|
||||
alias Mobilizon.Actors.{Actor, Follower}
|
||||
alias Mobilizon.Admin.{ActionLog, Setting}
|
||||
alias Mobilizon.Admin.{ActionLog, Setting, SettingMedia}
|
||||
alias Mobilizon.Cldr.Language
|
||||
alias Mobilizon.Config
|
||||
alias Mobilizon.Discussions.Comment
|
||||
alias Mobilizon.Events.Event
|
||||
alias Mobilizon.Federation.ActivityPub.{Actions, Relay}
|
||||
@@ -20,6 +19,9 @@ defmodule Mobilizon.GraphQL.Resolvers.Admin do
|
||||
alias Mobilizon.Storage.Page
|
||||
alias Mobilizon.Users.User
|
||||
alias Mobilizon.Web.Email
|
||||
|
||||
alias Mobilizon.GraphQL.Resolvers.Media, as: MediaResolver
|
||||
|
||||
import Mobilizon.Web.Gettext
|
||||
require Logger
|
||||
|
||||
@@ -268,8 +270,11 @@ defmodule Mobilizon.GraphQL.Resolvers.Admin do
|
||||
with {:ok, res} <- Admin.save_settings("instance", args),
|
||||
res <-
|
||||
res
|
||||
|> Enum.map(fn {key, %Setting{value: value}} ->
|
||||
{key, Admin.get_setting_value(value)}
|
||||
|> Enum.map(fn {key, val} ->
|
||||
case val do
|
||||
%Setting{value: value} -> {key, Admin.get_setting_value(value)}
|
||||
%SettingMedia{media: media} -> {key, media}
|
||||
end
|
||||
end)
|
||||
|> Enum.into(%{}),
|
||||
:ok <- eventually_update_instance_actor(res) do
|
||||
@@ -284,6 +289,38 @@ defmodule Mobilizon.GraphQL.Resolvers.Admin do
|
||||
dgettext("errors", "You need to be logged-in and an administrator to save admin settings")}
|
||||
end
|
||||
|
||||
@spec get_media_setting(any(), any(), Absinthe.Resolution.t()) ::
|
||||
{:ok, Media.t()} | {:error, String.t()}
|
||||
def get_media_setting(_parent, %{group: group, name: name}, %{
|
||||
context: %{current_user: %User{role: role}}
|
||||
})
|
||||
when is_admin(role) do
|
||||
{:ok, MediaResolver.transform_media(Admin.get_admin_setting_media(group, name, nil))}
|
||||
end
|
||||
|
||||
def get_media_setting(_parent, _args, _resolution) do
|
||||
{:error,
|
||||
dgettext("errors", "You need to be logged-in and an administrator to access admin settings")}
|
||||
end
|
||||
|
||||
@spec get_instance_logo(any(), any(), Absinthe.Resolution.t()) ::
|
||||
{:ok, Media.t() | nil} | {:error, String.t()}
|
||||
def get_instance_logo(parent, _args, resolution) do
|
||||
get_media_setting(parent, %{group: "instance", name: "instance_logo"}, resolution)
|
||||
end
|
||||
|
||||
@spec get_instance_favicon(any(), any(), Absinthe.Resolution.t()) ::
|
||||
{:ok, Media.t() | nil} | {:error, String.t()}
|
||||
def get_instance_favicon(parent, _args, resolution) do
|
||||
get_media_setting(parent, %{group: "instance", name: "instance_favicon"}, resolution)
|
||||
end
|
||||
|
||||
@spec get_default_picture(any(), any(), Absinthe.Resolution.t()) ::
|
||||
{:ok, Media.t() | nil} | {:error, String.t()}
|
||||
def get_default_picture(parent, _args, resolution) do
|
||||
get_media_setting(parent, %{group: "instance", name: "default_picture"}, resolution)
|
||||
end
|
||||
|
||||
@spec update_user(any, map(), Absinthe.Resolution.t()) ::
|
||||
{:error, :invalid_argument | :user_not_found | binary | Ecto.Changeset.t()}
|
||||
| {:ok, Mobilizon.Users.User.t()}
|
||||
|
||||
@@ -5,8 +5,11 @@ defmodule Mobilizon.GraphQL.Resolvers.Config do
|
||||
|
||||
alias Mobilizon.Config
|
||||
alias Mobilizon.Events.Categories
|
||||
alias Mobilizon.Medias.Media
|
||||
alias Mobilizon.Service.{AntiSpam, FrontEndAnalytics}
|
||||
|
||||
alias Mobilizon.GraphQL.Resolvers.Media, as: MediaResolver
|
||||
|
||||
@doc """
|
||||
Gets config.
|
||||
"""
|
||||
@@ -31,6 +34,16 @@ defmodule Mobilizon.GraphQL.Resolvers.Config do
|
||||
{:ok, data}
|
||||
end
|
||||
|
||||
@spec instance_logo(any(), map(), Absinthe.Resolution.t()) :: {:ok, Media.t()}
|
||||
def instance_logo(_parent, _params, _resolution) do
|
||||
{:ok, MediaResolver.transform_media(Config.instance_logo())}
|
||||
end
|
||||
|
||||
@spec default_picture(any(), map(), Absinthe.Resolution.t()) :: {:ok, Media.t()}
|
||||
def default_picture(_parent, _params, _resolution) do
|
||||
{:ok, MediaResolver.transform_media(Config.default_picture())}
|
||||
end
|
||||
|
||||
@spec terms(any(), map(), Absinthe.Resolution.t()) :: {:ok, map()}
|
||||
def terms(_parent, %{locale: locale}, _resolution) do
|
||||
type = Config.instance_terms_type()
|
||||
@@ -94,10 +107,15 @@ defmodule Mobilizon.GraphQL.Resolvers.Config do
|
||||
registrations_allowlist: Config.instance_registrations_allowlist?(),
|
||||
contact: Config.contact(),
|
||||
demo_mode: Config.instance_demo_mode?(),
|
||||
long_events: Config.instance_long_events?(),
|
||||
description: Config.instance_description(),
|
||||
long_description: Config.instance_long_description(),
|
||||
slogan: Config.instance_slogan(),
|
||||
languages: Config.instance_languages(),
|
||||
instance_logo: Config.instance_logo(),
|
||||
primary_color: Config.primary_color(),
|
||||
secondary_color: Config.secondary_color(),
|
||||
default_picture: Config.default_picture(),
|
||||
anonymous: %{
|
||||
participation: %{
|
||||
allowed: Config.anonymous_participation?(),
|
||||
|
||||
@@ -18,6 +18,10 @@ defmodule Mobilizon.GraphQL.Resolvers.Media do
|
||||
do_fetch_media(media_id)
|
||||
end
|
||||
|
||||
def media(%{media_id: media_id} = _parent, _args, _resolution) do
|
||||
do_fetch_media(media_id)
|
||||
end
|
||||
|
||||
def media(%{picture: media} = _parent, _args, _resolution), do: {:ok, media}
|
||||
def media(_parent, %{id: media_id}, _resolution), do: do_fetch_media(media_id)
|
||||
def media(_parent, _args, _resolution), do: {:ok, nil}
|
||||
@@ -133,8 +137,10 @@ defmodule Mobilizon.GraphQL.Resolvers.Media do
|
||||
|
||||
def user_size(_parent, _args, _resolution), do: {:error, :unauthenticated}
|
||||
|
||||
@spec transform_media(Media.t()) :: map()
|
||||
defp transform_media(%Media{id: id, file: file, metadata: metadata}) do
|
||||
@spec transform_media(Media.t() | nil) :: map() | nil
|
||||
def transform_media(nil), do: nil
|
||||
|
||||
def transform_media(%Media{id: id, file: file, metadata: metadata}) do
|
||||
%{
|
||||
name: file.name,
|
||||
url: file.url,
|
||||
|
||||
@@ -124,6 +124,24 @@ defmodule Mobilizon.GraphQL.Schema.AdminType do
|
||||
field(:instance_terms_type, :instance_terms_type, description: "The instance's terms type")
|
||||
field(:instance_terms_url, :string, description: "The instance's terms URL")
|
||||
|
||||
field(:instance_logo, :media,
|
||||
description: "The instance's logo",
|
||||
resolve: &Admin.get_instance_logo/3
|
||||
)
|
||||
|
||||
field(:instance_favicon, :media,
|
||||
description: "The instance's favicon",
|
||||
resolve: &Admin.get_instance_favicon/3
|
||||
)
|
||||
|
||||
field(:default_picture, :media,
|
||||
description: "The default picture",
|
||||
resolve: &Admin.get_default_picture/3
|
||||
)
|
||||
|
||||
field(:primary_color, :string, description: "The instance's primary color")
|
||||
field(:secondary_color, :string, description: "The instance's secondary color")
|
||||
|
||||
field(:instance_privacy_policy, :string,
|
||||
description: "The instance's privacy policy body text"
|
||||
)
|
||||
@@ -412,6 +430,25 @@ defmodule Mobilizon.GraphQL.Schema.AdminType do
|
||||
arg(:instance_long_description, :string, description: "The instance's long description")
|
||||
arg(:instance_slogan, :string, description: "The instance's slogan")
|
||||
arg(:contact, :string, description: "The instance's contact details")
|
||||
|
||||
arg(:instance_logo, :media_input,
|
||||
description:
|
||||
"The instance's logo, either as an object or directly the ID of an existing media"
|
||||
)
|
||||
|
||||
arg(:instance_favicon, :media_input,
|
||||
description:
|
||||
"The instance's favicon, either as an object or directly the ID of an existing media"
|
||||
)
|
||||
|
||||
arg(:default_picture, :media_input,
|
||||
description:
|
||||
"The default picture, either as an object or directly the ID of an existing media"
|
||||
)
|
||||
|
||||
arg(:primary_color, :string, description: "The instance's primary color")
|
||||
arg(:secondary_color, :string, description: "The instance's secondary color")
|
||||
|
||||
arg(:instance_terms, :string, description: "The instance's terms body text")
|
||||
arg(:instance_terms_type, :instance_terms_type, description: "The instance's terms type")
|
||||
arg(:instance_terms_url, :string, description: "The instance's terms URL")
|
||||
|
||||
@@ -31,6 +31,7 @@ defmodule Mobilizon.GraphQL.Schema.ConfigType do
|
||||
)
|
||||
|
||||
field(:demo_mode, :boolean, description: "Whether the demo mode is enabled")
|
||||
field(:long_events, :boolean, description: "Whether the long events mode is enabled")
|
||||
field(:country_code, :string, description: "The country code from the IP")
|
||||
field(:location, :lonlat, description: "The IP's location")
|
||||
field(:geocoding, :geocoding, description: "The instance's geocoding settings")
|
||||
@@ -59,6 +60,17 @@ defmodule Mobilizon.GraphQL.Schema.ConfigType do
|
||||
resolve(&Config.terms/3)
|
||||
end
|
||||
|
||||
field(:instance_logo, :media, description: "The instance's logo") do
|
||||
resolve(&Config.instance_logo/3)
|
||||
end
|
||||
|
||||
field(:default_picture, :media, description: "The default picture") do
|
||||
resolve(&Config.default_picture/3)
|
||||
end
|
||||
|
||||
field(:primary_color, :string, description: "The instance's primary color")
|
||||
field(:secondary_color, :string, description: "The instance's secondary color")
|
||||
|
||||
field(:privacy, :privacy, description: "The instance's privacy policy") do
|
||||
arg(:locale, :string,
|
||||
default_value: "en",
|
||||
|
||||
@@ -263,6 +263,10 @@ defmodule Mobilizon.GraphQL.Schema.EventType do
|
||||
description: "Whether or not to show the participation price"
|
||||
)
|
||||
|
||||
field(:hide_number_of_participants, :boolean,
|
||||
description: "Whether or not the number of participants is hidden"
|
||||
)
|
||||
|
||||
field(:show_start_time, :boolean, description: "Show event start time")
|
||||
field(:show_end_time, :boolean, description: "Show event end time")
|
||||
|
||||
@@ -316,6 +320,10 @@ defmodule Mobilizon.GraphQL.Schema.EventType do
|
||||
description: "Whether or not to show the participation price"
|
||||
)
|
||||
|
||||
field(:hide_number_of_participants, :boolean,
|
||||
description: "Whether or not the number of participants is hidden"
|
||||
)
|
||||
|
||||
field(:show_start_time, :boolean, description: "Show event start time")
|
||||
field(:show_end_time, :boolean, description: "Show event end time")
|
||||
|
||||
|
||||
@@ -52,8 +52,9 @@ defmodule Mobilizon.GraphQL.Schema.MediaType do
|
||||
input_object :media_input_object do
|
||||
field(:name, non_null(:string), description: "The media's name")
|
||||
field(:alt, :string, description: "The media's alternative text")
|
||||
field(:file, non_null(:upload), description: "The media file")
|
||||
field(:file, :upload, description: "The media file")
|
||||
field(:actor_id, :id, description: "The media owner")
|
||||
field(:url, :string, description: "The media URL")
|
||||
end
|
||||
|
||||
object :media_queries do
|
||||
|
||||
@@ -273,6 +273,8 @@ defmodule Mobilizon.GraphQL.Schema.SearchType do
|
||||
description: "Radius around the location to search in"
|
||||
)
|
||||
|
||||
arg(:longevents, :boolean, description: "if mention filter in or out long events")
|
||||
|
||||
arg(:bbox, :string, description: "The bbox to search events into")
|
||||
arg(:zoom, :integer, description: "The zoom level for searching events")
|
||||
|
||||
|
||||
@@ -9,7 +9,8 @@ defmodule Mobilizon.Admin do
|
||||
alias Mobilizon.Actors.Actor
|
||||
alias Mobilizon.{Admin, Users}
|
||||
alias Mobilizon.Admin.ActionLog
|
||||
alias Mobilizon.Admin.Setting
|
||||
alias Mobilizon.Admin.{Setting, SettingMedia}
|
||||
alias Mobilizon.Medias.Media
|
||||
alias Mobilizon.Storage.{Page, Repo}
|
||||
alias Mobilizon.Users.User
|
||||
|
||||
@@ -78,9 +79,47 @@ defmodule Mobilizon.Admin do
|
||||
|
||||
defp stringify_struct(struct), do: struct
|
||||
|
||||
@spec get_all_admin_settings :: list(Setting.t())
|
||||
@spec get_all_admin_settings :: map()
|
||||
def get_all_admin_settings do
|
||||
Repo.all(Setting)
|
||||
medias =
|
||||
SettingMedia
|
||||
|> Repo.all()
|
||||
|> Repo.preload(:media)
|
||||
|> Enum.map(fn %SettingMedia{group: group, name: name, media: media} ->
|
||||
{group, name, media}
|
||||
end)
|
||||
|
||||
values =
|
||||
Setting
|
||||
|> Repo.all()
|
||||
|> Enum.map(fn %Setting{group: group, name: name, value: value} ->
|
||||
{group, name, get_setting_value(value)}
|
||||
end)
|
||||
|
||||
all_settings = Enum.concat(values, medias)
|
||||
|
||||
Enum.reduce(
|
||||
all_settings,
|
||||
%{},
|
||||
# For each {group,name,value}
|
||||
fn {group, name, value}, acc ->
|
||||
# We update the %{group: map} in the accumulator
|
||||
{_, new_acc} =
|
||||
Map.get_and_update(
|
||||
acc,
|
||||
group,
|
||||
# We put the %{name: value} into the %{group: map}
|
||||
fn group_map ->
|
||||
{
|
||||
group_map,
|
||||
Map.put(group_map || %{}, name, value)
|
||||
}
|
||||
end
|
||||
)
|
||||
|
||||
new_acc
|
||||
end
|
||||
)
|
||||
end
|
||||
|
||||
@spec get_admin_setting_value(String.t(), String.t(), String.t() | nil) ::
|
||||
@@ -119,21 +158,40 @@ defmodule Mobilizon.Admin do
|
||||
end
|
||||
end
|
||||
|
||||
@spec get_admin_setting_media(String.t(), String.t(), String.t() | nil) ::
|
||||
{:ok, Media.t()} | {:error, :not_found} | nil
|
||||
def get_admin_setting_media(group, name, fallback \\ nil)
|
||||
when is_binary(group) and is_binary(name) do
|
||||
case SettingMedia
|
||||
|> where(group: ^group)
|
||||
|> where(name: ^name)
|
||||
|> preload(:media)
|
||||
|> Repo.one() do
|
||||
nil ->
|
||||
fallback
|
||||
|
||||
%SettingMedia{media: media} ->
|
||||
media
|
||||
|
||||
%SettingMedia{} ->
|
||||
fallback
|
||||
end
|
||||
end
|
||||
|
||||
@spec save_settings(String.t(), map()) :: {:ok, any} | {:error, any}
|
||||
def save_settings(group, args) do
|
||||
{medias, values} = Map.split(args, [:instance_logo, :instance_favicon, :default_picture])
|
||||
|
||||
Multi.new()
|
||||
|> do_save_setting(group, args)
|
||||
|> do_save_media_setting(group, medias)
|
||||
|> do_save_value_setting(group, values)
|
||||
|> Repo.transaction()
|
||||
end
|
||||
|
||||
def clear_settings(group) do
|
||||
Setting |> where([s], s.group == ^group) |> Repo.delete_all()
|
||||
end
|
||||
@spec do_save_value_setting(Ecto.Multi.t(), String.t(), map()) :: Ecto.Multi.t()
|
||||
defp do_save_value_setting(transaction, _group, args) when args == %{}, do: transaction
|
||||
|
||||
@spec do_save_setting(Ecto.Multi.t(), String.t(), map()) :: Ecto.Multi.t()
|
||||
defp do_save_setting(transaction, _group, args) when args == %{}, do: transaction
|
||||
|
||||
defp do_save_setting(transaction, group, args) do
|
||||
defp do_save_value_setting(transaction, group, args) do
|
||||
key = hd(Map.keys(args))
|
||||
{val, rest} = Map.pop(args, key)
|
||||
|
||||
@@ -150,7 +208,40 @@ defmodule Mobilizon.Admin do
|
||||
conflict_target: [:group, :name]
|
||||
)
|
||||
|
||||
do_save_setting(transaction, group, rest)
|
||||
do_save_value_setting(transaction, group, rest)
|
||||
end
|
||||
|
||||
@spec do_save_media_setting(Ecto.Multi.t(), String.t(), map()) :: Ecto.Multi.t()
|
||||
defp do_save_media_setting(transaction, _group, args) when args == %{}, do: transaction
|
||||
|
||||
defp do_save_media_setting(transaction, group, args) do
|
||||
key = hd(Map.keys(args))
|
||||
{val, rest} = Map.pop(args, key)
|
||||
|
||||
transaction =
|
||||
case val do
|
||||
val ->
|
||||
Multi.insert(
|
||||
transaction,
|
||||
key,
|
||||
SettingMedia.changeset(%SettingMedia{}, %{
|
||||
group: group,
|
||||
name: Atom.to_string(key),
|
||||
media: val
|
||||
}),
|
||||
on_conflict: :replace_all,
|
||||
conflict_target: [:group, :name]
|
||||
)
|
||||
end
|
||||
|
||||
do_save_media_setting(transaction, group, rest)
|
||||
end
|
||||
|
||||
def clear_settings(group) do
|
||||
Multi.new()
|
||||
|> Multi.delete_all(:settings, Setting |> where([s], s.group == ^group))
|
||||
|> Multi.delete_all(:settings_medias, SettingMedia |> where([s], s.group == ^group))
|
||||
|> Repo.transaction()
|
||||
end
|
||||
|
||||
@spec convert_to_string(any()) :: String.t()
|
||||
|
||||
@@ -4,6 +4,7 @@ defmodule Mobilizon.Admin.Setting do
|
||||
"""
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
alias Ecto.Changeset
|
||||
|
||||
@required_attrs [:group, :name]
|
||||
@optional_attrs [:value]
|
||||
@@ -32,3 +33,93 @@ defmodule Mobilizon.Admin.Setting do
|
||||
|> unique_constraint(:group, name: :admin_settings_group_name_index)
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Mobilizon.Admin.SettingMedia do
|
||||
@moduledoc """
|
||||
A Key-Value settings table for media settings
|
||||
"""
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
alias Ecto.Changeset
|
||||
alias Mobilizon.Federation.ActivityPub.Relay
|
||||
alias Mobilizon.Medias
|
||||
alias Mobilizon.Medias.Media
|
||||
alias Mobilizon.Storage.Repo
|
||||
|
||||
@required_attrs [:group, :name]
|
||||
|
||||
@type t :: %{
|
||||
group: String.t(),
|
||||
name: String.t(),
|
||||
media: Media.t()
|
||||
}
|
||||
|
||||
schema "admin_settings_medias" do
|
||||
field(:group, :string)
|
||||
field(:name, :string)
|
||||
belongs_to(:media, Media, on_replace: :delete)
|
||||
|
||||
timestamps()
|
||||
end
|
||||
|
||||
@doc false
|
||||
@spec changeset(t | Ecto.Schema.t(), map) :: Ecto.Changeset.t()
|
||||
def changeset(setting_media, attrs) do
|
||||
setting_media
|
||||
|> Repo.preload(:media)
|
||||
|> cast(attrs, @required_attrs)
|
||||
|> put_media(attrs)
|
||||
|> validate_required(@required_attrs)
|
||||
|> unique_constraint(:group, name: :admin_settings_medias_group_name_index)
|
||||
end
|
||||
|
||||
# # In case the provided media is an existing one
|
||||
@spec put_media(Changeset.t(), map) :: Changeset.t()
|
||||
defp put_media(%Changeset{} = changeset, %{media: %{media_id: id}}) do
|
||||
%Media{} = media = Medias.get_media!(id)
|
||||
put_assoc(changeset, :media, media)
|
||||
end
|
||||
|
||||
# In case it's a new media
|
||||
defp put_media(%Changeset{} = changeset, %{media: %{media: media}}) do
|
||||
{:ok, media} = upload_media(media)
|
||||
put_assoc(changeset, :media, media)
|
||||
end
|
||||
|
||||
# In case there is no media
|
||||
defp put_media(%Changeset{} = changeset, _media) do
|
||||
put_assoc(changeset, :media, nil)
|
||||
end
|
||||
|
||||
import Mobilizon.Web.Gettext
|
||||
@spec upload_media(map) :: {:ok, Media.t()} | {:error, any}
|
||||
defp upload_media(%{file: %Plug.Upload{} = file} = args) do
|
||||
with {:ok,
|
||||
%{
|
||||
name: _name,
|
||||
url: url,
|
||||
content_type: content_type,
|
||||
size: size
|
||||
} = uploaded} <-
|
||||
Mobilizon.Web.Upload.store(file),
|
||||
args <-
|
||||
args
|
||||
|> Map.put(:url, url)
|
||||
|> Map.put(:size, size)
|
||||
|> Map.put(:content_type, content_type),
|
||||
{:ok, media = %Media{}} <-
|
||||
Medias.create_media(%{
|
||||
file: args,
|
||||
actor_id: Map.get(args, :actor_id, Relay.get_actor().id),
|
||||
metadata: Map.take(uploaded, [:width, :height, :blurhash])
|
||||
}) do
|
||||
{:ok, media}
|
||||
else
|
||||
{:error, :mime_type_not_allowed} ->
|
||||
{:error, dgettext("errors", "File doesn't have an allowed MIME type.")}
|
||||
|
||||
error ->
|
||||
{:error, error}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,7 +4,8 @@ defmodule Mobilizon.Config do
|
||||
"""
|
||||
|
||||
alias Mobilizon.Actors
|
||||
alias Mobilizon.Admin.Setting
|
||||
alias Mobilizon.Admin
|
||||
alias Mobilizon.Medias.Media
|
||||
alias Mobilizon.Service.GitStatus
|
||||
require Logger
|
||||
import Mobilizon.Service.Export.Participants.Common, only: [enabled_formats: 0]
|
||||
@@ -29,56 +30,18 @@ defmodule Mobilizon.Config do
|
||||
@spec instance_config :: mobilizon_config
|
||||
def instance_config, do: Application.get_env(:mobilizon, :instance)
|
||||
|
||||
@spec db_instance_config :: list(Setting.t())
|
||||
def db_instance_config, do: Mobilizon.Admin.get_all_admin_settings()
|
||||
|
||||
@spec config_cache :: map()
|
||||
def config_cache do
|
||||
case Cachex.fetch(:config, :all_db_config, fn _key ->
|
||||
value =
|
||||
Enum.reduce(
|
||||
Mobilizon.Admin.get_all_admin_settings(),
|
||||
%{},
|
||||
&arrange_values/2
|
||||
)
|
||||
|
||||
{:commit, value}
|
||||
end) do
|
||||
case Cachex.fetch(
|
||||
:config,
|
||||
:all_db_config,
|
||||
fn _key -> {:commit, Admin.get_all_admin_settings()} end
|
||||
) do
|
||||
{status, value} when status in [:ok, :commit] -> value
|
||||
_err -> %{}
|
||||
end
|
||||
end
|
||||
|
||||
@spec arrange_values(Setting.t(), map()) :: map()
|
||||
defp arrange_values(setting, acc) do
|
||||
{_, new_data} =
|
||||
Map.get_and_update(acc, setting.group, fn current_value ->
|
||||
new_value = current_value || %{}
|
||||
|
||||
{current_value, Map.put(new_value, setting.name, process_value(setting.value))}
|
||||
end)
|
||||
|
||||
new_data
|
||||
end
|
||||
|
||||
@spec process_value(String.t() | nil) :: any()
|
||||
defp process_value(nil), do: nil
|
||||
defp process_value(""), do: nil
|
||||
|
||||
defp process_value(value) do
|
||||
case Jason.decode(value) do
|
||||
{:ok, val} ->
|
||||
val
|
||||
|
||||
{:error, _} ->
|
||||
case value do
|
||||
"true" -> true
|
||||
"false" -> false
|
||||
value -> value
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@spec config_cached_value(String.t(), String.t(), String.t()) :: any()
|
||||
def config_cached_value(group, name, fallback \\ nil) do
|
||||
config_cache()
|
||||
@@ -115,10 +78,23 @@ defmodule Mobilizon.Config do
|
||||
@spec instance_slogan :: String.t() | nil
|
||||
def instance_slogan, do: config_cached_value("instance", "instance_slogan")
|
||||
|
||||
@spec instance_logo :: Media.t() | nil
|
||||
def instance_logo, do: config_cached_value("instance", "instance_logo")
|
||||
|
||||
@spec instance_favicon :: Media.t() | nil
|
||||
def instance_favicon, do: config_cached_value("instance", "instance_favicon")
|
||||
|
||||
@spec default_picture :: Media.t() | nil
|
||||
def default_picture, do: config_cached_value("instance", "default_picture")
|
||||
|
||||
@spec primary_color :: Media.t() | nil
|
||||
def primary_color, do: config_cached_value("instance", "primary_color")
|
||||
|
||||
@spec secondary_color :: Media.t() | nil
|
||||
def secondary_color, do: config_cached_value("instance", "secondary_color")
|
||||
|
||||
@spec contact :: String.t() | nil
|
||||
def contact do
|
||||
config_cached_value("instance", "contact")
|
||||
end
|
||||
def contact, do: config_cached_value("instance", "contact")
|
||||
|
||||
@spec instance_terms(String.t()) :: String.t()
|
||||
def instance_terms(locale \\ "en") do
|
||||
@@ -202,6 +178,9 @@ defmodule Mobilizon.Config do
|
||||
@spec instance_demo_mode? :: boolean
|
||||
def instance_demo_mode?, do: to_boolean(instance_config()[:demo])
|
||||
|
||||
@spec instance_long_events? :: boolean
|
||||
def instance_long_events?, do: instance_config()[:duration_of_long_event] > 0
|
||||
|
||||
@spec instance_repository :: String.t()
|
||||
def instance_repository, do: instance_config()[:repository]
|
||||
|
||||
@@ -470,6 +449,9 @@ defmodule Mobilizon.Config do
|
||||
instance_slogan: instance_slogan(),
|
||||
registrations_open: instance_registrations_open?(),
|
||||
contact: contact(),
|
||||
primary_color: primary_color(),
|
||||
secondary_color: secondary_color(),
|
||||
instance_logo: instance_logo(),
|
||||
instance_terms: instance_terms(),
|
||||
instance_terms_type: instance_terms_type(),
|
||||
instance_terms_url: instance_terms_url(),
|
||||
|
||||
@@ -25,6 +25,7 @@ defmodule Mobilizon.Events.EventOptions do
|
||||
show_participation_price: boolean,
|
||||
offers: [EventOffer.t()],
|
||||
participation_condition: [EventParticipationCondition.t()],
|
||||
hide_number_of_participants: boolean,
|
||||
show_start_time: boolean,
|
||||
show_end_time: boolean,
|
||||
timezone: String.t() | nil,
|
||||
@@ -41,6 +42,7 @@ defmodule Mobilizon.Events.EventOptions do
|
||||
:program,
|
||||
:comment_moderation,
|
||||
:show_participation_price,
|
||||
:hide_number_of_participants,
|
||||
:show_start_time,
|
||||
:show_end_time,
|
||||
:timezone,
|
||||
@@ -59,6 +61,7 @@ defmodule Mobilizon.Events.EventOptions do
|
||||
field(:program, :string)
|
||||
field(:comment_moderation, CommentModeration)
|
||||
field(:show_participation_price, :boolean)
|
||||
field(:hide_number_of_participants, :boolean, default: false)
|
||||
field(:show_start_time, :boolean, default: true)
|
||||
field(:show_end_time, :boolean, default: true)
|
||||
field(:timezone, :string)
|
||||
|
||||
@@ -16,6 +16,7 @@ defmodule Mobilizon.Events do
|
||||
|
||||
alias Mobilizon.Actors.{Actor, Follower}
|
||||
alias Mobilizon.Addresses.Address
|
||||
alias Mobilizon.Config
|
||||
|
||||
alias Mobilizon.Events.{
|
||||
Event,
|
||||
@@ -571,6 +572,7 @@ defmodule Mobilizon.Events do
|
||||
|> events_for_search_query()
|
||||
|> events_for_begins_on(Map.get(args, :begins_on, DateTime.utc_now()))
|
||||
|> events_for_ends_on(Map.get(args, :ends_on))
|
||||
|> events_for_longevents(args)
|
||||
|> events_for_category(args)
|
||||
|> events_for_categories(args)
|
||||
|> events_for_languages(args)
|
||||
@@ -1377,6 +1379,38 @@ defmodule Mobilizon.Events do
|
||||
end
|
||||
end
|
||||
|
||||
@spec events_for_longevents(Ecto.Queryable.t(), map()) :: Ecto.Query.t()
|
||||
defp events_for_longevents(query, args) do
|
||||
duration = Config.get([:instance, :duration_of_long_event], 0)
|
||||
|
||||
if duration <= 0 do
|
||||
query
|
||||
else
|
||||
longevents = Map.get(args, :longevents)
|
||||
|
||||
case longevents do
|
||||
nil ->
|
||||
query
|
||||
|
||||
true ->
|
||||
where(
|
||||
query,
|
||||
[q],
|
||||
not is_nil(q.ends_on) and
|
||||
q.ends_on > fragment("? + '1 days'::interval * ?", q.begins_on, ^duration)
|
||||
)
|
||||
|
||||
false ->
|
||||
where(
|
||||
query,
|
||||
[q],
|
||||
is_nil(q.ends_on) or
|
||||
q.ends_on <= fragment("? + '1 days'::interval * ?", q.begins_on, ^duration)
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@spec events_for_category(Ecto.Queryable.t(), map()) :: Ecto.Query.t()
|
||||
defp events_for_category(query, %{category: category}) when is_valid_string(category) do
|
||||
where(query, [q], q.category == ^category)
|
||||
|
||||
@@ -185,7 +185,8 @@ defmodule Mobilizon.Medias do
|
||||
[from: "events_medias", param: "media_id"],
|
||||
[from: "posts", param: "picture_id"],
|
||||
[from: "posts_medias", param: "media_id"],
|
||||
[from: "comments_medias", param: "media_id"]
|
||||
[from: "comments_medias", param: "media_id"],
|
||||
[from: "admin_settings_medias", param: "media_id"]
|
||||
]
|
||||
|> Enum.map_join(" UNION ", fn [from: from, param: param] ->
|
||||
"SELECT 1 FROM #{from} WHERE #{from}.#{param} = m0.id"
|
||||
|
||||
44
lib/service/geospatial/hat.ex
Normal file
44
lib/service/geospatial/hat.ex
Normal file
@@ -0,0 +1,44 @@
|
||||
defmodule Mobilizon.Service.Geospatial.Hat do
|
||||
@moduledoc """
|
||||
Hat backend.
|
||||
"""
|
||||
|
||||
alias Mobilizon.Addresses.Address
|
||||
alias Mobilizon.Service.Geospatial.Addok
|
||||
alias Mobilizon.Service.Geospatial.Nominatim
|
||||
alias Mobilizon.Service.Geospatial.Provider
|
||||
import Mobilizon.Service.Geospatial.Provider, only: [endpoint: 1]
|
||||
require Logger
|
||||
|
||||
@behaviour Provider
|
||||
|
||||
@impl Provider
|
||||
@doc """
|
||||
Hat implementation for `c:Mobilizon.Service.Geospatial.Provider.geocode/3`.
|
||||
"""
|
||||
@spec geocode(String.t(), keyword()) :: list(Address.t())
|
||||
def geocode(lon, lat, options \\ []) do
|
||||
tasks = [
|
||||
Task.async(fn -> Addok.geocode(lon, lat, options) end),
|
||||
Task.async(fn -> Nominatim.geocode(lon, lat, options) end)
|
||||
]
|
||||
|
||||
[addrlist1, addrlist2] = Task.await_many(tasks, 12_000)
|
||||
addrlist2 ++ addrlist1
|
||||
end
|
||||
|
||||
@impl Provider
|
||||
@doc """
|
||||
Hat implementation for `c:Mobilizon.Service.Geospatial.Provider.search/2`.
|
||||
"""
|
||||
@spec search(String.t(), keyword()) :: list(Address.t())
|
||||
def search(q, options \\ []) do
|
||||
tasks = [
|
||||
Task.async(fn -> Addok.search(q, options) end),
|
||||
Task.async(fn -> Nominatim.search(q, options) end)
|
||||
]
|
||||
|
||||
[addrlist1, addrlist2] = Task.await_many(tasks, 12_000)
|
||||
addrlist2 ++ addrlist1
|
||||
end
|
||||
end
|
||||
58
lib/web/controllers/manifest_controller.ex
Normal file
58
lib/web/controllers/manifest_controller.ex
Normal file
@@ -0,0 +1,58 @@
|
||||
defmodule Mobilizon.Web.ManifestController do
|
||||
use Mobilizon.Web, :controller
|
||||
|
||||
alias Mobilizon.Config
|
||||
alias Mobilizon.Medias.Media
|
||||
|
||||
@spec manifest(Plug.Conn.t(), any) :: Plug.Conn.t()
|
||||
def manifest(conn, _params) do
|
||||
favicons =
|
||||
case Config.instance_favicon() do
|
||||
%Media{file: %{url: url}, metadata: metadata} ->
|
||||
[
|
||||
Map.merge(
|
||||
%{
|
||||
src: url
|
||||
},
|
||||
case metadata do
|
||||
%{width: width} -> %{sizes: "#{width}x#{width}"}
|
||||
_ -> %{}
|
||||
end
|
||||
)
|
||||
]
|
||||
|
||||
_ ->
|
||||
[
|
||||
%{
|
||||
src: "./img/icons/android-chrome-512x512.png",
|
||||
sizes: "512x512",
|
||||
type: "image/png"
|
||||
},
|
||||
%{
|
||||
src: "./img/icons/android-chrome-192x192.png",
|
||||
sizes: "192x192",
|
||||
type: "image/png"
|
||||
}
|
||||
]
|
||||
end
|
||||
|
||||
json(conn, %{
|
||||
name: Config.instance_name(),
|
||||
start_url: "/",
|
||||
scope: "/",
|
||||
display: "standalone",
|
||||
background_color: "#ffffff",
|
||||
theme_color: "#ffd599",
|
||||
orientation: "portrait-primary",
|
||||
icons: favicons
|
||||
})
|
||||
end
|
||||
|
||||
@spec favicon(Plug.Conn.t(), any) :: Plug.Conn.t()
|
||||
def favicon(conn, _params) do
|
||||
case Config.instance_favicon() do
|
||||
%Media{file: %{url: url}} -> redirect(conn, external: url)
|
||||
_ -> redirect(conn, to: "/img/icons/favicon.ico")
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -18,6 +18,8 @@ defmodule Mobilizon.Web.PageController do
|
||||
defdelegate my_events(conn, params), to: PageController, as: :index
|
||||
@spec create_event(Plug.Conn.t(), any) :: Plug.Conn.t()
|
||||
defdelegate create_event(conn, params), to: PageController, as: :index
|
||||
@spec calendar(Plug.Conn.t(), any) :: Plug.Conn.t()
|
||||
defdelegate calendar(conn, params), to: PageController, as: :index
|
||||
@spec list_events(Plug.Conn.t(), any) :: Plug.Conn.t()
|
||||
defdelegate list_events(conn, params), to: PageController, as: :index
|
||||
@spec edit_event(Plug.Conn.t(), any) :: Plug.Conn.t()
|
||||
|
||||
@@ -18,8 +18,7 @@ defmodule Mobilizon.Web do
|
||||
"""
|
||||
|
||||
def static_paths,
|
||||
do:
|
||||
~w(index.html manifest.json manifest.webmanifest service-worker.js css fonts img js favicon.ico robots.txt assets)
|
||||
do: ~w(index.html service-worker.js css fonts img js robots.txt assets)
|
||||
|
||||
def controller do
|
||||
quote do
|
||||
|
||||
@@ -77,7 +77,7 @@ defmodule Mobilizon.Web.Plugs.HTTPSecurityPlug do
|
||||
# unsafe-eval is because of JS issues with regenerator-runtime
|
||||
@script_src "script-src 'self' 'unsafe-eval' "
|
||||
@style_src "style-src 'self' "
|
||||
@font_src "font-src 'self' "
|
||||
@font_src "font-src 'self' data: "
|
||||
|
||||
@spec csp_string(Keyword.t()) :: String.t()
|
||||
defp csp_string(options) do
|
||||
@@ -117,6 +117,8 @@ defmodule Mobilizon.Web.Plugs.HTTPSecurityPlug do
|
||||
|
||||
style_src = [style_src] ++ [get_csp_config(:style_src, options)]
|
||||
|
||||
style_src = [style_src] ++ ["'sha256-47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU='"]
|
||||
|
||||
font_src = [@font_src] ++ [get_csp_config(:font_src, options)]
|
||||
|
||||
frame_src = build_csp_field(:frame_src, options)
|
||||
|
||||
@@ -113,6 +113,12 @@ defmodule Mobilizon.Web.Router do
|
||||
get("/nodeinfo/:version", NodeInfoController, :nodeinfo)
|
||||
end
|
||||
|
||||
scope "/", Mobilizon.Web do
|
||||
get("/manifest.webmanifest", ManifestController, :manifest)
|
||||
get("/manifest.json", ManifestController, :manifest)
|
||||
get("/favicon.ico", ManifestController, :favicon)
|
||||
end
|
||||
|
||||
scope "/", Mobilizon.Web do
|
||||
pipe_through(:activity_pub_and_html)
|
||||
pipe_through(:activity_pub_signature)
|
||||
@@ -120,6 +126,7 @@ defmodule Mobilizon.Web.Router do
|
||||
get("/@:name", PageController, :actor)
|
||||
get("/events/me", PageController, :my_events)
|
||||
get("/events/create", PageController, :create_event)
|
||||
get("/events/calendar", PageController, :calendar)
|
||||
get("/events/:uuid", PageController, :event)
|
||||
get("/comments/:uuid", PageController, :comment)
|
||||
get("/resource/:uuid", PageController, :resource)
|
||||
@@ -188,6 +195,7 @@ defmodule Mobilizon.Web.Router do
|
||||
get("/events/create", PageController, :create_event)
|
||||
get("/events/list", PageController, :list_events)
|
||||
get("/events/me", PageController, :my_events)
|
||||
get("/events/calendar", PageController, :calendar)
|
||||
get("/events/:uuid/edit", PageController, :edit_event)
|
||||
|
||||
# This is a hack to ease link generation into emails
|
||||
|
||||
@@ -54,7 +54,7 @@
|
||||
<p>
|
||||
<%= pgettext(
|
||||
"terms",
|
||||
"When we say “we”, “our”, or “us” in this document, we are referring to the owners, operators and administrators of this Mobilizon instance. The Mobilizon software is provided by the team of Mobilizon contributors, supported by <a href=\"https://framasoft.org\">Framasoft</a>, a French not-for-profit organization advocating for Free/Libre Software. Unless explicitly stated, this Mobilizon instance is an independent service using Mobilizon's source code. You may find more information about this instance on the <a href=\"/about/instance\">\"About this instance\"</a> page."
|
||||
"When we say “we”, “our”, or “us” in this document, we are referring to the owners, operators and administrators of this Mobilizon instance. The Mobilizon software is provided by the team of Mobilizon contributors. Unless explicitly stated, this Mobilizon instance is an independent service using Mobilizon's source code. You may find more information about this instance on the <a href=\"/about/instance\">\"About this instance\"</a> page."
|
||||
)
|
||||
|> raw %>
|
||||
</p>
|
||||
|
||||
@@ -4,15 +4,16 @@
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="apple-touch-icon" href="/img/icons/apple-touch-icon-152x152.png" sizes="152x152" />
|
||||
<link rel="apple-touch-icon" href={favicon_url()} sizes={favicon_sizes()} />
|
||||
<link rel="icon" href={favicon_url()} sizes={favicon_sizes()} />
|
||||
<link rel="mask-icon" href="/img/icons/safari-pinned-tab.svg" color={theme_color()} />
|
||||
<link rel="manifest" href="/manifest.webmanifest" />
|
||||
<meta name="theme-color" content={theme_color()} />
|
||||
<script>
|
||||
if (localStorage.theme === 'dark' || (!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
|
||||
document.documentElement.classList.add('dark')
|
||||
document.documentElement.classList.add('dark')
|
||||
} else {
|
||||
document.documentElement.classList.remove('dark')
|
||||
document.documentElement.classList.remove('dark')
|
||||
}
|
||||
</script>
|
||||
<%= if root?(assigns) do %>
|
||||
@@ -24,6 +25,7 @@
|
||||
<%= Vite.vite_client() %>
|
||||
<%= Vite.vite_snippet("src/main.ts") %>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<noscript>
|
||||
<strong>
|
||||
|
||||
@@ -6,6 +6,7 @@ defmodule Mobilizon.Web.PageView do
|
||||
use Mobilizon.Web, :view
|
||||
|
||||
alias Mobilizon.Actors.Actor
|
||||
alias Mobilizon.Config
|
||||
alias Mobilizon.Discussions.{Comment, Discussion}
|
||||
alias Mobilizon.Events.Event
|
||||
alias Mobilizon.Posts.Post
|
||||
@@ -91,4 +92,27 @@ defmodule Mobilizon.Web.PageView do
|
||||
def root?(assigns) do
|
||||
assigns |> Map.get(:conn, %{request_path: "/"}) |> Map.get(:request_path, "/") == "/"
|
||||
end
|
||||
|
||||
defp favicon do
|
||||
case Config.instance_favicon() do
|
||||
%{file: %{url: url}, metadata: metadata} ->
|
||||
%{
|
||||
src: url,
|
||||
sizes:
|
||||
case metadata do
|
||||
%{width: width} -> "#{width}x#{width}"
|
||||
_ -> "any"
|
||||
end
|
||||
}
|
||||
|
||||
_ ->
|
||||
%{
|
||||
src: "/img/icons/apple-touch-icon-152x152.png",
|
||||
sizes: "152x152"
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def favicon_url, do: Map.get(favicon(), :src)
|
||||
def favicon_sizes, do: Map.get(favicon(), :sizes)
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user