mirror of
https://github.com/wassname/talk.git
synced 2026-07-19 11:28:50 +08:00
Merge branch 'master' into logout-bug
This commit is contained in:
@@ -76,7 +76,8 @@ class UserDetailComment extends React.Component {
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.story}>
|
||||
Story: {comment.asset.title}
|
||||
{t('common.story')}:{' '}
|
||||
{comment.asset.title ? comment.asset.title : comment.asset.url}
|
||||
{
|
||||
<Link to={`/admin/moderate/${comment.asset.id}`}>
|
||||
{t('modqueue.moderate')}
|
||||
@@ -109,6 +110,7 @@ class UserDetailComment extends React.Component {
|
||||
<div className={styles.sideActions}>
|
||||
<IfHasLink text={comment.body}>
|
||||
<span className={styles.hasLinks}>
|
||||
{/* TODO: translate string */}
|
||||
<Icon name="error_outline" /> Contains Link
|
||||
</span>
|
||||
</IfHasLink>
|
||||
|
||||
@@ -122,7 +122,8 @@ class Comment extends React.Component {
|
||||
</div>
|
||||
|
||||
<div className={styles.moderateArticle}>
|
||||
Story: {comment.asset.title}
|
||||
{t('common.story')}:{' '}
|
||||
{comment.asset.title ? comment.asset.title : comment.asset.url}
|
||||
{!currentAsset && (
|
||||
<Link to={`/admin/moderate/${comment.asset.id}`}>
|
||||
{t('modqueue.moderate')}
|
||||
@@ -156,6 +157,7 @@ class Comment extends React.Component {
|
||||
<div className={styles.sideActions}>
|
||||
<IfHasLink text={comment.body}>
|
||||
<span className={styles.hasLinks}>
|
||||
{/* TODO: translate string */}
|
||||
<Icon name="error_outline" /> Contains Link
|
||||
</span>
|
||||
</IfHasLink>
|
||||
|
||||
+66
-2
@@ -1,7 +1,7 @@
|
||||
const uuid = require('uuid');
|
||||
const bcrypt = require('bcryptjs');
|
||||
const errors = require('../errors');
|
||||
const { some, merge } = require('lodash');
|
||||
const { difference, sample, some, merge, random } = require('lodash');
|
||||
const { ROOT_URL } = require('../config');
|
||||
const { jwt: JWT_SECRET } = require('../secrets');
|
||||
const debug = require('debug')('talk:services:users');
|
||||
@@ -346,6 +346,70 @@ class UsersService {
|
||||
return username.replace(/ /g, '_').replace(/[^a-zA-Z_]/g, '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the initial username for an external account. Searches to make
|
||||
* sure username not already used. Adds a random number if username already
|
||||
* in use.
|
||||
*/
|
||||
static async getInitialUsername(username) {
|
||||
const MAX_ATTEMPTS = 10;
|
||||
const END_NUMBER_MAX = 99999;
|
||||
const GROUP_ATTEMPTS = 50;
|
||||
|
||||
// Cast the original username.
|
||||
const castedName = UsersService.castUsername(username);
|
||||
const lowercaseUsername = castedName.toLowerCase();
|
||||
|
||||
// Try to see if our first guess has been taken.
|
||||
const existingUserWithName = await UserModel.findOne({
|
||||
lowercaseUsername,
|
||||
});
|
||||
if (!existingUserWithName) {
|
||||
return castedName;
|
||||
}
|
||||
|
||||
// Our first username was taken, lets try to find a non-taken name.
|
||||
for (let i = 0; i < MAX_ATTEMPTS; i++) {
|
||||
// Generate `GROUP_ATTEMPTS` guesses for the username.
|
||||
const usernameGuesses = Array.from(Array(GROUP_ATTEMPTS)).map(
|
||||
() => `${castedName}_${random(0, END_NUMBER_MAX)}`
|
||||
);
|
||||
|
||||
// Map them all to lowercase.
|
||||
const lowercaseUsernameGuesses = usernameGuesses.map(guess =>
|
||||
guess.toLowerCase()
|
||||
);
|
||||
|
||||
// See if any of these users aren't taken already.
|
||||
const existingUsernames = (await UserModel.find(
|
||||
{
|
||||
lowercaseUsername: { $in: lowercaseUsernameGuesses },
|
||||
},
|
||||
{ lowercaseUsername: 1 }
|
||||
)).map(({ lowercaseUsername }) => lowercaseUsername);
|
||||
if (existingUsernames.length === lowercaseUsernameGuesses.length) {
|
||||
// The number of found users is the same as the number of username
|
||||
// guesses, aka, all the usernames are taken.
|
||||
continue;
|
||||
}
|
||||
|
||||
// At least one of the usernames wasn't taken! Let's filter this to only
|
||||
// include unused usernames and grab one random entry from the list.
|
||||
const foundLowercaseUsernameIndex = lowercaseUsernameGuesses.indexOf(
|
||||
sample(difference(lowercaseUsernameGuesses, existingUsernames))
|
||||
);
|
||||
|
||||
// Now we get the uppercase version of that string.
|
||||
return usernameGuesses[foundLowercaseUsernameIndex];
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
'cannot find free name after ' +
|
||||
(MAX_ATTEMPTS * GROUP_ATTEMPTS + 1) +
|
||||
' tries'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a user given a social profile and if the user does not exist, creates
|
||||
* them.
|
||||
@@ -368,7 +432,7 @@ class UsersService {
|
||||
// User does not exist and need to be created.
|
||||
|
||||
// Create an initial username for the user.
|
||||
let username = UsersService.castUsername(displayName);
|
||||
let username = await UsersService.getInitialUsername(displayName);
|
||||
|
||||
// The user was not found, lets create them!
|
||||
user = new UserModel({
|
||||
|
||||
@@ -58,6 +58,19 @@ describe('services.UsersService', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('#getInitialUsername', () => {
|
||||
it('should find the first result when there is no conflict', async () => {
|
||||
const username = await UsersService.getInitialUsername(
|
||||
'TheGreatSockmonster'
|
||||
);
|
||||
expect(username).to.equal('TheGreatSockmonster');
|
||||
});
|
||||
it('should find a first result when there is a conflict', async () => {
|
||||
const username = await UsersService.getInitialUsername('Sockmonster');
|
||||
expect(username).to.match(/Sockmonster_[0-9]+/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#findPublicByIdArray()', () => {
|
||||
it('should find an array of users from an array of ids', async () => {
|
||||
const ids = mockUsers.map(user => user.id);
|
||||
|
||||
Reference in New Issue
Block a user