FEATURE: Introduce Ignore user (#7072)
This commit is contained in:
parent
5c476f639c
commit
986cc8a0fb
|
@ -195,6 +195,16 @@ export default Ember.Component.extend(
|
|||
this._close();
|
||||
},
|
||||
|
||||
ignoreUser() {
|
||||
this.get("user").ignore();
|
||||
this._close();
|
||||
},
|
||||
|
||||
watchUser() {
|
||||
this.get("user").watch();
|
||||
this._close();
|
||||
},
|
||||
|
||||
showUser() {
|
||||
this.showUser(this.get("user"));
|
||||
this._close();
|
||||
|
|
|
@ -145,6 +145,16 @@ export default Ember.Controller.extend(CanCheckEmails, {
|
|||
|
||||
adminDelete() {
|
||||
this.get("adminTools").deleteUser(this.get("model.id"));
|
||||
},
|
||||
|
||||
ignoreUser() {
|
||||
const user = this.get("model");
|
||||
user.ignore().then(() => user.set("ignored", true));
|
||||
},
|
||||
|
||||
watchUser() {
|
||||
const user = this.get("model");
|
||||
user.watch().then(() => user.set("ignored", false));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -615,6 +615,20 @@ const User = RestModel.extend({
|
|||
}
|
||||
},
|
||||
|
||||
ignore() {
|
||||
return ajax(`${userPath(this.get("username"))}/ignore.json`, {
|
||||
type: "PUT",
|
||||
data: { ignored_user_id: this.get("id") }
|
||||
});
|
||||
},
|
||||
|
||||
watch() {
|
||||
return ajax(`${userPath(this.get("username"))}/ignore.json`, {
|
||||
type: "DELETE",
|
||||
data: { ignored_user_id: this.get("id") }
|
||||
});
|
||||
},
|
||||
|
||||
dismissBanner(bannerKey) {
|
||||
this.set("dismissed_banner_key", bannerKey);
|
||||
ajax(userPath(this.get("username") + ".json"), {
|
||||
|
|
|
@ -48,6 +48,21 @@
|
|||
icon="envelope"
|
||||
label="user.private_message"}}
|
||||
</li>
|
||||
{{#if user.can_ignore_user}}
|
||||
<li>
|
||||
{{#if user.ignored}}
|
||||
{{d-button class="btn-default"
|
||||
action=(action "watchUser")
|
||||
icon="eye"
|
||||
label="user.watch"}}
|
||||
{{else}}
|
||||
{{d-button class="btn-danger"
|
||||
action=(action "ignoreUser")
|
||||
icon="eye-slash"
|
||||
label="user.ignore"}}
|
||||
{{/if}}
|
||||
</li>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
{{#if showFilter}}
|
||||
|
|
|
@ -43,12 +43,27 @@
|
|||
<section class='controls'>
|
||||
<ul>
|
||||
{{#if model.can_send_private_message_to_user}}
|
||||
<li>
|
||||
{{d-button class="btn btn-primary compose-pm"
|
||||
action=(route-action "composePrivateMessage" model)
|
||||
icon="envelope"
|
||||
label="user.private_message"}}
|
||||
</li>
|
||||
<li>
|
||||
{{d-button class="btn-primary compose-pm"
|
||||
action=(route-action "composePrivateMessage" model)
|
||||
icon="envelope"
|
||||
label="user.private_message"}}
|
||||
</li>
|
||||
{{#if model.can_ignore_user}}
|
||||
<li>
|
||||
{{#if model.ignored}}
|
||||
{{d-button class="btn-default"
|
||||
action=(action "watchUser")
|
||||
icon="eye"
|
||||
label="user.watch"}}
|
||||
{{else}}
|
||||
{{d-button class="btn-danger"
|
||||
action=(action "ignoreUser")
|
||||
icon="eye-slash"
|
||||
label="user.ignore"}}
|
||||
{{/if}}
|
||||
</li>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{#if currentUser.staff}}
|
||||
<li><a href={{model.adminPath}} class="btn btn-default">{{d-icon "wrench"}}{{i18n 'admin.user.show_admin_profile'}}</a></li>
|
||||
|
|
|
@ -14,7 +14,7 @@ class UsersController < ApplicationController
|
|||
:pick_avatar, :destroy_user_image, :destroy, :check_emails,
|
||||
:topic_tracking_state, :preferences, :create_second_factor,
|
||||
:update_second_factor, :create_second_factor_backup, :select_avatar,
|
||||
:revoke_auth_token
|
||||
:ignore, :watch, :revoke_auth_token
|
||||
]
|
||||
|
||||
skip_before_action :check_xhr, only: [
|
||||
|
@ -995,6 +995,22 @@ class UsersController < ApplicationController
|
|||
render json: success_json
|
||||
end
|
||||
|
||||
def ignore
|
||||
raise Discourse::NotFound unless SiteSetting.ignore_user_enabled
|
||||
|
||||
::IgnoredUser.find_or_create_by!(
|
||||
user: current_user,
|
||||
ignored_user_id: params[:ignored_user_id])
|
||||
render json: success_json
|
||||
end
|
||||
|
||||
def watch
|
||||
raise Discourse::NotFound unless SiteSetting.ignore_user_enabled
|
||||
|
||||
IgnoredUser.where(user: current_user, ignored_user_id: params[:ignored_user_id]).delete_all
|
||||
render json: success_json
|
||||
end
|
||||
|
||||
def read_faq
|
||||
if user = current_user
|
||||
user.user_stat.read_faq = 1.second.ago
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
class IgnoredUser < ActiveRecord::Base
|
||||
belongs_to :user
|
||||
belongs_to :ignored_user, class_name: "User"
|
||||
end
|
||||
|
||||
# == Schema Information
|
||||
#
|
||||
# Table name: ignored_users
|
||||
#
|
||||
# id :integer not null, primary key
|
||||
# user_id :integer not null
|
||||
# ignored_user_id :integer not null
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
#
|
||||
# Indexes
|
||||
#
|
||||
# index_ignored_users_on_ignored_user_id_and_user_id (ignored_user_id,user_id) UNIQUE
|
||||
# index_ignored_users_on_user_id_and_ignored_user_id (user_id,ignored_user_id) UNIQUE
|
||||
#
|
|
@ -49,6 +49,8 @@ class UserSerializer < BasicUserSerializer
|
|||
:can_edit_email,
|
||||
:can_edit_name,
|
||||
:stats,
|
||||
:ignored,
|
||||
:can_ignore_user,
|
||||
:can_send_private_messages,
|
||||
:can_send_private_message_to_user,
|
||||
:bio_excerpt,
|
||||
|
@ -274,6 +276,14 @@ class UserSerializer < BasicUserSerializer
|
|||
UserAction.stats(object.id, scope)
|
||||
end
|
||||
|
||||
def ignored
|
||||
IgnoredUser.where(user_id: scope.user&.id, ignored_user_id: object.id).exists?
|
||||
end
|
||||
|
||||
def can_ignore_user
|
||||
SiteSetting.ignore_user_enabled
|
||||
end
|
||||
|
||||
# Needed because 'send_private_message_to_user' will always return false
|
||||
# when the current user is being serialized
|
||||
def can_send_private_messages
|
||||
|
|
|
@ -636,6 +636,8 @@ en:
|
|||
new_private_message: "New Message"
|
||||
private_message: "Message"
|
||||
private_messages: "Messages"
|
||||
ignore: "Ignore"
|
||||
watch: "Watch"
|
||||
activity_stream: "Activity"
|
||||
preferences: "Preferences"
|
||||
profile_hidden: "This user's public profile is hidden."
|
||||
|
|
|
@ -421,6 +421,8 @@ Discourse::Application.routes.draw do
|
|||
post "#{root_path}/:username/preferences/revoke-auth-token" => "users#revoke_auth_token", constraints: { username: RouteFormat.username }
|
||||
get "#{root_path}/:username/staff-info" => "users#staff_info", constraints: { username: RouteFormat.username }
|
||||
get "#{root_path}/:username/summary" => "users#summary", constraints: { username: RouteFormat.username }
|
||||
put "#{root_path}/:username/ignore" => "users#ignore", constraints: { username: RouteFormat.username }
|
||||
delete "#{root_path}/:username/ignore" => "users#watch", constraints: { username: RouteFormat.username }
|
||||
get "#{root_path}/:username/invited" => "users#invited", constraints: { username: RouteFormat.username }
|
||||
get "#{root_path}/:username/invited_count" => "users#invited_count", constraints: { username: RouteFormat.username }
|
||||
get "#{root_path}/:username/invited/:filter" => "users#invited", constraints: { username: RouteFormat.username }
|
||||
|
|
|
@ -529,6 +529,9 @@ users:
|
|||
default: false
|
||||
client: true
|
||||
log_personal_messages_views: false
|
||||
ignore_user_enabled:
|
||||
hidden: true
|
||||
default: false
|
||||
|
||||
groups:
|
||||
enable_group_directory:
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
class AddIgnoredUsersTable < ActiveRecord::Migration[5.2]
|
||||
def change
|
||||
create_table :ignored_users do |t|
|
||||
t.integer :user_id, null: false
|
||||
t.integer :ignored_user_id, null: false
|
||||
t.timestamps null: false
|
||||
end
|
||||
|
||||
add_index :ignored_users, [:user_id, :ignored_user_id], unique: true
|
||||
add_index :ignored_users, [:ignored_user_id, :user_id], unique: true
|
||||
end
|
||||
end
|
|
@ -330,13 +330,13 @@ class TopicView
|
|||
return {} if post_ids.blank?
|
||||
|
||||
sql = <<~SQL
|
||||
SELECT user_id, count(*) AS count_all
|
||||
FROM posts
|
||||
WHERE id in (:post_ids)
|
||||
AND user_id IS NOT NULL
|
||||
GROUP BY user_id
|
||||
ORDER BY count_all DESC
|
||||
LIMIT #{MAX_PARTICIPANTS}
|
||||
SELECT user_id, count(*) AS count_all
|
||||
FROM posts
|
||||
WHERE id in (:post_ids)
|
||||
AND user_id IS NOT NULL
|
||||
GROUP BY user_id
|
||||
ORDER BY count_all DESC
|
||||
LIMIT #{MAX_PARTICIPANTS}
|
||||
SQL
|
||||
|
||||
Hash[*DB.query_single(sql, post_ids: post_ids)]
|
||||
|
@ -515,22 +515,22 @@ class TopicView
|
|||
|
||||
def get_sort_order(post_number)
|
||||
sql = <<~SQL
|
||||
SELECT posts.sort_order
|
||||
FROM posts
|
||||
WHERE posts.post_number = #{post_number.to_i}
|
||||
AND posts.topic_id = #{@topic.id.to_i}
|
||||
LIMIT 1
|
||||
SELECT posts.sort_order
|
||||
FROM posts
|
||||
WHERE posts.post_number = #{post_number.to_i}
|
||||
AND posts.topic_id = #{@topic.id.to_i}
|
||||
LIMIT 1
|
||||
SQL
|
||||
|
||||
sort_order = DB.query_single(sql).first
|
||||
|
||||
if !sort_order
|
||||
sql = <<~SQL
|
||||
SELECT posts.sort_order
|
||||
FROM posts
|
||||
WHERE posts.topic_id = #{@topic.id.to_i}
|
||||
ORDER BY @(post_number - #{post_number.to_i})
|
||||
LIMIT 1
|
||||
SELECT posts.sort_order
|
||||
FROM posts
|
||||
WHERE posts.topic_id = #{@topic.id.to_i}
|
||||
ORDER BY @(post_number - #{post_number.to_i})
|
||||
LIMIT 1
|
||||
SQL
|
||||
|
||||
sort_order = DB.query_single(sql).first
|
||||
|
@ -602,6 +602,12 @@ class TopicView
|
|||
@contains_gaps = false
|
||||
@filtered_posts = unfiltered_posts
|
||||
|
||||
if SiteSetting.ignore_user_enabled
|
||||
@filtered_posts = @filtered_posts.where.not("user_id IN (?) AND id <> ?",
|
||||
IgnoredUser.where(user_id: @user.id).select(:ignored_user_id),
|
||||
first_post_id)
|
||||
end
|
||||
|
||||
# Filters
|
||||
if @filter == 'summary'
|
||||
@filtered_posts = @filtered_posts.summary(@topic.id)
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
Fabricator(:ignored_user) do
|
||||
user
|
||||
end
|
|
@ -2020,6 +2020,76 @@ describe UsersController do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#ignore' do
|
||||
it 'raises an error when not logged in' do
|
||||
put "/u/#{user.username}/ignore.json", params: { ignored_user_id: "" }
|
||||
expect(response.status).to eq(403)
|
||||
end
|
||||
|
||||
context 'while logged in' do
|
||||
let(:user) { Fabricate(:user) }
|
||||
let(:another_user) { Fabricate(:user) }
|
||||
before do
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
describe 'when SiteSetting.ignore_user_enabled is false' do
|
||||
it 'raises an error' do
|
||||
SiteSetting.ignore_user_enabled = false
|
||||
put "/u/#{user.username}/ignore.json"
|
||||
expect(response.status).to eq(404)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when SiteSetting.ignore_user_enabled is true' do
|
||||
it 'creates IgnoredUser record' do
|
||||
SiteSetting.ignore_user_enabled = true
|
||||
put "/u/#{user.username}/ignore.json", params: { ignored_user_id: another_user.id }
|
||||
expect(response.status).to eq(200)
|
||||
expect(IgnoredUser.find_by(user_id: user.id,
|
||||
ignored_user_id: another_user.id)).to be_present
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#watch' do
|
||||
it 'raises an error when not logged in' do
|
||||
delete "/u/#{user.username}/ignore.json"
|
||||
expect(response.status).to eq(403)
|
||||
end
|
||||
|
||||
context 'while logged in' do
|
||||
let(:user) { Fabricate(:user) }
|
||||
let(:another_user) { Fabricate(:user) }
|
||||
before do
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
describe 'when SiteSetting.ignore_user_enabled is false' do
|
||||
it 'raises an error' do
|
||||
SiteSetting.ignore_user_enabled = false
|
||||
delete "/u/#{user.username}/ignore.json", params: { ignored_user_id: another_user.id }
|
||||
expect(response.status).to eq(404)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when SiteSetting.ignore_user_enabled is true' do
|
||||
before do
|
||||
Fabricate(:ignored_user, user_id: user.id, ignored_user_id: another_user.id)
|
||||
end
|
||||
|
||||
it 'destroys IgnoredUser record' do
|
||||
SiteSetting.ignore_user_enabled = true
|
||||
delete "/u/#{user.username}/ignore.json", params: { ignored_user_id: another_user.id }
|
||||
expect(response.status).to eq(200)
|
||||
expect(IgnoredUser.find_by(user_id: user.id,
|
||||
ignored_user_id: another_user.id)).to be_blank
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "for user with period in username" do
|
||||
let(:user_with_period) { Fabricate(:user, username: "myname.test") }
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ RSpec.describe WebHookUserSerializer do
|
|||
|
||||
it 'should only include the required keys' do
|
||||
count = serializer.as_json.keys.count
|
||||
difference = count - 43
|
||||
difference = count - 45
|
||||
|
||||
expect(difference).to eq(0), lambda {
|
||||
message = ""
|
||||
|
|
Loading…
Reference in New Issue