[#394] User tags Closes #394 See merge request pleroma/pleroma!508tags/v0.9.9
@@ -2,6 +2,7 @@ defmodule Pleroma.User do | |||||
use Ecto.Schema | use Ecto.Schema | ||||
import Ecto.{Changeset, Query} | import Ecto.{Changeset, Query} | ||||
alias Ecto.Multi | |||||
alias Pleroma.{Repo, User, Object, Web, Activity, Notification} | alias Pleroma.{Repo, User, Object, Web, Activity, Notification} | ||||
alias Comeonin.Pbkdf2 | alias Comeonin.Pbkdf2 | ||||
alias Pleroma.Formatter | alias Pleroma.Formatter | ||||
@@ -23,6 +24,7 @@ defmodule Pleroma.User do | |||||
field(:local, :boolean, default: true) | field(:local, :boolean, default: true) | ||||
field(:follower_address, :string) | field(:follower_address, :string) | ||||
field(:search_distance, :float, virtual: true) | field(:search_distance, :float, virtual: true) | ||||
field(:tags, {:array, :string}, default: []) | |||||
field(:last_refreshed_at, :naive_datetime) | field(:last_refreshed_at, :naive_datetime) | ||||
has_many(:notifications, Notification) | has_many(:notifications, Notification) | ||||
embeds_one(:info, Pleroma.User.Info) | embeds_one(:info, Pleroma.User.Info) | ||||
@@ -815,4 +817,41 @@ defmodule Pleroma.User do | |||||
CommonUtils.format_input(bio, mentions, tags, "text/plain") |> Formatter.emojify(emoji) | CommonUtils.format_input(bio, mentions, tags, "text/plain") |> Formatter.emojify(emoji) | ||||
end | end | ||||
def tag(user_identifiers, tags) when is_list(user_identifiers) do | |||||
Repo.transaction(fn -> | |||||
for user_identifier <- user_identifiers, do: tag(user_identifier, tags) | |||||
end) | |||||
end | |||||
def untag(user_identifiers, tags) when is_list(user_identifiers) do | |||||
Repo.transaction(fn -> | |||||
for user_identifier <- user_identifiers, do: untag(user_identifier, tags) | |||||
end) | |||||
end | |||||
def tag(nickname, tags) when is_binary(nickname), do: tag(User.get_by_nickname(nickname), tags) | |||||
def untag(nickname, tags) when is_binary(nickname), | |||||
do: untag(User.get_by_nickname(nickname), tags) | |||||
def tag(%User{} = user, tags), | |||||
do: update_tags(user, Enum.uniq(user.tags ++ normalize_tags(tags))) | |||||
def untag(%User{} = user, tags), do: update_tags(user, user.tags -- normalize_tags(tags)) | |||||
defp update_tags(%User{} = user, new_tags) do | |||||
{:ok, updated_user} = | |||||
user | |||||
|> change(%{tags: new_tags}) | |||||
|> Repo.update() | |||||
updated_user | |||||
end | |||||
defp normalize_tags(tags) do | |||||
[tags] | |||||
|> List.flatten() | |||||
|> Enum.map(&String.downcase(&1)) | |||||
end | |||||
end | end |
@@ -3,6 +3,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do | |||||
alias Pleroma.{User, Repo} | alias Pleroma.{User, Repo} | ||||
alias Pleroma.Web.ActivityPub.Relay | alias Pleroma.Web.ActivityPub.Relay | ||||
import Pleroma.Web.ControllerHelper, only: [json_response: 3] | |||||
require Logger | require Logger | ||||
action_fallback(:errors) | action_fallback(:errors) | ||||
@@ -40,6 +42,16 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do | |||||
|> json(new_user.nickname) | |> json(new_user.nickname) | ||||
end | end | ||||
def tag_users(conn, %{"nicknames" => nicknames, "tags" => tags}) do | |||||
with {:ok, _} <- User.tag(nicknames, tags), | |||||
do: json_response(conn, :no_content, "") | |||||
end | |||||
def untag_users(conn, %{"nicknames" => nicknames, "tags" => tags}) do | |||||
with {:ok, _} <- User.untag(nicknames, tags), | |||||
do: json_response(conn, :no_content, "") | |||||
end | |||||
def right_add(conn, %{"permission_group" => permission_group, "nickname" => nickname}) | def right_add(conn, %{"permission_group" => permission_group, "nickname" => nickname}) | ||||
when permission_group in ["moderator", "admin"] do | when permission_group in ["moderator", "admin"] do | ||||
user = User.get_by_nickname(nickname) | user = User.get_by_nickname(nickname) | ||||
@@ -0,0 +1,9 @@ | |||||
defmodule Pleroma.Web.ControllerHelper do | |||||
use Pleroma.Web, :controller | |||||
def json_response(conn, status, json) do | |||||
conn | |||||
|> put_status(status) | |||||
|> json(json) | |||||
end | |||||
end |
@@ -58,6 +58,11 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do | |||||
note: "", | note: "", | ||||
privacy: user_info.default_scope, | privacy: user_info.default_scope, | ||||
sensitive: false | sensitive: false | ||||
}, | |||||
# Pleroma extension | |||||
pleroma: %{ | |||||
tags: user.tags | |||||
} | } | ||||
} | } | ||||
end | end | ||||
@@ -98,6 +98,8 @@ defmodule Pleroma.Web.Router do | |||||
pipe_through(:admin_api) | pipe_through(:admin_api) | ||||
delete("/user", AdminAPIController, :user_delete) | delete("/user", AdminAPIController, :user_delete) | ||||
post("/user", AdminAPIController, :user_create) | post("/user", AdminAPIController, :user_create) | ||||
put("/users/tag", AdminAPIController, :tag_users) | |||||
delete("/users/tag", AdminAPIController, :untag_users) | |||||
get("/permission_group/:nickname", AdminAPIController, :right_get) | get("/permission_group/:nickname", AdminAPIController, :right_get) | ||||
get("/permission_group/:nickname/:permission_group", AdminAPIController, :right_get) | get("/permission_group/:nickname/:permission_group", AdminAPIController, :right_get) | ||||
@@ -77,7 +77,12 @@ defmodule Pleroma.Web.TwitterAPI.UserView do | |||||
"locked" => user.info.locked, | "locked" => user.info.locked, | ||||
"default_scope" => user.info.default_scope, | "default_scope" => user.info.default_scope, | ||||
"no_rich_text" => user.info.no_rich_text, | "no_rich_text" => user.info.no_rich_text, | ||||
"fields" => fields | |||||
"fields" => fields, | |||||
# Pleroma extension | |||||
"pleroma" => %{ | |||||
"tags" => user.tags | |||||
} | |||||
} | } | ||||
if assigns[:token] do | if assigns[:token] do | ||||
@@ -0,0 +1,11 @@ | |||||
defmodule Pleroma.Repo.Migrations.AddTagsToUsers do | |||||
use Ecto.Migration | |||||
def change do | |||||
alter table(:users) do | |||||
add :tags, {:array, :string} | |||||
end | |||||
create index(:users, [:tags], using: :gin) | |||||
end | |||||
end |
@@ -37,6 +37,78 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do | |||||
end | end | ||||
end | end | ||||
describe "PUT /api/pleroma/admin/users/tag" do | |||||
setup do | |||||
admin = insert(:user, info: %{is_admin: true}) | |||||
user1 = insert(:user, %{tags: ["x"]}) | |||||
user2 = insert(:user, %{tags: ["y"]}) | |||||
user3 = insert(:user, %{tags: ["unchanged"]}) | |||||
conn = | |||||
build_conn() | |||||
|> assign(:user, admin) | |||||
|> put_req_header("accept", "application/json") | |||||
|> put( | |||||
"/api/pleroma/admin/users/tag?nicknames[]=#{user1.nickname}&nicknames[]=#{ | |||||
user2.nickname | |||||
}&tags[]=foo&tags[]=bar" | |||||
) | |||||
%{conn: conn, user1: user1, user2: user2, user3: user3} | |||||
end | |||||
test "it appends specified tags to users with specified nicknames", %{ | |||||
conn: conn, | |||||
user1: user1, | |||||
user2: user2 | |||||
} do | |||||
assert json_response(conn, :no_content) | |||||
assert Repo.get(User, user1.id).tags == ["x", "foo", "bar"] | |||||
assert Repo.get(User, user2.id).tags == ["y", "foo", "bar"] | |||||
end | |||||
test "it does not modify tags of not specified users", %{conn: conn, user3: user3} do | |||||
assert json_response(conn, :no_content) | |||||
assert Repo.get(User, user3.id).tags == ["unchanged"] | |||||
end | |||||
end | |||||
describe "DELETE /api/pleroma/admin/users/tag" do | |||||
setup do | |||||
admin = insert(:user, info: %{is_admin: true}) | |||||
user1 = insert(:user, %{tags: ["x"]}) | |||||
user2 = insert(:user, %{tags: ["y", "z"]}) | |||||
user3 = insert(:user, %{tags: ["unchanged"]}) | |||||
conn = | |||||
build_conn() | |||||
|> assign(:user, admin) | |||||
|> put_req_header("accept", "application/json") | |||||
|> delete( | |||||
"/api/pleroma/admin/users/tag?nicknames[]=#{user1.nickname}&nicknames[]=#{ | |||||
user2.nickname | |||||
}&tags[]=x&tags[]=z" | |||||
) | |||||
%{conn: conn, user1: user1, user2: user2, user3: user3} | |||||
end | |||||
test "it removes specified tags from users with specified nicknames", %{ | |||||
conn: conn, | |||||
user1: user1, | |||||
user2: user2 | |||||
} do | |||||
assert json_response(conn, :no_content) | |||||
assert Repo.get(User, user1.id).tags == [] | |||||
assert Repo.get(User, user2.id).tags == ["y"] | |||||
end | |||||
test "it does not modify tags of not specified users", %{conn: conn, user3: user3} do | |||||
assert json_response(conn, :no_content) | |||||
assert Repo.get(User, user3.id).tags == ["unchanged"] | |||||
end | |||||
end | |||||
describe "/api/pleroma/admin/permission_group" do | describe "/api/pleroma/admin/permission_group" do | ||||
test "GET is giving user_info" do | test "GET is giving user_info" do | ||||
admin = insert(:user, info: %{is_admin: true}) | admin = insert(:user, info: %{is_admin: true}) | ||||
@@ -54,7 +54,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do | |||||
note: "", | note: "", | ||||
privacy: "public", | privacy: "public", | ||||
sensitive: false | sensitive: false | ||||
} | |||||
}, | |||||
pleroma: %{tags: []} | |||||
} | } | ||||
assert expected == AccountView.render("account.json", %{user: user}) | assert expected == AccountView.render("account.json", %{user: user}) | ||||
@@ -91,7 +92,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do | |||||
note: "", | note: "", | ||||
privacy: "public", | privacy: "public", | ||||
sensitive: false | sensitive: false | ||||
} | |||||
}, | |||||
pleroma: %{tags: []} | |||||
} | } | ||||
assert expected == AccountView.render("account.json", %{user: user}) | assert expected == AccountView.render("account.json", %{user: user}) | ||||
@@ -96,7 +96,8 @@ defmodule Pleroma.Web.TwitterAPI.UserViewTest do | |||||
"locked" => false, | "locked" => false, | ||||
"default_scope" => "public", | "default_scope" => "public", | ||||
"no_rich_text" => false, | "no_rich_text" => false, | ||||
"fields" => [] | |||||
"fields" => [], | |||||
"pleroma" => %{"tags" => []} | |||||
} | } | ||||
assert represented == UserView.render("show.json", %{user: user}) | assert represented == UserView.render("show.json", %{user: user}) | ||||
@@ -137,7 +138,8 @@ defmodule Pleroma.Web.TwitterAPI.UserViewTest do | |||||
"locked" => false, | "locked" => false, | ||||
"default_scope" => "public", | "default_scope" => "public", | ||||
"no_rich_text" => false, | "no_rich_text" => false, | ||||
"fields" => [] | |||||
"fields" => [], | |||||
"pleroma" => %{"tags" => []} | |||||
} | } | ||||
assert represented == UserView.render("show.json", %{user: user, for: follower}) | assert represented == UserView.render("show.json", %{user: user, for: follower}) | ||||
@@ -179,7 +181,8 @@ defmodule Pleroma.Web.TwitterAPI.UserViewTest do | |||||
"locked" => false, | "locked" => false, | ||||
"default_scope" => "public", | "default_scope" => "public", | ||||
"no_rich_text" => false, | "no_rich_text" => false, | ||||
"fields" => [] | |||||
"fields" => [], | |||||
"pleroma" => %{"tags" => []} | |||||
} | } | ||||
assert represented == UserView.render("show.json", %{user: follower, for: user}) | assert represented == UserView.render("show.json", %{user: follower, for: user}) | ||||
@@ -228,7 +231,8 @@ defmodule Pleroma.Web.TwitterAPI.UserViewTest do | |||||
"locked" => false, | "locked" => false, | ||||
"default_scope" => "public", | "default_scope" => "public", | ||||
"no_rich_text" => false, | "no_rich_text" => false, | ||||
"fields" => [] | |||||
"fields" => [], | |||||
"pleroma" => %{"tags" => []} | |||||
} | } | ||||
blocker = Repo.get(User, blocker.id) | blocker = Repo.get(User, blocker.id) | ||||