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();
|
this._close();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
ignoreUser() {
|
||||||
|
this.get("user").ignore();
|
||||||
|
this._close();
|
||||||
|
},
|
||||||
|
|
||||||
|
watchUser() {
|
||||||
|
this.get("user").watch();
|
||||||
|
this._close();
|
||||||
|
},
|
||||||
|
|
||||||
showUser() {
|
showUser() {
|
||||||
this.showUser(this.get("user"));
|
this.showUser(this.get("user"));
|
||||||
this._close();
|
this._close();
|
||||||
|
|
|
@ -145,6 +145,16 @@ export default Ember.Controller.extend(CanCheckEmails, {
|
||||||
|
|
||||||
adminDelete() {
|
adminDelete() {
|
||||||
this.get("adminTools").deleteUser(this.get("model.id"));
|
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) {
|
dismissBanner(bannerKey) {
|
||||||
this.set("dismissed_banner_key", bannerKey);
|
this.set("dismissed_banner_key", bannerKey);
|
||||||
ajax(userPath(this.get("username") + ".json"), {
|
ajax(userPath(this.get("username") + ".json"), {
|
||||||
|
|
|
@ -48,6 +48,21 @@
|
||||||
icon="envelope"
|
icon="envelope"
|
||||||
label="user.private_message"}}
|
label="user.private_message"}}
|
||||||
</li>
|
</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}}
|
||||||
|
|
||||||
{{#if showFilter}}
|
{{#if showFilter}}
|
||||||
|
|
|
@ -43,12 +43,27 @@
|
||||||
<section class='controls'>
|
<section class='controls'>
|
||||||
<ul>
|
<ul>
|
||||||
{{#if model.can_send_private_message_to_user}}
|
{{#if model.can_send_private_message_to_user}}
|
||||||
<li>
|
<li>
|
||||||
{{d-button class="btn btn-primary compose-pm"
|
{{d-button class="btn-primary compose-pm"
|
||||||
action=(route-action "composePrivateMessage" model)
|
action=(route-action "composePrivateMessage" model)
|
||||||
icon="envelope"
|
icon="envelope"
|
||||||
label="user.private_message"}}
|
label="user.private_message"}}
|
||||||
</li>
|
</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}}
|
||||||
{{#if currentUser.staff}}
|
{{#if currentUser.staff}}
|
||||||
<li><a href={{model.adminPath}} class="btn btn-default">{{d-icon "wrench"}}{{i18n 'admin.user.show_admin_profile'}}</a></li>
|
<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,
|
:pick_avatar, :destroy_user_image, :destroy, :check_emails,
|
||||||
:topic_tracking_state, :preferences, :create_second_factor,
|
:topic_tracking_state, :preferences, :create_second_factor,
|
||||||
:update_second_factor, :create_second_factor_backup, :select_avatar,
|
:update_second_factor, :create_second_factor_backup, :select_avatar,
|
||||||
:revoke_auth_token
|
:ignore, :watch, :revoke_auth_token
|
||||||
]
|
]
|
||||||
|
|
||||||
skip_before_action :check_xhr, only: [
|
skip_before_action :check_xhr, only: [
|
||||||
|
@ -995,6 +995,22 @@ class UsersController < ApplicationController
|
||||||
render json: success_json
|
render json: success_json
|
||||||
end
|
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
|
def read_faq
|
||||||
if user = current_user
|
if user = current_user
|
||||||
user.user_stat.read_faq = 1.second.ago
|
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_email,
|
||||||
:can_edit_name,
|
:can_edit_name,
|
||||||
:stats,
|
:stats,
|
||||||
|
:ignored,
|
||||||
|
:can_ignore_user,
|
||||||
:can_send_private_messages,
|
:can_send_private_messages,
|
||||||
:can_send_private_message_to_user,
|
:can_send_private_message_to_user,
|
||||||
:bio_excerpt,
|
:bio_excerpt,
|
||||||
|
@ -274,6 +276,14 @@ class UserSerializer < BasicUserSerializer
|
||||||
UserAction.stats(object.id, scope)
|
UserAction.stats(object.id, scope)
|
||||||
end
|
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
|
# Needed because 'send_private_message_to_user' will always return false
|
||||||
# when the current user is being serialized
|
# when the current user is being serialized
|
||||||
def can_send_private_messages
|
def can_send_private_messages
|
||||||
|
|
|
@ -636,6 +636,8 @@ en:
|
||||||
new_private_message: "New Message"
|
new_private_message: "New Message"
|
||||||
private_message: "Message"
|
private_message: "Message"
|
||||||
private_messages: "Messages"
|
private_messages: "Messages"
|
||||||
|
ignore: "Ignore"
|
||||||
|
watch: "Watch"
|
||||||
activity_stream: "Activity"
|
activity_stream: "Activity"
|
||||||
preferences: "Preferences"
|
preferences: "Preferences"
|
||||||
profile_hidden: "This user's public profile is hidden."
|
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 }
|
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/staff-info" => "users#staff_info", constraints: { username: RouteFormat.username }
|
||||||
get "#{root_path}/:username/summary" => "users#summary", 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" => "users#invited", constraints: { username: RouteFormat.username }
|
||||||
get "#{root_path}/:username/invited_count" => "users#invited_count", 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 }
|
get "#{root_path}/:username/invited/:filter" => "users#invited", constraints: { username: RouteFormat.username }
|
||||||
|
|
|
@ -529,6 +529,9 @@ users:
|
||||||
default: false
|
default: false
|
||||||
client: true
|
client: true
|
||||||
log_personal_messages_views: false
|
log_personal_messages_views: false
|
||||||
|
ignore_user_enabled:
|
||||||
|
hidden: true
|
||||||
|
default: false
|
||||||
|
|
||||||
groups:
|
groups:
|
||||||
enable_group_directory:
|
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?
|
return {} if post_ids.blank?
|
||||||
|
|
||||||
sql = <<~SQL
|
sql = <<~SQL
|
||||||
SELECT user_id, count(*) AS count_all
|
SELECT user_id, count(*) AS count_all
|
||||||
FROM posts
|
FROM posts
|
||||||
WHERE id in (:post_ids)
|
WHERE id in (:post_ids)
|
||||||
AND user_id IS NOT NULL
|
AND user_id IS NOT NULL
|
||||||
GROUP BY user_id
|
GROUP BY user_id
|
||||||
ORDER BY count_all DESC
|
ORDER BY count_all DESC
|
||||||
LIMIT #{MAX_PARTICIPANTS}
|
LIMIT #{MAX_PARTICIPANTS}
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
Hash[*DB.query_single(sql, post_ids: post_ids)]
|
Hash[*DB.query_single(sql, post_ids: post_ids)]
|
||||||
|
@ -515,22 +515,22 @@ class TopicView
|
||||||
|
|
||||||
def get_sort_order(post_number)
|
def get_sort_order(post_number)
|
||||||
sql = <<~SQL
|
sql = <<~SQL
|
||||||
SELECT posts.sort_order
|
SELECT posts.sort_order
|
||||||
FROM posts
|
FROM posts
|
||||||
WHERE posts.post_number = #{post_number.to_i}
|
WHERE posts.post_number = #{post_number.to_i}
|
||||||
AND posts.topic_id = #{@topic.id.to_i}
|
AND posts.topic_id = #{@topic.id.to_i}
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
sort_order = DB.query_single(sql).first
|
sort_order = DB.query_single(sql).first
|
||||||
|
|
||||||
if !sort_order
|
if !sort_order
|
||||||
sql = <<~SQL
|
sql = <<~SQL
|
||||||
SELECT posts.sort_order
|
SELECT posts.sort_order
|
||||||
FROM posts
|
FROM posts
|
||||||
WHERE posts.topic_id = #{@topic.id.to_i}
|
WHERE posts.topic_id = #{@topic.id.to_i}
|
||||||
ORDER BY @(post_number - #{post_number.to_i})
|
ORDER BY @(post_number - #{post_number.to_i})
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
sort_order = DB.query_single(sql).first
|
sort_order = DB.query_single(sql).first
|
||||||
|
@ -602,6 +602,12 @@ class TopicView
|
||||||
@contains_gaps = false
|
@contains_gaps = false
|
||||||
@filtered_posts = unfiltered_posts
|
@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
|
# Filters
|
||||||
if @filter == 'summary'
|
if @filter == 'summary'
|
||||||
@filtered_posts = @filtered_posts.summary(@topic.id)
|
@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
|
||||||
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
|
describe "for user with period in username" do
|
||||||
let(:user_with_period) { Fabricate(:user, username: "myname.test") }
|
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
|
it 'should only include the required keys' do
|
||||||
count = serializer.as_json.keys.count
|
count = serializer.as_json.keys.count
|
||||||
difference = count - 43
|
difference = count - 45
|
||||||
|
|
||||||
expect(difference).to eq(0), lambda {
|
expect(difference).to eq(0), lambda {
|
||||||
message = ""
|
message = ""
|
||||||
|
|
Loading…
Reference in New Issue