X-Git-Url: https://git.openstreetmap.org./rails.git/blobdiff_plain/282ff4936cc1831fcbe9de0dd2d30c6bccd62b19..c3254b7e93a1f276759faf2b825479aa2a006032:/app/models/user.rb diff --git a/app/models/user.rb b/app/models/user.rb index e6d7a4e46..b3325e4eb 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1,3 +1,50 @@ +# == Schema Information +# +# Table name: users +# +# email :string not null +# id :integer not null, primary key +# pass_crypt :string not null +# creation_time :datetime not null +# display_name :string default(""), not null +# data_public :boolean default(FALSE), not null +# description :text default(""), not null +# home_lat :float +# home_lon :float +# home_zoom :integer default(3) +# nearby :integer default(50) +# pass_salt :string +# image_file_name :text +# email_valid :boolean default(FALSE), not null +# new_email :string +# creation_ip :string +# languages :string +# status :enum default("pending"), not null +# terms_agreed :datetime +# consider_pd :boolean default(FALSE), not null +# auth_uid :string +# preferred_editor :string +# terms_seen :boolean default(FALSE), not null +# description_format :enum default("markdown"), not null +# image_fingerprint :string +# changesets_count :integer default(0), not null +# traces_count :integer default(0), not null +# diary_entries_count :integer default(0), not null +# image_use_gravatar :boolean default(FALSE), not null +# image_content_type :string +# auth_provider :string +# home_tile :integer +# +# Indexes +# +# users_auth_idx (auth_provider,auth_uid) UNIQUE +# users_display_name_idx (display_name) UNIQUE +# users_display_name_lower_idx (lower((display_name)::text)) +# users_email_idx (email) UNIQUE +# users_email_lower_idx (lower((email)::text)) +# users_home_idx (home_tile) +# + class User < ActiveRecord::Base require "xml/libxml" @@ -9,7 +56,7 @@ class User < ActiveRecord::Base 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 :sent_messages, -> { where(:from_user_visible => true).order(:sent_on => :desc).preload(:sender, :recipient) }, :class_name => "Message", :foreign_key => :from_user_id - has_many :friends, -> { joins(:befriendee).where(:users => { :status => %w(active confirmed) }) } + has_many :friends, -> { joins(:befriendee).where(:users => { :status => %w[active confirmed] }) } has_many :friend_users, :through => :friends, :source => :befriendee has_many :tokens, :class_name => "UserToken" has_many :preferences, :class_name => "UserPreference" @@ -28,29 +75,40 @@ class User < ActiveRecord::Base has_many :roles, :class_name => "UserRole" - scope :visible, -> { where(:status => %w(pending active confirmed)) } - scope :active, -> { where(:status => %w(active confirmed)) } + has_many :issues, :class_name => "Issue", :foreign_key => :reported_user_id + has_many :issue_comments + + has_many :reports + + scope :visible, -> { where(:status => %w[pending active confirmed]) } + scope :active, -> { where(:status => %w[active confirmed]) } scope :identifiable, -> { where(:data_public => true) } has_attached_file :image, :default_url => "/assets/:class/:attachment/:style.png", :styles => { :large => "100x100>", :small => "50x50>" } + INVALID_CHARS = "/;.,?%#" + validates :display_name, :presence => true, :allow_nil => true, :length => 3..255, - :exclusion => %w(new terms save confirm confirm-email go_public reset-password forgot-password suspended) + :exclusion => %w[new terms save confirm confirm-email go_public reset-password forgot-password suspended] validates :display_name, :if => proc { |u| u.display_name_changed? }, :uniqueness => { :case_sensitive => false } validates :display_name, :if => proc { |u| u.display_name_changed? }, - :format => { :with => %r{\A[^\x00-\x1f\x7f\ufffe\uffff/;.,?%#]*\z} } + :format => { :with => %r{\A[^\x00-\x1f\x7f\ufffe\uffff]*\z} } + validates :display_name, :if => proc { |u| u.display_name_changed? }, + :format => { :with => %r{\A[^#{INVALID_CHARS}]*\z}, + :message => I18n.t("users.account.invalid chars", invalid_chars: INVALID_CHARS) } validates :display_name, :if => proc { |u| u.display_name_changed? }, - :format => { :with => /\A\S/, :message => "has leading whitespace" } + :format => { :with => /\A\S/, :message => I18n.t("users.account.leading whitespace") } validates :display_name, :if => proc { |u| u.display_name_changed? }, - :format => { :with => /\S\z/, :message => "has trailing whitespace" } + :format => { :with => /\S\z/, :message => I18n.t("users.account.trailing whitespace") } validates :email, :presence => true, :confirmation => true validates :email, :if => proc { |u| u.email_changed? }, :uniqueness => { :case_sensitive => false } validates :pass_crypt, :confirmation => true, :length => 8..255 - validates :home_lat, :home_lon, :allow_nil => true, :numericality => true + validates :home_lat, :allow_nil => true, :numericality => true, :inclusion => { :in => -90..90 } + validates :home_lon, :allow_nil => true, :numericality => true, :inclusion => { :in => -180..180 } validates :home_zoom, :allow_nil => true, :numericality => { :only_integer => true } validates :preferred_editor, :inclusion => Editors::ALL_EDITORS, :allow_nil => true validates :image, :attachment_content_type => { :content_type => %r{\Aimage/.*\Z} } @@ -62,7 +120,13 @@ class User < ActiveRecord::Base after_initialize :set_defaults before_save :encrypt_password + before_save :update_tile after_save :spam_check + after_save :reset_preferred_languages + + def to_param + display_name + end def self.authenticate(options) if options[:username] && options[:password] @@ -94,7 +158,7 @@ class User < ActiveRecord::Base user = nil end - token.update_column(:expiry, 1.week.from_now) if token && user + token.update(:expiry => 1.week.from_now) if token && user user end @@ -136,14 +200,24 @@ class User < ActiveRecord::Base end def preferred_languages - @locales ||= Locale.list(languages) + @preferred_languages ||= Locale.list(languages) + end + + def reset_preferred_languages + @preferred_languages = nil end def nearby(radius = NEARBY_RADIUS, num = NEARBY_USERS) if home_lon && home_lat 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") - nearby = User.where("id != ? AND status IN (\'active\', \'confirmed\') AND data_public = ? AND #{sql_for_distance} <= ?", id, true, radius).order(sql_for_distance).limit(num) + nearby = User.active.identifiable + .where("id != ?", id) + .where(sql_for_area) + .where("#{sql_for_distance} <= ?", radius) + .order(Arel.sql(sql_for_distance)) + .limit(num) else nearby = [] end @@ -161,13 +235,13 @@ class User < ActiveRecord::Base ## # returns true if a user is visible def visible? - %w(pending active confirmed).include? status + %w[pending active confirmed].include? status end ## # returns true if a user is active def active? - %w(active confirmed).include? status + %w[active confirmed].include? status end ## @@ -216,8 +290,8 @@ class User < ActiveRecord::Base def spam_score changeset_score = changesets.size * 50 trace_score = traces.size * 50 - diary_entry_score = diary_entries.inject(0) { |acc, elem| acc + elem.body.spam_score } - diary_comment_score = diary_comments.inject(0) { |acc, elem| acc + elem.body.spam_score } + diary_entry_score = diary_entries.visible.inject(0) { |acc, elem| acc + elem.body.spam_score } + diary_comment_score = diary_comments.visible.inject(0) { |acc, elem| acc + elem.body.spam_score } score = description.spam_score / 4.0 score += diary_entries.where("created_at > ?", 1.day.ago).count * 10 @@ -232,9 +306,7 @@ class User < ActiveRecord::Base ## # perform a spam check on a user def spam_check - if status == "active" && spam_score > SPAM_THRESHOLD - update_column(:status, "suspended") - end + update(:status => "suspended") if status == "active" && spam_score > SPAM_THRESHOLD end ## @@ -255,4 +327,8 @@ class User < ActiveRecord::Base self.pass_crypt_confirmation = nil end end + + def update_tile + self.home_tile = QuadTile.tile_for_point(home_lat, home_lon) if home_lat && home_lon + end end