2013-02-05 14:16:51 -05:00
class UserAction < ActiveRecord :: Base
belongs_to :user
2013-03-23 20:32:59 +05:30
belongs_to :target_post , class_name : " Post "
belongs_to :target_topic , class_name : " Topic "
2013-02-07 16:45:24 +01:00
2013-02-05 14:16:51 -05:00
validates_presence_of :action_type
validates_presence_of :user_id
LIKE = 1
2013-02-07 16:45:24 +01:00
WAS_LIKED = 2
2013-02-05 14:16:51 -05:00
BOOKMARK = 3
NEW_TOPIC = 4
2013-05-01 10:52:31 +10:00
REPLY = 5
2013-02-05 14:16:51 -05:00
RESPONSE = 6
MENTION = 7
QUOTE = 9
2013-02-07 16:45:24 +01:00
STAR = 10
2013-02-05 14:16:51 -05:00
EDIT = 11
NEW_PRIVATE_MESSAGE = 12
GOT_PRIVATE_MESSAGE = 13
ORDER = Hash [ * [
GOT_PRIVATE_MESSAGE ,
2013-05-17 14:11:33 -04:00
NEW_PRIVATE_MESSAGE ,
2013-02-05 14:16:51 -05:00
BOOKMARK ,
NEW_TOPIC ,
2013-05-01 10:52:31 +10:00
REPLY ,
2013-02-05 14:16:51 -05:00
RESPONSE ,
LIKE ,
WAS_LIKED ,
MENTION ,
QUOTE ,
STAR ,
EDIT
] . each_with_index . to_a . flatten ]
2013-06-12 11:14:08 +10:00
# note, this is temporary until we upgrade to rails 4
# in rails 4 types are mapped correctly so you dont end up
# having strings where you would expect bools
class UserActionRow < OpenStruct
include ActiveModel :: SerializerSupport
end
2013-04-29 16:33:24 +10:00
2013-02-05 14:16:51 -05:00
def self . stats ( user_id , guardian )
2013-02-25 19:42:20 +03:00
2013-04-29 16:33:24 +10:00
# Sam: I tried this in AR and it got complex
builder = UserAction . sql_builder <<SQL
2013-02-05 14:16:51 -05:00
2013-04-29 16:33:24 +10:00
SELECT action_type , COUNT ( * ) count
FROM user_actions a
JOIN topics t ON t . id = a . target_topic_id
LEFT JOIN posts p on p . id = a . target_post_id
JOIN posts p2 on p2 . topic_id = a . target_topic_id and p2 . post_number = 1
LEFT JOIN categories c ON c . id = t . category_id
/ *where* /
GROUP BY action_type
SQL
builder . where ( 'a.user_id = :user_id' , user_id : user_id )
2013-04-10 12:50:00 +10:00
2013-04-29 16:33:24 +10:00
apply_common_filters ( builder , user_id , guardian )
2013-02-15 17:08:28 -05:00
2013-04-29 16:33:24 +10:00
results = builder . exec . to_a
2013-02-28 21:54:12 +03:00
results . sort! { | a , b | ORDER [ a . action_type ] < = > ORDER [ b . action_type ] }
2013-02-05 14:16:51 -05:00
results
end
def self . stream_item ( action_id , guardian )
2013-02-28 21:54:12 +03:00
stream ( action_id : action_id , guardian : guardian ) . first
2013-02-05 14:16:51 -05:00
end
2013-02-07 16:45:24 +01:00
def self . stream ( opts = { } )
2013-02-05 14:16:51 -05:00
user_id = opts [ :user_id ]
2013-02-28 21:54:12 +03:00
offset = opts [ :offset ] || 0
limit = opts [ :limit ] || 60
2013-02-07 16:45:24 +01:00
action_id = opts [ :action_id ]
2013-02-05 14:16:51 -05:00
action_types = opts [ :action_types ]
guardian = opts [ :guardian ]
ignore_private_messages = opts [ :ignore_private_messages ]
2013-02-25 19:42:20 +03:00
# The weird thing is that target_post_id can be null, so it makes everything
# ever so more complex. Should we allow this, not sure.
2013-02-13 20:38:43 +11:00
2013-06-12 11:14:08 +10:00
builder = SqlBuilder . new ( "
2013-02-25 19:42:20 +03:00
SELECT
t . title , a . action_type , a . created_at , t . id topic_id ,
2013-04-18 12:08:13 +02:00
a . user_id AS target_user_id , au . name AS target_name , au . username AS target_username ,
2013-02-25 19:42:20 +03:00
coalesce ( p . post_number , 1 ) post_number ,
2013-02-17 01:38:20 -05:00
p . reply_to_post_number ,
2013-02-25 19:42:20 +03:00
pu . email , pu . username , pu . name , pu . id user_id ,
u . email acting_email , u . username acting_username , u . name acting_name , u . id acting_user_id ,
2013-06-12 11:14:08 +10:00
coalesce ( p . cooked , p2 . cooked ) cooked ,
CASE WHEN coalesce ( p . deleted_at , p2 . deleted_at , t . deleted_at ) IS NULL THEN false ELSE true END deleted ,
p . hidden
2013-02-13 20:38:43 +11:00
FROM user_actions as a
JOIN topics t on t . id = a . target_topic_id
LEFT JOIN posts p on p . id = a . target_post_id
JOIN posts p2 on p2 . topic_id = a . target_topic_id and p2 . post_number = 1
JOIN users u on u . id = a . acting_user_id
JOIN users pu on pu . id = COALESCE ( p . user_id , t . user_id )
2013-04-18 12:08:13 +02:00
JOIN users au on au . id = a . user_id
2013-04-29 16:33:24 +10:00
LEFT JOIN categories c on c . id = t . category_id
2013-02-05 14:16:51 -05:00
/ *where* /
/ *order_by* /
/ *offset* /
/ *limit* /
" )
2013-04-29 16:33:24 +10:00
apply_common_filters ( builder , user_id , guardian , ignore_private_messages )
2013-02-05 14:16:51 -05:00
if action_id
builder . where ( " a.id = :id " , id : action_id . to_i )
2013-02-07 16:45:24 +01:00
else
2013-02-05 14:16:51 -05:00
builder . where ( " a.user_id = :user_id " , user_id : user_id . to_i )
2013-02-07 16:45:24 +01:00
builder . where ( " a.action_type in (:action_types) " , action_types : action_types ) if action_types && action_types . length > 0
2013-05-27 10:22:37 +10:00
builder
. order_by ( " a.created_at desc " )
. offset ( offset . to_i )
. limit ( limit . to_i )
2013-02-05 14:16:51 -05:00
end
2013-05-27 10:22:37 +10:00
2013-06-12 11:14:08 +10:00
builder . map_exec ( UserActionRow )
2013-02-05 14:16:51 -05:00
end
2013-05-20 16:44:06 +10:00
# slightly different to standard stream, it collapses replies
def self . private_message_stream ( action_type , opts )
user_id = opts [ :user_id ]
return [ ] unless opts [ :guardian ] . can_see_private_messages? ( user_id )
2013-06-12 11:14:08 +10:00
builder = SqlBuilder . new ( "
2013-05-20 16:44:06 +10:00
SELECT
t . title , :action_type action_type , p . created_at , t . id topic_id ,
:user_id AS target_user_id , au . name AS target_name , au . username AS target_username ,
coalesce ( p . post_number , 1 ) post_number ,
p . reply_to_post_number ,
pu . email , pu . username , pu . name , pu . id user_id ,
pu . email acting_email , pu . username acting_username , pu . name acting_name , pu . id acting_user_id ,
2013-06-12 11:14:08 +10:00
p . cooked ,
CASE WHEN coalesce ( p . deleted_at , t . deleted_at ) IS NULL THEN false ELSE true END deleted ,
p . hidden
2013-05-20 16:44:06 +10:00
FROM topics t
JOIN posts p ON p . topic_id = t . id and p . post_number = t . highest_post_number
JOIN users pu ON pu . id = p . user_id
JOIN users au ON au . id = :user_id
WHERE archetype = 'private_message' and EXISTS (
select 1 from user_actions a where a . user_id = :user_id and a . target_topic_id = t . id and action_type = :action_type )
ORDER BY p . created_at desc
/ *offset* /
/ *limit* /
" )
2013-05-27 10:22:37 +10:00
builder
. offset ( ( opts [ :offset ] || 0 ) . to_i )
. limit ( ( opts [ :limit ] || 60 ) . to_i )
2013-06-12 11:14:08 +10:00
. map_exec ( UserActionRow , user_id : user_id , action_type : action_type )
2013-05-20 16:44:06 +10:00
end
2013-02-05 14:16:51 -05:00
def self . log_action! ( hash )
require_parameters ( hash , :action_type , :user_id , :acting_user_id , :target_topic_id , :target_post_id )
transaction ( requires_new : true ) do
2013-02-07 16:45:24 +01:00
begin
2013-02-28 21:54:12 +03:00
action = new ( hash )
2013-02-05 14:16:51 -05:00
if hash [ :created_at ]
2013-02-07 16:45:24 +01:00
action . created_at = hash [ :created_at ]
2013-02-05 14:16:51 -05:00
end
action . save!
2013-04-05 15:29:46 +11:00
user_id = hash [ :user_id ]
2013-05-28 09:13:53 +10:00
update_like_count ( user_id , hash [ :action_type ] , 1 )
2013-04-05 15:29:46 +11:00
2013-05-16 15:03:03 +10:00
topic = Topic . includes ( :category ) . where ( id : hash [ :target_topic_id ] ) . first
# move into Topic perhaps
group_ids = nil
if topic && topic . category && topic . category . secure
2013-05-28 09:13:53 +10:00
group_ids = topic . category . groups . pluck ( " groups.id " )
2013-05-16 15:03:03 +10:00
end
MessageBus . publish ( " /users/ #{ action . user . username . downcase } " ,
action . id ,
user_ids : [ user_id ] ,
group_ids : group_ids )
2013-02-05 14:16:51 -05:00
rescue ActiveRecord :: RecordNotUnique
# can happen, don't care already logged
raise ActiveRecord :: Rollback
end
end
end
def self . remove_action! ( hash )
require_parameters ( hash , :action_type , :user_id , :acting_user_id , :target_topic_id , :target_post_id )
if action = UserAction . where ( hash ) . first
2013-02-07 16:45:24 +01:00
action . destroy
2013-02-05 14:16:51 -05:00
MessageBus . publish ( " /user/ #{ hash [ :user_id ] } " , { user_action_id : action . id , remove : true } )
end
2013-04-05 15:29:46 +11:00
2013-05-28 09:13:53 +10:00
update_like_count ( hash [ :user_id ] , hash [ :action_type ] , - 1 )
end
protected
def self . update_like_count ( user_id , action_type , delta )
2013-04-05 15:29:46 +11:00
if action_type == LIKE
2013-07-01 20:45:52 +02:00
User . where ( id : user_id ) . update_all ( " likes_given = likes_given + #{ delta . to_i } " )
2013-04-05 15:29:46 +11:00
elsif action_type == WAS_LIKED
2013-07-01 20:45:52 +02:00
User . where ( id : user_id ) . update_all ( " likes_received = likes_received + #{ delta . to_i } " )
2013-04-05 15:29:46 +11:00
end
2013-02-05 14:16:51 -05:00
end
2013-04-29 16:33:24 +10:00
def self . apply_common_filters ( builder , user_id , guardian , ignore_private_messages = false )
unless guardian . can_see_deleted_posts?
builder . where ( " p.deleted_at is null and p2.deleted_at is null and t.deleted_at is null " )
end
unless guardian . user && guardian . user . id == user_id
builder . where ( " a.action_type not in ( #{ BOOKMARK } ) " )
end
if ! guardian . can_see_private_messages? ( user_id ) || ignore_private_messages
builder . where ( " t.archetype != :archetype " , archetype : Archetype :: private_message )
end
2013-05-02 17:22:27 +10:00
unless guardian . is_staff?
2013-04-29 16:33:24 +10:00
allowed = guardian . secure_category_ids
if allowed . present?
builder . where ( " ( c.secure IS NULL OR
c . secure = 'f' OR
( c . secure = 't' and c . id in ( :cats ) ) ) " , cats: guardian.secure_category_ids )
else
builder . where ( " (c.secure IS NULL OR c.secure = 'f') " )
end
end
end
2013-02-05 14:16:51 -05:00
def self . require_parameters ( data , * params )
params . each do | p |
raise Discourse :: InvalidParameters . new ( p ) if data [ p ] . nil?
end
end
end
2013-05-24 12:48:32 +10:00
# == Schema Information
#
# Table name: user_actions
#
# id :integer not null, primary key
# action_type :integer not null
# user_id :integer not null
# target_topic_id :integer
# target_post_id :integer
# target_user_id :integer
# acting_user_id :integer
# created_at :datetime not null
# updated_at :datetime not null
#
# Indexes
#
# idx_unique_rows (action_type,user_id,target_topic_id,target_post_id,acting_user_id) UNIQUE
# index_actions_on_acting_user_id (acting_user_id)
# index_actions_on_user_id_and_action_type (user_id,action_type)
#