Upload files to "/"
This commit is contained in:
parent
f96b741405
commit
7d981d4479
2 changed files with 629 additions and 0 deletions
4
status.rb
Normal file
4
status.rb
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
class Status < ApplicationRecord
|
||||||
|
belongs_to :user
|
||||||
|
has_one_attached :icon
|
||||||
|
end
|
||||||
625
user.rb
Normal file
625
user.rb
Normal file
|
|
@ -0,0 +1,625 @@
|
||||||
|
class User < ApplicationRecord
|
||||||
|
audited redacted: [:encrypted_password, :password_salt]
|
||||||
|
include Justifiable
|
||||||
|
include WorksOwner
|
||||||
|
include PasswordResetsLimitable
|
||||||
|
include UserLoggable
|
||||||
|
include Searchable
|
||||||
|
|
||||||
|
devise :database_authenticatable,
|
||||||
|
:confirmable,
|
||||||
|
:registerable,
|
||||||
|
:rememberable,
|
||||||
|
:trackable,
|
||||||
|
:validatable,
|
||||||
|
:lockable,
|
||||||
|
:recoverable
|
||||||
|
devise :pwned_password unless Rails.env.test?
|
||||||
|
|
||||||
|
# Must come after Devise modules in order to alias devise_valid_password?
|
||||||
|
# properly
|
||||||
|
include BackwardsCompatiblePasswordDecryptor
|
||||||
|
|
||||||
|
# Allows other models to get the current user with User.current_user
|
||||||
|
thread_cattr_accessor :current_user
|
||||||
|
|
||||||
|
# Authorization plugin
|
||||||
|
acts_as_authorized_user
|
||||||
|
acts_as_authorizable
|
||||||
|
has_many :roles_users
|
||||||
|
has_many :roles, through: :roles_users, dependent: :destroy
|
||||||
|
|
||||||
|
### BETA INVITATIONS ###
|
||||||
|
has_many :invitations, as: :creator
|
||||||
|
has_one :invitation, as: :invitee
|
||||||
|
has_many :user_invite_requests, dependent: :destroy
|
||||||
|
|
||||||
|
attr_accessor :invitation_token
|
||||||
|
# attr_accessible :invitation_token
|
||||||
|
after_create :mark_invitation_redeemed, :remove_from_queue
|
||||||
|
|
||||||
|
has_many :external_authors, dependent: :destroy
|
||||||
|
has_many :external_creatorships, foreign_key: "archivist_id"
|
||||||
|
|
||||||
|
has_many :fannish_next_of_kins, dependent: :delete_all, inverse_of: :kin, foreign_key: :kin_id
|
||||||
|
has_one :fannish_next_of_kin, dependent: :destroy
|
||||||
|
|
||||||
|
has_many :favorite_tags, dependent: :destroy
|
||||||
|
|
||||||
|
has_one :default_pseud, -> { where(is_default: true) }, class_name: "Pseud", inverse_of: :user
|
||||||
|
delegate :id, to: :default_pseud, prefix: true
|
||||||
|
|
||||||
|
has_many :pseuds, dependent: :destroy
|
||||||
|
validates_associated :pseuds
|
||||||
|
|
||||||
|
has_one :profile, dependent: :destroy
|
||||||
|
validates_associated :profile
|
||||||
|
|
||||||
|
has_one :preference, dependent: :destroy
|
||||||
|
validates_associated :preference
|
||||||
|
has_many :statuses, dependent: :destroy
|
||||||
|
has_many :blocks_as_blocked, class_name: "Block", dependent: :delete_all, inverse_of: :blocked, foreign_key: :blocked_id
|
||||||
|
has_many :blocks_as_blocker, class_name: "Block", dependent: :delete_all, inverse_of: :blocker, foreign_key: :blocker_id
|
||||||
|
has_many :blocked_users, through: :blocks_as_blocker, source: :blocked
|
||||||
|
|
||||||
|
# The block (if it exists) with this user as the blocker and
|
||||||
|
# User.current_user as the blocked:
|
||||||
|
has_one :block_of_current_user,
|
||||||
|
-> { where(blocked: User.current_user) },
|
||||||
|
class_name: "Block", foreign_key: :blocker_id, inverse_of: :blocker
|
||||||
|
|
||||||
|
# The block (if it exists) with User.current_user as the blocker and this
|
||||||
|
# user as the blocked:
|
||||||
|
has_one :block_by_current_user,
|
||||||
|
-> { where(blocker: User.current_user) },
|
||||||
|
class_name: "Block", foreign_key: :blocked_id, inverse_of: :blocked
|
||||||
|
|
||||||
|
has_many :mutes_as_muted, class_name: "Mute", dependent: :delete_all, inverse_of: :muted, foreign_key: :muted_id
|
||||||
|
has_many :mutes_as_muter, class_name: "Mute", dependent: :delete_all, inverse_of: :muter, foreign_key: :muter_id
|
||||||
|
has_many :muted_users, through: :mutes_as_muter, source: :muted
|
||||||
|
|
||||||
|
# The mute (if it exists) with User.current_user as the muter and this
|
||||||
|
# user as the muted:
|
||||||
|
has_one :mute_by_current_user,
|
||||||
|
-> { where(muter: User.current_user) },
|
||||||
|
class_name: "Mute", foreign_key: :muted_id, inverse_of: :muted
|
||||||
|
|
||||||
|
has_many :skins, foreign_key: "author_id", dependent: :nullify
|
||||||
|
has_many :work_skins, foreign_key: "author_id", dependent: :nullify
|
||||||
|
|
||||||
|
before_create :create_default_associateds
|
||||||
|
before_destroy :remove_user_from_kudos
|
||||||
|
|
||||||
|
before_update :add_renamed_at, if: :will_save_change_to_login?
|
||||||
|
after_update :update_pseud_name
|
||||||
|
after_update :send_wrangler_username_change_notification, if: :is_tag_wrangler?
|
||||||
|
after_update :log_change_if_login_was_edited, if: :saved_change_to_login?
|
||||||
|
after_update :log_email_change, if: :saved_change_to_email?
|
||||||
|
|
||||||
|
after_commit :reindex_user_creations_after_rename
|
||||||
|
|
||||||
|
has_many :collection_participants, through: :pseuds
|
||||||
|
has_many :collections, through: :collection_participants
|
||||||
|
has_many :invited_collections, -> { where("collection_participants.participant_role = ?", CollectionParticipant::INVITED) }, through: :collection_participants, source: :collection
|
||||||
|
has_many :participated_collections, -> { where("collection_participants.participant_role IN (?)", [CollectionParticipant::OWNER, CollectionParticipant::MODERATOR, CollectionParticipant::MEMBER]) }, through: :collection_participants, source: :collection
|
||||||
|
has_many :maintained_collections, -> { where("collection_participants.participant_role IN (?)", [CollectionParticipant::OWNER, CollectionParticipant::MODERATOR]) }, through: :collection_participants, source: :collection
|
||||||
|
has_many :owned_collections, -> { where("collection_participants.participant_role = ?", CollectionParticipant::OWNER) }, through: :collection_participants, source: :collection
|
||||||
|
|
||||||
|
has_many :challenge_signups, through: :pseuds
|
||||||
|
has_many :offer_assignments, through: :pseuds
|
||||||
|
has_many :pinch_hit_assignments, through: :pseuds
|
||||||
|
has_many :request_claims, class_name: "ChallengeClaim", foreign_key: "claiming_user_id", inverse_of: :claiming_user
|
||||||
|
has_many :gifts, -> { where(rejected: false) }, through: :pseuds
|
||||||
|
has_many :gift_works, -> { distinct }, through: :pseuds
|
||||||
|
has_many :rejected_gifts, -> { where(rejected: true) }, class_name: "Gift", through: :pseuds
|
||||||
|
has_many :rejected_gift_works, -> { distinct }, through: :pseuds
|
||||||
|
has_many :readings, dependent: :delete_all
|
||||||
|
has_many :bookmarks, through: :pseuds
|
||||||
|
has_many :bookmark_collection_items, through: :bookmarks, source: :collection_items
|
||||||
|
has_many :comments, through: :pseuds
|
||||||
|
has_many :kudos
|
||||||
|
|
||||||
|
# Nested associations through creatorships got weird after 3.0.x
|
||||||
|
has_many :creatorships, through: :pseuds
|
||||||
|
|
||||||
|
has_many :works, -> { distinct }, through: :pseuds
|
||||||
|
has_many :series, -> { distinct }, through: :pseuds
|
||||||
|
has_many :chapters, through: :pseuds
|
||||||
|
has_many :related_works, through: :works
|
||||||
|
has_many :parent_work_relationships, through: :works
|
||||||
|
|
||||||
|
has_many :tags, through: :works
|
||||||
|
has_many :filters, through: :works
|
||||||
|
has_many :direct_filters, through: :works
|
||||||
|
|
||||||
|
has_many :bookmark_tags, through: :bookmarks, source: :tags
|
||||||
|
|
||||||
|
has_many :subscriptions, dependent: :destroy
|
||||||
|
has_many :followings,
|
||||||
|
class_name: "Subscription",
|
||||||
|
as: :subscribable,
|
||||||
|
dependent: :destroy
|
||||||
|
has_many :subscribed_users,
|
||||||
|
through: :subscriptions,
|
||||||
|
source: :subscribable,
|
||||||
|
source_type: "User"
|
||||||
|
has_many :subscribers,
|
||||||
|
through: :followings,
|
||||||
|
source: :user
|
||||||
|
|
||||||
|
thread_cattr_accessor :should_update_wrangling_activity
|
||||||
|
has_many :wrangling_assignments, dependent: :destroy
|
||||||
|
has_many :fandoms, through: :wrangling_assignments
|
||||||
|
has_many :wrangled_tags, class_name: "Tag", as: :last_wrangler
|
||||||
|
has_one :last_wrangling_activity, dependent: :destroy
|
||||||
|
|
||||||
|
has_many :inbox_comments, dependent: :destroy
|
||||||
|
has_many :feedback_comments, -> { where(is_deleted: false, approved: true).order(created_at: :desc) }, through: :inbox_comments
|
||||||
|
|
||||||
|
has_many :log_items, dependent: :destroy
|
||||||
|
validates_associated :log_items
|
||||||
|
|
||||||
|
after_update :expire_caches
|
||||||
|
|
||||||
|
def expire_caches
|
||||||
|
return unless saved_change_to_login?
|
||||||
|
series.each(&:expire_byline_cache)
|
||||||
|
chapters.each(&:expire_byline_cache)
|
||||||
|
self.works.each do |work|
|
||||||
|
work.touch
|
||||||
|
work.expire_caches
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def remove_user_from_kudos
|
||||||
|
# TODO: AO3-2195 Display orphaned kudos (no users; no IPs so not counted as guest kudos).
|
||||||
|
Kudo.where(user: self).update_all(user_id: nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
def read_inbox_comments
|
||||||
|
inbox_comments.where(read: true)
|
||||||
|
end
|
||||||
|
def unread_inbox_comments
|
||||||
|
inbox_comments.where(read: false)
|
||||||
|
end
|
||||||
|
def unread_inbox_comments_count
|
||||||
|
unread_inbox_comments.with_bad_comments_removed.count
|
||||||
|
end
|
||||||
|
|
||||||
|
scope :alphabetical, -> { order(:login) }
|
||||||
|
scope :starting_with, -> (letter) { where('login like ?', "#{letter}%") }
|
||||||
|
scope :valid, -> { where(banned: false, suspended: false) }
|
||||||
|
scope :out_of_invites, -> { where(out_of_invites: true) }
|
||||||
|
|
||||||
|
validates :login,
|
||||||
|
length: { within: ArchiveConfig.LOGIN_LENGTH_MIN..ArchiveConfig.LOGIN_LENGTH_MAX },
|
||||||
|
format: {
|
||||||
|
with: /\A[A-Za-z0-9]\w*[A-Za-z0-9]\Z/,
|
||||||
|
min_login: ArchiveConfig.LOGIN_LENGTH_MIN,
|
||||||
|
max_login: ArchiveConfig.LOGIN_LENGTH_MAX
|
||||||
|
},
|
||||||
|
uniqueness: true,
|
||||||
|
not_forbidden_name: { if: :will_save_change_to_login? }
|
||||||
|
validate :username_is_not_recently_changed, if: :will_save_change_to_login?
|
||||||
|
validate :admin_username_generic, if: :will_save_change_to_login?
|
||||||
|
|
||||||
|
# allow nil so can save existing users
|
||||||
|
validates_length_of :password,
|
||||||
|
within: ArchiveConfig.PASSWORD_LENGTH_MIN..ArchiveConfig.PASSWORD_LENGTH_MAX,
|
||||||
|
allow_nil: true,
|
||||||
|
too_short: ts("is too short (minimum is %{min_pwd} characters)",
|
||||||
|
min_pwd: ArchiveConfig.PASSWORD_LENGTH_MIN),
|
||||||
|
too_long: ts("is too long (maximum is %{max_pwd} characters)",
|
||||||
|
max_pwd: ArchiveConfig.PASSWORD_LENGTH_MAX)
|
||||||
|
|
||||||
|
validates :email, email_format: true, uniqueness: true
|
||||||
|
|
||||||
|
# Virtual attribute for age check, data processing agreement, and terms of service
|
||||||
|
attr_accessor :age_over_13, :data_processing, :terms_of_service
|
||||||
|
|
||||||
|
validates :data_processing, acceptance: { allow_nil: false, if: :first_save? }
|
||||||
|
validates :age_over_13, acceptance: { allow_nil: false, if: :first_save? }
|
||||||
|
validates :terms_of_service, acceptance: { allow_nil: false, if: :first_save? }
|
||||||
|
|
||||||
|
def to_param
|
||||||
|
login
|
||||||
|
end
|
||||||
|
|
||||||
|
# Override of Devise method to allow user to login with login OR username as
|
||||||
|
# well as to make login case insensitive without losing user-preferred case
|
||||||
|
# for login display
|
||||||
|
def self.find_first_by_auth_conditions(tainted_conditions, options = {})
|
||||||
|
conditions = devise_parameter_filter.filter(tainted_conditions).merge(options)
|
||||||
|
login = conditions.delete(:login)
|
||||||
|
relation = self.where(conditions)
|
||||||
|
|
||||||
|
if login.present?
|
||||||
|
# MySQL is case-insensitive with utf8mb4_unicode_ci so we don't have to use
|
||||||
|
# lowercase values
|
||||||
|
relation = relation.where(["login = :value OR email = :value",
|
||||||
|
value: login])
|
||||||
|
end
|
||||||
|
|
||||||
|
relation.first
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.for_claims(claims_ids)
|
||||||
|
joins(:request_claims).
|
||||||
|
where("challenge_claims.id IN (?)", claims_ids)
|
||||||
|
end
|
||||||
|
|
||||||
|
scope :with_includes_for_admin_index, -> { includes(:roles, :fannish_next_of_kin) }
|
||||||
|
|
||||||
|
def self.search_multiple_by_email(emails = [])
|
||||||
|
# Normalise and dedupe emails
|
||||||
|
all_emails = emails.map(&:downcase)
|
||||||
|
unique_emails = all_emails.uniq
|
||||||
|
# Find users and their email addresses
|
||||||
|
users = User.where(email: unique_emails)
|
||||||
|
found_emails = users.map(&:email).map(&:downcase)
|
||||||
|
# Remove found users from the total list of unique emails and count duplicates
|
||||||
|
not_found_emails = unique_emails - found_emails
|
||||||
|
num_duplicates = emails.size - unique_emails.size
|
||||||
|
|
||||||
|
[users, not_found_emails, num_duplicates]
|
||||||
|
end
|
||||||
|
|
||||||
|
### AUTHENTICATION AND PASSWORDS
|
||||||
|
def active?
|
||||||
|
!confirmed_at.nil?
|
||||||
|
end
|
||||||
|
|
||||||
|
def activate
|
||||||
|
return false if self.active?
|
||||||
|
self.update_attribute(:confirmed_at, Time.now.utc)
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_default_associateds
|
||||||
|
self.pseuds << Pseud.new(name: self.login, is_default: true)
|
||||||
|
self.profile = Profile.new
|
||||||
|
self.preference = Preference.new(locale: Locale.default)
|
||||||
|
end
|
||||||
|
|
||||||
|
def prevent_password_resets?
|
||||||
|
is_protected_user? || no_resets?
|
||||||
|
end
|
||||||
|
|
||||||
|
protected
|
||||||
|
|
||||||
|
def first_save?
|
||||||
|
self.new_record?
|
||||||
|
end
|
||||||
|
|
||||||
|
# Override of Devise method for email sending to set I18n.locale
|
||||||
|
# Based on https://github.com/heartcombo/devise/blob/v4.9.3/lib/devise/models/authenticatable.rb#L200
|
||||||
|
def send_devise_notification(notification, *args)
|
||||||
|
I18n.with_locale(preference.locale_for_mails) do
|
||||||
|
devise_mailer.send(notification, self, *args).deliver_now
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
public
|
||||||
|
|
||||||
|
# Returns an array (of pseuds) of this user's co-authors
|
||||||
|
def coauthors
|
||||||
|
works.collect(&:pseuds).flatten.uniq - pseuds
|
||||||
|
end
|
||||||
|
|
||||||
|
# Gets the user's most recent unposted work
|
||||||
|
def unposted_work
|
||||||
|
return @unposted_work if @unposted_work
|
||||||
|
@unposted_work = unposted_works.first
|
||||||
|
end
|
||||||
|
|
||||||
|
def unposted_works
|
||||||
|
return @unposted_works if @unposted_works
|
||||||
|
@unposted_works = works.where(posted: false).order("works.created_at DESC")
|
||||||
|
end
|
||||||
|
|
||||||
|
# removes ALL unposted works
|
||||||
|
def wipeout_unposted_works
|
||||||
|
works.where(posted: false).destroy_all
|
||||||
|
end
|
||||||
|
|
||||||
|
# Removes all of the user's series that don't have any listed works.
|
||||||
|
def destroy_empty_series
|
||||||
|
series.left_joins(:serial_works).where(serial_works: { id: nil }).
|
||||||
|
destroy_all
|
||||||
|
end
|
||||||
|
|
||||||
|
# Checks authorship of any sort of object
|
||||||
|
def is_author_of?(item)
|
||||||
|
if item.respond_to?(:pseud_id)
|
||||||
|
pseud_ids.include?(item.pseud_id)
|
||||||
|
elsif item.respond_to?(:user_id)
|
||||||
|
id == item.user_id
|
||||||
|
elsif item.respond_to?(:pseuds)
|
||||||
|
item.pseuds.pluck(:user_id).include?(id)
|
||||||
|
elsif item.respond_to?(:author)
|
||||||
|
self == item.author
|
||||||
|
elsif item.respond_to?(:creator_id)
|
||||||
|
self.id == item.creator_id
|
||||||
|
else
|
||||||
|
false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Gets the number of works by this user that the current user can see
|
||||||
|
def visible_work_count
|
||||||
|
Work.owned_by(self).visible_to_user(User.current_user).revealed.non_anon.distinct.count(:id)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Gets the user account for authored objects if orphaning is enabled
|
||||||
|
def self.orphan_account
|
||||||
|
User.fetch_orphan_account if ArchiveConfig.ORPHANING_ALLOWED
|
||||||
|
end
|
||||||
|
|
||||||
|
# Is this user an authorized tag wrangler?
|
||||||
|
def tag_wrangler
|
||||||
|
self.is_tag_wrangler?
|
||||||
|
end
|
||||||
|
|
||||||
|
def is_tag_wrangler?
|
||||||
|
has_role?(:tag_wrangler)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Is this user an authorized archivist?
|
||||||
|
def archivist
|
||||||
|
self.is_archivist?
|
||||||
|
end
|
||||||
|
|
||||||
|
def is_archivist?
|
||||||
|
has_role?(:archivist)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Is this user an authorized official?
|
||||||
|
def official
|
||||||
|
has_role?(:official)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Is this user a protected user? These are users experiencing certain types
|
||||||
|
# of harassment.
|
||||||
|
def protected_user
|
||||||
|
self.is_protected_user?
|
||||||
|
end
|
||||||
|
|
||||||
|
def is_protected_user?
|
||||||
|
has_role?(:protected_user)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Is this user assigned the no resets role? These users do not wish to receive
|
||||||
|
# password resets.
|
||||||
|
def no_resets?
|
||||||
|
has_role?(:no_resets)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Should this user's comments be spam-checked?
|
||||||
|
def should_spam_check_comments?
|
||||||
|
# When account_age_threshold_for_comment_spam_check is 0, no users' comments should be spam-checked
|
||||||
|
(Time.current - created_at).seconds.in_days.to_i < AdminSetting.current.account_age_threshold_for_comment_spam_check
|
||||||
|
end
|
||||||
|
|
||||||
|
# Creates log item tracking changes to user
|
||||||
|
def create_log_item(options = {})
|
||||||
|
options.reverse_merge! note: "System Generated", user_id: self.id
|
||||||
|
LogItem.create(options)
|
||||||
|
end
|
||||||
|
|
||||||
|
def update_last_wrangling_activity
|
||||||
|
return unless is_tag_wrangler?
|
||||||
|
|
||||||
|
last_activity = LastWranglingActivity.find_or_create_by(user: self)
|
||||||
|
last_activity.touch
|
||||||
|
end
|
||||||
|
|
||||||
|
# Returns true if user is the sole author of a work
|
||||||
|
# Should also be true if the user has used more than one of their pseuds on a work
|
||||||
|
def is_sole_author_of?(item)
|
||||||
|
other_pseuds = item.pseuds - pseuds
|
||||||
|
self.is_author_of?(item) && other_pseuds.blank?
|
||||||
|
end
|
||||||
|
|
||||||
|
# Returns array of works where the user is the sole author
|
||||||
|
def sole_authored_works
|
||||||
|
@sole_authored_works = []
|
||||||
|
works.where(posted: 1).each do |w|
|
||||||
|
if self.is_sole_author_of?(w)
|
||||||
|
@sole_authored_works << w
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return @sole_authored_works
|
||||||
|
end
|
||||||
|
|
||||||
|
# Returns array of the user's co-authored works
|
||||||
|
def coauthored_works
|
||||||
|
@coauthored_works = []
|
||||||
|
works.where(posted: 1).each do |w|
|
||||||
|
unless self.is_sole_author_of?(w)
|
||||||
|
@coauthored_works << w
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return @coauthored_works
|
||||||
|
end
|
||||||
|
|
||||||
|
# Returns array of collections where the user is the sole author
|
||||||
|
def sole_owned_collections
|
||||||
|
self.collections.to_a.delete_if { |collection| !(collection.all_owners - pseuds).empty? }
|
||||||
|
end
|
||||||
|
|
||||||
|
### BETA INVITATIONS ###
|
||||||
|
|
||||||
|
#If a new user has an invitation_token (meaning they were invited), the method sets the redeemed_at column for that invitation to Time.now
|
||||||
|
def mark_invitation_redeemed
|
||||||
|
unless self.invitation_token.blank?
|
||||||
|
invitation = Invitation.find_by(token: self.invitation_token)
|
||||||
|
if invitation
|
||||||
|
self.update_attribute(:invitation_id, invitation.id)
|
||||||
|
invitation.mark_as_redeemed(self)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Existing users should be removed from the invitations queue
|
||||||
|
def remove_from_queue
|
||||||
|
invite_request = InviteRequest.find_by(email: self.email)
|
||||||
|
invite_request.destroy if invite_request
|
||||||
|
end
|
||||||
|
|
||||||
|
def fix_user_subscriptions
|
||||||
|
# Delete any subscriptions the user has to deleted items because this causes
|
||||||
|
# the user's subscription page to error
|
||||||
|
@subscriptions = subscriptions.includes(:subscribable)
|
||||||
|
@subscriptions.to_a.each do |sub|
|
||||||
|
if sub.name.nil?
|
||||||
|
sub.destroy
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_user_work_dates
|
||||||
|
# Fix user stats page error caused by the existence of works with nil revised_at dates
|
||||||
|
works.each do |work|
|
||||||
|
if work.revised_at.nil?
|
||||||
|
work.save
|
||||||
|
end
|
||||||
|
IndexQueue.enqueue(work, :main)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def reindex_user_creations
|
||||||
|
IndexQueue.enqueue_ids(Work, works.pluck(:id), :main)
|
||||||
|
IndexQueue.enqueue_ids(Bookmark, bookmarks.pluck(:id), :main)
|
||||||
|
IndexQueue.enqueue_ids(Series, series.pluck(:id), :main)
|
||||||
|
IndexQueue.enqueue_ids(Pseud, pseuds.pluck(:id), :main)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Function to make it easier to retrieve info from the audits table.
|
||||||
|
#
|
||||||
|
# Looks up all past values of the given field, excluding the current value of
|
||||||
|
# the field:
|
||||||
|
def historic_values(field)
|
||||||
|
field = field.to_s
|
||||||
|
|
||||||
|
audits.filter_map do |audit|
|
||||||
|
audit.audited_changes[field]
|
||||||
|
end.flatten.uniq.without(self[field])
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# Override the default Justifiable enabled check, because we only need to justify
|
||||||
|
# username changes at the moment.
|
||||||
|
def justification_enabled?
|
||||||
|
User.current_user.is_a?(Admin) && login_changed?
|
||||||
|
end
|
||||||
|
|
||||||
|
# Create and/or return a user account for holding orphaned works
|
||||||
|
def self.fetch_orphan_account
|
||||||
|
orphan_account = User.find_or_create_by(login: "orphan_account")
|
||||||
|
if orphan_account.new_record?
|
||||||
|
Rails.logger.fatal "You must have a User with the login 'orphan_account'. Please create one."
|
||||||
|
end
|
||||||
|
orphan_account
|
||||||
|
end
|
||||||
|
|
||||||
|
def update_pseud_name
|
||||||
|
return unless saved_change_to_login? && login_before_last_save.present?
|
||||||
|
old_pseud = pseuds.where(name: login_before_last_save).first
|
||||||
|
if login.downcase == login_before_last_save.downcase
|
||||||
|
old_pseud.name = login
|
||||||
|
old_pseud.save!
|
||||||
|
else
|
||||||
|
new_pseud = pseuds.where(name: login).first
|
||||||
|
# do nothing if they already have the matching pseud
|
||||||
|
if new_pseud.blank?
|
||||||
|
if old_pseud.present?
|
||||||
|
# change the old pseud to match
|
||||||
|
old_pseud.name = login
|
||||||
|
old_pseud.save!(validate: false)
|
||||||
|
else
|
||||||
|
# shouldn't be able to get here, but just in case
|
||||||
|
Pseud.create!(name: login, user_id: id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def reindex_user_creations_after_rename
|
||||||
|
return unless saved_change_to_login? && login_before_last_save.present?
|
||||||
|
# Everything is indexed with the user's byline,
|
||||||
|
# which has the old username, so they all need to be reindexed.
|
||||||
|
reindex_user_creations
|
||||||
|
end
|
||||||
|
|
||||||
|
def add_renamed_at
|
||||||
|
if User.current_user == self
|
||||||
|
self.renamed_at = Time.current
|
||||||
|
else
|
||||||
|
self.admin_renamed_at = Time.current
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def log_change_if_login_was_edited
|
||||||
|
current_admin = User.current_user if User.current_user.is_a?(Admin)
|
||||||
|
options = {
|
||||||
|
action: ArchiveConfig.ACTION_RENAME,
|
||||||
|
admin: current_admin
|
||||||
|
}
|
||||||
|
options[:note] = if current_admin
|
||||||
|
"Old Username: #{login_before_last_save}, New Username: #{login}, Changed by: #{current_admin.login}, Ticket ID: ##{ticket_number}"
|
||||||
|
else
|
||||||
|
"Old Username: #{login_before_last_save}; New Username: #{login}"
|
||||||
|
end
|
||||||
|
create_log_item(options)
|
||||||
|
end
|
||||||
|
|
||||||
|
def send_wrangler_username_change_notification
|
||||||
|
return unless saved_change_to_login? && login_before_last_save.present?
|
||||||
|
|
||||||
|
TagWranglingSupervisorMailer.wrangler_username_change_notification(login_before_last_save, login).deliver_now
|
||||||
|
end
|
||||||
|
|
||||||
|
def log_email_change
|
||||||
|
current_admin = User.current_user if User.current_user.is_a?(Admin)
|
||||||
|
options = {
|
||||||
|
action: ArchiveConfig.ACTION_NEW_EMAIL,
|
||||||
|
admin_id: current_admin&.id
|
||||||
|
}
|
||||||
|
options[:note] = "Change made by #{current_admin&.login}" if current_admin
|
||||||
|
create_log_item(options)
|
||||||
|
end
|
||||||
|
|
||||||
|
def remove_stale_from_autocomplete
|
||||||
|
self.class.remove_from_autocomplete(self.autocomplete_search_string_was, self.autocomplete_prefixes, self.autocomplete_value_was)
|
||||||
|
end
|
||||||
|
|
||||||
|
def username_is_not_recently_changed
|
||||||
|
return if User.current_user.is_a?(Admin)
|
||||||
|
|
||||||
|
change_interval_days = ArchiveConfig.USER_RENAME_LIMIT_DAYS
|
||||||
|
return unless renamed_at && change_interval_days.days.ago <= renamed_at
|
||||||
|
|
||||||
|
errors.add(:login,
|
||||||
|
:changed_too_recently,
|
||||||
|
count: change_interval_days,
|
||||||
|
renamed_at: I18n.l(renamed_at))
|
||||||
|
end
|
||||||
|
|
||||||
|
def admin_username_generic
|
||||||
|
return unless User.current_user.is_a?(Admin)
|
||||||
|
|
||||||
|
errors.add(:login, :admin_must_use_default) unless login == "user#{id}"
|
||||||
|
end
|
||||||
|
|
||||||
|
# Extra callback to make sure readings are deleted in an order consistent
|
||||||
|
# with the ReadingsJob.
|
||||||
|
#
|
||||||
|
# TODO: In the long term, it might be better to change the indexes on the
|
||||||
|
# readings table so that it deletes things in the correct order by default if
|
||||||
|
# we just set dependent: :delete_all, but for now we need to explicitly sort
|
||||||
|
# by work_id to make sure that the readings are locked in the correct order.
|
||||||
|
before_destroy :clear_readings, prepend: true
|
||||||
|
def clear_readings
|
||||||
|
readings.order(:work_id).each(&:delete)
|
||||||
|
end
|
||||||
|
end
|
||||||
Loading…
Reference in a new issue