* Allow user to have many orgs * Allow users to handle multi orgs in settings * Make rounded buttons inline * Add multi org function to dashboards * Fix merge conflicts * Fix mistake in merge conflict fix oops * Display the correct membership level * Fix accessibility issues * Display organizations for article editors * Handle submitting org id with preact editors * Make listings work with multiple organizations * Allow listings to have multiple orgs on create * Display the correct number of credits for each org * Move script tag to Webpack * Allow multi orgs for purchasing and viewing credits * Use OrganizationMembership as authorization check * Display multiple organizations for notifications * Allow dashboard to be viewable under multi-orgs * Remove unused method * Add multi-org functionality for article editors * Show pro dashboard buttons for member+ org levels * Leave the correct organization * Allow article API to change org id * Add left-out authorization method oops * Make nav buttons a bit more clear * Fix merge conflict * Fix adding org id for /api/articles and tests * Fix tests for org policy * Use proper logic for displaying org members * Update org actions with new authorization * Use correct org when creating a listing * Remove additional payment charge oops * Mark org notifications as read with authorization * Remove deprecated post_as_organization attribute * Use new org_admin syntax * Remove deprecated org logic for article create and update * Default all RSS posts to not belong to any org * Render org_member page for guest users * Update org policy spec to work with multi orgs * Use org_membership for org traits and move identity code * Use org_member trait * Update to work with multi-orgs * Validate article's org_id if param org_id is blank * Make a let variable * Remove unnecessary eager load for credits * Fix HTML structure and org logic for non-org users * Update credits spec for multi-org * Add test for failed payment when purchased by org * Lint listings_spec * Test that the listing was created under the user * Add tests for POST /listings multi-org * Use double quotes for classes * Fix /manage and a few other multi-org bugs * Fix test for multi org * Use correct method SQL exists? not Rails exist? * Fix reads spec for multi-org * Fix org_controller actions to work with multi org * Test only multi org and not old usage and fix leave_org * Fix org showing user profile img test for multi-org * Fix org logic for users with no orgs * Remove switch org functionality * Update tests and add hidden param for org id * Redirect to the specific organization * Test other org button actions * Use settings_notice instead of legacy notice and refactor * Fix weird extra end issue prob from merge conflicts * Test for with new flash key * Fix user_views_org tests for multi-org * Test for new flash message * Update snapshot with new a11y html * Move styling to stylesheet * Add site admins functionality * Move org_member? method in user model and refactor * Use unspent_credits_count for organizations * Add tests for /listings/new and minor bug fixes * Use .present? in case of empty array * Fix a lingering deprecated method * Use greater than 1 for random numbers * Add tests for counting spent and unspent credits
645 lines
20 KiB
Ruby
645 lines
20 KiB
Ruby
class User < ApplicationRecord
|
|
include CloudinaryHelper
|
|
|
|
attr_accessor :scholar_email, :new_note, :quick_match, :mentorship_note, :note_for_current_role, :add_mentor, :add_mentee, :user_status, :toggle_mentorship, :pro, :merge_user_id, :add_credits, :remove_credits, :add_org_credits, :remove_org_credits, :ghostify
|
|
|
|
rolify
|
|
include AlgoliaSearch
|
|
include Storext.model
|
|
|
|
acts_as_followable
|
|
acts_as_follower
|
|
|
|
has_many :organization_memberships
|
|
has_many :organizations, through: :organization_memberships
|
|
has_many :api_secrets, dependent: :destroy
|
|
has_many :articles, dependent: :destroy
|
|
has_many :badge_achievements, dependent: :destroy
|
|
has_many :badges, through: :badge_achievements
|
|
has_many :collections, dependent: :destroy
|
|
has_many :comments, dependent: :destroy
|
|
has_many :email_messages, class_name: "Ahoy::Message"
|
|
has_many :github_repos, dependent: :destroy
|
|
has_many :identities, dependent: :destroy
|
|
has_many :mentions, dependent: :destroy
|
|
has_many :messages, dependent: :destroy
|
|
has_many :notes, as: :noteable, inverse_of: :noteable
|
|
has_many :authored_notes, as: :author, inverse_of: :author, class_name: "Note"
|
|
has_many :notifications, dependent: :destroy
|
|
has_many :reactions, dependent: :destroy
|
|
has_many :tweets, dependent: :destroy
|
|
has_many :chat_channel_memberships, dependent: :destroy
|
|
has_many :chat_channels, through: :chat_channel_memberships
|
|
has_many :push_notification_subscriptions, dependent: :destroy
|
|
has_many :feedback_messages
|
|
has_many :rating_votes
|
|
has_many :html_variants, dependent: :destroy
|
|
has_many :page_views
|
|
has_many :credits
|
|
has_many :classified_listings
|
|
has_many :mentor_relationships_as_mentee,
|
|
class_name: "MentorRelationship", foreign_key: "mentee_id", inverse_of: :mentee
|
|
has_many :mentor_relationships_as_mentor,
|
|
class_name: "MentorRelationship", foreign_key: "mentor_id", inverse_of: :mentor
|
|
has_many :mentors,
|
|
through: :mentor_relationships_as_mentee,
|
|
source: :mentor
|
|
has_many :mentees,
|
|
through: :mentor_relationships_as_mentor,
|
|
source: :mentee
|
|
|
|
mount_uploader :profile_image, ProfileImageUploader
|
|
|
|
devise :omniauthable, :rememberable,
|
|
:registerable, :database_authenticatable, :confirmable
|
|
validates :email,
|
|
length: { maximum: 50 },
|
|
email: true,
|
|
allow_nil: true
|
|
validates :email, uniqueness: { allow_nil: true, case_sensitive: false }, if: :email_changed?
|
|
validates :name, length: { minimum: 1, maximum: 100 }
|
|
validates :username,
|
|
presence: true,
|
|
format: { with: /\A[a-zA-Z0-9_]+\Z/ },
|
|
length: { in: 2..30 },
|
|
exclusion: { in: ReservedWords.all, message: "username is reserved" }
|
|
validates :username, uniqueness: { case_sensitive: false }, if: :username_changed?
|
|
validates :twitter_username, uniqueness: { allow_nil: true }, if: :twitter_username_changed?
|
|
validates :github_username, uniqueness: { allow_nil: true }, if: :github_username_changed?
|
|
validates :experience_level, numericality: { less_than_or_equal_to: 10 }, allow_blank: true
|
|
validates :text_color_hex, format: /\A#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})\z/, allow_blank: true
|
|
validates :bg_color_hex, format: /\A#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})\z/, allow_blank: true
|
|
validates :website_url, :employer_url, :mastodon_url,
|
|
url: { allow_blank: true, no_local: true, schemes: %w[https http] }
|
|
validates :facebook_url,
|
|
format: /\A(http(s)?:\/\/)?(www.facebook.com|facebook.com)\/.*\Z/,
|
|
allow_blank: true
|
|
validates :stackoverflow_url,
|
|
allow_blank: true,
|
|
format:
|
|
/\A(http(s)?:\/\/)?(www.stackoverflow.com|stackoverflow.com|www.stackexchange.com|stackexchange.com)\/.*\Z/
|
|
validates :behance_url,
|
|
allow_blank: true,
|
|
format: /\A(http(s)?:\/\/)?(www.behance.net|behance.net)\/.*\Z/
|
|
validates :linkedin_url,
|
|
allow_blank: true,
|
|
format:
|
|
/\A(http(s)?:\/\/)?(www.linkedin.com|linkedin.com|[A-Za-z]{2}.linkedin.com)\/.*\Z/
|
|
validates :dribbble_url,
|
|
allow_blank: true,
|
|
format: /\A(http(s)?:\/\/)?(www.dribbble.com|dribbble.com)\/.*\Z/
|
|
validates :medium_url,
|
|
allow_blank: true,
|
|
format: /\A(http(s)?:\/\/)?(www.medium.com|medium.com)\/.*\Z/
|
|
validates :gitlab_url,
|
|
allow_blank: true,
|
|
format: /\A(http(s)?:\/\/)?(www.gitlab.com|gitlab.com)\/.*\Z/
|
|
validates :twitch_url,
|
|
allow_blank: true,
|
|
format: /\A(http(s)?:\/\/)?(www.twitch.tv|twitch.tv)\/.*\Z/
|
|
validates :shirt_gender,
|
|
inclusion: { in: %w[unisex womens],
|
|
message: "%{value} is not a valid shirt style" },
|
|
allow_blank: true
|
|
validates :shirt_size,
|
|
inclusion: { in: %w[xs s m l xl 2xl 3xl 4xl],
|
|
message: "%{value} is not a valid size" },
|
|
allow_blank: true
|
|
validates :tabs_or_spaces,
|
|
inclusion: { in: %w[tabs spaces],
|
|
message: "%{value} is not a valid answer" },
|
|
allow_blank: true
|
|
validates :editor_version,
|
|
inclusion: { in: %w[v1 v2],
|
|
message: "%{value} must be either v1 or v2" }
|
|
|
|
validates :config_theme,
|
|
inclusion: { in: %w[default night_theme pink_theme],
|
|
message: "%{value} must be either default, pink theme, or night theme" }
|
|
validates :config_font,
|
|
inclusion: { in: %w[default sans_serif comic_sans],
|
|
message: "%{value} must be either default or sans serif" }
|
|
validates :shipping_country,
|
|
length: { in: 2..2 },
|
|
allow_blank: true
|
|
validates :website_url, :employer_name, :employer_url,
|
|
length: { maximum: 100 }
|
|
validates :employment_title, :education, :location,
|
|
length: { maximum: 100 }
|
|
validates :mostly_work_with, :currently_learning,
|
|
:currently_hacking_on, :available_for,
|
|
length: { maximum: 500 }
|
|
validates :mentee_description, :mentor_description,
|
|
length: { maximum: 1000 }
|
|
validates :inbox_type, inclusion: { in: %w[open private] }
|
|
validates :currently_streaming_on, inclusion: { in: %w[twitch] }, allow_nil: true
|
|
validate :conditionally_validate_summary
|
|
validate :validate_mastodon_url
|
|
validate :validate_feed_url, if: :feed_url_changed?
|
|
validate :unique_including_orgs, if: :username_changed?
|
|
|
|
scope :dev_account, -> { find_by(id: ApplicationConfig["DEVTO_USER_ID"]) }
|
|
|
|
after_create :send_welcome_notification
|
|
after_save :bust_cache
|
|
after_save :subscribe_to_mailchimp_newsletter
|
|
after_save :conditionally_resave_articles
|
|
after_create :estimate_default_language!
|
|
before_create :set_default_language
|
|
before_update :mentorship_status_update
|
|
before_validation :set_username
|
|
# make sure usernames are not empty, to be able to use the database unique index
|
|
before_validation :verify_twitter_username, :verify_github_username, :verify_email, :verify_twitch_username
|
|
before_validation :set_config_input
|
|
before_validation :downcase_email
|
|
before_validation :check_for_username_change
|
|
before_destroy :remove_from_algolia_index
|
|
before_destroy :destroy_empty_dm_channels
|
|
before_destroy :destroy_follows
|
|
before_destroy :unsubscribe_from_newsletters
|
|
|
|
algoliasearch per_environment: true, enqueue: :trigger_delayed_index do
|
|
attribute :name
|
|
add_index "searchables",
|
|
id: :index_id,
|
|
per_environment: true,
|
|
enqueue: :trigger_delayed_index do
|
|
attribute :user do
|
|
{ username: user.username,
|
|
name: user.username,
|
|
profile_image_90: profile_image_90 }
|
|
end
|
|
attribute :title, :path, :tag_list, :main_image, :id,
|
|
:featured, :published, :published_at, :featured_number, :comments_count,
|
|
:reactions_count, :positive_reactions_count, :class_name, :user_name,
|
|
:user_username, :comments_blob, :body_text, :tag_keywords_for_search,
|
|
:search_score, :hotness_score
|
|
searchableAttributes ["unordered(title)",
|
|
"body_text",
|
|
"tag_list",
|
|
"tag_keywords_for_search",
|
|
"user_name",
|
|
"user_username",
|
|
"comments_blob"]
|
|
attributesForFaceting [:class_name]
|
|
customRanking ["desc(search_score)", "desc(hotness_score)"]
|
|
end
|
|
end
|
|
|
|
def estimated_default_language
|
|
language_settings["estimated_default_language"]
|
|
end
|
|
|
|
def self.trigger_delayed_index(record, remove)
|
|
if remove
|
|
record.delay.remove_from_index! if record&.persisted?
|
|
else
|
|
record.delay.index!
|
|
end
|
|
end
|
|
|
|
def tag_line
|
|
summary
|
|
end
|
|
|
|
def index_id
|
|
"users-#{id}"
|
|
end
|
|
|
|
def set_remember_fields
|
|
self.remember_token ||= self.class.remember_token if respond_to?(:remember_token)
|
|
self.remember_created_at ||= Time.now.utc
|
|
end
|
|
|
|
def estimate_default_language!
|
|
Users::EstimateDefaultLanguageJob.perform_later(id)
|
|
end
|
|
|
|
def estimate_default_language_without_delay!
|
|
Users::EstimateDefaultLanguageJob.perform_now(id)
|
|
end
|
|
|
|
def calculate_score
|
|
score = (articles.where(featured: true).size * 100) + comments.sum(:score)
|
|
update_column(:score, score)
|
|
end
|
|
|
|
def path
|
|
"/" + username.to_s
|
|
end
|
|
|
|
def followed_articles
|
|
Article.tagged_with(cached_followed_tag_names, any: true).
|
|
union(Article.where(user_id: cached_following_users_ids)).
|
|
where(language: cached_preferred_langs, published: true)
|
|
end
|
|
|
|
def cached_following_users_ids
|
|
Rails.cache.fetch(
|
|
"user-#{id}-#{updated_at}-#{following_users_count}/following_users_ids",
|
|
expires_in: 120.hours,
|
|
) do
|
|
Follow.where(follower_id: id, followable_type: "User").limit(150).pluck(:followable_id)
|
|
end
|
|
end
|
|
|
|
def cached_following_organizations_ids
|
|
Rails.cache.fetch(
|
|
"user-#{id}-#{updated_at}-#{following_orgs_count}/following_organizations_ids",
|
|
expires_in: 120.hours,
|
|
) do
|
|
Follow.where(follower_id: id, followable_type: "Organization").limit(150).pluck(:followable_id)
|
|
end
|
|
end
|
|
|
|
def cached_following_podcasts_ids
|
|
Rails.cache.fetch(
|
|
"user-#{id}-#{updated_at}-#{last_followed_at}/following_podcasts_ids",
|
|
expires_in: 120.hours,
|
|
) do
|
|
Follow.where(follower_id: id, followable_type: "Podcast").pluck(:followable_id)
|
|
end
|
|
end
|
|
|
|
def cached_preferred_langs
|
|
Rails.cache.fetch("user-#{id}-#{updated_at}/cached_preferred_langs", expires_in: 24.hours) do
|
|
preferred_languages_array
|
|
end
|
|
end
|
|
|
|
# handles both old (prefer_language_*) and new (Array of language codes) formats
|
|
def preferred_languages_array
|
|
# return @prefer_languages_array if defined? @preferred_languages_array
|
|
return @preferred_languages_array if defined?(@preferred_languages_array)
|
|
|
|
if language_settings["preferred_languages"].present?
|
|
@preferred_languages_array = language_settings["preferred_languages"].to_a
|
|
else
|
|
languages = []
|
|
language_settings.each_key do |setting|
|
|
languages << setting.split("prefer_language_")[1] if language_settings[setting] && setting.include?("prefer_language_")
|
|
end
|
|
@preferred_languages_array = languages
|
|
end
|
|
@preferred_languages_array
|
|
end
|
|
|
|
def processed_website_url
|
|
website_url.to_s.strip if website_url.present?
|
|
end
|
|
|
|
def remember_me
|
|
true
|
|
end
|
|
|
|
def cached_followed_tag_names
|
|
cache_name = "user-#{id}-#{updated_at}/followed_tag_names"
|
|
Rails.cache.fetch(cache_name, expires_in: 24.hours) do
|
|
Tag.where(
|
|
id: Follow.where(
|
|
follower_id: id,
|
|
followable_type: "ActsAsTaggableOn::Tag",
|
|
).pluck(:followable_id),
|
|
).pluck(:name)
|
|
end
|
|
end
|
|
|
|
# methods for Administrate field
|
|
def banned
|
|
has_role? :banned
|
|
end
|
|
|
|
def warned
|
|
has_role? :warned
|
|
end
|
|
|
|
def banished?
|
|
user.notes.where(reason: "banned", content: "spam account").any? && user.banned && user.comments.none? && user.articles.none?
|
|
end
|
|
|
|
def banned_from_mentorship
|
|
has_role? :banned_from_mentorship
|
|
end
|
|
|
|
def admin?
|
|
has_role?(:super_admin)
|
|
end
|
|
|
|
def any_admin?
|
|
has_role?(:super_admin) || has_role?(:admin)
|
|
end
|
|
|
|
def tech_admin?
|
|
has_role?(:tech_admin) || has_role?(:super_admin)
|
|
end
|
|
|
|
def trusted
|
|
Rails.cache.fetch("user-#{id}/has_trusted_role", expires_in: 200.hours) do
|
|
has_role? :trusted
|
|
end
|
|
end
|
|
|
|
def scholar
|
|
valid_pass = workshop_expiration.nil? || workshop_expiration > Time.current
|
|
has_role?(:workshop_pass) && valid_pass
|
|
end
|
|
|
|
def comment_banned
|
|
has_role? :comment_banned
|
|
end
|
|
|
|
def workshop_eligible?
|
|
has_any_role?(:workshop_pass, :level_3_member, :level_4_member, :triple_unicorn_member)
|
|
end
|
|
|
|
def admin_organizations
|
|
org_ids = organization_memberships.where(type_of_user: "admin").pluck(:organization_id)
|
|
organizations.where(id: org_ids)
|
|
end
|
|
|
|
def member_organizations
|
|
org_ids = organization_memberships.where(type_of_user: %w[admin member]).pluck(:organization_id)
|
|
organizations.where(id: org_ids)
|
|
end
|
|
|
|
def org_member?(organization)
|
|
OrganizationMembership.exists?(user: user, organization: organization, type_of_user: %w[admin member])
|
|
end
|
|
|
|
def org_admin?(organization)
|
|
OrganizationMembership.exists?(user: user, organization: organization, type_of_user: "admin")
|
|
end
|
|
|
|
def unique_including_orgs
|
|
errors.add(:username, "is taken.") if Organization.find_by(slug: username)
|
|
end
|
|
|
|
def subscribe_to_mailchimp_newsletter_without_delay
|
|
return unless email.present? && email.include?("@")
|
|
|
|
return if saved_changes["unconfirmed_email"] && saved_changes["confirmation_sent_at"]
|
|
|
|
Users::SubscribeToMailchimpNewsletterJob.perform_now(id)
|
|
end
|
|
|
|
def subscribe_to_mailchimp_newsletter
|
|
return unless email.present? && email.include?("@")
|
|
|
|
return if saved_changes["unconfirmed_email"] && saved_changes["confirmation_sent_at"]
|
|
|
|
Users::SubscribeToMailchimpNewsletterJob.perform_later(id)
|
|
end
|
|
|
|
def a_sustaining_member?
|
|
monthly_dues.positive?
|
|
end
|
|
|
|
def resave_articles
|
|
cache_buster = CacheBuster.new
|
|
articles.find_each do |article|
|
|
cache_buster.bust(article.path) if article.path
|
|
cache_buster.bust(article.path + "?i=i") if article.path
|
|
article.save
|
|
end
|
|
end
|
|
|
|
def settings_tab_list
|
|
tab_list = %w[
|
|
Profile
|
|
Integrations
|
|
Notifications
|
|
Publishing\ from\ RSS
|
|
Organization
|
|
Billing
|
|
]
|
|
tab_list << "Switch Organizations" if has_role?(:switch_between_orgs)
|
|
tab_list.push("Account", "Misc")
|
|
end
|
|
|
|
def profile_image_90
|
|
ProfileImage.new(self).get(90)
|
|
end
|
|
|
|
def remove_from_algolia_index
|
|
remove_from_index!
|
|
index = Algolia::Index.new("searchables_#{Rails.env}")
|
|
index.delay.delete_object("users-#{id}")
|
|
end
|
|
|
|
def unsubscribe_from_newsletters
|
|
MailchimpBot.new(self).unsubscribe_all_newsletters
|
|
end
|
|
|
|
def tag_moderator?
|
|
roles.where(name: "tag_moderator").any?
|
|
end
|
|
|
|
def currently_streaming?
|
|
currently_streaming_on.present?
|
|
end
|
|
|
|
def currently_streaming_on_twitch?
|
|
currently_streaming_on == "twitch"
|
|
end
|
|
|
|
private
|
|
|
|
def set_default_language
|
|
language_settings["preferred_languages"] ||= ["en"]
|
|
end
|
|
|
|
def send_welcome_notification
|
|
Notification.send_welcome_notification(id)
|
|
end
|
|
|
|
def verify_twitter_username
|
|
self.twitter_username = nil if twitter_username == ""
|
|
end
|
|
|
|
def verify_github_username
|
|
self.github_username = nil if github_username == ""
|
|
end
|
|
|
|
def verify_email
|
|
self.email = nil if email == ""
|
|
end
|
|
|
|
def verify_twitch_username
|
|
self.twitch_username = nil if twitch_username == ""
|
|
end
|
|
|
|
def set_username
|
|
set_temp_username if username.blank?
|
|
self.username = username&.downcase
|
|
end
|
|
|
|
def set_temp_username
|
|
self.username = if temp_name_exists?
|
|
temp_username + "_" + rand(100).to_s
|
|
else
|
|
temp_username
|
|
end
|
|
end
|
|
|
|
def temp_name_exists?
|
|
User.find_by(username: temp_username) || Organization.find_by(slug: temp_username)
|
|
end
|
|
|
|
def temp_username
|
|
if twitter_username
|
|
twitter_username.downcase.gsub(/[^0-9a-z_]/i, "").delete(" ")
|
|
elsif github_username
|
|
github_username.downcase.gsub(/[^0-9a-z_]/i, "").delete(" ")
|
|
end
|
|
end
|
|
|
|
def downcase_email
|
|
self.email = email.downcase if email
|
|
end
|
|
|
|
def set_config_input
|
|
self.config_theme = config_theme.tr(" ", "_")
|
|
self.config_font = config_font.tr(" ", "_")
|
|
end
|
|
|
|
def check_for_username_change
|
|
return unless username_changed?
|
|
|
|
self.old_old_username = old_username
|
|
self.old_username = username_was
|
|
chat_channels.find_each do |c|
|
|
c.slug = c.slug.gsub(username_was, username)
|
|
c.save
|
|
end
|
|
articles.find_each do |a|
|
|
a.path = a.path.gsub(username_was, username)
|
|
a.save
|
|
end
|
|
end
|
|
|
|
def conditionally_resave_articles
|
|
delay.resave_articles if core_profile_details_changed? && !user.banned
|
|
end
|
|
|
|
def bust_cache
|
|
Users::BustCacheJob.perform_later(id)
|
|
end
|
|
|
|
def core_profile_details_changed?
|
|
saved_change_to_username? ||
|
|
saved_change_to_name? ||
|
|
saved_change_to_summary? ||
|
|
saved_change_to_bg_color_hex? ||
|
|
saved_change_to_text_color_hex? ||
|
|
saved_change_to_profile_image? ||
|
|
saved_change_to_github_username? ||
|
|
saved_change_to_twitter_username?
|
|
end
|
|
|
|
def conditionally_validate_summary
|
|
# Grandfather people who had a too long summary before.
|
|
return if summary_was && summary_was.size > 200
|
|
|
|
errors.add(:summary, "is too long.") if summary.present? && summary.size > 200
|
|
end
|
|
|
|
def validate_feed_url
|
|
return if feed_url.blank?
|
|
|
|
errors.add(:feed_url, "is not a valid rss feed") unless RssReader.new.valid_feed_url?(feed_url)
|
|
end
|
|
|
|
def validate_mastodon_url
|
|
return if mastodon_url.blank?
|
|
|
|
uri = URI.parse(mastodon_url)
|
|
return if uri.host&.in?(Constants::ALLOWED_MASTODON_INSTANCES)
|
|
|
|
errors.add(:mastodon_url, "is not an allowed Mastodon instance")
|
|
end
|
|
|
|
def title
|
|
name
|
|
end
|
|
|
|
def tag_list
|
|
"" # Unused but necessary for search index
|
|
end
|
|
|
|
def main_image; end
|
|
|
|
def featured
|
|
true
|
|
end
|
|
|
|
def published
|
|
true
|
|
end
|
|
|
|
def published_at; end
|
|
|
|
def featured_number; end
|
|
|
|
def positive_reactions_count
|
|
reactions_count
|
|
end
|
|
|
|
def user
|
|
self
|
|
end
|
|
|
|
def class_name
|
|
self.class.name
|
|
end
|
|
|
|
def user_name
|
|
username
|
|
end
|
|
|
|
def user_username
|
|
username
|
|
end
|
|
|
|
def comments_blob
|
|
"" # Unused but necessary for search index
|
|
end
|
|
|
|
def body_text
|
|
"" # Unused but necessary for search index
|
|
end
|
|
|
|
def tag_keywords_for_search
|
|
employer_name.to_s + mostly_work_with.to_s + available_for.to_s
|
|
end
|
|
|
|
def hotness_score
|
|
search_score
|
|
end
|
|
|
|
def search_score
|
|
counts_score = (articles_count + comments_count + reactions_count + badge_achievements_count) * 10
|
|
score = (counts_score + tag_keywords_for_search.size) * reputation_modifier
|
|
score.to_i
|
|
end
|
|
|
|
def destroy_empty_dm_channels
|
|
return if chat_channels.empty? ||
|
|
chat_channels.where(channel_type: "direct").empty?
|
|
|
|
empty_dm_channels = chat_channels.where(channel_type: "direct").
|
|
select { |chat_channel| chat_channel.messages.empty? }
|
|
empty_dm_channels.destroy_all
|
|
end
|
|
|
|
def destroy_follows
|
|
follower_relationships = Follow.where(followable_id: id, followable_type: "User")
|
|
follower_relationships.destroy_all
|
|
follows.destroy_all
|
|
end
|
|
|
|
def mentorship_status_update
|
|
self.mentor_form_updated_at = Time.current if mentor_description_changed? || offering_mentorship_changed?
|
|
|
|
self.mentee_form_updated_at = Time.current if mentee_description_changed? || seeking_mentorship_changed?
|
|
end
|
|
end
|