added bans
This commit is contained in:
53
app/models/ban.rb
Normal file
53
app/models/ban.rb
Normal file
@@ -0,0 +1,53 @@
|
||||
class Ban < ActiveRecord::Base
|
||||
after_create :update_feedback
|
||||
belongs_to :user
|
||||
belongs_to :banner, :class_name => "User"
|
||||
attr_accessible :reason, :duration, :user_id
|
||||
validate :user_is_inferior
|
||||
|
||||
def self.is_user_banned?(user)
|
||||
exists?(["user_id = ? AND expires_at > ?", user.id, Time.now])
|
||||
end
|
||||
|
||||
def self.is_ip_banned?(ip_addr)
|
||||
exists?(["ip_addr = ? AND expires_at > ?", ip_addr, Time.now])
|
||||
end
|
||||
|
||||
def user_is_inferior
|
||||
if user
|
||||
if user.is_admin?
|
||||
errors[:base] << "You can never ban an admin."
|
||||
false
|
||||
elsif user.is_moderator? && banner.is_admin?
|
||||
true
|
||||
elsif user.is_moderator?
|
||||
errors[:base] << "Only admins can ban moderators."
|
||||
false
|
||||
elsif banner.is_admin? || banner.is_moderator?
|
||||
true
|
||||
else
|
||||
errors[:base] << "No one else can ban."
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def update_feedback
|
||||
if user
|
||||
feedback = user.feedback.build
|
||||
feedback.is_positive = false
|
||||
feedback.body = "Banned: #{reason}"
|
||||
feedback.creator_id = banner_id
|
||||
feedback.save
|
||||
end
|
||||
end
|
||||
|
||||
def duration=(dur)
|
||||
self.expires_at = dur.to_i.days.from_now
|
||||
@duration = dur
|
||||
end
|
||||
|
||||
def duration
|
||||
@duration
|
||||
end
|
||||
end
|
||||
@@ -12,6 +12,8 @@ class User < ActiveRecord::Base
|
||||
validates_confirmation_of :password
|
||||
before_save :encrypt_password
|
||||
after_save :update_cache
|
||||
before_create :normalize_level
|
||||
has_many :feedback, :class_name => "UserFeedback", :dependent => :destroy
|
||||
scope :named, lambda {|name| where(["lower(name) = ?", name])}
|
||||
|
||||
module NameMethods
|
||||
@@ -92,10 +94,29 @@ class User < ActiveRecord::Base
|
||||
end
|
||||
end
|
||||
|
||||
module LevelMethods
|
||||
def normalize_level
|
||||
if is_admin?
|
||||
self.is_moderator = true
|
||||
self.is_janitor = true
|
||||
self.is_contributor = true
|
||||
self.is_privileged = true
|
||||
elsif is_moderator?
|
||||
self.is_janitor = true
|
||||
self.is_privileged = true
|
||||
elsif is_janitor?
|
||||
self.is_privileged = true
|
||||
elsif is_contributor?
|
||||
self.is_privileged = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
include NameMethods
|
||||
include PasswordMethods
|
||||
extend AuthenticationMethods
|
||||
include FavoriteMethods
|
||||
include LevelMethods
|
||||
|
||||
def can_update?(object, foreign_key = :user_id)
|
||||
is_moderator? || is_admin? || object.__send__(foreign_key) == id
|
||||
|
||||
5
app/models/user_feedback.rb
Normal file
5
app/models/user_feedback.rb
Normal file
@@ -0,0 +1,5 @@
|
||||
class UserFeedback < ActiveRecord::Base
|
||||
set_table_name "user_feedback"
|
||||
belongs_to :user
|
||||
attr_accessible :body, :user_id, :is_positive
|
||||
end
|
||||
@@ -1,10 +1,3 @@
|
||||
# Be sure to restart your server when you modify this file.
|
||||
|
||||
# Add new inflection rules using the following format
|
||||
# (all these examples are active by default):
|
||||
# ActiveSupport::Inflector.inflections do |inflect|
|
||||
# inflect.plural /^(ox)$/i, '\1en'
|
||||
# inflect.singular /^(ox)en/i, '\1'
|
||||
# inflect.irregular 'person', 'people'
|
||||
# inflect.uncountable %w( fish sheep )
|
||||
# end
|
||||
ActiveSupport::Inflector.inflections do |inflect|
|
||||
inflect.uncountable %w( feedback )
|
||||
end
|
||||
|
||||
@@ -250,6 +250,41 @@ CREATE SEQUENCE artists_id_seq
|
||||
ALTER SEQUENCE artists_id_seq OWNED BY artists.id;
|
||||
|
||||
|
||||
--
|
||||
-- Name: bans; Type: TABLE; Schema: public; Owner: -; Tablespace:
|
||||
--
|
||||
|
||||
CREATE TABLE bans (
|
||||
id integer NOT NULL,
|
||||
user_id integer,
|
||||
ip_addr inet,
|
||||
reason text NOT NULL,
|
||||
banner_id integer NOT NULL,
|
||||
expires_at timestamp without time zone NOT NULL,
|
||||
created_at timestamp without time zone,
|
||||
updated_at timestamp without time zone
|
||||
);
|
||||
|
||||
|
||||
--
|
||||
-- Name: bans_id_seq; Type: SEQUENCE; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE SEQUENCE bans_id_seq
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MAXVALUE
|
||||
NO MINVALUE
|
||||
CACHE 1;
|
||||
|
||||
|
||||
--
|
||||
-- Name: bans_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER SEQUENCE bans_id_seq OWNED BY bans.id;
|
||||
|
||||
|
||||
--
|
||||
-- Name: comment_votes; Type: TABLE; Schema: public; Owner: -; Tablespace:
|
||||
--
|
||||
@@ -1000,6 +1035,40 @@ CREATE SEQUENCE uploads_id_seq
|
||||
ALTER SEQUENCE uploads_id_seq OWNED BY uploads.id;
|
||||
|
||||
|
||||
--
|
||||
-- Name: user_feedback; Type: TABLE; Schema: public; Owner: -; Tablespace:
|
||||
--
|
||||
|
||||
CREATE TABLE user_feedback (
|
||||
id integer NOT NULL,
|
||||
user_id integer NOT NULL,
|
||||
creator_id integer NOT NULL,
|
||||
is_positive boolean NOT NULL,
|
||||
body text NOT NULL,
|
||||
created_at timestamp without time zone,
|
||||
updated_at timestamp without time zone
|
||||
);
|
||||
|
||||
|
||||
--
|
||||
-- Name: user_feedback_id_seq; Type: SEQUENCE; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE SEQUENCE user_feedback_id_seq
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MAXVALUE
|
||||
NO MINVALUE
|
||||
CACHE 1;
|
||||
|
||||
|
||||
--
|
||||
-- Name: user_feedback_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER SEQUENCE user_feedback_id_seq OWNED BY user_feedback.id;
|
||||
|
||||
|
||||
--
|
||||
-- Name: users; Type: TABLE; Schema: public; Owner: -; Tablespace:
|
||||
--
|
||||
@@ -1155,6 +1224,13 @@ ALTER TABLE artist_versions ALTER COLUMN id SET DEFAULT nextval('artist_versions
|
||||
ALTER TABLE artists ALTER COLUMN id SET DEFAULT nextval('artists_id_seq'::regclass);
|
||||
|
||||
|
||||
--
|
||||
-- Name: id; Type: DEFAULT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE bans ALTER COLUMN id SET DEFAULT nextval('bans_id_seq'::regclass);
|
||||
|
||||
|
||||
--
|
||||
-- Name: id; Type: DEFAULT; Schema: public; Owner: -
|
||||
--
|
||||
@@ -1309,6 +1385,13 @@ ALTER TABLE unapprovals ALTER COLUMN id SET DEFAULT nextval('unapprovals_id_seq'
|
||||
ALTER TABLE uploads ALTER COLUMN id SET DEFAULT nextval('uploads_id_seq'::regclass);
|
||||
|
||||
|
||||
--
|
||||
-- Name: id; Type: DEFAULT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE user_feedback ALTER COLUMN id SET DEFAULT nextval('user_feedback_id_seq'::regclass);
|
||||
|
||||
|
||||
--
|
||||
-- Name: id; Type: DEFAULT; Schema: public; Owner: -
|
||||
--
|
||||
@@ -1370,6 +1453,14 @@ ALTER TABLE ONLY artists
|
||||
ADD CONSTRAINT artists_pkey PRIMARY KEY (id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: bans_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace:
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY bans
|
||||
ADD CONSTRAINT bans_pkey PRIMARY KEY (id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: comment_votes_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace:
|
||||
--
|
||||
@@ -1546,6 +1637,14 @@ ALTER TABLE ONLY uploads
|
||||
ADD CONSTRAINT uploads_pkey PRIMARY KEY (id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: user_feedback_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace:
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY user_feedback
|
||||
ADD CONSTRAINT user_feedback_pkey PRIMARY KEY (id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: users_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace:
|
||||
--
|
||||
@@ -1647,6 +1746,27 @@ CREATE UNIQUE INDEX index_artists_on_name ON artists USING btree (name);
|
||||
CREATE INDEX index_artists_on_other_names_index ON artists USING gin (other_names_index);
|
||||
|
||||
|
||||
--
|
||||
-- Name: index_bans_on_expires_at; Type: INDEX; Schema: public; Owner: -; Tablespace:
|
||||
--
|
||||
|
||||
CREATE INDEX index_bans_on_expires_at ON bans USING btree (expires_at);
|
||||
|
||||
|
||||
--
|
||||
-- Name: index_bans_on_ip_addr; Type: INDEX; Schema: public; Owner: -; Tablespace:
|
||||
--
|
||||
|
||||
CREATE INDEX index_bans_on_ip_addr ON bans USING btree (ip_addr);
|
||||
|
||||
|
||||
--
|
||||
-- Name: index_bans_on_user_id; Type: INDEX; Schema: public; Owner: -; Tablespace:
|
||||
--
|
||||
|
||||
CREATE INDEX index_bans_on_user_id ON bans USING btree (user_id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: index_comment_votes_on_user_id; Type: INDEX; Schema: public; Owner: -; Tablespace:
|
||||
--
|
||||
@@ -1962,6 +2082,13 @@ CREATE UNIQUE INDEX index_tags_on_name ON tags USING btree (name);
|
||||
CREATE INDEX index_unapprovals_on_post_id ON unapprovals USING btree (post_id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: index_user_feedback_on_user_id; Type: INDEX; Schema: public; Owner: -; Tablespace:
|
||||
--
|
||||
|
||||
CREATE INDEX index_user_feedback_on_user_id ON user_feedback USING btree (user_id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: index_users_on_email; Type: INDEX; Schema: public; Owner: -; Tablespace:
|
||||
--
|
||||
@@ -2086,4 +2213,8 @@ INSERT INTO schema_migrations (version) VALUES ('20100215223541');
|
||||
|
||||
INSERT INTO schema_migrations (version) VALUES ('20100215224629');
|
||||
|
||||
INSERT INTO schema_migrations (version) VALUES ('20100215224635');
|
||||
INSERT INTO schema_migrations (version) VALUES ('20100215224635');
|
||||
|
||||
INSERT INTO schema_migrations (version) VALUES ('20100215225710');
|
||||
|
||||
INSERT INTO schema_migrations (version) VALUES ('20100215230642');
|
||||
20
db/migrate/20100215225710_create_bans.rb
Normal file
20
db/migrate/20100215225710_create_bans.rb
Normal file
@@ -0,0 +1,20 @@
|
||||
class CreateBans < ActiveRecord::Migration
|
||||
def self.up
|
||||
create_table :bans do |t|
|
||||
t.column :user_id, :integer
|
||||
t.column :ip_addr, "inet"
|
||||
t.column :reason, :text, :null => false
|
||||
t.column :banner_id, :integer, :null => false
|
||||
t.column :expires_at, :datetime, :null => false
|
||||
t.timestamps
|
||||
end
|
||||
|
||||
add_index :bans, :user_id
|
||||
add_index :bans, :ip_addr
|
||||
add_index :bans, :expires_at
|
||||
end
|
||||
|
||||
def self.down
|
||||
drop_table :bans
|
||||
end
|
||||
end
|
||||
17
db/migrate/20100215230642_create_user_feedback.rb
Normal file
17
db/migrate/20100215230642_create_user_feedback.rb
Normal file
@@ -0,0 +1,17 @@
|
||||
class CreateUserFeedback < ActiveRecord::Migration
|
||||
def self.up
|
||||
create_table :user_feedback do |t|
|
||||
t.column :user_id, :integer, :null => false
|
||||
t.column :creator_id, :integer, :null => false
|
||||
t.column :is_positive, :boolean, :null => false
|
||||
t.column :body, :text, :null => false
|
||||
t.timestamps
|
||||
end
|
||||
|
||||
add_index :user_feedback, :user_id
|
||||
end
|
||||
|
||||
def self.down
|
||||
drop_table :user_feedback
|
||||
end
|
||||
end
|
||||
6
test/factories/ban.rb
Normal file
6
test/factories/ban.rb
Normal file
@@ -0,0 +1,6 @@
|
||||
Factory.define(:ban) do |f|
|
||||
f.user {|x| x.association(:user)}
|
||||
f.banner {|x| x.association(:admin_user)}
|
||||
f.reason {Faker::Lorem.words}
|
||||
f.duration 60
|
||||
end
|
||||
170
test/unit/ban_test.rb
Normal file
170
test/unit/ban_test.rb
Normal file
@@ -0,0 +1,170 @@
|
||||
require File.dirname(__FILE__) + '/../test_helper'
|
||||
|
||||
class BanTest < ActiveSupport::TestCase
|
||||
context "A ban" do
|
||||
context "created by an admin" do
|
||||
setup do
|
||||
@banner = Factory.create(:admin_user)
|
||||
end
|
||||
|
||||
teardown do
|
||||
@banner = nil
|
||||
end
|
||||
|
||||
should "not be valid against another admin" do
|
||||
user = Factory.create(:admin_user)
|
||||
ban = Factory.build(:ban, :user => user, :banner => @banner)
|
||||
ban.save
|
||||
assert(ban.errors.any?)
|
||||
end
|
||||
|
||||
should "be valid against anyone who is not an admin" do
|
||||
user = Factory.create(:moderator_user)
|
||||
ban = Factory.create(:ban, :user => user, :banner => @banner)
|
||||
assert(ban.errors.empty?)
|
||||
|
||||
user = Factory.create(:janitor_user)
|
||||
ban = Factory.create(:ban, :user => user, :banner => @banner)
|
||||
assert(ban.errors.empty?)
|
||||
|
||||
user = Factory.create(:contributor_user)
|
||||
ban = Factory.create(:ban, :user => user, :banner => @banner)
|
||||
assert(ban.errors.empty?)
|
||||
|
||||
user = Factory.create(:privileged_user)
|
||||
ban = Factory.create(:ban, :user => user, :banner => @banner)
|
||||
assert(ban.errors.empty?)
|
||||
|
||||
user = Factory.create(:user)
|
||||
ban = Factory.create(:ban, :user => user, :banner => @banner)
|
||||
assert(ban.errors.empty?)
|
||||
end
|
||||
end
|
||||
|
||||
context "created by a moderator" do
|
||||
setup do
|
||||
@banner = Factory.create(:moderator_user)
|
||||
end
|
||||
|
||||
teardown do
|
||||
@banner = nil
|
||||
end
|
||||
|
||||
should "not be valid against an admin or moderator" do
|
||||
user = Factory.create(:admin_user)
|
||||
ban = Factory.build(:ban, :user => user, :banner => @banner)
|
||||
ban.save
|
||||
assert(ban.errors.any?)
|
||||
|
||||
user = Factory.create(:moderator_user)
|
||||
ban = Factory.build(:ban, :user => user, :banner => @banner)
|
||||
ban.save
|
||||
assert(ban.errors.any?)
|
||||
end
|
||||
|
||||
should "be valid against anyone who is not an admin or a moderator" do
|
||||
user = Factory.create(:janitor_user)
|
||||
ban = Factory.create(:ban, :user => user, :banner => @banner)
|
||||
assert(ban.errors.empty?)
|
||||
|
||||
user = Factory.create(:contributor_user)
|
||||
ban = Factory.create(:ban, :user => user, :banner => @banner)
|
||||
assert(ban.errors.empty?)
|
||||
|
||||
user = Factory.create(:privileged_user)
|
||||
ban = Factory.create(:ban, :user => user, :banner => @banner)
|
||||
assert(ban.errors.empty?)
|
||||
|
||||
user = Factory.create(:user)
|
||||
ban = Factory.create(:ban, :user => user, :banner => @banner)
|
||||
assert(ban.errors.empty?)
|
||||
end
|
||||
end
|
||||
|
||||
context "created by a janitor" do
|
||||
setup do
|
||||
@banner = Factory.create(:janitor_user)
|
||||
end
|
||||
|
||||
teardown do
|
||||
@banner = nil
|
||||
end
|
||||
|
||||
should "always be invalid" do
|
||||
user = Factory.create(:admin_user)
|
||||
ban = Factory.build(:ban, :user => user, :banner => @banner)
|
||||
ban.save
|
||||
assert(ban.errors.any?)
|
||||
|
||||
user = Factory.create(:moderator_user)
|
||||
ban = Factory.build(:ban, :user => user, :banner => @banner)
|
||||
ban.save
|
||||
assert(ban.errors.any?)
|
||||
|
||||
user = Factory.create(:janitor_user)
|
||||
ban = Factory.build(:ban, :user => user, :banner => @banner)
|
||||
ban.save
|
||||
assert(ban.errors.any?)
|
||||
|
||||
user = Factory.create(:contributor_user)
|
||||
ban = Factory.build(:ban, :user => user, :banner => @banner)
|
||||
ban.save
|
||||
assert(ban.errors.any?)
|
||||
|
||||
user = Factory.create(:privileged_user)
|
||||
ban = Factory.build(:ban, :user => user, :banner => @banner)
|
||||
ban.save
|
||||
assert(ban.errors.any?)
|
||||
|
||||
user = Factory.create(:user)
|
||||
ban = Factory.build(:ban, :user => user, :banner => @banner)
|
||||
ban.save
|
||||
assert(ban.errors.any?)
|
||||
end
|
||||
end
|
||||
|
||||
should "initialize the expiration date" do
|
||||
user = Factory.create(:user)
|
||||
admin = Factory.create(:admin_user)
|
||||
ban = Factory.create(:ban, :user => user, :banner => admin)
|
||||
assert_not_nil(ban.expires_at)
|
||||
end
|
||||
|
||||
should "update the user's feedback" do
|
||||
user = Factory.create(:user)
|
||||
admin = Factory.create(:admin_user)
|
||||
assert(user.feedback.empty?)
|
||||
ban = Factory.create(:ban, :user => user, :banner => admin)
|
||||
assert(!user.feedback.empty?)
|
||||
assert(!user.feedback.last.is_positive?)
|
||||
end
|
||||
end
|
||||
|
||||
context "Searching for a ban" do
|
||||
context "by user id" do
|
||||
should "not return expired bans" do
|
||||
admin = Factory.create(:admin_user)
|
||||
|
||||
user = Factory.create(:user)
|
||||
ban = Factory.create(:ban, :user => user, :banner => admin, :duration => -1)
|
||||
assert(!Ban.is_user_banned?(user))
|
||||
|
||||
user = Factory.create(:user)
|
||||
ban = Factory.create(:ban, :user => user, :banner => admin, :duration => 1)
|
||||
assert(Ban.is_user_banned?(user))
|
||||
end
|
||||
end
|
||||
|
||||
context "by ip address" do
|
||||
should "not return expired bans" do
|
||||
admin = Factory.create(:admin_user)
|
||||
|
||||
ban = Factory.create(:ban, :ip_addr => "1.2.3.4", :banner => admin, :duration => -1)
|
||||
assert(!Ban.is_ip_banned?("1.2.3.4"))
|
||||
|
||||
ban = Factory.create(:ban, :ip_addr => "5.6.7.8", :banner => admin, :duration => 1)
|
||||
assert(Ban.is_ip_banned?("5.6.7.8"))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
8
test/unit/user_feedback_test.rb
Normal file
8
test/unit/user_feedback_test.rb
Normal file
@@ -0,0 +1,8 @@
|
||||
require 'test_helper'
|
||||
|
||||
class UserFeedbackTest < ActiveSupport::TestCase
|
||||
# Replace this with your real tests.
|
||||
test "the truth" do
|
||||
assert true
|
||||
end
|
||||
end
|
||||
@@ -14,6 +14,49 @@ class UserTest < ActiveSupport::TestCase
|
||||
assert(!User.authenticate_hash(@user.name, "xxxx"), "Authentication should not have succeeded")
|
||||
end
|
||||
|
||||
should "normalize its level" do
|
||||
user = Factory.create(:user, :is_admin => true)
|
||||
assert(user.is_moderator?)
|
||||
assert(user.is_janitor?)
|
||||
assert(user.is_contributor?)
|
||||
assert(user.is_privileged?)
|
||||
|
||||
user = Factory.create(:user, :is_moderator => true)
|
||||
assert(!user.is_admin?)
|
||||
assert(user.is_moderator?)
|
||||
assert(user.is_janitor?)
|
||||
assert(!user.is_contributor?)
|
||||
assert(user.is_privileged?)
|
||||
|
||||
user = Factory.create(:user, :is_janitor => true)
|
||||
assert(!user.is_admin?)
|
||||
assert(!user.is_moderator?)
|
||||
assert(user.is_janitor?)
|
||||
assert(!user.is_contributor?)
|
||||
assert(user.is_privileged?)
|
||||
|
||||
user = Factory.create(:user, :is_contributor => true)
|
||||
assert(!user.is_admin?)
|
||||
assert(!user.is_moderator?)
|
||||
assert(!user.is_janitor?)
|
||||
assert(user.is_contributor?)
|
||||
assert(user.is_privileged?)
|
||||
|
||||
user = Factory.create(:user, :is_privileged => true)
|
||||
assert(!user.is_admin?)
|
||||
assert(!user.is_moderator?)
|
||||
assert(!user.is_janitor?)
|
||||
assert(!user.is_contributor?)
|
||||
assert(user.is_privileged?)
|
||||
|
||||
user = Factory.create(:user)
|
||||
assert(!user.is_admin?)
|
||||
assert(!user.is_moderator?)
|
||||
assert(!user.is_janitor?)
|
||||
assert(!user.is_contributor?)
|
||||
assert(!user.is_privileged?)
|
||||
end
|
||||
|
||||
context "name" do
|
||||
should "be #{Danbooru.config.default_guest_name} given an invalid user id" do
|
||||
assert_equal(Danbooru.config.default_guest_name, User.find_name(-1))
|
||||
|
||||
Reference in New Issue
Block a user