2020-04-19 00:59:18 +02:00
|
|
|
class User < ApplicationRecord
|
2021-12-31 22:19:21 +01:00
|
|
|
include User::Relationship
|
|
|
|
include User::Relationship::Follow
|
2022-04-18 21:24:15 +02:00
|
|
|
include User::Relationship::Block
|
2020-04-20 23:03:57 +02:00
|
|
|
include User::AnswerMethods
|
|
|
|
include User::InboxMethods
|
|
|
|
include User::QuestionMethods
|
|
|
|
include User::RelationshipMethods
|
|
|
|
include User::TimelineMethods
|
2020-10-18 10:39:46 +02:00
|
|
|
include ActiveModel::OneTimePassword
|
2020-04-20 23:03:57 +02:00
|
|
|
|
2014-08-01 12:07:16 +02:00
|
|
|
# Include default devise modules. Others available are:
|
|
|
|
# :confirmable, :lockable, :timeoutable and :omniauthable
|
2015-01-01 18:17:34 +01:00
|
|
|
devise :database_authenticatable, :async, :registerable,
|
2014-08-01 15:27:08 +02:00
|
|
|
:recoverable, :rememberable, :trackable,
|
2015-07-21 17:55:28 +02:00
|
|
|
:validatable, :confirmable, :authentication_keys => [:login]
|
2014-11-30 19:43:22 +01:00
|
|
|
|
2020-10-18 10:39:46 +02:00
|
|
|
has_one_time_password
|
2020-10-18 11:39:28 +02:00
|
|
|
enum otp_module: { disabled: 0, enabled: 1 }, _prefix: true
|
2020-10-18 10:39:46 +02:00
|
|
|
attr_accessor :otp_attempt, :otp_validation
|
|
|
|
|
2020-04-19 22:35:58 +02:00
|
|
|
rolify
|
|
|
|
|
2014-08-01 15:27:08 +02:00
|
|
|
# attr_accessor :login
|
2014-11-30 19:43:22 +01:00
|
|
|
|
2014-10-28 06:36:38 +01:00
|
|
|
has_many :questions, dependent: :destroy
|
|
|
|
has_many :answers, dependent: :destroy
|
|
|
|
has_many :comments, dependent: :destroy
|
2014-11-10 23:45:36 +01:00
|
|
|
has_many :inboxes, dependent: :destroy
|
2014-12-28 00:34:56 +01:00
|
|
|
has_many :smiles, dependent: :destroy
|
2015-05-04 03:39:41 +02:00
|
|
|
has_many :comment_smiles, dependent: :destroy
|
2014-12-28 00:34:56 +01:00
|
|
|
has_many :services, dependent: :destroy
|
|
|
|
has_many :notifications, foreign_key: :recipient_id, dependent: :destroy
|
2014-12-27 14:35:09 +01:00
|
|
|
has_many :reports, dependent: :destroy
|
2014-12-28 21:47:51 +01:00
|
|
|
has_many :moderation_comments, dependent: :destroy
|
|
|
|
has_many :moderation_votes, dependent: :destroy
|
2020-05-25 18:04:54 +02:00
|
|
|
has_many :lists, dependent: :destroy
|
|
|
|
has_many :list_memberships, class_name: "ListMember", foreign_key: 'user_id', dependent: :destroy
|
2021-12-22 19:30:55 +01:00
|
|
|
has_many :mute_rules, dependent: :destroy
|
2014-11-30 14:43:35 +01:00
|
|
|
|
2015-04-21 03:12:11 +02:00
|
|
|
has_many :subscriptions, dependent: :destroy
|
2020-11-15 10:21:06 +01:00
|
|
|
has_many :totp_recovery_codes, dependent: :destroy
|
2015-04-21 03:12:11 +02:00
|
|
|
|
2021-12-19 16:51:04 +01:00
|
|
|
has_one :profile, dependent: :destroy
|
2015-08-25 10:26:36 +02:00
|
|
|
has_one :theme, dependent: :destroy
|
|
|
|
|
2021-08-23 09:50:35 +02:00
|
|
|
has_many :bans, class_name: 'UserBan', dependent: :destroy
|
2021-08-14 16:07:12 +02:00
|
|
|
has_many :banned_users, class_name: 'UserBan',
|
|
|
|
foreign_key: 'banned_by_id',
|
|
|
|
dependent: :nullify
|
|
|
|
|
2014-08-01 15:27:08 +02:00
|
|
|
SCREEN_NAME_REGEX = /\A[a-zA-Z0-9_]{1,16}\z/
|
2014-12-01 20:47:10 +01:00
|
|
|
WEBSITE_REGEX = /https?:\/\/([A-Za-z.\-]+)\/?(?:.*)/i
|
2014-11-30 19:43:22 +01:00
|
|
|
|
2015-01-09 14:12:52 +01:00
|
|
|
before_validation do
|
|
|
|
screen_name.strip!
|
|
|
|
end
|
|
|
|
|
2022-01-11 18:37:07 +01:00
|
|
|
validates :email, fake_email: true, typoed_email: true
|
2015-01-09 14:12:52 +01:00
|
|
|
validates :screen_name, presence: true, format: { with: SCREEN_NAME_REGEX }, uniqueness: { case_sensitive: false }, screen_name: true
|
2014-11-11 20:20:00 +01:00
|
|
|
|
2020-05-02 00:43:46 +02:00
|
|
|
mount_uploader :profile_picture, ProfilePictureUploader, mount_on: :profile_picture_file_name
|
2020-05-17 20:58:27 +02:00
|
|
|
process_in_background :profile_picture
|
2020-05-02 00:43:46 +02:00
|
|
|
mount_uploader :profile_header, ProfileHeaderUploader, mount_on: :profile_header_file_name
|
2020-05-17 20:58:27 +02:00
|
|
|
process_in_background :profile_header
|
2014-12-29 10:18:12 +01:00
|
|
|
|
2020-05-02 18:45:11 +02:00
|
|
|
# when a user has been deleted, all reports relating to the user become invalid
|
2015-04-30 02:04:43 +02:00
|
|
|
before_destroy do
|
2015-04-30 02:22:24 +02:00
|
|
|
rep = Report.where(target_id: self.id, type: 'Reports::User')
|
2015-04-30 02:04:43 +02:00
|
|
|
rep.each do |r|
|
|
|
|
unless r.nil?
|
|
|
|
r.deleted = true
|
|
|
|
r.save
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2021-12-19 23:51:06 +01:00
|
|
|
after_create do
|
|
|
|
Profile.create(user_id: id) if Profile.where(user_id: id).count.zero?
|
|
|
|
end
|
|
|
|
|
2014-08-01 15:27:08 +02:00
|
|
|
def login=(login)
|
|
|
|
@login = login
|
|
|
|
end
|
|
|
|
|
|
|
|
def login
|
|
|
|
@login || self.screen_name || self.email
|
|
|
|
end
|
2014-11-30 19:43:22 +01:00
|
|
|
|
2014-08-01 15:27:08 +02:00
|
|
|
def self.find_first_by_auth_conditions(warden_conditions)
|
2015-04-22 00:33:55 +02:00
|
|
|
conditions = warden_conditions.dup
|
2014-08-01 15:27:08 +02:00
|
|
|
if login = conditions.delete(:login)
|
|
|
|
where(conditions).where(["lower(screen_name) = :value OR lower(email) = :value", { :value => login.downcase }]).first
|
|
|
|
else
|
|
|
|
where(conditions).first
|
|
|
|
end
|
|
|
|
end
|
2014-11-30 15:13:17 +01:00
|
|
|
|
2020-05-25 18:04:54 +02:00
|
|
|
# @param list [List]
|
|
|
|
# @return [Boolean] true if +self+ is a member of +list+
|
|
|
|
def member_of?(list)
|
|
|
|
list_memberships.pluck(:list_id).include? list.id
|
2015-01-14 07:07:40 +01:00
|
|
|
end
|
|
|
|
|
2015-01-03 18:09:56 +01:00
|
|
|
# answers a question
|
|
|
|
# @param question [Question] the question to answer
|
|
|
|
# @param content [String] the answer content
|
|
|
|
def answer(question, content)
|
2022-06-12 13:46:48 +02:00
|
|
|
# rubocop:disable Style/RedundantSelf
|
2022-06-13 19:31:37 +02:00
|
|
|
raise Errors::AnsweringOtherBlockedSelf if question.user&.blocking?(self)
|
2022-06-11 23:14:01 +02:00
|
|
|
raise Errors::AnsweringSelfBlockedOther if self.blocking?(question.user)
|
2022-06-12 13:46:48 +02:00
|
|
|
# rubocop:enable Style/RedundantSelf
|
2022-06-11 23:14:01 +02:00
|
|
|
|
2015-01-03 18:09:56 +01:00
|
|
|
Answer.create!(content: content,
|
|
|
|
user: self,
|
|
|
|
question: question)
|
|
|
|
end
|
|
|
|
|
2015-01-03 18:40:56 +01:00
|
|
|
# has the user answered +question+ yet?
|
|
|
|
# @param question [Question]
|
|
|
|
def answered?(question)
|
2015-01-14 07:50:27 +01:00
|
|
|
question.answers.pluck(:user_id).include? self.id
|
2015-01-03 18:40:56 +01:00
|
|
|
end
|
|
|
|
|
2014-11-30 19:43:22 +01:00
|
|
|
# smiles an answer
|
|
|
|
# @param answer [Answer] the answer to smile
|
|
|
|
def smile(answer)
|
2022-06-12 13:46:48 +02:00
|
|
|
# rubocop:disable Style/RedundantSelf
|
2022-06-09 19:49:24 +02:00
|
|
|
raise Errors::ReactingSelfBlockedOther if self.blocking?(answer.user)
|
|
|
|
raise Errors::ReactingOtherBlockedSelf if answer.user.blocking?(self)
|
2022-06-12 13:46:48 +02:00
|
|
|
# rubocop:enable Style/RedundantSelf
|
2022-06-09 19:49:24 +02:00
|
|
|
|
2014-12-28 23:26:16 +01:00
|
|
|
Smile.create!(user: self, answer: answer)
|
2014-11-30 19:43:22 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
# unsmile an answer
|
|
|
|
# @param answer [Answer] the answer to unsmile
|
|
|
|
def unsmile(answer)
|
2014-12-28 21:46:57 +01:00
|
|
|
Smile.find_by(user: self, answer: answer).destroy
|
2014-11-30 19:43:22 +01:00
|
|
|
end
|
2014-11-30 20:31:22 +01:00
|
|
|
|
2015-05-04 03:39:41 +02:00
|
|
|
# smiles a comment
|
|
|
|
# @param comment [Comment] the comment to smile
|
|
|
|
def smile_comment(comment)
|
2022-06-12 23:11:27 +02:00
|
|
|
# rubocop:disable Style/RedundantSelf
|
|
|
|
raise Errors::ReactingSelfBlockedOther if self.blocking?(comment.user)
|
|
|
|
raise Errors::ReactingOtherBlockedSelf if comment.user.blocking?(self)
|
|
|
|
# rubocop:enable Style/RedundantSelf
|
|
|
|
|
2015-05-04 03:39:41 +02:00
|
|
|
CommentSmile.create!(user: self, comment: comment)
|
|
|
|
end
|
|
|
|
|
|
|
|
# unsmile an comment
|
|
|
|
# @param comment [Comment] the comment to unsmile
|
|
|
|
def unsmile_comment(comment)
|
|
|
|
CommentSmile.find_by(user: self, comment: comment).destroy
|
|
|
|
end
|
|
|
|
|
2014-11-30 20:31:22 +01:00
|
|
|
def smiled?(answer)
|
2015-01-14 07:50:27 +01:00
|
|
|
answer.smiles.pluck(:user_id).include? self.id
|
2014-11-30 20:31:22 +01:00
|
|
|
end
|
2014-12-01 20:47:10 +01:00
|
|
|
|
2015-05-04 03:39:41 +02:00
|
|
|
def smiled_comment?(comment)
|
|
|
|
comment.smiles.pluck(:user_id).include? self.id
|
|
|
|
end
|
|
|
|
|
2014-12-05 14:11:08 +01:00
|
|
|
def comment(answer, content)
|
2022-06-12 13:46:48 +02:00
|
|
|
# rubocop:disable Style/RedundantSelf
|
2022-06-09 19:44:58 +02:00
|
|
|
raise Errors::CommentingSelfBlockedOther if self.blocking?(answer.user)
|
|
|
|
raise Errors::CommentingOtherBlockedSelf if answer.user.blocking?(self)
|
2022-06-12 13:46:48 +02:00
|
|
|
# rubocop:enable Style/RedundantSelf
|
2022-06-09 19:44:58 +02:00
|
|
|
|
2014-12-28 21:58:11 +01:00
|
|
|
Comment.create!(user: self, answer: answer, content: content)
|
2014-12-05 14:11:08 +01:00
|
|
|
end
|
2014-12-28 19:32:08 +01:00
|
|
|
|
|
|
|
# @return [Boolean] is the user a moderator?
|
|
|
|
def mod?
|
2020-04-19 22:35:58 +02:00
|
|
|
has_role?(:moderator) || has_role?(:administrator)
|
2014-12-28 19:32:08 +01:00
|
|
|
end
|
|
|
|
|
2015-01-03 18:09:56 +01:00
|
|
|
# region stuff used for reporting/moderation
|
2015-04-22 04:59:10 +02:00
|
|
|
def report(object, reason = nil)
|
2015-04-30 02:22:24 +02:00
|
|
|
existing = Report.find_by(type: "Reports::#{object.class}", target_id: object.id, user_id: self.id, deleted: false)
|
2015-04-22 04:59:10 +02:00
|
|
|
if existing.nil?
|
|
|
|
Report.create(type: "Reports::#{object.class}", target_id: object.id, user_id: self.id, reason: reason)
|
|
|
|
elsif not reason.nil? and reason.length > 0
|
|
|
|
if existing.reason.nil?
|
|
|
|
existing.update(reason: reason)
|
|
|
|
else
|
|
|
|
existing.update(reason: [existing.reason || "", reason].join("\n"))
|
|
|
|
end
|
|
|
|
else
|
|
|
|
existing
|
|
|
|
end
|
2014-12-28 19:32:08 +01:00
|
|
|
end
|
2014-12-28 23:26:16 +01:00
|
|
|
|
|
|
|
# @param upvote [Boolean]
|
|
|
|
def report_vote(report, upvote = false)
|
|
|
|
return unless mod?
|
|
|
|
ModerationVote.create!(user: self, report: report, upvote: upvote)
|
|
|
|
end
|
|
|
|
|
|
|
|
def report_unvote(report)
|
|
|
|
return unless mod?
|
|
|
|
ModerationVote.find_by(user: self, report: report).destroy
|
|
|
|
end
|
|
|
|
|
|
|
|
def report_voted?(report)
|
|
|
|
return false unless mod?
|
|
|
|
report.moderation_votes.each { |s| return true if s.user_id == self.id }
|
|
|
|
false
|
|
|
|
end
|
2014-12-28 23:57:07 +01:00
|
|
|
|
|
|
|
# @param upvote [Boolean]
|
|
|
|
def report_x_voted?(report, upvote)
|
|
|
|
return false unless mod?
|
|
|
|
report.moderation_votes.where(upvote: upvote).each { |s| return true if s.user_id == self.id }
|
|
|
|
false
|
|
|
|
end
|
2014-12-29 00:50:14 +01:00
|
|
|
|
|
|
|
def report_comment(report, content)
|
|
|
|
ModerationComment.create!(user: self, report: report, content: content)
|
|
|
|
end
|
2015-01-03 18:09:56 +01:00
|
|
|
# endregion
|
2014-12-29 14:51:52 +01:00
|
|
|
|
2015-04-23 02:56:29 +02:00
|
|
|
def banned?
|
2021-12-30 21:06:21 +01:00
|
|
|
self.bans.current.count > 0
|
2015-04-23 02:56:29 +02:00
|
|
|
end
|
|
|
|
|
|
|
|
def unban
|
2021-12-30 13:13:35 +01:00
|
|
|
UseCase::User::Unban.call(id)
|
2015-04-23 02:56:29 +02:00
|
|
|
end
|
|
|
|
|
2021-08-14 18:04:58 +02:00
|
|
|
# Bans a user.
|
2022-01-06 13:56:55 +01:00
|
|
|
# @param duration [Integer?] Ban duration
|
2021-08-14 18:04:58 +02:00
|
|
|
# @param duration_unit [String, nil] Unit for the <code>duration</code> parameter. Accepted units: hours, days, weeks, months
|
|
|
|
# @param reason [String] Reason for the ban. This is displayed to the user.
|
|
|
|
# @param banned_by [User] User who instated the ban
|
|
|
|
def ban(duration, duration_unit = 'hours', reason = nil, banned_by = nil)
|
2021-12-30 00:20:09 +01:00
|
|
|
if duration
|
|
|
|
expiry = duration.public_send(duration_unit)
|
|
|
|
else
|
|
|
|
expiry = nil
|
|
|
|
end
|
|
|
|
UseCase::User::Ban.call(
|
|
|
|
target_user_id: id,
|
|
|
|
expiry: expiry,
|
|
|
|
reason: reason,
|
|
|
|
source_user_id: banned_by&.id
|
|
|
|
)
|
2015-04-23 02:56:29 +02:00
|
|
|
end
|
2016-01-05 20:54:13 +01:00
|
|
|
|
|
|
|
def can_export?
|
2016-01-05 21:50:21 +01:00
|
|
|
unless self.export_created_at.nil?
|
|
|
|
return (Time.now > self.export_created_at.in(1.week)) && !self.export_processing
|
|
|
|
end
|
|
|
|
!self.export_processing
|
2016-01-05 20:54:13 +01:00
|
|
|
end
|
2020-04-19 22:35:58 +02:00
|
|
|
|
|
|
|
# %w[admin moderator].each do |m|
|
|
|
|
# define_method(m) { raise "not allowed: #{m}" }
|
|
|
|
# define_method(m+??) { raise "not allowed: #{m}?"}
|
|
|
|
# define_method(m+?=) { |*a| raise "not allowed: #{m}="}
|
|
|
|
# end
|
2014-08-01 12:07:16 +02:00
|
|
|
end
|