Improve member adding and excluding flow
Allow to exclude a member Send emails to the member when it's excluded Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
@@ -283,6 +283,7 @@ defmodule Mobilizon.Actors.Actor do
|
||||
|> validate_required(@remote_actor_creation_required_attrs)
|
||||
|> common_changeset(attrs)
|
||||
|> unique_username_validator()
|
||||
|> validate_required(:domain)
|
||||
|> validate_length(:summary, max: 5000)
|
||||
|> validate_length(:preferred_username, max: 100)
|
||||
|
||||
|
||||
@@ -558,7 +558,7 @@ defmodule Mobilizon.Actors do
|
||||
@doc """
|
||||
Gets a single member.
|
||||
"""
|
||||
@spec get_member(integer | String.t()) :: Member.t() | nil
|
||||
@spec get_member(integer | String.t()) :: {:ok, Member.t()} | nil
|
||||
def get_member(id) do
|
||||
Member
|
||||
|> Repo.get(id)
|
||||
@@ -642,7 +642,14 @@ defmodule Mobilizon.Actors do
|
||||
with {:ok, %Member{} = member} <-
|
||||
%Member{}
|
||||
|> Member.changeset(attrs)
|
||||
|> Repo.insert() do
|
||||
|> Repo.insert(
|
||||
on_conflict: {:replace_all_except, [:id, :url, :actor_id, :parent_id]},
|
||||
conflict_target: [:actor_id, :parent_id],
|
||||
# See https://hexdocs.pm/ecto/Ecto.Repo.html#c:insert/2-upserts,
|
||||
# when doing an upsert with on_conflict, PG doesn't return whether it's an insert or upsert
|
||||
# so we need to refresh the fields
|
||||
returning: true
|
||||
) do
|
||||
{:ok, Repo.preload(member, [:actor, :parent, :invited_by])}
|
||||
end
|
||||
end
|
||||
@@ -739,6 +746,20 @@ defmodule Mobilizon.Actors do
|
||||
|> Repo.all()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns whether the member is the last administrator for a group
|
||||
"""
|
||||
@spec is_only_administrator?(integer | String.t(), integer | String.t()) :: boolean()
|
||||
def is_only_administrator?(member_id, group_id) do
|
||||
Member
|
||||
|> where(
|
||||
[m],
|
||||
m.parent_id == ^group_id and m.id != ^member_id and m.role in ^@administrator_roles
|
||||
)
|
||||
|> Repo.aggregate(:count)
|
||||
|> (&(&1 == 0)).()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets a single bot.
|
||||
Raises `Ecto.NoResultsError` if the bot does not exist.
|
||||
@@ -1240,7 +1261,7 @@ defmodule Mobilizon.Actors do
|
||||
from(
|
||||
m in Member,
|
||||
where: m.actor_id == ^actor_id,
|
||||
preload: [:parent]
|
||||
preload: [:parent, :invited_by]
|
||||
)
|
||||
end
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ defmodule Mobilizon.Discussions.Comment do
|
||||
use Ecto.Schema
|
||||
|
||||
import Ecto.Changeset
|
||||
import Mobilizon.Storage.Ecto, only: [maybe_add_published_at: 1]
|
||||
|
||||
alias Mobilizon.Actors.Actor
|
||||
alias Mobilizon.Discussions.{Comment, CommentVisibility, Discussion}
|
||||
@@ -32,7 +33,7 @@ defmodule Mobilizon.Discussions.Comment do
|
||||
|
||||
# When deleting an event we only nihilify everything
|
||||
@required_attrs [:url]
|
||||
@creation_required_attrs @required_attrs ++ [:text, :actor_id]
|
||||
@creation_required_attrs @required_attrs ++ [:text, :actor_id, :published_at]
|
||||
@optional_attrs [
|
||||
:text,
|
||||
:actor_id,
|
||||
@@ -54,6 +55,7 @@ defmodule Mobilizon.Discussions.Comment do
|
||||
field(:uuid, Ecto.UUID)
|
||||
field(:total_replies, :integer, virtual: true, default: 0)
|
||||
field(:deleted_at, :utc_datetime)
|
||||
field(:published_at, :utc_datetime)
|
||||
|
||||
belongs_to(:actor, Actor, foreign_key: :actor_id)
|
||||
belongs_to(:attributed_to, Actor, foreign_key: :attributed_to_id)
|
||||
@@ -98,7 +100,6 @@ defmodule Mobilizon.Discussions.Comment do
|
||||
|> change()
|
||||
|> put_change(:text, nil)
|
||||
|> put_change(:actor_id, nil)
|
||||
|> put_change(:discussion_id, nil)
|
||||
|> put_change(:deleted_at, DateTime.utc_now() |> DateTime.truncate(:second))
|
||||
end
|
||||
|
||||
@@ -116,6 +117,7 @@ defmodule Mobilizon.Discussions.Comment do
|
||||
defp common_changeset(%__MODULE__{} = comment, attrs) do
|
||||
comment
|
||||
|> cast(attrs, @attrs)
|
||||
|> maybe_add_published_at()
|
||||
|> maybe_generate_uuid()
|
||||
|> maybe_generate_url()
|
||||
|> put_tags(attrs)
|
||||
|
||||
@@ -294,6 +294,7 @@ defmodule Mobilizon.Discussions do
|
||||
Discussion
|
||||
|> where([c], c.actor_id == ^actor_id)
|
||||
|> preload(^@discussion_preloads)
|
||||
|> order_by(desc: :updated_at)
|
||||
|> Page.build_page(page, limit)
|
||||
end
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ defmodule Mobilizon.Resources.Resource do
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
alias Ecto.Changeset
|
||||
import Mobilizon.Storage.Ecto, only: [ensure_url: 2]
|
||||
import Mobilizon.Storage.Ecto, only: [ensure_url: 2, maybe_add_published_at: 1]
|
||||
|
||||
import EctoEnum
|
||||
defenum(TypeEnum, folder: 0, link: 1, picture: 20, pad: 30, calc: 40, visio: 50)
|
||||
@@ -22,7 +22,8 @@ defmodule Mobilizon.Resources.Resource do
|
||||
parent: __MODULE__,
|
||||
actor: Actor.t(),
|
||||
creator: Actor.t(),
|
||||
local: boolean
|
||||
local: boolean,
|
||||
published_at: DateTime.t()
|
||||
}
|
||||
|
||||
@primary_key {:id, :binary_id, autogenerate: true}
|
||||
@@ -34,6 +35,7 @@ defmodule Mobilizon.Resources.Resource do
|
||||
field(:type, TypeEnum)
|
||||
field(:path, :string)
|
||||
field(:local, :boolean, default: true)
|
||||
field(:published_at, :utc_datetime)
|
||||
|
||||
embeds_one :metadata, Metadata, on_replace: :delete do
|
||||
field(:type, :string)
|
||||
@@ -58,7 +60,7 @@ defmodule Mobilizon.Resources.Resource do
|
||||
timestamps()
|
||||
end
|
||||
|
||||
@required_attrs [:title, :url, :actor_id, :creator_id, :type, :path]
|
||||
@required_attrs [:title, :url, :actor_id, :creator_id, :type, :path, :published_at]
|
||||
@optional_attrs [:summary, :parent_id, :resource_url, :local]
|
||||
@attrs @required_attrs ++ @optional_attrs
|
||||
@metadata_attrs [
|
||||
@@ -82,6 +84,7 @@ defmodule Mobilizon.Resources.Resource do
|
||||
|> cast(attrs, @attrs)
|
||||
|> cast_embed(:metadata, with: &metadata_changeset/2)
|
||||
|> ensure_url(:resource)
|
||||
|> maybe_add_published_at()
|
||||
|> validate_resource_or_folder()
|
||||
|> validate_required(@required_attrs)
|
||||
|> unique_constraint(:url, name: :resource_url_index)
|
||||
|
||||
@@ -22,7 +22,7 @@ defmodule Mobilizon.Resources do
|
||||
def get_resources_for_group(%Actor{id: group_id}, page \\ nil, limit \\ nil) do
|
||||
Resource
|
||||
|> where(actor_id: ^group_id)
|
||||
|> order_by(desc: :updated_at)
|
||||
|> order_by(desc: :published_at)
|
||||
|> preload([r], [:actor, :creator])
|
||||
|> Page.build_page(page, limit)
|
||||
end
|
||||
|
||||
@@ -4,14 +4,15 @@ defmodule Mobilizon.Storage.Ecto do
|
||||
"""
|
||||
|
||||
import Ecto.Query, warn: false
|
||||
import Ecto.Changeset, only: [fetch_change: 2, put_change: 3]
|
||||
import Ecto.Changeset, only: [fetch_change: 2, put_change: 3, get_field: 2]
|
||||
alias Ecto.{Changeset, Query}
|
||||
alias Mobilizon.Web.Endpoint
|
||||
alias Mobilizon.Web.Router.Helpers, as: Routes
|
||||
|
||||
@doc """
|
||||
Adds sort to the query.
|
||||
"""
|
||||
@spec sort(Ecto.Query.t(), atom, atom) :: Ecto.Query.t()
|
||||
@spec sort(Query.t(), atom, atom) :: Query.t()
|
||||
def sort(query, sort, direction) do
|
||||
from(query, order_by: [{^direction, ^sort}])
|
||||
end
|
||||
@@ -22,8 +23,8 @@ defmodule Mobilizon.Storage.Ecto do
|
||||
If there's a blank URL that's because we're doing the first insert.
|
||||
Most of the time just go with the given URL.
|
||||
"""
|
||||
@spec ensure_url(Ecto.Changeset.t(), atom()) :: Ecto.Changeset.t()
|
||||
def ensure_url(%Ecto.Changeset{data: %{url: nil}} = changeset, route) do
|
||||
@spec ensure_url(Changeset.t(), atom()) :: Changeset.t()
|
||||
def ensure_url(%Changeset{data: %{url: nil}} = changeset, route) do
|
||||
case fetch_change(changeset, :url) do
|
||||
{:ok, _url} ->
|
||||
changeset
|
||||
@@ -33,10 +34,10 @@ defmodule Mobilizon.Storage.Ecto do
|
||||
end
|
||||
end
|
||||
|
||||
def ensure_url(%Ecto.Changeset{} = changeset, _route), do: changeset
|
||||
def ensure_url(%Changeset{} = changeset, _route), do: changeset
|
||||
|
||||
@spec generate_url(Ecto.Changeset.t(), atom()) :: Ecto.Changeset.t()
|
||||
defp generate_url(%Ecto.Changeset{} = changeset, route) do
|
||||
@spec generate_url(Changeset.t(), atom()) :: Changeset.t()
|
||||
defp generate_url(%Changeset{} = changeset, route) do
|
||||
uuid = Ecto.UUID.generate()
|
||||
|
||||
changeset
|
||||
@@ -46,4 +47,13 @@ defmodule Mobilizon.Storage.Ecto do
|
||||
apply(Routes, String.to_existing_atom("page_url"), [Endpoint, route, uuid])
|
||||
)
|
||||
end
|
||||
|
||||
@spec maybe_add_published_at(Changeset.t()) :: Changeset.t()
|
||||
def maybe_add_published_at(%Changeset{} = changeset) do
|
||||
if is_nil(get_field(changeset, :published_at)) do
|
||||
put_change(changeset, :published_at, DateTime.utc_now() |> DateTime.truncate(:second))
|
||||
else
|
||||
changeset
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -5,7 +5,7 @@ defmodule Mobilizon.Todos.Todo do
|
||||
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
import Mobilizon.Storage.Ecto, only: [ensure_url: 2]
|
||||
import Mobilizon.Storage.Ecto, only: [ensure_url: 2, maybe_add_published_at: 1]
|
||||
alias Mobilizon.Actors.Actor
|
||||
alias Mobilizon.Todos.TodoList
|
||||
|
||||
@@ -16,7 +16,8 @@ defmodule Mobilizon.Todos.Todo do
|
||||
todo_list: TodoList.t(),
|
||||
creator: Actor.t(),
|
||||
assigned_to: Actor.t(),
|
||||
local: boolean
|
||||
local: boolean,
|
||||
published_at: DateTime.t()
|
||||
}
|
||||
|
||||
@primary_key {:id, :binary_id, autogenerate: true}
|
||||
@@ -26,6 +27,7 @@ defmodule Mobilizon.Todos.Todo do
|
||||
field(:url, :string)
|
||||
field(:due_date, :utc_datetime)
|
||||
field(:local, :boolean, default: true)
|
||||
field(:published_at, :utc_datetime)
|
||||
belongs_to(:todo_list, TodoList, type: :binary_id)
|
||||
belongs_to(:creator, Actor)
|
||||
belongs_to(:assigned_to, Actor)
|
||||
@@ -33,7 +35,7 @@ defmodule Mobilizon.Todos.Todo do
|
||||
timestamps()
|
||||
end
|
||||
|
||||
@required_attrs [:title, :creator_id, :url, :todo_list_id]
|
||||
@required_attrs [:title, :creator_id, :url, :todo_list_id, :published_at]
|
||||
@optional_attrs [:status, :due_date, :assigned_to_id, :local]
|
||||
@attrs @required_attrs ++ @optional_attrs
|
||||
|
||||
@@ -42,6 +44,7 @@ defmodule Mobilizon.Todos.Todo do
|
||||
todo
|
||||
|> cast(attrs, @attrs)
|
||||
|> ensure_url(:todo)
|
||||
|> maybe_add_published_at()
|
||||
|> validate_required(@required_attrs)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -5,7 +5,7 @@ defmodule Mobilizon.Todos.TodoList do
|
||||
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
import Mobilizon.Storage.Ecto, only: [ensure_url: 2]
|
||||
import Mobilizon.Storage.Ecto, only: [ensure_url: 2, maybe_add_published_at: 1]
|
||||
alias Mobilizon.Actors.Actor
|
||||
alias Mobilizon.Todos.Todo
|
||||
|
||||
@@ -13,7 +13,8 @@ defmodule Mobilizon.Todos.TodoList do
|
||||
title: String.t(),
|
||||
todos: [Todo.t()],
|
||||
actor: Actor.t(),
|
||||
local: boolean
|
||||
local: boolean,
|
||||
published_at: DateTime.t()
|
||||
}
|
||||
|
||||
@primary_key {:id, :binary_id, autogenerate: true}
|
||||
@@ -21,14 +22,14 @@ defmodule Mobilizon.Todos.TodoList do
|
||||
field(:title, :string)
|
||||
field(:url, :string)
|
||||
field(:local, :boolean, default: true)
|
||||
|
||||
field(:published_at, :utc_datetime)
|
||||
belongs_to(:actor, Actor)
|
||||
has_many(:todos, Todo)
|
||||
|
||||
timestamps()
|
||||
end
|
||||
|
||||
@required_attrs [:title, :url, :actor_id]
|
||||
@required_attrs [:title, :url, :actor_id, :published_at]
|
||||
@optional_attrs [:local]
|
||||
@attrs @required_attrs ++ @optional_attrs
|
||||
|
||||
@@ -37,6 +38,7 @@ defmodule Mobilizon.Todos.TodoList do
|
||||
todo_list
|
||||
|> cast(attrs, @attrs)
|
||||
|> ensure_url(:todo_list)
|
||||
|> maybe_add_published_at()
|
||||
|> validate_required(@required_attrs)
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user