2013-04-19 02:27:04 +08:00
require_dependency 'email'
2013-02-06 03:16:51 +08:00
require_dependency 'email_token'
require_dependency 'trust_level'
2013-03-06 20:12:16 +08:00
require_dependency 'pbkdf2'
2013-05-11 04:58:23 +08:00
require_dependency 'discourse'
2013-06-06 04:00:45 +08:00
require_dependency 'post_destroyer'
2013-06-06 22:40:10 +08:00
require_dependency 'user_name_suggester'
2013-06-23 12:32:46 +08:00
require_dependency 'pretty_text'
2013-11-23 02:18:45 +08:00
require_dependency 'url_helper'
2014-05-30 12:17:35 +08:00
require_dependency 'letter_avatar'
2014-06-17 08:46:30 +08:00
require_dependency 'promotion'
2013-02-06 03:16:51 +08:00
class User < ActiveRecord :: Base
2013-06-07 06:07:59 +08:00
include Roleable
2014-04-28 16:31:51 +08:00
include HasCustomFields
2013-06-07 06:07:59 +08:00
2013-02-06 03:16:51 +08:00
has_many :posts
2013-09-04 05:19:29 +08:00
has_many :notifications , dependent : :destroy
has_many :topic_users , dependent : :destroy
2013-02-06 03:16:51 +08:00
has_many :topics
2013-04-12 04:04:20 +08:00
has_many :user_open_ids , dependent : :destroy
2013-09-04 05:19:29 +08:00
has_many :user_actions , dependent : :destroy
has_many :post_actions , dependent : :destroy
2014-07-14 15:40:01 +08:00
has_many :user_badges , - > { where ( 'user_badges.badge_id IN (SELECT id FROM badges where enabled)' ) } , dependent : :destroy
2014-07-09 13:31:49 +08:00
has_many :badges , through : :user_badges
2014-11-29 03:20:43 +08:00
has_many :email_logs , dependent : :delete_all
2013-02-06 03:16:51 +08:00
has_many :post_timings
2013-09-04 05:19:29 +08:00
has_many :topic_allowed_users , dependent : :destroy
2013-02-06 03:16:51 +08:00
has_many :topics_allowed , through : :topic_allowed_users , source : :topic
2013-09-04 05:19:29 +08:00
has_many :email_tokens , dependent : :destroy
2013-02-06 03:16:51 +08:00
has_many :views
2013-09-04 05:19:29 +08:00
has_many :user_visits , dependent : :destroy
has_many :invites , dependent : :destroy
has_many :topic_links , dependent : :destroy
2013-11-23 01:29:07 +08:00
has_many :uploads
2014-09-08 23:11:56 +08:00
has_many :warnings
2013-05-11 04:58:23 +08:00
2014-05-22 15:37:02 +08:00
has_one :user_avatar , dependent : :destroy
2013-06-24 22:03:51 +08:00
has_one :facebook_user_info , dependent : :destroy
2013-04-12 04:04:20 +08:00
has_one :twitter_user_info , dependent : :destroy
has_one :github_user_info , dependent : :destroy
2014-09-25 13:50:54 +08:00
has_one :google_user_info , dependent : :destroy
2013-08-18 12:43:59 +08:00
has_one :oauth2_user_info , dependent : :destroy
2013-09-12 02:50:26 +08:00
has_one :user_stat , dependent : :destroy
2014-06-10 13:19:08 +08:00
has_one :user_profile , dependent : :destroy , inverse_of : :user
2014-02-25 11:30:49 +08:00
has_one :single_sign_on_record , dependent : :destroy
2013-02-06 03:16:51 +08:00
belongs_to :approved_by , class_name : 'User'
2014-04-24 10:42:04 +08:00
belongs_to :primary_group , class_name : 'Group'
2013-02-06 03:16:51 +08:00
2013-09-04 05:19:29 +08:00
has_many :group_users , dependent : :destroy
2013-04-29 14:33:24 +08:00
has_many :groups , through : :group_users
has_many :secure_categories , through : :groups , source : :categories
2015-01-09 07:35:52 +08:00
has_many :group_managers , dependent : :destroy
has_many :managed_groups , through : :group_managers , source : :group
2015-03-24 08:55:22 +08:00
has_many :muted_user_records , class_name : 'MutedUser'
has_many :muted_users , through : :muted_user_records
2013-09-04 05:19:29 +08:00
has_one :user_search_data , dependent : :destroy
2013-10-23 03:53:08 +08:00
has_one :api_key , dependent : :destroy
2013-05-23 03:33:33 +08:00
2014-05-22 15:37:02 +08:00
belongs_to :uploaded_avatar , class_name : 'Upload'
2013-08-14 04:08:29 +08:00
2013-11-15 23:27:43 +08:00
delegate :last_sent_email_address , :to = > :email_logs
2015-01-30 03:41:41 +08:00
before_validation :strip_downcase_email
2014-07-14 22:16:24 +08:00
2013-02-06 03:16:51 +08:00
validates_presence_of :username
validate :username_validator
2013-07-26 01:01:27 +08:00
validates :email , presence : true , uniqueness : true
validates :email , email : true , if : :email_changed?
2013-02-06 03:16:51 +08:00
validate :password_validator
2015-04-03 05:07:56 +08:00
validates :name , user_full_name : true , if : :name_changed?
2013-10-22 02:49:51 +08:00
validates :ip_address , allowed_ip_address : { on : :create , message : :signup_not_allowed }
2013-02-06 03:16:51 +08:00
after_initialize :add_trust_level
2015-08-22 02:39:21 +08:00
before_create :set_default_user_preferences
2013-02-06 03:16:51 +08:00
2013-02-06 10:44:49 +08:00
after_create :create_email_token
2013-09-12 02:50:26 +08:00
after_create :create_user_stat
2014-05-28 01:54:04 +08:00
after_create :create_user_profile
2014-06-17 08:46:30 +08:00
after_create :ensure_in_trust_level_group
2015-01-24 01:25:43 +08:00
after_create :automatic_group_membership
2015-08-22 02:39:21 +08:00
after_create :set_default_categories_preferences
2014-08-14 04:17:16 +08:00
before_save :update_username_lower
before_save :ensure_password_is_hashed
after_save :update_tracked_topics
after_save :clear_global_notice_if_needed
2014-05-22 15:37:02 +08:00
after_save :refresh_avatar
2014-07-23 09:42:24 +08:00
after_save :badge_grant
2015-06-06 01:50:06 +08:00
after_save :expire_old_email_tokens
2013-02-06 03:16:51 +08:00
2013-09-04 05:19:29 +08:00
before_destroy do
# These tables don't have primary keys, so destroying them with activerecord is tricky:
PostTiming . delete_all ( user_id : self . id )
2014-08-04 17:07:55 +08:00
TopicViewItem . delete_all ( user_id : self . id )
2013-09-04 05:19:29 +08:00
end
2013-02-06 03:16:51 +08:00
# Whether we need to be sending a system message after creation
attr_accessor :send_welcome_message
# This is just used to pass some information into the serializer
attr_accessor :notification_channel_position
2014-08-14 04:17:16 +08:00
# set to true to optimize creation and save for imports
attr_accessor :import_mode
2015-05-11 07:10:10 +08:00
# excluding fake users like the system user or anonymous users
scope :real , - > { where ( 'id > 0' ) . where ( ' NOT EXISTS (
SELECT 1
FROM user_custom_fields ucf
WHERE
ucf . user_id = users . id AND
ucf . name = ? AND
ucf . value :: int > 0
) ', ' master_id ' ) }
2013-03-29 14:29:58 +08:00
2015-04-24 01:33:29 +08:00
scope :staff , - > { where ( " admin OR moderator " ) }
2014-09-04 05:50:19 +08:00
# TODO-PERF: There is no indexes on any of these
# and NotifyMailingListSubscribers does a select-all-and-loop
# may want to create an index on (active, blocked, suspended_till, mailing_list_mode)?
scope :blocked , - > { where ( blocked : true ) }
scope :not_blocked , - > { where ( blocked : false ) }
scope :suspended , - > { where ( 'suspended_till IS NOT NULL AND suspended_till > ?' , Time . zone . now ) }
scope :not_suspended , - > { where ( 'suspended_till IS NULL OR suspended_till <= ?' , Time . zone . now ) }
scope :activated , - > { where ( active : true ) }
2013-02-14 14:32:58 +08:00
module NewTopicDuration
2013-02-26 00:42:20 +08:00
ALWAYS = - 1
2013-02-14 14:32:58 +08:00
LAST_VISIT = - 2
end
2014-03-08 01:58:53 +08:00
2014-09-12 03:22:11 +08:00
def self . max_password_length
200
end
2013-02-06 03:16:51 +08:00
def self . username_length
2014-07-17 00:25:24 +08:00
SiteSetting . min_username_length . to_i .. SiteSetting . max_username_length . to_i
2013-02-06 03:16:51 +08:00
end
2014-02-07 06:34:48 +08:00
def custom_groups
2014-04-23 04:43:46 +08:00
groups . where ( automatic : false , visible : true )
2014-02-07 06:34:48 +08:00
end
2013-06-06 22:40:10 +08:00
def self . username_available? ( username )
lower = username . downcase
2015-08-26 04:33:50 +08:00
User . where ( username_lower : lower ) . blank? && ! SiteSetting . reserved_usernames . split ( " | " ) . include? ( username )
2013-02-06 03:16:51 +08:00
end
2015-02-06 11:38:51 +08:00
def effective_locale
if SiteSetting . allow_user_locale && self . locale . present?
self . locale
else
SiteSetting . default_locale
end
end
2013-04-01 00:51:13 +08:00
EMAIL = %r{ ([^@]+)@([^ \ .]+) }
2013-04-13 06:46:55 +08:00
def self . new_from_params ( params )
user = User . new
user . name = params [ :name ]
user . email = params [ :email ]
user . password = params [ :password ]
user . username = params [ :username ]
user
end
2013-02-06 03:16:51 +08:00
def self . suggest_name ( email )
2015-08-20 17:59:28 +08:00
return " " if email . blank?
name = email . split ( / [@ \ +] / ) [ 0 ] . gsub ( " . " , " " )
2013-02-28 21:08:56 +08:00
name . titleize
2013-02-06 03:16:51 +08:00
end
2013-04-29 14:33:24 +08:00
def self . find_by_username_or_email ( username_or_email )
2013-10-28 13:29:07 +08:00
if username_or_email . include? ( '@' )
find_by_email ( username_or_email )
2013-06-19 08:31:19 +08:00
else
2013-10-28 13:29:07 +08:00
find_by_username ( username_or_email )
2013-06-19 08:31:19 +08:00
end
2013-04-29 14:33:24 +08:00
end
2013-10-24 15:59:58 +08:00
def self . find_by_email ( email )
2014-05-06 21:41:59 +08:00
find_by ( email : Email . downcase ( email ) )
2013-10-24 15:59:58 +08:00
end
def self . find_by_username ( username )
2014-05-06 21:41:59 +08:00
find_by ( username_lower : username . downcase )
2013-10-24 15:59:58 +08:00
end
2013-12-20 02:45:55 +08:00
2013-04-29 14:33:24 +08:00
def enqueue_welcome_message ( message_type )
return unless SiteSetting . send_welcome_message?
Jobs . enqueue ( :send_system_message , user_id : id , message_type : message_type )
end
2015-01-17 06:30:46 +08:00
def change_username ( new_username , actor = nil )
2015-03-07 05:44:54 +08:00
UsernameChanger . change ( self , new_username , actor )
2013-02-06 03:16:51 +08:00
end
2013-09-13 05:46:43 +08:00
def created_topic_count
2014-07-29 01:17:37 +08:00
stat = user_stat || create_user_stat
stat . topic_count
2013-09-13 05:46:43 +08:00
end
2013-02-27 00:27:59 +08:00
2014-07-29 01:17:37 +08:00
alias_method :topic_count , :created_topic_count
2013-02-06 03:16:51 +08:00
# tricky, we need our bus to be subscribed from the right spot
def sync_notification_channel_position
@unread_notifications_by_type = nil
2015-05-04 10:21:00 +08:00
self . notification_channel_position = MessageBus . last_id ( " /notification/ #{ id } " )
2013-02-06 03:16:51 +08:00
end
def invited_by
used_invite = invites . where ( " redeemed_at is not null " ) . includes ( :invited_by ) . first
2013-02-28 21:08:56 +08:00
used_invite . try ( :invited_by )
2013-02-06 03:16:51 +08:00
end
# Approve this user
2013-06-27 01:24:30 +08:00
def approve ( approved_by , send_mail = true )
2013-02-06 03:16:51 +08:00
self . approved = true
2013-07-11 09:21:39 +08:00
2014-03-27 03:20:41 +08:00
if approved_by . is_a? ( Fixnum )
2013-07-11 09:21:39 +08:00
self . approved_by_id = approved_by
else
self . approved_by = approved_by
end
2013-02-06 03:16:51 +08:00
self . approved_at = Time . now
2013-06-06 11:16:31 +08:00
2013-06-27 01:24:30 +08:00
send_approval_email if save and send_mail
2013-02-06 03:16:51 +08:00
end
def self . email_hash ( email )
2013-02-06 10:44:49 +08:00
Digest :: MD5 . hexdigest ( email . strip . downcase )
2013-02-06 03:16:51 +08:00
end
def email_hash
2013-02-28 21:08:56 +08:00
User . email_hash ( email )
2013-02-06 03:16:51 +08:00
end
def reload
2015-04-17 14:01:20 +08:00
@unread_notifications = nil
2014-10-13 18:26:30 +08:00
@unread_total_notifications = nil
2013-05-16 14:37:47 +08:00
@unread_pms = nil
2013-02-06 03:16:51 +08:00
super
end
def unread_private_messages
2015-04-17 14:01:20 +08:00
@unread_pms || =
begin
# perf critical, much more efficient than AR
sql = "
SELECT COUNT ( * ) FROM notifications n
LEFT JOIN topics t ON n . topic_id = t . id
WHERE
t . deleted_at IS NULL AND
n . notification_type = :type AND
n . user_id = :user_id AND
NOT read "
User . exec_sql ( sql , user_id : id ,
type : Notification . types [ :private_message ] )
. getvalue ( 0 , 0 ) . to_i
end
2013-02-06 03:16:51 +08:00
end
def unread_notifications
2015-04-17 14:01:20 +08:00
@unread_notifications || =
begin
# perf critical, much more efficient than AR
sql = "
SELECT COUNT ( * ) FROM notifications n
LEFT JOIN topics t ON n . topic_id = t . id
WHERE
t . deleted_at IS NULL AND
n . notification_type < > :pm AND
n . user_id = :user_id AND
NOT read AND
n . id > :seen_notification_id "
User . exec_sql ( sql , user_id : id ,
seen_notification_id : seen_notification_id ,
pm : Notification . types [ :private_message ] )
. getvalue ( 0 , 0 ) . to_i
end
2013-02-06 03:16:51 +08:00
end
2013-02-06 10:44:49 +08:00
2014-10-13 18:26:30 +08:00
def total_unread_notifications
@unread_total_notifications || = notifications . where ( " read = false " ) . count
end
2013-02-06 03:16:51 +08:00
def saw_notification_id ( notification_id )
2014-10-02 14:03:56 +08:00
User . where ( " id = ? and seen_notification_id < ? " , id , notification_id )
2013-08-10 00:12:56 +08:00
. update_all [ " seen_notification_id = ? " , notification_id ]
2014-10-02 14:03:56 +08:00
2015-08-13 13:39:58 +08:00
# mark all "badge granted" and "invite accepted" notifications read
Notification . where ( 'user_id = ? AND NOT read AND notification_type IN (?)' , id , [ Notification . types [ :granted_badge ] , Notification . types [ :invitee_accepted ] ] )
2014-10-02 14:03:56 +08:00
. update_all [ " read = ? " , true ]
2013-02-06 03:16:51 +08:00
end
def publish_notifications_state
2015-05-04 10:21:00 +08:00
MessageBus . publish ( " /notification/ #{ id } " ,
2013-06-06 22:40:10 +08:00
{ unread_notifications : unread_notifications ,
2014-10-13 18:26:30 +08:00
unread_private_messages : unread_private_messages ,
total_unread_notifications : total_unread_notifications } ,
2013-06-06 22:40:10 +08:00
user_ids : [ id ] # only publish the notification to this user
)
2013-02-06 03:16:51 +08:00
end
# A selection of people to autocomplete on @mention
def self . mentionable_usernames
2013-02-06 10:44:49 +08:00
User . select ( :username ) . order ( 'last_posted_at desc' ) . limit ( 20 )
end
2013-02-06 03:16:51 +08:00
def password = ( password )
2013-02-06 10:44:49 +08:00
# special case for passwordless accounts
2015-06-06 01:09:02 +08:00
unless password . blank?
@raw_password = password
self . auth_token = nil
end
2013-02-06 03:16:51 +08:00
end
2013-12-20 04:12:03 +08:00
def password
'' # so that validator doesn't complain that a password attribute doesn't exist
end
2013-02-13 04:42:04 +08:00
# Indicate that this is NOT a passwordless account for the purposes of validation
2013-02-28 21:08:56 +08:00
def password_required!
2013-02-13 04:42:04 +08:00
@password_required = true
end
2013-12-20 04:12:03 +08:00
def password_required?
! ! @password_required
end
2014-01-22 01:42:20 +08:00
def has_password?
password_hash . present?
end
2013-12-20 04:12:03 +08:00
def password_validator
PasswordValidator . new ( attributes : :password ) . validate_each ( self , :password , @raw_password )
end
2013-02-06 03:16:51 +08:00
def confirm_password? ( password )
2013-02-28 21:08:56 +08:00
return false unless password_hash && salt
self . password_hash == hash_password ( password , salt )
2013-02-06 03:16:51 +08:00
end
2013-10-12 01:33:23 +08:00
2015-03-26 13:48:36 +08:00
def first_day_user?
! staff? &&
trust_level < TrustLevel [ 2 ] &&
created_at > = 24 . hours . ago
end
2013-10-12 01:33:23 +08:00
def new_user?
2015-03-26 13:04:32 +08:00
( created_at > = 24 . hours . ago || trust_level == TrustLevel [ 0 ] ) &&
trust_level < TrustLevel [ 2 ] &&
! staff?
2013-10-12 01:33:23 +08:00
end
2013-02-06 03:16:51 +08:00
2013-02-12 13:41:04 +08:00
def seen_before?
last_seen_at . present?
end
2015-07-08 00:31:07 +08:00
def create_visit_record! ( date , opts = { } )
user_stat . update_column ( :days_visited , user_stat . days_visited + 1 )
user_visits . create! ( visited_at : date , posts_read : opts [ :posts_read ] || 0 , mobile : opts [ :mobile ] || false )
end
2014-01-25 04:19:20 +08:00
def visit_record_for ( date )
2014-05-06 21:41:59 +08:00
user_visits . find_by ( visited_at : date )
2013-02-12 13:41:04 +08:00
end
def update_visit_record! ( date )
2014-01-25 04:19:20 +08:00
create_visit_record! ( date ) unless visit_record_for ( date )
end
2015-07-08 00:31:07 +08:00
def update_posts_read! ( num_posts , opts = { } )
now = opts [ :at ] || Time . zone . now
_retry = opts [ :retry ] || false
2014-01-25 04:19:20 +08:00
if user_visit = visit_record_for ( now . to_date )
user_visit . posts_read += num_posts
2015-07-08 00:31:07 +08:00
user_visit . mobile = true if opts [ :mobile ]
2014-01-25 04:19:20 +08:00
user_visit . save
user_visit
else
2015-06-01 09:55:07 +08:00
begin
2015-07-08 00:31:07 +08:00
create_visit_record! ( now . to_date , posts_read : num_posts , mobile : opts . fetch ( :mobile , false ) )
2015-06-01 09:55:07 +08:00
rescue ActiveRecord :: RecordNotUnique
if ! _retry
2015-08-24 08:28:38 +08:00
update_posts_read! ( num_posts , opts . merge ( retry : true ) )
2015-06-01 09:55:07 +08:00
else
raise
end
end
2013-02-12 13:41:04 +08:00
end
end
2013-02-24 18:42:04 +08:00
def update_ip_address! ( new_ip_address )
2013-03-09 09:24:10 +08:00
unless ip_address == new_ip_address || new_ip_address . blank?
2013-02-24 19:56:08 +08:00
update_column ( :ip_address , new_ip_address )
2013-02-24 18:42:04 +08:00
end
end
2013-10-24 05:24:50 +08:00
def update_last_seen! ( now = Time . zone . now )
now_date = now . to_date
2013-02-06 03:16:51 +08:00
# Only update last seen once every minute
2013-10-24 05:24:50 +08:00
redis_key = " user: #{ id } : #{ now_date } "
return unless $redis . setnx ( redis_key , " 1 " )
2013-02-06 03:16:51 +08:00
2013-10-24 05:24:50 +08:00
$redis . expire ( redis_key , SiteSetting . active_user_rate_limit_secs )
update_previous_visit ( now )
# using update_column to avoid the AR transaction
update_column ( :last_seen_at , now )
2013-02-06 03:16:51 +08:00
end
2013-08-14 04:08:29 +08:00
def self . gravatar_template ( email )
2013-02-06 03:16:51 +08:00
email_hash = self . email_hash ( email )
2014-04-25 17:40:38 +08:00
" //www.gravatar.com/avatar/ #{ email_hash } .png?s={size}&r=pg&d=identicon "
2013-02-06 03:16:51 +08:00
end
2013-03-09 04:58:37 +08:00
# Don't pass this up to the client - it's meant for server side use
2013-08-14 04:08:29 +08:00
# This is used in
# - self oneboxes in open graph data
# - emails
2013-03-09 04:58:37 +08:00
def small_avatar_url
2014-05-22 15:37:02 +08:00
avatar_template_url . gsub ( " {size} " , " 45 " )
2013-03-09 04:58:37 +08:00
end
2014-05-22 15:37:02 +08:00
def avatar_template_url
2015-06-12 18:02:36 +08:00
UrlHelper . schemaless UrlHelper . absolute avatar_template
2013-09-11 03:18:22 +08:00
end
2015-06-27 01:37:50 +08:00
def self . default_template ( username )
if SiteSetting . default_avatars . present?
split_avatars = SiteSetting . default_avatars . split ( " \n " )
if split_avatars . present?
hash = username . each_char . reduce ( 0 ) do | result , char |
[ ( ( result << 5 ) - result ) + char . ord ] . pack ( 'L' ) . unpack ( 'l' ) . first
end
avatar_template = split_avatars [ hash . abs % split_avatars . size ]
end
else
" #{ Discourse . base_uri } /letter_avatar/ #{ username . downcase } /{size}/ #{ LetterAvatar . version } .png "
end
end
2014-05-22 15:37:02 +08:00
def self . avatar_template ( username , uploaded_avatar_id )
2015-06-27 01:37:50 +08:00
return default_template ( username ) if ! uploaded_avatar_id
2014-05-22 15:37:02 +08:00
username || = " "
2015-05-30 00:51:17 +08:00
hostname = RailsMultisite :: ConnectionManagement . current_hostname
UserAvatar . local_avatar_template ( hostname , username . downcase , uploaded_avatar_id )
2014-05-22 15:37:02 +08:00
end
2014-05-30 12:17:35 +08:00
def self . letter_avatar_template ( username )
2015-04-29 08:58:46 +08:00
" #{ Discourse . base_uri } /letter_avatar/ #{ username . downcase } /{size}/ #{ LetterAvatar . version } .png "
2014-05-30 12:17:35 +08:00
end
2013-02-06 03:16:51 +08:00
def avatar_template
2014-05-22 15:37:02 +08:00
self . class . avatar_template ( username , uploaded_avatar_id )
2013-02-06 03:16:51 +08:00
end
# The following count methods are somewhat slow - definitely don't use them in a loop.
2013-03-06 15:52:24 +08:00
# They might need to be denormalized
2013-02-06 03:16:51 +08:00
def like_count
2013-02-28 21:08:56 +08:00
UserAction . where ( user_id : id , action_type : UserAction :: WAS_LIKED ) . count
2013-02-06 03:16:51 +08:00
end
2014-08-23 03:23:10 +08:00
def like_given_count
UserAction . where ( user_id : id , action_type : UserAction :: LIKE ) . count
2014-08-23 02:37:00 +08:00
end
2013-02-06 03:16:51 +08:00
def post_count
2014-07-29 01:17:37 +08:00
stat = user_stat || create_user_stat
stat . post_count
2014-02-21 01:29:40 +08:00
end
2013-02-06 03:16:51 +08:00
def flags_given_count
2013-03-01 20:07:44 +08:00
PostAction . where ( user_id : id , post_action_type_id : PostActionType . flag_types . values ) . count
2013-02-06 03:16:51 +08:00
end
2014-09-08 23:11:56 +08:00
def warnings_received_count
warnings . count
end
2013-02-06 03:16:51 +08:00
def flags_received_count
2013-03-01 20:07:44 +08:00
posts . includes ( :post_actions ) . where ( 'post_actions.post_action_type_id' = > PostActionType . flag_types . values ) . count
2013-02-06 03:16:51 +08:00
end
def private_topics_count
topics_allowed . where ( archetype : Archetype . private_message ) . count
end
2013-12-20 02:45:55 +08:00
def posted_too_much_in_topic? ( topic_id )
2014-01-03 01:57:40 +08:00
2014-04-30 00:59:14 +08:00
# Does not apply to staff, non-new members or your own topics
return false if staff? ||
2014-09-05 13:20:39 +08:00
( trust_level != TrustLevel [ 0 ] ) ||
2014-04-30 00:59:14 +08:00
Topic . where ( id : topic_id , user_id : id ) . exists?
2014-01-03 01:57:40 +08:00
2014-04-30 00:59:14 +08:00
last_action_in_topic = UserAction . last_action_in_topic ( id , topic_id )
since_reply = Post . where ( user_id : id , topic_id : topic_id )
since_reply = since_reply . where ( 'id > ?' , last_action_in_topic ) if last_action_in_topic
( since_reply . count > = SiteSetting . newuser_max_replies_per_topic )
2013-12-20 02:45:55 +08:00
end
2013-02-07 15:11:56 +08:00
def delete_all_posts! ( guardian )
raise Discourse :: InvalidAccess unless guardian . can_delete_all_posts? self
2013-02-07 23:45:24 +08:00
2015-04-25 04:04:44 +08:00
QueuedPost . where ( user_id : id ) . delete_all
2013-02-07 15:11:56 +08:00
posts . order ( " post_number desc " ) . each do | p |
2013-06-06 04:00:45 +08:00
PostDestroyer . new ( guardian . user , p ) . destroy
2013-02-07 15:11:56 +08:00
end
end
2013-11-08 02:53:32 +08:00
def suspended?
suspended_till && suspended_till > DateTime . now
2013-02-06 03:16:51 +08:00
end
2013-11-08 02:53:32 +08:00
def suspend_record
UserHistory . for ( self , :suspend_user ) . order ( 'id DESC' ) . first
2013-11-01 22:47:03 +08:00
end
2013-11-08 02:53:32 +08:00
def suspend_reason
suspend_record . try ( :details ) if suspended?
2013-11-01 22:47:03 +08:00
end
2013-02-06 03:16:51 +08:00
# Use this helper to determine if the user has a particular trust level.
# Takes into account admin, etc.
2013-02-06 10:44:49 +08:00
def has_trust_level? ( level )
2014-09-05 13:20:39 +08:00
raise " Invalid trust level #{ level } " unless TrustLevel . valid? ( level )
2013-03-20 12:05:19 +08:00
admin? || moderator? || TrustLevel . compare ( trust_level , level )
2013-02-06 03:16:51 +08:00
end
2013-03-20 07:51:39 +08:00
# a touch faster than automatic
2013-04-01 00:51:13 +08:00
def admin?
2013-03-20 07:51:39 +08:00
admin
end
2013-02-06 03:16:51 +08:00
def guardian
Guardian . new ( self )
end
2013-02-08 07:23:41 +08:00
def username_format_validator
2013-07-07 19:05:18 +08:00
UsernameValidator . perform_validation ( self , 'username' )
2013-02-08 07:23:41 +08:00
end
2013-02-12 00:18:26 +08:00
def email_confirmed?
2013-02-28 21:08:56 +08:00
email_tokens . where ( email : email , confirmed : true ) . present? || email_tokens . empty?
2013-02-12 00:18:26 +08:00
end
2013-05-08 09:58:34 +08:00
def activate
email_token = self . email_tokens . active . first
if email_token
EmailToken . confirm ( email_token . token )
else
self . active = true
save
end
end
def deactivate
self . active = false
save
end
2014-06-17 08:46:30 +08:00
def change_trust_level! ( level , opts = nil )
Promotion . new ( self ) . change_trust_level! ( level , opts )
end
2013-02-14 14:32:58 +08:00
def treat_as_new_topic_start_date
2015-08-26 14:19:21 +08:00
duration = new_topic_duration_minutes || SiteSetting . default_other_new_topic_duration_minutes . to_i
2014-03-04 05:11:59 +08:00
[ case duration
2013-06-06 22:40:10 +08:00
when User :: NewTopicDuration :: ALWAYS
2014-03-04 05:11:59 +08:00
created_at
2013-06-06 22:40:10 +08:00
when User :: NewTopicDuration :: LAST_VISIT
2014-03-04 03:31:29 +08:00
previous_visit_at || user_stat . new_since
2013-06-06 22:40:10 +08:00
else
duration . minutes . ago
2014-03-04 05:11:59 +08:00
end , user_stat . new_since ] . max
2013-02-14 14:32:58 +08:00
end
2013-02-08 07:23:41 +08:00
2013-02-22 02:20:00 +08:00
def readable_name
2013-03-07 04:17:07 +08:00
return " #{ name } ( #{ username } ) " if name . present? && name != username
username
2013-02-22 02:20:00 +08:00
end
2014-04-16 18:22:21 +08:00
def badge_count
2014-07-16 14:21:46 +08:00
user_badges . select ( 'distinct badge_id' ) . count
2014-04-16 18:22:21 +08:00
end
2014-04-16 18:11:11 +08:00
def featured_user_badges
2014-07-03 15:29:44 +08:00
user_badges
. joins ( :badge )
2014-10-02 13:55:33 +08:00
. order ( " CASE WHEN badges.id = (SELECT MAX(ub2.badge_id) FROM user_badges ub2
WHERE ub2 . badge_id IN ( #{Badge.trust_level_badge_ids.join(",")}) AND
ub2 . user_id = #{self.id}) THEN 1 ELSE 0 END DESC")
2014-07-03 15:29:44 +08:00
. order ( 'badges.badge_type_id ASC, badges.grant_count ASC' )
. includes ( :user , :granted_by , badge : :badge_type )
. where ( " user_badges.id in (select min(u2.id)
from user_badges u2 where u2 . user_id = ? group by u2 . badge_id ) " , id)
. limit ( 3 )
2014-04-16 18:11:11 +08:00
end
2014-11-06 02:11:23 +08:00
def self . count_by_signup_date ( start_date , end_date )
2014-12-30 22:06:15 +08:00
where ( 'created_at >= ? and created_at <= ?' , start_date , end_date ) . group ( 'date(created_at)' ) . order ( 'date(created_at)' ) . count
2013-03-08 00:07:59 +08:00
end
2013-04-05 13:48:38 +08:00
2013-04-29 14:33:24 +08:00
def secure_category_ids
2014-02-07 11:11:52 +08:00
cats = self . admin? ? Category . where ( read_restricted : true ) : secure_categories . references ( :categories )
2013-09-10 12:29:02 +08:00
cats . pluck ( 'categories.id' ) . sort
2013-04-29 14:33:24 +08:00
end
2013-07-14 09:24:16 +08:00
def topic_create_allowed_category_ids
Category . topic_create_allowed ( self . id ) . select ( :id )
end
2013-09-13 05:46:43 +08:00
2013-05-11 04:58:23 +08:00
# Flag all posts from a user as spam
def flag_linked_posts_as_spam
admin = Discourse . system_user
topic_links . includes ( :post ) . each do | tl |
begin
2013-10-18 03:08:11 +08:00
PostAction . act ( admin , tl . post , PostActionType . types [ :spam ] , message : I18n . t ( 'flag_reason.spam_hosts' ) )
2013-05-11 04:58:23 +08:00
rescue PostAction :: AlreadyActed
# If the user has already acted, just ignore it
end
end
end
2013-05-13 16:04:03 +08:00
2013-08-14 04:08:29 +08:00
def has_uploaded_avatar
uploaded_avatar . present?
end
2013-05-24 18:58:26 +08:00
2013-10-23 03:53:08 +08:00
def generate_api_key ( created_by )
if api_key . present?
api_key . regenerate! ( created_by )
api_key
else
ApiKey . create ( user : self , key : SecureRandom . hex ( 32 ) , created_by : created_by )
end
end
def revoke_api_key
ApiKey . where ( user_id : self . id ) . delete_all
end
2013-11-15 23:27:43 +08:00
def find_email
last_sent_email_address || email
end
2014-09-25 08:19:26 +08:00
def tl3_requirements
2014-09-05 13:20:39 +08:00
@lq || = TrustLevel3Requirements . new ( self )
2014-01-23 06:09:56 +08:00
end
2014-09-25 08:19:26 +08:00
def on_tl3_grace_period?
2014-09-14 04:55:26 +08:00
UserHistory . for ( self , :auto_trust_level_change )
. where ( 'created_at >= ?' , SiteSetting . tl3_promotion_min_duration . to_i . days . ago )
. where ( previous_value : TrustLevel [ 2 ] . to_s )
. where ( new_value : TrustLevel [ 3 ] . to_s )
. exists?
end
2014-03-08 01:58:53 +08:00
def should_be_redirected_to_top
redirected_to_top_reason . present?
end
def redirected_to_top_reason
2014-04-01 03:53:38 +08:00
# redirect is enabled
return unless SiteSetting . redirect_users_to_top_page
2014-03-08 01:58:53 +08:00
# top must be in the top_menu
return unless SiteSetting . top_menu =~ / top /i
# there should be enough topics
return unless SiteSetting . has_enough_topics_to_redirect_to_top
2014-05-06 01:00:40 +08:00
if ! seen_before? || ( trust_level == 0 && ! redirected_to_top_yet? )
update_last_redirected_to_top!
return I18n . t ( 'redirected_to_top_reasons.new_user' )
elsif last_seen_at < 1 . month . ago
update_last_redirected_to_top!
return I18n . t ( 'redirected_to_top_reasons.not_seen_in_a_month' )
end
# no reason
2014-03-08 01:58:53 +08:00
nil
end
2014-05-06 01:00:40 +08:00
def redirected_to_top_yet?
last_redirected_to_top_at . present?
end
def update_last_redirected_to_top!
key = " user: #{ id } :update_last_redirected_to_top "
delay = SiteSetting . active_user_rate_limit_secs
# only update last_redirected_to_top_at once every minute
return unless $redis . setnx ( key , " 1 " )
$redis . expire ( key , delay )
# delay the update
Jobs . enqueue_in ( delay / 2 , :update_top_redirection , user_id : self . id , redirected_at : Time . zone . now )
end
2014-05-22 15:37:02 +08:00
def refresh_avatar
2014-08-14 04:17:16 +08:00
return if @import_mode
2014-05-28 14:54:21 +08:00
avatar = user_avatar || create_user_avatar
2014-05-22 15:37:02 +08:00
2014-05-28 14:54:21 +08:00
if SiteSetting . automatically_download_gravatars? && ! avatar . last_gravatar_download_attempt
2014-12-16 05:10:27 +08:00
Jobs . enqueue ( :update_gravatar , user_id : self . id , avatar_id : avatar . id )
2014-07-03 15:29:44 +08:00
end
2015-03-30 18:31:10 +08:00
2015-04-24 17:14:10 +08:00
# mark all the user's quoted posts as "needing a rebake"
Post . rebake_all_quoted_posts ( self . id ) if self . uploaded_avatar_id_changed?
2014-07-03 15:29:44 +08:00
end
2014-07-29 01:17:37 +08:00
def first_post_created_at
user_stat . try ( :first_post_created_at )
end
2014-09-25 13:50:54 +08:00
def associated_accounts
result = [ ]
2014-09-30 04:31:05 +08:00
result << " Twitter( #{ twitter_user_info . screen_name } ) " if twitter_user_info
result << " Facebook( #{ facebook_user_info . username } ) " if facebook_user_info
result << " Google( #{ google_user_info . email } ) " if google_user_info
result << " Github( #{ github_user_info . screen_name } ) " if github_user_info
2014-09-25 13:50:54 +08:00
user_open_ids . each do | oid |
result << " OpenID #{ oid . url [ 0 .. 20 ] } ...( #{ oid . email } ) "
end
result . empty? ? I18n . t ( " user.no_accounts_associated " ) : result . join ( " , " )
end
2014-09-27 02:48:34 +08:00
def user_fields
return @user_fields if @user_fields
user_field_ids = UserField . pluck ( :id )
if user_field_ids . present?
@user_fields = { }
user_field_ids . each do | fid |
@user_fields [ fid . to_s ] = custom_fields [ " user_field_ #{ fid } " ]
end
end
@user_fields
end
2014-10-08 07:26:18 +08:00
def title = ( val )
write_attribute ( :title , val )
if ! new_record? && user_profile
user_profile . update_column ( :badge_granted_title , false )
end
end
2015-02-20 01:11:07 +08:00
def number_of_deleted_posts
Post . with_deleted
. where ( user_id : self . id )
. where . not ( deleted_at : nil )
. count
end
def number_of_flagged_posts
Post . with_deleted
. where ( user_id : self . id )
. where ( id : PostAction . where ( post_action_type_id : PostActionType . notify_flag_type_ids )
. where ( disagreed_at : nil )
. select ( :post_id ) )
. count
end
def number_of_flags_given
PostAction . where ( user_id : self . id )
. where ( disagreed_at : nil )
. where ( post_action_type_id : PostActionType . notify_flag_type_ids )
. count
end
def number_of_warnings
self . warnings . count
end
def number_of_suspensions
UserHistory . for ( self , :suspend_user ) . count
end
2015-03-07 05:44:54 +08:00
def create_user_profile
UserProfile . create ( user_id : id )
end
2015-04-08 10:29:43 +08:00
def anonymous?
SiteSetting . allow_anonymous_posting &&
trust_level > = 1 &&
custom_fields [ " master_id " ] . to_i > 0
end
2013-02-06 03:16:51 +08:00
protected
2014-07-23 09:42:24 +08:00
def badge_grant
BadgeGranter . queue_badge_grant ( Badge :: Trigger :: UserChange , user : self )
end
2015-06-06 01:50:06 +08:00
def expire_old_email_tokens
if password_hash_changed? && ! id_changed?
email_tokens . where ( 'not expired' ) . update_all ( expired : true )
end
end
2013-06-06 22:40:10 +08:00
def update_tracked_topics
return unless auto_track_topics_after_msecs_changed?
2013-10-24 05:24:50 +08:00
TrackedTopicsUpdater . new ( id , auto_track_topics_after_msecs ) . call
2013-06-06 22:40:10 +08:00
end
2013-02-06 03:16:51 +08:00
2014-03-24 15:03:39 +08:00
def clear_global_notice_if_needed
if admin && SiteSetting . has_login_hint
SiteSetting . has_login_hint = false
SiteSetting . global_notice = " "
end
end
2014-06-17 08:46:30 +08:00
def ensure_in_trust_level_group
Group . user_trust_level_change! ( id , trust_level )
end
2015-01-24 01:25:43 +08:00
def automatic_group_membership
Group . where ( automatic : false )
. where ( " LENGTH(COALESCE(automatic_membership_email_domains, '')) > 0 " )
. each do | group |
domains = group . automatic_membership_email_domains . gsub ( '.' , '\.' )
2015-01-27 06:17:29 +08:00
if self . email =~ Regexp . new ( " @( #{ domains } )$ " , true )
2015-01-24 01:25:43 +08:00
group . add ( self ) rescue ActiveRecord :: RecordNotUnique
end
end
end
2013-09-12 02:50:26 +08:00
def create_user_stat
2014-03-04 03:31:29 +08:00
stat = UserStat . new ( new_since : Time . now )
2013-10-04 11:28:49 +08:00
stat . user_id = id
2013-09-12 02:50:26 +08:00
stat . save!
end
2013-06-06 22:40:10 +08:00
def create_email_token
email_tokens . create ( email : email )
end
2013-02-06 03:16:51 +08:00
2013-06-06 22:40:10 +08:00
def ensure_password_is_hashed
if @raw_password
self . salt = SecureRandom . hex ( 16 )
self . password_hash = hash_password ( @raw_password , salt )
2013-02-06 03:16:51 +08:00
end
2013-06-06 22:40:10 +08:00
end
2013-02-06 03:16:51 +08:00
2013-06-06 22:40:10 +08:00
def hash_password ( password , salt )
2014-09-12 03:22:11 +08:00
raise " password is too long " if password . size > User . max_password_length
2013-07-23 09:36:01 +08:00
Pbkdf2 . hash_password ( password , salt , Rails . configuration . pbkdf2_iterations , Rails . configuration . pbkdf2_algorithm )
2013-06-06 22:40:10 +08:00
end
2013-02-06 03:16:51 +08:00
2013-06-06 22:40:10 +08:00
def add_trust_level
2013-12-21 15:19:22 +08:00
# there is a possibility we did not load trust level column, skip it
2013-06-06 22:40:10 +08:00
return unless has_attribute? :trust_level
self . trust_level || = SiteSetting . default_trust_level
end
2013-02-06 03:16:51 +08:00
2013-06-06 22:40:10 +08:00
def update_username_lower
self . username_lower = username . downcase
end
2013-02-06 10:44:49 +08:00
2015-01-30 03:41:41 +08:00
def strip_downcase_email
if self . email
self . email = self . email . strip
self . email = self . email . downcase
end
2014-07-14 22:16:24 +08:00
end
2013-06-06 22:40:10 +08:00
def username_validator
username_format_validator || begin
lower = username . downcase
2014-05-06 21:41:59 +08:00
existing = User . find_by ( username_lower : lower )
2013-06-29 04:21:46 +08:00
if username_changed? && existing && existing . id != self . id
2013-06-06 22:40:10 +08:00
errors . add ( :username , I18n . t ( :'user.username.unique' ) )
2013-02-06 03:16:51 +08:00
end
2013-02-08 07:23:41 +08:00
end
2013-06-06 22:40:10 +08:00
end
2013-02-06 03:16:51 +08:00
2013-08-14 04:08:29 +08:00
def send_approval_email
2015-05-05 03:30:25 +08:00
if SiteSetting . must_approve_users
Jobs . enqueue ( :user_email ,
type : :signup_after_approval ,
user_id : id ,
email_token : email_tokens . first . token
)
end
2013-08-14 04:08:29 +08:00
end
2013-07-07 18:40:35 +08:00
2015-08-22 02:39:21 +08:00
def set_default_user_preferences
set_default_email_digest_frequency
set_default_email_private_messages
set_default_email_direct
set_default_email_mailing_list_mode
set_default_email_always
set_default_other_new_topic_duration_minutes
set_default_other_auto_track_topics_after_msecs
set_default_other_external_links_in_new_tab
set_default_other_enable_quoting
set_default_other_dynamic_favicon
set_default_other_disable_jump_reply
set_default_other_edit_history_public
# needed, otherwise the callback chain is broken...
true
end
def set_default_categories_preferences
values = [ ]
%w{ watching tracking muted } . each do | s |
category_ids = SiteSetting . send ( " default_categories_ #{ s } " ) . split ( " | " )
category_ids . each do | category_id |
values << " ( #{ self . id } , #{ category_id } , #{ CategoryUser . notification_levels [ s . to_sym ] } ) "
2013-08-24 05:35:01 +08:00
end
end
2015-08-22 02:39:21 +08:00
if values . present?
exec_sql ( " INSERT INTO category_users (user_id, category_id, notification_level) VALUES #{ values . join ( " , " ) } " )
2014-01-03 04:27:26 +08:00
end
end
2014-12-03 13:36:25 +08:00
# Delete unactivated accounts (without verified email) that are over a week old
def self . purge_unactivated
2014-08-14 02:13:41 +08:00
to_destroy = User . where ( active : false )
. joins ( 'INNER JOIN user_stats AS us ON us.user_id = users.id' )
2014-12-03 13:36:25 +08:00
. where ( " created_at < ? " , SiteSetting . purge_unactivated_users_grace_period_days . days . ago )
2014-08-20 01:46:40 +08:00
. where ( 'NOT admin AND NOT moderator' )
2014-08-14 05:56:21 +08:00
. limit ( 100 )
2014-08-14 02:13:41 +08:00
destroyer = UserDestroyer . new ( Discourse . system_user )
to_destroy . each do | u |
2014-08-20 01:46:40 +08:00
begin
2014-08-28 04:04:46 +08:00
destroyer . destroy ( u , context : I18n . t ( :purge_reason ) )
2015-05-04 23:37:49 +08:00
rescue Discourse :: InvalidAccess , UserDestroyer :: PostsExistError
2014-08-20 01:46:40 +08:00
# if for some reason the user can't be deleted, continue on to the next one
end
2014-08-14 02:13:41 +08:00
end
end
2013-07-07 18:40:35 +08:00
private
2013-10-24 05:24:50 +08:00
def previous_visit_at_update_required? ( timestamp )
2014-01-17 11:38:08 +08:00
seen_before? && ( last_seen_at < ( timestamp - SiteSetting . previous_visit_timeout_hours . hours ) )
2013-10-24 05:24:50 +08:00
end
def update_previous_visit ( timestamp )
update_visit_record! ( timestamp . to_date )
if previous_visit_at_update_required? ( timestamp )
update_column ( :previous_visit_at , last_seen_at )
end
end
2015-08-22 02:39:21 +08:00
def set_default_email_digest_frequency
if has_attribute? ( :email_digests )
if SiteSetting . default_email_digest_frequency . blank?
self . email_digests = false
else
self . email_digests = true
self . digest_after_days || = SiteSetting . default_email_digest_frequency . to_i if has_attribute? ( :digest_after_days )
end
end
end
def set_default_email_mailing_list_mode
self . mailing_list_mode = SiteSetting . default_email_mailing_list_mode if has_attribute? ( :mailing_list_mode )
end
%w{ private_messages direct always } . each do | s |
define_method ( " set_default_email_ #{ s } " ) do
self . send ( " email_ #{ s } = " , SiteSetting . send ( " default_email_ #{ s } " ) ) if has_attribute? ( " email_ #{ s } " )
end
end
%w{ new_topic_duration_minutes auto_track_topics_after_msecs } . each do | s |
define_method ( " set_default_other_ #{ s } " ) do
self . send ( " #{ s } = " , SiteSetting . send ( " default_other_ #{ s } " ) . to_i ) if has_attribute? ( s )
end
end
%w{ external_links_in_new_tab enable_quoting dynamic_favicon disable_jump_reply edit_history_public } . each do | s |
define_method ( " set_default_other_ #{ s } " ) do
self . send ( " #{ s } = " , SiteSetting . send ( " default_other_ #{ s } " ) ) if has_attribute? ( s )
end
end
2013-02-06 03:16:51 +08:00
end
2013-05-24 10:48:32 +08:00
# == Schema Information
#
# Table name: users
#
# id :integer not null, primary key
2014-04-15 13:53:48 +08:00
# username :string(60) not null
2014-08-27 13:19:25 +08:00
# created_at :datetime not null
# updated_at :datetime not null
2013-05-24 10:48:32 +08:00
# name :string(255)
# seen_notification_id :integer default(0), not null
# last_posted_at :datetime
# email :string(256) not null
# password_hash :string(64)
# salt :string(32)
2014-08-23 01:01:44 +08:00
# active :boolean default(FALSE), not null
2014-04-15 13:53:48 +08:00
# username_lower :string(60) not null
2013-05-24 10:48:32 +08:00
# auth_token :string(32)
# last_seen_at :datetime
# admin :boolean default(FALSE), not null
# last_emailed_at :datetime
2013-08-28 08:42:58 +08:00
# email_digests :boolean not null
2013-05-24 10:48:32 +08:00
# trust_level :integer not null
# email_private_messages :boolean default(TRUE)
# email_direct :boolean default(TRUE), not null
# approved :boolean default(FALSE), not null
# approved_by_id :integer
# approved_at :datetime
2013-08-28 08:42:58 +08:00
# digest_after_days :integer
2013-05-24 10:48:32 +08:00
# previous_visit_at :datetime
2013-11-08 02:53:32 +08:00
# suspended_at :datetime
# suspended_till :datetime
2013-05-24 10:48:32 +08:00
# date_of_birth :date
# auto_track_topics_after_msecs :integer
# views :integer default(0), not null
# flag_level :integer default(0), not null
2013-12-05 14:40:35 +08:00
# ip_address :inet
2013-05-24 10:48:32 +08:00
# new_topic_duration_minutes :integer
2014-02-07 08:07:36 +08:00
# external_links_in_new_tab :boolean not null
2013-05-24 10:48:32 +08:00
# enable_quoting :boolean default(TRUE), not null
# moderator :boolean default(FALSE)
2013-06-17 08:48:58 +08:00
# blocked :boolean default(FALSE)
2013-06-20 15:42:15 +08:00
# dynamic_favicon :boolean default(FALSE), not null
2013-07-14 09:24:16 +08:00
# title :string(255)
2013-08-14 04:08:29 +08:00
# uploaded_avatar_id :integer
2013-10-04 11:28:49 +08:00
# email_always :boolean default(FALSE), not null
2014-02-07 08:07:36 +08:00
# mailing_list_mode :boolean default(FALSE), not null
2014-08-27 13:19:25 +08:00
# locale :string(10)
2014-11-20 11:53:15 +08:00
# primary_group_id :integer
2014-05-08 14:45:49 +08:00
# registration_ip_address :inet
# last_redirected_to_top_at :datetime
2014-05-28 09:49:50 +08:00
# disable_jump_reply :boolean default(FALSE), not null
2014-07-27 12:04:46 +08:00
# edit_history_public :boolean default(FALSE), not null
2014-11-20 11:53:15 +08:00
# trust_level_locked :boolean default(FALSE), not null
2013-05-24 10:48:32 +08:00
#
# Indexes
#
# index_users_on_auth_token (auth_token)
# index_users_on_last_posted_at (last_posted_at)
2014-08-07 11:33:11 +08:00
# index_users_on_last_seen_at (last_seen_at)
2013-05-24 10:48:32 +08:00
# index_users_on_username (username) UNIQUE
# index_users_on_username_lower (username_lower) UNIQUE
#