mirror of
https://github.com/discourse/discourse.git
synced 2024-11-22 13:41:31 +08:00
get regular trust level going, self heal inconsistent topic timings
This commit is contained in:
parent
6aaa0ffe5e
commit
4fbf017272
|
@ -152,6 +152,14 @@ class SiteSetting < ActiveRecord::Base
|
||||||
setting(:basic_requires_read_posts, 50)
|
setting(:basic_requires_read_posts, 50)
|
||||||
setting(:basic_requires_time_spent_mins, 15)
|
setting(:basic_requires_time_spent_mins, 15)
|
||||||
|
|
||||||
|
setting(:regular_requires_topics_entered, 3)
|
||||||
|
setting(:regular_requires_read_posts, 100)
|
||||||
|
setting(:regular_requires_time_spent_mins, 60)
|
||||||
|
setting(:regular_requires_days_visited, 15)
|
||||||
|
setting(:regular_requires_likes_received, 1)
|
||||||
|
setting(:regular_requires_likes_given, 1)
|
||||||
|
setting(:regular_requires_topic_reply_count, 3)
|
||||||
|
|
||||||
# Entropy checks
|
# Entropy checks
|
||||||
setting(:title_min_entropy, 10)
|
setting(:title_min_entropy, 10)
|
||||||
setting(:body_min_entropy, 7)
|
setting(:body_min_entropy, 7)
|
||||||
|
|
|
@ -171,4 +171,24 @@ class TopicUser < ActiveRecord::Base
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.ensure_consistency!
|
||||||
|
exec_sql <<SQL
|
||||||
|
UPDATE topic_users t
|
||||||
|
SET
|
||||||
|
last_read_post_number = last_read,
|
||||||
|
seen_post_count = post_count
|
||||||
|
FROM (
|
||||||
|
SELECT topic_id, user_id, COUNT(*) post_count, MAX(post_number) last_read
|
||||||
|
FROM post_timings
|
||||||
|
GROUP BY topic_id, user_id
|
||||||
|
) as X
|
||||||
|
WHERE X.topic_id = t.topic_id AND
|
||||||
|
X.user_id = t.user_id AND
|
||||||
|
(
|
||||||
|
last_read_post_number <> last_read OR
|
||||||
|
seen_post_count <> post_count
|
||||||
|
)
|
||||||
|
SQL
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -201,6 +201,15 @@ JOIN users pu on pu.id = COALESCE(p.user_id, t.user_id)
|
||||||
action.created_at = hash[:created_at]
|
action.created_at = hash[:created_at]
|
||||||
end
|
end
|
||||||
action.save!
|
action.save!
|
||||||
|
|
||||||
|
action_type = hash[:action_type]
|
||||||
|
user_id = hash[:user_id]
|
||||||
|
if action_type == LIKE
|
||||||
|
User.update_all('likes_given = likes_given + 1', id: user_id)
|
||||||
|
elsif action_type == WAS_LIKED
|
||||||
|
User.update_all('likes_received = likes_received + 1', id: user_id)
|
||||||
|
end
|
||||||
|
|
||||||
rescue ActiveRecord::RecordNotUnique
|
rescue ActiveRecord::RecordNotUnique
|
||||||
# can happen, don't care already logged
|
# can happen, don't care already logged
|
||||||
raise ActiveRecord::Rollback
|
raise ActiveRecord::Rollback
|
||||||
|
@ -214,6 +223,14 @@ JOIN users pu on pu.id = COALESCE(p.user_id, t.user_id)
|
||||||
action.destroy
|
action.destroy
|
||||||
MessageBus.publish("/user/#{hash[:user_id]}", {user_action_id: action.id, remove: true})
|
MessageBus.publish("/user/#{hash[:user_id]}", {user_action_id: action.id, remove: true})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
action_type = hash[:action_type]
|
||||||
|
user_id = hash[:user_id]
|
||||||
|
if action_type == LIKE
|
||||||
|
User.update_all('likes_given = likes_given - 1', id: user_id)
|
||||||
|
elsif action_type == WAS_LIKED
|
||||||
|
User.update_all('likes_received = likes_received - 1', id: user_id)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
protected
|
protected
|
||||||
|
|
|
@ -12,8 +12,8 @@ module Clockwork
|
||||||
|
|
||||||
every(1.day, 'enqueue_digest_emails', at: '06:00')
|
every(1.day, 'enqueue_digest_emails', at: '06:00')
|
||||||
every(1.day, 'category_stats', at: '04:00')
|
every(1.day, 'category_stats', at: '04:00')
|
||||||
|
every(1.day, 'ensure_db_consistency', at: '02:00')
|
||||||
every(10.minutes, 'periodical_updates')
|
every(10.minutes, 'periodical_updates')
|
||||||
every(1.day, 'version_check')
|
every(1.day, 'version_check')
|
||||||
every(1.minute, 'clockwork_heartbeat')
|
every(1.minute, 'clockwork_heartbeat')
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -455,6 +455,15 @@ en:
|
||||||
basic_requires_topics_entered: "How many a topics a new user must enter before promotion to basic (1) trust level"
|
basic_requires_topics_entered: "How many a topics a new user must enter before promotion to basic (1) trust level"
|
||||||
basic_requires_read_posts: "How many posts a new user must read before promotion to basic (1) trust level"
|
basic_requires_read_posts: "How many posts a new user must read before promotion to basic (1) trust level"
|
||||||
basic_requires_time_spent_mins: "How many minutes a new user must read posts before promotion to basic (1) trust level"
|
basic_requires_time_spent_mins: "How many minutes a new user must read posts before promotion to basic (1) trust level"
|
||||||
|
|
||||||
|
regular_requires_topics_entered: "How many a topics a basic user must enter before promotion to regular (2) trust level"
|
||||||
|
regular_requires_read_posts: "How many posts a basic user must read before promotion to regular (2) trust level"
|
||||||
|
regular_requires_time_spent_mins: "How many minutes a basic user must read posts before promotion to regular (2) trust level"
|
||||||
|
regular_requires_days_visited: "How many days a basic user must visit the site before promotion to regular (2) trust level"
|
||||||
|
regular_requires_likes_received: "How many likes a basic user must receive before promotion to regular (2) trust level"
|
||||||
|
regular_requires_likes_given: "How many likes a basic user must cast before promotion to regular (2) trust level"
|
||||||
|
regular_requires_topic_reply_count: "How many topics a basic user must reply to before promotion to regular (2) trust level"
|
||||||
|
|
||||||
visitor_max_links: "How many links a visitor can add to a post"
|
visitor_max_links: "How many links a visitor can add to a post"
|
||||||
visitor_max_images: "How many images a visitor can add to a post"
|
visitor_max_images: "How many images a visitor can add to a post"
|
||||||
visitor_max_mentions_per_post: "Maximum number of @name notifications a visitor can use in a post"
|
visitor_max_mentions_per_post: "Maximum number of @name notifications a visitor can use in a post"
|
||||||
|
|
70
db/migrate/20130404232558_add_user_extras.rb
Normal file
70
db/migrate/20130404232558_add_user_extras.rb
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
class AddUserExtras < ActiveRecord::Migration
|
||||||
|
def up
|
||||||
|
|
||||||
|
# NOTE: our user table is getting bloated, we probably want to split it for performance
|
||||||
|
# put lesser used columns into a user_extras table and frequently used ones here.
|
||||||
|
add_column :users, :likes_given, :integer, null: false, default: 0
|
||||||
|
add_column :users, :likes_received, :integer, null: false, default: 0
|
||||||
|
add_column :users, :topic_reply_count, :integer, null: false, default: 0
|
||||||
|
|
||||||
|
|
||||||
|
# NOTE: to keep migrations working through refactorings we avoid externalizing this stuff.
|
||||||
|
# even though a helper method may make sense
|
||||||
|
execute <<SQL
|
||||||
|
UPDATE users u
|
||||||
|
SET
|
||||||
|
likes_given = X.likes_given
|
||||||
|
FROM (
|
||||||
|
SELECT
|
||||||
|
a.user_id,
|
||||||
|
COUNT(*) likes_given
|
||||||
|
FROM user_actions a
|
||||||
|
JOIN posts p ON p.id = a.target_post_id
|
||||||
|
WHERE p.deleted_at IS NULL AND a.action_type = 1
|
||||||
|
GROUP BY a.user_id
|
||||||
|
) as X
|
||||||
|
WHERE X.user_id = u.id
|
||||||
|
SQL
|
||||||
|
|
||||||
|
execute <<SQL
|
||||||
|
UPDATE users u
|
||||||
|
SET
|
||||||
|
likes_received = Y.likes_received
|
||||||
|
FROM (
|
||||||
|
SELECT
|
||||||
|
a.user_id,
|
||||||
|
COUNT(*) likes_received
|
||||||
|
FROM user_actions a
|
||||||
|
JOIN posts p ON p.id = a.target_post_id
|
||||||
|
WHERE p.deleted_at IS NULL AND a.action_type = 2
|
||||||
|
GROUP BY a.user_id
|
||||||
|
) as Y
|
||||||
|
WHERE Y.user_id = u.id
|
||||||
|
SQL
|
||||||
|
|
||||||
|
execute <<SQL
|
||||||
|
UPDATE users u
|
||||||
|
SET
|
||||||
|
topic_reply_count = Z.topic_reply_count
|
||||||
|
FROM (
|
||||||
|
SELECT
|
||||||
|
p.user_id,
|
||||||
|
COUNT(DISTINCT topic_id) topic_reply_count
|
||||||
|
FROM posts p
|
||||||
|
JOIN topics t on t.id = p.topic_id
|
||||||
|
WHERE t.user_id <> p.user_id AND
|
||||||
|
p.deleted_at IS NULL AND t.deleted_at IS NULL
|
||||||
|
GROUP BY p.user_id
|
||||||
|
) Z
|
||||||
|
WHERE
|
||||||
|
Z.user_id = u.id
|
||||||
|
SQL
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
def down
|
||||||
|
remove_column :users, :likes_given
|
||||||
|
remove_column :users, :likes_received
|
||||||
|
remove_column :users, :topic_reply_count
|
||||||
|
end
|
||||||
|
end
|
|
@ -186,7 +186,7 @@ class Guardian
|
||||||
return false if @user.blank?
|
return false if @user.blank?
|
||||||
return false unless can_see?(object)
|
return false unless can_see?(object)
|
||||||
return false if SiteSetting.must_approve_users?
|
return false if SiteSetting.must_approve_users?
|
||||||
@user.moderator?
|
@user.has_trust_level?(:regular) || @user.moderator?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
8
lib/jobs/ensure_db_consistency.rb
Normal file
8
lib/jobs/ensure_db_consistency.rb
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
module Jobs
|
||||||
|
# checks to see if any users need to be promoted
|
||||||
|
class EnsureDbConsistency < Jobs::Base
|
||||||
|
def execute(args)
|
||||||
|
TopicUser.ensure_consistency!
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -23,6 +23,8 @@ class PostCreator
|
||||||
# target_usernames - comma delimited list of usernames for membership (private message)
|
# target_usernames - comma delimited list of usernames for membership (private message)
|
||||||
# meta_data - Topic meta data hash
|
# meta_data - Topic meta data hash
|
||||||
def initialize(user, opts)
|
def initialize(user, opts)
|
||||||
|
# TODO: we should reload user in case it is tainted, should take in a user_id as opposed to user
|
||||||
|
# If we don't do this we introduce a rather risky dependency
|
||||||
@user = user
|
@user = user
|
||||||
@opts = opts
|
@opts = opts
|
||||||
raise Discourse::InvalidParameters.new(:raw) if @opts[:raw].blank?
|
raise Discourse::InvalidParameters.new(:raw) if @opts[:raw].blank?
|
||||||
|
@ -113,8 +115,13 @@ class PostCreator
|
||||||
# Track the topic
|
# Track the topic
|
||||||
TopicUser.auto_track(@user.id, topic.id, TopicUser.notification_reasons[:created_post])
|
TopicUser.auto_track(@user.id, topic.id, TopicUser.notification_reasons[:created_post])
|
||||||
|
|
||||||
# Update `last_posted_at` to match the post's created_at
|
|
||||||
@user.update_column(:last_posted_at, post.created_at)
|
if @user.id != topic.user_id
|
||||||
|
@user.topic_reply_count += 1
|
||||||
|
end
|
||||||
|
|
||||||
|
@user.last_posted_at = post.created_at
|
||||||
|
@user.save!
|
||||||
|
|
||||||
# Publish the post in the message bus
|
# Publish the post in the message bus
|
||||||
MessageBus.publish("/topic/#{post.topic_id}",
|
MessageBus.publish("/topic/#{post.topic_id}",
|
||||||
|
|
|
@ -32,4 +32,17 @@ class Promotion
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def review_basic
|
||||||
|
return false if @user.topics_entered < SiteSetting.regular_requires_topics_entered
|
||||||
|
return false if @user.posts_read_count < SiteSetting.regular_requires_read_posts
|
||||||
|
return false if (@user.time_read / 60) < SiteSetting.regular_requires_time_spent_mins
|
||||||
|
return false if @user.days_visited < SiteSetting.regular_requires_days_visited
|
||||||
|
return false if @user.likes_received < SiteSetting.regular_requires_likes_received
|
||||||
|
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
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -71,7 +71,6 @@ describe PostCreator do
|
||||||
creator.create
|
creator.create
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
it 'assigns a category when supplied' do
|
it 'assigns a category when supplied' do
|
||||||
creator_with_category.create.topic.category.should == category
|
creator_with_category.create.topic.category.should == category
|
||||||
end
|
end
|
||||||
|
@ -84,6 +83,18 @@ describe PostCreator do
|
||||||
Post.any_instance.expects(:image_sizes=).with(image_sizes)
|
Post.any_instance.expects(:image_sizes=).with(image_sizes)
|
||||||
creator_with_image_sizes.create
|
creator_with_image_sizes.create
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'increases topic response counts' do
|
||||||
|
first_post = creator.create
|
||||||
|
user2 = Fabricate(:coding_horror)
|
||||||
|
|
||||||
|
user2.topic_reply_count.should == 0
|
||||||
|
first_post.user.reload.topic_reply_count.should == 0
|
||||||
|
|
||||||
|
PostCreator.new(user2, topic_id: first_post.topic_id, raw: "this is my test post 123").create
|
||||||
|
user2.reload.topic_reply_count.should == 1
|
||||||
|
first_post.user.reload.topic_reply_count.should == 0
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -44,5 +44,46 @@ describe Promotion do
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context "basic" do
|
||||||
|
|
||||||
|
let(:user) { Fabricate(:user, trust_level: TrustLevel.levels[:basic])}
|
||||||
|
let(:promotion) { Promotion.new(user) }
|
||||||
|
|
||||||
|
context 'that has done nothing' do
|
||||||
|
let!(:result) { promotion.review }
|
||||||
|
|
||||||
|
it "returns false" do
|
||||||
|
result.should be_false
|
||||||
|
end
|
||||||
|
|
||||||
|
it "has not changed the user's trust level" do
|
||||||
|
user.trust_level.should == TrustLevel.levels[:basic]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "that has done the requisite things" do
|
||||||
|
|
||||||
|
before do
|
||||||
|
user.topics_entered = SiteSetting.regular_requires_topics_entered
|
||||||
|
user.posts_read_count = SiteSetting.regular_requires_read_posts
|
||||||
|
user.time_read = SiteSetting.regular_requires_time_spent_mins * 60
|
||||||
|
user.days_visited = SiteSetting.regular_requires_days_visited * 60
|
||||||
|
user.likes_received = SiteSetting.regular_requires_likes_received
|
||||||
|
user.likes_given = SiteSetting.regular_requires_likes_given
|
||||||
|
user.topic_reply_count = SiteSetting.regular_requires_topic_reply_count
|
||||||
|
|
||||||
|
@result = promotion.review
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns true" do
|
||||||
|
@result.should be_true
|
||||||
|
end
|
||||||
|
|
||||||
|
it "has upgraded the user to regular" do
|
||||||
|
user.trust_level.should == TrustLevel.levels[:regular]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -207,4 +207,23 @@ describe TopicUser do
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
it "is able to self heal" do
|
||||||
|
p1 = Fabricate(:post)
|
||||||
|
p2 = Fabricate(:post, user: p1.user, topic: p1.topic, post_number: 2)
|
||||||
|
|
||||||
|
TopicUser.exec_sql("UPDATE topic_users set seen_post_count=0, last_read_post_number=0
|
||||||
|
WHERE topic_id = :topic_id AND user_id = :user_id", topic_id: p1.topic_id, user_id: p1.user_id)
|
||||||
|
|
||||||
|
[p1,p2].each do |p|
|
||||||
|
PostTiming.create(topic_id: p.topic_id, post_number: p.post_number, user_id: p.user_id, msecs: 100)
|
||||||
|
end
|
||||||
|
|
||||||
|
TopicUser.ensure_consistency!
|
||||||
|
|
||||||
|
tu = TopicUser.where(user_id: p1.user_id, topic_id: p1.topic_id).first
|
||||||
|
tu.last_read_post_number.should == p2.post_number
|
||||||
|
tu.seen_post_count.should == 2
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -108,13 +108,17 @@ describe UserAction do
|
||||||
@likee_action = likee.user_actions.where(action_type: UserAction::WAS_LIKED).first
|
@likee_action = likee.user_actions.where(action_type: UserAction::WAS_LIKED).first
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'should create a like action on the liker' do
|
it 'should result in correct data assignment' do
|
||||||
@liker_action.should_not be_nil
|
@liker_action.should_not be_nil
|
||||||
|
@likee_action.should_not be_nil
|
||||||
|
likee.reload.likes_received.should == 1
|
||||||
|
liker.reload.likes_given.should == 1
|
||||||
|
|
||||||
|
PostAction.remove_act(liker, post, PostActionType.types[:like])
|
||||||
|
likee.reload.likes_received.should == 0
|
||||||
|
liker.reload.likes_given.should == 0
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'should create a like action on the likee' do
|
|
||||||
@likee_action.should_not be_nil
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
context "liking a private message" do
|
context "liking a private message" do
|
||||||
|
|
Loading…
Reference in New Issue
Block a user