mirror of
https://github.com/discourse/discourse.git
synced 2024-11-30 00:35:07 +08:00
5eea7244c7
Follow-up to e3ae57ea7a
The previous commit added an `after_create` callback that triggers a refresh for the user directory whenever a `User` record is created. Theoretically, this approach should work, however, there's a gotcha in practice, because during a real user registration, when the `User` record is created in the database, it's not marked as active until the user verifies their email address and the user directory excludes inactive users, so the initial directory refresh triggered by the `after_create` callback becomes pointless.
To make a new user appear in the user directory immediately after sign up, we need to trigger a refresh via an `after_save` callback when they verify their email address and become active.
201 lines
7.6 KiB
Ruby
201 lines
7.6 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
class DirectoryItem < ActiveRecord::Base
|
|
belongs_to :user
|
|
has_one :user_stat, foreign_key: :user_id, primary_key: :user_id
|
|
|
|
@@plugin_queries = []
|
|
|
|
def self.period_types
|
|
@types ||= Enum.new(all: 1, yearly: 2, monthly: 3, weekly: 4, daily: 5, quarterly: 6)
|
|
end
|
|
|
|
def self.refresh!
|
|
period_types.each_key { |p| refresh_period!(p) }
|
|
end
|
|
|
|
def self.last_updated_at(period_type)
|
|
val = Discourse.redis.get("directory_#{period_type}")
|
|
return nil if val.nil?
|
|
|
|
Time.zone.at(val.to_i)
|
|
end
|
|
|
|
def self.add_plugin_query(details)
|
|
@@plugin_queries << details
|
|
end
|
|
|
|
def self.clear_plugin_queries
|
|
@@plugin_queries = []
|
|
end
|
|
|
|
def self.refresh_period!(period_type, force: false)
|
|
DiscourseEvent.trigger("before_directory_refresh")
|
|
Discourse.redis.set("directory_#{period_types[period_type]}", Time.zone.now.to_i)
|
|
|
|
# Don't calculate it if the user directory is disabled
|
|
return unless SiteSetting.enable_user_directory? || force
|
|
|
|
since =
|
|
case period_type
|
|
when :daily
|
|
1.day.ago
|
|
when :weekly
|
|
1.week.ago
|
|
when :monthly
|
|
1.month.ago
|
|
when :quarterly
|
|
3.months.ago
|
|
when :yearly
|
|
1.year.ago
|
|
else
|
|
1000.years.ago
|
|
end
|
|
|
|
ActiveRecord::Base.transaction do
|
|
# Delete records that belonged to users who have been deleted
|
|
DB.exec(
|
|
"DELETE FROM directory_items
|
|
USING directory_items di
|
|
LEFT JOIN users u ON (u.id = user_id AND u.active AND u.silenced_till IS NULL AND u.id > 0)
|
|
WHERE di.id = directory_items.id AND
|
|
u.id IS NULL AND
|
|
di.period_type = :period_type",
|
|
period_type: period_types[period_type],
|
|
)
|
|
|
|
add_missing_users(period_type)
|
|
|
|
# Calculate new values and update records
|
|
#
|
|
#
|
|
# TODO
|
|
# WARNING: post_count is a wrong name, it should be reply_count (excluding topic post)
|
|
#
|
|
#
|
|
query_args = {
|
|
period_type: period_types[period_type],
|
|
since: since,
|
|
like_type: UserAction::LIKE,
|
|
was_liked_type: UserAction::WAS_LIKED,
|
|
new_topic_type: UserAction::NEW_TOPIC,
|
|
reply_type: UserAction::REPLY,
|
|
regular_post_type: Post.types[:regular],
|
|
}
|
|
|
|
DB.exec(
|
|
"WITH x AS (SELECT
|
|
u.id user_id,
|
|
SUM(CASE WHEN p.id IS NOT NULL AND t.id IS NOT NULL AND ua.action_type = :was_liked_type THEN 1 ELSE 0 END) likes_received,
|
|
SUM(CASE WHEN p.id IS NOT NULL AND t.id IS NOT NULL AND ua.action_type = :like_type THEN 1 ELSE 0 END) likes_given,
|
|
COALESCE((SELECT COUNT(topic_id) FROM topic_views AS v WHERE v.user_id = u.id AND v.viewed_at > :since), 0) topics_entered,
|
|
COALESCE((SELECT COUNT(id) FROM user_visits AS uv WHERE uv.user_id = u.id AND uv.visited_at > :since), 0) days_visited,
|
|
COALESCE((SELECT SUM(posts_read) FROM user_visits AS uv2 WHERE uv2.user_id = u.id AND uv2.visited_at > :since), 0) posts_read,
|
|
SUM(CASE WHEN t2.id IS NOT NULL AND ua.action_type = :new_topic_type THEN 1 ELSE 0 END) topic_count,
|
|
SUM(CASE WHEN p.id IS NOT NULL AND t.id IS NOT NULL AND ua.action_type = :reply_type THEN 1 ELSE 0 END) post_count
|
|
FROM users AS u
|
|
LEFT OUTER JOIN user_actions AS ua ON ua.user_id = u.id AND COALESCE(ua.created_at, :since) > :since
|
|
LEFT OUTER JOIN posts AS p ON ua.target_post_id = p.id AND p.deleted_at IS NULL AND p.post_type = :regular_post_type AND NOT p.hidden
|
|
LEFT OUTER JOIN topics AS t ON p.topic_id = t.id AND t.archetype = 'regular' AND t.deleted_at IS NULL AND t.visible
|
|
LEFT OUTER JOIN topics AS t2 ON t2.id = ua.target_topic_id AND t2.archetype = 'regular' AND t2.deleted_at IS NULL AND t2.visible
|
|
LEFT OUTER JOIN categories AS c ON t.category_id = c.id
|
|
WHERE u.active
|
|
AND u.silenced_till IS NULL
|
|
AND u.id > 0
|
|
GROUP BY u.id)
|
|
UPDATE directory_items di SET
|
|
likes_received = x.likes_received,
|
|
likes_given = x.likes_given,
|
|
topics_entered = x.topics_entered,
|
|
days_visited = x.days_visited,
|
|
posts_read = x.posts_read,
|
|
topic_count = x.topic_count,
|
|
post_count = x.post_count
|
|
FROM x
|
|
WHERE
|
|
x.user_id = di.user_id AND
|
|
di.period_type = :period_type AND (
|
|
di.likes_received <> x.likes_received OR
|
|
di.likes_given <> x.likes_given OR
|
|
di.topics_entered <> x.topics_entered OR
|
|
di.days_visited <> x.days_visited OR
|
|
di.posts_read <> x.posts_read OR
|
|
di.topic_count <> x.topic_count OR
|
|
di.post_count <> x.post_count )
|
|
|
|
",
|
|
query_args,
|
|
)
|
|
|
|
@@plugin_queries.each { |plugin_query| DB.exec(plugin_query, query_args) }
|
|
|
|
DB.exec <<~SQL if period_type == :all
|
|
UPDATE user_stats s
|
|
SET likes_given = d.likes_given,
|
|
likes_received = d.likes_received,
|
|
topic_count = d.topic_count,
|
|
post_count = d.post_count
|
|
|
|
FROM directory_items d
|
|
WHERE s.user_id = d.user_id AND
|
|
d.period_type = 1 AND
|
|
( s.likes_given <> d.likes_given OR
|
|
s.likes_received <> d.likes_received OR
|
|
s.topic_count <> d.topic_count OR
|
|
s.post_count <> d.post_count
|
|
)
|
|
SQL
|
|
end
|
|
end
|
|
|
|
def self.add_missing_users_all_periods
|
|
period_types.each_key { |p| add_missing_users(p) }
|
|
end
|
|
|
|
def self.add_missing_users(period_type)
|
|
column_names = DirectoryColumn.automatic_column_names + DirectoryColumn.plugin_directory_columns
|
|
DB.exec(
|
|
"INSERT INTO directory_items(period_type, user_id, #{column_names.map(&:to_s).join(", ")})
|
|
SELECT
|
|
:period_type,
|
|
u.id,
|
|
#{Array.new(column_names.count, 0).join(", ")}
|
|
FROM users u
|
|
LEFT JOIN directory_items di ON di.user_id = u.id AND di.period_type = :period_type
|
|
WHERE di.id IS NULL AND u.id > 0 AND u.silenced_till IS NULL AND u.active
|
|
#{SiteSetting.must_approve_users ? "AND u.approved" : ""}
|
|
",
|
|
period_type: period_types[period_type],
|
|
)
|
|
end
|
|
end
|
|
|
|
# == Schema Information
|
|
#
|
|
# Table name: directory_items
|
|
#
|
|
# id :integer not null, primary key
|
|
# period_type :integer not null
|
|
# user_id :integer not null
|
|
# likes_received :integer not null
|
|
# likes_given :integer not null
|
|
# topics_entered :integer not null
|
|
# topic_count :integer not null
|
|
# post_count :integer not null
|
|
# created_at :datetime
|
|
# updated_at :datetime
|
|
# days_visited :integer default(0), not null
|
|
# posts_read :integer default(0), not null
|
|
#
|
|
# Indexes
|
|
#
|
|
# index_directory_items_on_days_visited (days_visited)
|
|
# index_directory_items_on_likes_given (likes_given)
|
|
# index_directory_items_on_likes_received (likes_received)
|
|
# index_directory_items_on_period_type_and_user_id (period_type,user_id) UNIQUE
|
|
# index_directory_items_on_post_count (post_count)
|
|
# index_directory_items_on_posts_read (posts_read)
|
|
# index_directory_items_on_topic_count (topic_count)
|
|
# index_directory_items_on_topics_entered (topics_entered)
|
|
#
|