X-Git-Url: https://git.openstreetmap.org./rails.git/blobdiff_plain/2731e7244ac554c4a17f69347851f8f829cd1caa..c95e2870f3310a68f8287bddcec6d0117b5f8961:/app/models/user.rb diff --git a/app/models/user.rb b/app/models/user.rb index cc12adecc..5790d81e5 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -12,7 +12,6 @@ # home_lat :float # home_lon :float # home_zoom :integer default(3) -# nearby :integer default(50) # pass_salt :string # email_valid :boolean default(FALSE), not null # new_email :string @@ -48,37 +47,41 @@ class User < ApplicationRecord include AASM has_many :traces, -> { where(:visible => true) } - has_many :diary_entries, -> { order(:created_at => :desc) } - has_many :diary_comments, -> { order(:created_at => :desc) } + has_many :diary_entries, -> { order(:created_at => :desc) }, :inverse_of => :user + has_many :diary_comments, -> { order(:created_at => :desc) }, :inverse_of => :user has_many :diary_entry_subscriptions, :class_name => "DiaryEntrySubscription" has_many :diary_subscriptions, :through => :diary_entry_subscriptions, :source => :diary_entry - has_many :messages, -> { where(:to_user_visible => true).order(:sent_on => :desc).preload(:sender, :recipient) }, :foreign_key => :to_user_id - has_many :new_messages, -> { where(:to_user_visible => true, :message_read => false).order(:sent_on => :desc) }, :class_name => "Message", :foreign_key => :to_user_id + has_many :messages, -> { where(:to_user_visible => true, :muted => false).order(:sent_on => :desc).preload(:sender, :recipient) }, :foreign_key => :to_user_id + has_many :new_messages, -> { where(:to_user_visible => true, :muted => false, :message_read => false).order(:sent_on => :desc) }, :class_name => "Message", :foreign_key => :to_user_id has_many :sent_messages, -> { where(:from_user_visible => true).order(:sent_on => :desc).preload(:sender, :recipient) }, :class_name => "Message", :foreign_key => :from_user_id + has_many :muted_messages, -> { where(:to_user_visible => true, :muted => true).order(:sent_on => :desc).preload(:sender, :recipient) }, :class_name => "Message", :foreign_key => :to_user_id has_many :friendships, -> { joins(:befriendee).where(:users => { :status => %w[active confirmed] }) } has_many :friends, :through => :friendships, :source => :befriendee has_many :tokens, :class_name => "UserToken", :dependent => :destroy has_many :preferences, :class_name => "UserPreference" - has_many :changesets, -> { order(:created_at => :desc) } - has_many :changeset_comments, :foreign_key => :author_id + has_many :changesets, -> { order(:created_at => :desc) }, :inverse_of => :user + has_many :changeset_comments, :foreign_key => :author_id, :inverse_of => :author has_and_belongs_to_many :changeset_subscriptions, :class_name => "Changeset", :join_table => "changesets_subscribers", :foreign_key => "subscriber_id" - has_many :note_comments, :foreign_key => :author_id + has_many :note_comments, :foreign_key => :author_id, :inverse_of => :author has_many :notes, :through => :note_comments has_many :client_applications - has_many :oauth_tokens, -> { order(:authorized_at => :desc).preload(:client_application) }, :class_name => "OauthToken" + has_many :oauth_tokens, -> { order(:authorized_at => :desc).preload(:client_application) }, :class_name => "OauthToken", :inverse_of => :user has_many :oauth2_applications, :class_name => Doorkeeper.config.application_model.name, :as => :owner has_many :access_grants, :class_name => Doorkeeper.config.access_grant_model.name, :foreign_key => :resource_owner_id has_many :access_tokens, :class_name => Doorkeeper.config.access_token_model.name, :foreign_key => :resource_owner_id has_many :blocks, :class_name => "UserBlock" - has_many :blocks_created, :class_name => "UserBlock", :foreign_key => :creator_id - has_many :blocks_revoked, :class_name => "UserBlock", :foreign_key => :revoker_id + has_many :blocks_created, :class_name => "UserBlock", :foreign_key => :creator_id, :inverse_of => :creator + has_many :blocks_revoked, :class_name => "UserBlock", :foreign_key => :revoker_id, :inverse_of => :revoker + + has_many :mutes, -> { order(:created_at => :desc) }, :class_name => "UserMute", :foreign_key => :owner_id, :inverse_of => :owner + has_many :muted_users, :through => :mutes, :source => :subject has_many :roles, :class_name => "UserRole" - has_many :issues, :class_name => "Issue", :foreign_key => :reported_user_id + has_many :issues, :class_name => "Issue", :foreign_key => :reported_user_id, :inverse_of => :reported_user has_many :issue_comments has_many :reports @@ -87,7 +90,7 @@ class User < ApplicationRecord scope :active, -> { where(:status => %w[active confirmed]) } scope :identifiable, -> { where(:data_public => true) } - has_one_attached :avatar + has_one_attached :avatar, :service => Settings.avatar_storage validates :display_name, :presence => true, :length => 3..255, :exclusion => %w[new terms save confirm confirm-email go_public reset-password forgot-password suspended] @@ -116,6 +119,7 @@ class User < ApplicationRecord alias_attribute :created_at, :creation_time + after_initialize :encrypt_password before_save :encrypt_password before_save :update_tile after_save :spam_check @@ -172,8 +176,10 @@ class User < ApplicationRecord end # Used in test suite, not something that we would normally need to do. - event :deactivate do - transitions :from => :active, :to => :pending + if Rails.env.test? + event :deactivate do + transitions :from => :active, :to => :pending + end end # To confirm an account is used to override the spam scoring @@ -208,6 +214,7 @@ class User < ApplicationRecord # Mark the account as deleted and remove personal data event :soft_destroy do before do + revoke_authentication_tokens remove_personal_data end @@ -235,8 +242,12 @@ class User < ApplicationRecord @preferred_languages ||= Locale.list(languages) end + def home_location? + home_lat && home_lon + end + def nearby(radius = Settings.nearby_radius, num = Settings.nearby_users) - if home_lon && home_lat + if home_location? gc = OSM::GreatCircle.new(home_lat, home_lon) sql_for_area = QuadTile.sql_for_area(gc.bounds(radius), "home_") sql_for_distance = gc.sql_for_distance("home_lat", "home_lon") @@ -256,7 +267,7 @@ class User < ApplicationRecord OSM::GreatCircle.new(home_lat, home_lon).distance(nearby_user.home_lat, nearby_user.home_lon) end - def is_friends_with?(new_friend) + def friends_with?(new_friend) friendships.exists?(:befriendee => new_friend) end @@ -275,18 +286,24 @@ class User < ApplicationRecord ## # returns true if the user has the moderator role, false otherwise def moderator? - has_role? "moderator" + role? "moderator" end ## # returns true if the user has the administrator role, false otherwise def administrator? - has_role? "administrator" + role? "administrator" + end + + ## + # returns true if the user has the importer role, false otherwise + def importer? + role? "importer" end ## # returns true if the user has the requested role - def has_role?(role) + def role?(role) roles.any? { |r| r.role == role } end @@ -297,6 +314,13 @@ class User < ApplicationRecord blocks.active.detect(&:needs_view?) end + ## + # revoke any authentication tokens + def revoke_authentication_tokens + oauth_tokens.authorized.each(&:invalidate!) + access_tokens.not_expired.each(&:revoke) + end + ## # remove personal data - leave the account but purge most personal data def remove_personal_data @@ -363,12 +387,19 @@ class User < ApplicationRecord digest.hexdigest end + def active_reports + issues + .with_status(:open) + .joins(:reports) + .where("reports.updated_at >= COALESCE(issues.resolved_at, '1970-01-01')") + .count + end + def max_messages_per_hour account_age_in_seconds = Time.now.utc - created_at account_age_in_hours = account_age_in_seconds / 3600 recent_messages = messages.where("sent_on >= ?", Time.now.utc - 3600).count - active_reports = issues.with_status(:open).sum(:reports_count) - max_messages = account_age_in_hours.ceil + recent_messages - active_reports * 10 + max_messages = account_age_in_hours.ceil + recent_messages - (active_reports * 10) max_messages.clamp(0, Settings.max_messages_per_hour) end @@ -376,11 +407,34 @@ class User < ApplicationRecord account_age_in_seconds = Time.now.utc - created_at account_age_in_hours = account_age_in_seconds / 3600 recent_friends = Friendship.where(:befriendee => self).where("created_at >= ?", Time.now.utc - 3600).count - active_reports = issues.with_status(:open).sum(:reports_count) - max_friends = account_age_in_hours.ceil + recent_friends - active_reports * 10 + max_friends = account_age_in_hours.ceil + recent_friends - (active_reports * 10) max_friends.clamp(0, Settings.max_friends_per_hour) end + def max_changeset_comments_per_hour + if moderator? + Settings.moderator_changeset_comments_per_hour + else + previous_comments = changeset_comments.limit(200).count + max_comments = previous_comments / 200.0 * Settings.max_changeset_comments_per_hour + max_comments = max_comments.floor.clamp(Settings.initial_changeset_comments_per_hour, Settings.max_changeset_comments_per_hour) + max_comments /= 2**active_reports + max_comments.floor.clamp(Settings.min_changeset_comments_per_hour, Settings.max_changeset_comments_per_hour) + end + end + + def deletion_allowed_at + unless Settings.user_account_deletion_delay.nil? + last_changeset = changesets.reorder(:closed_at => :desc).first + return last_changeset.closed_at.utc + Settings.user_account_deletion_delay.hours if last_changeset + end + creation_time.utc + end + + def deletion_allowed? + deletion_allowed_at <= Time.now.utc + end + private def encrypt_password @@ -391,6 +445,6 @@ class User < ApplicationRecord end def update_tile - self.home_tile = QuadTile.tile_for_point(home_lat, home_lon) if home_lat && home_lon + self.home_tile = QuadTile.tile_for_point(home_lat, home_lon) if home_location? end end