class User < ApplicationRecord include Verification devise :database_authenticatable, :registerable, :confirmable, :recoverable, :rememberable, :trackable, :validatable, :omniauthable, :password_expirable, :secure_validatable, authentication_keys: [:login] acts_as_voter acts_as_paranoid column: :hidden_at include ActsAsParanoidAliases include Graphqlable has_one :administrator has_one :moderator has_one :valuator has_one :manager has_one :sdg_manager, class_name: "SDG::Manager", dependent: :destroy has_one :poll_officer, class_name: "Poll::Officer" has_one :organization has_one :lock has_many :flags has_many :identities, dependent: :destroy has_many :debates, -> { with_hidden }, foreign_key: :author_id, inverse_of: :author has_many :proposals, -> { with_hidden }, foreign_key: :author_id, inverse_of: :author has_many :activities has_many :budget_investments, -> { with_hidden }, class_name: "Budget::Investment", foreign_key: :author_id, inverse_of: :author has_many :comments, -> { with_hidden }, inverse_of: :user has_many :failed_census_calls has_many :notifications has_many :direct_messages_sent, class_name: "DirectMessage", foreign_key: :sender_id, inverse_of: :sender has_many :direct_messages_received, class_name: "DirectMessage", foreign_key: :receiver_id, inverse_of: :receiver has_many :legislation_answers, class_name: "Legislation::Answer", dependent: :destroy, inverse_of: :user has_many :follows has_many :legislation_annotations, class_name: "Legislation::Annotation", foreign_key: :author_id, inverse_of: :author has_many :legislation_proposals, class_name: "Legislation::Proposal", foreign_key: :author_id, inverse_of: :author has_many :legislation_questions, class_name: "Legislation::Question", foreign_key: :author_id, inverse_of: :author has_many :polls, foreign_key: :author_id, inverse_of: :author has_many :poll_answers, class_name: "Poll::Answer", foreign_key: :author_id, inverse_of: :author has_many :poll_pair_answers, class_name: "Poll::PairAnswer", foreign_key: :author_id, inverse_of: :author has_many :poll_partial_results, class_name: "Poll::PartialResult", foreign_key: :author_id, inverse_of: :author has_many :poll_questions, class_name: "Poll::Question", foreign_key: :author_id, inverse_of: :author has_many :poll_recounts, class_name: "Poll::Recount", foreign_key: :author_id, inverse_of: :author has_many :related_contents, foreign_key: :author_id, inverse_of: :author, dependent: nil has_many :topics, foreign_key: :author_id, inverse_of: :author belongs_to :geozone validates :username, presence: true, if: :username_required? validates :username, uniqueness: { scope: :registering_with_oauth }, if: :username_required? validates :document_number, uniqueness: { scope: :document_type }, allow_nil: true validate :validate_username_length validates :official_level, inclusion: { in: 0..5 } validates :terms_of_service, acceptance: { allow_nil: false }, on: :create validates_associated :organization, message: false accepts_nested_attributes_for :organization, update_only: true attr_accessor :skip_password_validation, :use_redeemable_code, :login scope :administrators, -> { joins(:administrator) } scope :moderators, -> { joins(:moderator) } scope :organizations, -> { joins(:organization) } scope :sdg_managers, -> { joins(:sdg_manager) } scope :officials, -> { where("official_level > 0") } scope :male, -> { where(gender: "male") } scope :female, -> { where(gender: "female") } scope :newsletter, -> { where(newsletter: true) } scope :for_render, -> { includes(:organization) } scope :by_document, ->(document_type, document_number) do where(document_type: document_type, document_number: document_number) end scope :email_digest, -> { where(email_digest: true) } scope :active, -> { where(erased_at: nil) } scope :erased, -> { where.not(erased_at: nil) } scope :public_for_api, -> { all } scope :by_authors, ->(author_ids) { where(id: author_ids) } scope :by_comments, ->(commentables) do joins(:comments).where("comments.commentable": commentables).distinct end scope :by_username_email_or_document_number, ->(search_string) do search = "%#{search_string.strip}%" where("username ILIKE ? OR email ILIKE ? OR document_number ILIKE ?", search, search, search) end scope :between_ages, ->(from, to) do where( "date_of_birth > ? AND date_of_birth < ?", to.years.ago.beginning_of_year, from.years.ago.end_of_year ) end before_validation :clean_document_number # Get the existing user by email if the provider gives us a verified email. def self.first_or_initialize_for_oauth(auth) oauth_email = auth.info.email oauth_email_confirmed = oauth_email.present? && (auth.info.verified || auth.info.verified_email) oauth_user = User.find_by(email: oauth_email) if oauth_email_confirmed oauth_user || User.new( username: auth.info.name || auth.uid, email: oauth_email, oauth_email: oauth_email, password: Devise.friendly_token[0, 20], terms_of_service: "1", confirmed_at: oauth_email_confirmed ? DateTime.current : nil ) end def name organization? ? organization.name : username end def comment_flags(comments) comment_flags = flags.for_comments(comments) comment_flags.each_with_object({}) { |f, h| h[f.flaggable_id] = true } end def voted_in_group?(group) votes.where(votable: Budget::Investment.where(group: group)).exists? end def headings_voted_within_group(group) Budget::Heading.where(id: voted_investments.by_group(group).pluck(:heading_id)) end def voted_investments Budget::Investment.where(id: votes.where(votable: Budget::Investment.all).pluck(:votable_id)) end def administrator? administrator.present? end def moderator? moderator.present? end def valuator? valuator.present? end def manager? manager.present? end def sdg_manager? sdg_manager.present? end def poll_officer? poll_officer.present? end def organization? organization.present? end def verified_organization? organization&.verified? end def official? official_level && official_level > 0 end def add_official_position!(position, level) return if position.blank? || level.blank? update! official_position: position, official_level: level.to_i end def remove_official_position! update! official_position: nil, official_level: 0 end def has_official_email? domain = Setting["email_domain_for_officials"] email.present? && ((email.end_with? "@#{domain}") || (email.end_with? ".#{domain}")) end def display_official_position_badge? return true if official_level > 1 official_position_badge? && official_level == 1 end def block hide Debate.hide_all debate_ids Comment.hide_all comment_ids Legislation::Proposal.hide_all legislation_proposal_ids Proposal.hide_all proposal_ids Budget::Investment.hide_all budget_investment_ids ProposalNotification.hide_all ProposalNotification.where(author_id: id).ids end def full_restore ActiveRecord::Base.transaction do Debate.restore_all debates.where("hidden_at >= ?", hidden_at) Comment.restore_all comments.where("hidden_at >= ?", hidden_at) Legislation::Proposal.restore_all legislation_proposals.only_hidden.where("hidden_at >= ?", hidden_at) Proposal.restore_all proposals.where("hidden_at >= ?", hidden_at) Budget::Investment.restore_all budget_investments.where("hidden_at >= ?", hidden_at) ProposalNotification.restore_all( ProposalNotification.only_hidden.where("hidden_at >= ?", hidden_at).where(author_id: id) ) restore end end def erase(erase_reason = nil) update!( erased_at: Time.current, erase_reason: erase_reason, username: nil, email: nil, unconfirmed_email: nil, phone_number: nil, encrypted_password: "", confirmation_token: nil, reset_password_token: nil, email_verification_token: nil, confirmed_phone: nil, unconfirmed_phone: nil ) identities.destroy_all end def erased? erased_at.present? end def take_votes_if_erased_document(document_number, document_type) erased_user = User.erased.find_by(document_number: document_number, document_type: document_type) if erased_user.present? take_votes_from(erased_user) erased_user.update!(document_number: nil, document_type: nil) end end def take_votes_from(other_user) return if other_user.blank? Poll::Voter.where(user_id: other_user.id).update_all(user_id: id) Budget::Ballot.where(user_id: other_user.id).update_all(user_id: id) Vote.where("voter_id = ? AND voter_type = ?", other_user.id, "User").update_all(voter_id: id) data_log = "id: #{other_user.id} - #{Time.current.strftime("%Y-%m-%d %H:%M:%S")}" update!(former_users_data_log: "#{former_users_data_log} | #{data_log}") end def locked? Lock.find_or_create_by!(user: self).locked? end def self.search(term) return none if term.blank? search = term.strip where("email = ? OR username ILIKE ?", search, "%#{search}%") end def self.username_max_length @username_max_length ||= columns.find { |c| c.name == "username" }.limit || 60 end def self.minimum_required_age (Setting["min_age_to_participate"] || 16).to_i end def show_welcome_screen? verification = Setting["feature.user.skip_verification"].present? ? true : unverified? sign_in_count == 1 && verification && !organization && !administrator? end def password_required? return false if skip_password_validation super end def username_required? !organization? && !erased? end def email_required? !erased? && unverified? end def locale self[:locale] ||= I18n.default_locale.to_s end def confirmation_required? super && !registering_with_oauth end def send_oauth_confirmation_instructions if oauth_email != email || confirmed_at.nil? update(confirmed_at: nil) send_confirmation_instructions end update(oauth_email: nil) if oauth_email.present? end def name_and_email "#{name} (#{email})" end def age Age.in_years(date_of_birth) end def save_requiring_finish_signup begin self.registering_with_oauth = true save!(validate: false) # Devise puts unique constraints for the email the db, so we must detect & handle that rescue ActiveRecord::RecordNotUnique self.email = nil save!(validate: false) end true end def ability @ability ||= Ability.new(self) end delegate :can?, :cannot?, to: :ability def public_proposals public_activity? ? proposals : User.none end def public_debates public_activity? ? debates : User.none end def public_comments public_activity? ? comments : User.none end # overwritting of Devise method to allow login using email OR username def self.find_for_database_authentication(warden_conditions) conditions = warden_conditions.dup login = conditions.delete(:login) where(conditions.to_hash).find_by(["lower(email) = ?", login.downcase]) || where(conditions.to_hash).find_by(["username = ?", login]) end def self.find_by_manager_login(manager_login) find_by(id: manager_login.split("_").last) end def interests followables = follows.map(&:followable) followables.compact.map { |followable| followable.tags.map(&:name) }.flatten.compact.uniq end def send_devise_notification(notification, *args) devise_mailer.send(notification, self, *args).deliver_later end def add_subscriptions_token update!(subscriptions_token: SecureRandom.base58(32)) if subscriptions_token.blank? end private def clean_document_number return unless document_number.present? self.document_number = document_number.gsub(/[^a-z0-9]+/i, "").upcase end def validate_username_length validator = ActiveModel::Validations::LengthValidator.new( attributes: :username, maximum: User.username_max_length) validator.validate(self) end end