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:
parent
1a03bea0c4
commit
1f20622f2b
|
@ -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
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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 })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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 })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -100,7 +100,6 @@ export class VideoImportTorrentComponent extends VideoSend implements OnInit, Ca
|
||||||
previewUrl: null
|
previewUrl: null
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
|
||||||
this.hydrateFormFromVideo()
|
this.hydrateFormFromVideo()
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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') })
|
||||||
|
|
|
@ -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' } })
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue