mirror of
https://github.com/discourse/discourse.git
synced 2025-04-11 13:42:57 +08:00
FEATURE: allow a huge number of users to share common prefix
Previously username suggester would give up after 100 attempts at getting a username and fallback to random string. This amends the logic so we do all the work of figuring out a good username in SQL and avoids a large amount of queries in cases where a lot of usernames were used up. This corrects an issue on sites with large numbers of anon users
This commit is contained in:
parent
ea214b2b0c
commit
a8fbb19e7c
@ -23,14 +23,46 @@ module UserNameSuggester
|
|||||||
|
|
||||||
def self.find_available_username_based_on(name, allowed_username = nil)
|
def self.find_available_username_based_on(name, allowed_username = nil)
|
||||||
name = fix_username(name)
|
name = fix_username(name)
|
||||||
|
offset = nil
|
||||||
i = 1
|
i = 1
|
||||||
attempt = name
|
attempt = name
|
||||||
|
|
||||||
until attempt == allowed_username || User.username_available?(attempt) || i > 100
|
until attempt == allowed_username || User.username_available?(attempt) || i > 100
|
||||||
suffix = i.to_s
|
|
||||||
|
if offset.nil?
|
||||||
|
normalized = User.normalize_username(name)
|
||||||
|
similar = "#{normalized}(0|1|2|3|4|5|6|7|8|9)+"
|
||||||
|
|
||||||
|
count = DB.query_single(<<~SQL, like: "#{normalized}%", similar: similar).first
|
||||||
|
SELECT count(*) FROM users
|
||||||
|
WHERE username_lower LIKE :like AND
|
||||||
|
username_lower SIMILAR TO :similar
|
||||||
|
SQL
|
||||||
|
|
||||||
|
if count > 0
|
||||||
|
available = DB.query_single(<<~SQL, count: count, name: normalized).first
|
||||||
|
WITH numbers AS (SELECT generate_series(1, :count) AS n)
|
||||||
|
|
||||||
|
SELECT n FROM numbers
|
||||||
|
LEFT JOIN users ON username_lower = :name || n::varchar
|
||||||
|
WHERE users.id IS NULL
|
||||||
|
ORDER by n ASC
|
||||||
|
LIMIT 1
|
||||||
|
SQL
|
||||||
|
|
||||||
|
# we start at 1
|
||||||
|
offset = available - 1
|
||||||
|
else
|
||||||
|
offset = 0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
suffix = (i + offset).to_s
|
||||||
max_length = User.username_length.end - suffix.length
|
max_length = User.username_length.end - suffix.length
|
||||||
attempt = "#{truncate(name, max_length)}#{suffix}"
|
attempt = "#{truncate(name, max_length)}#{suffix}"
|
||||||
i += 1
|
i += 1
|
||||||
end
|
end
|
||||||
|
|
||||||
until attempt == allowed_username || User.username_available?(attempt) || i > 200
|
until attempt == allowed_username || User.username_available?(attempt) || i > 200
|
||||||
attempt = SecureRandom.hex[1..SiteSetting.max_username_length]
|
attempt = SecureRandom.hex[1..SiteSetting.max_username_length]
|
||||||
i += 1
|
i += 1
|
||||||
|
@ -10,6 +10,15 @@ describe UserNameSuggester do
|
|||||||
SiteSetting.max_username_length = 15
|
SiteSetting.max_username_length = 15
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "keeps adding numbers to the username" do
|
||||||
|
Fabricate(:user, username: 'sam')
|
||||||
|
Fabricate(:user, username: 'sAm1')
|
||||||
|
Fabricate(:user, username: 'sam2')
|
||||||
|
Fabricate(:user, username: 'sam4')
|
||||||
|
|
||||||
|
expect(UserNameSuggester.suggest('saM')).to eq('saM3')
|
||||||
|
end
|
||||||
|
|
||||||
it "doesn't raise an error on nil username" do
|
it "doesn't raise an error on nil username" do
|
||||||
expect(UserNameSuggester.suggest(nil)).to eq(nil)
|
expect(UserNameSuggester.suggest(nil)).to eq(nil)
|
||||||
end
|
end
|
||||||
|
Loading…
x
Reference in New Issue
Block a user