mirror of
https://gh.wpcy.net/https://github.com/discourse/discourse.git
synced 2026-05-27 17:02:55 +08:00
* It only imports users and emails so far * It stores mapped IDs and usernames in a SQLite DB. In the future, we might want to copy those into the Discourse DB at the end of a migration. * The importer is split into steps which can mostly be configured with a simple DSL * Data that needs to be shared between steps can be stored in an instance of the `SharedData` class * Steps are automatically sorted via their defined dependencies before they are executed * Common logic for finding unique names (username, group name) is extracted into a helper class * If possible, steps try to avoid loading already imported data (via `mapping.ids` table) * And steps should select the `discourse_id` instead of the `original_id` of mapped IDs via SQL
84 lines
2.5 KiB
Ruby
Vendored
84 lines
2.5 KiB
Ruby
Vendored
# frozen_string_literal: true
|
|
|
|
module Migrations::Importer
|
|
class UniqueNameFinder
|
|
MAX_USERNAME_LENGTH = 60
|
|
|
|
def initialize(shared_data)
|
|
@used_usernames_lower = shared_data.load(:usernames)
|
|
@used_group_names_lower = shared_data.load(:group_names)
|
|
@last_suffixes = {}
|
|
|
|
@fallback_username =
|
|
UserNameSuggester.sanitize_username(I18n.t("fallback_username")).presence ||
|
|
UserNameSuggester::LAST_RESORT_USERNAME
|
|
@fallback_group_name = "group"
|
|
end
|
|
|
|
def find_available_username(username, allow_reserved_username: false)
|
|
username, username_lower =
|
|
find_available_name(
|
|
username,
|
|
fallback_name: @fallback_username,
|
|
max_name_length: MAX_USERNAME_LENGTH,
|
|
allow_reserved_username:,
|
|
)
|
|
|
|
@used_usernames_lower.add(username_lower)
|
|
username
|
|
end
|
|
|
|
def find_available_group_name(group_name)
|
|
group_name, group_name_lower =
|
|
find_available_name(group_name, fallback_name: @fallback_group_name)
|
|
|
|
@used_group_names_lower.add(group_name_lower)
|
|
group_name
|
|
end
|
|
|
|
private
|
|
|
|
def name_available?(name, allow_reserved_username: false)
|
|
name_lower = name.downcase
|
|
|
|
return false if @used_usernames_lower.include?(name_lower)
|
|
return false if @used_group_names_lower.include?(name_lower)
|
|
return false if !allow_reserved_username && User.reserved_username?(name_lower)
|
|
true
|
|
end
|
|
|
|
def find_available_name(
|
|
name,
|
|
fallback_name:,
|
|
max_name_length: nil,
|
|
allow_reserved_username: false
|
|
)
|
|
name = name.unicode_normalize
|
|
name = UserNameSuggester.sanitize_username(name)
|
|
name = fallback_name.dup if name.blank?
|
|
name = UserNameSuggester.truncate(name, max_name_length) if max_name_length
|
|
|
|
if !name_available?(name, allow_reserved_username:)
|
|
# if the name ends with a number, then use an underscore before appending the suffix
|
|
suffix_separator = name.match?(/\d$/) ? "_" : ""
|
|
suffix = next_suffix(name).to_s
|
|
|
|
# TODO This needs better logic, because it's possible that the max username length is exceeded
|
|
name = +"#{name}#{suffix_separator}#{suffix}"
|
|
name.next! until name_available?(name, allow_reserved_username:)
|
|
end
|
|
|
|
[name, name.downcase]
|
|
end
|
|
|
|
def next_suffix(name)
|
|
name_lower = name.downcase
|
|
@last_suffixes.fetch(name_lower, 0) + 1
|
|
end
|
|
|
|
def store_last_suffix(name)
|
|
name_lower = name.downcase
|
|
@last_suffixes[$1] = $2.to_i if name_lower =~ /^(.+?)(\d+)$/
|
|
end
|
|
end
|
|
end
|