diff --git a/lib/pleroma/group.ex b/lib/pleroma/group.ex
index 732791ec2..880cd52c6 100644
--- a/lib/pleroma/group.ex
+++ b/lib/pleroma/group.ex
@@ -92,4 +92,15 @@ defmodule Pleroma.Group do
{:ok, group}
end
end
+
+ @spec get_for_object(map()) :: t() | nil
+ def get_for_object(%{"type" => "Group", "id" => id}) do
+ with %User{} = user <- User.get_cached_by_ap_id(id),
+ group <- Repo.preload(user, :group).group do
+ group
+ end
+ end
+
+ def get_for_object(%{"type" => "Create", "object" => object}), do: get_for_object(object)
+ def get_for_object(_), do: nil
end
diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index 1e5c87403..5d9016e96 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -18,6 +18,7 @@ defmodule Pleroma.User do
alias Pleroma.Emoji
alias Pleroma.FollowingRelationship
alias Pleroma.Formatter
+ alias Pleroma.Group
alias Pleroma.HTML
alias Pleroma.Keys
alias Pleroma.MFA
@@ -209,6 +210,9 @@ defmodule Pleroma.User do
on_replace: :delete
)
+ # Some `users` are actually groups. In this case, they can have a corresponding `Group`
+ has_one(:group, Group)
+
timestamps()
end
diff --git a/lib/pleroma/web/activity_pub/builder.ex b/lib/pleroma/web/activity_pub/builder.ex
index f56bfc600..08f017a51 100644
--- a/lib/pleroma/web/activity_pub/builder.ex
+++ b/lib/pleroma/web/activity_pub/builder.ex
@@ -12,6 +12,7 @@ defmodule Pleroma.Web.ActivityPub.Builder do
alias Pleroma.Emoji
alias Pleroma.Object
alias Pleroma.User
+ alias Pleroma.Web
alias Pleroma.Web.ActivityPub.Relay
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.ActivityPub.Visibility
@@ -105,6 +106,30 @@ defmodule Pleroma.Web.ActivityPub.Builder do
}, []}
end
+ def group(owner, name \\ nil, description \\ nil) do
+ id = Ecto.UUID.generate()
+ ap_id = "#{Web.base_url()}/groups/#{id}"
+
+ {:ok,
+ %{
+ "id" => ap_id,
+ "type" => "Group",
+ "name" => name,
+ "summary" => description,
+ "following" => "#{ap_id}/following",
+ "followers" => "#{ap_id}/followers",
+ "members" => "#{ap_id}/members",
+ # attributedTo? owner? admin?
+ "attributedTo" => owner.ap_id
+ }, []}
+ end
+
+ def create_group(owner, params \\ %{}) do
+ with {:ok, group, _} <- group(owner, params[:name], params[:description]) do
+ create(owner, group, [])
+ end
+ end
+
def create(actor, object, recipients) do
context =
if is_map(object) do
diff --git a/lib/pleroma/web/activity_pub/object_validator.ex b/lib/pleroma/web/activity_pub/object_validator.ex
index 297c19cc0..264ae5831 100644
--- a/lib/pleroma/web/activity_pub/object_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validator.ex
@@ -29,6 +29,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
alias Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.EventValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.FollowValidator
+ alias Pleroma.Web.ActivityPub.ObjectValidators.GroupValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.QuestionValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.UndoValidator
@@ -37,6 +38,16 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
@impl true
def validate(object, meta)
+ def validate(%{"type" => "Group"} = object, meta) do
+ with {:ok, object} <-
+ object
+ |> GroupValidator.cast_and_validate()
+ |> Ecto.Changeset.apply_action(:insert) do
+ object = stringify_keys(object)
+ {:ok, object, meta}
+ end
+ end
+
def validate(%{"type" => type} = object, meta)
when type in ~w[Accept Reject] do
with {:ok, object} <-
diff --git a/lib/pleroma/web/activity_pub/object_validators/group_validator.ex b/lib/pleroma/web/activity_pub/object_validators/group_validator.ex
new file mode 100644
index 000000000..cf7ae36c5
--- /dev/null
+++ b/lib/pleroma/web/activity_pub/object_validators/group_validator.ex
@@ -0,0 +1,40 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2021 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.ObjectValidators.GroupValidator do
+ use Ecto.Schema
+
+ alias Pleroma.EctoType.ActivityPub.ObjectValidators
+
+ import Ecto.Changeset
+
+ @primary_key false
+
+ embedded_schema do
+ field(:id, ObjectValidators.ObjectID, primary_key: true)
+ field(:type, :string)
+ end
+
+ def cast_and_validate(data) do
+ data
+ |> cast_data()
+ |> validate_data()
+ end
+
+ def cast_data(data) do
+ %__MODULE__{}
+ |> changeset(data)
+ end
+
+ def changeset(struct, data) do
+ struct
+ |> cast(data, __schema__(:fields))
+ end
+
+ def validate_data(data_cng) do
+ data_cng
+ |> validate_inclusion(:type, ["Group"])
+ |> validate_required([:id])
+ end
+end
diff --git a/lib/pleroma/web/common_api.ex b/lib/pleroma/web/common_api.ex
index b003e30c7..400fe00c9 100644
--- a/lib/pleroma/web/common_api.ex
+++ b/lib/pleroma/web/common_api.ex
@@ -30,6 +30,13 @@ defmodule Pleroma.Web.CommonAPI do
end
end
+ def create_group(user, params) do
+ with {:ok, group_data, _} <- Builder.create_group(user, params),
+ {:ok, group, _} <- Pipeline.common_pipeline(group_data, local: true) do
+ {:ok, group}
+ end
+ end
+
def post_chat_message(%User{} = user, %User{} = recipient, content, opts \\ []) do
with maybe_attachment <- opts[:media_id] && Object.get_by_id(opts[:media_id]),
:ok <- validate_chat_content_length(content, !!maybe_attachment),
diff --git a/priv/repo/migrations/20210113150220_create_groups.exs b/priv/repo/migrations/20210113150220_create_groups.exs
index 642326d51..e98be498e 100644
--- a/priv/repo/migrations/20210113150220_create_groups.exs
+++ b/priv/repo/migrations/20210113150220_create_groups.exs
@@ -6,7 +6,6 @@ defmodule Pleroma.Repo.Migrations.CreateGroups do
add(:id, :uuid, primary_key: true)
add(:user_id, references(:users, type: :uuid, on_delete: :delete_all), null: false)
add(:owner_id, references(:users, type: :uuid, on_delete: :nilify_all))
- add(:members, {:array, :string}, default: [])
add(:name, :text)
add(:description, :text)
add(:members_collection, :text)
diff --git a/test/pleroma/group_test.exs b/test/pleroma/group_test.exs
index e4c6cfd92..c10dd10f4 100644
--- a/test/pleroma/group_test.exs
+++ b/test/pleroma/group_test.exs
@@ -10,6 +10,32 @@ defmodule Pleroma.GroupTest do
import Pleroma.Factory
+ test "get_for_object/1 gets a group based on the group object or the create activity" do
+ user = insert(:user)
+
+ {:ok, group} = Group.create(%{owner_id: user.id, name: "cofe", description: "corndog"})
+ group = Repo.preload(group, :user)
+
+ group_object = %{
+ "id" => group.user.ap_id,
+ "type" => "Group"
+ }
+
+ assert group.id == Group.get_for_object(group_object).id
+
+ # Same works if wrapped in a 'create'
+ group_create = %{
+ "type" => "Create",
+ "object" => group_object
+ }
+
+ assert group.id == Group.get_for_object(group_create).id
+
+ # Nil for nonsense
+
+ assert nil == Group.get_for_object(%{"nothing" => "PS4 games"})
+ end
+
test "a user can create a group" do
user = insert(:user)
{:ok, group} = Group.create(%{owner_id: user.id, name: "cofe", description: "corndog"})
diff --git a/test/pleroma/web/activity_pub/object_validators/group_validation_test.exs b/test/pleroma/web/activity_pub/object_validators/group_validation_test.exs
new file mode 100644
index 000000000..3aab7493d
--- /dev/null
+++ b/test/pleroma/web/activity_pub/object_validators/group_validation_test.exs
@@ -0,0 +1,24 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2021 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.ObjectValidators.GroupValidationTest do
+ use Pleroma.DataCase, async: true
+
+ import Pleroma.Factory
+
+ alias Pleroma.Web.ActivityPub.Builder
+ alias Pleroma.Web.ActivityPub.ObjectValidator
+
+ describe "Group objects" do
+ test "it validates a group" do
+ user = insert(:user)
+
+ {:ok, group_data, []} = Builder.group(user, "a group", "a description")
+
+ {:ok, group, _} = ObjectValidator.validate(group_data, [])
+
+ assert group
+ end
+ end
+end
diff --git a/test/pleroma/web/common_api/group_messaging_test.exs b/test/pleroma/web/common_api/group_messaging_test.exs
new file mode 100644
index 000000000..2509428ae
--- /dev/null
+++ b/test/pleroma/web/common_api/group_messaging_test.exs
@@ -0,0 +1,25 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2021 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.CommonAPI.GroupMessagingTest do
+ use Pleroma.DataCase
+
+ import Pleroma.Factory
+
+ alias Pleroma.Group
+ alias Pleroma.Web.CommonAPI
+
+ describe "Group chats" do
+ test "local chat" do
+ user = insert(:user)
+
+ {:ok, group_creation_activity} =
+ CommonAPI.create_group(user, %{name: "cofe", description: "for cofe enthusiasts"})
+
+ group = Group.get_for_object(group_creation_activity)
+
+ assert group
+ end
+ end
+end