Improve registration
authorChocobozzz <me@florianbigard.com>
Fri, 7 Jun 2019 14:59:53 +0000 (16:59 +0200)
committerChocobozzz <me@florianbigard.com>
Fri, 7 Jun 2019 15:05:42 +0000 (17:05 +0200)
 * 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

23 files changed:
client/src/app/+my-account/my-account-settings/my-account-profile/my-account-profile.component.ts
client/src/app/+signup/+register/register-step-channel.component.html
client/src/app/+signup/+register/register-step-channel.component.ts
client/src/app/+signup/+register/register-step-user.component.html
client/src/app/+signup/+register/register-step-user.component.ts
client/src/app/+signup/+register/register.component.html
client/src/app/+signup/+register/register.component.scss
client/src/app/shared/forms/form-validators/user-validators.service.ts
client/src/app/shared/misc/loader.component.html
client/src/app/shared/misc/loader.component.scss
client/src/app/shared/users/user.service.ts
client/src/app/videos/+video-edit/video-add-components/video-import-torrent.component.ts
server/controllers/api/users/index.ts
server/initializers/installer.ts
server/initializers/migrations/0100-activitypub.ts
server/lib/user.ts
server/middlewares/validators/users.ts
server/tests/api/check-params/users.ts
server/tests/api/users/users.ts
server/tests/api/videos/video-playlists.ts
shared/extra-utils/users/users.ts
shared/models/users/user-register.model.ts
support/doc/api/openapi.yaml

index a9503ed1b8eb9769fb5292b357df98b3024490f7..fcad5a6c2759e3522829ab12fdbb688c0de74a8a 100644 (file)
@@ -30,7 +30,7 @@ export class MyAccountProfileComponent extends FormReactive implements OnInit {
 
   ngOnInit () {
     this.buildForm({
-      'display-name': this.userValidatorsService.USER_DISPLAY_NAME,
+      'display-name': this.userValidatorsService.USER_DISPLAY_NAME_REQUIRED,
       description: this.userValidatorsService.USER_DESCRIPTION
     })
 
index 68ea4473a1666453bc9f039d5e9032d448ccfa43..253374f871bf71d53ae65a46f8bfb7e98dfe8bb7 100644 (file)
     </p>
   </div>
 
+  <div class="form-group">
+    <label for="displayName" i18n>Channel display name</label>
+
+    <div class="input-group">
+      <input
+        type="text" id="displayName"
+        formControlName="displayName" [ngClass]="{ 'input-error': formErrors['displayName'] }"
+      >
+    </div>
+
+    <div *ngIf="formErrors.displayName" class="form-error">
+      {{ formErrors.displayName }}
+    </div>
+  </div>
+
   <div class="form-group">
     <label for="name" i18n>Channel name</label>
 
       </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>
       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">
-    <label for="displayName" i18n>Channel display name</label>
-
-    <div class="input-group">
-      <input
-        type="text" id="displayName"
-        formControlName="displayName" [ngClass]="{ 'input-error': formErrors['displayName'] }"
-      >
-    </div>
-
-    <div *ngIf="formErrors.displayName" class="form-error">
-      {{ formErrors.displayName }}
-    </div>
-  </div>
 </form>
index 9e13f75b39d616c55adb77028b5a243993580b61..e434b91a7796c6e9ebaf09d6313da0f66aa8037f 100644 (file)
@@ -1,8 +1,10 @@
 import { Component, EventEmitter, Input, OnInit, Output } from '@angular/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 { FormGroup } from '@angular/forms'
+import { pairwise } from 'rxjs/operators'
+import { concat, of } from 'rxjs'
 
 @Component({
   selector: 'my-register-step-channel',
@@ -16,6 +18,7 @@ export class RegisterStepChannelComponent extends FormReactive implements OnInit
   constructor (
     protected formValidatorService: FormValidatorService,
     private authService: AuthService,
+    private userService: UserService,
     private videoChannelValidatorsService: VideoChannelValidatorsService
   ) {
     super()
@@ -25,16 +28,29 @@ export class RegisterStepChannelComponent extends FormReactive implements OnInit
     return window.location.host
   }
 
-  isSameThanUsername () {
-    return this.username && this.username === this.form.value['name']
-  }
-
   ngOnInit () {
     this.buildForm({
-      name: this.videoChannelValidatorsService.VIDEO_CHANNEL_NAME,
-      displayName: this.videoChannelValidatorsService.VIDEO_CHANNEL_DISPLAY_NAME
+      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 () {
+    return this.username && this.username === this.form.value['name']
+  }
+
+  private onDisplayNameChange (oldDisplayName: string, newDisplayName: string) {
+    const name = this.form.value['name'] || ''
+
+    const newName = this.userService.getNewUsername(oldDisplayName, newDisplayName, name)
+    this.form.patchValue({ name: newName })
   }
 }
index cd0c78bfa57d17906bd6bf10476fe5895f7df4fb..47b3be8cc5a19ffe4146b8b2fec698bd660c8e1e 100644 (file)
@@ -1,5 +1,20 @@
 <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">
     <label for="username" i18n>Username</label>
 
       </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">
       {{ formErrors.username }}
     </div>
index 3825ae371cfbc70be6004c9f031b1a6f3c95f03a..3b71fd3c4139028536dbeba4fc626c0ccb5e452d 100644 (file)
@@ -1,8 +1,10 @@
 import { Component, EventEmitter, OnInit, Output } from '@angular/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 { FormGroup } from '@angular/forms'
+import { pairwise } from 'rxjs/operators'
+import { concat, of } from 'rxjs'
 
 @Component({
   selector: 'my-register-step-user',
@@ -15,6 +17,7 @@ export class RegisterStepUserComponent extends FormReactive implements OnInit {
   constructor (
     protected formValidatorService: FormValidatorService,
     private authService: AuthService,
+    private userService: UserService,
     private userValidatorsService: UserValidatorsService
   ) {
     super()
@@ -26,6 +29,7 @@ export class RegisterStepUserComponent extends FormReactive implements OnInit {
 
   ngOnInit () {
     this.buildForm({
+      displayName: this.userValidatorsService.USER_DISPLAY_NAME_REQUIRED,
       username: this.userValidatorsService.USER_USERNAME,
       password: this.userValidatorsService.USER_PASSWORD,
       email: this.userValidatorsService.USER_EMAIL,
@@ -33,5 +37,18 @@ export class RegisterStepUserComponent extends FormReactive implements OnInit {
     })
 
     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 })
   }
 }
index 24def68c1295dbd73268eedda4ff355e75498b5e..d7e47c1a80bf6beab46a6247b736194811e360d5 100644 (file)
         </cdk-step>
 
         <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>
         </cdk-step>
       </my-custom-stepper>
index 6f61b78f719df5a3c04eb7e6cbfc5461e7016327..8d14992e724d01d949f76fef4148d7ac2903ceed 100644 (file)
@@ -56,3 +56,26 @@ button {
   @include peertube-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;
+    }
+  }
+}
index 6589b25804ed73eda6d7fa10130085fad51d4cd3..2dafb1816c382f0f0fa1eb270622a0dddc94ed34 100644 (file)
@@ -12,7 +12,7 @@ export class UserValidatorsService {
   readonly USER_VIDEO_QUOTA: BuildFormValidator
   readonly USER_VIDEO_QUOTA_DAILY: BuildFormValidator
   readonly USER_ROLE: BuildFormValidator
-  readonly USER_DISPLAY_NAME: BuildFormValidator
+  readonly USER_DISPLAY_NAME_REQUIRED: BuildFormValidator
   readonly USER_DESCRIPTION: BuildFormValidator
   readonly USER_TERMS: BuildFormValidator
 
@@ -85,18 +85,7 @@ export class UserValidatorsService {
       }
     }
 
-    this.USER_DISPLAY_NAME = {
-      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_DISPLAY_NAME_REQUIRED = this.getDisplayName(true)
 
     this.USER_DESCRIPTION = {
       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
+  }
 }
index b8b7ad343a667ff51baa05a044c2dec0ea1902fd..ca8ed063eb456986af5a112a6e7425a46355bd70 100644 (file)
@@ -1,5 +1,5 @@
 <div *ngIf="loading">
-  <div class="lds-ring">
+  <div class="loader">
     <div></div>
     <div></div>
     <div></div>
index ddb64f07a795e5b758001dd6194ae4aa5dcacd80..ffac9c707bc533a1b827cac28ae7feac740c9ab6 100644 (file)
@@ -3,14 +3,14 @@
 
 // Thanks to https://loading.io/css/ (CC0 License)
 
-.lds-ring {
+.loader {
   display: inline-block;
   position: relative;
   width: 50px;
   height: 50px;
 }
 
-.lds-ring div {
+.loader div {
   box-sizing: border-box;
   display: block;
   position: absolute;
   margin: 6px;
   border: 4px solid;
   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;
 }
 
-.lds-ring div:nth-child(1) {
+.loader div:nth-child(1) {
   animation-delay: -0.45s;
 }
 
-.lds-ring div:nth-child(2) {
+.loader div:nth-child(2) {
   animation-delay: -0.3s;
 }
 
-.lds-ring div:nth-child(3) {
+.loader div:nth-child(3) {
   animation-delay: -0.15s;
 }
 
-@keyframes lds-ring {
+@keyframes loader {
   0% {
     transform: rotate(0deg);
   }
index 20883456fd68edd5a100857a32de5739a847771b..70ff9a058616bfce137196c34250900713573693 100644 (file)
@@ -136,6 +136,22 @@ export class UserService {
       .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 ###### */
 
   addUser (userCreate: UserCreate) {
index ed9cb5840803ca3345b68f196fb5c176715eaf1d..e47624dd655bd3915e35cc794c115dedebffc0ec 100644 (file)
@@ -100,7 +100,6 @@ export class VideoImportTorrentComponent extends VideoSend implements OnInit, Ca
           previewUrl: null
         }))
 
-
         this.hydrateFormFromVideo()
       },
 
index 48a6c63b832cc16e9d4112a3285a32ed7b799b61..99f51a64831200228d601ebe082098ffda1418e8 100644 (file)
@@ -184,7 +184,7 @@ async function createUser (req: express.Request, res: express.Response) {
     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()))
   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
   })
 
-  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()))
   logger.info('User %s with its channel and account registered.', body.username)
index e14554ede5191c0df5376b2b2a67dcfbac0b83c0..cb58454cb650be3edcda509ea0353eda99797092 100644 (file)
@@ -146,7 +146,7 @@ async function createOAuthAdminIfNotExist () {
   }
   const user = new UserModel(userData)
 
-  await createUserAccountAndChannelAndPlaylist(user, undefined, validatePassword)
+  await createUserAccountAndChannelAndPlaylist({ userToCreate: user, channelNames: undefined, validateUser: validatePassword })
   logger.info('Username: ' + username)
   logger.info('User password: ' + password)
 }
index 2880a97d9da18656a6273e8671a2796d61ffa2ee..96d44a7cee1aca65e9972180dd507575aa0461bd 100644 (file)
@@ -65,7 +65,12 @@ async function up (utils: {
   // Create application account
   {
     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()
     accountCreated.Actor.publicKey = publicKey
@@ -83,7 +88,7 @@ async function up (utils: {
   // Recreate accounts for each user
   const users = await db.User.findAll()
   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()
     account.Actor.publicKey = publicKey
index d9fd89e1594f77f70959d060e8c19f081330cd9b..b50b09d72a5a59f2762dd5622e5eea7429f710dd 100644 (file)
@@ -1,4 +1,3 @@
-import * as Sequelize from 'sequelize'
 import * as uuidv4 from 'uuid/v4'
 import { ActivityPubActorType } from '../../shared/models/activitypub'
 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 { createWatchLaterPlaylist } from './video-playlist'
 import { sequelizeTypescript } from '../initializers/database'
+import { Transaction } from 'sequelize/types'
 
 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 userOptions = {
       transaction: t,
@@ -24,7 +31,13 @@ async function createUserAccountAndChannelAndPlaylist (userToCreate: UserModel,
     const userCreated = await userToCreate.save(userOptions)
     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
 
     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 }
 }
 
-async function createLocalAccountWithoutKeys (
+async function createLocalAccountWithoutKeys (parameters: {
   name: string,
+  displayName?: string,
   userId: number | null,
   applicationId: number | null,
-  t: Sequelize.Transaction | undefined,
-  type: ActivityPubActorType= 'Person'
-) {
+  t: Transaction | undefined,
+  type?: ActivityPubActorType
+}) {
+  const { name, displayName, userId, applicationId, t, type = 'Person' } = parameters
   const url = getAccountActivityPubUrl(name)
 
   const actorInstance = buildActorInstance(type, url, name)
   const actorInstanceCreated = await actorInstance.save({ transaction: t })
 
   const accountInstance = new AccountModel({
-    name,
+    name: displayName || name,
     userId,
     applicationId,
     actorId: actorInstanceCreated.id
@@ -72,7 +87,13 @@ async function createLocalAccountWithoutKeys (
 }
 
 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)
 
@@ -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 } = {
     userId: user.id,
     newVideoFromSubscription: UserNotificationSettingValue.WEB,
index 7a081af33c266ceef1bec7551e9180997f14e912..b4e09c9b7a794b05e14bc9ddf45f206558faca30 100644 (file)
@@ -53,8 +53,16 @@ const usersRegisterValidator = [
   body('username').custom(isUserUsernameValid).withMessage('Should have a valid username'),
   body('password').custom(isUserPasswordValid).withMessage('Should have a valid password'),
   body('email').isEmail().withMessage('Should have a valid email'),
-  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'),
+  body('displayName')
+    .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) => {
     logger.debug('Checking usersRegister parameters', { parameters: omit(req.body, 'password') })
index 95097817b21dd5134370f353cded7f65f4b005a1..3268f8c9011b7c2e944f5dc69d0bc831f1c3d5c5 100644 (file)
@@ -643,6 +643,7 @@ describe('Test users API validators', function () {
     const registrationPath = path + '/register'
     const baseCorrectParams = {
       username: 'user3',
+      displayName: 'super user',
       email: 'test3@example.com',
       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 () {
       const fields = immutableAssign(baseCorrectParams, { channel: { name: '[]azf', displayName: 'toto' } })
 
index 9d2ef786f1da14303ce6f43fd6ca3146f81053d3..b1f214fe210f75d250d9049262b184340aebcf67 100644 (file)
@@ -17,11 +17,12 @@ import {
   getUserInformation,
   getUsersList,
   getUsersListPaginationAndSort,
+  getVideoChannel,
   getVideosList,
   login,
   makePutBodyRequest,
   rateVideo,
-  registerUser,
+  registerUserWithChannel,
   removeUser,
   removeVideo,
   ServerInfo,
@@ -31,8 +32,7 @@ import {
   updateMyUser,
   updateUser,
   uploadVideo,
-  userLogin,
-  registerUserWithChannel, getVideoChannel
+  userLogin
 } from '../../../../shared/extra-utils'
 import { follow } from '../../../../shared/extra-utils/server/follows'
 import { setAccessTokensToServers } from '../../../../shared/extra-utils/users/login'
@@ -618,7 +618,7 @@ describe('Test users', function () {
 
   describe('Registering a new user', 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' }
 
       await registerUserWithChannel({ url: server.url, user, channel })
@@ -633,6 +633,13 @@ describe('Test users', function () {
       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 () {
       const res = await getMyUserInformation(server.url, accessToken)
       const user = res.body
index 8690327c4db61ce787a1d0d50ca2ef8309f0c748..f82c8cbce0937f4b19390c9292f702acd4251c6d 100644 (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 () {
     this.timeout(30000)
 
index 0f2f0ae157ad4a3412c923365e20f2657279e54a..c09211b71ab43c5a5520ded9e979d78e1d4ff5c9 100644 (file)
@@ -73,7 +73,7 @@ function registerUser (url: string, username: string, password: string, specialS
 
 function registerUserWithChannel (options: {
   url: string,
-  user: { username: string, password: string },
+  user: { username: string, password: string, displayName?: string },
   channel: { name: string, displayName: string }
 }) {
   const path = '/api/v1/users/register'
@@ -84,6 +84,10 @@ function registerUserWithChannel (options: {
     channel: options.channel
   }
 
+  if (options.user.displayName) {
+    Object.assign(body, { displayName: options.user.displayName })
+  }
+
   return makePostBodyRequest({
     url: options.url,
     path,
index ce5c9c3d26b927052b7b0e8875ad7dc1baeb91a4..cf9a43a67ba9c64bb0d2296ac1fe19b8682580bd 100644 (file)
@@ -3,6 +3,8 @@ export interface UserRegister {
   password: string
   email: string
 
+  displayName?: string
+
   channel?: {
     name: string
     displayName: string
index 5a4f6fcb2b14c9a045ce3822fd3289f35b786988..332c0050c8847c18b066f8195674d849a2226aed 100644 (file)
@@ -2290,6 +2290,19 @@ components:
         email:
           type: string
           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:
         - username
         - password