import { AdminRoutingModule } from './admin-routing.module'
import { FriendsComponent, FriendAddComponent, FriendListComponent, FriendService } from './friends'
import { RequestSchedulersComponent, RequestSchedulersStatsComponent, RequestSchedulersService } from './request-schedulers'
-import { UsersComponent, UserAddComponent, UserListComponent, UserService } from './users'
+import { UsersComponent, UserAddComponent, UserUpdateComponent, UserListComponent, UserService } from './users'
import { VideoAbusesComponent, VideoAbuseListComponent } from './video-abuses'
import { SharedModule } from '../shared'
UsersComponent,
UserAddComponent,
+ UserUpdateComponent,
UserListComponent,
VideoAbusesComponent,
export * from './shared'
-export * from './user-add'
+export * from './user-edit'
export * from './user-list'
export * from './users.component'
export * from './users.routes'
import { BytesPipe } from 'angular-pipes/src/math/bytes.pipe'
import { AuthHttp, RestExtractor, RestDataSource, User } from '../../../shared'
-import { UserCreate } from '../../../../../../shared'
+import { UserCreate, UserUpdate } from '../../../../../../shared'
@Injectable()
export class UserService {
.catch(this.restExtractor.handleError)
}
+ updateUser (userId: number, userUpdate: UserUpdate) {
+ return this.authHttp.put(UserService.BASE_USERS_URL + userId, userUpdate)
+ .map(this.restExtractor.extractDataBool)
+ .catch(this.restExtractor.handleError)
+ }
+
+ getUser (userId: number) {
+ return this.authHttp.get(UserService.BASE_USERS_URL + userId)
+ .map(this.restExtractor.extractDataGet)
+ .catch(this.restExtractor.handleError)
+ }
+
getDataSource () {
return new RestDataSource(this.authHttp, UserService.BASE_USERS_URL, this.formatDataSource.bind(this))
}
+++ /dev/null
-export * from './user-add.component'
+++ /dev/null
-<div class="row">
- <div class="content-padding">
-
- <h3>Add user</h3>
-
- <div *ngIf="error" class="alert alert-danger">{{ error }}</div>
-
- <form role="form" (ngSubmit)="addUser()" [formGroup]="form">
- <div class="form-group">
- <label for="username">Username</label>
- <input
- type="text" class="form-control" id="username" placeholder="john"
- formControlName="username"
- >
- <div *ngIf="formErrors.username" class="alert alert-danger">
- {{ formErrors.username }}
- </div>
- </div>
-
- <div class="form-group">
- <label for="email">Email</label>
- <input
- type="text" class="form-control" id="email" placeholder="mail@example.com"
- formControlName="email"
- >
- <div *ngIf="formErrors.email" class="alert alert-danger">
- {{ formErrors.email }}
- </div>
- </div>
-
- <div class="form-group">
- <label for="password">Password</label>
- <input
- type="password" class="form-control" id="password"
- formControlName="password"
- >
- <div *ngIf="formErrors.password" class="alert alert-danger">
- {{ formErrors.password }}
- </div>
- </div>
-
- <div class="form-group">
- <label for="videoQuota">Video quota</label>
- <select class="form-control" id="videoQuota" formControlName="videoQuota">
- <option value="-1">Unlimited</option>
- <option value="100000000">100MB</option>
- <option value="500000000">500MB</option>
- <option value="1000000000">1GB</option>
- <option value="5000000000">5GB</option>
- <option value="20000000000">20GB</option>
- <option value="50000000000">50GB</option>
- </select>
- </div>
-
- <input type="submit" value="Add user" class="btn btn-default" [disabled]="!form.valid">
- </form>
- </div>
-</div>
+++ /dev/null
-import { Component, OnInit } from '@angular/core'
-import { FormBuilder, FormGroup } from '@angular/forms'
-import { Router } from '@angular/router'
-
-import { NotificationsService } from 'angular2-notifications'
-
-import { UserService } from '../shared'
-import {
- FormReactive,
- USER_USERNAME,
- USER_EMAIL,
- USER_PASSWORD,
- USER_VIDEO_QUOTA
-} from '../../../shared'
-import { UserCreate } from '../../../../../../shared'
-
-@Component({
- selector: 'my-user-add',
- templateUrl: './user-add.component.html'
-})
-export class UserAddComponent extends FormReactive implements OnInit {
- error: string = null
-
- form: FormGroup
- formErrors = {
- 'username': '',
- 'email': '',
- 'password': '',
- 'videoQuota': ''
- }
- validationMessages = {
- 'username': USER_USERNAME.MESSAGES,
- 'email': USER_EMAIL.MESSAGES,
- 'password': USER_PASSWORD.MESSAGES,
- 'videoQuota': USER_VIDEO_QUOTA.MESSAGES
- }
-
- constructor (
- private formBuilder: FormBuilder,
- private router: Router,
- private notificationsService: NotificationsService,
- private userService: UserService
- ) {
- super()
- }
-
- buildForm () {
- this.form = this.formBuilder.group({
- username: [ '', USER_USERNAME.VALIDATORS ],
- email: [ '', USER_EMAIL.VALIDATORS ],
- password: [ '', USER_PASSWORD.VALIDATORS ],
- videoQuota: [ '-1', USER_VIDEO_QUOTA.VALIDATORS ]
- })
-
- this.form.valueChanges.subscribe(data => this.onValueChanged(data))
- }
-
- ngOnInit () {
- this.buildForm()
- }
-
- addUser () {
- this.error = null
-
- const userCreate: UserCreate = this.form.value
-
- // A select in HTML is always mapped as a string, we convert it to number
- userCreate.videoQuota = parseInt(this.form.value['videoQuota'], 10)
-
- this.userService.addUser(userCreate).subscribe(
- () => {
- this.notificationsService.success('Success', `User ${userCreate.username} created.`)
- this.router.navigate([ '/admin/users/list' ])
- },
-
- err => this.error = err.text
- )
- }
-}
--- /dev/null
+export * from './user-add.component'
+export * from './user-update.component'
--- /dev/null
+import { Component, OnInit } from '@angular/core'
+import { FormBuilder, FormGroup } from '@angular/forms'
+import { Router } from '@angular/router'
+
+import { NotificationsService } from 'angular2-notifications'
+
+import { UserService } from '../shared'
+import {
+ USER_USERNAME,
+ USER_EMAIL,
+ USER_PASSWORD,
+ USER_VIDEO_QUOTA
+} from '../../../shared'
+import { UserCreate } from '../../../../../../shared'
+import { UserEdit } from './user-edit'
+
+@Component({
+ selector: 'my-user-add',
+ templateUrl: './user-edit.component.html'
+})
+export class UserAddComponent extends UserEdit implements OnInit {
+ error: string
+
+ form: FormGroup
+ formErrors = {
+ 'username': '',
+ 'email': '',
+ 'password': '',
+ 'videoQuota': ''
+ }
+ validationMessages = {
+ 'username': USER_USERNAME.MESSAGES,
+ 'email': USER_EMAIL.MESSAGES,
+ 'password': USER_PASSWORD.MESSAGES,
+ 'videoQuota': USER_VIDEO_QUOTA.MESSAGES
+ }
+
+ constructor (
+ private formBuilder: FormBuilder,
+ private router: Router,
+ private notificationsService: NotificationsService,
+ private userService: UserService
+ ) {
+ super()
+ }
+
+ buildForm () {
+ this.form = this.formBuilder.group({
+ username: [ '', USER_USERNAME.VALIDATORS ],
+ email: [ '', USER_EMAIL.VALIDATORS ],
+ password: [ '', USER_PASSWORD.VALIDATORS ],
+ videoQuota: [ '-1', USER_VIDEO_QUOTA.VALIDATORS ]
+ })
+
+ this.form.valueChanges.subscribe(data => this.onValueChanged(data))
+ }
+
+ ngOnInit () {
+ this.buildForm()
+ }
+
+ formValidated () {
+ this.error = undefined
+
+ const userCreate: UserCreate = this.form.value
+
+ // A select in HTML is always mapped as a string, we convert it to number
+ userCreate.videoQuota = parseInt(this.form.value['videoQuota'], 10)
+
+ this.userService.addUser(userCreate).subscribe(
+ () => {
+ this.notificationsService.success('Success', `User ${userCreate.username} created.`)
+ this.router.navigate([ '/admin/users/list' ])
+ },
+
+ err => this.error = err.text
+ )
+ }
+
+ isCreation () {
+ return true
+ }
+
+ getFormButtonTitle () {
+ return 'Add user'
+ }
+}
--- /dev/null
+<div class="row">
+ <div class="content-padding">
+
+ <h3 *ngIf="isCreation() === true">Add user</h3>
+ <h3 *ngIf="isCreation() === false">Edit user {{ username }}</h3>
+
+ <div *ngIf="error" class="alert alert-danger">{{ error }}</div>
+
+ <form role="form" (ngSubmit)="formValidated()" [formGroup]="form">
+ <div class="form-group" *ngIf="isCreation()">
+ <label for="username">Username</label>
+ <input
+ type="text" class="form-control" id="username" placeholder="john"
+ formControlName="username"
+ >
+ <div *ngIf="formErrors.username" class="alert alert-danger">
+ {{ formErrors.username }}
+ </div>
+ </div>
+
+ <div class="form-group">
+ <label for="email">Email</label>
+ <input
+ type="text" class="form-control" id="email" placeholder="mail@example.com"
+ formControlName="email"
+ >
+ <div *ngIf="formErrors.email" class="alert alert-danger">
+ {{ formErrors.email }}
+ </div>
+ </div>
+
+ <div class="form-group" *ngIf="isCreation()">
+ <label for="password">Password</label>
+ <input
+ type="password" class="form-control" id="password"
+ formControlName="password"
+ >
+ <div *ngIf="formErrors.password" class="alert alert-danger">
+ {{ formErrors.password }}
+ </div>
+ </div>
+
+ <div class="form-group">
+ <label for="videoQuota">Video quota</label>
+ <select class="form-control" id="videoQuota" formControlName="videoQuota">
+ <option *ngFor="let videoQuotaOption of videoQuotaOptions" [value]="videoQuotaOption.value">
+ {{ videoQuotaOption.label }}
+ </option>
+ </select>
+ </div>
+
+ <input type="submit" value="{{ getFormButtonTitle() }}" class="btn btn-default" [disabled]="!form.valid">
+ </form>
+ </div>
+</div>
--- /dev/null
+import { FormReactive } from '../../../shared'
+
+export abstract class UserEdit extends FormReactive {
+ videoQuotaOptions = [
+ { value: -1, label: 'Unlimited' },
+ { value: 100 * 1024 * 1024, label: '100MB' },
+ { value: 5 * 1024 * 1024, label: '500MB' },
+ { value: 1024 * 1024 * 1024, label: '1GB' },
+ { value: 5 * 1024 * 1024 * 1024, label: '5GB' },
+ { value: 20 * 1024 * 1024 * 1024, label: '20GB' },
+ { value: 50 * 1024 * 1024 * 1024, label: '50GB' }
+ ]
+
+ abstract isCreation (): boolean
+ abstract getFormButtonTitle (): string
+}
--- /dev/null
+import { Component, OnDestroy, OnInit } from '@angular/core'
+import { FormBuilder, FormGroup } from '@angular/forms'
+import { ActivatedRoute, Router } from '@angular/router'
+import { Subscription } from 'rxjs/Subscription'
+
+import { NotificationsService } from 'angular2-notifications'
+
+import { UserService } from '../shared'
+import { USER_EMAIL, USER_VIDEO_QUOTA } from '../../../shared'
+import { UserUpdate } from '../../../../../../shared/models/users/user-update.model'
+import { User } from '../../../shared/users/user.model'
+import { UserEdit } from './user-edit'
+
+@Component({
+ selector: 'my-user-update',
+ templateUrl: './user-edit.component.html'
+})
+export class UserUpdateComponent extends UserEdit implements OnInit, OnDestroy {
+ error: string
+ userId: number
+ username: string
+
+ form: FormGroup
+ formErrors = {
+ 'email': '',
+ 'videoQuota': ''
+ }
+ validationMessages = {
+ 'email': USER_EMAIL.MESSAGES,
+ 'videoQuota': USER_VIDEO_QUOTA.MESSAGES
+ }
+
+ private paramsSub: Subscription
+
+ constructor (
+ private formBuilder: FormBuilder,
+ private route: ActivatedRoute,
+ private router: Router,
+ private notificationsService: NotificationsService,
+ private userService: UserService
+ ) {
+ super()
+ }
+
+ buildForm () {
+ this.form = this.formBuilder.group({
+ email: [ '', USER_EMAIL.VALIDATORS ],
+ videoQuota: [ '-1', USER_VIDEO_QUOTA.VALIDATORS ]
+ })
+
+ this.form.valueChanges.subscribe(data => this.onValueChanged(data))
+ }
+
+ ngOnInit () {
+ this.buildForm()
+
+ this.paramsSub = this.route.params.subscribe(routeParams => {
+ const userId = routeParams['id']
+ this.userService.getUser(userId).subscribe(
+ user => this.onUserFetched(user),
+
+ err => this.error = err.text
+ )
+ })
+ }
+
+ ngOnDestroy () {
+ this.paramsSub.unsubscribe()
+ }
+
+ formValidated () {
+ this.error = undefined
+
+ const userUpdate: UserUpdate = this.form.value
+
+ // A select in HTML is always mapped as a string, we convert it to number
+ userUpdate.videoQuota = parseInt(this.form.value['videoQuota'], 10)
+
+ this.userService.updateUser(this.userId, userUpdate).subscribe(
+ () => {
+ this.notificationsService.success('Success', `User ${this.username} updated.`)
+ this.router.navigate([ '/admin/users/list' ])
+ },
+
+ err => this.error = err.text
+ )
+ }
+
+ isCreation () {
+ return false
+ }
+
+ getFormButtonTitle () {
+ return 'Update user'
+ }
+
+ private onUserFetched (userJson: User) {
+ this.userId = userJson.id
+ this.username = userJson.username
+
+ this.form.patchValue({
+ email: userJson.email,
+ videoQuota: userJson.videoQuota
+ })
+ }
+}
<ng2-smart-table
[settings]="tableSettings" [source]="usersSource"
- (delete)="removeUser($event)"
+ (delete)="removeUser($event)" (edit)="editUser($event)"
></ng2-smart-table>
<a class="add-user btn btn-success pull-right" [routerLink]="['/admin/users/add']">
import { ConfirmService } from '../../../core'
import { RestDataSource, User, Utils } from '../../../shared'
import { UserService } from '../shared'
+import { Router } from '@angular/router'
@Component({
selector: 'my-user-list',
actions: {
position: 'right',
add: false,
- edit: false,
+ edit: true,
delete: true
},
delete: {
deleteButtonContent: Utils.getRowDeleteButton()
},
+ edit: {
+ editButtonContent: Utils.getRowEditButton()
+ },
pager: {
display: true,
- perPage: 1
+ perPage: 10
},
columns: {
id: {
}
constructor (
+ private router: Router,
private notificationsService: NotificationsService,
private confirmService: ConfirmService,
private userService: UserService
this.usersSource = this.userService.getDataSource()
}
- removeUser ({ data }) {
- const user: User = data
+ editUser ({ data }: { data: User }) {
+ this.router.navigate([ '/admin', 'users', data.id, 'update' ])
+ }
+
+ removeUser ({ data }: { data: User }) {
+ const user = data
if (user.username === 'root') {
this.notificationsService.error('Error', 'You cannot delete root.')
import { Routes } from '@angular/router'
import { UsersComponent } from './users.component'
-import { UserAddComponent } from './user-add'
+import { UserAddComponent, UserUpdateComponent } from './user-edit'
import { UserListComponent } from './user-list'
export const UsersRoutes: Routes = [
title: 'Add a user'
}
}
+ },
+ {
+ path: ':id/update',
+ component: UserUpdateComponent,
+ data: {
+ meta: {
+ title: 'Update a user'
+ }
+ }
}
]
}
constructor (
private formBuilder: FormBuilder,
- private router: Router,
private notificationsService: NotificationsService,
private userService: UserService
) {
UserService,
USER_PASSWORD
} from '../../shared'
-import { UserUpdate } from '../../../../../shared'
+import { UserUpdateMe } from '../../../../../shared'
@Component({
selector: 'my-account-details',
constructor (
private authService: AuthService,
private formBuilder: FormBuilder,
- private router: Router,
private notificationsService: NotificationsService,
private userService: UserService
) {
updateDetails () {
const displayNSFW = this.form.value['displayNSFW']
- const details: UserUpdate = {
+ const details: UserUpdateMe = {
displayNSFW
}
this.error = null
- this.userService.updateDetails(details).subscribe(
+ this.userService.updateMyDetails(details).subscribe(
() => {
- this.notificationsService.success('Success', 'Informations updated.')
+ this.notificationsService.success('Success', 'Information updated.')
this.authService.refreshUserInformations()
},
import { AuthService } from '../../core'
import { AuthHttp } from '../auth'
import { RestExtractor } from '../rest'
-import { UserCreate, UserUpdate } from '../../../../../shared'
+import { UserCreate, UserUpdateMe } from '../../../../../shared'
@Injectable()
export class UserService {
checkTokenValidity () {
const url = UserService.BASE_USERS_URL + 'me'
- // AuthHttp will redirect us to the login page if the oken is not valid anymore
+ // AuthHttp will redirect us to the login page if the token is not valid anymore
this.authHttp.get(url).subscribe()
}
changePassword (newPassword: string) {
- const url = UserService.BASE_USERS_URL + this.authService.getUser().id
- const body: UserUpdate = {
+ const url = UserService.BASE_USERS_URL + 'me'
+ const body: UserUpdateMe = {
password: newPassword
}
.catch((res) => this.restExtractor.handleError(res))
}
- updateDetails (details: UserUpdate) {
- const url = UserService.BASE_USERS_URL + this.authService.getUser().id
+ updateMyDetails (details: UserUpdateMe) {
+ const url = UserService.BASE_USERS_URL + 'me'
return this.authHttp.put(url, details)
.map(this.restExtractor.extractDataBool)
static getRowDeleteButton () {
return '<span class="glyphicon glyphicon-remove glyphicon-black"></span>'
}
+
+ static getRowEditButton () {
+ return '<span class="glyphicon glyphicon-pencil glyphicon-black"></span>'
+ }
}
ensureUserRegistrationAllowed,
usersAddValidator,
usersUpdateValidator,
+ usersUpdateMeValidator,
usersRemoveValidator,
usersVideoRatingValidator,
+ usersGetValidator,
paginationValidator,
setPagination,
usersSortValidator,
setUsersSort,
token
} from '../../middlewares'
-import { UserVideoRate as FormattedUserVideoRate, UserCreate, UserUpdate } from '../../../shared'
+import {
+ UserVideoRate as FormattedUserVideoRate,
+ UserCreate,
+ UserUpdate,
+ UserUpdateMe
+} from '../../../shared'
const usersRouter = express.Router()
listUsers
)
+usersRouter.get('/:id',
+ usersGetValidator,
+ getUser
+)
+
usersRouter.post('/',
authenticate,
ensureIsAdmin,
createUser
)
+usersRouter.put('/me',
+ authenticate,
+ usersUpdateMeValidator,
+ updateMe
+)
+
usersRouter.put('/:id',
authenticate,
+ ensureIsAdmin,
usersUpdateValidator,
updateUser
)
.catch(err => next(err))
}
+function getUser (req: express.Request, res: express.Response, next: express.NextFunction) {
+ return res.json(res.locals.user.toFormattedJSON())
+}
+
function getUserVideoRating (req: express.Request, res: express.Response, next: express.NextFunction) {
const videoId = +req.params.videoId
const userId = +res.locals.oauth.token.User.id
})
}
-function updateUser (req: express.Request, res: express.Response, next: express.NextFunction) {
- const body: UserUpdate = req.body
+function updateMe (req: express.Request, res: express.Response, next: express.NextFunction) {
+ const body: UserUpdateMe = req.body
+ // FIXME: user is not already a Sequelize instance?
db.User.loadByUsername(res.locals.oauth.token.user.username)
.then(user => {
- if (body.password) user.password = body.password
+ if (body.password !== undefined) user.password = body.password
+ if (body.email !== undefined) user.email = body.email
if (body.displayNSFW !== undefined) user.displayNSFW = body.displayNSFW
- if (body.videoQuota !== undefined) user.videoQuota = body.videoQuota
return user.save()
})
.catch(err => next(err))
}
+function updateUser (req: express.Request, res: express.Response, next: express.NextFunction) {
+ const body: UserUpdate = req.body
+ const user = res.locals.user
+
+ if (body.email !== undefined) user.email = body.email
+ if (body.videoQuota !== undefined) user.videoQuota = body.videoQuota
+
+ return user.save()
+ .then(() => res.sendStatus(204))
+ .catch(err => next(err))
+}
+
function success (req: express.Request, res: express.Response, next: express.NextFunction) {
res.end()
}
function usersUpdateValidator (req: express.Request, res: express.Response, next: express.NextFunction) {
req.checkParams('id', 'Should have a valid id').notEmpty().isInt()
+ req.checkBody('email', 'Should have a valid email attribute').optional().isEmail()
+ req.checkBody('videoQuota', 'Should have a valid user quota').optional().isUserVideoQuotaValid()
+
+ logger.debug('Checking usersUpdate parameters', { parameters: req.body })
+
+ checkErrors(req, res, () => {
+ checkUserExists(req.params.id, res, next)
+ })
+}
+
+function usersUpdateMeValidator (req: express.Request, res: express.Response, next: express.NextFunction) {
// Add old password verification
req.checkBody('password', 'Should have a valid password').optional().isUserPasswordValid()
+ req.checkBody('email', 'Should have a valid email attribute').optional().isEmail()
req.checkBody('displayNSFW', 'Should have a valid display Not Safe For Work attribute').optional().isUserDisplayNSFWValid()
- req.checkBody('videoQuota', 'Should have a valid user quota').optional().isUserVideoQuotaValid()
logger.debug('Checking usersUpdate parameters', { parameters: req.body })
checkErrors(req, res, next)
}
+function usersGetValidator (req: express.Request, res: express.Response, next: express.NextFunction) {
+ req.checkParams('id', 'Should have a valid id').notEmpty().isInt()
+
+ checkErrors(req, res, () => {
+ checkUserExists(req.params.id, res, next)
+ })
+}
+
function usersVideoRatingValidator (req: express.Request, res: express.Response, next: express.NextFunction) {
req.checkParams('videoId', 'Should have a valid video id').notEmpty().isVideoIdOrUUIDValid()
usersAddValidator,
usersRemoveValidator,
usersUpdateValidator,
+ usersUpdateMeValidator,
usersVideoRatingValidator,
- ensureUserRegistrationAllowed
+ ensureUserRegistrationAllowed,
+ usersGetValidator
+}
+
+// ---------------------------------------------------------------------------
+
+function checkUserExists (id: number, res: express.Response, callback: () => void) {
+ db.User.loadById(id)
+ .then(user => {
+ if (!user) return res.status(404).send('User not found')
+
+ res.locals.user = user
+ callback()
+ })
+ .catch(err => {
+ logger.error('Error in user request validator.', err)
+ return res.sendStatus(500)
+ })
}
export * from './user.model'
export * from './user-create.model'
export * from './user-update.model'
+export * from './user-update-me.model'
export * from './user-role.type'
--- /dev/null
+export interface UserUpdateMe {
+ displayNSFW?: boolean
+ email?: string
+ password?: string
+}
export interface UserUpdate {
- displayNSFW?: boolean
- password?: string
+ email?: string
videoQuota?: number
}