Add ability to update the user display name/description
This commit is contained in:
parent
d62cf3234c
commit
ed56ad1193
|
@ -5,7 +5,7 @@
|
|||
</a>
|
||||
|
||||
<div class="logged-in-info">
|
||||
<a routerLink="/my-account/settings" class="logged-in-username">{{ user.username }}</a>
|
||||
<a routerLink="/my-account/settings" class="logged-in-username">{{ user.account?.displayName }}</a>
|
||||
<div class="logged-in-email">{{ user.email }}</div>
|
||||
</div>
|
||||
|
||||
|
@ -14,8 +14,8 @@
|
|||
|
||||
<ul *dropdownMenu class="dropdown-menu">
|
||||
<li>
|
||||
<a i18n routerLink="/my-account/settings" class="dropdown-item" title="My account">
|
||||
My account
|
||||
<a routerLink="/my-account/settings" class="dropdown-item" title="My settings">
|
||||
My settings
|
||||
</a>
|
||||
|
||||
<a (click)="logout($event)" class="dropdown-item" title="Log out" href="#">
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
export * from './my-account-details.component'
|
|
@ -0,0 +1 @@
|
|||
export * from './my-account-profile.component'
|
|
@ -0,0 +1,24 @@
|
|||
<div *ngIf="error" class="alert alert-danger">{{ error }}</div>
|
||||
|
||||
<form role="form" (ngSubmit)="updateMyProfile()" [formGroup]="form">
|
||||
|
||||
<label for="display-name">Display name</label>
|
||||
<input
|
||||
type="text" id="display-name"
|
||||
formControlName="display-name" [ngClass]="{ 'input-error': formErrors['display-name'] }"
|
||||
>
|
||||
<div *ngIf="formErrors['display-name']" class="form-error">
|
||||
{{ formErrors['display-name'] }}
|
||||
</div>
|
||||
|
||||
<label for="description">Description</label>
|
||||
<textarea
|
||||
id="description" formControlName="description"
|
||||
[ngClass]="{ 'input-error': formErrors['description'] }"
|
||||
></textarea>
|
||||
<div *ngIf="formErrors.description" class="form-error">
|
||||
{{ formErrors.description }}
|
||||
</div>
|
||||
|
||||
<input type="submit" value="Update my profile" [disabled]="!form.valid">
|
||||
</form>
|
|
@ -0,0 +1,23 @@
|
|||
@import '_variables';
|
||||
@import '_mixins';
|
||||
|
||||
input[type=text] {
|
||||
@include peertube-input-text(340px);
|
||||
|
||||
display: block;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
textarea {
|
||||
@include peertube-textarea(500px, 150px);
|
||||
|
||||
display: block;
|
||||
}
|
||||
|
||||
input[type=submit] {
|
||||
@include peertube-button;
|
||||
@include orange-button;
|
||||
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
import { Component, Input, OnInit } from '@angular/core'
|
||||
import { FormBuilder, FormGroup } from '@angular/forms'
|
||||
import { NotificationsService } from 'angular2-notifications'
|
||||
import { FormReactive, USER_DESCRIPTION, USER_DISPLAY_NAME, UserService } from '../../../shared'
|
||||
import { User } from '@app/shared'
|
||||
|
||||
@Component({
|
||||
selector: 'my-account-profile',
|
||||
templateUrl: './my-account-profile.component.html',
|
||||
styleUrls: [ './my-account-profile.component.scss' ]
|
||||
})
|
||||
export class MyAccountProfileComponent extends FormReactive implements OnInit {
|
||||
@Input() user: User = null
|
||||
|
||||
error: string = null
|
||||
|
||||
form: FormGroup
|
||||
formErrors = {
|
||||
'display-name': '',
|
||||
'description': ''
|
||||
}
|
||||
validationMessages = {
|
||||
'display-name': USER_DISPLAY_NAME.MESSAGES,
|
||||
'description': USER_DESCRIPTION.MESSAGES
|
||||
}
|
||||
|
||||
constructor (
|
||||
private formBuilder: FormBuilder,
|
||||
private notificationsService: NotificationsService,
|
||||
private userService: UserService
|
||||
) {
|
||||
super()
|
||||
}
|
||||
|
||||
buildForm () {
|
||||
this.form = this.formBuilder.group({
|
||||
'display-name': [ this.user.account.displayName, USER_DISPLAY_NAME.VALIDATORS ],
|
||||
'description': [ this.user.account.description, USER_DESCRIPTION.VALIDATORS ]
|
||||
})
|
||||
|
||||
this.form.valueChanges.subscribe(data => this.onValueChanged(data))
|
||||
}
|
||||
|
||||
ngOnInit () {
|
||||
this.buildForm()
|
||||
}
|
||||
|
||||
updateMyProfile () {
|
||||
const displayName = this.form.value['display-name']
|
||||
const description = this.form.value['description']
|
||||
|
||||
this.error = null
|
||||
|
||||
this.userService.updateMyProfile({ displayName, description }).subscribe(
|
||||
() => {
|
||||
this.user.account.displayName = displayName
|
||||
this.user.account.description = description
|
||||
|
||||
this.notificationsService.success('Success', 'Profile updated.')
|
||||
},
|
||||
|
||||
err => this.error = err.message
|
||||
)
|
||||
}
|
||||
}
|
|
@ -17,8 +17,13 @@
|
|||
<span class="user-quota-label">Video quota:</span> {{ userVideoQuotaUsed | bytes: 0 }} / {{ userVideoQuota }}
|
||||
</div>
|
||||
|
||||
<div class="account-title">Account settings</div>
|
||||
<ng-template [ngIf]="user && user.account">
|
||||
<div class="account-title">Profile</div>
|
||||
<my-account-profile [user]="user"></my-account-profile>
|
||||
</ng-template>
|
||||
|
||||
<div class="account-title">Password</div>
|
||||
<my-account-change-password></my-account-change-password>
|
||||
|
||||
<div class="account-title">Video settings</div>
|
||||
<my-account-details [user]="user"></my-account-details>
|
||||
<my-account-video-settings [user]="user"></my-account-video-settings>
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
export * from './my-account-video-settings.component'
|
|
@ -6,11 +6,11 @@ import { AuthService } from '../../../core'
|
|||
import { FormReactive, User, UserService } from '../../../shared'
|
||||
|
||||
@Component({
|
||||
selector: 'my-account-details',
|
||||
templateUrl: './my-account-details.component.html',
|
||||
styleUrls: [ './my-account-details.component.scss' ]
|
||||
selector: 'my-account-video-settings',
|
||||
templateUrl: './my-account-video-settings.component.html',
|
||||
styleUrls: [ './my-account-video-settings.component.scss' ]
|
||||
})
|
||||
export class MyAccountDetailsComponent extends FormReactive implements OnInit {
|
||||
export class MyAccountVideoSettingsComponent extends FormReactive implements OnInit {
|
||||
@Input() user: User = null
|
||||
|
||||
form: FormGroup
|
||||
|
@ -47,7 +47,7 @@ export class MyAccountDetailsComponent extends FormReactive implements OnInit {
|
|||
autoPlayVideo
|
||||
}
|
||||
|
||||
this.userService.updateMyDetails(details).subscribe(
|
||||
this.userService.updateMyProfile(details).subscribe(
|
||||
() => {
|
||||
this.notificationsService.success('Success', 'Information updated.')
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
<div class="row">
|
||||
<div class="sub-menu">
|
||||
<a routerLink="/my-account/settings" routerLinkActive="active" class="title-page">My account</a>
|
||||
<a routerLink="/my-account/settings" routerLinkActive="active" class="title-page">My settings</a>
|
||||
|
||||
<a routerLink="/my-account/videos" routerLinkActive="active" class="title-page">My videos</a>
|
||||
</div>
|
||||
|
|
|
@ -2,10 +2,11 @@ import { NgModule } from '@angular/core'
|
|||
import { SharedModule } from '../shared'
|
||||
import { MyAccountRoutingModule } from './my-account-routing.module'
|
||||
import { MyAccountChangePasswordComponent } from './my-account-settings/my-account-change-password/my-account-change-password.component'
|
||||
import { MyAccountDetailsComponent } from './my-account-settings/my-account-details/my-account-details.component'
|
||||
import { MyAccountVideoSettingsComponent } from './my-account-settings/my-account-video-settings/my-account-video-settings.component'
|
||||
import { MyAccountSettingsComponent } from './my-account-settings/my-account-settings.component'
|
||||
import { MyAccountComponent } from './my-account.component'
|
||||
import { MyAccountVideosComponent } from './my-account-videos/my-account-videos.component'
|
||||
import { MyAccountProfileComponent } from '@app/my-account/my-account-settings/my-account-profile/my-account-profile.component'
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
|
@ -17,7 +18,8 @@ import { MyAccountVideosComponent } from './my-account-videos/my-account-videos.
|
|||
MyAccountComponent,
|
||||
MyAccountSettingsComponent,
|
||||
MyAccountChangePasswordComponent,
|
||||
MyAccountDetailsComponent,
|
||||
MyAccountVideoSettingsComponent,
|
||||
MyAccountProfileComponent,
|
||||
MyAccountVideosComponent
|
||||
],
|
||||
|
||||
|
|
|
@ -46,3 +46,27 @@ export const USER_ROLE = {
|
|||
'required': 'User role is required.'
|
||||
}
|
||||
}
|
||||
export const USER_DISPLAY_NAME = {
|
||||
VALIDATORS: [
|
||||
Validators.required,
|
||||
Validators.minLength(3),
|
||||
Validators.maxLength(120)
|
||||
],
|
||||
MESSAGES: {
|
||||
'required': 'Display name is required.',
|
||||
'minlength': 'Display name must be at least 3 characters long.',
|
||||
'maxlength': 'Display name cannot be more than 120 characters long.'
|
||||
}
|
||||
}
|
||||
export const USER_DESCRIPTION = {
|
||||
VALIDATORS: [
|
||||
Validators.required,
|
||||
Validators.minLength(3),
|
||||
Validators.maxLength(250)
|
||||
],
|
||||
MESSAGES: {
|
||||
'required': 'Display name is required.',
|
||||
'minlength': 'Display name must be at least 3 characters long.',
|
||||
'maxlength': 'Display name cannot be more than 250 characters long.'
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,10 +26,10 @@ export class UserService {
|
|||
.catch(res => this.restExtractor.handleError(res))
|
||||
}
|
||||
|
||||
updateMyDetails (details: UserUpdateMe) {
|
||||
updateMyProfile (profile: UserUpdateMe) {
|
||||
const url = UserService.BASE_USERS_URL + 'me'
|
||||
|
||||
return this.authHttp.put(url, details)
|
||||
return this.authHttp.put(url, profile)
|
||||
.map(this.restExtractor.extractDataBool)
|
||||
.catch(res => this.restExtractor.handleError(res))
|
||||
}
|
||||
|
|
|
@ -303,6 +303,7 @@ async function updateMe (req: express.Request, res: express.Response, next: expr
|
|||
await sequelizeTypescript.transaction(async t => {
|
||||
await user.save({ transaction: t })
|
||||
|
||||
if (body.displayName !== undefined) user.Account.name = body.displayName
|
||||
if (body.description !== undefined) user.Account.description = body.description
|
||||
await user.Account.save({ transaction: t })
|
||||
|
||||
|
|
|
@ -22,6 +22,10 @@ function isUserUsernameValid (value: string) {
|
|||
return exists(value) && validator.matches(value, new RegExp(`^[a-z0-9._]{${min},${max}}$`))
|
||||
}
|
||||
|
||||
function isUserDisplayNameValid (value: string) {
|
||||
return value === null || (exists(value) && validator.isLength(value, CONSTRAINTS_FIELDS.USERS.NAME))
|
||||
}
|
||||
|
||||
function isUserDescriptionValid (value: string) {
|
||||
return value === null || (exists(value) && validator.isLength(value, CONSTRAINTS_FIELDS.USERS.DESCRIPTION))
|
||||
}
|
||||
|
@ -60,6 +64,7 @@ export {
|
|||
isUserUsernameValid,
|
||||
isUserNSFWPolicyValid,
|
||||
isUserAutoPlayVideoValid,
|
||||
isUserDisplayNameValid,
|
||||
isUserDescriptionValid,
|
||||
isAvatarFile
|
||||
}
|
||||
|
|
|
@ -180,9 +180,10 @@ const CONFIG = {
|
|||
|
||||
const CONSTRAINTS_FIELDS = {
|
||||
USERS: {
|
||||
NAME: { min: 3, max: 120 }, // Length
|
||||
DESCRIPTION: { min: 3, max: 250 }, // Length
|
||||
USERNAME: { min: 3, max: 20 }, // Length
|
||||
PASSWORD: { min: 6, max: 255 }, // Length
|
||||
DESCRIPTION: { min: 3, max: 250 }, // Length
|
||||
VIDEO_QUOTA: { min: -1 }
|
||||
},
|
||||
VIDEO_ABUSES: {
|
||||
|
|
|
@ -7,7 +7,7 @@ import { isIdOrUUIDValid } from '../../helpers/custom-validators/misc'
|
|||
import {
|
||||
isAvatarFile,
|
||||
isUserAutoPlayVideoValid,
|
||||
isUserDescriptionValid,
|
||||
isUserDescriptionValid, isUserDisplayNameValid,
|
||||
isUserNSFWPolicyValid,
|
||||
isUserPasswordValid,
|
||||
isUserRoleValid,
|
||||
|
@ -98,6 +98,7 @@ const usersUpdateValidator = [
|
|||
]
|
||||
|
||||
const usersUpdateMeValidator = [
|
||||
body('displayName').optional().custom(isUserDisplayNameValid).withMessage('Should have a valid display name'),
|
||||
body('description').optional().custom(isUserDescriptionValid).withMessage('Should have a valid description'),
|
||||
body('password').optional().custom(isUserPasswordValid).withMessage('Should have a valid password'),
|
||||
body('email').optional().isEmail().withMessage('Should have a valid email attribute'),
|
||||
|
|
|
@ -76,6 +76,22 @@ describe('Test users with multiple servers', function () {
|
|||
await wait(5000)
|
||||
})
|
||||
|
||||
it('Should be able to update my display name', async function () {
|
||||
this.timeout(10000)
|
||||
|
||||
await updateMyUser({
|
||||
url: servers[0].url,
|
||||
accessToken: servers[0].accessToken,
|
||||
displayName: 'my super display name'
|
||||
})
|
||||
|
||||
const res = await getMyUserInformation(servers[0].url, servers[0].accessToken)
|
||||
user = res.body
|
||||
expect(user.account.displayName).to.equal('my super display name')
|
||||
|
||||
await wait(5000)
|
||||
})
|
||||
|
||||
it('Should be able to update my description', async function () {
|
||||
this.timeout(10000)
|
||||
|
||||
|
@ -87,6 +103,7 @@ describe('Test users with multiple servers', function () {
|
|||
|
||||
const res = await getMyUserInformation(servers[0].url, servers[0].accessToken)
|
||||
user = res.body
|
||||
expect(user.account.displayName).to.equal('my super display name')
|
||||
expect(user.account.description).to.equal('my super description updated')
|
||||
|
||||
await wait(5000)
|
||||
|
@ -111,7 +128,7 @@ describe('Test users with multiple servers', function () {
|
|||
await wait(5000)
|
||||
})
|
||||
|
||||
it('Should have updated my avatar and my description on other servers too', async function () {
|
||||
it('Should have updated my profile on other servers too', async function () {
|
||||
for (const server of servers) {
|
||||
const resAccounts = await getAccountsList(server.url, '-createdAt')
|
||||
|
||||
|
@ -122,6 +139,7 @@ describe('Test users with multiple servers', function () {
|
|||
const rootServer1Get = resAccount.body as Account
|
||||
expect(rootServer1Get.name).to.equal('root')
|
||||
expect(rootServer1Get.host).to.equal('localhost:9001')
|
||||
expect(rootServer1Get.displayName).to.equal('my super display name')
|
||||
expect(rootServer1Get.description).to.equal('my super description updated')
|
||||
|
||||
await testImage(server.url, 'avatar2-resized', rootServer1Get.avatar.path, '.png')
|
||||
|
|
|
@ -172,6 +172,7 @@ describe('Test users', function () {
|
|||
expect(user.videoQuota).to.equal(2 * 1024 * 1024)
|
||||
expect(user.roleLabel).to.equal('User')
|
||||
expect(user.id).to.be.a('number')
|
||||
expect(user.account.displayName).to.equal('user_1')
|
||||
expect(user.account.description).to.be.null
|
||||
})
|
||||
|
||||
|
@ -316,6 +317,7 @@ describe('Test users', function () {
|
|||
expect(user.nsfwPolicy).to.equal('do_not_list')
|
||||
expect(user.videoQuota).to.equal(2 * 1024 * 1024)
|
||||
expect(user.id).to.be.a('number')
|
||||
expect(user.account.displayName).to.equal('user_1')
|
||||
expect(user.account.description).to.be.null
|
||||
})
|
||||
|
||||
|
@ -347,6 +349,7 @@ describe('Test users', function () {
|
|||
expect(user.nsfwPolicy).to.equal('do_not_list')
|
||||
expect(user.videoQuota).to.equal(2 * 1024 * 1024)
|
||||
expect(user.id).to.be.a('number')
|
||||
expect(user.account.displayName).to.equal('user_1')
|
||||
expect(user.account.description).to.be.null
|
||||
})
|
||||
|
||||
|
@ -365,6 +368,25 @@ describe('Test users', function () {
|
|||
await testImage(server.url, 'avatar-resized', user.account.avatar.path, '.png')
|
||||
})
|
||||
|
||||
it('Should be able to update my display name', async function () {
|
||||
await updateMyUser({
|
||||
url: server.url,
|
||||
accessToken: accessTokenUser,
|
||||
displayName: 'new display name'
|
||||
})
|
||||
|
||||
const res = await getMyUserInformation(server.url, accessTokenUser)
|
||||
const user = res.body
|
||||
|
||||
expect(user.username).to.equal('user_1')
|
||||
expect(user.email).to.equal('updated@example.com')
|
||||
expect(user.nsfwPolicy).to.equal('do_not_list')
|
||||
expect(user.videoQuota).to.equal(2 * 1024 * 1024)
|
||||
expect(user.id).to.be.a('number')
|
||||
expect(user.account.displayName).to.equal('new display name')
|
||||
expect(user.account.description).to.be.null
|
||||
})
|
||||
|
||||
it('Should be able to update my description', async function () {
|
||||
await updateMyUser({
|
||||
url: server.url,
|
||||
|
@ -380,6 +402,7 @@ describe('Test users', function () {
|
|||
expect(user.nsfwPolicy).to.equal('do_not_list')
|
||||
expect(user.videoQuota).to.equal(2 * 1024 * 1024)
|
||||
expect(user.id).to.be.a('number')
|
||||
expect(user.account.displayName).to.equal('new display name')
|
||||
expect(user.account.description).to.equal('my super description updated')
|
||||
})
|
||||
|
||||
|
|
|
@ -132,6 +132,7 @@ function updateMyUser (options: {
|
|||
nsfwPolicy?: NSFWPolicyType,
|
||||
email?: string,
|
||||
autoPlayVideo?: boolean
|
||||
displayName?: string,
|
||||
description?: string
|
||||
}) {
|
||||
const path = '/api/v1/users/me'
|
||||
|
@ -142,6 +143,7 @@ function updateMyUser (options: {
|
|||
if (options.autoPlayVideo !== undefined && options.autoPlayVideo !== null) toSend['autoPlayVideo'] = options.autoPlayVideo
|
||||
if (options.email !== undefined && options.email !== null) toSend['email'] = options.email
|
||||
if (options.description !== undefined && options.description !== null) toSend['description'] = options.description
|
||||
if (options.displayName !== undefined && options.displayName !== null) toSend['displayName'] = options.displayName
|
||||
|
||||
return makePutBodyRequest({
|
||||
url: options.url,
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { NSFWPolicyType } from '../videos/nsfw-policy.type'
|
||||
|
||||
export interface UserUpdateMe {
|
||||
displayName?: string
|
||||
description?: string
|
||||
nsfwPolicy?: NSFWPolicyType
|
||||
autoPlayVideo?: boolean
|
||||
|
|
Loading…
Reference in New Issue