From be1ab8b275fbc6d4a0032cfffadb66c6e9843d41 Mon Sep 17 00:00:00 2001 From: Sam <sam.saffron@gmail.com> Date: Mon, 6 May 2013 14:49:56 +1000 Subject: [PATCH] automatic group infrustructure --- app/controllers/admin/users_controller.rb | 10 +-- app/models/group.rb | 82 +++++++++++++++++++ app/models/user.rb | 34 +++++++- .../20130506020935_add_automatic_to_groups.rb | 14 ++++ lib/promotion.rb | 6 +- spec/models/group_spec.rb | 48 +++++++++++ 6 files changed, 183 insertions(+), 11 deletions(-) create mode 100644 db/migrate/20130506020935_add_automatic_to_groups.rb diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index a68cada91..6d3df8a18 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -63,30 +63,28 @@ class Admin::UsersController < Admin::AdminController def revoke_admin @admin = User.where(id: params[:user_id]).first guardian.ensure_can_revoke_admin!(@admin) - @admin.update_column(:admin, false) + @admin.revoke_admin! render nothing: true end def grant_admin @user = User.where(id: params[:user_id]).first guardian.ensure_can_grant_admin!(@user) - @user.update_column(:admin, true) + @user.grant_admin! render_serialized(@user, AdminUserSerializer) end def revoke_moderation @moderator = User.where(id: params[:user_id]).first guardian.ensure_can_revoke_moderation!(@moderator) - @moderator.moderator = false - @moderator.save + @moderator.revoke_moderation! render nothing: true end def grant_moderation @user = User.where(id: params[:user_id]).first guardian.ensure_can_grant_moderation!(@user) - @user.moderator = true - @user.save + @user.grant_moderation! render_serialized(@user, AdminUserSerializer) end diff --git a/app/models/group.rb b/app/models/group.rb index 0c5c70b36..69657be45 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -5,6 +5,88 @@ class Group < ActiveRecord::Base has_many :categories, through: :category_groups has_many :users, through: :group_users + AUTO_GROUPS = { + :admins => 1, + :moderators => 2, + :staff => 3, + :trust_level_1 => 11, + :trust_level_2 => 12, + :trust_level_3 => 13, + :trust_level_4 => 14, + :trust_level_5 => 15 + } + + def self.trust_group_ids + (10..19).to_a + end + + def self.refresh_automatic_group!(name) + + id = AUTO_GROUPS[name] + + unless group = self[name] + group = Group.new(name: name.to_s, automatic: true) + group.id = id + group.save! + end + + + real_ids = case name + when :admins + "SELECT u.id FROM users u WHERE u.admin = 't'" + when :moderators + "SELECT u.id FROM users u WHERE u.moderator = 't'" + when :staff + "SELECT u.id FROM users u WHERE u.moderator = 't' OR u.admin = 't'" + when :trust_level_1, :trust_level_2, :trust_level_3, :trust_level_4, :trust_level_5 + "SELECT u.id FROM users u WHERE u.trust_level = #{id-10}" + end + + + extra_users = group.users.where("users.id NOT IN (#{real_ids})").select('users.id') + missing_users = GroupUser.joins("RIGHT JOIN (#{real_ids}) X ON X.id = user_id AND group_id = #{group.id}") + .where("user_id IS NULL") + .select("X.id") + + group.group_users.where("user_id IN (#{extra_users.to_sql})").delete_all + + missing_users.each do |u| + group.group_users.build(user_id: u.id) + end + + group.save! + end + + def self.refresh_automatic_groups!(*args) + args.each do |group| + refresh_automatic_group!(group) + end + end + + def self.[](name) + raise ArgumentError, "unknown group" unless id = AUTO_GROUPS[name] + + Group.where(id: id).first + end + + + def self.user_trust_level_change!(user_id, trust_level) + name = "trust_level_#{trust_level}".to_sym + + GroupUser.where(group_id: trust_group_ids, user_id: user_id).delete_all + + if group = Group[name] + group_users.build(user_id: user_id) + group_users.save! + else + refresh_automatic_group!(name) + end + end + + def user_ids + users.select('users.id').map(&:id) + end + def self.builtin Enum.new(:moderators, :admins, :trust_level_1, :trust_level_2) end diff --git a/app/models/user.rb b/app/models/user.rb index e590937fd..1d70b5036 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -177,6 +177,34 @@ class User < ActiveRecord::Base where("username_lower = :user or lower(username) = :user or email = :email or lower(name) = :user", user: lower_user, email: lower_email) end + + def save_and_refresh_staff_groups! + transaction do + self.save! + Group.refresh_automatic_groups!(:admins,:moderators,:staff) + end + end + + def grant_moderation! + self.moderator = true + save_and_refresh_staff_groups! + end + + def revoke_moderation! + self.moderator = false + save_and_refresh_staff_groups! + end + + def grant_admin! + self.admin = true + save_and_refresh_staff_groups! + end + + def revoke_admin! + self.admin = false + save_and_refresh_staff_groups! + end + def enqueue_welcome_message(message_type) return unless SiteSetting.send_welcome_message? Jobs.enqueue(:send_system_message, user_id: id, message_type: message_type) @@ -439,9 +467,13 @@ class User < ActiveRecord::Base admin end - def change_trust_level(level) + def change_trust_level!(level) raise "Invalid trust level #{level}" unless TrustLevel.valid_level?(level) self.trust_level = TrustLevel.levels[level] + transaction do + self.save! + Group.user_trust_level_change!(self.id, self.trust_level) + end end def guardian diff --git a/db/migrate/20130506020935_add_automatic_to_groups.rb b/db/migrate/20130506020935_add_automatic_to_groups.rb new file mode 100644 index 000000000..e62081c7b --- /dev/null +++ b/db/migrate/20130506020935_add_automatic_to_groups.rb @@ -0,0 +1,14 @@ +class AddAutomaticToGroups < ActiveRecord::Migration + def up + add_column :groups, :automatic, :boolean, default: false, null: false + + # all numbers below 100 are reserved for automatic + execute <<SQL + ALTER SEQUENCE groups_id_seq START WITH 100 +SQL + end + + def down + remove_column :groups, :automatic + end +end diff --git a/lib/promotion.rb b/lib/promotion.rb index 39b315ff7..921cbc5c6 100644 --- a/lib/promotion.rb +++ b/lib/promotion.rb @@ -26,8 +26,7 @@ class Promotion return false if @user.posts_read_count < SiteSetting.basic_requires_read_posts return false if (@user.time_read / 60) < SiteSetting.basic_requires_time_spent_mins - @user.trust_level = TrustLevel.levels[:basic] - @user.save + @user.change_trust_level!(:basic) true end @@ -41,8 +40,7 @@ class Promotion return false if @user.likes_given < SiteSetting.regular_requires_likes_given return false if @user.topic_reply_count < SiteSetting.regular_requires_topic_reply_count - @user.trust_level = TrustLevel.levels[:regular] - @user.save + @user.change_trust_level!(:regular) end end diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb index 32135450e..253532fae 100644 --- a/spec/models/group_spec.rb +++ b/spec/models/group_spec.rb @@ -1,4 +1,52 @@ require 'spec_helper' describe Group do + + it "Can update moderator/staff/admin groups correctly" do + admin = Fabricate(:admin) + moderator = Fabricate(:moderator) + + Group.refresh_automatic_groups!(:admins, :staff, :moderators) + + Group[:admins].user_ids.should == [admin.id] + Group[:moderators].user_ids.should == [moderator.id] + Group[:staff].user_ids.sort.should == [moderator.id,admin.id].sort + + admin.admin = false + admin.save + + Group.refresh_automatic_group!(:admins) + Group[:admins].user_ids.should == [] + + moderator.revoke_moderation! + + admin.grant_admin! + Group[:admins].user_ids.should == [admin.id] + Group[:staff].user_ids.should == [admin.id] + + admin.revoke_admin! + Group[:admins].user_ids.should == [] + Group[:staff].user_ids.should == [] + + admin.grant_moderation! + Group[:moderators].user_ids.should == [admin.id] + Group[:staff].user_ids.should == [admin.id] + + admin.revoke_moderation! + Group[:admins].user_ids.should == [] + Group[:staff].user_ids.should == [] + end + + it "Correctly updates automatic trust level groups" do + user = Fabricate(:user) + user.change_trust_level!(:basic) + + Group[:trust_level_1].user_ids.should == [user.id] + + user.change_trust_level!(:regular) + + Group[:trust_level_1].user_ids.should == [] + Group[:trust_level_2].user_ids.should == [user.id] + end + end