diff --git a/app/logical/daily_maintenance.rb b/app/logical/daily_maintenance.rb index fc7802d5b..067c6cbf8 100644 --- a/app/logical/daily_maintenance.rb +++ b/app/logical/daily_maintenance.rb @@ -6,8 +6,8 @@ class DailyMaintenance Upload.delete_all(['created_at < ?', 1.day.ago]) ModAction.delete_all(['created_at < ?', 3.days.ago]) Delayed::Job.delete_all(['created_at < ?', 7.days.ago]) - PostVote.delete_all(['created_at < ?', 1.month.ago]) - CommentVote.delete_all(['created_at < ?', 1.month.ago]) + PostVote.prune! + CommentVote.prune! TagSubscription.process_all ApiCacheGenerator.new.generate_tag_cache PostDisapproval.prune! @@ -15,5 +15,7 @@ class DailyMaintenance TagAlias.update_cached_post_counts_for_all PostDisapproval.dmail_messages! Tag.clean_up_negative_post_counts! + SuperVoter.prune! + SuperVoter.init! end end diff --git a/app/logical/reports/user_similarity.rb b/app/logical/reports/user_similarity.rb index edce43c18..369740fe8 100644 --- a/app/logical/reports/user_similarity.rb +++ b/app/logical/reports/user_similarity.rb @@ -1,5 +1,7 @@ module Reports class UserSimilarity + NOT_READY_STRING = "not ready" + attr_reader :user_id def initialize(user_id) @@ -10,6 +12,16 @@ module Reports User.find(user_id) end + def prime + 10.times do + if fetch_similar_user_ids == NOT_READY_STRING + sleep(60) + else + break + end + end + end + def fetch_similar_user_ids return NotImplementedError unless Danbooru.config.report_server diff --git a/app/models/comment_vote.rb b/app/models/comment_vote.rb index b9ca46a68..28fc3e43c 100644 --- a/app/models/comment_vote.rb +++ b/app/models/comment_vote.rb @@ -12,7 +12,7 @@ class CommentVote < ActiveRecord::Base attr_accessible :comment_id, :user_id, :score def self.prune! - destroy_all("created_at < ?", 14.days.ago) + where("created_at < ?", 14.days.ago).delete_all end def self.search(params) diff --git a/app/models/post.rb b/app/models/post.rb index c6b6a0ca8..970b1080a 100644 --- a/app/models/post.rb +++ b/app/models/post.rb @@ -952,14 +952,22 @@ class Post < ActiveRecord::Base !PostVote.exists?(:user_id => user.id, :post_id => id) end + def vote_magnitude + if CurrentUser.is_super_voter? + SuperVoter::MAGNITUDE + else + 1 + end + end + def vote!(score) if can_be_voted_by?(CurrentUser.user) if score == "up" - Post.where(:id => id).update_all("score = score + 1, up_score = up_score + 1") - self.score += 1 + Post.where(:id => id).update_all("score = score + #{vote_magnitude}, up_score = up_score + #{vote_magnitude}") + self.score += vote_magnitude elsif score == "down" - Post.where(:id => id).update_all("score = score - 1, down_score = down_score - 1") - self.score -= 1 + Post.where(:id => id).update_all("score = score - #{vote_magnitude}, down_score = down_score - #{vote_magnitude}") + self.score -= vote_magnitude end votes.create(:score => score) @@ -975,11 +983,11 @@ class Post < ActiveRecord::Base vote = votes.where("user_id = ?", CurrentUser.user.id).first if vote.score == 1 - Post.where(:id => id).update_all("score = score - 1, up_score = up_score - 1") - self.score -= 1 + Post.where(:id => id).update_all("score = score - #{vote_magnitude}, up_score = up_score - #{vote_magnitude}") + self.score -= vote_magnitude else - Post.where(:id => id).update_all("score = score + 1, down_score = down_score + 1") - self.score += 1 + Post.where(:id => id).update_all("score = score + #{vote_magnitude}, down_score = down_score + #{vote_magnitude}") + self.score += vote_magnitude end vote.destroy diff --git a/app/models/post_vote.rb b/app/models/post_vote.rb index c7f5786d2..d444d2c32 100644 --- a/app/models/post_vote.rb +++ b/app/models/post_vote.rb @@ -7,6 +7,10 @@ class PostVote < ActiveRecord::Base validates_inclusion_of :score, :in => [1, -1] attr_accessible :post_id, :user_id, :score + def self.prune! + where("created_at < ?", 30.days.ago).delete_all + end + def score=(x) if x == "up" write_attribute(:score, 1) diff --git a/app/models/super_voter.rb b/app/models/super_voter.rb new file mode 100644 index 000000000..68af07096 --- /dev/null +++ b/app/models/super_voter.rb @@ -0,0 +1,33 @@ +class SuperVoter < ActiveRecord::Base + MAGNITUDE = 5 + DURATION = 1.week + + belongs_to :user + validates_uniqueness_of :user_id + after_create :update_user_on_create + after_destroy :update_user_on_destroy + + def self.prune! + where("created_at < ?", DURATION.ago).destroy_all + end + + def self.init! + report = Reports::UserSimilarity.new(User.admins.first.id) + report.prime + report.fetch_similar_user_ids.scan(/\S+/).in_groups_of(2).each do |user_id, score| + unless where("user_id = ?", user_id.to_i).exists? + create(:user_id => user_id) + end + end + end + + def update_user_on_create + user.is_super_voter = true + user.save + end + + def update_user_on_destroy + user.is_super_voter = false + user.save + end +end diff --git a/app/models/user.rb b/app/models/user.rb index 0cfe94190..f719a8aa3 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -33,6 +33,7 @@ class User < ActiveRecord::Base can_approve_posts can_upload_free disable_categorized_saved_searches + is_super_voter ) include Danbooru::HasBitFlags @@ -67,6 +68,7 @@ class User < ActiveRecord::Base has_one :recent_ban, lambda {order("bans.id desc")}, :class_name => "Ban" has_one :api_key has_one :dmail_filter + has_one :super_voter has_many :subscriptions, lambda {order("tag_subscriptions.name")}, :class_name => "TagSubscription", :foreign_key => "creator_id" has_many :note_versions, :foreign_key => "updater_id" has_many :dmails, lambda {order("dmails.id desc")}, :foreign_key => "owner_id" diff --git a/app/presenters/user_similarity_presenter.rb b/app/presenters/user_similarity_presenter.rb index 53d9c4a55..8afd6c5b9 100644 --- a/app/presenters/user_similarity_presenter.rb +++ b/app/presenters/user_similarity_presenter.rb @@ -17,7 +17,7 @@ class UserSimilarityPresenter def fetch data = report.fetch_similar_user_ids - if data == "not ready" + if data == Reports::UserSimilarity::NOT_READY_STRING @not_ready = true else @user_ids_with_scores = data.scan(/\S+/).in_groups_of(2) diff --git a/db/migrate/20160222211328_create_super_voters.rb b/db/migrate/20160222211328_create_super_voters.rb new file mode 100644 index 000000000..19acf1c65 --- /dev/null +++ b/db/migrate/20160222211328_create_super_voters.rb @@ -0,0 +1,8 @@ +class CreateSuperVoters < ActiveRecord::Migration + def change + create_table :super_voters do |t| + t.integer :user_id + t.timestamps null: false + end + end +end diff --git a/db/structure.sql b/db/structure.sql index 91c4fa25c..30d843043 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -2830,6 +2830,37 @@ CREATE TABLE schema_migrations ( ); +-- +-- Name: super_voters; Type: TABLE; Schema: public; Owner: -; Tablespace: +-- + +CREATE TABLE super_voters ( + id integer NOT NULL, + user_id integer, + created_at timestamp without time zone NOT NULL, + updated_at timestamp without time zone NOT NULL +); + + +-- +-- Name: super_voters_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE super_voters_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: super_voters_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE super_voters_id_seq OWNED BY super_voters.id; + + -- -- Name: tag_aliases; Type: TABLE; Schema: public; Owner: -; Tablespace: -- @@ -4261,6 +4292,13 @@ ALTER TABLE ONLY posts ALTER COLUMN id SET DEFAULT nextval('posts_id_seq'::regcl ALTER TABLE ONLY saved_searches ALTER COLUMN id SET DEFAULT nextval('saved_searches_id_seq'::regclass); +-- +-- Name: id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY super_voters ALTER COLUMN id SET DEFAULT nextval('super_voters_id_seq'::regclass); + + -- -- Name: id; Type: DEFAULT; Schema: public; Owner: - -- @@ -4657,6 +4695,14 @@ ALTER TABLE ONLY saved_searches ADD CONSTRAINT saved_searches_pkey PRIMARY KEY (id); +-- +-- Name: super_voters_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: +-- + +ALTER TABLE ONLY super_voters + ADD CONSTRAINT super_voters_pkey PRIMARY KEY (id); + + -- -- Name: tag_aliases_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: -- @@ -7313,3 +7359,5 @@ INSERT INTO schema_migrations (version) VALUES ('20160219010854'); INSERT INTO schema_migrations (version) VALUES ('20160219172840'); +INSERT INTO schema_migrations (version) VALUES ('20160222211328'); + diff --git a/test/factories/super_voter.rb b/test/factories/super_voter.rb new file mode 100644 index 000000000..d9267450b --- /dev/null +++ b/test/factories/super_voter.rb @@ -0,0 +1,4 @@ +FactoryGirl.define do + factory(:super_voter) do + end +end \ No newline at end of file diff --git a/test/models/super_voter_test.rb b/test/models/super_voter_test.rb new file mode 100644 index 000000000..16c6fa2d4 --- /dev/null +++ b/test/models/super_voter_test.rb @@ -0,0 +1,38 @@ +require 'test_helper' + +class SuperVoterTest < ActiveSupport::TestCase + def setup + super + @user = FactoryGirl.create(:user) + end + + context "#init" do + setup do + @admin = FactoryGirl.create(:admin_user) + Reports::UserSimilarity.any_instance.stubs(:fetch_similar_user_ids).returns("#{@user.id} 1") + end + + should "create super voter objects" do + assert_difference("SuperVoter.count") do + SuperVoter.init! + end + end + end + + context "creation" do + should "update the is_super_voter field on the user object" do + FactoryGirl.create(:super_voter, user: @user) + @user.reload + assert_equal(true, @user.is_super_voter?) + end + end + + context "destruction" do + should "update the is_super_voter field on the user object" do + voter = FactoryGirl.create(:super_voter, user: @user) + voter.destroy + @user.reload + assert_equal(false, @user.is_super_voter?) + end + end +end diff --git a/test/unit/post_test.rb b/test/unit/post_test.rb index 861cff7cb..4a7b9a7d9 100644 --- a/test/unit/post_test.rb +++ b/test/unit/post_test.rb @@ -1463,6 +1463,24 @@ class PostTest < ActiveSupport::TestCase end context "Voting:" do + context "with a super voter" do + setup do + @user = FactoryGirl.create(:user) + FactoryGirl.create(:super_voter, user: @user) + @post = FactoryGirl.create(:post) + end + + should "account for magnitude" do + CurrentUser.scoped(@user, "127.0.0.1") do + assert_nothing_raised {@post.vote!("up")} + assert_raises(PostVote::Error) {@post.vote!("up")} + @post.reload + assert_equal(1, PostVote.count) + assert_equal(SuperVoter::MAGNITUDE, @post.score) + end + end + end + should "not allow duplicate votes" do user = FactoryGirl.create(:user) post = FactoryGirl.create(:post)