2
0
Fork 0
mirror of https://github.com/discourse/discourse.git synced 2025-09-07 12:02:53 +08:00

REFACTOR: Track manual locked user levels separately from groups

This commit is contained in:
Robin Ward 2017-11-23 15:55:44 -05:00
parent 87ada55e67
commit 77f90876d3
24 changed files with 226 additions and 185 deletions

View file

@ -288,7 +288,7 @@
</div> </div>
<div class="controls"> <div class="controls">
{{#if model.canLockTrustLevel}} {{#if model.canLockTrustLevel}}
{{#if model.trust_level_locked}} {{#if model.manual_locked_trust_level}}
{{d-icon "lock" title="admin.user.trust_level_locked_tip"}} {{d-icon "lock" title="admin.user.trust_level_locked_tip"}}
{{d-button action="lockTrustLevel" actionParam=false label="admin.user.unlock_trust_level"}} {{d-button action="lockTrustLevel" actionParam=false label="admin.user.unlock_trust_level"}}
{{else}} {{else}}

View file

@ -200,14 +200,14 @@ class Admin::UsersController < Admin::AdminController
guardian.ensure_can_change_trust_level!(@user) guardian.ensure_can_change_trust_level!(@user)
level = params[:level].to_i level = params[:level].to_i
if !@user.trust_level_locked && [0, 1, 2].include?(level) && Promotion.send("tl#{level + 1}_met?", @user) if @user.manual_locked_trust_level.nil?
@user.trust_level_locked = true if [0, 1, 2].include?(level) && Promotion.send("tl#{level + 1}_met?", @user)
@user.manual_locked_trust_level = level
@user.save
elsif level == 3 && Promotion.tl3_lost?(@user)
@user.manual_locked_trust_level = level
@user.save @user.save
end end
if !@user.trust_level_locked && level == 3 && Promotion.tl3_lost?(@user)
@user.trust_level_locked = true
@user.save
end end
@user.change_trust_level!(level, log_action_for: current_user) @user.change_trust_level!(level, log_action_for: current_user)
@ -225,19 +225,11 @@ class Admin::UsersController < Admin::AdminController
return render_json_error I18n.t('errors.invalid_boolean') return render_json_error I18n.t('errors.invalid_boolean')
end end
@user.trust_level_locked = new_lock == "true" @user.manual_locked_trust_level = (new_lock == "true") ? @user.trust_level : nil
@user.save @user.save
StaffActionLogger.new(current_user).log_lock_trust_level(@user) StaffActionLogger.new(current_user).log_lock_trust_level(@user)
Promotion.recalculate(@user, current_user)
unless @user.trust_level_locked
p = Promotion.new(@user)
2.times { p.review }
p.review_tl2
if @user.trust_level == 3 && Promotion.tl3_lost?(@user)
@user.change_trust_level!(2, log_action_for: current_user)
end
end
render body: nil render body: nil
end end

View file

@ -6,7 +6,11 @@ module Jobs
def execute(args) def execute(args)
# Demotions # Demotions
demoted_user_ids = [] demoted_user_ids = []
User.real.where(trust_level: TrustLevel[3], trust_level_locked: false).find_each do |u| User.real.where(
trust_level: TrustLevel[3],
manual_locked_trust_level: nil,
group_locked_trust_level: nil
).find_each do |u|
# Don't demote too soon after being promoted # Don't demote too soon after being promoted
next if u.on_tl3_grace_period? next if u.on_tl3_grace_period?
@ -17,9 +21,11 @@ module Jobs
end end
# Promotions # Promotions
User.real.where(trust_level: TrustLevel[2], User.real.where(
trust_level_locked: false) trust_level: TrustLevel[2],
.where.not(id: demoted_user_ids).find_each do |u| manual_locked_trust_level: nil,
group_locked_trust_level: nil
).where.not(id: demoted_user_ids).find_each do |u|
Promotion.new(u).review_tl2 Promotion.new(u).review_tl2
end end
end end

View file

@ -65,12 +65,15 @@ class GroupUser < ActiveRecord::Base
def grant_trust_level def grant_trust_level
return if group.grant_trust_level.nil? return if group.grant_trust_level.nil?
if (user.group_locked_trust_level || 0) < group.grant_trust_level
user.group_locked_trust_level = group.grant_trust_level
user.save
end
TrustLevelGranter.grant(group.grant_trust_level, user) TrustLevelGranter.grant(group.grant_trust_level, user)
end end
def recalculate_trust_level def recalculate_trust_level
return if group.grant_trust_level.nil? return if group.grant_trust_level.nil?
return unless SiteSetting.group_removes_trust_level?
# Find the highest level of the user's remaining groups # Find the highest level of the user's remaining groups
highest_level = GroupUser highest_level = GroupUser
@ -81,12 +84,14 @@ class GroupUser < ActiveRecord::Base
if highest_level.nil? if highest_level.nil?
# If the user no longer has a group with a trust level, # If the user no longer has a group with a trust level,
# unlock them, start at 0 and consider promotions. # unlock them, start at 0 and consider promotions.
user.trust_level_locked = false user.group_locked_trust_level = nil
user.trust_level = 0
user.save user.save
Promotion.new(user).review Promotion.recalculate(user)
else else
user.group_locked_trust_level = highest_level
user.save
user.change_trust_level!(highest_level) user.change_trust_level!(highest_level)
end end
end end

View file

@ -775,6 +775,8 @@ end
# public_version :integer default(1), not null # public_version :integer default(1), not null
# action_code :string # action_code :string
# image_url :string # image_url :string
# trolling_count :integer default(0), not null
# real_life_threat_count :integer default(0), not null
# #
# Indexes # Indexes
# #

View file

@ -1324,16 +1324,11 @@ end
# percent_rank :float default(1.0), not null # percent_rank :float default(1.0), not null
# subtype :string # subtype :string
# slug :string # slug :string
# auto_close_at :datetime
# auto_close_user_id :integer
# auto_close_started_at :datetime
# deleted_by_id :integer # deleted_by_id :integer
# participant_count :integer default(1) # participant_count :integer default(1)
# word_count :integer # word_count :integer
# excerpt :string(1000) # excerpt :string(1000)
# pinned_globally :boolean default(FALSE), not null # pinned_globally :boolean default(FALSE), not null
# auto_close_based_on_last_post :boolean default(FALSE)
# auto_close_hours :float
# pinned_until :datetime # pinned_until :datetime
# fancy_title :string(400) # fancy_title :string(400)
# highest_staff_post_number :integer default(0), not null # highest_staff_post_number :integer default(0), not null

View file

@ -63,7 +63,7 @@ class TrustLevel3Requirements
end end
def trust_level_locked def trust_level_locked
@user.trust_level_locked !@user.manual_locked_trust_level.nil?
end end
def on_grace_period def on_grace_period

View file

@ -1162,7 +1162,6 @@ end
# flag_level :integer default(0), not null # flag_level :integer default(0), not null
# ip_address :inet # ip_address :inet
# moderator :boolean default(FALSE) # moderator :boolean default(FALSE)
# silenced :boolean default(FALSE)
# title :string # title :string
# uploaded_avatar_id :integer # uploaded_avatar_id :integer
# locale :string(10) # locale :string(10)
@ -1171,12 +1170,15 @@ end
# trust_level_locked :boolean default(FALSE), not null # trust_level_locked :boolean default(FALSE), not null
# staged :boolean default(FALSE), not null # staged :boolean default(FALSE), not null
# first_seen_at :datetime # first_seen_at :datetime
# blizzard_avatar :string
# silenced_till :datetime
# group_locked_trust_level :integer
# manual_locked_trust_level :integer
# #
# Indexes # Indexes
# #
# idx_users_admin (id) # idx_users_admin (id)
# idx_users_moderator (id) # idx_users_moderator (id)
# index_users_on_email (lower((email)::text)) UNIQUE
# index_users_on_last_posted_at (last_posted_at) # index_users_on_last_posted_at (last_posted_at)
# index_users_on_last_seen_at (last_seen_at) # index_users_on_last_seen_at (last_seen_at)
# index_users_on_uploaded_avatar_id (uploaded_avatar_id) # index_users_on_uploaded_avatar_id (uploaded_avatar_id)

View file

@ -176,7 +176,7 @@ end
# theme_key :string # theme_key :string
# theme_key_seq :integer default(0), not null # theme_key_seq :integer default(0), not null
# allow_private_messages :boolean default(TRUE), not null # allow_private_messages :boolean default(TRUE), not null
# homepage_id :integer default(null) # homepage_id :integer
# #
# Indexes # Indexes
# #

View file

@ -43,9 +43,11 @@ end
# visited_at :date not null # visited_at :date not null
# posts_read :integer default(0) # posts_read :integer default(0)
# mobile :boolean default(FALSE) # mobile :boolean default(FALSE)
# time_read :integer default(0), not null
# #
# Indexes # Indexes
# #
# index_user_visits_on_user_id_and_visited_at (user_id,visited_at) UNIQUE # index_user_visits_on_user_id_and_visited_at (user_id,visited_at) UNIQUE
# index_user_visits_on_user_id_and_visited_at_and_time_read (user_id,visited_at,time_read)
# index_user_visits_on_visited_at_and_mobile (visited_at,mobile) # index_user_visits_on_visited_at_and_mobile (visited_at,mobile)
# #

View file

@ -12,7 +12,7 @@ class AdminUserListSerializer < BasicUserSerializer
:created_at_age, :created_at_age,
:username_lower, :username_lower,
:trust_level, :trust_level,
:trust_level_locked, :manual_locked_trust_level,
:flag_level, :flag_level,
:username, :username,
:title, :title,

View file

@ -39,7 +39,7 @@ class AnonymousShadowCreator
username: username, username: username,
active: true, active: true,
trust_level: 1, trust_level: 1,
trust_level_locked: true, manual_locked_trust_level: 1,
created_at: 1.day.ago # bypass new user restrictions created_at: 1.day.ago # bypass new user restrictions
) )

View file

@ -89,7 +89,7 @@ class StaffActionLogger
def log_lock_trust_level(user, opts = {}) def log_lock_trust_level(user, opts = {})
raise Discourse::InvalidParameters.new(:user) unless user && user.is_a?(User) raise Discourse::InvalidParameters.new(:user) unless user && user.is_a?(User)
UserHistory.create!(params(opts).merge(action: UserHistory.actions[user.trust_level_locked ? :lock_trust_level : :unlock_trust_level], UserHistory.create!(params(opts).merge(action: UserHistory.actions[user.manual_locked_trust_level.nil? ? :unlock_trust_level : :lock_trust_level],
target_user_id: user.id)) target_user_id: user.id))
end end

View file

@ -11,7 +11,6 @@ class TrustLevelGranter
def grant def grant
if @user.trust_level < @trust_level if @user.trust_level < @trust_level
@user.change_trust_level!(@trust_level) @user.change_trust_level!(@trust_level)
@user.trust_level_locked = true
@user.save! @user.save!
end end
end end

View file

@ -1040,7 +1040,6 @@ en:
max_topics_in_first_day: "The maximum number of topics a user is allowed to create in the 24 hour period after creating their first post" max_topics_in_first_day: "The maximum number of topics a user is allowed to create in the 24 hour period after creating their first post"
max_replies_in_first_day: "The maximum number of replies a user is allowed to create in the 24 hour period after creating their first post" max_replies_in_first_day: "The maximum number of replies a user is allowed to create in the 24 hour period after creating their first post"
group_removes_trust_level: "When a user is removed grom a group, their adjusted trust level should be reverted."
tl2_additional_likes_per_day_multiplier: "Increase limit of likes per day for tl2 (member) by multiplying with this number" tl2_additional_likes_per_day_multiplier: "Increase limit of likes per day for tl2 (member) by multiplying with this number"
tl3_additional_likes_per_day_multiplier: "Increase limit of likes per day for tl3 (regular) by multiplying with this number" tl3_additional_likes_per_day_multiplier: "Increase limit of likes per day for tl3 (regular) by multiplying with this number"

View file

@ -908,8 +908,6 @@ trust:
tl3_links_no_follow: tl3_links_no_follow:
default: false default: false
client: true client: true
group_removes_trust_level:
default: false
security: security:
force_https: force_https:

View file

@ -83,6 +83,17 @@ ColumnDropper.drop(
} }
) )
ColumnDropper.drop(
table: 'users',
after_migration: 'AddTrustLevelLocksToUsers',
columns: %w[
trust_level_locked
],
on_drop: ->() {
STDERR.puts 'Removing user trust_level_locked!'
}
)
# User for the smoke tests # User for the smoke tests
if ENV["SMOKE"] == "1" if ENV["SMOKE"] == "1"
UserEmail.seed do |ue| UserEmail.seed do |ue|

View file

@ -0,0 +1,27 @@
class AddTrustLevelLocksToUsers < ActiveRecord::Migration[5.1]
def up
add_column :users, :group_locked_trust_level, :integer, null: true
add_column :users, :manual_locked_trust_level, :integer, null: true
execute <<~SQL
UPDATE users SET manual_locked_trust_level = trust_level WHERE trust_level_locked
SQL
execute <<~SQL
UPDATE users SET group_locked_trust_level = x.tl
FROM users AS u
INNER JOIN (
SELECT gu.user_id, MAX(g.grant_trust_level) AS tl
FROM group_users AS gu
INNER JOIN groups AS g ON gu.group_id = g.id
WHERE g.grant_trust_level IS NOT NULL
GROUP BY gu.user_id
) AS x ON x.user_id = u.id
WHERE users.id = u.id
SQL
end
def down
raise ActiveRecord::IrreversibleMigration
end
end

View file

@ -11,7 +11,7 @@ class Promotion
# Returns true if the user was promoted, false otherwise. # Returns true if the user was promoted, false otherwise.
def review def review
# nil users are never promoted # nil users are never promoted
return false if @user.blank? || @user.trust_level_locked return false if @user.blank? || !@user.manual_locked_trust_level.nil?
# Promotion beyond basic requires some expensive queries, so don't do that here. # Promotion beyond basic requires some expensive queries, so don't do that here.
return false if @user.trust_level >= TrustLevel[2] return false if @user.trust_level >= TrustLevel[2]
@ -40,7 +40,7 @@ class Promotion
old_level = @user.trust_level old_level = @user.trust_level
new_level = level new_level = level
if new_level < old_level && !@user.trust_level_locked if new_level < old_level && @user.manual_locked_trust_level.nil?
next_up = new_level + 1 next_up = new_level + 1
key = "tl#{next_up}_met?" key = "tl#{next_up}_met?"
if self.class.respond_to?(key) && self.class.send(key, @user) if self.class.respond_to?(key) && self.class.send(key, @user)
@ -107,4 +107,31 @@ class Promotion
TrustLevel3Requirements.new(user).requirements_lost? TrustLevel3Requirements.new(user).requirements_lost?
end end
# Figure out what a user's trust level should be from scratch
def self.recalculate(user, performed_by = nil)
# First, use the manual locked level
unless user.manual_locked_trust_level.nil?
user.trust_level = user.manual_locked_trust_level
user.save
return
end
# Then consider the group locked level
if user.group_locked_trust_level
user.trust_level = user.group_locked_trust_level
user.save
return
end
user.update_column(:trust_level, TrustLevel[0])
p = Promotion.new(user)
p.review_tl0
p.review_tl1
p.review_tl2
if user.trust_level == 3 && Promotion.tl3_lost?(user)
user.change_trust_level!(2, log_action_for: performed_by || Discourse.system_user)
end
end
end end

View file

@ -372,12 +372,14 @@ describe Admin::UsersController do
@another_user.update_attributes(trust_level: TrustLevel[1]) @another_user.update_attributes(trust_level: TrustLevel[1])
put :trust_level, params: { put :trust_level, params: {
user_id: @another_user.id, level: TrustLevel[0] user_id: @another_user.id,
level: TrustLevel[0]
}, format: :json }, format: :json
expect(response).to be_success expect(response).to be_success
@another_user.reload @another_user.reload
expect(@another_user.trust_level_locked).to eq(true) expect(@another_user.trust_level).to eq(TrustLevel[0])
expect(@another_user.manual_locked_trust_level).to eq(TrustLevel[0])
end end
end end

View file

@ -97,7 +97,7 @@ Fabricator(:anonymous, from: :user) do
username { sequence(:username) { |i| "anonymous#{i}" } } username { sequence(:username) { |i| "anonymous#{i}" } }
email { sequence(:email) { |i| "anonymous#{i}@anonymous.com" } } email { sequence(:email) { |i| "anonymous#{i}@anonymous.com" } }
trust_level TrustLevel[1] trust_level TrustLevel[1]
trust_level_locked true manual_locked_trust_level TrustLevel[1]
before_create do |user| before_create do |user|
user.custom_fields["master_id"] = 1 user.custom_fields["master_id"] = 1

View file

@ -468,65 +468,38 @@ describe Group do
expect(u3.reload.trust_level).to eq(3) expect(u3.reload.trust_level).to eq(3)
end end
describe "group_removes_trust_level" do
context "set to false" do
before do
SiteSetting.group_removes_trust_level = false
end
it "maintains the chosen trust level" do
g0 = Fabricate(:group, grant_trust_level: 2)
g1 = Fabricate(:group, grant_trust_level: 3)
user = Fabricate(:user, trust_level: 0)
g0.add(user)
expect(user.reload.trust_level).to eq(2)
g1.add(user)
expect(user.reload.trust_level).to eq(3)
g1.remove(user)
expect(user.reload.trust_level).to eq(3)
g0.remove(user)
expect(user.reload.trust_level).to eq(3)
end
end
context "set to true" do
before do
SiteSetting.group_removes_trust_level = true
end
it "adjusts the user trust level" do it "adjusts the user trust level" do
g0 = Fabricate(:group, grant_trust_level: 2) g0 = Fabricate(:group, grant_trust_level: 2)
g1 = Fabricate(:group, grant_trust_level: 3) g1 = Fabricate(:group, grant_trust_level: 3)
g2 = Fabricate(:group) g2 = Fabricate(:group)
user = Fabricate(:user, trust_level: 0)
# Add a group without one to consider `NULL` check # Add a group without one to consider `NULL` check
g2.add(user) g2.add(user)
expect(user.group_locked_trust_level).to be_nil
user = Fabricate(:user, trust_level: 0) expect(user.manual_locked_trust_level).to be_nil
g0.add(user) g0.add(user)
expect(user.reload.trust_level).to eq(2) expect(user.reload.trust_level).to eq(2)
expect(user.trust_level_locked?).to eq(true) expect(user.group_locked_trust_level).to eq(2)
expect(user.manual_locked_trust_level).to be_nil
g1.add(user) g1.add(user)
expect(user.reload.trust_level).to eq(3) expect(user.reload.trust_level).to eq(3)
expect(user.trust_level_locked?).to eq(true) expect(user.group_locked_trust_level).to eq(3)
expect(user.manual_locked_trust_level).to be_nil
g1.remove(user) g1.remove(user)
expect(user.reload.trust_level).to eq(2) expect(user.reload.trust_level).to eq(2)
expect(user.trust_level_locked?).to eq(true) expect(user.group_locked_trust_level).to eq(2)
expect(user.manual_locked_trust_level).to be_nil
g0.remove(user) g0.remove(user)
expect(user.reload.trust_level).to eq(0) user.reload
expect(user.trust_level_locked?).to eq(false) expect(user.manual_locked_trust_level).to be_nil
end expect(user.group_locked_trust_level).to be_nil
end expect(user.trust_level).to eq(0)
end end
it 'should cook the bio' do it 'should cook the bio' do

View file

@ -1270,7 +1270,8 @@ describe User do
expect(user.title).to eq("bars and wats") expect(user.title).to eq("bars and wats")
expect(user.trust_level).to eq(1) expect(user.trust_level).to eq(1)
expect(user.trust_level_locked).to eq(true) expect(user.manual_locked_trust_level).to be_nil
expect(user.group_locked_trust_level).to eq(1)
end end
end end

View file

@ -385,12 +385,12 @@ describe StaffActionLogger do
end end
it "creates a new UserHistory record" do it "creates a new UserHistory record" do
user.trust_level_locked = true user.manual_locked_trust_level = 3
expect { logger.log_lock_trust_level(user) }.to change { UserHistory.count }.by(1) expect { logger.log_lock_trust_level(user) }.to change { UserHistory.count }.by(1)
user_history = UserHistory.last user_history = UserHistory.last
expect(user_history.action).to eq(UserHistory.actions[:lock_trust_level]) expect(user_history.action).to eq(UserHistory.actions[:lock_trust_level])
user.trust_level_locked = false user.manual_locked_trust_level = nil
expect { logger.log_lock_trust_level(user) }.to change { UserHistory.count }.by(1) expect { logger.log_lock_trust_level(user) }.to change { UserHistory.count }.by(1)
user_history = UserHistory.last user_history = UserHistory.last
expect(user_history.action).to eq(UserHistory.actions[:unlock_trust_level]) expect(user_history.action).to eq(UserHistory.actions[:unlock_trust_level])