MastoAPI: add lists.
authoreal <eal@waifu.club>
Sun, 29 Apr 2018 13:02:46 +0000 (16:02 +0300)
committereal <eal@waifu.club>
Thu, 24 May 2018 10:18:39 +0000 (13:18 +0300)
lib/pleroma/list.ex [new file with mode: 0644]
lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
lib/pleroma/web/mastodon_api/views/list_view.ex [new file with mode: 0644]
lib/pleroma/web/router.ex
priv/repo/migrations/20180429094642_create_lists.exs [new file with mode: 0644]

diff --git a/lib/pleroma/list.ex b/lib/pleroma/list.ex
new file mode 100644 (file)
index 0000000..657d426
--- /dev/null
@@ -0,0 +1,86 @@
+defmodule Pleroma.List do
+  use Ecto.Schema
+  import Ecto.{Changeset, Query}
+  alias Pleroma.{User, Repo}
+
+  schema "lists" do
+    belongs_to(:user, Pleroma.User)
+    field(:title, :string)
+    field(:following, {:array, :string}, default: [])
+
+    timestamps()
+  end
+
+  def title_changeset(list, attrs \\ %{}) do
+    list
+    |> cast(attrs, [:title])
+    |> validate_required([:title])
+  end
+
+  def follow_changeset(list, attrs \\ %{}) do
+    list
+    |> cast(attrs, [:following])
+    |> validate_required([:following])
+  end
+
+  def for_user(user, opts) do
+    query =
+      from(
+        l in Pleroma.List,
+        where: l.user_id == ^user.id,
+        order_by: [desc: l.id],
+        limit: 50
+      )
+
+    Repo.all(query)
+  end
+
+  def get(%{id: user_id} = _user, id) do
+    query =
+      from(
+        l in Pleroma.List,
+        where: l.id == ^id,
+        where: l.user_id == ^user_id
+      )
+
+    Repo.one(query)
+  end
+
+  def get_following(%Pleroma.List{following: following} = list) do
+    q = from(
+      u in User,
+      where: u.follower_address in ^following
+    )
+    {:ok, Repo.all(q)}
+  end
+
+  def rename(%Pleroma.List{} = list, title) do
+    list
+    |> title_changeset(%{title: title})
+    |> Repo.update()
+  end
+
+  def create(title, %User{} = creator) do
+    list = %Pleroma.List{user_id: creator.id, title: title}
+    Repo.insert(list)
+  end
+
+  # TODO check that user is following followed
+  def follow(%Pleroma.List{following: following} = list, %User{} = followed) do
+    update_follows(list, %{following: Enum.uniq([followed.follower_address | following])})
+  end
+
+  def unfollow(%Pleroma.List{following: following} = list, %User{} = unfollowed) do
+    update_follows(list, %{following: List.delete(following, unfollowed.follower_address)})
+  end
+
+  def delete(%Pleroma.List{} = list) do
+    Repo.delete(list)
+  end
+
+  def update_follows(%Pleroma.List{} = list, attrs) do
+    list
+    |> follow_changeset(attrs)
+    |> Repo.update()
+  end
+end
index c84c226e8176ab7ea28634df3cfb4d5adff2dc89..ff52b2aab6cafdfe63b00cd2607100568c2c0749 100644 (file)
@@ -2,7 +2,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
   use Pleroma.Web, :controller
   alias Pleroma.{Repo, Activity, User, Notification, Stats}
   alias Pleroma.Web
-  alias Pleroma.Web.MastodonAPI.{StatusView, AccountView, MastodonView}
+  alias Pleroma.Web.MastodonAPI.{StatusView, AccountView, MastodonView, ListView}
   alias Pleroma.Web.ActivityPub.ActivityPub
   alias Pleroma.Web.{CommonAPI, OStatus}
   alias Pleroma.Web.OAuth.{Authorization, Token, App}
@@ -565,6 +565,102 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
     |> render(StatusView, "index.json", %{activities: activities, for: user, as: :activity})
   end
 
+  def get_lists(%{assigns: %{user: user}} = conn, opts) do
+    lists = Pleroma.List.for_user(user, opts)
+    res = ListView.render("lists.json", lists: lists)
+    json(conn, res)
+  end
+
+  def get_list(%{assigns: %{user: user}} = conn, %{"id" => id}) do
+    with %Pleroma.List{} = list <- Pleroma.List.get(user, id) do
+      res = ListView.render("list.json", list: list)
+      json(conn, res)
+    else
+      _e -> json(conn, "error")
+    end
+  end
+
+  def delete_list(%{assigns: %{user: user}} = conn, %{"id" => id}) do
+    with %Pleroma.List{} = list <- Pleroma.List.get(user, id),
+         {:ok, _list} <- Pleroma.List.delete(list) do
+      json(conn, %{})
+    else
+      _e ->
+        json(conn, "error")
+    end
+  end
+
+  def create_list(%{assigns: %{user: user}} = conn, %{"title" => title}) do
+    with {:ok, %Pleroma.List{} = list} <- Pleroma.List.create(title, user) do
+      res = ListView.render("list.json", list: list)
+      json(conn, res)
+    end
+  end
+
+  def add_to_list(%{assigns: %{user: user}} = conn, %{"id" => id, "account_ids" => accounts}) do
+    accounts
+    |> Enum.each(fn account_id ->
+      with %Pleroma.List{} = list <- Pleroma.List.get(user, id),
+           %User{} = followed <- Repo.get(User, account_id) do
+        ret = Pleroma.List.follow(list, followed)
+      end
+    end)
+
+    json(conn, %{})
+  end
+
+  def remove_from_list(%{assigns: %{user: user}} = conn, %{"id" => id, "account_ids" => accounts}) do
+    accounts
+    |> Enum.each(fn account_id ->
+      with %Pleroma.List{} = list <- Pleroma.List.get(user, id),
+           %User{} = followed <- Repo.get(Pleroma.User, account_id) do
+        Pleroma.List.unfollow(list, followed)
+      end
+    end)
+
+    json(conn, %{})
+  end
+
+  def list_accounts(%{assigns: %{user: user}} = conn, %{"id" => id}) do
+    with %Pleroma.List{} = list <- Pleroma.List.get(user, id),
+         {:ok, users} = Pleroma.List.get_following(list) do
+      render(conn, AccountView, "accounts.json", %{users: users, as: :user})
+    end
+  end
+
+  def rename_list(%{assigns: %{user: user}} = conn, %{"id" => id, "title" => title}) do
+    with %Pleroma.List{} = list <- Pleroma.List.get(user, id),
+         {:ok, list} <- Pleroma.List.rename(list, title) do
+      res = ListView.render("list.json", list: list)
+      json(conn, res)
+    else
+      _e ->
+        json(conn, "error")
+    end
+  end
+
+  def list_timeline(%{assigns: %{user: user}} = conn, %{"list_id" => id} = params) do
+    with %Pleroma.List{title: title, following: following} <- Pleroma.List.get(user, id) do
+      params =
+        params
+        |> Map.put("type", "Create")
+        |> Map.put("blocking_user", user)
+
+      # adding title is a hack to not make empty lists function like a public timeline
+      activities =
+        ActivityPub.fetch_activities([title | following], params)
+        |> Enum.reverse()
+
+      conn
+      |> render(StatusView, "index.json", %{activities: activities, for: user, as: :activity})
+    else
+      _e ->
+        conn
+        |> put_status(403)
+        |> json(%{error: "Error."})
+    end
+  end
+
   def index(%{assigns: %{user: user}} = conn, _params) do
     token =
       conn
diff --git a/lib/pleroma/web/mastodon_api/views/list_view.ex b/lib/pleroma/web/mastodon_api/views/list_view.ex
new file mode 100644 (file)
index 0000000..1a1b743
--- /dev/null
@@ -0,0 +1,15 @@
+defmodule Pleroma.Web.MastodonAPI.ListView do
+  use Pleroma.Web, :view
+  alias Pleroma.Web.MastodonAPI.ListView
+
+  def render("lists.json", %{lists: lists} = opts) do
+    render_many(lists, ListView, "list.json", opts)
+  end
+
+  def render("list.json", %{list: list}) do
+    %{
+      id: to_string(list.id),
+      title: list.title
+    }
+  end
+end
index cecf5527c82a1b26cac0dad0867a6d2bd8c6ee2b..c58f77817b559774949f84e4307636328e30e93e 100644 (file)
@@ -102,7 +102,6 @@ defmodule Pleroma.Web.Router do
     get("/domain_blocks", MastodonAPIController, :empty_array)
     get("/follow_requests", MastodonAPIController, :empty_array)
     get("/mutes", MastodonAPIController, :empty_array)
-    get("/lists", MastodonAPIController, :empty_array)
 
     get("/timelines/home", MastodonAPIController, :home_timeline)
 
@@ -121,6 +120,15 @@ defmodule Pleroma.Web.Router do
     get("/notifications/:id", MastodonAPIController, :get_notification)
 
     post("/media", MastodonAPIController, :upload)
+
+    get("/lists", MastodonAPIController, :get_lists)
+    get("/lists/:id", MastodonAPIController, :get_list)
+    delete("/lists/:id", MastodonAPIController, :delete_list)
+    post("/lists", MastodonAPIController, :create_list)
+    put("/lists/:id", MastodonAPIController, :rename_list)
+    get("/lists/:id/accounts", MastodonAPIController, :list_accounts)
+    post("/lists/:id/accounts", MastodonAPIController, :add_to_list)
+    delete("/lists/:id/accounts", MastodonAPIController, :remove_from_list)
   end
 
   scope "/api/web", Pleroma.Web.MastodonAPI do
@@ -138,6 +146,7 @@ defmodule Pleroma.Web.Router do
 
     get("/timelines/public", MastodonAPIController, :public_timeline)
     get("/timelines/tag/:tag", MastodonAPIController, :hashtag_timeline)
+    get("/timelines/list/:list_id", MastodonAPIController, :list_timeline)
 
     get("/statuses/:id", MastodonAPIController, :get_status)
     get("/statuses/:id/context", MastodonAPIController, :get_context)
diff --git a/priv/repo/migrations/20180429094642_create_lists.exs b/priv/repo/migrations/20180429094642_create_lists.exs
new file mode 100644 (file)
index 0000000..64c6225
--- /dev/null
@@ -0,0 +1,15 @@
+defmodule Pleroma.Repo.Migrations.CreateLists do
+  use Ecto.Migration
+
+  def change do
+    create table(:lists) do
+      add :user_id, references(:users, on_delete: :delete_all)
+      add :title, :string
+      add :following, {:array, :string}
+
+      timestamps()
+    end
+
+    create index(:lists, [:user_id])
+  end
+end