2019-05-02 18:17:27 -04:00
# frozen_string_literal: true
2014-01-01 20:00:08 -05:00
class CategoryUser < ActiveRecord :: Base
belongs_to :category
belongs_to :user
2014-01-02 01:58:49 -05:00
def self . lookup ( user , level )
self . where ( user : user , notification_level : notification_levels [ level ] )
end
2014-01-01 20:00:08 -05:00
def self . notification_levels
2016-07-06 15:56:40 -04:00
NotificationLevels . all
2016-07-05 15:16:32 -04:00
end
def self . watching_levels
[ notification_levels [ :watching ] , notification_levels [ :watching_first_post ] ]
2014-01-01 20:00:08 -05:00
end
2014-01-02 01:58:49 -05:00
def self . batch_set ( user , level , category_ids )
2019-06-24 22:13:27 -04:00
level_num = notification_levels [ level ]
category_ids = Category . where ( id : category_ids ) . pluck ( :id )
2014-01-02 01:58:49 -05:00
2019-06-24 22:13:27 -04:00
changed = false
2014-01-08 01:10:16 -05:00
2019-06-24 22:13:27 -04:00
# Update pre-existing category users
2019-06-24 23:05:43 -04:00
if category_ids . present? &&
CategoryUser
. where ( user_id : user . id , category_id : category_ids )
. where . not ( notification_level : level_num )
. update_all ( notification_level : level_num ) > 0
changed = true
2014-01-02 01:58:49 -05:00
end
2019-06-24 22:13:27 -04:00
# Remove extraneous category users
2019-06-24 23:05:43 -04:00
if CategoryUser
. where ( user_id : user . id , notification_level : level_num )
2019-06-24 22:13:27 -04:00
. where . not ( category_id : category_ids )
. delete_all > 0
2019-06-24 23:05:43 -04:00
changed = true
end
if category_ids . present?
params = { user_id : user . id , level_num : level_num }
sql = << ~ SQL
INSERT INTO category_users ( user_id , category_id , notification_level )
SELECT :user_id , :category_id , :level_num
ON CONFLICT DO NOTHING
SQL
# we could use VALUES here but it would introduce a string
# into the query, plus it is a bit of a micro optimisation
category_ids . each do | category_id |
params [ :category_id ] = category_id
changed = true if DB . exec ( sql , params ) > 0
end
2016-07-07 22:58:18 -04:00
end
if changed
auto_watch ( user_id : user . id )
auto_track ( user_id : user . id )
2014-01-02 01:58:49 -05:00
end
2016-07-07 22:58:18 -04:00
changed
2014-01-02 01:58:49 -05:00
end
2014-04-17 05:17:39 -04:00
def self . set_notification_level_for_category ( user , level , category_id )
record = CategoryUser . where ( user : user , category_id : category_id ) . first
2016-08-02 11:22:02 -04:00
return if record && record . notification_level == level
2016-07-07 22:58:18 -04:00
2014-04-17 05:17:39 -04:00
if record . present?
record . notification_level = level
record . save!
else
2018-10-01 11:41:23 -04:00
begin
CategoryUser . create! ( user : user , category_id : category_id , notification_level : level )
rescue ActiveRecord :: RecordNotUnique
# does not matter
end
2014-04-17 05:17:39 -04:00
end
2016-07-07 22:58:18 -04:00
auto_watch ( user_id : user . id )
auto_track ( user_id : user . id )
2014-04-17 05:17:39 -04:00
end
2016-07-07 22:58:18 -04:00
def self . auto_track ( opts = { } )
2018-06-20 03:48:02 -04:00
builder = DB . build << ~ SQL
UPDATE topic_users tu
SET notification_level = :tracking ,
notifications_reason_id = :auto_track_category
FROM topics t , category_users cu
/ *where* /
SQL
2016-07-07 22:58:18 -04:00
builder . where (
" tu.topic_id = t.id AND
cu . category_id = t . category_id AND
cu . user_id = tu . user_id AND
cu . notification_level = :tracking AND
tu . notification_level = :regular " ,
)
if category_id = opts [ :category_id ]
builder . where ( " t.category_id = :category_id " , category_id : category_id )
end
if topic_id = opts [ :topic_id ]
builder . where ( " tu.topic_id = :topic_id " , topic_id : topic_id )
end
if user_id = opts [ :user_id ]
builder . where ( " tu.user_id = :user_id " , user_id : user_id )
end
2018-06-20 03:48:02 -04:00
builder . exec (
tracking : notification_levels [ :tracking ] ,
regular : notification_levels [ :regular ] ,
2018-12-04 04:48:16 -05:00
auto_track_category : TopicUser . notification_reasons [ :auto_track_category ] ,
2018-06-20 03:48:02 -04:00
)
2015-08-19 16:40:20 -04:00
end
2016-07-07 22:58:18 -04:00
def self . auto_watch ( opts = { } )
2018-06-20 03:48:02 -04:00
builder = DB . build << ~ SQL
UPDATE topic_users tu
SET notification_level =
CASE WHEN should_track THEN :tracking
WHEN should_watch THEN :watching
ELSE notification_level
END ,
notifications_reason_id =
CASE WHEN should_track THEN null
WHEN should_watch THEN :auto_watch_category
ELSE notifications_reason_id
END
FROM (
SELECT tu1 . topic_id ,
tu1 . user_id ,
CASE WHEN
cu . user_id IS NULL AND tu1 . notification_level = :watching AND tu1 . notifications_reason_id = :auto_watch_category THEN true
ELSE false
END should_track ,
CASE WHEN
cu . user_id IS NOT NULL AND tu1 . notification_level in ( :regular , :tracking ) THEN true
2016-07-07 22:58:18 -04:00
ELSE false
2018-06-20 03:48:02 -04:00
END should_watch
2016-07-07 22:58:18 -04:00
2018-06-20 03:48:02 -04:00
FROM topic_users tu1
JOIN topics t ON t . id = tu1 . topic_id
LEFT JOIN category_users cu ON cu . category_id = t . category_id AND cu . user_id = tu1 . user_id AND cu . notification_level = :watching
/ *where2* /
) as X
2016-07-07 22:58:18 -04:00
2018-06-20 03:48:02 -04:00
/ *where* /
SQL
2016-07-07 22:58:18 -04:00
builder . where ( " X.topic_id = tu.topic_id AND X.user_id = tu.user_id " )
builder . where ( " should_watch OR should_track " )
if category_id = opts [ :category_id ]
builder . where2 ( " t.category_id = :category_id " , category_id : category_id )
end
if topic_id = opts [ :topic_id ]
builder . where ( " tu.topic_id = :topic_id " , topic_id : topic_id )
builder . where2 ( " tu1.topic_id = :topic_id " , topic_id : topic_id )
end
if user_id = opts [ :user_id ]
builder . where ( " tu.user_id = :user_id " , user_id : user_id )
builder . where2 ( " tu1.user_id = :user_id " , user_id : user_id )
end
2018-06-20 03:48:02 -04:00
builder . exec (
watching : notification_levels [ :watching ] ,
tracking : notification_levels [ :tracking ] ,
regular : notification_levels [ :regular ] ,
2018-12-04 04:48:16 -05:00
auto_watch_category : TopicUser . notification_reasons [ :auto_watch_category ] ,
2018-06-20 03:48:02 -04:00
)
2014-01-01 20:00:08 -05:00
end
2015-09-02 16:02:31 -04:00
def self . ensure_consistency!
2018-06-19 02:13:14 -04:00
DB . exec << ~ SQL
DELETE FROM category_users
WHERE user_id IN (
SELECT cu . user_id FROM category_users cu
LEFT JOIN users u ON u . id = cu . user_id
WHERE u . id IS NULL
)
SQL
2015-09-02 16:02:31 -04:00
end
2019-11-07 21:58:11 -05:00
def self . default_notification_level
if SiteSetting . mute_all_categories_by_default
notification_levels [ :muted ]
2023-01-09 07:20:10 -05:00
else
2019-11-07 21:58:11 -05:00
notification_levels [ :regular ]
2023-01-09 07:20:10 -05:00
end
2019-11-07 21:58:11 -05:00
end
2021-05-13 19:45:14 -04:00
def self . notification_levels_for ( user )
2021-05-05 19:14:07 -04:00
# Anonymous users have all default categories set to regular tracking,
# except for default muted categories which stay muted.
2021-05-13 19:45:14 -04:00
if user . blank?
2019-11-07 21:58:11 -05:00
notification_levels =
[
SiteSetting . default_categories_watching . split ( " | " ) ,
SiteSetting . default_categories_tracking . split ( " | " ) ,
SiteSetting . default_categories_watching_first_post . split ( " | " ) ,
2022-06-19 23:49:33 -04:00
SiteSetting . default_categories_normal . split ( " | " ) ,
2021-05-05 19:14:07 -04:00
] . flatten . map { | id | [ id . to_i , self . notification_levels [ :regular ] ] }
2023-01-09 07:20:10 -05:00
2021-05-05 19:14:07 -04:00
notification_levels +=
SiteSetting
. default_categories_muted
. split ( " | " )
. map { | id | [ id . to_i , self . notification_levels [ :muted ] ] }
2019-11-07 21:58:11 -05:00
else
2021-05-13 19:45:14 -04:00
notification_levels = CategoryUser . where ( user : user ) . pluck ( :category_id , :notification_level )
2019-11-07 21:58:11 -05:00
end
Hash [ * notification_levels . flatten ]
end
2019-11-13 19:16:13 -05:00
def self . lookup_for ( user , category_ids )
return { } if user . blank? || category_ids . blank?
create_lookup ( CategoryUser . where ( category_id : category_ids , user_id : user . id ) )
end
def self . create_lookup ( category_users )
category_users . each_with_object ( { } ) do | category_user , acc |
acc [ category_user . category_id ] = category_user
end
end
2022-02-16 18:42:02 -05:00
2022-03-01 23:02:09 -05:00
def self . muted_category_ids_query ( user , include_direct : false )
query = Category
query = query . where . not ( parent_category_id : nil ) if ! include_direct
query =
query
2022-02-16 18:42:02 -05:00
. joins ( " LEFT JOIN categories categories2 ON categories2.id = categories.parent_category_id " )
. joins (
" LEFT JOIN category_users ON category_users.category_id = categories.id AND category_users.user_id = #{ user . id } " ,
)
. joins (
" LEFT JOIN category_users category_users2 ON category_users2.category_id = categories2.id AND category_users2.user_id = #{ user . id } " ,
)
2022-03-01 23:02:09 -05:00
direct_category_muted_sql =
" COALESCE(category_users.notification_level, #{ CategoryUser . default_notification_level } ) = #{ CategoryUser . notification_levels [ :muted ] } "
parent_category_muted_sql =
" (category_users.id IS NULL AND COALESCE(category_users2.notification_level, #{ CategoryUser . default_notification_level } ) = #{ notification_levels [ :muted ] } ) "
conditions = [ parent_category_muted_sql ]
conditions . push ( direct_category_muted_sql ) if include_direct
2022-02-16 18:42:02 -05:00
if SiteSetting . max_category_nesting === 3
query =
query . joins (
" LEFT JOIN categories categories3 ON categories3.id = categories2.parent_category_id " ,
) . joins (
" LEFT JOIN category_users category_users3 ON category_users3.category_id = categories3.id AND category_users3.user_id = #{ user . id } " ,
)
2022-03-01 23:02:09 -05:00
grandparent_category_muted_sql =
" (category_users.id IS NULL AND category_users2.id IS NULL AND COALESCE(category_users3.notification_level, #{ CategoryUser . default_notification_level } ) = #{ notification_levels [ :muted ] } ) "
conditions . push ( grandparent_category_muted_sql )
2022-02-16 18:42:02 -05:00
end
2022-03-01 23:02:09 -05:00
query . where ( conditions . join ( " OR " ) )
end
def self . muted_category_ids ( user )
muted_category_ids_query ( user , include_direct : true ) . pluck ( " categories.id " )
end
def self . indirectly_muted_category_ids ( user )
muted_category_ids_query ( user ) . pluck ( " categories.id " )
2022-02-16 18:42:02 -05:00
end
2014-01-01 20:00:08 -05:00
end
2014-02-06 19:07:36 -05:00
# == Schema Information
#
# Table name: category_users
#
# id :integer not null, primary key
# category_id :integer not null
# user_id :integer not null
2021-12-28 17:19:39 -05:00
# notification_level :integer not null
2019-11-19 05:20:14 -05:00
# last_seen_at :datetime
2014-02-06 19:07:36 -05:00
#
2016-01-11 01:30:56 -05:00
# Indexes
#
2023-07-12 19:02:23 -04:00
# idx_category_users_category_id_user_id (category_id,user_id) UNIQUE
# idx_category_users_user_id_category_id (user_id,category_id) UNIQUE
# index_category_users_on_category_id_and_notification_level (category_id,notification_level)
# index_category_users_on_user_id_and_last_seen_at (user_id,last_seen_at)
2016-01-11 01:30:56 -05:00
#