diff --git a/lib/conversation.ex b/lib/conversation.ex
new file mode 100644
index 000000000..cfb78d925
--- /dev/null
+++ b/lib/conversation.ex
@@ -0,0 +1,30 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Conversation do
+ alias Pleroma.Repo
+ alias Pleroma.Conversation.Participation
+ use Ecto.Schema
+ import Ecto.Changeset
+
+ schema "conversations" do
+ field(:ap_id, :string)
+ has_many(:participations, Participation)
+
+ timestamps()
+ end
+
+ def creation_cng(struct, params) do
+ struct
+ |> cast(params, [:ap_id])
+ |> validate_required([:ap_id])
+ |> unique_constraint(:ap_id)
+ end
+
+ def create_for_ap_id(ap_id) do
+ %__MODULE__{}
+ |> creation_cng(%{ap_id: ap_id})
+ |> Repo.insert()
+ end
+end
diff --git a/lib/conversation/participation.ex b/lib/conversation/participation.ex
new file mode 100644
index 000000000..244d37c46
--- /dev/null
+++ b/lib/conversation/participation.ex
@@ -0,0 +1,31 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Conversation.Participation do
+ use Ecto.Schema
+ alias Pleroma.User
+ alias Pleroma.Conversation
+ alias Pleroma.Repo
+ import Ecto.Changeset
+
+ schema "conversation_participations" do
+ belongs_to(:user, User, type: Pleroma.FlakeId)
+ belongs_to(:conversation, Conversation)
+ field(:read, :boolean, default: false)
+
+ timestamps()
+ end
+
+ def creation_cng(struct, params) do
+ struct
+ |> cast(params, [:user_id, :conversation_id])
+ |> validate_required([:user_id, :conversation_id])
+ end
+
+ def create_for_user_and_conversation(user, conversation) do
+ %__MODULE__{}
+ |> creation_cng(%{user_id: user.id, conversation_id: conversation.id})
+ |> Repo.insert()
+ end
+end
diff --git a/priv/repo/migrations/20190408123347_create_conversations.exs b/priv/repo/migrations/20190408123347_create_conversations.exs
new file mode 100644
index 000000000..68bf766bc
--- /dev/null
+++ b/priv/repo/migrations/20190408123347_create_conversations.exs
@@ -0,0 +1,26 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Repo.Migrations.CreateConversations do
+ use Ecto.Migration
+
+ def change do
+ create table(:conversations) do
+ add(:ap_id, :string, null: false)
+ timestamps()
+ end
+
+ create table(:conversation_participations) do
+ add(:user_id, references(:users, type: :uuid, on_delete: :delete_all))
+ add(:conversation_id, references(:conversations, on_delete: :delete_all))
+ add(:read, :boolean, default: false)
+
+ timestamps()
+ end
+
+ create index(:conversation_participations, [:user_id])
+ create index(:conversation_participations, [:conversation_id])
+ create unique_index(:conversations, [:ap_id])
+ end
+end
diff --git a/test/conversation/participation_test.exs b/test/conversation/participation_test.exs
new file mode 100644
index 000000000..8dc15a802
--- /dev/null
+++ b/test/conversation/participation_test.exs
@@ -0,0 +1,22 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Conversation.ParticipationTest do
+ use Pleroma.DataCase
+
+ import Pleroma.Factory
+
+ alias Pleroma.Conversation.Participation
+
+ test "it creates a participation for a conversation and a user" do
+ user = insert(:user)
+ conversation = insert(:conversation)
+
+ {:ok, %Participation{} = participation} =
+ Participation.create_for_user_and_conversation(user, conversation)
+
+ assert participation.user_id == user.id
+ assert participation.conversation_id == conversation.id
+ end
+end
diff --git a/test/conversation_test.exs b/test/conversation_test.exs
new file mode 100644
index 000000000..8fb55d51c
--- /dev/null
+++ b/test/conversation_test.exs
@@ -0,0 +1,12 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.ConversationTest do
+ use Pleroma.DataCase
+ alias Pleroma.Conversation
+
+ test "it creates a conversation for given ap_id" do
+ assert {:ok, %Conversation{}} = Conversation.create_for_ap_id("https://some_ap_id")
+ end
+end
diff --git a/test/support/factory.ex b/test/support/factory.ex
index ea59912cf..af38be46c 100644
--- a/test/support/factory.ex
+++ b/test/support/factory.ex
@@ -5,6 +5,12 @@
defmodule Pleroma.Factory do
use ExMachina.Ecto, repo: Pleroma.Repo
+ def conversation_factory do
+ %Pleroma.Conversation{
+ ap_id: sequence(:ap_id, &"https://some_conversation/#{&1}")
+ }
+ end
+
def user_factory do
user = %Pleroma.User{
name: sequence(:name, &"Test テスト User #{&1}"),