Improve registration

* Add ability to set the user display name
 * Use display name to guess the username/channel name
 * Add explanations about what is the purpose of a username/channel name
 * Add a loader at the "done" step
This commit is contained in:
Chocobozzz 2019-06-07 16:59:53 +02:00
parent 1a03bea0c4
commit 1f20622f2b
No known key found for this signature in database
GPG Key ID: 583A612D890159BE
23 changed files with 253 additions and 76 deletions

View File

@ -30,7 +30,7 @@ export class MyAccountProfileComponent extends FormReactive implements OnInit {
ngOnInit () { ngOnInit () {
this.buildForm({ this.buildForm({
'display-name': this.userValidatorsService.USER_DISPLAY_NAME, 'display-name': this.userValidatorsService.USER_DISPLAY_NAME_REQUIRED,
description: this.userValidatorsService.USER_DESCRIPTION description: this.userValidatorsService.USER_DESCRIPTION
}) })

View File

@ -11,28 +11,6 @@
</p> </p>
</div> </div>
<div class="form-group">
<label for="name" i18n>Channel name</label>
<div class="input-group">
<input
type="text" id="name" i18n-placeholder placeholder="Example: my_super_channel"
formControlName="name" [ngClass]="{ 'input-error': formErrors['name'] }"
>
<div class="input-group-append">
<span class="input-group-text">@{{ instanceHost }}</span>
</div>
</div>
<div *ngIf="formErrors.name" class="form-error">
{{ formErrors.name }}
</div>
<div *ngIf="isSameThanUsername()" class="form-error" i18n>
Channel name cannot be the same than your account name. You can click on the first step to update your account name.
</div>
</div>
<div class="form-group"> <div class="form-group">
<label for="displayName" i18n>Channel display name</label> <label for="displayName" i18n>Channel display name</label>
@ -47,4 +25,30 @@
{{ formErrors.displayName }} {{ formErrors.displayName }}
</div> </div>
</div> </div>
<div class="form-group">
<label for="name" i18n>Channel name</label>
<div class="input-group">
<input
type="text" id="name" i18n-placeholder placeholder="Example: my_super_channel"
formControlName="name" [ngClass]="{ 'input-error': formErrors['name'] }"
>
<div class="input-group-append">
<span class="input-group-text">@{{ instanceHost }}</span>
</div>
</div>
<div class="name-information" i18n>
The channel name is a unique identifier of your channel on this instance. It's like an address mail, so other people can find your channel.
</div>
<div *ngIf="formErrors.name" class="form-error">
{{ formErrors.name }}
</div>
<div *ngIf="isSameThanUsername()" class="form-error" i18n>
Channel name cannot be the same than your account name. You can click on the first step to update your account name.
</div>
</div>
</form> </form>

View File

@ -1,8 +1,10 @@
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core' import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'
import { AuthService } from '@app/core' import { AuthService } from '@app/core'
import { FormReactive, VideoChannelValidatorsService } from '@app/shared' import { FormReactive, UserService, VideoChannelValidatorsService } from '@app/shared'
import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service'
import { FormGroup } from '@angular/forms' import { FormGroup } from '@angular/forms'
import { pairwise } from 'rxjs/operators'
import { concat, of } from 'rxjs'
@Component({ @Component({
selector: 'my-register-step-channel', selector: 'my-register-step-channel',
@ -16,6 +18,7 @@ export class RegisterStepChannelComponent extends FormReactive implements OnInit
constructor ( constructor (
protected formValidatorService: FormValidatorService, protected formValidatorService: FormValidatorService,
private authService: AuthService, private authService: AuthService,
private userService: UserService,
private videoChannelValidatorsService: VideoChannelValidatorsService private videoChannelValidatorsService: VideoChannelValidatorsService
) { ) {
super() super()
@ -25,16 +28,29 @@ export class RegisterStepChannelComponent extends FormReactive implements OnInit
return window.location.host return window.location.host
} }
ngOnInit () {
this.buildForm({
displayName: this.videoChannelValidatorsService.VIDEO_CHANNEL_DISPLAY_NAME,
name: this.videoChannelValidatorsService.VIDEO_CHANNEL_NAME
})
setTimeout(() => this.formBuilt.emit(this.form))
concat(
of(''),
this.form.get('displayName').valueChanges
).pipe(pairwise())
.subscribe(([ oldValue, newValue ]) => this.onDisplayNameChange(oldValue, newValue))
}
isSameThanUsername () { isSameThanUsername () {
return this.username && this.username === this.form.value['name'] return this.username && this.username === this.form.value['name']
} }
ngOnInit () { private onDisplayNameChange (oldDisplayName: string, newDisplayName: string) {
this.buildForm({ const name = this.form.value['name'] || ''
name: this.videoChannelValidatorsService.VIDEO_CHANNEL_NAME,
displayName: this.videoChannelValidatorsService.VIDEO_CHANNEL_DISPLAY_NAME
})
setTimeout(() => this.formBuilt.emit(this.form)) const newName = this.userService.getNewUsername(oldDisplayName, newDisplayName, name)
this.form.patchValue({ name: newName })
} }
} }

View File

@ -1,5 +1,20 @@
<form role="form" [formGroup]="form"> <form role="form" [formGroup]="form">
<div class="form-group">
<label for="displayName" i18n>Display name</label>
<div class="input-group">
<input
type="text" id="displayName" placeholder="John Doe"
formControlName="displayName" [ngClass]="{ 'input-error': formErrors['displayName'] }"
>
</div>
<div *ngIf="formErrors.displayName" class="form-error">
{{ formErrors.displayName }}
</div>
</div>
<div class="form-group"> <div class="form-group">
<label for="username" i18n>Username</label> <label for="username" i18n>Username</label>
@ -13,6 +28,10 @@
</div> </div>
</div> </div>
<div class="name-information" i18n>
The username is a unique identifier of your account on this instance. It's like an address mail, so other people can find you.
</div>
<div *ngIf="formErrors.username" class="form-error"> <div *ngIf="formErrors.username" class="form-error">
{{ formErrors.username }} {{ formErrors.username }}
</div> </div>

View File

@ -1,8 +1,10 @@
import { Component, EventEmitter, OnInit, Output } from '@angular/core' import { Component, EventEmitter, OnInit, Output } from '@angular/core'
import { AuthService } from '@app/core' import { AuthService } from '@app/core'
import { FormReactive, UserValidatorsService } from '@app/shared' import { FormReactive, UserService, UserValidatorsService } from '@app/shared'
import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service'
import { FormGroup } from '@angular/forms' import { FormGroup } from '@angular/forms'
import { pairwise } from 'rxjs/operators'
import { concat, of } from 'rxjs'
@Component({ @Component({
selector: 'my-register-step-user', selector: 'my-register-step-user',
@ -15,6 +17,7 @@ export class RegisterStepUserComponent extends FormReactive implements OnInit {
constructor ( constructor (
protected formValidatorService: FormValidatorService, protected formValidatorService: FormValidatorService,
private authService: AuthService, private authService: AuthService,
private userService: UserService,
private userValidatorsService: UserValidatorsService private userValidatorsService: UserValidatorsService
) { ) {
super() super()
@ -26,6 +29,7 @@ export class RegisterStepUserComponent extends FormReactive implements OnInit {
ngOnInit () { ngOnInit () {
this.buildForm({ this.buildForm({
displayName: this.userValidatorsService.USER_DISPLAY_NAME_REQUIRED,
username: this.userValidatorsService.USER_USERNAME, username: this.userValidatorsService.USER_USERNAME,
password: this.userValidatorsService.USER_PASSWORD, password: this.userValidatorsService.USER_PASSWORD,
email: this.userValidatorsService.USER_EMAIL, email: this.userValidatorsService.USER_EMAIL,
@ -33,5 +37,18 @@ export class RegisterStepUserComponent extends FormReactive implements OnInit {
}) })
setTimeout(() => this.formBuilt.emit(this.form)) setTimeout(() => this.formBuilt.emit(this.form))
concat(
of(''),
this.form.get('displayName').valueChanges
).pipe(pairwise())
.subscribe(([ oldValue, newValue ]) => this.onDisplayNameChange(oldValue, newValue))
}
private onDisplayNameChange (oldDisplayName: string, newDisplayName: string) {
const username = this.form.value['username'] || ''
const newUsername = this.userService.getNewUsername(oldDisplayName, newDisplayName, username)
this.form.patchValue({ username: newUsername })
} }
} }

View File

@ -27,6 +27,12 @@
</cdk-step> </cdk-step>
<cdk-step i18n-label label="Done" editable="false"> <cdk-step i18n-label label="Done" editable="false">
<div *ngIf="!signupDone && !error" class="done-loader">
<my-loader [loading]="true"></my-loader>
<div i18n>PeerTube is creating your account...</div>
</div>
<div *ngIf="error" class="alert alert-danger">{{ error }}</div> <div *ngIf="error" class="alert alert-danger">{{ error }}</div>
</cdk-step> </cdk-step>
</my-custom-stepper> </my-custom-stepper>

View File

@ -56,3 +56,26 @@ button {
@include peertube-button; @include peertube-button;
@include orange-button; @include orange-button;
} }
.name-information {
margin-top: 10px;
}
.done-loader {
display: flex;
justify-content: center;
flex-direction: column;
align-items: center;
my-loader {
margin-bottom: 20px;
/deep/ .loader div {
border-color: var(--mainColor) transparent transparent transparent;
}
& + div {
font-size: 15px;
}
}
}

View File

@ -12,7 +12,7 @@ export class UserValidatorsService {
readonly USER_VIDEO_QUOTA: BuildFormValidator readonly USER_VIDEO_QUOTA: BuildFormValidator
readonly USER_VIDEO_QUOTA_DAILY: BuildFormValidator readonly USER_VIDEO_QUOTA_DAILY: BuildFormValidator
readonly USER_ROLE: BuildFormValidator readonly USER_ROLE: BuildFormValidator
readonly USER_DISPLAY_NAME: BuildFormValidator readonly USER_DISPLAY_NAME_REQUIRED: BuildFormValidator
readonly USER_DESCRIPTION: BuildFormValidator readonly USER_DESCRIPTION: BuildFormValidator
readonly USER_TERMS: BuildFormValidator readonly USER_TERMS: BuildFormValidator
@ -85,18 +85,7 @@ export class UserValidatorsService {
} }
} }
this.USER_DISPLAY_NAME = { this.USER_DISPLAY_NAME_REQUIRED = this.getDisplayName(true)
VALIDATORS: [
Validators.required,
Validators.minLength(1),
Validators.maxLength(50)
],
MESSAGES: {
'required': this.i18n('Display name is required.'),
'minlength': this.i18n('Display name must be at least 1 character long.'),
'maxlength': this.i18n('Display name cannot be more than 50 characters long.')
}
}
this.USER_DESCRIPTION = { this.USER_DESCRIPTION = {
VALIDATORS: [ VALIDATORS: [
@ -129,4 +118,22 @@ export class UserValidatorsService {
} }
} }
} }
private getDisplayName (required: boolean) {
const control = {
VALIDATORS: [
Validators.minLength(1),
Validators.maxLength(120)
],
MESSAGES: {
'required': this.i18n('Display name is required.'),
'minlength': this.i18n('Display name must be at least 1 character long.'),
'maxlength': this.i18n('Display name cannot be more than 50 characters long.')
}
}
if (required) control.VALIDATORS.push(Validators.required)
return control
}
} }

View File

@ -1,5 +1,5 @@
<div *ngIf="loading"> <div *ngIf="loading">
<div class="lds-ring"> <div class="loader">
<div></div> <div></div>
<div></div> <div></div>
<div></div> <div></div>

View File

@ -3,14 +3,14 @@
// Thanks to https://loading.io/css/ (CC0 License) // Thanks to https://loading.io/css/ (CC0 License)
.lds-ring { .loader {
display: inline-block; display: inline-block;
position: relative; position: relative;
width: 50px; width: 50px;
height: 50px; height: 50px;
} }
.lds-ring div { .loader div {
box-sizing: border-box; box-sizing: border-box;
display: block; display: block;
position: absolute; position: absolute;
@ -19,23 +19,23 @@
margin: 6px; margin: 6px;
border: 4px solid; border: 4px solid;
border-radius: 50%; border-radius: 50%;
animation: lds-ring 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite; animation: loader 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;
border-color: #999999 transparent transparent transparent; border-color: #999999 transparent transparent transparent;
} }
.lds-ring div:nth-child(1) { .loader div:nth-child(1) {
animation-delay: -0.45s; animation-delay: -0.45s;
} }
.lds-ring div:nth-child(2) { .loader div:nth-child(2) {
animation-delay: -0.3s; animation-delay: -0.3s;
} }
.lds-ring div:nth-child(3) { .loader div:nth-child(3) {
animation-delay: -0.15s; animation-delay: -0.15s;
} }
@keyframes lds-ring { @keyframes loader {
0% { 0% {
transform: rotate(0deg); transform: rotate(0deg);
} }

View File

@ -136,6 +136,22 @@ export class UserService {
.pipe(catchError(res => this.restExtractor.handleError(res))) .pipe(catchError(res => this.restExtractor.handleError(res)))
} }
getNewUsername (oldDisplayName: string, newDisplayName: string, currentUsername: string) {
// Don't update display name, the user seems to have changed it
if (this.displayNameToUsername(oldDisplayName) !== currentUsername) return currentUsername
return this.displayNameToUsername(newDisplayName)
}
displayNameToUsername (displayName: string) {
if (!displayName) return ''
return displayName
.toLowerCase()
.replace(/\s/g, '_')
.replace(/[^a-z0-9_.]/g, '')
}
/* ###### Admin methods ###### */ /* ###### Admin methods ###### */
addUser (userCreate: UserCreate) { addUser (userCreate: UserCreate) {

View File

@ -100,7 +100,6 @@ export class VideoImportTorrentComponent extends VideoSend implements OnInit, Ca
previewUrl: null previewUrl: null
})) }))
this.hydrateFormFromVideo() this.hydrateFormFromVideo()
}, },

View File

@ -184,7 +184,7 @@ async function createUser (req: express.Request, res: express.Response) {
adminFlags: body.adminFlags || UserAdminFlag.NONE adminFlags: body.adminFlags || UserAdminFlag.NONE
}) })
const { user, account } = await createUserAccountAndChannelAndPlaylist(userToCreate) const { user, account } = await createUserAccountAndChannelAndPlaylist({ userToCreate: userToCreate })
auditLogger.create(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON())) auditLogger.create(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON()))
logger.info('User %s with its channel and account created.', body.username) logger.info('User %s with its channel and account created.', body.username)
@ -214,7 +214,11 @@ async function registerUser (req: express.Request, res: express.Response) {
emailVerified: CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION ? false : null emailVerified: CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION ? false : null
}) })
const { user } = await createUserAccountAndChannelAndPlaylist(userToCreate, body.channel) const { user } = await createUserAccountAndChannelAndPlaylist({
userToCreate: userToCreate,
userDisplayName: body.displayName || undefined,
channelNames: body.channel
})
auditLogger.create(body.username, new UserAuditView(user.toFormattedJSON())) auditLogger.create(body.username, new UserAuditView(user.toFormattedJSON()))
logger.info('User %s with its channel and account registered.', body.username) logger.info('User %s with its channel and account registered.', body.username)

View File

@ -146,7 +146,7 @@ async function createOAuthAdminIfNotExist () {
} }
const user = new UserModel(userData) const user = new UserModel(userData)
await createUserAccountAndChannelAndPlaylist(user, undefined, validatePassword) await createUserAccountAndChannelAndPlaylist({ userToCreate: user, channelNames: undefined, validateUser: validatePassword })
logger.info('Username: ' + username) logger.info('Username: ' + username)
logger.info('User password: ' + password) logger.info('User password: ' + password)
} }

View File

@ -65,7 +65,12 @@ async function up (utils: {
// Create application account // Create application account
{ {
const applicationInstance = await ApplicationModel.findOne() const applicationInstance = await ApplicationModel.findOne()
const accountCreated = await createLocalAccountWithoutKeys(SERVER_ACTOR_NAME, null, applicationInstance.id, undefined) const accountCreated = await createLocalAccountWithoutKeys({
name: SERVER_ACTOR_NAME,
userId: null,
applicationId: applicationInstance.id,
t: undefined
})
const { publicKey, privateKey } = await createPrivateAndPublicKeys() const { publicKey, privateKey } = await createPrivateAndPublicKeys()
accountCreated.Actor.publicKey = publicKey accountCreated.Actor.publicKey = publicKey
@ -83,7 +88,7 @@ async function up (utils: {
// Recreate accounts for each user // Recreate accounts for each user
const users = await db.User.findAll() const users = await db.User.findAll()
for (const user of users) { for (const user of users) {
const account = await createLocalAccountWithoutKeys(user.username, user.id, null, undefined) const account = await createLocalAccountWithoutKeys({ name: user.username, userId: user.id, applicationId: null, t: undefined })
const { publicKey, privateKey } = await createPrivateAndPublicKeys() const { publicKey, privateKey } = await createPrivateAndPublicKeys()
account.Actor.publicKey = publicKey account.Actor.publicKey = publicKey

View File

@ -1,4 +1,3 @@
import * as Sequelize from 'sequelize'
import * as uuidv4 from 'uuid/v4' import * as uuidv4 from 'uuid/v4'
import { ActivityPubActorType } from '../../shared/models/activitypub' import { ActivityPubActorType } from '../../shared/models/activitypub'
import { SERVER_ACTOR_NAME } from '../initializers/constants' import { SERVER_ACTOR_NAME } from '../initializers/constants'
@ -12,9 +11,17 @@ import { UserNotificationSettingModel } from '../models/account/user-notificatio
import { UserNotificationSetting, UserNotificationSettingValue } from '../../shared/models/users' import { UserNotificationSetting, UserNotificationSettingValue } from '../../shared/models/users'
import { createWatchLaterPlaylist } from './video-playlist' import { createWatchLaterPlaylist } from './video-playlist'
import { sequelizeTypescript } from '../initializers/database' import { sequelizeTypescript } from '../initializers/database'
import { Transaction } from 'sequelize/types'
type ChannelNames = { name: string, displayName: string } type ChannelNames = { name: string, displayName: string }
async function createUserAccountAndChannelAndPlaylist (userToCreate: UserModel, channelNames?: ChannelNames, validateUser = true) { async function createUserAccountAndChannelAndPlaylist (parameters: {
userToCreate: UserModel,
userDisplayName?: string,
channelNames?: ChannelNames,
validateUser?: boolean
}) {
const { userToCreate, userDisplayName, channelNames, validateUser = true } = parameters
const { user, account, videoChannel } = await sequelizeTypescript.transaction(async t => { const { user, account, videoChannel } = await sequelizeTypescript.transaction(async t => {
const userOptions = { const userOptions = {
transaction: t, transaction: t,
@ -24,7 +31,13 @@ async function createUserAccountAndChannelAndPlaylist (userToCreate: UserModel,
const userCreated = await userToCreate.save(userOptions) const userCreated = await userToCreate.save(userOptions)
userCreated.NotificationSetting = await createDefaultUserNotificationSettings(userCreated, t) userCreated.NotificationSetting = await createDefaultUserNotificationSettings(userCreated, t)
const accountCreated = await createLocalAccountWithoutKeys(userCreated.username, userCreated.id, null, t) const accountCreated = await createLocalAccountWithoutKeys({
name: userCreated.username,
displayName: userDisplayName,
userId: userCreated.id,
applicationId: null,
t: t
})
userCreated.Account = accountCreated userCreated.Account = accountCreated
const channelAttributes = await buildChannelAttributes(userCreated, channelNames) const channelAttributes = await buildChannelAttributes(userCreated, channelNames)
@ -46,20 +59,22 @@ async function createUserAccountAndChannelAndPlaylist (userToCreate: UserModel,
return { user, account, videoChannel } as { user: UserModel, account: AccountModel, videoChannel: VideoChannelModel } return { user, account, videoChannel } as { user: UserModel, account: AccountModel, videoChannel: VideoChannelModel }
} }
async function createLocalAccountWithoutKeys ( async function createLocalAccountWithoutKeys (parameters: {
name: string, name: string,
displayName?: string,
userId: number | null, userId: number | null,
applicationId: number | null, applicationId: number | null,
t: Sequelize.Transaction | undefined, t: Transaction | undefined,
type: ActivityPubActorType= 'Person' type?: ActivityPubActorType
) { }) {
const { name, displayName, userId, applicationId, t, type = 'Person' } = parameters
const url = getAccountActivityPubUrl(name) const url = getAccountActivityPubUrl(name)
const actorInstance = buildActorInstance(type, url, name) const actorInstance = buildActorInstance(type, url, name)
const actorInstanceCreated = await actorInstance.save({ transaction: t }) const actorInstanceCreated = await actorInstance.save({ transaction: t })
const accountInstance = new AccountModel({ const accountInstance = new AccountModel({
name, name: displayName || name,
userId, userId,
applicationId, applicationId,
actorId: actorInstanceCreated.id actorId: actorInstanceCreated.id
@ -72,7 +87,13 @@ async function createLocalAccountWithoutKeys (
} }
async function createApplicationActor (applicationId: number) { async function createApplicationActor (applicationId: number) {
const accountCreated = await createLocalAccountWithoutKeys(SERVER_ACTOR_NAME, null, applicationId, undefined, 'Application') const accountCreated = await createLocalAccountWithoutKeys({
name: SERVER_ACTOR_NAME,
userId: null,
applicationId: applicationId,
t: undefined,
type: 'Application'
})
accountCreated.Actor = await setAsyncActorKeys(accountCreated.Actor) accountCreated.Actor = await setAsyncActorKeys(accountCreated.Actor)
@ -89,7 +110,7 @@ export {
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
function createDefaultUserNotificationSettings (user: UserModel, t: Sequelize.Transaction | undefined) { function createDefaultUserNotificationSettings (user: UserModel, t: Transaction | undefined) {
const values: UserNotificationSetting & { userId: number } = { const values: UserNotificationSetting & { userId: number } = {
userId: user.id, userId: user.id,
newVideoFromSubscription: UserNotificationSettingValue.WEB, newVideoFromSubscription: UserNotificationSettingValue.WEB,

View File

@ -53,8 +53,16 @@ const usersRegisterValidator = [
body('username').custom(isUserUsernameValid).withMessage('Should have a valid username'), body('username').custom(isUserUsernameValid).withMessage('Should have a valid username'),
body('password').custom(isUserPasswordValid).withMessage('Should have a valid password'), body('password').custom(isUserPasswordValid).withMessage('Should have a valid password'),
body('email').isEmail().withMessage('Should have a valid email'), body('email').isEmail().withMessage('Should have a valid email'),
body('channel.name').optional().custom(isActorPreferredUsernameValid).withMessage('Should have a valid channel name'), body('displayName')
body('channel.displayName').optional().custom(isVideoChannelNameValid).withMessage('Should have a valid display name'), .optional()
.custom(isUserDisplayNameValid).withMessage('Should have a valid display name'),
body('channel.name')
.optional()
.custom(isActorPreferredUsernameValid).withMessage('Should have a valid channel name'),
body('channel.displayName')
.optional()
.custom(isVideoChannelNameValid).withMessage('Should have a valid display name'),
async (req: express.Request, res: express.Response, next: express.NextFunction) => { async (req: express.Request, res: express.Response, next: express.NextFunction) => {
logger.debug('Checking usersRegister parameters', { parameters: omit(req.body, 'password') }) logger.debug('Checking usersRegister parameters', { parameters: omit(req.body, 'password') })

View File

@ -643,6 +643,7 @@ describe('Test users API validators', function () {
const registrationPath = path + '/register' const registrationPath = path + '/register'
const baseCorrectParams = { const baseCorrectParams = {
username: 'user3', username: 'user3',
displayName: 'super user',
email: 'test3@example.com', email: 'test3@example.com',
password: 'my super password' password: 'my super password'
} }
@ -725,6 +726,12 @@ describe('Test users API validators', function () {
}) })
}) })
it('Should fail with a bad display name', async function () {
const fields = immutableAssign(baseCorrectParams, { displayName: 'a'.repeat(150) })
await makePostBodyRequest({ url: server.url, path: registrationPath, token: server.accessToken, fields })
})
it('Should fail with a bad channel name', async function () { it('Should fail with a bad channel name', async function () {
const fields = immutableAssign(baseCorrectParams, { channel: { name: '[]azf', displayName: 'toto' } }) const fields = immutableAssign(baseCorrectParams, { channel: { name: '[]azf', displayName: 'toto' } })

View File

@ -17,11 +17,12 @@ import {
getUserInformation, getUserInformation,
getUsersList, getUsersList,
getUsersListPaginationAndSort, getUsersListPaginationAndSort,
getVideoChannel,
getVideosList, getVideosList,
login, login,
makePutBodyRequest, makePutBodyRequest,
rateVideo, rateVideo,
registerUser, registerUserWithChannel,
removeUser, removeUser,
removeVideo, removeVideo,
ServerInfo, ServerInfo,
@ -31,8 +32,7 @@ import {
updateMyUser, updateMyUser,
updateUser, updateUser,
uploadVideo, uploadVideo,
userLogin, userLogin
registerUserWithChannel, getVideoChannel
} from '../../../../shared/extra-utils' } from '../../../../shared/extra-utils'
import { follow } from '../../../../shared/extra-utils/server/follows' import { follow } from '../../../../shared/extra-utils/server/follows'
import { setAccessTokensToServers } from '../../../../shared/extra-utils/users/login' import { setAccessTokensToServers } from '../../../../shared/extra-utils/users/login'
@ -618,7 +618,7 @@ describe('Test users', function () {
describe('Registering a new user', function () { describe('Registering a new user', function () {
it('Should register a new user', async function () { it('Should register a new user', async function () {
const user = { username: 'user_15', password: 'my super password' } const user = { displayName: 'super user 15', username: 'user_15', password: 'my super password' }
const channel = { name: 'my_user_15_channel', displayName: 'my channel rocks' } const channel = { name: 'my_user_15_channel', displayName: 'my channel rocks' }
await registerUserWithChannel({ url: server.url, user, channel }) await registerUserWithChannel({ url: server.url, user, channel })
@ -633,6 +633,13 @@ describe('Test users', function () {
accessToken = await userLogin(server, user15) accessToken = await userLogin(server, user15)
}) })
it('Should have the correct display name', async function () {
const res = await getMyUserInformation(server.url, accessToken)
const user: User = res.body
expect(user.account.displayName).to.equal('super user 15')
})
it('Should have the correct video quota', async function () { it('Should have the correct video quota', async function () {
const res = await getMyUserInformation(server.url, accessToken) const res = await getMyUserInformation(server.url, accessToken)
const user = res.body const user = res.body

View File

@ -754,7 +754,6 @@ describe('Test video playlists', function () {
} }
}) })
it('Should be able to create a public playlist, and set it to private', async function () { it('Should be able to create a public playlist, and set it to private', async function () {
this.timeout(30000) this.timeout(30000)

View File

@ -73,7 +73,7 @@ function registerUser (url: string, username: string, password: string, specialS
function registerUserWithChannel (options: { function registerUserWithChannel (options: {
url: string, url: string,
user: { username: string, password: string }, user: { username: string, password: string, displayName?: string },
channel: { name: string, displayName: string } channel: { name: string, displayName: string }
}) { }) {
const path = '/api/v1/users/register' const path = '/api/v1/users/register'
@ -84,6 +84,10 @@ function registerUserWithChannel (options: {
channel: options.channel channel: options.channel
} }
if (options.user.displayName) {
Object.assign(body, { displayName: options.user.displayName })
}
return makePostBodyRequest({ return makePostBodyRequest({
url: options.url, url: options.url,
path, path,

View File

@ -3,6 +3,8 @@ export interface UserRegister {
password: string password: string
email: string email: string
displayName?: string
channel?: { channel?: {
name: string name: string
displayName: string displayName: string

View File

@ -2290,6 +2290,19 @@ components:
email: email:
type: string type: string
description: 'The email of the user ' description: 'The email of the user '
displayName:
type: string
description: 'The user display name'
channel:
type: object
properties:
name:
type: string
description: 'The default channel name'
displayName:
type: string
description: 'The default channel display name'
required: required:
- username - username
- password - password