2007-03-30 04:36:52 +00:00
|
|
|
require 'digest/sha1'
|
2011-09-07 17:19:04 +02:00
|
|
|
require 'bcrypt'
|
2007-03-30 04:36:52 +00:00
|
|
|
|
2018-11-03 15:57:14 -05:00
|
|
|
class User < ApplicationRecord
|
2007-07-08 06:41:10 +00:00
|
|
|
# Virtual attribute for the unencrypted password
|
|
|
|
attr_accessor :password
|
2014-08-14 21:05:05 -05:00
|
|
|
|
2012-04-24 20:47:07 +02:00
|
|
|
cattr_accessor :per_page
|
2020-10-10 15:50:59 +03:00
|
|
|
@@per_page = 25
|
2007-07-08 06:41:10 +00:00
|
|
|
|
2013-05-11 23:13:32 +02:00
|
|
|
has_many(:contexts, -> { order 'position ASC' }, dependent: :delete_all) do
|
2012-04-17 16:47:37 +02:00
|
|
|
def find_by_params(params)
|
2013-02-27 11:50:49 +01:00
|
|
|
find(params['id'] || params['context_id']) || nil
|
2012-04-17 16:47:37 +02:00
|
|
|
end
|
2020-10-10 13:58:13 +03:00
|
|
|
|
2009-10-02 19:45:49 -04:00
|
|
|
def update_positions(context_ids)
|
2020-10-27 21:39:19 +02:00
|
|
|
context_ids.each_with_index do |id, position|
|
|
|
|
context = detect { |c| c.id == id.to_i }
|
2020-10-10 13:58:13 +03:00
|
|
|
raise I18n.t('models.user.error_context_not_associated', :context => id, :user => @user.id) if context.nil?
|
|
|
|
context.update_attribute(:position, position + 1)
|
2020-10-27 21:39:19 +02:00
|
|
|
end
|
2020-10-10 13:58:13 +03:00
|
|
|
end
|
2007-03-30 04:36:52 +00:00
|
|
|
end
|
2013-05-11 23:13:32 +02:00
|
|
|
|
2020-10-10 02:27:42 +03:00
|
|
|
has_many(:projects, -> { order 'projects.position ASC' }, dependent: :delete_all) do
|
2012-04-17 16:47:37 +02:00
|
|
|
def find_by_params(params)
|
2013-02-27 11:50:49 +01:00
|
|
|
find(params['id'] || params['project_id'])
|
2012-04-17 16:47:37 +02:00
|
|
|
end
|
2020-10-10 13:58:13 +03:00
|
|
|
|
2007-03-30 04:36:52 +00:00
|
|
|
def update_positions(project_ids)
|
2020-10-27 21:39:19 +02:00
|
|
|
project_ids.each_with_index do |id, position|
|
|
|
|
project = find_by(id: id.to_i)
|
2010-10-31 21:27:13 +08:00
|
|
|
raise I18n.t('models.user.error_project_not_associated', :project => id, :user => @user.id) if project.nil?
|
2007-03-30 04:36:52 +00:00
|
|
|
project.update_attribute(:position, position + 1)
|
2020-10-27 21:39:19 +02:00
|
|
|
end
|
2007-03-30 04:36:52 +00:00
|
|
|
end
|
2020-10-10 13:58:13 +03:00
|
|
|
|
2007-03-30 04:36:52 +00:00
|
|
|
def projects_in_state_by_position(state)
|
2020-10-27 21:39:19 +02:00
|
|
|
select { |p| p.state == state }.sort_by { |p| p.position }
|
2007-03-30 04:36:52 +00:00
|
|
|
end
|
2020-10-10 13:58:13 +03:00
|
|
|
|
2007-03-30 04:36:52 +00:00
|
|
|
def next_from(project)
|
2020-10-27 21:39:19 +02:00
|
|
|
offset_from(project, 1)
|
2007-03-30 04:36:52 +00:00
|
|
|
end
|
2020-10-10 13:58:13 +03:00
|
|
|
|
2007-03-30 04:36:52 +00:00
|
|
|
def previous_from(project)
|
2020-10-27 21:39:19 +02:00
|
|
|
offset_from(project, -1)
|
2007-03-30 04:36:52 +00:00
|
|
|
end
|
2020-10-10 13:58:13 +03:00
|
|
|
|
2007-03-30 04:36:52 +00:00
|
|
|
def offset_from(project, offset)
|
2020-10-27 21:39:19 +02:00
|
|
|
projects = projects_in_state_by_position(project.state)
|
2007-03-30 04:36:52 +00:00
|
|
|
position = projects.index(project)
|
|
|
|
return nil if position == 0 && offset < 0
|
2020-10-10 02:27:42 +03:00
|
|
|
projects.at(position + offset)
|
2007-03-30 04:36:52 +00:00
|
|
|
end
|
2020-10-10 13:58:13 +03:00
|
|
|
|
2007-03-30 04:36:52 +00:00
|
|
|
def cache_note_counts
|
2012-04-18 14:22:58 +02:00
|
|
|
project_note_counts = Note.group(:project_id).count
|
2020-10-27 21:39:19 +02:00
|
|
|
each do |project|
|
2007-03-30 04:36:52 +00:00
|
|
|
project.cached_note_count = project_note_counts[project.id] || 0
|
|
|
|
end
|
|
|
|
end
|
2020-10-10 13:58:13 +03:00
|
|
|
|
2007-04-06 05:45:21 +00:00
|
|
|
def alphabetize(scope_conditions = {})
|
2012-04-18 14:22:58 +02:00
|
|
|
projects = where(scope_conditions)
|
2018-10-27 10:20:58 -05:00
|
|
|
projects = projects.sort_by { |project| project.name.downcase }
|
2020-10-27 21:39:19 +02:00
|
|
|
update_positions(projects.map(&:id))
|
2007-04-06 05:45:21 +00:00
|
|
|
return projects
|
|
|
|
end
|
2020-10-10 13:58:13 +03:00
|
|
|
|
2011-06-12 04:29:35 +02:00
|
|
|
def actionize(scope_conditions = {})
|
2012-04-18 14:22:58 +02:00
|
|
|
todos_in_project = where(scope_conditions).includes(:todos)
|
2018-10-27 10:20:58 -05:00
|
|
|
todos_in_project = todos_in_project.sort_by { |x| [-x.todos.active.count, -x.id] }
|
2020-10-10 13:58:13 +03:00
|
|
|
todos_in_project.reject { |p| p.todos.active.count > 0 }
|
|
|
|
sorted_project_ids = todos_in_project.map(&:id)
|
2011-09-30 19:49:18 +02:00
|
|
|
|
2020-10-27 21:39:19 +02:00
|
|
|
all_project_ids = map(&:id)
|
2011-06-12 04:29:35 +02:00
|
|
|
other_project_ids = all_project_ids - sorted_project_ids
|
2011-09-30 19:49:18 +02:00
|
|
|
|
2011-06-12 04:29:35 +02:00
|
|
|
update_positions(sorted_project_ids + other_project_ids)
|
2011-04-29 23:15:41 +02:00
|
|
|
|
2012-04-18 14:22:58 +02:00
|
|
|
return where(scope_conditions)
|
2008-09-23 17:06:14 -03:00
|
|
|
end
|
2007-03-30 04:36:52 +00:00
|
|
|
end
|
2013-05-11 23:13:32 +02:00
|
|
|
|
|
|
|
has_many(:todos, -> { order 'todos.completed_at DESC, todos.created_at DESC' }, dependent: :delete_all) do
|
2020-10-10 13:58:13 +03:00
|
|
|
def count_by_group(g)
|
|
|
|
except(:order).group(g).count
|
|
|
|
end
|
2012-10-04 10:20:26 -04:00
|
|
|
end
|
2013-05-11 23:13:32 +02:00
|
|
|
|
2008-07-19 20:27:45 +02:00
|
|
|
has_many :recurring_todos,
|
2020-10-10 02:27:42 +03:00
|
|
|
-> { order 'recurring_todos.completed_at DESC, recurring_todos.created_at DESC' },
|
2013-05-11 23:13:32 +02:00
|
|
|
dependent: :delete_all
|
|
|
|
|
|
|
|
has_many(:deferred_todos,
|
2020-10-10 02:27:42 +03:00
|
|
|
-> { where('state = ?', 'deferred')
|
|
|
|
.order('show_from ASC, todos.created_at DESC') },
|
2013-05-11 23:13:32 +02:00
|
|
|
:class_name => 'Todo') do
|
2020-10-10 13:58:13 +03:00
|
|
|
def find_and_activate_ready
|
|
|
|
where('show_from <= ?', Time.current).collect { |t| t.activate! }
|
|
|
|
end
|
2007-03-30 04:36:52 +00:00
|
|
|
end
|
2013-05-11 23:13:32 +02:00
|
|
|
|
2019-06-25 03:15:23 +03:00
|
|
|
has_many :tags, dependent: :delete_all
|
2013-05-11 23:13:32 +02:00
|
|
|
has_many :notes, -> { order "created_at DESC" }, dependent: :delete_all
|
|
|
|
has_one :preference, dependent: :destroy
|
2015-08-05 13:01:02 +02:00
|
|
|
has_many :attachments, through: :todos
|
2011-09-30 19:49:18 +02:00
|
|
|
|
2020-10-27 21:39:19 +02:00
|
|
|
validates :login, presence: true, length: { within: 3..80 }
|
2020-08-20 18:33:36 +03:00
|
|
|
validates_uniqueness_of :login, on: :create, :case_sensitive => false
|
2020-10-27 21:39:19 +02:00
|
|
|
validates :password, presence: true, length: { within: 5..72 }, if: :password_required?
|
|
|
|
validates :password_confirmation, presence: true, if: :password_required?
|
|
|
|
validates :password, confirmation: true
|
|
|
|
validates :email, allow_blank: true, format: { with: URI::MailTo::EMAIL_REGEXP }
|
|
|
|
|
2012-04-17 16:47:37 +02:00
|
|
|
validate :validate_auth_type
|
2007-03-30 04:36:52 +00:00
|
|
|
|
2007-07-17 04:47:35 +00:00
|
|
|
before_create :crypt_password, :generate_token
|
2007-07-08 06:41:10 +00:00
|
|
|
before_update :crypt_password
|
2020-10-27 21:39:19 +02:00
|
|
|
# Run before deleting todos, projects, contexts, etc.
|
|
|
|
before_destroy :destroy_dependencies, :delete_taggings, prepend: true
|
2008-07-14 13:10:55 -04:00
|
|
|
|
2012-04-17 16:47:37 +02:00
|
|
|
def validate_auth_type
|
2007-03-30 04:36:52 +00:00
|
|
|
unless Tracks::Config.auth_schemes.include?(auth_type)
|
2007-07-16 02:18:07 +00:00
|
|
|
errors.add("auth_type", "not a valid authentication type (#{auth_type})")
|
2007-03-30 04:36:52 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
alias_method :prefs, :preference
|
|
|
|
|
|
|
|
def self.authenticate(login, pass)
|
2007-11-26 01:57:21 +00:00
|
|
|
return nil if login.blank?
|
2012-04-18 14:22:58 +02:00
|
|
|
candidate = where("login = ?", login).first
|
2007-03-30 04:36:52 +00:00
|
|
|
return nil if candidate.nil?
|
2011-01-20 07:33:39 +08:00
|
|
|
|
|
|
|
if Tracks::Config.auth_schemes.include?('database')
|
2020-10-27 21:39:19 +02:00
|
|
|
|
|
|
|
return candidate if (candidate.auth_type == 'database' && candidate.password_matches?(pass))
|
2011-01-20 07:33:39 +08:00
|
|
|
end
|
2011-09-30 19:49:18 +02:00
|
|
|
|
2007-11-26 02:00:09 +00:00
|
|
|
return nil
|
2007-03-30 04:36:52 +00:00
|
|
|
end
|
2011-09-30 19:49:18 +02:00
|
|
|
|
2007-03-30 04:36:52 +00:00
|
|
|
def self.no_users_yet?
|
|
|
|
count == 0
|
|
|
|
end
|
2011-09-30 19:49:18 +02:00
|
|
|
|
2007-03-30 04:36:52 +00:00
|
|
|
def self.find_admin
|
2012-04-17 16:47:37 +02:00
|
|
|
where(:is_admin => true).first
|
2007-03-30 04:36:52 +00:00
|
|
|
end
|
2011-09-30 19:49:18 +02:00
|
|
|
|
2007-03-30 04:36:52 +00:00
|
|
|
def to_param
|
|
|
|
login
|
|
|
|
end
|
2011-09-30 19:49:18 +02:00
|
|
|
|
2007-03-30 04:36:52 +00:00
|
|
|
def display_name
|
|
|
|
if first_name.blank? && last_name.blank?
|
|
|
|
return login
|
|
|
|
elsif first_name.blank?
|
|
|
|
return last_name
|
|
|
|
elsif last_name.blank?
|
|
|
|
return first_name
|
|
|
|
end
|
|
|
|
"#{first_name} #{last_name}"
|
|
|
|
end
|
2011-09-30 19:49:18 +02:00
|
|
|
|
2020-10-10 13:58:13 +03:00
|
|
|
def change_password(pass, pass_confirm)
|
2007-03-30 04:36:52 +00:00
|
|
|
self.password = pass
|
|
|
|
self.password_confirmation = pass_confirm
|
|
|
|
save!
|
|
|
|
end
|
2011-09-30 19:49:18 +02:00
|
|
|
|
2007-03-30 04:36:52 +00:00
|
|
|
def date
|
2014-06-12 21:24:31 -04:00
|
|
|
Date.current
|
2008-09-13 13:33:48 -07:00
|
|
|
end
|
2011-09-30 19:49:18 +02:00
|
|
|
|
2007-07-17 04:47:35 +00:00
|
|
|
def generate_token
|
2020-10-27 21:39:19 +02:00
|
|
|
self.token = Digest::SHA1.hexdigest "#{Time.zone.now.to_i}#{rand}"
|
2007-07-17 04:47:35 +00:00
|
|
|
end
|
2011-09-30 19:49:18 +02:00
|
|
|
|
2007-07-08 06:41:10 +00:00
|
|
|
def remember_token?
|
2020-10-27 21:39:19 +02:00
|
|
|
remember_token_expires_at && Time.zone.now.utc < remember_token_expires_at
|
2007-07-08 06:41:10 +00:00
|
|
|
end
|
2011-09-30 19:49:18 +02:00
|
|
|
|
2007-07-08 06:41:10 +00:00
|
|
|
# These create and unset the fields required for remembering users between browser closes
|
|
|
|
def remember_me
|
|
|
|
self.remember_token_expires_at = 2.weeks.from_now.utc
|
2012-04-05 22:19:47 +02:00
|
|
|
self.remember_token ||= Digest::SHA1.hexdigest("#{login}--#{remember_token_expires_at}")
|
|
|
|
save
|
2007-07-08 06:41:10 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def forget_me
|
|
|
|
self.remember_token_expires_at = nil
|
|
|
|
self.remember_token = nil
|
2012-04-05 22:19:47 +02:00
|
|
|
save
|
2007-07-08 06:41:10 +00:00
|
|
|
end
|
2007-03-30 04:36:52 +00:00
|
|
|
|
2011-09-05 01:29:48 +02:00
|
|
|
def password_matches?(pass)
|
2014-11-15 09:46:59 -05:00
|
|
|
BCrypt::Password.new(crypted_password) == pass
|
2011-07-23 10:52:38 +02:00
|
|
|
end
|
|
|
|
|
2012-04-17 16:47:37 +02:00
|
|
|
def create_hash(s)
|
|
|
|
BCrypt::Password.create(s)
|
2007-07-08 06:41:10 +00:00
|
|
|
end
|
2011-09-30 19:49:18 +02:00
|
|
|
|
2020-10-10 02:27:42 +03:00
|
|
|
protected
|
2011-10-10 22:31:51 +02:00
|
|
|
|
2007-03-30 04:36:52 +00:00
|
|
|
def crypt_password
|
2007-07-08 06:41:10 +00:00
|
|
|
return if password.blank?
|
2020-10-27 21:39:19 +02:00
|
|
|
write_attribute("crypted_password", create_hash(password)) if password == password_confirmation
|
2007-03-30 04:36:52 +00:00
|
|
|
end
|
2011-09-30 19:49:18 +02:00
|
|
|
|
2007-03-30 04:36:52 +00:00
|
|
|
def password_required?
|
2013-09-13 15:19:25 +03:00
|
|
|
auth_type == 'database' && crypted_password.blank? || password.present?
|
2007-07-08 06:41:10 +00:00
|
|
|
end
|
2011-09-30 19:49:18 +02:00
|
|
|
|
2014-09-23 16:35:45 +02:00
|
|
|
def destroy_dependencies
|
|
|
|
ids = todos.pluck(:id)
|
|
|
|
pred_deps = Dependency.where(predecessor_id: ids).destroy_all
|
|
|
|
succ_deps = Dependency.where(predecessor_id: ids).destroy_all
|
|
|
|
end
|
|
|
|
|
|
|
|
def delete_taggings
|
|
|
|
ids = todos.pluck(:id)
|
|
|
|
taggings = Tagging.where(taggable_id: ids).pluck(:id)
|
|
|
|
Tagging.where(id: taggings).delete_all
|
|
|
|
end
|
2007-03-30 04:36:52 +00:00
|
|
|
end
|