Introduce the group activity section
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
148
lib/mobilizon/activities/activities.ex
Normal file
148
lib/mobilizon/activities/activities.ex
Normal file
@@ -0,0 +1,148 @@
|
||||
defmodule Mobilizon.Activities do
|
||||
@moduledoc """
|
||||
The Activities context.
|
||||
"""
|
||||
|
||||
import Ecto.Query, warn: false
|
||||
import EctoEnum
|
||||
alias Mobilizon.Activities.Activity
|
||||
alias Mobilizon.Actors.Member
|
||||
alias Mobilizon.Storage.{Page, Repo}
|
||||
|
||||
defenum(Priority,
|
||||
very_low: 10,
|
||||
low: 20,
|
||||
medium: 30,
|
||||
high: 40,
|
||||
very_high: 50
|
||||
)
|
||||
|
||||
@activity_types ["event", "post", "discussion", "resource", "group", "member"]
|
||||
@event_activity_subjects ["event_created", "event_updated", "event_deleted", "comment_posted"]
|
||||
@post_activity_subjects ["post_created", "post_updated", "post_deleted"]
|
||||
@discussion_activity_subjects [
|
||||
"discussion_created",
|
||||
"discussion_replied",
|
||||
"discussion_renamed",
|
||||
"discussion_archived",
|
||||
"discussion_deleted"
|
||||
]
|
||||
@resource_activity_subjects [
|
||||
"resource_created",
|
||||
"resource_renamed",
|
||||
"resource_moved",
|
||||
"resource_deleted"
|
||||
]
|
||||
@member_activity_subjects [
|
||||
"member_request",
|
||||
"member_invited",
|
||||
"member_accepted_invitation",
|
||||
"member_rejected_invitation",
|
||||
"member_added",
|
||||
"member_joined",
|
||||
"member_approved",
|
||||
"member_updated",
|
||||
"member_removed",
|
||||
"member_quit"
|
||||
]
|
||||
@settings_activity_subjects ["group_created", "group_updated"]
|
||||
|
||||
@subjects @event_activity_subjects ++
|
||||
@post_activity_subjects ++
|
||||
@discussion_activity_subjects ++
|
||||
@resource_activity_subjects ++
|
||||
@member_activity_subjects ++ @settings_activity_subjects
|
||||
|
||||
@object_type ["event", "actor", "post", "discussion", "resource", "member", "group"]
|
||||
|
||||
defenum(Type, @activity_types)
|
||||
defenum(Subject, @subjects)
|
||||
defenum(ObjectType, @object_type)
|
||||
|
||||
@doc """
|
||||
Returns the list of activities.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> list_activities()
|
||||
[%Activity{}, ...]
|
||||
|
||||
"""
|
||||
def list_activities do
|
||||
Repo.all(Activity)
|
||||
end
|
||||
|
||||
@spec list_activities_for_group(
|
||||
integer() | String.t(),
|
||||
Keyword.t(),
|
||||
integer() | nil,
|
||||
integer() | nil
|
||||
) :: Page.t()
|
||||
def list_activities_for_group(
|
||||
group_id,
|
||||
actor_asking_id,
|
||||
filters \\ [],
|
||||
page \\ nil,
|
||||
limit \\ nil
|
||||
) do
|
||||
Activity
|
||||
|> where([a], a.group_id == ^group_id)
|
||||
|> join(:inner, [a], m in Member,
|
||||
on: m.parent_id == a.group_id and m.actor_id == ^actor_asking_id
|
||||
)
|
||||
|> where([a, m], a.inserted_at >= m.member_since)
|
||||
|> filter_object_type(Keyword.get(filters, :type))
|
||||
|> order_by(desc: :inserted_at)
|
||||
|> preload([:author, :group])
|
||||
|> Page.build_page(page, limit)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets a single activity.
|
||||
|
||||
Raises `Ecto.NoResultsError` if the Activity does not exist.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> get_activity!(123)
|
||||
%Activity{}
|
||||
|
||||
iex> get_activity!(456)
|
||||
** (Ecto.NoResultsError)
|
||||
|
||||
"""
|
||||
def get_activity!(id), do: Repo.get!(Activity, id)
|
||||
|
||||
@doc """
|
||||
Creates a activity.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> create_activity(%{field: value})
|
||||
{:ok, %Activity{}}
|
||||
|
||||
iex> create_activity(%{field: bad_value})
|
||||
{:error, %Ecto.Changeset{}}
|
||||
|
||||
"""
|
||||
def create_activity(attrs \\ %{}) do
|
||||
%Activity{}
|
||||
|> Activity.changeset(attrs)
|
||||
|> Repo.insert()
|
||||
end
|
||||
|
||||
def object_types, do: @object_type
|
||||
|
||||
def subjects, do: @subjects
|
||||
|
||||
def activity_types, do: @activity_types
|
||||
|
||||
@spec filter_object_type(Query.t(), atom()) :: Query.t()
|
||||
defp filter_object_type(query, :type) do
|
||||
where(query, [q], q.type == ^:type)
|
||||
end
|
||||
|
||||
defp filter_object_type(query, _) do
|
||||
query
|
||||
end
|
||||
end
|
||||
77
lib/mobilizon/activities/activity.ex
Normal file
77
lib/mobilizon/activities/activity.ex
Normal file
@@ -0,0 +1,77 @@
|
||||
defmodule Mobilizon.Activities.Activity do
|
||||
@moduledoc """
|
||||
Any activity for users
|
||||
"""
|
||||
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
alias Mobilizon.Activities.{ObjectType, Priority, Subject, Type}
|
||||
alias Mobilizon.Actors.Actor
|
||||
|
||||
@required_attrs [:type, :subject, :author_id, :group_id, :inserted_at]
|
||||
@optional_attrs [
|
||||
:priority,
|
||||
:subject_params,
|
||||
:message,
|
||||
:message_params,
|
||||
:object_type,
|
||||
:object_id
|
||||
]
|
||||
@attrs @required_attrs ++ @optional_attrs
|
||||
|
||||
@type t :: %__MODULE__{
|
||||
priority: Priority.t(),
|
||||
type: Type.t(),
|
||||
subject: Subject.t(),
|
||||
subject_params: map(),
|
||||
message: String.t(),
|
||||
message_params: map(),
|
||||
object_type: ObjectType.t(),
|
||||
object_id: String.t(),
|
||||
author: Actor.t(),
|
||||
group: Actor.t()
|
||||
}
|
||||
|
||||
schema "activities" do
|
||||
field(:priority, Priority, default: :medium)
|
||||
field(:type, Type)
|
||||
field(:subject, Subject)
|
||||
field(:subject_params, :map, default: %{})
|
||||
field(:message, :string)
|
||||
field(:message_params, :map, default: %{})
|
||||
field(:object_type, ObjectType)
|
||||
field(:object_id, :string)
|
||||
field(:inserted_at, :utc_datetime)
|
||||
belongs_to(:author, Actor)
|
||||
belongs_to(:group, Actor)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def changeset(activity, attrs) do
|
||||
activity
|
||||
|> cast(attrs, @attrs)
|
||||
|> validate_required(@required_attrs)
|
||||
|> stringify_params(:subject_params)
|
||||
|> stringify_params(:message_params)
|
||||
end
|
||||
|
||||
defp stringify_params(changeset, attr) do
|
||||
stringified_params =
|
||||
changeset
|
||||
|> get_change(attr, %{})
|
||||
|> Enum.map(fn {key, value} -> {key, stringify_struct(value)} end)
|
||||
|> Map.new()
|
||||
|
||||
put_change(changeset, attr, stringified_params)
|
||||
end
|
||||
|
||||
defp stringify_struct(%_{} = struct) do
|
||||
association_fields = struct.__struct__.__schema__(:associations)
|
||||
|
||||
struct
|
||||
|> Map.from_struct()
|
||||
|> Map.drop(association_fields ++ [:__meta__])
|
||||
end
|
||||
|
||||
defp stringify_struct(smth), do: smth
|
||||
end
|
||||
@@ -66,6 +66,7 @@ defmodule Mobilizon.Actors do
|
||||
Gets a single actor.
|
||||
"""
|
||||
@spec get_actor(integer | String.t()) :: Actor.t() | nil
|
||||
def get_actor(nil), do: nil
|
||||
def get_actor(id), do: Repo.get(Actor, id)
|
||||
|
||||
@doc """
|
||||
@@ -860,7 +861,7 @@ defmodule Mobilizon.Actors do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the list of members for a group.
|
||||
Returns a paginated list of members for a group.
|
||||
"""
|
||||
@spec list_members_for_group(Actor.t(), list(atom()), integer | nil, integer | nil) :: Page.t()
|
||||
def list_members_for_group(
|
||||
@@ -882,6 +883,13 @@ defmodule Mobilizon.Actors do
|
||||
|> Repo.all()
|
||||
end
|
||||
|
||||
@spec list_internal_actors_members_for_group(Actor.t(), list()) :: list(Actor.t())
|
||||
def list_internal_actors_members_for_group(%Actor{id: group_id, type: :Group}, roles \\ []) do
|
||||
group_id
|
||||
|> group_internal_member_actor_query(roles)
|
||||
|> Repo.all()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a paginated list of administrator members for a group.
|
||||
"""
|
||||
@@ -1512,6 +1520,16 @@ defmodule Mobilizon.Actors do
|
||||
|> select([m, _a], m)
|
||||
end
|
||||
|
||||
@spec group_internal_member_actor_query(integer(), list()) :: Ecto.Query.t()
|
||||
defp group_internal_member_actor_query(group_id, role) do
|
||||
Member
|
||||
|> where([m], m.parent_id == ^group_id)
|
||||
|> filter_member_role(role)
|
||||
|> join(:inner, [m], a in Actor, on: m.actor_id == a.id)
|
||||
|> where([_m, a], is_nil(a.domain))
|
||||
|> select([_m, a], a)
|
||||
end
|
||||
|
||||
@spec group_internal_member_query(integer()) :: Ecto.Query.t()
|
||||
defp group_internal_member_query(group_id) do
|
||||
Member
|
||||
@@ -1523,13 +1541,13 @@ defmodule Mobilizon.Actors do
|
||||
end
|
||||
|
||||
@spec filter_member_role(Ecto.Query.t(), list(atom()) | atom()) :: Ecto.Query.t()
|
||||
def filter_member_role(query, []), do: query
|
||||
defp filter_member_role(query, []), do: query
|
||||
|
||||
def filter_member_role(query, roles) when is_list(roles) do
|
||||
defp filter_member_role(query, roles) when is_list(roles) do
|
||||
where(query, [m], m.role in ^roles)
|
||||
end
|
||||
|
||||
def filter_member_role(query, role) when is_atom(role) do
|
||||
defp filter_member_role(query, role) when is_atom(role) do
|
||||
from(m in query, where: m.role == ^role)
|
||||
end
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ defmodule Mobilizon.Actors.Member do
|
||||
schema "members" do
|
||||
field(:role, MemberRole, default: :member)
|
||||
field(:url, :string)
|
||||
field(:member_since, :utc_datetime)
|
||||
|
||||
embeds_one :metadata, Metadata, on_replace: :delete do
|
||||
# TODO : Use this space to put notes when someone is invited / requested to join
|
||||
@@ -64,6 +65,7 @@ defmodule Mobilizon.Actors.Member do
|
||||
|> cast(attrs, @attrs)
|
||||
|> cast_embed(:metadata, with: &metadata_changeset/2)
|
||||
|> ensure_url()
|
||||
|> update_member_since()
|
||||
|> validate_required(@required_attrs)
|
||||
# On both parent_id and actor_id
|
||||
|> unique_constraint(:parent_id, name: :members_actor_parent_unique_index)
|
||||
@@ -98,4 +100,29 @@ defmodule Mobilizon.Actors.Member do
|
||||
|> put_change(:id, uuid)
|
||||
|> put_change(:url, "#{Endpoint.url()}/member/#{uuid}")
|
||||
end
|
||||
|
||||
@spec update_member_since(Ecto.Changeset.t()) :: Ecto.Changeset.t()
|
||||
defp update_member_since(%Ecto.Changeset{data: data} = changeset) do
|
||||
new_role = get_change(changeset, :role)
|
||||
|
||||
cond do
|
||||
new_role in [
|
||||
:member,
|
||||
:moderator,
|
||||
:administrator,
|
||||
:creator
|
||||
] ->
|
||||
put_change(
|
||||
changeset,
|
||||
:member_since,
|
||||
DateTime.truncate(data.member_since || DateTime.utc_now(), :second)
|
||||
)
|
||||
|
||||
new_role in [:invited, :not_approved, :rejected] ->
|
||||
put_change(changeset, :member_since, nil)
|
||||
|
||||
true ->
|
||||
changeset
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -115,7 +115,10 @@ defmodule Mobilizon.Resources do
|
||||
Multi.new()
|
||||
|> do_find_parent_path(Map.get(attrs, :parent_id))
|
||||
|> Multi.insert(:insert, fn %{find_parent_path: path} ->
|
||||
Resource.changeset(%Resource{}, Map.put(attrs, :path, "#{path}/#{attrs.title}"))
|
||||
Resource.changeset(
|
||||
%Resource{},
|
||||
Map.put(attrs, :path, "#{path}/#{String.replace(attrs.title, "/", "")}")
|
||||
)
|
||||
end)
|
||||
|> Repo.transaction()
|
||||
|> case do
|
||||
@@ -142,7 +145,11 @@ defmodule Mobilizon.Resources do
|
||||
|> update_children(resource, attrs)
|
||||
|> Multi.update(:update, fn %{find_parent_path: path} ->
|
||||
title = Map.get(attrs, :title, old_title)
|
||||
Resource.changeset(resource, Map.put(attrs, :path, "#{path}/#{title}"))
|
||||
|
||||
Resource.changeset(
|
||||
resource,
|
||||
Map.put(attrs, :path, "#{path}/#{String.replace(title, "/", "")}")
|
||||
)
|
||||
end)
|
||||
|> Repo.transaction()
|
||||
|> case do
|
||||
|
||||
Reference in New Issue
Block a user