aboutsummaryrefslogtreecommitdiffhomepage
path: root/client
diff options
context:
space:
mode:
authorChocobozzz <florian.bigard@gmail.com>2017-06-16 14:32:15 +0200
committerChocobozzz <florian.bigard@gmail.com>2017-06-16 14:32:15 +0200
commitdf98563e2104b82b119c00a3cd83cd0dc1242d25 (patch)
treea9720bf01bac9ad5646bd3d3c9bc7653617afdad /client
parent46757b477c1adb5f98060d15998a3852e18902a6 (diff)
downloadPeerTube-df98563e2104b82b119c00a3cd83cd0dc1242d25.tar.gz
PeerTube-df98563e2104b82b119c00a3cd83cd0dc1242d25.tar.zst
PeerTube-df98563e2104b82b119c00a3cd83cd0dc1242d25.zip
Use typescript standard and lint all files
Diffstat (limited to 'client')
-rw-r--r--client/package.json5
-rw-r--r--client/src/app/+admin/admin-routing.module.ts16
-rw-r--r--client/src/app/+admin/admin.component.ts2
-rw-r--r--client/src/app/+admin/admin.module.ts18
-rw-r--r--client/src/app/+admin/friends/friend-add/friend-add.component.ts108
-rw-r--r--client/src/app/+admin/friends/friend-add/index.ts2
-rw-r--r--client/src/app/+admin/friends/friend-list/friend-list.component.ts38
-rw-r--r--client/src/app/+admin/friends/friend-list/index.ts2
-rw-r--r--client/src/app/+admin/friends/friends.component.ts5
-rw-r--r--client/src/app/+admin/friends/friends.routes.ts60
-rw-r--r--client/src/app/+admin/friends/index.ts10
-rw-r--r--client/src/app/+admin/friends/shared/friend.model.ts10
-rw-r--r--client/src/app/+admin/friends/shared/friend.service.ts30
-rw-r--r--client/src/app/+admin/friends/shared/index.ts4
-rw-r--r--client/src/app/+admin/index.ts12
-rw-r--r--client/src/app/+admin/requests/index.ts8
-rw-r--r--client/src/app/+admin/requests/request-stats/index.ts2
-rw-r--r--client/src/app/+admin/requests/request-stats/request-stats.component.ts55
-rw-r--r--client/src/app/+admin/requests/requests.component.ts5
-rw-r--r--client/src/app/+admin/requests/requests.routes.ts42
-rw-r--r--client/src/app/+admin/requests/shared/index.ts4
-rw-r--r--client/src/app/+admin/requests/shared/request-stats.model.ts36
-rw-r--r--client/src/app/+admin/requests/shared/request.service.ts28
-rw-r--r--client/src/app/+admin/users/index.ts10
-rw-r--r--client/src/app/+admin/users/shared/index.ts2
-rw-r--r--client/src/app/+admin/users/shared/user.service.ts26
-rw-r--r--client/src/app/+admin/users/user-add/index.ts2
-rw-r--r--client/src/app/+admin/users/user-add/user-add.component.ts50
-rw-r--r--client/src/app/+admin/users/user-list/index.ts2
-rw-r--r--client/src/app/+admin/users/user-list/user-list.component.ts36
-rw-r--r--client/src/app/+admin/users/users.component.ts5
-rw-r--r--client/src/app/+admin/users/users.routes.ts10
-rw-r--r--client/src/app/+admin/video-abuses/index.ts6
-rw-r--r--client/src/app/+admin/video-abuses/video-abuse-list/index.ts2
-rw-r--r--client/src/app/+admin/video-abuses/video-abuse-list/video-abuse-list.component.ts24
-rw-r--r--client/src/app/+admin/video-abuses/video-abuses.component.ts2
-rw-r--r--client/src/app/+admin/video-abuses/video-abuses.routes.ts8
-rw-r--r--client/src/app/account/account-change-password/account-change-password.component.ts48
-rw-r--r--client/src/app/account/account-change-password/index.ts2
-rw-r--r--client/src/app/account/account-details/account-details.component.ts52
-rw-r--r--client/src/app/account/account-details/index.ts2
-rw-r--r--client/src/app/account/account-routing.module.ts8
-rw-r--r--client/src/app/account/account.component.ts20
-rw-r--r--client/src/app/account/account.module.ts14
-rw-r--r--client/src/app/account/index.ts6
-rw-r--r--client/src/app/app-routing.module.ts7
-rw-r--r--client/src/app/app.component.ts50
-rw-r--r--client/src/app/app.module.ts80
-rw-r--r--client/src/app/app.resolver.ts14
-rw-r--r--client/src/app/app.service.ts20
-rw-r--r--client/src/app/core/auth/auth-user.model.ts180
-rw-r--r--client/src/app/core/auth/auth.service.ts236
-rw-r--r--client/src/app/core/auth/index.ts4
-rw-r--r--client/src/app/core/config/config.service.ts24
-rw-r--r--client/src/app/core/config/index.ts2
-rw-r--r--client/src/app/core/confirm/confirm.component.ts48
-rw-r--r--client/src/app/core/confirm/confirm.service.ts16
-rw-r--r--client/src/app/core/confirm/index.ts4
-rw-r--r--client/src/app/core/core.module.ts26
-rw-r--r--client/src/app/core/index.ts8
-rw-r--r--client/src/app/core/menu/index.ts4
-rw-r--r--client/src/app/core/menu/menu-admin.component.ts2
-rw-r--r--client/src/app/core/menu/menu.component.ts40
-rw-r--r--client/src/app/core/module-import-guard.ts4
-rw-r--r--client/src/app/environment.ts2
-rw-r--r--client/src/app/index.ts2
-rw-r--r--client/src/app/login/index.ts6
-rw-r--r--client/src/app/login/login-routing.module.ts8
-rw-r--r--client/src/app/login/login.component.ts50
-rw-r--r--client/src/app/login/login.module.ts8
-rw-r--r--client/src/app/shared/auth/auth-http.service.ts82
-rw-r--r--client/src/app/shared/auth/index.ts2
-rw-r--r--client/src/app/shared/forms/form-reactive.ts30
-rw-r--r--client/src/app/shared/forms/form-validators/host.validator.ts8
-rw-r--r--client/src/app/shared/forms/form-validators/index.ts8
-rw-r--r--client/src/app/shared/forms/form-validators/user.ts12
-rw-r--r--client/src/app/shared/forms/form-validators/video-abuse.ts4
-rw-r--r--client/src/app/shared/forms/form-validators/video.ts14
-rw-r--r--client/src/app/shared/forms/index.ts4
-rw-r--r--client/src/app/shared/index.ts16
-rw-r--r--client/src/app/shared/rest/index.ts8
-rw-r--r--client/src/app/shared/rest/rest-data-source.ts60
-rw-r--r--client/src/app/shared/rest/rest-extractor.service.ts48
-rw-r--r--client/src/app/shared/rest/rest-pagination.ts8
-rw-r--r--client/src/app/shared/rest/rest.service.ts22
-rw-r--r--client/src/app/shared/search/index.ts8
-rw-r--r--client/src/app/shared/search/search-field.type.ts2
-rw-r--r--client/src/app/shared/search/search.component.ts48
-rw-r--r--client/src/app/shared/search/search.model.ts6
-rw-r--r--client/src/app/shared/search/search.service.ts18
-rw-r--r--client/src/app/shared/shared.module.ts36
-rw-r--r--client/src/app/shared/users/index.ts4
-rw-r--r--client/src/app/shared/users/user.model.ts34
-rw-r--r--client/src/app/shared/users/user.service.ts44
-rw-r--r--client/src/app/shared/utils.ts10
-rw-r--r--client/src/app/shared/video-abuse/index.ts4
-rw-r--r--client/src/app/shared/video-abuse/video-abuse.model.ts12
-rw-r--r--client/src/app/shared/video-abuse/video-abuse.service.ts42
-rw-r--r--client/src/app/signup/index.ts6
-rw-r--r--client/src/app/signup/signup-routing.module.ts8
-rw-r--r--client/src/app/signup/signup.component.ts50
-rw-r--r--client/src/app/signup/signup.module.ts8
-rw-r--r--client/src/app/videos/index.ts14
-rw-r--r--client/src/app/videos/shared/index.ts8
-rw-r--r--client/src/app/videos/shared/rate-type.type.ts2
-rw-r--r--client/src/app/videos/shared/sort-field.type.ts3
-rw-r--r--client/src/app/videos/shared/video.model.ts150
-rw-r--r--client/src/app/videos/shared/video.service.ts146
-rw-r--r--client/src/app/videos/video-edit/index.ts4
-rw-r--r--client/src/app/videos/video-edit/video-add.component.ts159
-rw-r--r--client/src/app/videos/video-edit/video-update.component.ts99
-rw-r--r--client/src/app/videos/video-list/index.ts8
-rw-r--r--client/src/app/videos/video-list/loader.component.ts4
-rw-r--r--client/src/app/videos/video-list/video-list.component.html2
-rw-r--r--client/src/app/videos/video-list/video-list.component.ts126
-rw-r--r--client/src/app/videos/video-list/video-miniature.component.ts31
-rw-r--r--client/src/app/videos/video-list/video-sort.component.ts22
-rw-r--r--client/src/app/videos/video-watch/index.ts10
-rw-r--r--client/src/app/videos/video-watch/video-magnet.component.ts20
-rw-r--r--client/src/app/videos/video-watch/video-report.component.ts56
-rw-r--r--client/src/app/videos/video-watch/video-share.component.ts32
-rw-r--r--client/src/app/videos/video-watch/video-watch.component.ts323
-rw-r--r--client/src/app/videos/video-watch/webtorrent.service.ts34
-rw-r--r--client/src/app/videos/videos-routing.module.ts14
-rw-r--r--client/src/app/videos/videos.component.ts2
-rw-r--r--client/src/app/videos/videos.module.ts18
-rw-r--r--client/src/custom-typings.d.ts2
-rw-r--r--client/src/main.browser.aot.ts2
-rw-r--r--client/src/main.browser.ts2
-rw-r--r--client/src/polyfills.browser.ts2
-rw-r--r--client/tslint.json43
-rw-r--r--client/yarn.lock137
132 files changed, 1924 insertions, 1901 deletions
diff --git a/client/package.json b/client/package.json
index aee548c3f..794121ffb 100644
--- a/client/package.json
+++ b/client/package.json
@@ -13,7 +13,7 @@
13 "url": "git://github.com/Chocobozzz/PeerTube.git" 13 "url": "git://github.com/Chocobozzz/PeerTube.git"
14 }, 14 },
15 "scripts": { 15 "scripts": {
16 "test": "standard && tslint -c ./tslint.json src/**/*.ts", 16 "test": "standard && tslint --type-check --project ./tsconfig.json -c ./tslint.json 'src/app/**/*.ts'",
17 "webpack": "webpack", 17 "webpack": "webpack",
18 "webpack-dev-server": "webpack-dev-server" 18 "webpack-dev-server": "webpack-dev-server"
19 }, 19 },
@@ -78,7 +78,7 @@
78 "tslib": "^1.5.0", 78 "tslib": "^1.5.0",
79 "tslint": "~5.4.3", 79 "tslint": "~5.4.3",
80 "tslint-loader": "^3.3.0", 80 "tslint-loader": "^3.3.0",
81 "typescript": "~2.3.0", 81 "typescript": "~2.4.0",
82 "url-loader": "^0.5.7", 82 "url-loader": "^0.5.7",
83 "video.js": "^5.19.2", 83 "video.js": "^5.19.2",
84 "videojs-dock": "^2.0.2", 84 "videojs-dock": "^2.0.2",
@@ -93,6 +93,7 @@
93 "codelyzer": "^3.0.0-beta.4", 93 "codelyzer": "^3.0.0-beta.4",
94 "ng2-completer": "1.2.2", 94 "ng2-completer": "1.2.2",
95 "standard": "^10.0.0", 95 "standard": "^10.0.0",
96 "tslint-config-standard": "^6.0.1",
96 "webpack-bundle-analyzer": "^2.8.2", 97 "webpack-bundle-analyzer": "^2.8.2",
97 "webpack-dev-server": "^2.4.5", 98 "webpack-dev-server": "^2.4.5",
98 "webpack-dll-bundles-plugin": "^1.0.0-beta.5" 99 "webpack-dll-bundles-plugin": "^1.0.0-beta.5"
diff --git a/client/src/app/+admin/admin-routing.module.ts b/client/src/app/+admin/admin-routing.module.ts
index d3adf3f3b..839913135 100644
--- a/client/src/app/+admin/admin-routing.module.ts
+++ b/client/src/app/+admin/admin-routing.module.ts
@@ -1,11 +1,11 @@
1import { NgModule } from '@angular/core'; 1import { NgModule } from '@angular/core'
2import { RouterModule, Routes } from '@angular/router'; 2import { RouterModule, Routes } from '@angular/router'
3 3
4import { AdminComponent } from './admin.component'; 4import { AdminComponent } from './admin.component'
5import { FriendsRoutes } from './friends'; 5import { FriendsRoutes } from './friends'
6import { RequestsRoutes } from './requests'; 6import { RequestsRoutes } from './requests'
7import { UsersRoutes } from './users'; 7import { UsersRoutes } from './users'
8import { VideoAbusesRoutes } from './video-abuses'; 8import { VideoAbusesRoutes } from './video-abuses'
9 9
10const adminRoutes: Routes = [ 10const adminRoutes: Routes = [
11 { 11 {
@@ -23,7 +23,7 @@ const adminRoutes: Routes = [
23 ...VideoAbusesRoutes 23 ...VideoAbusesRoutes
24 ] 24 ]
25 } 25 }
26]; 26]
27 27
28@NgModule({ 28@NgModule({
29 imports: [ RouterModule.forChild(adminRoutes) ], 29 imports: [ RouterModule.forChild(adminRoutes) ],
diff --git a/client/src/app/+admin/admin.component.ts b/client/src/app/+admin/admin.component.ts
index 64a7400e7..93ec216bf 100644
--- a/client/src/app/+admin/admin.component.ts
+++ b/client/src/app/+admin/admin.component.ts
@@ -1,4 +1,4 @@
1import { Component } from '@angular/core'; 1import { Component } from '@angular/core'
2 2
3@Component({ 3@Component({
4 template: '<router-outlet></router-outlet>' 4 template: '<router-outlet></router-outlet>'
diff --git a/client/src/app/+admin/admin.module.ts b/client/src/app/+admin/admin.module.ts
index db1ce2d7f..9ecce5dc3 100644
--- a/client/src/app/+admin/admin.module.ts
+++ b/client/src/app/+admin/admin.module.ts
@@ -1,12 +1,12 @@
1import { NgModule } from '@angular/core'; 1import { NgModule } from '@angular/core'
2 2
3import { AdminComponent } from './admin.component'; 3import { AdminComponent } from './admin.component'
4import { AdminRoutingModule } from './admin-routing.module'; 4import { AdminRoutingModule } from './admin-routing.module'
5import { FriendsComponent, FriendAddComponent, FriendListComponent, FriendService } from './friends'; 5import { FriendsComponent, FriendAddComponent, FriendListComponent, FriendService } from './friends'
6import { RequestsComponent, RequestStatsComponent, RequestService } from './requests'; 6import { RequestsComponent, RequestStatsComponent, RequestService } from './requests'
7import { UsersComponent, UserAddComponent, UserListComponent, UserService } from './users'; 7import { UsersComponent, UserAddComponent, UserListComponent, UserService } from './users'
8import { VideoAbusesComponent, VideoAbuseListComponent } from './video-abuses'; 8import { VideoAbusesComponent, VideoAbuseListComponent } from './video-abuses'
9import { SharedModule } from '../shared'; 9import { SharedModule } from '../shared'
10 10
11@NgModule({ 11@NgModule({
12 imports: [ 12 imports: [
diff --git a/client/src/app/+admin/friends/friend-add/friend-add.component.ts b/client/src/app/+admin/friends/friend-add/friend-add.component.ts
index 12c46e5cd..35cf4a1f7 100644
--- a/client/src/app/+admin/friends/friend-add/friend-add.component.ts
+++ b/client/src/app/+admin/friends/friend-add/friend-add.component.ts
@@ -1,12 +1,12 @@
1import { Component, OnInit } from '@angular/core'; 1import { Component, OnInit } from '@angular/core'
2import { FormControl, FormGroup } from '@angular/forms'; 2import { FormControl, FormGroup } from '@angular/forms'
3import { Router } from '@angular/router'; 3import { Router } from '@angular/router'
4 4
5import { NotificationsService } from 'angular2-notifications'; 5import { NotificationsService } from 'angular2-notifications'
6 6
7import { ConfirmService } from '../../../core'; 7import { ConfirmService } from '../../../core'
8import { validateHost } from '../../../shared'; 8import { validateHost } from '../../../shared'
9import { FriendService } from '../shared'; 9import { FriendService } from '../shared'
10 10
11@Component({ 11@Component({
12 selector: 'my-friend-add', 12 selector: 'my-friend-add',
@@ -14,107 +14,107 @@ import { FriendService } from '../shared';
14 styleUrls: [ './friend-add.component.scss' ] 14 styleUrls: [ './friend-add.component.scss' ]
15}) 15})
16export class FriendAddComponent implements OnInit { 16export class FriendAddComponent implements OnInit {
17 form: FormGroup; 17 form: FormGroup
18 hosts = [ ]; 18 hosts = [ ]
19 error: string = null; 19 error: string = null
20 20
21 constructor( 21 constructor (
22 private router: Router, 22 private router: Router,
23 private notificationsService: NotificationsService, 23 private notificationsService: NotificationsService,
24 private confirmService: ConfirmService, 24 private confirmService: ConfirmService,
25 private friendService: FriendService 25 private friendService: FriendService
26 ) {} 26 ) {}
27 27
28 ngOnInit() { 28 ngOnInit () {
29 this.form = new FormGroup({}); 29 this.form = new FormGroup({})
30 this.addField(); 30 this.addField()
31 } 31 }
32 32
33 addField() { 33 addField () {
34 this.form.addControl(`host-${this.hosts.length}`, new FormControl('', [ validateHost ])); 34 this.form.addControl(`host-${this.hosts.length}`, new FormControl('', [ validateHost ]))
35 this.hosts.push(''); 35 this.hosts.push('')
36 } 36 }
37 37
38 canMakeFriends() { 38 canMakeFriends () {
39 return window.location.protocol === 'https:'; 39 return window.location.protocol === 'https:'
40 } 40 }
41 41
42 customTrackBy(index: number, obj: any): any { 42 customTrackBy (index: number, obj: any): any {
43 return index; 43 return index
44 } 44 }
45 45
46 displayAddField(index: number) { 46 displayAddField (index: number) {
47 return index === (this.hosts.length - 1); 47 return index === (this.hosts.length - 1)
48 } 48 }
49 49
50 displayRemoveField(index: number) { 50 displayRemoveField (index: number) {
51 return (index !== 0 || this.hosts.length > 1) && index !== (this.hosts.length - 1); 51 return (index !== 0 || this.hosts.length > 1) && index !== (this.hosts.length - 1)
52 } 52 }
53 53
54 isFormValid() { 54 isFormValid () {
55 // Do not check the last input 55 // Do not check the last input
56 for (let i = 0; i < this.hosts.length - 1; i++) { 56 for (let i = 0; i < this.hosts.length - 1; i++) {
57 if (!this.form.controls[`host-${i}`].valid) return false; 57 if (!this.form.controls[`host-${i}`].valid) return false
58 } 58 }
59 59
60 const lastIndex = this.hosts.length - 1; 60 const lastIndex = this.hosts.length - 1
61 // If the last input (which is not the first) is empty, it's ok 61 // If the last input (which is not the first) is empty, it's ok
62 if (this.hosts[lastIndex] === '' && lastIndex !== 0) { 62 if (this.hosts[lastIndex] === '' && lastIndex !== 0) {
63 return true; 63 return true
64 } else { 64 } else {
65 return this.form.controls[`host-${lastIndex}`].valid; 65 return this.form.controls[`host-${lastIndex}`].valid
66 } 66 }
67 } 67 }
68 68
69 removeField(index: number) { 69 removeField (index: number) {
70 // Remove the last control 70 // Remove the last control
71 this.form.removeControl(`host-${this.hosts.length - 1}`); 71 this.form.removeControl(`host-${this.hosts.length - 1}`)
72 this.hosts.splice(index, 1); 72 this.hosts.splice(index, 1)
73 } 73 }
74 74
75 makeFriends() { 75 makeFriends () {
76 this.error = ''; 76 this.error = ''
77 77
78 const notEmptyHosts = this.getNotEmptyHosts(); 78 const notEmptyHosts = this.getNotEmptyHosts()
79 if (notEmptyHosts.length === 0) { 79 if (notEmptyHosts.length === 0) {
80 this.error = 'You need to specify at least 1 host.'; 80 this.error = 'You need to specify at least 1 host.'
81 return; 81 return
82 } 82 }
83 83
84 if (!this.isHostsUnique(notEmptyHosts)) { 84 if (!this.isHostsUnique(notEmptyHosts)) {
85 this.error = 'Hosts need to be unique.'; 85 this.error = 'Hosts need to be unique.'
86 return; 86 return
87 } 87 }
88 88
89 const confirmMessage = 'Are you sure to make friends with:<br /> - ' + notEmptyHosts.join('<br /> - '); 89 const confirmMessage = 'Are you sure to make friends with:<br /> - ' + notEmptyHosts.join('<br /> - ')
90 this.confirmService.confirm(confirmMessage, 'Make friends').subscribe( 90 this.confirmService.confirm(confirmMessage, 'Make friends').subscribe(
91 res => { 91 res => {
92 if (res === false) return; 92 if (res === false) return
93 93
94 this.friendService.makeFriends(notEmptyHosts).subscribe( 94 this.friendService.makeFriends(notEmptyHosts).subscribe(
95 status => { 95 status => {
96 this.notificationsService.success('Sucess', 'Make friends request sent!'); 96 this.notificationsService.success('Sucess', 'Make friends request sent!')
97 this.router.navigate([ '/admin/friends/list' ]); 97 this.router.navigate([ '/admin/friends/list' ])
98 }, 98 },
99 99
100 err => this.notificationsService.error('Error', err.text) 100 err => this.notificationsService.error('Error', err.text)
101 ); 101 )
102 } 102 }
103 ); 103 )
104 } 104 }
105 105
106 private getNotEmptyHosts() { 106 private getNotEmptyHosts () {
107 const notEmptyHosts = []; 107 const notEmptyHosts = []
108 108
109 Object.keys(this.form.value).forEach((hostKey) => { 109 Object.keys(this.form.value).forEach((hostKey) => {
110 const host = this.form.value[hostKey]; 110 const host = this.form.value[hostKey]
111 if (host !== '') notEmptyHosts.push(host); 111 if (host !== '') notEmptyHosts.push(host)
112 }); 112 })
113 113
114 return notEmptyHosts; 114 return notEmptyHosts
115 } 115 }
116 116
117 private isHostsUnique(hosts: string[]) { 117 private isHostsUnique (hosts: string[]) {
118 return hosts.every(host => hosts.indexOf(host) === hosts.lastIndexOf(host)); 118 return hosts.every(host => hosts.indexOf(host) === hosts.lastIndexOf(host))
119 } 119 }
120} 120}
diff --git a/client/src/app/+admin/friends/friend-add/index.ts b/client/src/app/+admin/friends/friend-add/index.ts
index a101b3be5..978ab3d46 100644
--- a/client/src/app/+admin/friends/friend-add/index.ts
+++ b/client/src/app/+admin/friends/friend-add/index.ts
@@ -1 +1 @@
export * from './friend-add.component'; export * from './friend-add.component'
diff --git a/client/src/app/+admin/friends/friend-list/friend-list.component.ts b/client/src/app/+admin/friends/friend-list/friend-list.component.ts
index 525a9fbc3..7bf9d2c6b 100644
--- a/client/src/app/+admin/friends/friend-list/friend-list.component.ts
+++ b/client/src/app/+admin/friends/friend-list/friend-list.component.ts
@@ -1,11 +1,11 @@
1import { Component } from '@angular/core'; 1import { Component } from '@angular/core'
2 2
3import { NotificationsService } from 'angular2-notifications'; 3import { NotificationsService } from 'angular2-notifications'
4import { ServerDataSource } from 'ng2-smart-table'; 4import { ServerDataSource } from 'ng2-smart-table'
5 5
6import { ConfirmService } from '../../../core'; 6import { ConfirmService } from '../../../core'
7import { Utils } from '../../../shared'; 7import { Utils } from '../../../shared'
8import { Friend, FriendService } from '../shared'; 8import { Friend, FriendService } from '../shared'
9 9
10@Component({ 10@Component({
11 selector: 'my-friend-list', 11 selector: 'my-friend-list',
@@ -13,7 +13,7 @@ import { Friend, FriendService } from '../shared';
13 styleUrls: [ './friend-list.component.scss' ] 13 styleUrls: [ './friend-list.component.scss' ]
14}) 14})
15export class FriendListComponent { 15export class FriendListComponent {
16 friendsSource = null; 16 friendsSource = null
17 tableSettings = { 17 tableSettings = {
18 attr: { 18 attr: {
19 class: 'table-hover' 19 class: 'table-hover'
@@ -49,36 +49,36 @@ export class FriendListComponent {
49 valuePrepareFunction: Utils.dateToHuman 49 valuePrepareFunction: Utils.dateToHuman
50 } 50 }
51 } 51 }
52 }; 52 }
53 53
54 constructor( 54 constructor (
55 private notificationsService: NotificationsService, 55 private notificationsService: NotificationsService,
56 private confirmService: ConfirmService, 56 private confirmService: ConfirmService,
57 private friendService: FriendService 57 private friendService: FriendService
58 ) { 58 ) {
59 this.friendsSource = this.friendService.getDataSource(); 59 this.friendsSource = this.friendService.getDataSource()
60 } 60 }
61 61
62 hasFriends() { 62 hasFriends () {
63 return this.friendsSource.count() !== 0; 63 return this.friendsSource.count() !== 0
64 } 64 }
65 65
66 quitFriends() { 66 quitFriends () {
67 const confirmMessage = 'Do you really want to quit your friends? All their videos will be deleted.'; 67 const confirmMessage = 'Do you really want to quit your friends? All their videos will be deleted.'
68 this.confirmService.confirm(confirmMessage, 'Quit friends').subscribe( 68 this.confirmService.confirm(confirmMessage, 'Quit friends').subscribe(
69 res => { 69 res => {
70 if (res === false) return; 70 if (res === false) return
71 71
72 this.friendService.quitFriends().subscribe( 72 this.friendService.quitFriends().subscribe(
73 status => { 73 status => {
74 this.notificationsService.success('Sucess', 'Friends left!'); 74 this.notificationsService.success('Sucess', 'Friends left!')
75 75
76 this.friendsSource.refresh(); 76 this.friendsSource.refresh()
77 }, 77 },
78 78
79 err => this.notificationsService.error('Error', err.text) 79 err => this.notificationsService.error('Error', err.text)
80 ); 80 )
81 } 81 }
82 ); 82 )
83 } 83 }
84} 84}
diff --git a/client/src/app/+admin/friends/friend-list/index.ts b/client/src/app/+admin/friends/friend-list/index.ts
index 354c978a4..c9cbd2800 100644
--- a/client/src/app/+admin/friends/friend-list/index.ts
+++ b/client/src/app/+admin/friends/friend-list/index.ts
@@ -1 +1 @@
export * from './friend-list.component'; export * from './friend-list.component'
diff --git a/client/src/app/+admin/friends/friends.component.ts b/client/src/app/+admin/friends/friends.component.ts
index 5ca29111c..5ef0aaa03 100644
--- a/client/src/app/+admin/friends/friends.component.ts
+++ b/client/src/app/+admin/friends/friends.component.ts
@@ -1,8 +1,7 @@
1import { Component } from '@angular/core'; 1import { Component } from '@angular/core'
2 2
3@Component({ 3@Component({
4 template: '<router-outlet></router-outlet>' 4 template: '<router-outlet></router-outlet>'
5}) 5})
6
7export class FriendsComponent { 6export class FriendsComponent {
8} 7}
diff --git a/client/src/app/+admin/friends/friends.routes.ts b/client/src/app/+admin/friends/friends.routes.ts
index 747066d1f..615b6f4f7 100644
--- a/client/src/app/+admin/friends/friends.routes.ts
+++ b/client/src/app/+admin/friends/friends.routes.ts
@@ -1,37 +1,37 @@
1import { Routes } from '@angular/router'; 1import { Routes } from '@angular/router'
2 2
3import { FriendsComponent } from './friends.component'; 3import { FriendsComponent } from './friends.component'
4import { FriendAddComponent } from './friend-add'; 4import { FriendAddComponent } from './friend-add'
5import { FriendListComponent } from './friend-list'; 5import { FriendListComponent } from './friend-list'
6 6
7export const FriendsRoutes: Routes = [ 7export const FriendsRoutes: Routes = [
8 { 8 {
9 path: 'friends', 9 path: 'friends',
10 component: FriendsComponent, 10 component: FriendsComponent,
11 children: [ 11 children: [
12 { 12 {
13 path: '', 13 path: '',
14 redirectTo: 'list', 14 redirectTo: 'list',
15 pathMatch: 'full' 15 pathMatch: 'full'
16 }, 16 },
17 { 17 {
18 path: 'list', 18 path: 'list',
19 component: FriendListComponent, 19 component: FriendListComponent,
20 data: { 20 data: {
21 meta: { 21 meta: {
22 title: 'Friends list' 22 title: 'Friends list'
23 }
24 } 23 }
25 }, 24 }
26 { 25 },
27 path: 'add', 26 {
28 component: FriendAddComponent, 27 path: 'add',
29 data: { 28 component: FriendAddComponent,
30 meta: { 29 data: {
31 title: 'Add friends' 30 meta: {
32 } 31 title: 'Add friends'
33 } 32 }
34 } 33 }
35 ] 34 }
36 } 35 ]
37]; 36 }
37]
diff --git a/client/src/app/+admin/friends/index.ts b/client/src/app/+admin/friends/index.ts
index dd4df2538..356dee8e9 100644
--- a/client/src/app/+admin/friends/index.ts
+++ b/client/src/app/+admin/friends/index.ts
@@ -1,5 +1,5 @@
1export * from './friend-add'; 1export * from './friend-add'
2export * from './friend-list'; 2export * from './friend-list'
3export * from './shared'; 3export * from './shared'
4export * from './friends.component'; 4export * from './friends.component'
5export * from './friends.routes'; 5export * from './friends.routes'
diff --git a/client/src/app/+admin/friends/shared/friend.model.ts b/client/src/app/+admin/friends/shared/friend.model.ts
index f72156d05..6950405b9 100644
--- a/client/src/app/+admin/friends/shared/friend.model.ts
+++ b/client/src/app/+admin/friends/shared/friend.model.ts
@@ -1,7 +1,7 @@
1export interface Friend { 1export interface Friend {
2 id: string; 2 id: string
3 host: string; 3 host: string
4 score: number; 4 score: number
5 email: string; 5 email: string
6 createdAt: Date; 6 createdAt: Date
7} 7}
diff --git a/client/src/app/+admin/friends/shared/friend.service.ts b/client/src/app/+admin/friends/shared/friend.service.ts
index 6e51c954f..f4ecd36ad 100644
--- a/client/src/app/+admin/friends/shared/friend.service.ts
+++ b/client/src/app/+admin/friends/shared/friend.service.ts
@@ -1,39 +1,39 @@
1import { Injectable } from '@angular/core'; 1import { Injectable } from '@angular/core'
2import { Observable } from 'rxjs/Observable'; 2import { Observable } from 'rxjs/Observable'
3import 'rxjs/add/operator/catch'; 3import 'rxjs/add/operator/catch'
4import 'rxjs/add/operator/map'; 4import 'rxjs/add/operator/map'
5 5
6import { ServerDataSource } from 'ng2-smart-table'; 6import { ServerDataSource } from 'ng2-smart-table'
7 7
8import { Friend } from './friend.model'; 8import { Friend } from './friend.model'
9import { AuthHttp, RestExtractor, RestDataSource, ResultList } from '../../../shared'; 9import { AuthHttp, RestExtractor, RestDataSource, ResultList } from '../../../shared'
10 10
11@Injectable() 11@Injectable()
12export class FriendService { 12export class FriendService {
13 private static BASE_FRIEND_URL = API_URL + '/api/v1/pods/'; 13 private static BASE_FRIEND_URL = API_URL + '/api/v1/pods/'
14 14
15 constructor ( 15 constructor (
16 private authHttp: AuthHttp, 16 private authHttp: AuthHttp,
17 private restExtractor: RestExtractor 17 private restExtractor: RestExtractor
18 ) {} 18 ) {}
19 19
20 getDataSource() { 20 getDataSource () {
21 return new RestDataSource(this.authHttp, FriendService.BASE_FRIEND_URL); 21 return new RestDataSource(this.authHttp, FriendService.BASE_FRIEND_URL)
22 } 22 }
23 23
24 makeFriends(notEmptyHosts) { 24 makeFriends (notEmptyHosts) {
25 const body = { 25 const body = {
26 hosts: notEmptyHosts 26 hosts: notEmptyHosts
27 }; 27 }
28 28
29 return this.authHttp.post(FriendService.BASE_FRIEND_URL + 'makefriends', body) 29 return this.authHttp.post(FriendService.BASE_FRIEND_URL + 'makefriends', body)
30 .map(this.restExtractor.extractDataBool) 30 .map(this.restExtractor.extractDataBool)
31 .catch((res) => this.restExtractor.handleError(res)); 31 .catch((res) => this.restExtractor.handleError(res))
32 } 32 }
33 33
34 quitFriends() { 34 quitFriends () {
35 return this.authHttp.get(FriendService.BASE_FRIEND_URL + 'quitfriends') 35 return this.authHttp.get(FriendService.BASE_FRIEND_URL + 'quitfriends')
36 .map(res => res.status) 36 .map(res => res.status)
37 .catch((res) => this.restExtractor.handleError(res)); 37 .catch((res) => this.restExtractor.handleError(res))
38 } 38 }
39} 39}
diff --git a/client/src/app/+admin/friends/shared/index.ts b/client/src/app/+admin/friends/shared/index.ts
index 0d671637d..41aa6edd6 100644
--- a/client/src/app/+admin/friends/shared/index.ts
+++ b/client/src/app/+admin/friends/shared/index.ts
@@ -1,2 +1,2 @@
1export * from './friend.model'; 1export * from './friend.model'
2export * from './friend.service'; 2export * from './friend.service'
diff --git a/client/src/app/+admin/index.ts b/client/src/app/+admin/index.ts
index 2f47a3cc3..11e2587c0 100644
--- a/client/src/app/+admin/index.ts
+++ b/client/src/app/+admin/index.ts
@@ -1,6 +1,6 @@
1export * from './friends'; 1export * from './friends'
2export * from './requests'; 2export * from './requests'
3export * from './users'; 3export * from './users'
4export * from './admin-routing.module'; 4export * from './admin-routing.module'
5export * from './admin.module'; 5export * from './admin.module'
6export * from './admin.component'; 6export * from './admin.component'
diff --git a/client/src/app/+admin/requests/index.ts b/client/src/app/+admin/requests/index.ts
index 236a9ee8f..d96a893c3 100644
--- a/client/src/app/+admin/requests/index.ts
+++ b/client/src/app/+admin/requests/index.ts
@@ -1,4 +1,4 @@
1export * from './request-stats'; 1export * from './request-stats'
2export * from './shared'; 2export * from './shared'
3export * from './requests.component'; 3export * from './requests.component'
4export * from './requests.routes'; 4export * from './requests.routes'
diff --git a/client/src/app/+admin/requests/request-stats/index.ts b/client/src/app/+admin/requests/request-stats/index.ts
index be3a66f77..740c401bb 100644
--- a/client/src/app/+admin/requests/request-stats/index.ts
+++ b/client/src/app/+admin/requests/request-stats/index.ts
@@ -1 +1 @@
export * from './request-stats.component'; export * from './request-stats.component'
diff --git a/client/src/app/+admin/requests/request-stats/request-stats.component.ts b/client/src/app/+admin/requests/request-stats/request-stats.component.ts
index 85dd7e492..cca4926cf 100644
--- a/client/src/app/+admin/requests/request-stats/request-stats.component.ts
+++ b/client/src/app/+admin/requests/request-stats/request-stats.component.ts
@@ -1,12 +1,12 @@
1import { Component, OnInit, OnDestroy } from '@angular/core'; 1import { Component, OnInit, OnDestroy } from '@angular/core'
2 2
3import { NotificationsService } from 'angular2-notifications'; 3import { NotificationsService } from 'angular2-notifications'
4 4
5import { RequestService, RequestStats } from '../shared'; 5import { RequestService, RequestStats } from '../shared'
6 6
7@Component({ 7@Component({
8 selector: 'my-request-stats', 8 selector: 'my-request-stats',
9 templateUrl: './request-stats.component.html', 9 templateUrl: './request-stats.component.html',
10 styleUrls: [ './request-stats.component.scss' ] 10 styleUrls: [ './request-stats.component.scss' ]
11}) 11})
12export class RequestStatsComponent implements OnInit, OnDestroy { 12export class RequestStatsComponent implements OnInit, OnDestroy {
@@ -14,70 +14,67 @@ export class RequestStatsComponent implements OnInit, OnDestroy {
14 requestScheduler: 'Basic request scheduler', 14 requestScheduler: 'Basic request scheduler',
15 requestVideoEventScheduler: 'Video events request scheduler', 15 requestVideoEventScheduler: 'Video events request scheduler',
16 requestVideoQaduScheduler: 'Quick and dirty video updates request scheduler' 16 requestVideoQaduScheduler: 'Quick and dirty video updates request scheduler'
17 }; 17 }
18 18
19 stats: { [ id: string ]: RequestStats } = { 19 stats: { [ id: string ]: RequestStats } = {
20 requestScheduler: null, 20 requestScheduler: null,
21 requestVideoEventScheduler: null, 21 requestVideoEventScheduler: null,
22 requestVideoQaduScheduler: null 22 requestVideoQaduScheduler: null
23 }; 23 }
24 24
25 private intervals: { [ id: string ]: number } = { 25 private intervals: { [ id: string ]: number } = {
26 requestScheduler: null, 26 requestScheduler: null,
27 requestVideoEventScheduler: null, 27 requestVideoEventScheduler: null,
28 requestVideoQaduScheduler: null 28 requestVideoQaduScheduler: null
29 }; 29 }
30 30
31 private timeouts: { [ id: string ]: number } = { 31 private timeouts: { [ id: string ]: number } = {
32 requestScheduler: null, 32 requestScheduler: null,
33 requestVideoEventScheduler: null, 33 requestVideoEventScheduler: null,
34 requestVideoQaduScheduler: null 34 requestVideoQaduScheduler: null
35 }; 35 }
36
37 36
38 constructor( 37 constructor (
39 private notificationsService: NotificationsService, 38 private notificationsService: NotificationsService,
40 private requestService: RequestService 39 private requestService: RequestService
41 ) { } 40 ) { }
42 41
43 ngOnInit() { 42 ngOnInit () {
44 this.getStats(); 43 this.getStats()
45 this.runIntervals(); 44 this.runIntervals()
46 } 45 }
47 46
48 ngOnDestroy() { 47 ngOnDestroy () {
49 Object.keys(this.stats).forEach(requestSchedulerName => { 48 Object.keys(this.stats).forEach(requestSchedulerName => {
50 if (this.intervals[requestSchedulerName] !== null) { 49 if (this.intervals[requestSchedulerName] !== null) {
51 window.clearInterval(this.intervals[requestSchedulerName]); 50 window.clearInterval(this.intervals[requestSchedulerName])
52 } 51 }
53 52
54 if (this.timeouts[requestSchedulerName] !== null) { 53 if (this.timeouts[requestSchedulerName] !== null) {
55 window.clearTimeout(this.timeouts[requestSchedulerName]); 54 window.clearTimeout(this.timeouts[requestSchedulerName])
56 } 55 }
57 }); 56 })
58 } 57 }
59 58
60 getStats() { 59 getStats () {
61 this.requestService.getStats().subscribe( 60 this.requestService.getStats().subscribe(
62 stats => this.stats = stats, 61 stats => this.stats = stats,
63 62
64 err => this.notificationsService.error('Error', err.text) 63 err => this.notificationsService.error('Error', err.text)
65 ); 64 )
66 } 65 }
67 66
68 private runIntervals() { 67 private runIntervals () {
69 Object.keys(this.intervals).forEach(requestSchedulerName => { 68 Object.keys(this.intervals).forEach(requestSchedulerName => {
70 this.intervals[requestSchedulerName] = window.setInterval(() => { 69 this.intervals[requestSchedulerName] = window.setInterval(() => {
71 const stats = this.stats[requestSchedulerName]; 70 const stats = this.stats[requestSchedulerName]
72 71
73 stats.remainingMilliSeconds -= 1000; 72 stats.remainingMilliSeconds -= 1000
74 73
75 if (stats.remainingMilliSeconds <= 0) { 74 if (stats.remainingMilliSeconds <= 0) {
76 this.timeouts[requestSchedulerName] = window.setTimeout(() => this.getStats(), stats.remainingMilliSeconds + 100); 75 this.timeouts[requestSchedulerName] = window.setTimeout(() => this.getStats(), stats.remainingMilliSeconds + 100)
77 } 76 }
78 }, 1000); 77 }, 1000)
79 }); 78 })
80 } 79 }
81
82
83} 80}
diff --git a/client/src/app/+admin/requests/requests.component.ts b/client/src/app/+admin/requests/requests.component.ts
index 471112b45..88a90fa4e 100644
--- a/client/src/app/+admin/requests/requests.component.ts
+++ b/client/src/app/+admin/requests/requests.component.ts
@@ -1,8 +1,7 @@
1import { Component } from '@angular/core'; 1import { Component } from '@angular/core'
2 2
3@Component({ 3@Component({
4 template: '<router-outlet></router-outlet>' 4 template: '<router-outlet></router-outlet>'
5}) 5})
6
7export class RequestsComponent { 6export class RequestsComponent {
8} 7}
diff --git a/client/src/app/+admin/requests/requests.routes.ts b/client/src/app/+admin/requests/requests.routes.ts
index 251dd0da8..84db3fea8 100644
--- a/client/src/app/+admin/requests/requests.routes.ts
+++ b/client/src/app/+admin/requests/requests.routes.ts
@@ -1,27 +1,27 @@
1import { Routes } from '@angular/router'; 1import { Routes } from '@angular/router'
2 2
3import { RequestsComponent } from './requests.component'; 3import { RequestsComponent } from './requests.component'
4import { RequestStatsComponent } from './request-stats'; 4import { RequestStatsComponent } from './request-stats'
5 5
6export const RequestsRoutes: Routes = [ 6export const RequestsRoutes: Routes = [
7 { 7 {
8 path: 'requests', 8 path: 'requests',
9 component: RequestsComponent, 9 component: RequestsComponent,
10 children: [ 10 children: [
11 { 11 {
12 path: '', 12 path: '',
13 redirectTo: 'stats', 13 redirectTo: 'stats',
14 pathMatch: 'full' 14 pathMatch: 'full'
15 }, 15 },
16 { 16 {
17 path: 'stats', 17 path: 'stats',
18 component: RequestStatsComponent, 18 component: RequestStatsComponent,
19 data: { 19 data: {
20 meta: { 20 meta: {
21 title: 'Request stats' 21 title: 'Request stats'
22 }
23 } 22 }
24 } 23 }
25 ] 24 }
26 } 25 ]
27]; 26 }
27]
diff --git a/client/src/app/+admin/requests/shared/index.ts b/client/src/app/+admin/requests/shared/index.ts
index 32ab5767b..2442e810a 100644
--- a/client/src/app/+admin/requests/shared/index.ts
+++ b/client/src/app/+admin/requests/shared/index.ts
@@ -1,2 +1,2 @@
1export * from './request-stats.model'; 1export * from './request-stats.model'
2export * from './request.service'; 2export * from './request.service'
diff --git a/client/src/app/+admin/requests/shared/request-stats.model.ts b/client/src/app/+admin/requests/shared/request-stats.model.ts
index f658c4682..31550b5c0 100644
--- a/client/src/app/+admin/requests/shared/request-stats.model.ts
+++ b/client/src/app/+admin/requests/shared/request-stats.model.ts
@@ -1,35 +1,35 @@
1export interface Request { 1export interface Request {
2 request: any; 2 request: any
3 to: any; 3 to: any
4} 4}
5 5
6export class RequestStats { 6export class RequestStats {
7 requestsLimitPods: number; 7 requestsLimitPods: number
8 requestsLimitPerPod: number; 8 requestsLimitPerPod: number
9 milliSecondsInterval: number; 9 milliSecondsInterval: number
10 remainingMilliSeconds: number; 10 remainingMilliSeconds: number
11 totalRequests: number; 11 totalRequests: number
12 12
13 constructor(hash: { 13 constructor (hash: {
14 requestsLimitPods: number, 14 requestsLimitPods: number,
15 requestsLimitPerPod: number, 15 requestsLimitPerPod: number,
16 milliSecondsInterval: number, 16 milliSecondsInterval: number,
17 remainingMilliSeconds: number, 17 remainingMilliSeconds: number,
18 totalRequests: number; 18 totalRequests: number
19 }) { 19 }) {
20 this.requestsLimitPods = hash.requestsLimitPods; 20 this.requestsLimitPods = hash.requestsLimitPods
21 this.requestsLimitPerPod = hash.requestsLimitPerPod; 21 this.requestsLimitPerPod = hash.requestsLimitPerPod
22 this.milliSecondsInterval = hash.milliSecondsInterval; 22 this.milliSecondsInterval = hash.milliSecondsInterval
23 this.remainingMilliSeconds = hash.remainingMilliSeconds; 23 this.remainingMilliSeconds = hash.remainingMilliSeconds
24 this.totalRequests = hash.totalRequests; 24 this.totalRequests = hash.totalRequests
25 } 25 }
26 26
27 get remainingSeconds() { 27 get remainingSeconds () {
28 return Math.floor(this.remainingMilliSeconds / 1000); 28 return Math.floor(this.remainingMilliSeconds / 1000)
29 } 29 }
30 30
31 get secondsInterval() { 31 get secondsInterva () {
32 return Math.floor(this.milliSecondsInterval / 1000); 32 return Math.floor(this.milliSecondsInterval / 1000)
33 } 33 }
34 34
35} 35}
diff --git a/client/src/app/+admin/requests/shared/request.service.ts b/client/src/app/+admin/requests/shared/request.service.ts
index 0872ba0b8..faa6b9383 100644
--- a/client/src/app/+admin/requests/shared/request.service.ts
+++ b/client/src/app/+admin/requests/shared/request.service.ts
@@ -1,34 +1,34 @@
1import { Injectable } from '@angular/core'; 1import { Injectable } from '@angular/core'
2import { Observable } from 'rxjs/Observable'; 2import { Observable } from 'rxjs/Observable'
3import 'rxjs/add/operator/catch'; 3import 'rxjs/add/operator/catch'
4import 'rxjs/add/operator/map'; 4import 'rxjs/add/operator/map'
5 5
6import { RequestStats } from './request-stats.model'; 6import { RequestStats } from './request-stats.model'
7import { AuthHttp, RestExtractor } from '../../../shared'; 7import { AuthHttp, RestExtractor } from '../../../shared'
8 8
9@Injectable() 9@Injectable()
10export class RequestService { 10export class RequestService {
11 private static BASE_REQUEST_URL = API_URL + '/api/v1/requests/'; 11 private static BASE_REQUEST_URL = API_URL + '/api/v1/requests/'
12 12
13 constructor ( 13 constructor (
14 private authHttp: AuthHttp, 14 private authHttp: AuthHttp,
15 private restExtractor: RestExtractor 15 private restExtractor: RestExtractor
16 ) {} 16 ) {}
17 17
18 getStats(): Observable<{ [ id: string ]: RequestStats }> { 18 getStats (): Observable<{ [ id: string ]: RequestStats }> {
19 return this.authHttp.get(RequestService.BASE_REQUEST_URL + 'stats') 19 return this.authHttp.get(RequestService.BASE_REQUEST_URL + 'stats')
20 .map(this.restExtractor.extractDataGet) 20 .map(this.restExtractor.extractDataGet)
21 .map(this.buildRequestObjects) 21 .map(this.buildRequestObjects)
22 .catch((res) => this.restExtractor.handleError(res)); 22 .catch((res) => this.restExtractor.handleError(res))
23 } 23 }
24 24
25 private buildRequestObjects(data: any) { 25 private buildRequestObjects (data: any) {
26 const requestSchedulers = {}; 26 const requestSchedulers = {}
27 27
28 Object.keys(data).forEach(requestSchedulerName => { 28 Object.keys(data).forEach(requestSchedulerName => {
29 requestSchedulers[requestSchedulerName] = new RequestStats(data[requestSchedulerName]); 29 requestSchedulers[requestSchedulerName] = new RequestStats(data[requestSchedulerName])
30 }); 30 })
31 31
32 return requestSchedulers; 32 return requestSchedulers
33 } 33 }
34} 34}
diff --git a/client/src/app/+admin/users/index.ts b/client/src/app/+admin/users/index.ts
index e98a81f62..cef2c282c 100644
--- a/client/src/app/+admin/users/index.ts
+++ b/client/src/app/+admin/users/index.ts
@@ -1,5 +1,5 @@
1export * from './shared'; 1export * from './shared'
2export * from './user-add'; 2export * from './user-add'
3export * from './user-list'; 3export * from './user-list'
4export * from './users.component'; 4export * from './users.component'
5export * from './users.routes'; 5export * from './users.routes'
diff --git a/client/src/app/+admin/users/shared/index.ts b/client/src/app/+admin/users/shared/index.ts
index e17ee5c7a..1f1302dc5 100644
--- a/client/src/app/+admin/users/shared/index.ts
+++ b/client/src/app/+admin/users/shared/index.ts
@@ -1 +1 @@
export * from './user.service'; export * from './user.service'
diff --git a/client/src/app/+admin/users/shared/user.service.ts b/client/src/app/+admin/users/shared/user.service.ts
index 9c7176d39..d52993a29 100644
--- a/client/src/app/+admin/users/shared/user.service.ts
+++ b/client/src/app/+admin/users/shared/user.service.ts
@@ -1,35 +1,35 @@
1import { Injectable } from '@angular/core'; 1import { Injectable } from '@angular/core'
2import 'rxjs/add/operator/catch'; 2import 'rxjs/add/operator/catch'
3import 'rxjs/add/operator/map'; 3import 'rxjs/add/operator/map'
4 4
5import { AuthHttp, RestExtractor, RestDataSource, User } from '../../../shared'; 5import { AuthHttp, RestExtractor, RestDataSource, User } from '../../../shared'
6 6
7@Injectable() 7@Injectable()
8export class UserService { 8export class UserService {
9 private static BASE_USERS_URL = API_URL + '/api/v1/users/'; 9 private static BASE_USERS_URL = API_URL + '/api/v1/users/'
10 10
11 constructor( 11 constructor (
12 private authHttp: AuthHttp, 12 private authHttp: AuthHttp,
13 private restExtractor: RestExtractor 13 private restExtractor: RestExtractor
14 ) {} 14 ) {}
15 15
16 addUser(username: string, password: string, email: string) { 16 addUser (username: string, password: string, email: string) {
17 const body = { 17 const body = {
18 username, 18 username,
19 email, 19 email,
20 password 20 password
21 }; 21 }
22 22
23 return this.authHttp.post(UserService.BASE_USERS_URL, body) 23 return this.authHttp.post(UserService.BASE_USERS_URL, body)
24 .map(this.restExtractor.extractDataBool) 24 .map(this.restExtractor.extractDataBool)
25 .catch(this.restExtractor.handleError); 25 .catch(this.restExtractor.handleError)
26 } 26 }
27 27
28 getDataSource() { 28 getDataSource () {
29 return new RestDataSource(this.authHttp, UserService.BASE_USERS_URL); 29 return new RestDataSource(this.authHttp, UserService.BASE_USERS_URL)
30 } 30 }
31 31
32 removeUser(user: User) { 32 removeUser (user: User) {
33 return this.authHttp.delete(UserService.BASE_USERS_URL + user.id); 33 return this.authHttp.delete(UserService.BASE_USERS_URL + user.id)
34 } 34 }
35} 35}
diff --git a/client/src/app/+admin/users/user-add/index.ts b/client/src/app/+admin/users/user-add/index.ts
index 66d5ca04f..3a4654101 100644
--- a/client/src/app/+admin/users/user-add/index.ts
+++ b/client/src/app/+admin/users/user-add/index.ts
@@ -1 +1 @@
export * from './user-add.component'; export * from './user-add.component'
diff --git a/client/src/app/+admin/users/user-add/user-add.component.ts b/client/src/app/+admin/users/user-add/user-add.component.ts
index f1d2fde80..2d25ab469 100644
--- a/client/src/app/+admin/users/user-add/user-add.component.ts
+++ b/client/src/app/+admin/users/user-add/user-add.component.ts
@@ -1,71 +1,71 @@
1import { Component, OnInit } from '@angular/core'; 1import { Component, OnInit } from '@angular/core'
2import { FormBuilder, FormGroup } from '@angular/forms'; 2import { FormBuilder, FormGroup } from '@angular/forms'
3import { Router } from '@angular/router'; 3import { Router } from '@angular/router'
4 4
5import { NotificationsService } from 'angular2-notifications'; 5import { NotificationsService } from 'angular2-notifications'
6 6
7import { UserService } from '../shared'; 7import { UserService } from '../shared'
8import { 8import {
9 FormReactive, 9 FormReactive,
10 USER_USERNAME, 10 USER_USERNAME,
11 USER_EMAIL, 11 USER_EMAIL,
12 USER_PASSWORD 12 USER_PASSWORD
13} from '../../../shared'; 13} from '../../../shared'
14 14
15@Component({ 15@Component({
16 selector: 'my-user-add', 16 selector: 'my-user-add',
17 templateUrl: './user-add.component.html' 17 templateUrl: './user-add.component.html'
18}) 18})
19export class UserAddComponent extends FormReactive implements OnInit { 19export class UserAddComponent extends FormReactive implements OnInit {
20 error: string = null; 20 error: string = null
21 21
22 form: FormGroup; 22 form: FormGroup
23 formErrors = { 23 formErrors = {
24 'username': '', 24 'username': '',
25 'email': '', 25 'email': '',
26 'password': '' 26 'password': ''
27 }; 27 }
28 validationMessages = { 28 validationMessages = {
29 'username': USER_USERNAME.MESSAGES, 29 'username': USER_USERNAME.MESSAGES,
30 'email': USER_EMAIL.MESSAGES, 30 'email': USER_EMAIL.MESSAGES,
31 'password': USER_PASSWORD.MESSAGES, 31 'password': USER_PASSWORD.MESSAGES
32 }; 32 }
33 33
34 constructor( 34 constructor (
35 private formBuilder: FormBuilder, 35 private formBuilder: FormBuilder,
36 private router: Router, 36 private router: Router,
37 private notificationsService: NotificationsService, 37 private notificationsService: NotificationsService,
38 private userService: UserService 38 private userService: UserService
39 ) { 39 ) {
40 super(); 40 super()
41 } 41 }
42 42
43 buildForm() { 43 buildForm () {
44 this.form = this.formBuilder.group({ 44 this.form = this.formBuilder.group({
45 username: [ '', USER_USERNAME.VALIDATORS ], 45 username: [ '', USER_USERNAME.VALIDATORS ],
46 email: [ '', USER_EMAIL.VALIDATORS ], 46 email: [ '', USER_EMAIL.VALIDATORS ],
47 password: [ '', USER_PASSWORD.VALIDATORS ], 47 password: [ '', USER_PASSWORD.VALIDATORS ]
48 }); 48 })
49 49
50 this.form.valueChanges.subscribe(data => this.onValueChanged(data)); 50 this.form.valueChanges.subscribe(data => this.onValueChanged(data))
51 } 51 }
52 52
53 ngOnInit() { 53 ngOnInit () {
54 this.buildForm(); 54 this.buildForm()
55 } 55 }
56 56
57 addUser() { 57 addUser () {
58 this.error = null; 58 this.error = null
59 59
60 const { username, password, email } = this.form.value; 60 const { username, password, email } = this.form.value
61 61
62 this.userService.addUser(username, password, email).subscribe( 62 this.userService.addUser(username, password, email).subscribe(
63 () => { 63 () => {
64 this.notificationsService.success('Success', `User ${username} created.`); 64 this.notificationsService.success('Success', `User ${username} created.`)
65 this.router.navigate([ '/admin/users/list' ]); 65 this.router.navigate([ '/admin/users/list' ])
66 }, 66 },
67 67
68 err => this.error = err.text 68 err => this.error = err.text
69 ); 69 )
70 } 70 }
71} 71}
diff --git a/client/src/app/+admin/users/user-list/index.ts b/client/src/app/+admin/users/user-list/index.ts
index 51fbefa80..1826a4abe 100644
--- a/client/src/app/+admin/users/user-list/index.ts
+++ b/client/src/app/+admin/users/user-list/index.ts
@@ -1 +1 @@
export * from './user-list.component'; export * from './user-list.component'
diff --git a/client/src/app/+admin/users/user-list/user-list.component.ts b/client/src/app/+admin/users/user-list/user-list.component.ts
index 69ae4353d..b6fb0ed99 100644
--- a/client/src/app/+admin/users/user-list/user-list.component.ts
+++ b/client/src/app/+admin/users/user-list/user-list.component.ts
@@ -1,10 +1,10 @@
1import { Component } from '@angular/core'; 1import { Component } from '@angular/core'
2 2
3import { NotificationsService } from 'angular2-notifications'; 3import { NotificationsService } from 'angular2-notifications'
4 4
5import { ConfirmService } from '../../../core'; 5import { ConfirmService } from '../../../core'
6import { User, Utils } from '../../../shared'; 6import { User, Utils } from '../../../shared'
7import { UserService } from '../shared'; 7import { UserService } from '../shared'
8 8
9@Component({ 9@Component({
10 selector: 'my-user-list', 10 selector: 'my-user-list',
@@ -12,7 +12,7 @@ import { UserService } from '../shared';
12 styleUrls: [ './user-list.component.scss' ] 12 styleUrls: [ './user-list.component.scss' ]
13}) 13})
14export class UserListComponent { 14export class UserListComponent {
15 usersSource = null; 15 usersSource = null
16 tableSettings = { 16 tableSettings = {
17 mode: 'external', 17 mode: 'external',
18 attr: { 18 attr: {
@@ -52,37 +52,37 @@ export class UserListComponent {
52 valuePrepareFunction: Utils.dateToHuman 52 valuePrepareFunction: Utils.dateToHuman
53 } 53 }
54 } 54 }
55 }; 55 }
56 56
57 constructor( 57 constructor (
58 private notificationsService: NotificationsService, 58 private notificationsService: NotificationsService,
59 private confirmService: ConfirmService, 59 private confirmService: ConfirmService,
60 private userService: UserService 60 private userService: UserService
61 ) { 61 ) {
62 this.usersSource = this.userService.getDataSource(); 62 this.usersSource = this.userService.getDataSource()
63 } 63 }
64 64
65 removeUser({ data }) { 65 removeUser ({ data }) {
66 const user: User = data; 66 const user: User = data
67 67
68 if (user.username === 'root') { 68 if (user.username === 'root') {
69 this.notificationsService.error('Error', 'You cannot delete root.'); 69 this.notificationsService.error('Error', 'You cannot delete root.')
70 return; 70 return
71 } 71 }
72 72
73 this.confirmService.confirm('Do you really want to delete this user?', 'Delete').subscribe( 73 this.confirmService.confirm('Do you really want to delete this user?', 'Delete').subscribe(
74 res => { 74 res => {
75 if (res === false) return; 75 if (res === false) return
76 76
77 this.userService.removeUser(user).subscribe( 77 this.userService.removeUser(user).subscribe(
78 () => { 78 () => {
79 this.notificationsService.success('Success', `User ${user.username} deleted.`); 79 this.notificationsService.success('Success', `User ${user.username} deleted.`)
80 this.usersSource.refresh(); 80 this.usersSource.refresh()
81 }, 81 },
82 82
83 err => this.notificationsService.error('Error', err.text) 83 err => this.notificationsService.error('Error', err.text)
84 ); 84 )
85 } 85 }
86 ); 86 )
87 } 87 }
88} 88}
diff --git a/client/src/app/+admin/users/users.component.ts b/client/src/app/+admin/users/users.component.ts
index 91af6c320..e9c8f6b0d 100644
--- a/client/src/app/+admin/users/users.component.ts
+++ b/client/src/app/+admin/users/users.component.ts
@@ -1,8 +1,7 @@
1import { Component } from '@angular/core'; 1import { Component } from '@angular/core'
2 2
3@Component({ 3@Component({
4 template: '<router-outlet></router-outlet>' 4 template: '<router-outlet></router-outlet>'
5}) 5})
6
7export class UsersComponent { 6export class UsersComponent {
8} 7}
diff --git a/client/src/app/+admin/users/users.routes.ts b/client/src/app/+admin/users/users.routes.ts
index 21fb192e0..723c5715d 100644
--- a/client/src/app/+admin/users/users.routes.ts
+++ b/client/src/app/+admin/users/users.routes.ts
@@ -1,8 +1,8 @@
1import { Routes } from '@angular/router'; 1import { Routes } from '@angular/router'
2 2
3import { UsersComponent } from './users.component'; 3import { UsersComponent } from './users.component'
4import { UserAddComponent } from './user-add'; 4import { UserAddComponent } from './user-add'
5import { UserListComponent } from './user-list'; 5import { UserListComponent } from './user-list'
6 6
7export const UsersRoutes: Routes = [ 7export const UsersRoutes: Routes = [
8 { 8 {
@@ -34,4 +34,4 @@ export const UsersRoutes: Routes = [
34 } 34 }
35 ] 35 ]
36 } 36 }
37]; 37]
diff --git a/client/src/app/+admin/video-abuses/index.ts b/client/src/app/+admin/video-abuses/index.ts
index 7f5e65f91..395fac2e7 100644
--- a/client/src/app/+admin/video-abuses/index.ts
+++ b/client/src/app/+admin/video-abuses/index.ts
@@ -1,3 +1,3 @@
1export * from './video-abuse-list'; 1export * from './video-abuse-list'
2export * from './video-abuses.component'; 2export * from './video-abuses.component'
3export * from './video-abuses.routes'; 3export * from './video-abuses.routes'
diff --git a/client/src/app/+admin/video-abuses/video-abuse-list/index.ts b/client/src/app/+admin/video-abuses/video-abuse-list/index.ts
index 3f2ed1714..01c24d860 100644
--- a/client/src/app/+admin/video-abuses/video-abuse-list/index.ts
+++ b/client/src/app/+admin/video-abuses/video-abuse-list/index.ts
@@ -1 +1 @@
export * from './video-abuse-list.component'; export * from './video-abuse-list.component'
diff --git a/client/src/app/+admin/video-abuses/video-abuse-list/video-abuse-list.component.ts b/client/src/app/+admin/video-abuses/video-abuse-list/video-abuse-list.component.ts
index 55d82f790..60eaebb44 100644
--- a/client/src/app/+admin/video-abuses/video-abuse-list/video-abuse-list.component.ts
+++ b/client/src/app/+admin/video-abuses/video-abuse-list/video-abuse-list.component.ts
@@ -1,15 +1,15 @@
1import { Component } from '@angular/core'; 1import { Component } from '@angular/core'
2 2
3import { NotificationsService } from 'angular2-notifications'; 3import { NotificationsService } from 'angular2-notifications'
4 4
5import { Utils, VideoAbuseService, VideoAbuse } from '../../../shared'; 5import { Utils, VideoAbuseService, VideoAbuse } from '../../../shared'
6 6
7@Component({ 7@Component({
8 selector: 'my-video-abuse-list', 8 selector: 'my-video-abuse-list',
9 templateUrl: './video-abuse-list.component.html' 9 templateUrl: './video-abuse-list.component.html'
10}) 10})
11export class VideoAbuseListComponent { 11export class VideoAbuseListComponent {
12 videoAbusesSource = null; 12 videoAbusesSource = null
13 tableSettings = { 13 tableSettings = {
14 mode: 'external', 14 mode: 'external',
15 attr: { 15 attr: {
@@ -54,18 +54,18 @@ export class VideoAbuseListComponent {
54 valuePrepareFunction: Utils.dateToHuman 54 valuePrepareFunction: Utils.dateToHuman
55 } 55 }
56 } 56 }
57 }; 57 }
58 58
59 constructor( 59 constructor (
60 private notificationsService: NotificationsService, 60 private notificationsService: NotificationsService,
61 private videoAbuseService: VideoAbuseService 61 private videoAbuseService: VideoAbuseService
62 ) { 62 ) {
63 this.videoAbusesSource = this.videoAbuseService.getDataSource(); 63 this.videoAbusesSource = this.videoAbuseService.getDataSource()
64 } 64 }
65 65
66 buildVideoLink(videoId: string) { 66 buildVideoLink (videoId: string) {
67 // TODO: transform to routerLink 67 // TODO: transform to routerLink
68 // https://github.com/akveo/ng2-smart-table/issues/57 68 // https://github.com/akveo/ng2-smart-table/issues/57
69 return `<a href="/videos/${videoId}" title="Go to the video">${videoId}</a>`; 69 return `<a href="/videos/${videoId}" title="Go to the video">${videoId}</a>`
70 } 70 }
71} 71}
diff --git a/client/src/app/+admin/video-abuses/video-abuses.component.ts b/client/src/app/+admin/video-abuses/video-abuses.component.ts
index 001f27e87..9dae5c0b6 100644
--- a/client/src/app/+admin/video-abuses/video-abuses.component.ts
+++ b/client/src/app/+admin/video-abuses/video-abuses.component.ts
@@ -1,4 +1,4 @@
1import { Component } from '@angular/core'; 1import { Component } from '@angular/core'
2 2
3@Component({ 3@Component({
4 template: '<router-outlet></router-outlet>' 4 template: '<router-outlet></router-outlet>'
diff --git a/client/src/app/+admin/video-abuses/video-abuses.routes.ts b/client/src/app/+admin/video-abuses/video-abuses.routes.ts
index 1f19d7885..a8c1561cd 100644
--- a/client/src/app/+admin/video-abuses/video-abuses.routes.ts
+++ b/client/src/app/+admin/video-abuses/video-abuses.routes.ts
@@ -1,7 +1,7 @@
1import { Routes } from '@angular/router'; 1import { Routes } from '@angular/router'
2 2
3import { VideoAbusesComponent } from './video-abuses.component'; 3import { VideoAbusesComponent } from './video-abuses.component'
4import { VideoAbuseListComponent } from './video-abuse-list'; 4import { VideoAbuseListComponent } from './video-abuse-list'
5 5
6export const VideoAbusesRoutes: Routes = [ 6export const VideoAbusesRoutes: Routes = [
7 { 7 {
@@ -25,4 +25,4 @@ export const VideoAbusesRoutes: Routes = [
25 } 25 }
26 ] 26 ]
27 } 27 }
28]; 28]
diff --git a/client/src/app/account/account-change-password/account-change-password.component.ts b/client/src/app/account/account-change-password/account-change-password.component.ts
index 15dc42d22..ce786cfa3 100644
--- a/client/src/app/account/account-change-password/account-change-password.component.ts
+++ b/client/src/app/account/account-change-password/account-change-password.component.ts
@@ -1,10 +1,10 @@
1import { Component, OnInit } from '@angular/core'; 1import { Component, OnInit } from '@angular/core'
2import { FormBuilder, FormGroup } from '@angular/forms'; 2import { FormBuilder, FormGroup } from '@angular/forms'
3import { Router } from '@angular/router'; 3import { Router } from '@angular/router'
4 4
5import { NotificationsService } from 'angular2-notifications'; 5import { NotificationsService } from 'angular2-notifications'
6 6
7import { FormReactive, UserService, USER_PASSWORD } from '../../shared'; 7import { FormReactive, UserService, USER_PASSWORD } from '../../shared'
8 8
9@Component({ 9@Component({
10 selector: 'my-account-change-password', 10 selector: 'my-account-change-password',
@@ -12,55 +12,55 @@ import { FormReactive, UserService, USER_PASSWORD } from '../../shared';
12}) 12})
13 13
14export class AccountChangePasswordComponent extends FormReactive implements OnInit { 14export class AccountChangePasswordComponent extends FormReactive implements OnInit {
15 error: string = null; 15 error: string = null
16 16
17 form: FormGroup; 17 form: FormGroup
18 formErrors = { 18 formErrors = {
19 'new-password': '', 19 'new-password': '',
20 'new-confirmed-password': '' 20 'new-confirmed-password': ''
21 }; 21 }
22 validationMessages = { 22 validationMessages = {
23 'new-password': USER_PASSWORD.MESSAGES, 23 'new-password': USER_PASSWORD.MESSAGES,
24 'new-confirmed-password': USER_PASSWORD.MESSAGES 24 'new-confirmed-password': USER_PASSWORD.MESSAGES
25 }; 25 }
26 26
27 constructor( 27 constructor (
28 private formBuilder: FormBuilder, 28 private formBuilder: FormBuilder,
29 private router: Router, 29 private router: Router,
30 private notificationsService: NotificationsService, 30 private notificationsService: NotificationsService,
31 private userService: UserService 31 private userService: UserService
32 ) { 32 ) {
33 super(); 33 super()
34 } 34 }
35 35
36 buildForm() { 36 buildForm () {
37 this.form = this.formBuilder.group({ 37 this.form = this.formBuilder.group({
38 'new-password': [ '', USER_PASSWORD.VALIDATORS ], 38 'new-password': [ '', USER_PASSWORD.VALIDATORS ],
39 'new-confirmed-password': [ '', USER_PASSWORD.VALIDATORS ], 39 'new-confirmed-password': [ '', USER_PASSWORD.VALIDATORS ]
40 }); 40 })
41 41
42 this.form.valueChanges.subscribe(data => this.onValueChanged(data)); 42 this.form.valueChanges.subscribe(data => this.onValueChanged(data))
43 } 43 }
44 44
45 ngOnInit() { 45 ngOnInit () {
46 this.buildForm(); 46 this.buildForm()
47 } 47 }
48 48
49 changePassword() { 49 changePassword () {
50 const newPassword = this.form.value['new-password']; 50 const newPassword = this.form.value['new-password']
51 const newConfirmedPassword = this.form.value['new-confirmed-password']; 51 const newConfirmedPassword = this.form.value['new-confirmed-password']
52 52
53 this.error = null; 53 this.error = null
54 54
55 if (newPassword !== newConfirmedPassword) { 55 if (newPassword !== newConfirmedPassword) {
56 this.error = 'The new password and the confirmed password do not correspond.'; 56 this.error = 'The new password and the confirmed password do not correspond.'
57 return; 57 return
58 } 58 }
59 59
60 this.userService.changePassword(newPassword).subscribe( 60 this.userService.changePassword(newPassword).subscribe(
61 () => this.notificationsService.success('Success', 'Password updated.'), 61 () => this.notificationsService.success('Success', 'Password updated.'),
62 62
63 err => this.error = err 63 err => this.error = err
64 ); 64 )
65 } 65 }
66} 66}
diff --git a/client/src/app/account/account-change-password/index.ts b/client/src/app/account/account-change-password/index.ts
index 72a63e48d..44c330b66 100644
--- a/client/src/app/account/account-change-password/index.ts
+++ b/client/src/app/account/account-change-password/index.ts
@@ -1 +1 @@
export * from './account-change-password.component'; export * from './account-change-password.component'
diff --git a/client/src/app/account/account-details/account-details.component.ts b/client/src/app/account/account-details/account-details.component.ts
index 30e5b14ee..d7531cb55 100644
--- a/client/src/app/account/account-details/account-details.component.ts
+++ b/client/src/app/account/account-details/account-details.component.ts
@@ -1,16 +1,16 @@
1import { Component, OnInit, Input } from '@angular/core'; 1import { Component, OnInit, Input } from '@angular/core'
2import { FormBuilder, FormGroup } from '@angular/forms'; 2import { FormBuilder, FormGroup } from '@angular/forms'
3import { Router } from '@angular/router'; 3import { Router } from '@angular/router'
4 4
5import { NotificationsService } from 'angular2-notifications'; 5import { NotificationsService } from 'angular2-notifications'
6 6
7import { AuthService } from '../../core'; 7import { AuthService } from '../../core'
8import { 8import {
9 FormReactive, 9 FormReactive,
10 User, 10 User,
11 UserService, 11 UserService,
12 USER_PASSWORD 12 USER_PASSWORD
13} from '../../shared'; 13} from '../../shared'
14 14
15@Component({ 15@Component({
16 selector: 'my-account-details', 16 selector: 'my-account-details',
@@ -18,51 +18,51 @@ import {
18}) 18})
19 19
20export class AccountDetailsComponent extends FormReactive implements OnInit { 20export class AccountDetailsComponent extends FormReactive implements OnInit {
21 @Input() user: User = null; 21 @Input() user: User = null
22 22
23 error: string = null; 23 error: string = null
24 24
25 form: FormGroup; 25 form: FormGroup
26 formErrors = {}; 26 formErrors = {}
27 validationMessages = {}; 27 validationMessages = {}
28 28
29 constructor( 29 constructor (
30 private authService: AuthService, 30 private authService: AuthService,
31 private formBuilder: FormBuilder, 31 private formBuilder: FormBuilder,
32 private router: Router, 32 private router: Router,
33 private notificationsService: NotificationsService, 33 private notificationsService: NotificationsService,
34 private userService: UserService 34 private userService: UserService
35 ) { 35 ) {
36 super(); 36 super()
37 } 37 }
38 38
39 buildForm() { 39 buildForm () {
40 this.form = this.formBuilder.group({ 40 this.form = this.formBuilder.group({
41 displayNSFW: [ this.user.displayNSFW ], 41 displayNSFW: [ this.user.displayNSFW ]
42 }); 42 })
43 43
44 this.form.valueChanges.subscribe(data => this.onValueChanged(data)); 44 this.form.valueChanges.subscribe(data => this.onValueChanged(data))
45 } 45 }
46 46
47 ngOnInit() { 47 ngOnInit () {
48 this.buildForm(); 48 this.buildForm()
49 } 49 }
50 50
51 updateDetails() { 51 updateDetails () {
52 const displayNSFW = this.form.value['displayNSFW']; 52 const displayNSFW = this.form.value['displayNSFW']
53 const details = { 53 const details = {
54 displayNSFW 54 displayNSFW
55 }; 55 }
56 56
57 this.error = null; 57 this.error = null
58 this.userService.updateDetails(details).subscribe( 58 this.userService.updateDetails(details).subscribe(
59 () => { 59 () => {
60 this.notificationsService.success('Success', 'Informations updated.'); 60 this.notificationsService.success('Success', 'Informations updated.')
61 61
62 this.authService.refreshUserInformations(); 62 this.authService.refreshUserInformations()
63 }, 63 },
64 64
65 err => this.error = err 65 err => this.error = err
66 ); 66 )
67 } 67 }
68} 68}
diff --git a/client/src/app/account/account-details/index.ts b/client/src/app/account/account-details/index.ts
index 28f644738..4829f608a 100644
--- a/client/src/app/account/account-details/index.ts
+++ b/client/src/app/account/account-details/index.ts
@@ -1 +1 @@
export * from './account-details.component'; export * from './account-details.component'
diff --git a/client/src/app/account/account-routing.module.ts b/client/src/app/account/account-routing.module.ts
index 9004605f3..e9b8f7031 100644
--- a/client/src/app/account/account-routing.module.ts
+++ b/client/src/app/account/account-routing.module.ts
@@ -1,7 +1,7 @@
1import { NgModule } from '@angular/core'; 1import { NgModule } from '@angular/core'
2import { RouterModule, Routes } from '@angular/router'; 2import { RouterModule, Routes } from '@angular/router'
3 3
4import { AccountComponent } from './account.component'; 4import { AccountComponent } from './account.component'
5 5
6const accountRoutes: Routes = [ 6const accountRoutes: Routes = [
7 { 7 {
@@ -13,7 +13,7 @@ const accountRoutes: Routes = [
13 } 13 }
14 } 14 }
15 } 15 }
16]; 16]
17 17
18@NgModule({ 18@NgModule({
19 imports: [ RouterModule.forChild(accountRoutes) ], 19 imports: [ RouterModule.forChild(accountRoutes) ],
diff --git a/client/src/app/account/account.component.ts b/client/src/app/account/account.component.ts
index 57b3d4ccd..929934f67 100644
--- a/client/src/app/account/account.component.ts
+++ b/client/src/app/account/account.component.ts
@@ -1,16 +1,16 @@
1import { Component, OnInit } from '@angular/core'; 1import { Component, OnInit } from '@angular/core'
2import { FormBuilder, FormGroup } from '@angular/forms'; 2import { FormBuilder, FormGroup } from '@angular/forms'
3import { Router } from '@angular/router'; 3import { Router } from '@angular/router'
4 4
5import { NotificationsService } from 'angular2-notifications'; 5import { NotificationsService } from 'angular2-notifications'
6 6
7import { AuthService } from '../core'; 7import { AuthService } from '../core'
8import { 8import {
9 FormReactive, 9 FormReactive,
10 User, 10 User,
11 UserService, 11 UserService,
12 USER_PASSWORD 12 USER_PASSWORD
13} from '../shared'; 13} from '../shared'
14 14
15@Component({ 15@Component({
16 selector: 'my-account', 16 selector: 'my-account',
@@ -18,11 +18,11 @@ import {
18 styleUrls: [ './account.component.scss' ] 18 styleUrls: [ './account.component.scss' ]
19}) 19})
20export class AccountComponent implements OnInit { 20export class AccountComponent implements OnInit {
21 user: User = null; 21 user: User = null
22 22
23 constructor(private authService: AuthService) {} 23 constructor (private authService: AuthService) {}
24 24
25 ngOnInit() { 25 ngOnInit () {
26 this.user = this.authService.getUser(); 26 this.user = this.authService.getUser()
27 } 27 }
28} 28}
diff --git a/client/src/app/account/account.module.ts b/client/src/app/account/account.module.ts
index f6c141ae6..380e9d235 100644
--- a/client/src/app/account/account.module.ts
+++ b/client/src/app/account/account.module.ts
@@ -1,11 +1,11 @@
1import { NgModule } from '@angular/core'; 1import { NgModule } from '@angular/core'
2 2
3import { AccountRoutingModule } from './account-routing.module'; 3import { AccountRoutingModule } from './account-routing.module'
4import { AccountComponent } from './account.component'; 4import { AccountComponent } from './account.component'
5import { AccountChangePasswordComponent } from './account-change-password'; 5import { AccountChangePasswordComponent } from './account-change-password'
6import { AccountDetailsComponent } from './account-details'; 6import { AccountDetailsComponent } from './account-details'
7import { AccountService } from './account.service'; 7import { AccountService } from './account.service'
8import { SharedModule } from '../shared'; 8import { SharedModule } from '../shared'
9 9
10@NgModule({ 10@NgModule({
11 imports: [ 11 imports: [
diff --git a/client/src/app/account/index.ts b/client/src/app/account/index.ts
index 9265fa10a..dc56ffdbd 100644
--- a/client/src/app/account/index.ts
+++ b/client/src/app/account/index.ts
@@ -1,3 +1,3 @@
1export * from './account-routing.module'; 1export * from './account-routing.module'
2export * from './account.component'; 2export * from './account.component'
3export * from './account.module'; 3export * from './account.module'
diff --git a/client/src/app/app-routing.module.ts b/client/src/app/app-routing.module.ts
index f9465dc9c..8036c52e6 100644
--- a/client/src/app/app-routing.module.ts
+++ b/client/src/app/app-routing.module.ts
@@ -1,5 +1,5 @@
1import { NgModule } from '@angular/core'; 1import { NgModule } from '@angular/core'
2import { Routes, RouterModule } from '@angular/router'; 2import { Routes, RouterModule } from '@angular/router'
3 3
4const routes: Routes = [ 4const routes: Routes = [
5 { 5 {
@@ -11,11 +11,10 @@ const routes: Routes = [
11 path: 'admin', 11 path: 'admin',
12 loadChildren: './+admin#AdminModule' 12 loadChildren: './+admin#AdminModule'
13 } 13 }
14]; 14]
15 15
16@NgModule({ 16@NgModule({
17 imports: [ RouterModule.forRoot(routes) ], 17 imports: [ RouterModule.forRoot(routes) ],
18 exports: [ RouterModule ] 18 exports: [ RouterModule ]
19}) 19})
20export class AppRoutingModule {} 20export class AppRoutingModule {}
21
diff --git a/client/src/app/app.component.ts b/client/src/app/app.component.ts
index b8b732169..a90654e26 100644
--- a/client/src/app/app.component.ts
+++ b/client/src/app/app.component.ts
@@ -1,9 +1,9 @@
1import { Component, OnInit, ViewContainerRef } from '@angular/core'; 1import { Component, OnInit, ViewContainerRef } from '@angular/core'
2import { Router } from '@angular/router'; 2import { Router } from '@angular/router'
3 3
4import { AuthService, ConfigService } from './core'; 4import { AuthService, ConfigService } from './core'
5import { VideoService } from './videos'; 5import { VideoService } from './videos'
6import { UserService } from './shared'; 6import { UserService } from './shared'
7 7
8@Component({ 8@Component({
9 selector: 'my-app', 9 selector: 'my-app',
@@ -22,11 +22,11 @@ export class AppComponent implements OnInit {
22 preventDuplicates: false, 22 preventDuplicates: false,
23 preventLastDuplicates: 'visible', 23 preventLastDuplicates: 'visible',
24 rtl: false 24 rtl: false
25 }; 25 }
26 26
27 isMenuDisplayed = true; 27 isMenuDisplayed = true
28 28
29 constructor( 29 constructor (
30 private router: Router, 30 private router: Router,
31 private authService: AuthService, 31 private authService: AuthService,
32 private configService: ConfigService, 32 private configService: ConfigService,
@@ -35,46 +35,46 @@ export class AppComponent implements OnInit {
35 viewContainerRef: ViewContainerRef 35 viewContainerRef: ViewContainerRef
36 ) {} 36 ) {}
37 37
38 ngOnInit() { 38 ngOnInit () {
39 if (this.authService.isLoggedIn()) { 39 if (this.authService.isLoggedIn()) {
40 // The service will automatically redirect to the login page if the token is not valid anymore 40 // The service will automatically redirect to the login page if the token is not valid anymore
41 this.userService.checkTokenValidity(); 41 this.userService.checkTokenValidity()
42 } 42 }
43 43
44 this.configService.loadConfig(); 44 this.configService.loadConfig()
45 this.videoService.loadVideoCategories(); 45 this.videoService.loadVideoCategories()
46 this.videoService.loadVideoLicences(); 46 this.videoService.loadVideoLicences()
47 this.videoService.loadVideoLanguages(); 47 this.videoService.loadVideoLanguages()
48 48
49 // Do not display menu on small screens 49 // Do not display menu on small screens
50 if (window.innerWidth < 600) { 50 if (window.innerWidth < 600) {
51 this.isMenuDisplayed = false; 51 this.isMenuDisplayed = false
52 } 52 }
53 } 53 }
54 54
55 isInAdmin() { 55 isInAdmin () {
56 return this.router.url.indexOf('/admin/') !== -1; 56 return this.router.url.indexOf('/admin/') !== -1
57 } 57 }
58 58
59 toggleMenu() { 59 toggleMenu () {
60 this.isMenuDisplayed = !this.isMenuDisplayed; 60 this.isMenuDisplayed = !this.isMenuDisplayed
61 } 61 }
62 62
63 getMainColClasses() { 63 getMainColClasses () {
64 const colSizes = { 64 const colSizes = {
65 md: 10, 65 md: 10,
66 sm: 9, 66 sm: 9,
67 xs: 9 67 xs: 9
68 }; 68 }
69 69
70 // Take all width is the menu is not displayed 70 // Take all width is the menu is not displayed
71 if (this.isMenuDisplayed === false) { 71 if (this.isMenuDisplayed === false) {
72 Object.keys(colSizes).forEach(col => colSizes[col] = 12); 72 Object.keys(colSizes).forEach(col => colSizes[col] = 12)
73 } 73 }
74 74
75 const classes = [ 'main-col' ]; 75 const classes = [ 'main-col' ]
76 Object.keys(colSizes).forEach(col => classes.push(`col-${col}-${colSizes[col]}`)); 76 Object.keys(colSizes).forEach(col => classes.push(`col-${col}-${colSizes[col]}`))
77 77
78 return classes; 78 return classes
79 } 79 }
80} 80}
diff --git a/client/src/app/app.module.ts b/client/src/app/app.module.ts
index 8a072eaac..d7c9f6548 100644
--- a/client/src/app/app.module.ts
+++ b/client/src/app/app.module.ts
@@ -1,29 +1,29 @@
1import { ApplicationRef, NgModule } from '@angular/core'; 1import { ApplicationRef, NgModule } from '@angular/core'
2import { BrowserModule } from '@angular/platform-browser'; 2import { BrowserModule } from '@angular/platform-browser'
3import { 3import {
4 removeNgStyles, 4 removeNgStyles,
5 createNewHosts, 5 createNewHosts,
6 createInputTransfer 6 createInputTransfer
7} from '@angularclass/hmr'; 7} from '@angularclass/hmr'
8 8
9import { MetaModule, MetaLoader, MetaStaticLoader, PageTitlePositioning } from '@nglibs/meta'; 9import { MetaModule, MetaLoader, MetaStaticLoader, PageTitlePositioning } from '@nglibs/meta'
10// TODO: remove, we need this to avoid error in ng2-smart-table 10// TODO: remove, we need this to avoid error in ng2-smart-table
11import 'rxjs/add/operator/toPromise'; 11import 'rxjs/add/operator/toPromise'
12import 'bootstrap-loader'; 12import 'bootstrap-loader'
13 13
14import { ENV_PROVIDERS } from './environment'; 14import { ENV_PROVIDERS } from './environment'
15import { AppRoutingModule } from './app-routing.module'; 15import { AppRoutingModule } from './app-routing.module'
16import { AppComponent } from './app.component'; 16import { AppComponent } from './app.component'
17import { AppState, InternalStateType } from './app.service'; 17import { AppState, InternalStateType } from './app.service'
18 18
19import { AccountModule } from './account'; 19import { AccountModule } from './account'
20import { CoreModule } from './core'; 20import { CoreModule } from './core'
21import { LoginModule } from './login'; 21import { LoginModule } from './login'
22import { SignupModule } from './signup'; 22import { SignupModule } from './signup'
23import { SharedModule } from './shared'; 23import { SharedModule } from './shared'
24import { VideosModule } from './videos'; 24import { VideosModule } from './videos'
25 25
26export function metaFactory(): MetaLoader { 26export function metaFactory (): MetaLoader {
27 return new MetaStaticLoader({ 27 return new MetaStaticLoader({
28 pageTitlePositioning: PageTitlePositioning.PrependPageTitle, 28 pageTitlePositioning: PageTitlePositioning.PrependPageTitle,
29 pageTitleSeparator: ' - ', 29 pageTitleSeparator: ' - ',
@@ -32,19 +32,19 @@ export function metaFactory(): MetaLoader {
32 title: 'PeerTube', 32 title: 'PeerTube',
33 description: 'PeerTube, a decentralized video streaming platform using P2P (BitTorrent) directly in the web browser' 33 description: 'PeerTube, a decentralized video streaming platform using P2P (BitTorrent) directly in the web browser'
34 } 34 }
35 }); 35 })
36} 36}
37 37
38type StoreType = { 38type StoreType = {
39 state: InternalStateType, 39 state: InternalStateType,
40 restoreInputValues: () => void, 40 restoreInputValues: () => void,
41 disposeOldHosts: () => void 41 disposeOldHosts: () => void
42}; 42}
43 43
44// Application wide providers 44// Application wide providers
45const APP_PROVIDERS = [ 45const APP_PROVIDERS = [
46 AppState 46 AppState
47]; 47]
48 48
49@NgModule({ 49@NgModule({
50 bootstrap: [ AppComponent ], 50 bootstrap: [ AppComponent ],
@@ -77,59 +77,59 @@ const APP_PROVIDERS = [
77 ] 77 ]
78}) 78})
79export class AppModule { 79export class AppModule {
80 constructor( 80 constructor (
81 public appRef: ApplicationRef, 81 public appRef: ApplicationRef,
82 public appState: AppState 82 public appState: AppState
83 ) {} 83 ) {}
84 84
85 public hmrOnInit(store: StoreType) { 85 public hmrOnInit (store: StoreType) {
86 if (!store || !store.state) { 86 if (!store || !store.state) {
87 return; 87 return
88 } 88 }
89 console.log('HMR store', JSON.stringify(store, null, 2)); 89 console.log('HMR store', JSON.stringify(store, null, 2))
90 /** 90 /**
91 * Set state 91 * Set state
92 */ 92 */
93 this.appState._state = store.state; 93 this.appState._state = store.state
94 /** 94 /**
95 * Set input values 95 * Set input values
96 */ 96 */
97 if ('restoreInputValues' in store) { 97 if ('restoreInputValues' in store) {
98 let restoreInputValues = store.restoreInputValues; 98 let restoreInputValues = store.restoreInputValues
99 setTimeout(restoreInputValues); 99 setTimeout(restoreInputValues)
100 } 100 }
101 101
102 this.appRef.tick(); 102 this.appRef.tick()
103 delete store.state; 103 delete store.state
104 delete store.restoreInputValues; 104 delete store.restoreInputValues
105 } 105 }
106 106
107 public hmrOnDestroy(store: StoreType) { 107 public hmrOnDestroy (store: StoreType) {
108 const cmpLocation = this.appRef.components.map((cmp) => cmp.location.nativeElement); 108 const cmpLocation = this.appRef.components.map((cmp) => cmp.location.nativeElement)
109 /** 109 /**
110 * Save state 110 * Save state
111 */ 111 */
112 const state = this.appState._state; 112 const state = this.appState._state
113 store.state = state; 113 store.state = state
114 /** 114 /**
115 * Recreate root elements 115 * Recreate root elements
116 */ 116 */
117 store.disposeOldHosts = createNewHosts(cmpLocation); 117 store.disposeOldHosts = createNewHosts(cmpLocation)
118 /** 118 /**
119 * Save input values 119 * Save input values
120 */ 120 */
121 store.restoreInputValues = createInputTransfer(); 121 store.restoreInputValues = createInputTransfer()
122 /** 122 /**
123 * Remove styles 123 * Remove styles
124 */ 124 */
125 removeNgStyles(); 125 removeNgStyles()
126 } 126 }
127 127
128 public hmrAfterDestroy(store: StoreType) { 128 public hmrAfterDestroy (store: StoreType) {
129 /** 129 /**
130 * Display new elements 130 * Display new elements
131 */ 131 */
132 store.disposeOldHosts(); 132 store.disposeOldHosts ()
133 delete store.disposeOldHosts; 133 delete store.disposeOldHosts
134 } 134 }
135} 135}
diff --git a/client/src/app/app.resolver.ts b/client/src/app/app.resolver.ts
index 45774b8d1..bc054e90b 100644
--- a/client/src/app/app.resolver.ts
+++ b/client/src/app/app.resolver.ts
@@ -1,12 +1,14 @@
1import { Resolve, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router'; 1/* tslint:disable */
2import { Injectable } from '@angular/core'; 2
3import { Observable } from 'rxjs/Observable'; 3import { Resolve, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router'
4import 'rxjs/add/observable/of'; 4import { Injectable } from '@angular/core'
5import { Observable } from 'rxjs/Observable'
6import 'rxjs/add/observable/of'
5 7
6@Injectable() 8@Injectable()
7export class DataResolver implements Resolve<any> { 9export class DataResolver implements Resolve<any> {
8 public resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) { 10 public resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
9 return Observable.of({ res: 'I am data'}); 11 return Observable.of({ res: 'I am data'})
10 } 12 }
11} 13}
12 14
@@ -15,4 +17,4 @@ export class DataResolver implements Resolve<any> {
15 */ 17 */
16export const APP_RESOLVER_PROVIDERS = [ 18export const APP_RESOLVER_PROVIDERS = [
17 DataResolver 19 DataResolver
18]; 20]
diff --git a/client/src/app/app.service.ts b/client/src/app/app.service.ts
index a7eb880a4..abffc87f1 100644
--- a/client/src/app/app.service.ts
+++ b/client/src/app/app.service.ts
@@ -1,46 +1,48 @@
1import { Injectable } from '@angular/core'; 1/* tslint:disable */
2
3import { Injectable } from '@angular/core'
2 4
3export type InternalStateType = { 5export type InternalStateType = {
4 [key: string]: any 6 [key: string]: any
5}; 7}
6 8
7@Injectable() 9@Injectable()
8export class AppState { 10export class AppState {
9 11
10 public _state: InternalStateType = { }; 12 public _state: InternalStateType = { }
11 13
12 /** 14 /**
13 * Already return a clone of the current state. 15 * Already return a clone of the current state.
14 */ 16 */
15 public get state() { 17 public get state() {
16 return this._state = this._clone(this._state); 18 return this._state = this._clone(this._state)
17 } 19 }
18 /** 20 /**
19 * Never allow mutation 21 * Never allow mutation
20 */ 22 */
21 public set state(value) { 23 public set state(value) {
22 throw new Error('do not mutate the `.state` directly'); 24 throw new Error('do not mutate the `.state` directly')
23 } 25 }
24 26
25 public get(prop?: any) { 27 public get(prop?: any) {
26 /** 28 /**
27 * Use our state getter for the clone. 29 * Use our state getter for the clone.
28 */ 30 */
29 const state = this.state; 31 const state = this.state
30 return state.hasOwnProperty(prop) ? state[prop] : state; 32 return state.hasOwnProperty(prop) ? state[prop] : state
31 } 33 }
32 34
33 public set(prop: string, value: any) { 35 public set(prop: string, value: any) {
34 /** 36 /**
35 * Internally mutate our state. 37 * Internally mutate our state.
36 */ 38 */
37 return this._state[prop] = value; 39 return this._state[prop] = value
38 } 40 }
39 41
40 private _clone(object: InternalStateType) { 42 private _clone(object: InternalStateType) {
41 /** 43 /**
42 * Simple object clone. 44 * Simple object clone.
43 */ 45 */
44 return JSON.parse(JSON.stringify( object )); 46 return JSON.parse(JSON.stringify( object ))
45 } 47 }
46} 48}
diff --git a/client/src/app/core/auth/auth-user.model.ts b/client/src/app/core/auth/auth-user.model.ts
index 1a31a7834..65c37bcfa 100644
--- a/client/src/app/core/auth/auth-user.model.ts
+++ b/client/src/app/core/auth/auth-user.model.ts
@@ -1,6 +1,66 @@
1// Do not use the barrel (dependency loop) 1// Do not use the barrel (dependency loop)
2import { UserRole } from '../../../../../shared/models/user.model' 2import { UserRole } from '../../../../../shared/models/user.model'
3import { User } from '../../shared/users/user.model'; 3import { User } from '../../shared/users/user.model'
4
5export type TokenOptions = {
6 accessToken: string
7 refreshToken: string
8 tokenType: string
9}
10
11// Private class only used by User
12class Tokens {
13 private static KEYS = {
14 ACCESS_TOKEN: 'access_token',
15 REFRESH_TOKEN: 'refresh_token',
16 TOKEN_TYPE: 'token_type'
17 }
18
19 accessToken: string
20 refreshToken: string
21 tokenType: string
22
23 static load () {
24 const accessTokenLocalStorage = localStorage.getItem(this.KEYS.ACCESS_TOKEN)
25 const refreshTokenLocalStorage = localStorage.getItem(this.KEYS.REFRESH_TOKEN)
26 const tokenTypeLocalStorage = localStorage.getItem(this.KEYS.TOKEN_TYPE)
27
28 if (accessTokenLocalStorage && refreshTokenLocalStorage && tokenTypeLocalStorage) {
29 return new Tokens({
30 accessToken: accessTokenLocalStorage,
31 refreshToken: refreshTokenLocalStorage,
32 tokenType: tokenTypeLocalStorage
33 })
34 }
35
36 return null
37 }
38
39 static flush () {
40 localStorage.removeItem(this.KEYS.ACCESS_TOKEN)
41 localStorage.removeItem(this.KEYS.REFRESH_TOKEN)
42 localStorage.removeItem(this.KEYS.TOKEN_TYPE)
43 }
44
45 constructor (hash?: TokenOptions) {
46 if (hash) {
47 this.accessToken = hash.accessToken
48 this.refreshToken = hash.refreshToken
49
50 if (hash.tokenType === 'bearer') {
51 this.tokenType = 'Bearer'
52 } else {
53 this.tokenType = hash.tokenType
54 }
55 }
56 }
57
58 save () {
59 localStorage.setItem(Tokens.KEYS.ACCESS_TOKEN, this.accessToken)
60 localStorage.setItem(Tokens.KEYS.REFRESH_TOKEN, this.refreshToken)
61 localStorage.setItem(Tokens.KEYS.TOKEN_TYPE, this.tokenType)
62 }
63}
4 64
5export class AuthUser extends User { 65export class AuthUser extends User {
6 private static KEYS = { 66 private static KEYS = {
@@ -9,123 +69,69 @@ export class AuthUser extends User {
9 EMAIL: 'email', 69 EMAIL: 'email',
10 USERNAME: 'username', 70 USERNAME: 'username',
11 DISPLAY_NSFW: 'display_nsfw' 71 DISPLAY_NSFW: 'display_nsfw'
12 }; 72 }
13 73
14 tokens: Tokens; 74 tokens: Tokens
15 75
16 static load() { 76 static load () {
17 const usernameLocalStorage = localStorage.getItem(this.KEYS.USERNAME); 77 const usernameLocalStorage = localStorage.getItem(this.KEYS.USERNAME)
18 if (usernameLocalStorage) { 78 if (usernameLocalStorage) {
19 return new AuthUser( 79 return new AuthUser(
20 { 80 {
21 id: parseInt(localStorage.getItem(this.KEYS.ID)), 81 id: parseInt(localStorage.getItem(this.KEYS.ID), 10),
22 username: localStorage.getItem(this.KEYS.USERNAME), 82 username: localStorage.getItem(this.KEYS.USERNAME),
23 email: localStorage.getItem(this.KEYS.EMAIL), 83 email: localStorage.getItem(this.KEYS.EMAIL),
24 role: localStorage.getItem(this.KEYS.ROLE) as UserRole, 84 role: localStorage.getItem(this.KEYS.ROLE) as UserRole,
25 displayNSFW: localStorage.getItem(this.KEYS.DISPLAY_NSFW) === 'true' 85 displayNSFW: localStorage.getItem(this.KEYS.DISPLAY_NSFW) === 'true'
26 }, 86 },
27 Tokens.load() 87 Tokens.load()
28 ); 88 )
29 } 89 }
30 90
31 return null; 91 return null
32 } 92 }
33 93
34 static flush() { 94 static flush () {
35 localStorage.removeItem(this.KEYS.USERNAME); 95 localStorage.removeItem(this.KEYS.USERNAME)
36 localStorage.removeItem(this.KEYS.ID); 96 localStorage.removeItem(this.KEYS.ID)
37 localStorage.removeItem(this.KEYS.ROLE); 97 localStorage.removeItem(this.KEYS.ROLE)
38 localStorage.removeItem(this.KEYS.DISPLAY_NSFW); 98 localStorage.removeItem(this.KEYS.DISPLAY_NSFW)
39 Tokens.flush(); 99 Tokens.flush()
40 } 100 }
41 101
42 constructor(userHash: { 102 constructor (userHash: {
43 id: number, 103 id: number,
44 username: string, 104 username: string,
45 role: UserRole, 105 role: UserRole,
46 email: string, 106 email: string,
47 displayNSFW: boolean 107 displayNSFW: boolean
48 }, hashTokens: any) { 108 }, hashTokens: TokenOptions) {
49 super(userHash); 109 super(userHash)
50 this.tokens = new Tokens(hashTokens); 110 this.tokens = new Tokens(hashTokens)
51 }
52
53 getAccessToken() {
54 return this.tokens.access_token;
55 }
56
57 getRefreshToken() {
58 return this.tokens.refresh_token;
59 }
60
61 getTokenType() {
62 return this.tokens.token_type;
63 }
64
65 refreshTokens(access_token: string, refresh_token: string) {
66 this.tokens.access_token = access_token;
67 this.tokens.refresh_token = refresh_token;
68 } 111 }
69 112
70 save() { 113 getAccessToken () {
71 localStorage.setItem(AuthUser.KEYS.ID, this.id.toString()); 114 return this.tokens.accessToken
72 localStorage.setItem(AuthUser.KEYS.USERNAME, this.username);
73 localStorage.setItem(AuthUser.KEYS.ROLE, this.role);
74 localStorage.setItem(AuthUser.KEYS.DISPLAY_NSFW, JSON.stringify(this.displayNSFW));
75 this.tokens.save();
76 } 115 }
77}
78
79// Private class only used by User
80class Tokens {
81 private static KEYS = {
82 ACCESS_TOKEN: 'access_token',
83 REFRESH_TOKEN: 'refresh_token',
84 TOKEN_TYPE: 'token_type',
85 };
86
87 access_token: string;
88 refresh_token: string;
89 token_type: string;
90
91 static load() {
92 const accessTokenLocalStorage = localStorage.getItem(this.KEYS.ACCESS_TOKEN);
93 const refreshTokenLocalStorage = localStorage.getItem(this.KEYS.REFRESH_TOKEN);
94 const tokenTypeLocalStorage = localStorage.getItem(this.KEYS.TOKEN_TYPE);
95
96 if (accessTokenLocalStorage && refreshTokenLocalStorage && tokenTypeLocalStorage) {
97 return new Tokens({
98 access_token: accessTokenLocalStorage,
99 refresh_token: refreshTokenLocalStorage,
100 token_type: tokenTypeLocalStorage
101 });
102 }
103 116
104 return null; 117 getRefreshToken () {
118 return this.tokens.refreshToken
105 } 119 }
106 120
107 static flush() { 121 getTokenType () {
108 localStorage.removeItem(this.KEYS.ACCESS_TOKEN); 122 return this.tokens.tokenType
109 localStorage.removeItem(this.KEYS.REFRESH_TOKEN);
110 localStorage.removeItem(this.KEYS.TOKEN_TYPE);
111 } 123 }
112 124
113 constructor(hash?: any) { 125 refreshTokens (accessToken: string, refreshToken: string) {
114 if (hash) { 126 this.tokens.accessToken = accessToken
115 this.access_token = hash.access_token; 127 this.tokens.refreshToken = refreshToken
116 this.refresh_token = hash.refresh_token;
117
118 if (hash.token_type === 'bearer') {
119 this.token_type = 'Bearer';
120 } else {
121 this.token_type = hash.token_type;
122 }
123 }
124 } 128 }
125 129
126 save() { 130 save () {
127 localStorage.setItem('access_token', this.access_token); 131 localStorage.setItem(AuthUser.KEYS.ID, this.id.toString())
128 localStorage.setItem('refresh_token', this.refresh_token); 132 localStorage.setItem(AuthUser.KEYS.USERNAME, this.username)
129 localStorage.setItem('token_type', this.token_type); 133 localStorage.setItem(AuthUser.KEYS.ROLE, this.role)
134 localStorage.setItem(AuthUser.KEYS.DISPLAY_NSFW, JSON.stringify(this.displayNSFW))
135 this.tokens.save()
130 } 136 }
131} 137}
diff --git a/client/src/app/core/auth/auth.service.ts b/client/src/app/core/auth/auth.service.ts
index 4c75df1ca..32f7a5503 100644
--- a/client/src/app/core/auth/auth.service.ts
+++ b/client/src/app/core/auth/auth.service.ts
@@ -1,40 +1,40 @@
1import { Injectable } from '@angular/core'; 1import { Injectable } from '@angular/core'
2import { Headers, Http, Response, URLSearchParams } from '@angular/http'; 2import { Headers, Http, Response, URLSearchParams } from '@angular/http'
3import { Router } from '@angular/router'; 3import { Router } from '@angular/router'
4import { Observable } from 'rxjs/Observable'; 4import { Observable } from 'rxjs/Observable'
5import { Subject } from 'rxjs/Subject'; 5import { Subject } from 'rxjs/Subject'
6import 'rxjs/add/operator/map'; 6import 'rxjs/add/operator/map'
7import 'rxjs/add/operator/mergeMap'; 7import 'rxjs/add/operator/mergeMap'
8import 'rxjs/add/observable/throw'; 8import 'rxjs/add/observable/throw'
9 9
10import { NotificationsService } from 'angular2-notifications'; 10import { NotificationsService } from 'angular2-notifications'
11 11
12import { AuthStatus } from './auth-status.model'; 12import { AuthStatus } from './auth-status.model'
13import { AuthUser } from './auth-user.model'; 13import { AuthUser } from './auth-user.model'
14// Do not use the barrel (dependency loop) 14// Do not use the barrel (dependency loop)
15import { RestExtractor } from '../../shared/rest'; 15import { RestExtractor } from '../../shared/rest'
16 16
17@Injectable() 17@Injectable()
18export class AuthService { 18export class AuthService {
19 private static BASE_CLIENT_URL = API_URL + '/api/v1/clients/local'; 19 private static BASE_CLIENT_URL = API_URL + '/api/v1/clients/local'
20 private static BASE_TOKEN_URL = API_URL + '/api/v1/users/token'; 20 private static BASE_TOKEN_URL = API_URL + '/api/v1/users/token'
21 private static BASE_USER_INFORMATIONS_URL = API_URL + '/api/v1/users/me'; 21 private static BASE_USER_INFORMATIONS_URL = API_URL + '/api/v1/users/me'
22 22
23 loginChangedSource: Observable<AuthStatus>; 23 loginChangedSource: Observable<AuthStatus>
24 24
25 private clientId: string; 25 private clientId: string
26 private clientSecret: string; 26 private clientSecret: string
27 private loginChanged: Subject<AuthStatus>; 27 private loginChanged: Subject<AuthStatus>
28 private user: AuthUser = null; 28 private user: AuthUser = null
29 29
30 constructor( 30 constructor (
31 private http: Http, 31 private http: Http,
32 private notificationsService: NotificationsService, 32 private notificationsService: NotificationsService,
33 private restExtractor: RestExtractor, 33 private restExtractor: RestExtractor,
34 private router: Router 34 private router: Router
35 ) { 35 ) {
36 this.loginChanged = new Subject<AuthStatus>(); 36 this.loginChanged = new Subject<AuthStatus>()
37 this.loginChangedSource = this.loginChanged.asObservable(); 37 this.loginChangedSource = this.loginChanged.asObservable()
38 38
39 // Fetch the client_id/client_secret 39 // Fetch the client_id/client_secret
40 // FIXME: save in local storage? 40 // FIXME: save in local storage?
@@ -43,120 +43,120 @@ export class AuthService {
43 .catch((res) => this.restExtractor.handleError(res)) 43 .catch((res) => this.restExtractor.handleError(res))
44 .subscribe( 44 .subscribe(
45 result => { 45 result => {
46 this.clientId = result.client_id; 46 this.clientId = result.client_id
47 this.clientSecret = result.client_secret; 47 this.clientSecret = result.client_secret
48 console.log('Client credentials loaded.'); 48 console.log('Client credentials loaded.')
49 }, 49 },
50 50
51 error => { 51 error => {
52 let errorMessage = `Cannot retrieve OAuth Client credentials: ${error.text}. \n`; 52 let errorMessage = `Cannot retrieve OAuth Client credentials: ${error.text}. \n`
53 errorMessage += 'Ensure you have correctly configured PeerTube (config/ directory), in particular the "webserver" section.'; 53 errorMessage += 'Ensure you have correctly configured PeerTube (config/ directory), in particular the "webserver" section.'
54 54
55 // We put a bigger timeout 55 // We put a bigger timeout
56 // This is an important message 56 // This is an important message
57 this.notificationsService.error('Error', errorMessage, { timeOut: 7000 }); 57 this.notificationsService.error('Error', errorMessage, { timeOut: 7000 })
58 } 58 }
59 ); 59 )
60 60
61 // Return null if there is nothing to load 61 // Return null if there is nothing to load
62 this.user = AuthUser.load(); 62 this.user = AuthUser.load()
63 } 63 }
64 64
65 getRefreshToken() { 65 getRefreshToken () {
66 if (this.user === null) return null; 66 if (this.user === null) return null
67 67
68 return this.user.getRefreshToken(); 68 return this.user.getRefreshToken()
69 } 69 }
70 70
71 getRequestHeaderValue() { 71 getRequestHeaderValue () {
72 return `${this.getTokenType()} ${this.getAccessToken()}`; 72 return `${this.getTokenType()} ${this.getAccessToken()}`
73 } 73 }
74 74
75 getAccessToken() { 75 getAccessToken () {
76 if (this.user === null) return null; 76 if (this.user === null) return null
77 77
78 return this.user.getAccessToken(); 78 return this.user.getAccessToken()
79 } 79 }
80 80
81 getTokenType() { 81 getTokenType () {
82 if (this.user === null) return null; 82 if (this.user === null) return null
83 83
84 return this.user.getTokenType(); 84 return this.user.getTokenType()
85 } 85 }
86 86
87 getUser(): AuthUser { 87 getUser () {
88 return this.user; 88 return this.user
89 } 89 }
90 90
91 isAdmin() { 91 isAdmin () {
92 if (this.user === null) return false; 92 if (this.user === null) return false
93 93
94 return this.user.isAdmin(); 94 return this.user.isAdmin()
95 } 95 }
96 96
97 isLoggedIn() { 97 isLoggedIn () {
98 if (this.getAccessToken()) { 98 if (this.getAccessToken()) {
99 return true; 99 return true
100 } else { 100 } else {
101 return false; 101 return false
102 } 102 }
103 } 103 }
104 104
105 login(username: string, password: string) { 105 login (username: string, password: string) {
106 let body = new URLSearchParams(); 106 let body = new URLSearchParams()
107 body.set('client_id', this.clientId); 107 body.set('client_id', this.clientId)
108 body.set('client_secret', this.clientSecret); 108 body.set('client_secret', this.clientSecret)
109 body.set('response_type', 'code'); 109 body.set('response_type', 'code')
110 body.set('grant_type', 'password'); 110 body.set('grant_type', 'password')
111 body.set('scope', 'upload'); 111 body.set('scope', 'upload')
112 body.set('username', username); 112 body.set('username', username)
113 body.set('password', password); 113 body.set('password', password)
114 114
115 let headers = new Headers(); 115 let headers = new Headers()
116 headers.append('Content-Type', 'application/x-www-form-urlencoded'); 116 headers.append('Content-Type', 'application/x-www-form-urlencoded')
117 117
118 let options = { 118 let options = {
119 headers: headers 119 headers: headers
120 }; 120 }
121 121
122 return this.http.post(AuthService.BASE_TOKEN_URL, body.toString(), options) 122 return this.http.post(AuthService.BASE_TOKEN_URL, body.toString(), options)
123 .map(this.restExtractor.extractDataGet) 123 .map(this.restExtractor.extractDataGet)
124 .map(res => { 124 .map(res => {
125 res.username = username; 125 res.username = username
126 return res; 126 return res
127 }) 127 })
128 .flatMap(res => this.mergeUserInformations(res)) 128 .flatMap(res => this.mergeUserInformations(res))
129 .map(res => this.handleLogin(res)) 129 .map(res => this.handleLogin(res))
130 .catch((res) => this.restExtractor.handleError(res)); 130 .catch((res) => this.restExtractor.handleError(res))
131 } 131 }
132 132
133 logout() { 133 logout () {
134 // TODO: make an HTTP request to revoke the tokens 134 // TODO: make an HTTP request to revoke the tokens
135 this.user = null; 135 this.user = null
136 136
137 AuthUser.flush(); 137 AuthUser.flush()
138 138
139 this.setStatus(AuthStatus.LoggedOut); 139 this.setStatus(AuthStatus.LoggedOut)
140 } 140 }
141 141
142 refreshAccessToken() { 142 refreshAccessToken () {
143 console.log('Refreshing token...'); 143 console.log('Refreshing token...')
144 144
145 const refreshToken = this.getRefreshToken(); 145 const refreshToken = this.getRefreshToken()
146 146
147 let body = new URLSearchParams(); 147 let body = new URLSearchParams()
148 body.set('refresh_token', refreshToken); 148 body.set('refresh_token', refreshToken)
149 body.set('client_id', this.clientId); 149 body.set('client_id', this.clientId)
150 body.set('client_secret', this.clientSecret); 150 body.set('client_secret', this.clientSecret)
151 body.set('response_type', 'code'); 151 body.set('response_type', 'code')
152 body.set('grant_type', 'refresh_token'); 152 body.set('grant_type', 'refresh_token')
153 153
154 let headers = new Headers(); 154 let headers = new Headers()
155 headers.append('Content-Type', 'application/x-www-form-urlencoded'); 155 headers.append('Content-Type', 'application/x-www-form-urlencoded')
156 156
157 let options = { 157 let options = {
158 headers: headers 158 headers: headers
159 }; 159 }
160 160
161 return this.http.post(AuthService.BASE_TOKEN_URL, body.toString(), options) 161 return this.http.post(AuthService.BASE_TOKEN_URL, body.toString(), options)
162 .map(this.restExtractor.extractDataGet) 162 .map(this.restExtractor.extractDataGet)
@@ -164,41 +164,41 @@ export class AuthService {
164 .catch((res: Response) => { 164 .catch((res: Response) => {
165 // The refresh token is invalid? 165 // The refresh token is invalid?
166 if (res.status === 400 && res.json() && res.json().error === 'invalid_grant') { 166 if (res.status === 400 && res.json() && res.json().error === 'invalid_grant') {
167 console.error('Cannot refresh token -> logout...'); 167 console.error('Cannot refresh token -> logout...')
168 this.logout(); 168 this.logout()
169 this.router.navigate(['/login']); 169 this.router.navigate(['/login'])
170 170
171 return Observable.throw({ 171 return Observable.throw({
172 json: () => '', 172 json: () => '',
173 text: () => 'You need to reconnect.' 173 text: () => 'You need to reconnect.'
174 }); 174 })
175 } 175 }
176 176
177 return this.restExtractor.handleError(res); 177 return this.restExtractor.handleError(res)
178 }); 178 })
179 } 179 }
180 180
181 refreshUserInformations() { 181 refreshUserInformations () {
182 const obj = { 182 const obj = {
183 access_token: this.user.getAccessToken() 183 access_token: this.user.getAccessToken()
184 }; 184 }
185 185
186 this.mergeUserInformations(obj) 186 this.mergeUserInformations (obj)
187 .subscribe( 187 .subscribe(
188 res => { 188 res => {
189 this.user.displayNSFW = res.displayNSFW; 189 this.user.displayNSFW = res.displayNSFW
190 this.user.role = res.role; 190 this.user.role = res.role
191 191
192 this.user.save(); 192 this.user.save()
193 } 193 }
194 ); 194 )
195 } 195 }
196 196
197 private mergeUserInformations(obj: { access_token: string }) { 197 private mergeUserInformations (obj: { access_token: string }) {
198 // Do not call authHttp here to avoid circular dependencies headaches 198 // Do not call authHttp here to avoid circular dependencies headaches
199 199
200 const headers = new Headers(); 200 const headers = new Headers()
201 headers.set('Authorization', `Bearer ${obj.access_token}`); 201 headers.set('Authorization', `Bearer ${obj.access_token}`)
202 202
203 return this.http.get(AuthService.BASE_USER_INFORMATIONS_URL, { headers }) 203 return this.http.get(AuthService.BASE_USER_INFORMATIONS_URL, { headers })
204 .map(res => res.json()) 204 .map(res => res.json())
@@ -207,38 +207,38 @@ export class AuthService {
207 id: res.id, 207 id: res.id,
208 role: res.role, 208 role: res.role,
209 displayNSFW: res.displayNSFW 209 displayNSFW: res.displayNSFW
210 }; 210 }
211 211
212 return Object.assign(obj, newProperties); 212 return Object.assign(obj, newProperties)
213 } 213 }
214 ); 214 )
215 } 215 }
216 216
217 private handleLogin (obj: any) { 217 private handleLogin (obj: any) {
218 const id = obj.id; 218 const id = obj.id
219 const username = obj.username; 219 const username = obj.username
220 const role = obj.role; 220 const role = obj.role
221 const email = obj.email; 221 const email = obj.email
222 const displayNSFW = obj.displayNSFW; 222 const displayNSFW = obj.displayNSFW
223 const hashTokens = { 223 const hashTokens = {
224 access_token: obj.access_token, 224 accessToken: obj.access_token,
225 token_type: obj.token_type, 225 tokenType: obj.token_type,
226 refresh_token: obj.refresh_token 226 refreshToken: obj.refresh_token
227 }; 227 }
228 228
229 this.user = new AuthUser({ id, username, role, displayNSFW, email }, hashTokens); 229 this.user = new AuthUser({ id, username, role, displayNSFW, email }, hashTokens)
230 this.user.save(); 230 this.user.save()
231 231
232 this.setStatus(AuthStatus.LoggedIn); 232 this.setStatus(AuthStatus.LoggedIn)
233 } 233 }
234 234
235 private handleRefreshToken (obj: any) { 235 private handleRefreshToken (obj: any) {
236 this.user.refreshTokens(obj.access_token, obj.refresh_token); 236 this.user.refreshTokens(obj.access_token, obj.refresh_token)
237 this.user.save(); 237 this.user.save()
238 } 238 }
239 239
240 private setStatus(status: AuthStatus) { 240 private setStatus (status: AuthStatus) {
241 this.loginChanged.next(status); 241 this.loginChanged.next(status)
242 } 242 }
243 243
244} 244}
diff --git a/client/src/app/core/auth/index.ts b/client/src/app/core/auth/index.ts
index 67a18cfbb..8e5caa7ed 100644
--- a/client/src/app/core/auth/index.ts
+++ b/client/src/app/core/auth/index.ts
@@ -1,3 +1,3 @@
1export * from './auth-status.model'; 1export * from './auth-status.model'
2export * from './auth-user.model'; 2export * from './auth-user.model'
3export * from './auth.service' 3export * from './auth.service'
diff --git a/client/src/app/core/config/config.service.ts b/client/src/app/core/config/config.service.ts
index 407dca083..a83ec61d2 100644
--- a/client/src/app/core/config/config.service.ts
+++ b/client/src/app/core/config/config.service.ts
@@ -1,11 +1,11 @@
1import { Injectable } from '@angular/core'; 1import { Injectable } from '@angular/core'
2import { Http } from '@angular/http'; 2import { Http } from '@angular/http'
3 3
4import { RestExtractor } from '../../shared/rest'; 4import { RestExtractor } from '../../shared/rest'
5 5
6@Injectable() 6@Injectable()
7export class ConfigService { 7export class ConfigService {
8 private static BASE_CONFIG_URL = API_URL + '/api/v1/config/'; 8 private static BASE_CONFIG_URL = API_URL + '/api/v1/config/'
9 9
10 private config: { 10 private config: {
11 signup: { 11 signup: {
@@ -15,22 +15,22 @@ export class ConfigService {
15 signup: { 15 signup: {
16 enabled: false 16 enabled: false
17 } 17 }
18 }; 18 }
19 19
20 constructor( 20 constructor (
21 private http: Http, 21 private http: Http,
22 private restExtractor: RestExtractor, 22 private restExtractor: RestExtractor
23 ) {} 23 ) {}
24 24
25 loadConfig() { 25 loadConfig () {
26 this.http.get(ConfigService.BASE_CONFIG_URL) 26 this.http.get(ConfigService.BASE_CONFIG_URL)
27 .map(this.restExtractor.extractDataGet) 27 .map(this.restExtractor.extractDataGet)
28 .subscribe(data => { 28 .subscribe(data => {
29 this.config = data; 29 this.config = data
30 }); 30 })
31 } 31 }
32 32
33 getConfig() { 33 getConfig () {
34 return this.config; 34 return this.config
35 } 35 }
36} 36}
diff --git a/client/src/app/core/config/index.ts b/client/src/app/core/config/index.ts
index 90392254a..3724e12f2 100644
--- a/client/src/app/core/config/index.ts
+++ b/client/src/app/core/config/index.ts
@@ -1 +1 @@
export * from './config.service'; export * from './config.service'
diff --git a/client/src/app/core/confirm/confirm.component.ts b/client/src/app/core/confirm/confirm.component.ts
index ae42ff68a..066e3fc5f 100644
--- a/client/src/app/core/confirm/confirm.component.ts
+++ b/client/src/app/core/confirm/confirm.component.ts
@@ -1,12 +1,12 @@
1import { Component, HostListener, OnInit, ViewChild } from '@angular/core'; 1import { Component, HostListener, OnInit, ViewChild } from '@angular/core'
2 2
3import { ModalDirective } from 'ngx-bootstrap/modal'; 3import { ModalDirective } from 'ngx-bootstrap/modal'
4 4
5import { ConfirmService } from './confirm.service'; 5import { ConfirmService } from './confirm.service'
6 6
7export interface ConfigChangedEvent { 7export interface ConfigChangedEvent {
8 columns: { [id: string]: { isDisplayed: boolean }; }; 8 columns: { [id: string]: { isDisplayed: boolean } }
9 config: { resultsPerPage: number }; 9 config: { resultsPerPage: number }
10} 10}
11 11
12@Component({ 12@Component({
@@ -14,48 +14,48 @@ export interface ConfigChangedEvent {
14 templateUrl: './confirm.component.html' 14 templateUrl: './confirm.component.html'
15}) 15})
16export class ConfirmComponent implements OnInit { 16export class ConfirmComponent implements OnInit {
17 @ViewChild('confirmModal') confirmModal: ModalDirective; 17 @ViewChild('confirmModal') confirmModal: ModalDirective
18 18
19 title = ''; 19 title = ''
20 message = ''; 20 message = ''
21 21
22 constructor (private confirmService: ConfirmService) { 22 constructor (private confirmService: ConfirmService) {
23 // Empty 23 // Empty
24 } 24 }
25 25
26 ngOnInit() { 26 ngOnInit () {
27 this.confirmModal.config = { 27 this.confirmModal.config = {
28 backdrop: 'static', 28 backdrop: 'static',
29 keyboard: false 29 keyboard: false
30 }; 30 }
31 31
32 this.confirmService.showConfirm.subscribe( 32 this.confirmService.showConfirm.subscribe(
33 ({ title, message }) => { 33 ({ title, message }) => {
34 this.title = title; 34 this.title = title
35 this.message = message; 35 this.message = message
36 36
37 this.showModal(); 37 this.showModal()
38 } 38 }
39 ); 39 )
40 } 40 }
41 41
42 @HostListener('keydown.enter') 42 @HostListener('keydown.enter')
43 confirm() { 43 confirm () {
44 this.confirmService.confirmResponse.next(true); 44 this.confirmService.confirmResponse.next(true)
45 this.hideModal(); 45 this.hideModal()
46 } 46 }
47 47
48 @HostListener('keydown.esc') 48 @HostListener('keydown.esc')
49 abort() { 49 abort () {
50 this.confirmService.confirmResponse.next(false); 50 this.confirmService.confirmResponse.next(false)
51 this.hideModal(); 51 this.hideModal()
52 } 52 }
53 53
54 showModal() { 54 showModal () {
55 this.confirmModal.show(); 55 this.confirmModal.show()
56 } 56 }
57 57
58 hideModal() { 58 hideModal () {
59 this.confirmModal.hide(); 59 this.confirmModal.hide()
60 } 60 }
61} 61}
diff --git a/client/src/app/core/confirm/confirm.service.ts b/client/src/app/core/confirm/confirm.service.ts
index 08127a66f..f12ff1848 100644
--- a/client/src/app/core/confirm/confirm.service.ts
+++ b/client/src/app/core/confirm/confirm.service.ts
@@ -1,15 +1,15 @@
1import { Injectable } from '@angular/core'; 1import { Injectable } from '@angular/core'
2import { Subject } from 'rxjs/Subject'; 2import { Subject } from 'rxjs/Subject'
3import 'rxjs/add/operator/first'; 3import 'rxjs/add/operator/first'
4 4
5@Injectable() 5@Injectable()
6export class ConfirmService { 6export class ConfirmService {
7 showConfirm = new Subject<{ title, message }>(); 7 showConfirm = new Subject<{ title, message }>()
8 confirmResponse = new Subject<boolean>(); 8 confirmResponse = new Subject<boolean>()
9 9
10 confirm(message = '', title = '') { 10 confirm (message = '', title = '') {
11 this.showConfirm.next({ title, message }); 11 this.showConfirm.next({ title, message })
12 12
13 return this.confirmResponse.asObservable().first(); 13 return this.confirmResponse.asObservable().first()
14 } 14 }
15} 15}
diff --git a/client/src/app/core/confirm/index.ts b/client/src/app/core/confirm/index.ts
index 789e7e547..44aabfc13 100644
--- a/client/src/app/core/confirm/index.ts
+++ b/client/src/app/core/confirm/index.ts
@@ -1,2 +1,2 @@
1export * from './confirm.component'; 1export * from './confirm.component'
2export * from './confirm.service'; 2export * from './confirm.service'
diff --git a/client/src/app/core/core.module.ts b/client/src/app/core/core.module.ts
index 81c8f2da6..382febe5c 100644
--- a/client/src/app/core/core.module.ts
+++ b/client/src/app/core/core.module.ts
@@ -1,16 +1,16 @@
1import { NgModule, Optional, SkipSelf } from '@angular/core'; 1import { NgModule, Optional, SkipSelf } from '@angular/core'
2import { CommonModule } from '@angular/common'; 2import { CommonModule } from '@angular/common'
3import { HttpModule } from '@angular/http'; 3import { HttpModule } from '@angular/http'
4import { RouterModule } from '@angular/router'; 4import { RouterModule } from '@angular/router'
5 5
6import { SimpleNotificationsModule } from 'angular2-notifications'; 6import { SimpleNotificationsModule } from 'angular2-notifications'
7import { ModalModule } from 'ngx-bootstrap/modal'; 7import { ModalModule } from 'ngx-bootstrap/modal'
8 8
9import { AuthService } from './auth'; 9import { AuthService } from './auth'
10import { ConfigService } from './config'; 10import { ConfigService } from './config'
11import { ConfirmComponent, ConfirmService } from './confirm'; 11import { ConfirmComponent, ConfirmService } from './confirm'
12import { MenuComponent, MenuAdminComponent } from './menu'; 12import { MenuComponent, MenuAdminComponent } from './menu'
13import { throwIfAlreadyLoaded } from './module-import-guard'; 13import { throwIfAlreadyLoaded } from './module-import-guard'
14 14
15@NgModule({ 15@NgModule({
16 imports: [ 16 imports: [
@@ -43,7 +43,7 @@ import { throwIfAlreadyLoaded } from './module-import-guard';
43 ] 43 ]
44}) 44})
45export class CoreModule { 45export class CoreModule {
46 constructor( @Optional() @SkipSelf() parentModule: CoreModule) { 46 constructor ( @Optional() @SkipSelf() parentModule: CoreModule) {
47 throwIfAlreadyLoaded(parentModule, 'CoreModule'); 47 throwIfAlreadyLoaded(parentModule, 'CoreModule')
48 } 48 }
49} 49}
diff --git a/client/src/app/core/index.ts b/client/src/app/core/index.ts
index 96b28658b..01b12ce7e 100644
--- a/client/src/app/core/index.ts
+++ b/client/src/app/core/index.ts
@@ -1,5 +1,5 @@
1export * from './auth'; 1export * from './auth'
2export * from './config'; 2export * from './config'
3export * from './confirm'; 3export * from './confirm'
4export * from './menu'; 4export * from './menu'
5export * from './core.module' 5export * from './core.module'
diff --git a/client/src/app/core/menu/index.ts b/client/src/app/core/menu/index.ts
index ff40f26e1..c905ed20a 100644
--- a/client/src/app/core/menu/index.ts
+++ b/client/src/app/core/menu/index.ts
@@ -1,2 +1,2 @@
1export * from './menu.component'; 1export * from './menu.component'
2export * from './menu-admin.component'; 2export * from './menu-admin.component'
diff --git a/client/src/app/core/menu/menu-admin.component.ts b/client/src/app/core/menu/menu-admin.component.ts
index 236161fce..f6cc6554c 100644
--- a/client/src/app/core/menu/menu-admin.component.ts
+++ b/client/src/app/core/menu/menu-admin.component.ts
@@ -1,4 +1,4 @@
1import { Component } from '@angular/core'; 1import { Component } from '@angular/core'
2 2
3@Component({ 3@Component({
4 selector: 'my-menu-admin', 4 selector: 'my-menu-admin',
diff --git a/client/src/app/core/menu/menu.component.ts b/client/src/app/core/menu/menu.component.ts
index 5ab8bf464..b725f64a7 100644
--- a/client/src/app/core/menu/menu.component.ts
+++ b/client/src/app/core/menu/menu.component.ts
@@ -1,8 +1,8 @@
1import { Component, OnInit } from '@angular/core'; 1import { Component, OnInit } from '@angular/core'
2import { Router } from '@angular/router'; 2import { Router } from '@angular/router'
3 3
4import { AuthService, AuthStatus } from '../auth'; 4import { AuthService, AuthStatus } from '../auth'
5import { ConfigService } from '../config'; 5import { ConfigService } from '../config'
6 6
7@Component({ 7@Component({
8 selector: 'my-menu', 8 selector: 'my-menu',
@@ -10,7 +10,7 @@ import { ConfigService } from '../config';
10 styleUrls: [ './menu.component.scss' ] 10 styleUrls: [ './menu.component.scss' ]
11}) 11})
12export class MenuComponent implements OnInit { 12export class MenuComponent implements OnInit {
13 isLoggedIn: boolean; 13 isLoggedIn: boolean
14 14
15 constructor ( 15 constructor (
16 private authService: AuthService, 16 private authService: AuthService,
@@ -18,35 +18,35 @@ export class MenuComponent implements OnInit {
18 private router: Router 18 private router: Router
19 ) {} 19 ) {}
20 20
21 ngOnInit() { 21 ngOnInit () {
22 this.isLoggedIn = this.authService.isLoggedIn(); 22 this.isLoggedIn = this.authService.isLoggedIn()
23 23
24 this.authService.loginChangedSource.subscribe( 24 this.authService.loginChangedSource.subscribe(
25 status => { 25 status => {
26 if (status === AuthStatus.LoggedIn) { 26 if (status === AuthStatus.LoggedIn) {
27 this.isLoggedIn = true; 27 this.isLoggedIn = true
28 console.log('Logged in.'); 28 console.log('Logged in.')
29 } else if (status === AuthStatus.LoggedOut) { 29 } else if (status === AuthStatus.LoggedOut) {
30 this.isLoggedIn = false; 30 this.isLoggedIn = false
31 console.log('Logged out.'); 31 console.log('Logged out.')
32 } else { 32 } else {
33 console.error('Unknown auth status: ' + status); 33 console.error('Unknown auth status: ' + status)
34 } 34 }
35 } 35 }
36 ); 36 )
37 } 37 }
38 38
39 isRegistrationEnabled() { 39 isRegistrationEnabled () {
40 return this.configService.getConfig().signup.enabled; 40 return this.configService.getConfig().signup.enabled
41 } 41 }
42 42
43 isUserAdmin() { 43 isUserAdmin () {
44 return this.authService.isAdmin(); 44 return this.authService.isAdmin()
45 } 45 }
46 46
47 logout() { 47 logout () {
48 this.authService.logout(); 48 this.authService.logout()
49 // Redirect to home page 49 // Redirect to home page
50 this.router.navigate(['/videos/list']); 50 this.router.navigate(['/videos/list'])
51 } 51 }
52} 52}
diff --git a/client/src/app/core/module-import-guard.ts b/client/src/app/core/module-import-guard.ts
index 445640c4f..32b1d8f19 100644
--- a/client/src/app/core/module-import-guard.ts
+++ b/client/src/app/core/module-import-guard.ts
@@ -1,5 +1,5 @@
1export function throwIfAlreadyLoaded(parentModule: any, moduleName: string) { 1export function throwIfAlreadyLoaded (parentModule: any, moduleName: string) {
2 if (parentModule) { 2 if (parentModule) {
3 throw new Error(`${moduleName} has already been loaded. Import Core modules in the AppModule only.`); 3 throw new Error(`${moduleName} has already been loaded. Import Core modules in the AppModule only.`)
4 } 4 }
5} 5}
diff --git a/client/src/app/environment.ts b/client/src/app/environment.ts
index 799102d82..dd327a62e 100644
--- a/client/src/app/environment.ts
+++ b/client/src/app/environment.ts
@@ -1,3 +1,5 @@
1/* tslint:disable */
2
1/** 3/**
2 * Angular 2 4 * Angular 2
3 */ 5 */
diff --git a/client/src/app/index.ts b/client/src/app/index.ts
index da53f6aef..d45dbeff6 100644
--- a/client/src/app/index.ts
+++ b/client/src/app/index.ts
@@ -1 +1 @@
export * from './app.module'; export * from './app.module'
diff --git a/client/src/app/login/index.ts b/client/src/app/login/index.ts
index 5639915f7..f1301d8b5 100644
--- a/client/src/app/login/index.ts
+++ b/client/src/app/login/index.ts
@@ -1,3 +1,3 @@
1export * from './login-routing.module'; 1export * from './login-routing.module'
2export * from './login.component'; 2export * from './login.component'
3export * from './login.module'; 3export * from './login.module'
diff --git a/client/src/app/login/login-routing.module.ts b/client/src/app/login/login-routing.module.ts
index 371993884..1a91677c0 100644
--- a/client/src/app/login/login-routing.module.ts
+++ b/client/src/app/login/login-routing.module.ts
@@ -1,7 +1,7 @@
1import { NgModule } from '@angular/core'; 1import { NgModule } from '@angular/core'
2import { RouterModule, Routes } from '@angular/router'; 2import { RouterModule, Routes } from '@angular/router'
3 3
4import { LoginComponent } from './login.component'; 4import { LoginComponent } from './login.component'
5 5
6const loginRoutes: Routes = [ 6const loginRoutes: Routes = [
7 { 7 {
@@ -13,7 +13,7 @@ const loginRoutes: Routes = [
13 } 13 }
14 } 14 }
15 } 15 }
16]; 16]
17 17
18@NgModule({ 18@NgModule({
19 imports: [ RouterModule.forChild(loginRoutes) ], 19 imports: [ RouterModule.forChild(loginRoutes) ],
diff --git a/client/src/app/login/login.component.ts b/client/src/app/login/login.component.ts
index fd4a314cc..77703a80c 100644
--- a/client/src/app/login/login.component.ts
+++ b/client/src/app/login/login.component.ts
@@ -1,9 +1,9 @@
1import { Component, OnInit } from '@angular/core'; 1import { Component, OnInit } from '@angular/core'
2import { FormBuilder, FormGroup, Validators } from '@angular/forms'; 2import { FormBuilder, FormGroup, Validators } from '@angular/forms'
3import { Router } from '@angular/router'; 3import { Router } from '@angular/router'
4 4
5import { AuthService } from '../core'; 5import { AuthService } from '../core'
6import { FormReactive } from '../shared'; 6import { FormReactive } from '../shared'
7 7
8@Component({ 8@Component({
9 selector: 'my-login', 9 selector: 'my-login',
@@ -11,60 +11,60 @@ import { FormReactive } from '../shared';
11}) 11})
12 12
13export class LoginComponent extends FormReactive implements OnInit { 13export class LoginComponent extends FormReactive implements OnInit {
14 error: string = null; 14 error: string = null
15 15
16 form: FormGroup; 16 form: FormGroup
17 formErrors = { 17 formErrors = {
18 'username': '', 18 'username': '',
19 'password': '' 19 'password': ''
20 }; 20 }
21 validationMessages = { 21 validationMessages = {
22 'username': { 22 'username': {
23 'required': 'Username is required.', 23 'required': 'Username is required.'
24 }, 24 },
25 'password': { 25 'password': {
26 'required': 'Password is required.' 26 'required': 'Password is required.'
27 } 27 }
28 }; 28 }
29 29
30 constructor( 30 constructor (
31 private authService: AuthService, 31 private authService: AuthService,
32 private formBuilder: FormBuilder, 32 private formBuilder: FormBuilder,
33 private router: Router 33 private router: Router
34 ) { 34 ) {
35 super(); 35 super()
36 } 36 }
37 37
38 buildForm() { 38 buildForm () {
39 this.form = this.formBuilder.group({ 39 this.form = this.formBuilder.group({
40 username: [ '', Validators.required ], 40 username: [ '', Validators.required ],
41 password: [ '', Validators.required ], 41 password: [ '', Validators.required ]
42 }); 42 })
43 43
44 this.form.valueChanges.subscribe(data => this.onValueChanged(data)); 44 this.form.valueChanges.subscribe(data => this.onValueChanged(data))
45 } 45 }
46 46
47 ngOnInit() { 47 ngOnInit () {
48 this.buildForm(); 48 this.buildForm()
49 } 49 }
50 50
51 login() { 51 login () {
52 this.error = null; 52 this.error = null
53 53
54 const { username, password } = this.form.value; 54 const { username, password } = this.form.value
55 55
56 this.authService.login(username, password).subscribe( 56 this.authService.login(username, password).subscribe(
57 result => this.router.navigate(['/videos/list']), 57 result => this.router.navigate(['/videos/list']),
58 58
59 error => { 59 error => {
60 console.error(error.json); 60 console.error(error.json)
61 61
62 if (error.json.error === 'invalid_grant') { 62 if (error.json.error === 'invalid_grant') {
63 this.error = 'Credentials are invalid.'; 63 this.error = 'Credentials are invalid.'
64 } else { 64 } else {
65 this.error = `${error.json.error}: ${error.json.error_description}`; 65 this.error = `${error.json.error}: ${error.json.error_description}`
66 } 66 }
67 } 67 }
68 ); 68 )
69 } 69 }
70} 70}
diff --git a/client/src/app/login/login.module.ts b/client/src/app/login/login.module.ts
index 31a723b43..1de72dbaa 100644
--- a/client/src/app/login/login.module.ts
+++ b/client/src/app/login/login.module.ts
@@ -1,8 +1,8 @@
1import { NgModule } from '@angular/core'; 1import { NgModule } from '@angular/core'
2 2
3import { LoginRoutingModule } from './login-routing.module'; 3import { LoginRoutingModule } from './login-routing.module'
4import { LoginComponent } from './login.component'; 4import { LoginComponent } from './login.component'
5import { SharedModule } from '../shared'; 5import { SharedModule } from '../shared'
6 6
7@NgModule({ 7@NgModule({
8 imports: [ 8 imports: [
diff --git a/client/src/app/shared/auth/auth-http.service.ts b/client/src/app/shared/auth/auth-http.service.ts
index c4114aa02..0fbaab0a8 100644
--- a/client/src/app/shared/auth/auth-http.service.ts
+++ b/client/src/app/shared/auth/auth-http.service.ts
@@ -1,4 +1,4 @@
1import { Injectable } from '@angular/core'; 1import { Injectable } from '@angular/core'
2import { 2import {
3 ConnectionBackend, 3 ConnectionBackend,
4 Headers, 4 Headers,
@@ -9,79 +9,79 @@ import {
9 RequestOptionsArgs, 9 RequestOptionsArgs,
10 Response, 10 Response,
11 XHRBackend 11 XHRBackend
12} from '@angular/http'; 12} from '@angular/http'
13import { Observable } from 'rxjs/Observable'; 13import { Observable } from 'rxjs/Observable'
14 14
15import { AuthService } from '../../core'; 15import { AuthService } from '../../core'
16 16
17@Injectable() 17@Injectable()
18export class AuthHttp extends Http { 18export class AuthHttp extends Http {
19 constructor(backend: ConnectionBackend, defaultOptions: RequestOptions, private authService: AuthService) { 19 constructor (backend: ConnectionBackend, defaultOptions: RequestOptions, private authService: AuthService) {
20 super(backend, defaultOptions); 20 super(backend, defaultOptions)
21 } 21 }
22 22
23 request(url: string | Request, options?: RequestOptionsArgs): Observable<Response> { 23 request (url: string | Request, options?: RequestOptionsArgs): Observable<Response> {
24 if (!options) options = {}; 24 if (!options) options = {}
25 25
26 options.headers = new Headers(); 26 options.headers = new Headers()
27 this.setAuthorizationHeader(options.headers); 27 this.setAuthorizationHeader(options.headers)
28 28
29 return super.request(url, options) 29 return super.request(url, options)
30 .catch((err) => { 30 .catch((err) => {
31 if (err.status === 401) { 31 if (err.status === 401) {
32 return this.handleTokenExpired(url, options); 32 return this.handleTokenExpired(url, options)
33 } 33 }
34 34
35 return Observable.throw(err); 35 return Observable.throw(err)
36 }); 36 })
37 } 37 }
38 38
39 delete(url: string, options?: RequestOptionsArgs): Observable<Response> { 39 delete (url: string, options?: RequestOptionsArgs): Observable<Response> {
40 if (!options) options = {}; 40 if (!options) options = {}
41 options.method = RequestMethod.Delete; 41 options.method = RequestMethod.Delete
42 42
43 return this.request(url, options); 43 return this.request(url, options)
44 } 44 }
45 45
46 get(url: string, options?: RequestOptionsArgs): Observable<Response> { 46 get (url: string, options?: RequestOptionsArgs): Observable<Response> {
47 if (!options) options = {}; 47 if (!options) options = {}
48 options.method = RequestMethod.Get; 48 options.method = RequestMethod.Get
49 49
50 return this.request(url, options); 50 return this.request(url, options)
51 } 51 }
52 52
53 post(url: string, body: any, options?: RequestOptionsArgs): Observable<Response> { 53 post (url: string, body: any, options?: RequestOptionsArgs): Observable<Response> {
54 if (!options) options = {}; 54 if (!options) options = {}
55 options.method = RequestMethod.Post; 55 options.method = RequestMethod.Post
56 options.body = body; 56 options.body = body
57 57
58 return this.request(url, options); 58 return this.request(url, options)
59 } 59 }
60 60
61 put(url: string, body: any, options?: RequestOptionsArgs): Observable<Response> { 61 put (url: string, body: any, options?: RequestOptionsArgs): Observable<Response> {
62 if (!options) options = {}; 62 if (!options) options = {}
63 options.method = RequestMethod.Put; 63 options.method = RequestMethod.Put
64 options.body = body; 64 options.body = body
65 65
66 return this.request(url, options); 66 return this.request(url, options)
67 } 67 }
68 68
69 private handleTokenExpired(url: string | Request, options: RequestOptionsArgs) { 69 private handleTokenExpired (url: string | Request, options: RequestOptionsArgs) {
70 return this.authService.refreshAccessToken() 70 return this.authService.refreshAccessToken()
71 .flatMap(() => { 71 .flatMap(() => {
72 this.setAuthorizationHeader(options.headers); 72 this.setAuthorizationHeader(options.headers)
73 73
74 return super.request(url, options); 74 return super.request(url, options)
75 }); 75 })
76 } 76 }
77 77
78 private setAuthorizationHeader(headers: Headers) { 78 private setAuthorizationHeader (headers: Headers) {
79 headers.set('Authorization', this.authService.getRequestHeaderValue()); 79 headers.set('Authorization', this.authService.getRequestHeaderValue())
80 } 80 }
81} 81}
82 82
83export function useFactory(backend: XHRBackend, defaultOptions: RequestOptions, authService: AuthService) { 83export function useFactory (backend: XHRBackend, defaultOptions: RequestOptions, authService: AuthService) {
84 return new AuthHttp(backend, defaultOptions, authService); 84 return new AuthHttp(backend, defaultOptions, authService)
85} 85}
86 86
87export const AUTH_HTTP_PROVIDERS = [ 87export const AUTH_HTTP_PROVIDERS = [
@@ -89,5 +89,5 @@ export const AUTH_HTTP_PROVIDERS = [
89 provide: AuthHttp, 89 provide: AuthHttp,
90 useFactory, 90 useFactory,
91 deps: [ XHRBackend, RequestOptions, AuthService ] 91 deps: [ XHRBackend, RequestOptions, AuthService ]
92 }, 92 }
93]; 93]
diff --git a/client/src/app/shared/auth/index.ts b/client/src/app/shared/auth/index.ts
index c488aed69..0f2bfb0d6 100644
--- a/client/src/app/shared/auth/index.ts
+++ b/client/src/app/shared/auth/index.ts
@@ -1 +1 @@
export * from './auth-http.service'; export * from './auth-http.service'
diff --git a/client/src/app/shared/forms/form-reactive.ts b/client/src/app/shared/forms/form-reactive.ts
index a5732e083..e7764d069 100644
--- a/client/src/app/shared/forms/form-reactive.ts
+++ b/client/src/app/shared/forms/form-reactive.ts
@@ -1,38 +1,38 @@
1import { FormGroup } from '@angular/forms'; 1import { FormGroup } from '@angular/forms'
2 2
3export abstract class FormReactive { 3export abstract class FormReactive {
4 abstract form: FormGroup; 4 abstract form: FormGroup
5 abstract formErrors: Object; 5 abstract formErrors: Object
6 abstract validationMessages: Object; 6 abstract validationMessages: Object
7 7
8 abstract buildForm(): void; 8 abstract buildForm (): void
9 9
10 protected onValueChanged(data?: any) { 10 protected onValueChanged (data?: any) {
11 for (const field in this.formErrors) { 11 for (const field in this.formErrors) {
12 // clear previous error message (if any) 12 // clear previous error message (if any)
13 this.formErrors[field] = ''; 13 this.formErrors[field] = ''
14 const control = this.form.get(field); 14 const control = this.form.get(field)
15 15
16 if (control && control.dirty && !control.valid) { 16 if (control && control.dirty && !control.valid) {
17 const messages = this.validationMessages[field]; 17 const messages = this.validationMessages[field]
18 for (const key in control.errors) { 18 for (const key in control.errors) {
19 this.formErrors[field] += messages[key] + ' '; 19 this.formErrors[field] += messages[key] + ' '
20 } 20 }
21 } 21 }
22 } 22 }
23 } 23 }
24 24
25 // Same as onValueChanged but force checking even if the field is not dirty 25 // Same as onValueChanged but force checking even if the field is not dirty
26 protected forceCheck() { 26 protected forceCheck () {
27 for (const field in this.formErrors) { 27 for (const field in this.formErrors) {
28 // clear previous error message (if any) 28 // clear previous error message (if any)
29 this.formErrors[field] = ''; 29 this.formErrors[field] = ''
30 const control = this.form.get(field); 30 const control = this.form.get(field)
31 31
32 if (control && !control.valid) { 32 if (control && !control.valid) {
33 const messages = this.validationMessages[field]; 33 const messages = this.validationMessages[field]
34 for (const key in control.errors) { 34 for (const key in control.errors) {
35 this.formErrors[field] += messages[key] + ' '; 35 this.formErrors[field] += messages[key] + ' '
36 } 36 }
37 } 37 }
38 } 38 }
diff --git a/client/src/app/shared/forms/form-validators/host.validator.ts b/client/src/app/shared/forms/form-validators/host.validator.ts
index ec417e079..03e810fdb 100644
--- a/client/src/app/shared/forms/form-validators/host.validator.ts
+++ b/client/src/app/shared/forms/form-validators/host.validator.ts
@@ -1,14 +1,14 @@
1import { FormControl } from '@angular/forms'; 1import { FormControl } from '@angular/forms'
2 2
3export function validateHost(c: FormControl) { 3export function validateHost (c: FormControl) {
4 // Thanks to http://stackoverflow.com/a/106223 4 // Thanks to http://stackoverflow.com/a/106223
5 const HOST_REGEXP = new RegExp( 5 const HOST_REGEXP = new RegExp(
6 '^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$' 6 '^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$'
7 ); 7 )
8 8
9 return HOST_REGEXP.test(c.value) ? null : { 9 return HOST_REGEXP.test(c.value) ? null : {
10 validateHost: { 10 validateHost: {
11 valid: false 11 valid: false
12 } 12 }
13 }; 13 }
14} 14}
diff --git a/client/src/app/shared/forms/form-validators/index.ts b/client/src/app/shared/forms/form-validators/index.ts
index ab7c2df31..efe77d4ae 100644
--- a/client/src/app/shared/forms/form-validators/index.ts
+++ b/client/src/app/shared/forms/form-validators/index.ts
@@ -1,4 +1,4 @@
1export * from './host.validator'; 1export * from './host.validator'
2export * from './user'; 2export * from './user'
3export * from './video-abuse'; 3export * from './video-abuse'
4export * from './video'; 4export * from './video'
diff --git a/client/src/app/shared/forms/form-validators/user.ts b/client/src/app/shared/forms/form-validators/user.ts
index 259d2b868..fd316583e 100644
--- a/client/src/app/shared/forms/form-validators/user.ts
+++ b/client/src/app/shared/forms/form-validators/user.ts
@@ -1,4 +1,4 @@
1import { Validators } from '@angular/forms'; 1import { Validators } from '@angular/forms'
2 2
3export const USER_USERNAME = { 3export const USER_USERNAME = {
4 VALIDATORS: [ Validators.required, Validators.minLength(3), Validators.maxLength(20) ], 4 VALIDATORS: [ Validators.required, Validators.minLength(3), Validators.maxLength(20) ],
@@ -7,18 +7,18 @@ export const USER_USERNAME = {
7 'minlength': 'Username must be at least 3 characters long.', 7 'minlength': 'Username must be at least 3 characters long.',
8 'maxlength': 'Username cannot be more than 20 characters long.' 8 'maxlength': 'Username cannot be more than 20 characters long.'
9 } 9 }
10}; 10}
11export const USER_EMAIL = { 11export const USER_EMAIL = {
12 VALIDATORS: [ Validators.required, Validators.email ], 12 VALIDATORS: [ Validators.required, Validators.email ],
13 MESSAGES: { 13 MESSAGES: {
14 'required': 'Email is required.', 14 'required': 'Email is required.',
15 'email': 'Email must be valid.', 15 'email': 'Email must be valid.'
16 } 16 }
17}; 17}
18export const USER_PASSWORD = { 18export const USER_PASSWORD = {
19 VALIDATORS: [ Validators.required, Validators.minLength(6) ], 19 VALIDATORS: [ Validators.required, Validators.minLength(6) ],
20 MESSAGES: { 20 MESSAGES: {
21 'required': 'Password is required.', 21 'required': 'Password is required.',
22 'minlength': 'Password must be at least 6 characters long.', 22 'minlength': 'Password must be at least 6 characters long.'
23 } 23 }
24}; 24}
diff --git a/client/src/app/shared/forms/form-validators/video-abuse.ts b/client/src/app/shared/forms/form-validators/video-abuse.ts
index 94a29a3b7..3c7f26205 100644
--- a/client/src/app/shared/forms/form-validators/video-abuse.ts
+++ b/client/src/app/shared/forms/form-validators/video-abuse.ts
@@ -1,4 +1,4 @@
1import { Validators } from '@angular/forms'; 1import { Validators } from '@angular/forms'
2 2
3export const VIDEO_ABUSE_REASON = { 3export const VIDEO_ABUSE_REASON = {
4 VALIDATORS: [ Validators.required, Validators.minLength(2), Validators.maxLength(300) ], 4 VALIDATORS: [ Validators.required, Validators.minLength(2), Validators.maxLength(300) ],
@@ -7,4 +7,4 @@ export const VIDEO_ABUSE_REASON = {
7 'minlength': 'Report reson must be at least 2 characters long.', 7 'minlength': 'Report reson must be at least 2 characters long.',
8 'maxlength': 'Report reson cannot be more than 300 characters long.' 8 'maxlength': 'Report reson cannot be more than 300 characters long.'
9 } 9 }
10}; 10}
diff --git a/client/src/app/shared/forms/form-validators/video.ts b/client/src/app/shared/forms/form-validators/video.ts
index f7e4e5e4b..6542cf8f6 100644
--- a/client/src/app/shared/forms/form-validators/video.ts
+++ b/client/src/app/shared/forms/form-validators/video.ts
@@ -1,4 +1,4 @@
1import { Validators } from '@angular/forms'; 1import { Validators } from '@angular/forms'
2 2
3export const VIDEO_NAME = { 3export const VIDEO_NAME = {
4 VALIDATORS: [ Validators.required, Validators.minLength(3), Validators.maxLength(50) ], 4 VALIDATORS: [ Validators.required, Validators.minLength(3), Validators.maxLength(50) ],
@@ -7,26 +7,26 @@ export const VIDEO_NAME = {
7 'minlength': 'Video name must be at least 3 characters long.', 7 'minlength': 'Video name must be at least 3 characters long.',
8 'maxlength': 'Video name cannot be more than 50 characters long.' 8 'maxlength': 'Video name cannot be more than 50 characters long.'
9 } 9 }
10}; 10}
11 11
12export const VIDEO_CATEGORY = { 12export const VIDEO_CATEGORY = {
13 VALIDATORS: [ Validators.required ], 13 VALIDATORS: [ Validators.required ],
14 MESSAGES: { 14 MESSAGES: {
15 'required': 'Video category is required.' 15 'required': 'Video category is required.'
16 } 16 }
17}; 17}
18 18
19export const VIDEO_LICENCE = { 19export const VIDEO_LICENCE = {
20 VALIDATORS: [ Validators.required ], 20 VALIDATORS: [ Validators.required ],
21 MESSAGES: { 21 MESSAGES: {
22 'required': 'Video licence is required.' 22 'required': 'Video licence is required.'
23 } 23 }
24}; 24}
25 25
26export const VIDEO_LANGUAGE = { 26export const VIDEO_LANGUAGE = {
27 VALIDATORS: [ ], 27 VALIDATORS: [ ],
28 MESSAGES: {} 28 MESSAGES: {}
29}; 29}
30 30
31export const VIDEO_DESCRIPTION = { 31export const VIDEO_DESCRIPTION = {
32 VALIDATORS: [ Validators.required, Validators.minLength(3), Validators.maxLength(250) ], 32 VALIDATORS: [ Validators.required, Validators.minLength(3), Validators.maxLength(250) ],
@@ -35,7 +35,7 @@ export const VIDEO_DESCRIPTION = {
35 'minlength': 'Video description must be at least 3 characters long.', 35 'minlength': 'Video description must be at least 3 characters long.',
36 'maxlength': 'Video description cannot be more than 250 characters long.' 36 'maxlength': 'Video description cannot be more than 250 characters long.'
37 } 37 }
38}; 38}
39 39
40export const VIDEO_TAGS = { 40export const VIDEO_TAGS = {
41 VALIDATORS: [ Validators.minLength(2), Validators.maxLength(10) ], 41 VALIDATORS: [ Validators.minLength(2), Validators.maxLength(10) ],
@@ -43,4 +43,4 @@ export const VIDEO_TAGS = {
43 'minlength': 'A tag should be more than 2 characters long.', 43 'minlength': 'A tag should be more than 2 characters long.',
44 'maxlength': 'A tag should be less than 10 characters long.' 44 'maxlength': 'A tag should be less than 10 characters long.'
45 } 45 }
46}; 46}
diff --git a/client/src/app/shared/forms/index.ts b/client/src/app/shared/forms/index.ts
index 588ebb4be..7464bb022 100644
--- a/client/src/app/shared/forms/index.ts
+++ b/client/src/app/shared/forms/index.ts
@@ -1,2 +1,2 @@
1export * from './form-validators'; 1export * from './form-validators'
2export * from './form-reactive'; 2export * from './form-reactive'
diff --git a/client/src/app/shared/index.ts b/client/src/app/shared/index.ts
index 61e8ed523..212645c51 100644
--- a/client/src/app/shared/index.ts
+++ b/client/src/app/shared/index.ts
@@ -1,8 +1,8 @@
1export * from './auth'; 1export * from './auth'
2export * from './forms'; 2export * from './forms'
3export * from './rest'; 3export * from './rest'
4export * from './search'; 4export * from './search'
5export * from './users'; 5export * from './users'
6export * from './video-abuse'; 6export * from './video-abuse'
7export * from './shared.module'; 7export * from './shared.module'
8export * from './utils'; 8export * from './utils'
diff --git a/client/src/app/shared/rest/index.ts b/client/src/app/shared/rest/index.ts
index 3cb123c3b..e0be155cf 100644
--- a/client/src/app/shared/rest/index.ts
+++ b/client/src/app/shared/rest/index.ts
@@ -1,4 +1,4 @@
1export * from './rest-data-source'; 1export * from './rest-data-source'
2export * from './rest-extractor.service'; 2export * from './rest-extractor.service'
3export * from './rest-pagination'; 3export * from './rest-pagination'
4export * from './rest.service'; 4export * from './rest.service'
diff --git a/client/src/app/shared/rest/rest-data-source.ts b/client/src/app/shared/rest/rest-data-source.ts
index 1def38c73..2ef5d38da 100644
--- a/client/src/app/shared/rest/rest-data-source.ts
+++ b/client/src/app/shared/rest/rest-data-source.ts
@@ -1,51 +1,51 @@
1import { Http, RequestOptionsArgs, URLSearchParams, } from '@angular/http'; 1import { Http, RequestOptionsArgs, URLSearchParams, Response } from '@angular/http'
2 2
3import { ServerDataSource } from 'ng2-smart-table'; 3import { ServerDataSource } from 'ng2-smart-table'
4 4
5export class RestDataSource extends ServerDataSource { 5export class RestDataSource extends ServerDataSource {
6 constructor(http: Http, endpoint: string) { 6 constructor (http: Http, endpoint: string) {
7 const options = { 7 const options = {
8 endPoint: endpoint, 8 endPoint: endpoint,
9 sortFieldKey: 'sort', 9 sortFieldKey: 'sort',
10 dataKey: 'data' 10 dataKey: 'data'
11 }; 11 }
12 12
13 super(http, options); 13 super(http, options)
14 } 14 }
15 15
16 protected extractTotalFromResponse(res) { 16 protected extractTotalFromResponse (res: Response) {
17 const rawData = res.json(); 17 const rawData = res.json()
18 return rawData ? parseInt(rawData.total) : 0; 18 return rawData ? parseInt(rawData.total, 10) : 0
19 } 19 }
20 20
21 protected addSortRequestOptions(requestOptions: RequestOptionsArgs) { 21 protected addSortRequestOptions (requestOptions: RequestOptionsArgs) {
22 let searchParams: URLSearchParams = <URLSearchParams> requestOptions.search; 22 const searchParams = requestOptions.search as URLSearchParams
23 23
24 if (this.sortConf) { 24 if (this.sortConf) {
25 this.sortConf.forEach((fieldConf) => { 25 this.sortConf.forEach((fieldConf) => {
26 const sortPrefix = fieldConf.direction === 'desc' ? '-' : ''; 26 const sortPrefix = fieldConf.direction === 'desc' ? '-' : ''
27 27
28 searchParams.set(this.conf.sortFieldKey, sortPrefix + fieldConf.field); 28 searchParams.set(this.conf.sortFieldKey, sortPrefix + fieldConf.field)
29 }); 29 })
30 } 30 }
31 31
32 return requestOptions; 32 return requestOptions
33 } 33 }
34 34
35 protected addPagerRequestOptions(requestOptions: RequestOptionsArgs) { 35 protected addPagerRequestOptions (requestOptions: RequestOptionsArgs) {
36 let searchParams: URLSearchParams = <URLSearchParams> requestOptions.search; 36 const searchParams = requestOptions.search as URLSearchParams
37 37
38 if (this.pagingConf && this.pagingConf['page'] && this.pagingConf['perPage']) { 38 if (this.pagingConf && this.pagingConf['page'] && this.pagingConf['perPage']) {
39 const perPage = this.pagingConf['perPage']; 39 const perPage = this.pagingConf['perPage']
40 const page = this.pagingConf['page']; 40 const page = this.pagingConf['page']
41 41
42 const start = (page - 1) * perPage; 42 const start = (page - 1) * perPage
43 const count = perPage; 43 const count = perPage
44 44
45 searchParams.set('start', start.toString()); 45 searchParams.set('start', start.toString())
46 searchParams.set('count', count.toString()); 46 searchParams.set('count', count.toString())
47 } 47 }
48 48
49 return requestOptions; 49 return requestOptions
50 } 50 }
51} 51}
diff --git a/client/src/app/shared/rest/rest-extractor.service.ts b/client/src/app/shared/rest/rest-extractor.service.ts
index fcb1598f4..f6a818ec8 100644
--- a/client/src/app/shared/rest/rest-extractor.service.ts
+++ b/client/src/app/shared/rest/rest-extractor.service.ts
@@ -1,52 +1,52 @@
1import { Injectable } from '@angular/core'; 1import { Injectable } from '@angular/core'
2import { Response } from '@angular/http'; 2import { Response } from '@angular/http'
3import { Observable } from 'rxjs/Observable'; 3import { Observable } from 'rxjs/Observable'
4 4
5export interface ResultList { 5export interface ResultList {
6 data: any[]; 6 data: any[]
7 total: number; 7 total: number
8} 8}
9 9
10@Injectable() 10@Injectable()
11export class RestExtractor { 11export class RestExtractor {
12 12
13 constructor () { ; } 13 extractDataBool (res: Response) {
14 14 return true
15 extractDataBool(res: Response) {
16 return true;
17 } 15 }
18 16
19 extractDataList(res: Response) { 17 extractDataList (res: Response) {
20 const body = res.json(); 18 const body = res.json()
21 19
22 const ret: ResultList = { 20 const ret: ResultList = {
23 data: body.data, 21 data: body.data,
24 total: body.total 22 total: body.total
25 }; 23 }
26 24
27 return ret; 25 return ret
28 } 26 }
29 27
30 extractDataGet(res: Response) { 28 extractDataGet (res: Response) {
31 return res.json(); 29 return res.json()
32 } 30 }
33 31
34 handleError(res: Response) { 32 handleError (res: Response) {
35 let text = 'Server error: '; 33 let text = 'Server error: '
36 text += res.text(); 34 text += res.text()
37 let json = ''; 35 let json = ''
38 36
39 try { 37 try {
40 json = res.json(); 38 json = res.json()
41 } catch (err) { ; } 39 } catch (err) {
40 console.error('Cannot get JSON from response.')
41 }
42 42
43 const error = { 43 const error = {
44 json, 44 json,
45 text 45 text
46 }; 46 }
47 47
48 console.error(error); 48 console.error(error)
49 49
50 return Observable.throw(error); 50 return Observable.throw(error)
51 } 51 }
52} 52}
diff --git a/client/src/app/shared/rest/rest-pagination.ts b/client/src/app/shared/rest/rest-pagination.ts
index 0cfa4f468..766e7a9e5 100644
--- a/client/src/app/shared/rest/rest-pagination.ts
+++ b/client/src/app/shared/rest/rest-pagination.ts
@@ -1,5 +1,5 @@
1export interface RestPagination { 1export interface RestPagination {
2 currentPage: number; 2 currentPage: number
3 itemsPerPage: number; 3 itemsPerPage: number
4 totalItems: number; 4 totalItems: number
5}; 5}
diff --git a/client/src/app/shared/rest/rest.service.ts b/client/src/app/shared/rest/rest.service.ts
index 16b47e957..43dc20b34 100644
--- a/client/src/app/shared/rest/rest.service.ts
+++ b/client/src/app/shared/rest/rest.service.ts
@@ -1,27 +1,27 @@
1import { Injectable } from '@angular/core'; 1import { Injectable } from '@angular/core'
2import { URLSearchParams } from '@angular/http'; 2import { URLSearchParams } from '@angular/http'
3 3
4import { RestPagination } from './rest-pagination'; 4import { RestPagination } from './rest-pagination'
5 5
6@Injectable() 6@Injectable()
7export class RestService { 7export class RestService {
8 8
9 buildRestGetParams(pagination?: RestPagination, sort?: string) { 9 buildRestGetParams (pagination?: RestPagination, sort?: string) {
10 const params = new URLSearchParams(); 10 const params = new URLSearchParams()
11 11
12 if (pagination) { 12 if (pagination) {
13 const start: number = (pagination.currentPage - 1) * pagination.itemsPerPage; 13 const start: number = (pagination.currentPage - 1) * pagination.itemsPerPage
14 const count: number = pagination.itemsPerPage; 14 const count: number = pagination.itemsPerPage
15 15
16 params.set('start', start.toString()); 16 params.set('start', start.toString())
17 params.set('count', count.toString()); 17 params.set('count', count.toString())
18 } 18 }
19 19
20 if (sort) { 20 if (sort) {
21 params.set('sort', sort); 21 params.set('sort', sort)
22 } 22 }
23 23
24 return params; 24 return params
25 } 25 }
26 26
27} 27}
diff --git a/client/src/app/shared/search/index.ts b/client/src/app/shared/search/index.ts
index a897ed099..d4016cf89 100644
--- a/client/src/app/shared/search/index.ts
+++ b/client/src/app/shared/search/index.ts
@@ -1,4 +1,4 @@
1export * from './search-field.type'; 1export * from './search-field.type'
2export * from './search.component'; 2export * from './search.component'
3export * from './search.model'; 3export * from './search.model'
4export * from './search.service'; 4export * from './search.service'
diff --git a/client/src/app/shared/search/search-field.type.ts b/client/src/app/shared/search/search-field.type.ts
index 6be584ed1..63557898a 100644
--- a/client/src/app/shared/search/search-field.type.ts
+++ b/client/src/app/shared/search/search-field.type.ts
@@ -1 +1 @@
export type SearchField = "name" | "author" | "host" | "magnetUri" | "tags"; export type SearchField = 'name' | 'author' | 'host' | 'magnetUri' | 'tags'
diff --git a/client/src/app/shared/search/search.component.ts b/client/src/app/shared/search/search.component.ts
index 48413b4f2..ecce20666 100644
--- a/client/src/app/shared/search/search.component.ts
+++ b/client/src/app/shared/search/search.component.ts
@@ -1,9 +1,9 @@
1import { Component, OnInit } from '@angular/core'; 1import { Component, OnInit } from '@angular/core'
2import { Router } from '@angular/router'; 2import { Router } from '@angular/router'
3 3
4import { Search } from './search.model'; 4import { Search } from './search.model'
5import { SearchField } from './search-field.type'; 5import { SearchField } from './search-field.type'
6import { SearchService } from './search.service'; 6import { SearchService } from './search.service'
7 7
8@Component({ 8@Component({
9 selector: 'my-search', 9 selector: 'my-search',
@@ -18,53 +18,53 @@ export class SearchComponent implements OnInit {
18 host: 'Pod Host', 18 host: 'Pod Host',
19 magnetUri: 'Magnet URI', 19 magnetUri: 'Magnet URI',
20 tags: 'Tags' 20 tags: 'Tags'
21 }; 21 }
22 searchCriterias: Search = { 22 searchCriterias: Search = {
23 field: 'name', 23 field: 'name',
24 value: '' 24 value: ''
25 }; 25 }
26 26
27 constructor(private searchService: SearchService, private router: Router) {} 27 constructor (private searchService: SearchService, private router: Router) {}
28 28
29 ngOnInit() { 29 ngOnInit () {
30 // Subscribe if the search changed 30 // Subscribe if the search changed
31 // Usually changed by videos list component 31 // Usually changed by videos list component
32 this.searchService.updateSearch.subscribe( 32 this.searchService.updateSearch.subscribe(
33 newSearchCriterias => { 33 newSearchCriterias => {
34 // Put a field by default 34 // Put a field by default
35 if (!newSearchCriterias.field) { 35 if (!newSearchCriterias.field) {
36 newSearchCriterias.field = 'name'; 36 newSearchCriterias.field = 'name'
37 } 37 }
38 38
39 this.searchCriterias = newSearchCriterias; 39 this.searchCriterias = newSearchCriterias
40 } 40 }
41 ); 41 )
42 } 42 }
43 43
44 get choiceKeys() { 44 get choiceKeys () {
45 return Object.keys(this.fieldChoices); 45 return Object.keys(this.fieldChoices)
46 } 46 }
47 47
48 choose($event: MouseEvent, choice: SearchField) { 48 choose ($event: MouseEvent, choice: SearchField) {
49 $event.preventDefault(); 49 $event.preventDefault()
50 $event.stopPropagation(); 50 $event.stopPropagation()
51 51
52 this.searchCriterias.field = choice; 52 this.searchCriterias.field = choice
53 53
54 if (this.searchCriterias.value) { 54 if (this.searchCriterias.value) {
55 this.doSearch(); 55 this.doSearch()
56 } 56 }
57 } 57 }
58 58
59 doSearch() { 59 doSearch () {
60 if (this.router.url.indexOf('/videos/list') === -1) { 60 if (this.router.url.indexOf('/videos/list') === -1) {
61 this.router.navigate([ '/videos/list' ]); 61 this.router.navigate([ '/videos/list' ])
62 } 62 }
63 63
64 this.searchService.searchUpdated.next(this.searchCriterias); 64 this.searchService.searchUpdated.next(this.searchCriterias)
65 } 65 }
66 66
67 getStringChoice(choiceKey: SearchField) { 67 getStringChoice (choiceKey: SearchField) {
68 return this.fieldChoices[choiceKey]; 68 return this.fieldChoices[choiceKey]
69 } 69 }
70} 70}
diff --git a/client/src/app/shared/search/search.model.ts b/client/src/app/shared/search/search.model.ts
index 932a6566c..174adf2c6 100644
--- a/client/src/app/shared/search/search.model.ts
+++ b/client/src/app/shared/search/search.model.ts
@@ -1,6 +1,6 @@
1import { SearchField } from './search-field.type'; 1import { SearchField } from './search-field.type'
2 2
3export interface Search { 3export interface Search {
4 field: SearchField; 4 field: SearchField
5 value: string; 5 value: string
6} 6}
diff --git a/client/src/app/shared/search/search.service.ts b/client/src/app/shared/search/search.service.ts
index 717a7fa50..0480b46bd 100644
--- a/client/src/app/shared/search/search.service.ts
+++ b/client/src/app/shared/search/search.service.ts
@@ -1,18 +1,18 @@
1import { Injectable } from '@angular/core'; 1import { Injectable } from '@angular/core'
2import { Subject } from 'rxjs/Subject'; 2import { Subject } from 'rxjs/Subject'
3import { ReplaySubject } from 'rxjs/ReplaySubject'; 3import { ReplaySubject } from 'rxjs/ReplaySubject'
4 4
5import { Search } from './search.model'; 5import { Search } from './search.model'
6 6
7// This class is needed to communicate between videos/ and search component 7// This class is needed to communicate between videos/ and search component
8// Remove it when we'll be able to subscribe to router changes 8// Remove it when we'll be able to subscribe to router changes
9@Injectable() 9@Injectable()
10export class SearchService { 10export class SearchService {
11 searchUpdated: Subject<Search>; 11 searchUpdated: Subject<Search>
12 updateSearch: Subject<Search>; 12 updateSearch: Subject<Search>
13 13
14 constructor() { 14 constructor () {
15 this.updateSearch = new Subject<Search>(); 15 this.updateSearch = new Subject<Search>()
16 this.searchUpdated = new ReplaySubject<Search>(1); 16 this.searchUpdated = new ReplaySubject<Search>(1)
17 } 17 }
18} 18}
diff --git a/client/src/app/shared/shared.module.ts b/client/src/app/shared/shared.module.ts
index 8ffaf964b..99b51aa4e 100644
--- a/client/src/app/shared/shared.module.ts
+++ b/client/src/app/shared/shared.module.ts
@@ -1,23 +1,23 @@
1import { NgModule } from '@angular/core'; 1import { NgModule } from '@angular/core'
2import { CommonModule } from '@angular/common'; 2import { CommonModule } from '@angular/common'
3import { HttpModule } from '@angular/http'; 3import { HttpModule } from '@angular/http'
4import { FormsModule, ReactiveFormsModule } from '@angular/forms'; 4import { FormsModule, ReactiveFormsModule } from '@angular/forms'
5import { RouterModule } from '@angular/router'; 5import { RouterModule } from '@angular/router'
6 6
7import { BytesPipe } from 'angular-pipes/src/math/bytes.pipe'; 7import { BytesPipe } from 'angular-pipes/src/math/bytes.pipe'
8import { KeysPipe } from 'angular-pipes/src/object/keys.pipe'; 8import { KeysPipe } from 'angular-pipes/src/object/keys.pipe'
9import { BsDropdownModule } from 'ngx-bootstrap/dropdown'; 9import { BsDropdownModule } from 'ngx-bootstrap/dropdown'
10import { ProgressbarModule } from 'ngx-bootstrap/progressbar'; 10import { ProgressbarModule } from 'ngx-bootstrap/progressbar'
11import { PaginationModule } from 'ngx-bootstrap/pagination'; 11import { PaginationModule } from 'ngx-bootstrap/pagination'
12import { ModalModule } from 'ngx-bootstrap/modal'; 12import { ModalModule } from 'ngx-bootstrap/modal'
13import { FileUploadModule } from 'ng2-file-upload/ng2-file-upload'; 13import { FileUploadModule } from 'ng2-file-upload/ng2-file-upload'
14import { Ng2SmartTableModule } from 'ng2-smart-table'; 14import { Ng2SmartTableModule } from 'ng2-smart-table'
15 15
16import { AUTH_HTTP_PROVIDERS } from './auth'; 16import { AUTH_HTTP_PROVIDERS } from './auth'
17import { RestExtractor, RestService } from './rest'; 17import { RestExtractor, RestService } from './rest'
18import { SearchComponent, SearchService } from './search'; 18import { SearchComponent, SearchService } from './search'
19import { UserService } from './users'; 19import { UserService } from './users'
20import { VideoAbuseService } from './video-abuse'; 20import { VideoAbuseService } from './video-abuse'
21 21
22@NgModule({ 22@NgModule({
23 imports: [ 23 imports: [
diff --git a/client/src/app/shared/users/index.ts b/client/src/app/shared/users/index.ts
index ff009e89b..7b5a67bc7 100644
--- a/client/src/app/shared/users/index.ts
+++ b/client/src/app/shared/users/index.ts
@@ -1,2 +1,2 @@
1export * from './user.model'; 1export * from './user.model'
2export * from './user.service'; 2export * from './user.service'
diff --git a/client/src/app/shared/users/user.model.ts b/client/src/app/shared/users/user.model.ts
index d4695ab67..1c2b481e3 100644
--- a/client/src/app/shared/users/user.model.ts
+++ b/client/src/app/shared/users/user.model.ts
@@ -1,33 +1,33 @@
1import { User as UserServerModel, UserRole } from '../../../../../shared'; 1import { User as UserServerModel, UserRole } from '../../../../../shared'
2 2
3export class User implements UserServerModel { 3export class User implements UserServerModel {
4 id: number; 4 id: number
5 username: string; 5 username: string
6 email: string; 6 email: string
7 role: UserRole; 7 role: UserRole
8 displayNSFW: boolean; 8 displayNSFW: boolean
9 createdAt: Date; 9 createdAt: Date
10 10
11 constructor(hash: { 11 constructor (hash: {
12 id: number, 12 id: number,
13 username: string, 13 username: string,
14 email: string, 14 email: string,
15 role: UserRole, 15 role: UserRole,
16 displayNSFW?: boolean, 16 displayNSFW?: boolean,
17 createdAt?: Date, 17 createdAt?: Date
18 }) { 18 }) {
19 this.id = hash.id; 19 this.id = hash.id
20 this.username = hash.username; 20 this.username = hash.username
21 this.email = hash.email; 21 this.email = hash.email
22 this.role = hash.role; 22 this.role = hash.role
23 this.displayNSFW = hash.displayNSFW; 23 this.displayNSFW = hash.displayNSFW
24 24
25 if (hash.createdAt) { 25 if (hash.createdAt) {
26 this.createdAt = hash.createdAt; 26 this.createdAt = hash.createdAt
27 } 27 }
28 } 28 }
29 29
30 isAdmin() { 30 isAdmin () {
31 return this.role === 'admin'; 31 return this.role === 'admin'
32 } 32 }
33} 33}
diff --git a/client/src/app/shared/users/user.service.ts b/client/src/app/shared/users/user.service.ts
index f1265be0a..e956df5b1 100644
--- a/client/src/app/shared/users/user.service.ts
+++ b/client/src/app/shared/users/user.service.ts
@@ -1,58 +1,58 @@
1import { Injectable } from '@angular/core'; 1import { Injectable } from '@angular/core'
2import { Http } from '@angular/http'; 2import { Http } from '@angular/http'
3import 'rxjs/add/operator/catch'; 3import 'rxjs/add/operator/catch'
4import 'rxjs/add/operator/map'; 4import 'rxjs/add/operator/map'
5 5
6import { AuthService } from '../../core'; 6import { AuthService } from '../../core'
7import { AuthHttp } from '../auth'; 7import { AuthHttp } from '../auth'
8import { RestExtractor } from '../rest'; 8import { RestExtractor } from '../rest'
9 9
10@Injectable() 10@Injectable()
11export class UserService { 11export class UserService {
12 static BASE_USERS_URL = API_URL + '/api/v1/users/'; 12 static BASE_USERS_URL = API_URL + '/api/v1/users/'
13 13
14 constructor( 14 constructor (
15 private http: Http, 15 private http: Http,
16 private authHttp: AuthHttp, 16 private authHttp: AuthHttp,
17 private authService: AuthService, 17 private authService: AuthService,
18 private restExtractor: RestExtractor 18 private restExtractor: RestExtractor
19 ) {} 19 ) {}
20 20
21 checkTokenValidity() { 21 checkTokenValidity () {
22 const url = UserService.BASE_USERS_URL + 'me'; 22 const url = UserService.BASE_USERS_URL + 'me'
23 23
24 // AuthHttp will redirect us to the login page if the oken is not valid anymore 24 // AuthHttp will redirect us to the login page if the oken is not valid anymore
25 this.authHttp.get(url).subscribe(() => { ; }); 25 this.authHttp.get(url).subscribe()
26 } 26 }
27 27
28 changePassword(newPassword: string) { 28 changePassword (newPassword: string) {
29 const url = UserService.BASE_USERS_URL + this.authService.getUser().id; 29 const url = UserService.BASE_USERS_URL + this.authService.getUser().id
30 const body = { 30 const body = {
31 password: newPassword 31 password: newPassword
32 }; 32 }
33 33
34 return this.authHttp.put(url, body) 34 return this.authHttp.put(url, body)
35 .map(this.restExtractor.extractDataBool) 35 .map(this.restExtractor.extractDataBool)
36 .catch((res) => this.restExtractor.handleError(res)); 36 .catch((res) => this.restExtractor.handleError(res))
37 } 37 }
38 38
39 updateDetails(details: { displayNSFW: boolean }) { 39 updateDetails (details: { displayNSFW: boolean }) {
40 const url = UserService.BASE_USERS_URL + this.authService.getUser().id; 40 const url = UserService.BASE_USERS_URL + this.authService.getUser().id
41 41
42 return this.authHttp.put(url, details) 42 return this.authHttp.put(url, details)
43 .map(this.restExtractor.extractDataBool) 43 .map(this.restExtractor.extractDataBool)
44 .catch((res) => this.restExtractor.handleError(res)); 44 .catch((res) => this.restExtractor.handleError(res))
45 } 45 }
46 46
47 signup(username: string, password: string, email: string) { 47 signup (username: string, password: string, email: string) {
48 const body = { 48 const body = {
49 username, 49 username,
50 email, 50 email,
51 password 51 password
52 }; 52 }
53 53
54 return this.http.post(UserService.BASE_USERS_URL + 'register', body) 54 return this.http.post(UserService.BASE_USERS_URL + 'register', body)
55 .map(this.restExtractor.extractDataBool) 55 .map(this.restExtractor.extractDataBool)
56 .catch(this.restExtractor.handleError); 56 .catch(this.restExtractor.handleError)
57 } 57 }
58} 58}
diff --git a/client/src/app/shared/utils.ts b/client/src/app/shared/utils.ts
index 5ab41fe5a..832311f89 100644
--- a/client/src/app/shared/utils.ts
+++ b/client/src/app/shared/utils.ts
@@ -1,12 +1,12 @@
1import { DatePipe } from '@angular/common'; 1import { DatePipe } from '@angular/common'
2 2
3export class Utils { 3export class Utils {
4 4
5 static dateToHuman(date: String) { 5 static dateToHuman (date: String) {
6 return new DatePipe('en').transform(date, 'medium'); 6 return new DatePipe('en').transform(date, 'medium')
7 } 7 }
8 8
9 static getRowDeleteButton() { 9 static getRowDeleteButton () {
10 return '<span class="glyphicon glyphicon-remove glyphicon-black"></span>'; 10 return '<span class="glyphicon glyphicon-remove glyphicon-black"></span>'
11 } 11 }
12} 12}
diff --git a/client/src/app/shared/video-abuse/index.ts b/client/src/app/shared/video-abuse/index.ts
index 563533ba5..7cf24c87d 100644
--- a/client/src/app/shared/video-abuse/index.ts
+++ b/client/src/app/shared/video-abuse/index.ts
@@ -1,2 +1,2 @@
1export * from './video-abuse.service'; 1export * from './video-abuse.service'
2export * from './video-abuse.model'; 2export * from './video-abuse.model'
diff --git a/client/src/app/shared/video-abuse/video-abuse.model.ts b/client/src/app/shared/video-abuse/video-abuse.model.ts
index bb0373027..49c87d6b8 100644
--- a/client/src/app/shared/video-abuse/video-abuse.model.ts
+++ b/client/src/app/shared/video-abuse/video-abuse.model.ts
@@ -1,8 +1,8 @@
1export interface VideoAbuse { 1export interface VideoAbuse {
2 id: string; 2 id: string
3 reason: string; 3 reason: string
4 reporterPodHost: string; 4 reporterPodHost: string
5 reporterUsername: string; 5 reporterUsername: string
6 videoId: string; 6 videoId: string
7 createdAt: Date; 7 createdAt: Date
8} 8}
diff --git a/client/src/app/shared/video-abuse/video-abuse.service.ts b/client/src/app/shared/video-abuse/video-abuse.service.ts
index da7b2ef8a..4317f9353 100644
--- a/client/src/app/shared/video-abuse/video-abuse.service.ts
+++ b/client/src/app/shared/video-abuse/video-abuse.service.ts
@@ -1,42 +1,42 @@
1import { Injectable } from '@angular/core'; 1import { Injectable } from '@angular/core'
2import { Http } from '@angular/http'; 2import { Http } from '@angular/http'
3import { Observable } from 'rxjs/Observable'; 3import { Observable } from 'rxjs/Observable'
4import 'rxjs/add/operator/catch'; 4import 'rxjs/add/operator/catch'
5import 'rxjs/add/operator/map'; 5import 'rxjs/add/operator/map'
6 6
7import { AuthService } from '../core'; 7import { AuthService } from '../core'
8import { AuthHttp } from '../auth'; 8import { AuthHttp } from '../auth'
9import { RestDataSource, RestExtractor, ResultList } from '../rest'; 9import { RestDataSource, RestExtractor, ResultList } from '../rest'
10import { VideoAbuse } from './video-abuse.model'; 10import { VideoAbuse } from './video-abuse.model'
11 11
12@Injectable() 12@Injectable()
13export class VideoAbuseService { 13export class VideoAbuseService {
14 private static BASE_VIDEO_ABUSE_URL = API_URL + '/api/v1/videos/'; 14 private static BASE_VIDEO_ABUSE_URL = API_URL + '/api/v1/videos/'
15 15
16 constructor( 16 constructor (
17 private authHttp: AuthHttp, 17 private authHttp: AuthHttp,
18 private restExtractor: RestExtractor 18 private restExtractor: RestExtractor
19 ) {} 19 ) {}
20 20
21 getDataSource() { 21 getDataSource () {
22 return new RestDataSource(this.authHttp, VideoAbuseService.BASE_VIDEO_ABUSE_URL + 'abuse'); 22 return new RestDataSource(this.authHttp, VideoAbuseService.BASE_VIDEO_ABUSE_URL + 'abuse')
23 } 23 }
24 24
25 reportVideo(id: string, reason: string) { 25 reportVideo (id: string, reason: string) {
26 const body = { 26 const body = {
27 reason 27 reason
28 }; 28 }
29 const url = VideoAbuseService.BASE_VIDEO_ABUSE_URL + id + '/abuse'; 29 const url = VideoAbuseService.BASE_VIDEO_ABUSE_URL + id + '/abuse'
30 30
31 return this.authHttp.post(url, body) 31 return this.authHttp.post(url, body)
32 .map(this.restExtractor.extractDataBool) 32 .map(this.restExtractor.extractDataBool)
33 .catch((res) => this.restExtractor.handleError(res)); 33 .catch((res) => this.restExtractor.handleError(res))
34 } 34 }
35 35
36 private extractVideoAbuses(result: ResultList) { 36 private extractVideoAbuses (result: ResultList) {
37 const videoAbuses: VideoAbuse[] = result.data; 37 const videoAbuses: VideoAbuse[] = result.data
38 const totalVideoAbuses = result.total; 38 const totalVideoAbuses = result.total
39 39
40 return { videoAbuses, totalVideoAbuses }; 40 return { videoAbuses, totalVideoAbuses }
41 } 41 }
42} 42}
diff --git a/client/src/app/signup/index.ts b/client/src/app/signup/index.ts
index 1f4290ab5..b0aca9723 100644
--- a/client/src/app/signup/index.ts
+++ b/client/src/app/signup/index.ts
@@ -1,3 +1,3 @@
1export * from './signup-routing.module'; 1export * from './signup-routing.module'
2export * from './signup.component'; 2export * from './signup.component'
3export * from './signup.module'; 3export * from './signup.module'
diff --git a/client/src/app/signup/signup-routing.module.ts b/client/src/app/signup/signup-routing.module.ts
index 367eed90c..122d6c976 100644
--- a/client/src/app/signup/signup-routing.module.ts
+++ b/client/src/app/signup/signup-routing.module.ts
@@ -1,7 +1,7 @@
1import { NgModule } from '@angular/core'; 1import { NgModule } from '@angular/core'
2import { RouterModule, Routes } from '@angular/router'; 2import { RouterModule, Routes } from '@angular/router'
3 3
4import { SignupComponent } from './signup.component'; 4import { SignupComponent } from './signup.component'
5 5
6const signupRoutes: Routes = [ 6const signupRoutes: Routes = [
7 { 7 {
@@ -13,7 +13,7 @@ const signupRoutes: Routes = [
13 } 13 }
14 } 14 }
15 } 15 }
16]; 16]
17 17
18@NgModule({ 18@NgModule({
19 imports: [ RouterModule.forChild(signupRoutes) ], 19 imports: [ RouterModule.forChild(signupRoutes) ],
diff --git a/client/src/app/signup/signup.component.ts b/client/src/app/signup/signup.component.ts
index 85f93793b..bcbc8ded3 100644
--- a/client/src/app/signup/signup.component.ts
+++ b/client/src/app/signup/signup.component.ts
@@ -1,72 +1,72 @@
1import { Component, OnInit } from '@angular/core'; 1import { Component, OnInit } from '@angular/core'
2import { FormBuilder, FormGroup, Validators } from '@angular/forms'; 2import { FormBuilder, FormGroup, Validators } from '@angular/forms'
3import { Router } from '@angular/router'; 3import { Router } from '@angular/router'
4 4
5import { NotificationsService } from 'angular2-notifications'; 5import { NotificationsService } from 'angular2-notifications'
6 6
7import { AuthService } from '../core'; 7import { AuthService } from '../core'
8import { 8import {
9 FormReactive, 9 FormReactive,
10 UserService, 10 UserService,
11 USER_USERNAME, 11 USER_USERNAME,
12 USER_EMAIL, 12 USER_EMAIL,
13 USER_PASSWORD 13 USER_PASSWORD
14} from '../shared'; 14} from '../shared'
15 15
16@Component({ 16@Component({
17 selector: 'my-signup', 17 selector: 'my-signup',
18 templateUrl: './signup.component.html' 18 templateUrl: './signup.component.html'
19}) 19})
20export class SignupComponent extends FormReactive implements OnInit { 20export class SignupComponent extends FormReactive implements OnInit {
21 error: string = null; 21 error: string = null
22 22
23 form: FormGroup; 23 form: FormGroup
24 formErrors = { 24 formErrors = {
25 'username': '', 25 'username': '',
26 'email': '', 26 'email': '',
27 'password': '' 27 'password': ''
28 }; 28 }
29 validationMessages = { 29 validationMessages = {
30 'username': USER_USERNAME.MESSAGES, 30 'username': USER_USERNAME.MESSAGES,
31 'email': USER_EMAIL.MESSAGES, 31 'email': USER_EMAIL.MESSAGES,
32 'password': USER_PASSWORD.MESSAGES, 32 'password': USER_PASSWORD.MESSAGES
33 }; 33 }
34 34
35 constructor( 35 constructor (
36 private formBuilder: FormBuilder, 36 private formBuilder: FormBuilder,
37 private router: Router, 37 private router: Router,
38 private notificationsService: NotificationsService, 38 private notificationsService: NotificationsService,
39 private userService: UserService 39 private userService: UserService
40 ) { 40 ) {
41 super(); 41 super()
42 } 42 }
43 43
44 buildForm() { 44 buildForm () {
45 this.form = this.formBuilder.group({ 45 this.form = this.formBuilder.group({
46 username: [ '', USER_USERNAME.VALIDATORS ], 46 username: [ '', USER_USERNAME.VALIDATORS ],
47 email: [ '', USER_EMAIL.VALIDATORS ], 47 email: [ '', USER_EMAIL.VALIDATORS ],
48 password: [ '', USER_PASSWORD.VALIDATORS ], 48 password: [ '', USER_PASSWORD.VALIDATORS ]
49 }); 49 })
50 50
51 this.form.valueChanges.subscribe(data => this.onValueChanged(data)); 51 this.form.valueChanges.subscribe(data => this.onValueChanged(data))
52 } 52 }
53 53
54 ngOnInit() { 54 ngOnInit () {
55 this.buildForm(); 55 this.buildForm()
56 } 56 }
57 57
58 signup() { 58 signup () {
59 this.error = null; 59 this.error = null
60 60
61 const { username, password, email } = this.form.value; 61 const { username, password, email } = this.form.value
62 62
63 this.userService.signup(username, password, email).subscribe( 63 this.userService.signup(username, password, email).subscribe(
64 () => { 64 () => {
65 this.notificationsService.success('Success', `Registration for ${username} complete.`); 65 this.notificationsService.success('Success', `Registration for ${username} complete.`)
66 this.router.navigate([ '/videos/list' ]); 66 this.router.navigate([ '/videos/list' ])
67 }, 67 },
68 68
69 err => this.error = err.text 69 err => this.error = err.text
70 ); 70 )
71 } 71 }
72} 72}
diff --git a/client/src/app/signup/signup.module.ts b/client/src/app/signup/signup.module.ts
index acb7e5515..61560ddcf 100644
--- a/client/src/app/signup/signup.module.ts
+++ b/client/src/app/signup/signup.module.ts
@@ -1,8 +1,8 @@
1import { NgModule } from '@angular/core'; 1import { NgModule } from '@angular/core'
2 2
3import { SignupRoutingModule } from './signup-routing.module'; 3import { SignupRoutingModule } from './signup-routing.module'
4import { SignupComponent } from './signup.component'; 4import { SignupComponent } from './signup.component'
5import { SharedModule } from '../shared'; 5import { SharedModule } from '../shared'
6 6
7@NgModule({ 7@NgModule({
8 imports: [ 8 imports: [
diff --git a/client/src/app/videos/index.ts b/client/src/app/videos/index.ts
index 5158a23f8..83edcc758 100644
--- a/client/src/app/videos/index.ts
+++ b/client/src/app/videos/index.ts
@@ -1,7 +1,7 @@
1export * from './shared'; 1export * from './shared'
2export * from './video-edit'; 2export * from './video-edit'
3export * from './video-list'; 3export * from './video-list'
4export * from './video-watch'; 4export * from './video-watch'
5export * from './videos-routing.module'; 5export * from './videos-routing.module'
6export * from './videos.component'; 6export * from './videos.component'
7export * from './videos.module'; 7export * from './videos.module'
diff --git a/client/src/app/videos/shared/index.ts b/client/src/app/videos/shared/index.ts
index a68491022..0fa14f641 100644
--- a/client/src/app/videos/shared/index.ts
+++ b/client/src/app/videos/shared/index.ts
@@ -1,4 +1,4 @@
1export * from './sort-field.type'; 1export * from './sort-field.type'
2export * from './rate-type.type'; 2export * from './rate-type.type'
3export * from './video.model'; 3export * from './video.model'
4export * from './video.service'; 4export * from './video.service'
diff --git a/client/src/app/videos/shared/rate-type.type.ts b/client/src/app/videos/shared/rate-type.type.ts
index 88034d1ff..20eea3ae5 100644
--- a/client/src/app/videos/shared/rate-type.type.ts
+++ b/client/src/app/videos/shared/rate-type.type.ts
@@ -1 +1 @@
export type RateType = 'like' | 'dislike'; export type RateType = 'like' | 'dislike'
diff --git a/client/src/app/videos/shared/sort-field.type.ts b/client/src/app/videos/shared/sort-field.type.ts
index 6cc598d8b..776f360f8 100644
--- a/client/src/app/videos/shared/sort-field.type.ts
+++ b/client/src/app/videos/shared/sort-field.type.ts
@@ -2,5 +2,4 @@ export type SortField = 'name' | '-name'
2 | 'duration' | '-duration' 2 | 'duration' | '-duration'
3 | 'createdAt' | '-createdAt' 3 | 'createdAt' | '-createdAt'
4 | 'views' | '-views' 4 | 'views' | '-views'
5 | 'likes' | '-likes'; 5 | 'likes' | '-likes'
6
diff --git a/client/src/app/videos/shared/video.model.ts b/client/src/app/videos/shared/video.model.ts
index e897eb175..f5e16fc13 100644
--- a/client/src/app/videos/shared/video.model.ts
+++ b/client/src/app/videos/shared/video.model.ts
@@ -1,56 +1,56 @@
1import { Video as VideoServerModel } from '../../../../../shared'; 1import { Video as VideoServerModel } from '../../../../../shared'
2import { User } from '../../shared'; 2import { User } from '../../shared'
3 3
4export class Video implements VideoServerModel { 4export class Video implements VideoServerModel {
5 author: string; 5 author: string
6 by: string; 6 by: string
7 createdAt: Date; 7 createdAt: Date
8 categoryLabel: string; 8 categoryLabel: string
9 category: number; 9 category: number
10 licenceLabel: string; 10 licenceLabel: string
11 licence: number; 11 licence: number
12 languageLabel: string; 12 languageLabel: string
13 language: number; 13 language: number
14 description: string; 14 description: string
15 duration: number; 15 duration: number
16 durationLabel: string; 16 durationLabel: string
17 id: string; 17 id: string
18 isLocal: boolean; 18 isLocal: boolean
19 magnetUri: string; 19 magnetUri: string
20 name: string; 20 name: string
21 podHost: string; 21 podHost: string
22 tags: string[]; 22 tags: string[]
23 thumbnailPath: string; 23 thumbnailPath: string
24 thumbnailUrl: string; 24 thumbnailUrl: string
25 views: number; 25 views: number
26 likes: number; 26 likes: number
27 dislikes: number; 27 dislikes: number
28 nsfw: boolean; 28 nsfw: boolean
29 29
30 private static createByString(author: string, podHost: string) { 30 private static createByString (author: string, podHost: string) {
31 return author + '@' + podHost; 31 return author + '@' + podHost
32 } 32 }
33 33
34 private static createDurationString(duration: number) { 34 private static createDurationString (duration: number) {
35 const minutes = Math.floor(duration / 60); 35 const minutes = Math.floor(duration / 60)
36 const seconds = duration % 60; 36 const seconds = duration % 60
37 const minutes_padding = minutes >= 10 ? '' : '0'; 37 const minutesPadding = minutes >= 10 ? '' : '0'
38 const seconds_padding = seconds >= 10 ? '' : '0'; 38 const secondsPadding = seconds >= 10 ? '' : '0'
39 39
40 return minutes_padding + minutes.toString() + ':' + seconds_padding + seconds.toString(); 40 return minutesPadding + minutes.toString() + ':' + secondsPadding + seconds.toString()
41 } 41 }
42 42
43 constructor(hash: { 43 constructor (hash: {
44 author: string, 44 author: string,
45 createdAt: string, 45 createdAt: string,
46 categoryLabel: string, 46 categoryLabel: string,
47 category: number, 47 category: number,
48 licenceLabel: string, 48 licenceLabel: string,
49 licence: number, 49 licence: number,
50 languageLabel: string; 50 languageLabel: string
51 language: number; 51 language: number
52 description: string, 52 description: string,
53 duration: number; 53 duration: number
54 id: string, 54 id: string,
55 isLocal: boolean, 55 isLocal: boolean,
56 magnetUri: string, 56 magnetUri: string,
@@ -63,57 +63,57 @@ export class Video implements VideoServerModel {
63 dislikes: number, 63 dislikes: number,
64 nsfw: boolean 64 nsfw: boolean
65 }) { 65 }) {
66 this.author = hash.author; 66 this.author = hash.author
67 this.createdAt = new Date(hash.createdAt); 67 this.createdAt = new Date(hash.createdAt)
68 this.categoryLabel = hash.categoryLabel; 68 this.categoryLabel = hash.categoryLabel
69 this.category = hash.category; 69 this.category = hash.category
70 this.licenceLabel = hash.licenceLabel; 70 this.licenceLabel = hash.licenceLabel
71 this.licence = hash.licence; 71 this.licence = hash.licence
72 this.languageLabel = hash.languageLabel; 72 this.languageLabel = hash.languageLabel
73 this.language = hash.language; 73 this.language = hash.language
74 this.description = hash.description; 74 this.description = hash.description
75 this.duration = hash.duration; 75 this.duration = hash.duration
76 this.durationLabel = Video.createDurationString(hash.duration); 76 this.durationLabel = Video.createDurationString(hash.duration)
77 this.id = hash.id; 77 this.id = hash.id
78 this.isLocal = hash.isLocal; 78 this.isLocal = hash.isLocal
79 this.magnetUri = hash.magnetUri; 79 this.magnetUri = hash.magnetUri
80 this.name = hash.name; 80 this.name = hash.name
81 this.podHost = hash.podHost; 81 this.podHost = hash.podHost
82 this.tags = hash.tags; 82 this.tags = hash.tags
83 this.thumbnailPath = hash.thumbnailPath; 83 this.thumbnailPath = hash.thumbnailPath
84 this.thumbnailUrl = API_URL + hash.thumbnailPath; 84 this.thumbnailUrl = API_URL + hash.thumbnailPath
85 this.views = hash.views; 85 this.views = hash.views
86 this.likes = hash.likes; 86 this.likes = hash.likes
87 this.dislikes = hash.dislikes; 87 this.dislikes = hash.dislikes
88 this.nsfw = hash.nsfw; 88 this.nsfw = hash.nsfw
89 89
90 this.by = Video.createByString(hash.author, hash.podHost); 90 this.by = Video.createByString(hash.author, hash.podHost)
91 } 91 }
92 92
93 isRemovableBy(user) { 93 isRemovableBy (user) {
94 return user && this.isLocal === true && (this.author === user.username || user.isAdmin() === true); 94 return user && this.isLocal === true && (this.author === user.username || user.isAdmin() === true)
95 } 95 }
96 96
97 isBlackistableBy(user) { 97 isBlackistableBy (user) {
98 return user && user.isAdmin() === true && this.isLocal === false; 98 return user && user.isAdmin() === true && this.isLocal === false
99 } 99 }
100 100
101 isUpdatableBy(user) { 101 isUpdatableBy (user) {
102 return user && this.isLocal === true && user.username === this.author; 102 return user && this.isLocal === true && user.username === this.author
103 } 103 }
104 104
105 isVideoNSFWForUser(user: User) { 105 isVideoNSFWForUser (user: User) {
106 // If the video is NSFW and the user is not logged in, or the user does not want to display NSFW videos... 106 // If the video is NSFW and the user is not logged in, or the user does not want to display NSFW videos...
107 return (this.nsfw && (!user || user.displayNSFW === false)); 107 return (this.nsfw && (!user || user.displayNSFW === false))
108 } 108 }
109 109
110 patch(values: Object) { 110 patch (values: Object) {
111 Object.keys(values).forEach((key) => { 111 Object.keys(values).forEach((key) => {
112 this[key] = values[key]; 112 this[key] = values[key]
113 }); 113 })
114 } 114 }
115 115
116 toJSON() { 116 toJSON () {
117 return { 117 return {
118 author: this.author, 118 author: this.author,
119 createdAt: this.createdAt, 119 createdAt: this.createdAt,
@@ -133,6 +133,6 @@ export class Video implements VideoServerModel {
133 likes: this.likes, 133 likes: this.likes,
134 dislikes: this.dislikes, 134 dislikes: this.dislikes,
135 nsfw: this.nsfw 135 nsfw: this.nsfw
136 }; 136 }
137 } 137 }
138} 138}
diff --git a/client/src/app/videos/shared/video.service.ts b/client/src/app/videos/shared/video.service.ts
index a53ea1064..a4e3d16df 100644
--- a/client/src/app/videos/shared/video.service.ts
+++ b/client/src/app/videos/shared/video.service.ts
@@ -1,13 +1,13 @@
1import { Injectable } from '@angular/core'; 1import { Injectable } from '@angular/core'
2import { Http, Headers, RequestOptions } from '@angular/http'; 2import { Http, Headers, RequestOptions } from '@angular/http'
3import { Observable } from 'rxjs/Observable'; 3import { Observable } from 'rxjs/Observable'
4import 'rxjs/add/operator/catch'; 4import 'rxjs/add/operator/catch'
5import 'rxjs/add/operator/map'; 5import 'rxjs/add/operator/map'
6 6
7import { Search } from '../../shared'; 7import { Search } from '../../shared'
8import { SortField } from './sort-field.type'; 8import { SortField } from './sort-field.type'
9import { RateType } from './rate-type.type'; 9import { RateType } from './rate-type.type'
10import { AuthService } from '../../core'; 10import { AuthService } from '../../core'
11import { 11import {
12 AuthHttp, 12 AuthHttp,
13 RestExtractor, 13 RestExtractor,
@@ -15,18 +15,18 @@ import {
15 RestService, 15 RestService,
16 ResultList, 16 ResultList,
17 UserService 17 UserService
18} from '../../shared'; 18} from '../../shared'
19import { Video } from './video.model'; 19import { Video } from './video.model'
20 20
21@Injectable() 21@Injectable()
22export class VideoService { 22export class VideoService {
23 private static BASE_VIDEO_URL = API_URL + '/api/v1/videos/'; 23 private static BASE_VIDEO_URL = API_URL + '/api/v1/videos/'
24 24
25 videoCategories: Array<{ id: number, label: string }> = []; 25 videoCategories: Array<{ id: number, label: string }> = []
26 videoLicences: Array<{ id: number, label: string }> = []; 26 videoLicences: Array<{ id: number, label: string }> = []
27 videoLanguages: Array<{ id: number, label: string }> = []; 27 videoLanguages: Array<{ id: number, label: string }> = []
28 28
29 constructor( 29 constructor (
30 private authService: AuthService, 30 private authService: AuthService,
31 private authHttp: AuthHttp, 31 private authHttp: AuthHttp,
32 private http: Http, 32 private http: Http,
@@ -34,54 +34,54 @@ export class VideoService {
34 private restService: RestService 34 private restService: RestService
35 ) {} 35 ) {}
36 36
37 loadVideoCategories() { 37 loadVideoCategories () {
38 return this.http.get(VideoService.BASE_VIDEO_URL + 'categories') 38 return this.http.get(VideoService.BASE_VIDEO_URL + 'categories')
39 .map(this.restExtractor.extractDataGet) 39 .map(this.restExtractor.extractDataGet)
40 .subscribe(data => { 40 .subscribe(data => {
41 Object.keys(data).forEach(categoryKey => { 41 Object.keys(data).forEach(categoryKey => {
42 this.videoCategories.push({ 42 this.videoCategories.push({
43 id: parseInt(categoryKey), 43 id: parseInt(categoryKey, 10),
44 label: data[categoryKey] 44 label: data[categoryKey]
45 }); 45 })
46 }); 46 })
47 }); 47 })
48 } 48 }
49 49
50 loadVideoLicences() { 50 loadVideoLicences () {
51 return this.http.get(VideoService.BASE_VIDEO_URL + 'licences') 51 return this.http.get(VideoService.BASE_VIDEO_URL + 'licences')
52 .map(this.restExtractor.extractDataGet) 52 .map(this.restExtractor.extractDataGet)
53 .subscribe(data => { 53 .subscribe(data => {
54 Object.keys(data).forEach(licenceKey => { 54 Object.keys(data).forEach(licenceKey => {
55 this.videoLicences.push({ 55 this.videoLicences.push({
56 id: parseInt(licenceKey), 56 id: parseInt(licenceKey, 10),
57 label: data[licenceKey] 57 label: data[licenceKey]
58 }); 58 })
59 }); 59 })
60 }); 60 })
61 } 61 }
62 62
63 loadVideoLanguages() { 63 loadVideoLanguages () {
64 return this.http.get(VideoService.BASE_VIDEO_URL + 'languages') 64 return this.http.get(VideoService.BASE_VIDEO_URL + 'languages')
65 .map(this.restExtractor.extractDataGet) 65 .map(this.restExtractor.extractDataGet)
66 .subscribe(data => { 66 .subscribe(data => {
67 Object.keys(data).forEach(languageKey => { 67 Object.keys(data).forEach(languageKey => {
68 this.videoLanguages.push({ 68 this.videoLanguages.push({
69 id: parseInt(languageKey), 69 id: parseInt(languageKey, 10),
70 label: data[languageKey] 70 label: data[languageKey]
71 }); 71 })
72 }); 72 })
73 }); 73 })
74 } 74 }
75 75
76 getVideo(id: string): Observable<Video> { 76 getVideo (id: string): Observable<Video> {
77 return this.http.get(VideoService.BASE_VIDEO_URL + id) 77 return this.http.get(VideoService.BASE_VIDEO_URL + id)
78 .map(this.restExtractor.extractDataGet) 78 .map(this.restExtractor.extractDataGet)
79 .map(video_hash => new Video(video_hash)) 79 .map(videoHash => new Video(videoHash))
80 .catch((res) => this.restExtractor.handleError(res)); 80 .catch((res) => this.restExtractor.handleError(res))
81 } 81 }
82 82
83 updateVideo(video: Video) { 83 updateVideo (video: Video) {
84 const language = video.language ? video.language : null; 84 const language = video.language ? video.language : null
85 85
86 const body = { 86 const body = {
87 name: video.name, 87 name: video.name,
@@ -90,94 +90,94 @@ export class VideoService {
90 language, 90 language,
91 description: video.description, 91 description: video.description,
92 tags: video.tags 92 tags: video.tags
93 }; 93 }
94 94
95 const headers = new Headers({ 'Content-Type': 'application/json' }); 95 const headers = new Headers({ 'Content-Type': 'application/json' })
96 const options = new RequestOptions({ headers: headers }); 96 const options = new RequestOptions({ headers: headers })
97 97
98 return this.authHttp.put(`${VideoService.BASE_VIDEO_URL}/${video.id}`, body, options) 98 return this.authHttp.put(`${VideoService.BASE_VIDEO_URL}/${video.id}`, body, options)
99 .map(this.restExtractor.extractDataBool) 99 .map(this.restExtractor.extractDataBool)
100 .catch(this.restExtractor.handleError); 100 .catch(this.restExtractor.handleError)
101 } 101 }
102 102
103 getVideos(pagination: RestPagination, sort: SortField) { 103 getVideos (pagination: RestPagination, sort: SortField) {
104 const params = this.restService.buildRestGetParams(pagination, sort); 104 const params = this.restService.buildRestGetParams(pagination, sort)
105 105
106 return this.http.get(VideoService.BASE_VIDEO_URL, { search: params }) 106 return this.http.get(VideoService.BASE_VIDEO_URL, { search: params })
107 .map(res => res.json()) 107 .map(res => res.json())
108 .map(this.extractVideos) 108 .map(this.extractVideos)
109 .catch((res) => this.restExtractor.handleError(res)); 109 .catch((res) => this.restExtractor.handleError(res))
110 } 110 }
111 111
112 removeVideo(id: string) { 112 removeVideo (id: string) {
113 return this.authHttp.delete(VideoService.BASE_VIDEO_URL + id) 113 return this.authHttp.delete(VideoService.BASE_VIDEO_URL + id)
114 .map(this.restExtractor.extractDataBool) 114 .map(this.restExtractor.extractDataBool)
115 .catch((res) => this.restExtractor.handleError(res)); 115 .catch((res) => this.restExtractor.handleError(res))
116 } 116 }
117 117
118 searchVideos(search: Search, pagination: RestPagination, sort: SortField) { 118 searchVideos (search: Search, pagination: RestPagination, sort: SortField) {
119 const params = this.restService.buildRestGetParams(pagination, sort); 119 const params = this.restService.buildRestGetParams(pagination, sort)
120 120
121 if (search.field) params.set('field', search.field); 121 if (search.field) params.set('field', search.field)
122 122
123 return this.http.get(VideoService.BASE_VIDEO_URL + 'search/' + encodeURIComponent(search.value), { search: params }) 123 return this.http.get(VideoService.BASE_VIDEO_URL + 'search/' + encodeURIComponent(search.value), { search: params })
124 .map(this.restExtractor.extractDataList) 124 .map(this.restExtractor.extractDataList)
125 .map(this.extractVideos) 125 .map(this.extractVideos)
126 .catch((res) => this.restExtractor.handleError(res)); 126 .catch((res) => this.restExtractor.handleError(res))
127 } 127 }
128 128
129 reportVideo(id: string, reason: string) { 129 reportVideo (id: string, reason: string) {
130 const url = VideoService.BASE_VIDEO_URL + id + '/abuse'; 130 const url = VideoService.BASE_VIDEO_URL + id + '/abuse'
131 const body = { 131 const body = {
132 reason 132 reason
133 }; 133 }
134 134
135 return this.authHttp.post(url, body) 135 return this.authHttp.post(url, body)
136 .map(this.restExtractor.extractDataBool) 136 .map(this.restExtractor.extractDataBool)
137 .catch((res) => this.restExtractor.handleError(res)); 137 .catch((res) => this.restExtractor.handleError(res))
138 } 138 }
139 139
140 setVideoLike(id: string) { 140 setVideoLike (id: string) {
141 return this.setVideoRate(id, 'like'); 141 return this.setVideoRate(id, 'like')
142 } 142 }
143 143
144 setVideoDislike(id: string) { 144 setVideoDislike (id: string) {
145 return this.setVideoRate(id, 'dislike'); 145 return this.setVideoRate(id, 'dislike')
146 } 146 }
147 147
148 getUserVideoRating(id: string) { 148 getUserVideoRating (id: string) {
149 const url = UserService.BASE_USERS_URL + '/me/videos/' + id + '/rating'; 149 const url = UserService.BASE_USERS_URL + '/me/videos/' + id + '/rating'
150 150
151 return this.authHttp.get(url) 151 return this.authHttp.get(url)
152 .map(this.restExtractor.extractDataGet) 152 .map(this.restExtractor.extractDataGet)
153 .catch((res) => this.restExtractor.handleError(res)); 153 .catch((res) => this.restExtractor.handleError(res))
154 } 154 }
155 155
156 blacklistVideo(id: string) { 156 blacklistVideo (id: string) {
157 return this.authHttp.post(VideoService.BASE_VIDEO_URL + id + '/blacklist', {}) 157 return this.authHttp.post(VideoService.BASE_VIDEO_URL + id + '/blacklist', {})
158 .map(this.restExtractor.extractDataBool) 158 .map(this.restExtractor.extractDataBool)
159 .catch((res) => this.restExtractor.handleError(res)); 159 .catch((res) => this.restExtractor.handleError(res))
160 } 160 }
161 161
162 private setVideoRate(id: string, rateType: RateType) { 162 private setVideoRate (id: string, rateType: RateType) {
163 const url = VideoService.BASE_VIDEO_URL + id + '/rate'; 163 const url = VideoService.BASE_VIDEO_URL + id + '/rate'
164 const body = { 164 const body = {
165 rating: rateType 165 rating: rateType
166 }; 166 }
167 167
168 return this.authHttp.put(url, body) 168 return this.authHttp.put(url, body)
169 .map(this.restExtractor.extractDataBool) 169 .map(this.restExtractor.extractDataBool)
170 .catch((res) => this.restExtractor.handleError(res)); 170 .catch((res) => this.restExtractor.handleError(res))
171 } 171 }
172 172
173 private extractVideos(result: ResultList) { 173 private extractVideos (result: ResultList) {
174 const videosJson = result.data; 174 const videosJson = result.data
175 const totalVideos = result.total; 175 const totalVideos = result.total
176 const videos = []; 176 const videos = []
177 for (const videoJson of videosJson) { 177 for (const videoJson of videosJson) {
178 videos.push(new Video(videoJson)); 178 videos.push(new Video(videoJson))
179 } 179 }
180 180
181 return { videos, totalVideos }; 181 return { videos, totalVideos }
182 } 182 }
183} 183}
diff --git a/client/src/app/videos/video-edit/index.ts b/client/src/app/videos/video-edit/index.ts
index 5ce4fb9b1..3b4a9cb87 100644
--- a/client/src/app/videos/video-edit/index.ts
+++ b/client/src/app/videos/video-edit/index.ts
@@ -1,2 +1,2 @@
1export * from './video-add.component'; 1export * from './video-add.component'
2export * from './video-update.component'; 2export * from './video-update.component'
diff --git a/client/src/app/videos/video-edit/video-add.component.ts b/client/src/app/videos/video-edit/video-add.component.ts
index e5eb9a9f4..0653f5ac4 100644
--- a/client/src/app/videos/video-edit/video-add.component.ts
+++ b/client/src/app/videos/video-edit/video-add.component.ts
@@ -1,11 +1,11 @@
1import { Component, ElementRef, OnInit } from '@angular/core'; 1import { Component, ElementRef, OnInit } from '@angular/core'
2import { FormBuilder, FormGroup } from '@angular/forms'; 2import { FormBuilder, FormGroup } from '@angular/forms'
3import { Router } from '@angular/router'; 3import { Router } from '@angular/router'
4 4
5import { FileUploader } from 'ng2-file-upload/ng2-file-upload'; 5import { FileUploader } from 'ng2-file-upload/ng2-file-upload'
6import { NotificationsService } from 'angular2-notifications'; 6import { NotificationsService } from 'angular2-notifications'
7 7
8import { AuthService } from '../../core'; 8import { AuthService } from '../../core'
9import { 9import {
10 FormReactive, 10 FormReactive,
11 VIDEO_NAME, 11 VIDEO_NAME,
@@ -14,8 +14,8 @@ import {
14 VIDEO_LANGUAGE, 14 VIDEO_LANGUAGE,
15 VIDEO_DESCRIPTION, 15 VIDEO_DESCRIPTION,
16 VIDEO_TAGS 16 VIDEO_TAGS
17} from '../../shared'; 17} from '../../shared'
18import { VideoService } from '../shared'; 18import { VideoService } from '../shared'
19 19
20@Component({ 20@Component({
21 selector: 'my-videos-add', 21 selector: 'my-videos-add',
@@ -24,36 +24,36 @@ import { VideoService } from '../shared';
24}) 24})
25 25
26export class VideoAddComponent extends FormReactive implements OnInit { 26export class VideoAddComponent extends FormReactive implements OnInit {
27 tags: string[] = []; 27 tags: string[] = []
28 uploader: FileUploader; 28 uploader: FileUploader
29 videoCategories = []; 29 videoCategories = []
30 videoLicences = []; 30 videoLicences = []
31 videoLanguages = []; 31 videoLanguages = []
32 32
33 tagValidators = VIDEO_TAGS.VALIDATORS; 33 tagValidators = VIDEO_TAGS.VALIDATORS
34 tagValidatorsMessages = VIDEO_TAGS.MESSAGES; 34 tagValidatorsMessages = VIDEO_TAGS.MESSAGES
35 35
36 error: string = null; 36 error: string = null
37 form: FormGroup; 37 form: FormGroup
38 formErrors = { 38 formErrors = {
39 name: '', 39 name: '',
40 category: '', 40 category: '',
41 licence: '', 41 licence: '',
42 language: '', 42 language: '',
43 description: '' 43 description: ''
44 }; 44 }
45 validationMessages = { 45 validationMessages = {
46 name: VIDEO_NAME.MESSAGES, 46 name: VIDEO_NAME.MESSAGES,
47 category: VIDEO_CATEGORY.MESSAGES, 47 category: VIDEO_CATEGORY.MESSAGES,
48 licence: VIDEO_LICENCE.MESSAGES, 48 licence: VIDEO_LICENCE.MESSAGES,
49 language: VIDEO_LANGUAGE.MESSAGES, 49 language: VIDEO_LANGUAGE.MESSAGES,
50 description: VIDEO_DESCRIPTION.MESSAGES 50 description: VIDEO_DESCRIPTION.MESSAGES
51 }; 51 }
52 52
53 // Special error messages 53 // Special error messages
54 fileError = ''; 54 fileError = ''
55 55
56 constructor( 56 constructor (
57 private authService: AuthService, 57 private authService: AuthService,
58 private elementRef: ElementRef, 58 private elementRef: ElementRef,
59 private formBuilder: FormBuilder, 59 private formBuilder: FormBuilder,
@@ -61,18 +61,18 @@ export class VideoAddComponent extends FormReactive implements OnInit {
61 private notificationsService: NotificationsService, 61 private notificationsService: NotificationsService,
62 private videoService: VideoService 62 private videoService: VideoService
63 ) { 63 ) {
64 super(); 64 super()
65 } 65 }
66 66
67 get filename() { 67 get filename () {
68 if (this.uploader.queue.length === 0) { 68 if (this.uploader.queue.length === 0) {
69 return null; 69 return null
70 } 70 }
71 71
72 return this.uploader.queue[0].file.name; 72 return this.uploader.queue[0].file.name
73 } 73 }
74 74
75 buildForm() { 75 buildForm () {
76 this.form = this.formBuilder.group({ 76 this.form = this.formBuilder.group({
77 name: [ '', VIDEO_NAME.VALIDATORS ], 77 name: [ '', VIDEO_NAME.VALIDATORS ],
78 nsfw: [ false ], 78 nsfw: [ false ],
@@ -81,115 +81,106 @@ export class VideoAddComponent extends FormReactive implements OnInit {
81 language: [ '', VIDEO_LANGUAGE.VALIDATORS ], 81 language: [ '', VIDEO_LANGUAGE.VALIDATORS ],
82 description: [ '', VIDEO_DESCRIPTION.VALIDATORS ], 82 description: [ '', VIDEO_DESCRIPTION.VALIDATORS ],
83 tags: [ ''] 83 tags: [ '']
84 }); 84 })
85 85
86 this.form.valueChanges.subscribe(data => this.onValueChanged(data)); 86 this.form.valueChanges.subscribe(data => this.onValueChanged(data))
87 } 87 }
88 88
89 ngOnInit() { 89 ngOnInit () {
90 this.videoCategories = this.videoService.videoCategories; 90 this.videoCategories = this.videoService.videoCategories
91 this.videoLicences = this.videoService.videoLicences; 91 this.videoLicences = this.videoService.videoLicences
92 this.videoLanguages = this.videoService.videoLanguages; 92 this.videoLanguages = this.videoService.videoLanguages
93 93
94 this.uploader = new FileUploader({ 94 this.uploader = new FileUploader({
95 authToken: this.authService.getRequestHeaderValue(), 95 authToken: this.authService.getRequestHeaderValue(),
96 queueLimit: 1, 96 queueLimit: 1,
97 url: API_URL + '/api/v1/videos', 97 url: API_URL + '/api/v1/videos',
98 removeAfterUpload: true 98 removeAfterUpload: true
99 }); 99 })
100 100
101 this.uploader.onBuildItemForm = (item, form) => { 101 this.uploader.onBuildItemForm = (item, form) => {
102 const name = this.form.value['name']; 102 const name = this.form.value['name']
103 const nsfw = this.form.value['nsfw']; 103 const nsfw = this.form.value['nsfw']
104 const category = this.form.value['category']; 104 const category = this.form.value['category']
105 const licence = this.form.value['licence']; 105 const licence = this.form.value['licence']
106 const language = this.form.value['language']; 106 const language = this.form.value['language']
107 const description = this.form.value['description']; 107 const description = this.form.value['description']
108 const tags = this.form.value['tags']; 108 const tags = this.form.value['tags']
109 109
110 form.append('name', name); 110 form.append('name', name)
111 form.append('category', category); 111 form.append('category', category)
112 form.append('nsfw', nsfw); 112 form.append('nsfw', nsfw)
113 form.append('licence', licence); 113 form.append('licence', licence)
114 114
115 // Language is optional 115 // Language is optional
116 if (language) { 116 if (language) {
117 form.append('language', language); 117 form.append('language', language)
118 } 118 }
119 119
120 form.append('description', description); 120 form.append('description', description)
121 121
122 for (let i = 0; i < tags.length; i++) { 122 for (let i = 0; i < tags.length; i++) {
123 form.append(`tags[${i}]`, tags[i]); 123 form.append(`tags[${i}]`, tags[i])
124 } 124 }
125 }; 125 }
126 126
127 this.buildForm(); 127 this.buildForm()
128 } 128 }
129 129
130 checkForm() { 130 checkForm () {
131 this.forceCheck(); 131 this.forceCheck()
132 132
133 if (this.filename === null) { 133 if (this.filename === null) {
134 this.fileError = 'You did not add a file.'; 134 this.fileError = 'You did not add a file.'
135 } 135 }
136 136
137 return this.form.valid === true && this.fileError === ''; 137 return this.form.valid === true && this.fileError === ''
138 } 138 }
139 139
140 fileChanged() { 140 fileChanged () {
141 this.fileError = ''; 141 this.fileError = ''
142 } 142 }
143 143
144 removeFile() { 144 removeFile () {
145 this.uploader.clearQueue(); 145 this.uploader.clearQueue()
146 } 146 }
147 147
148 upload() { 148 upload () {
149 if (this.checkForm() === false) { 149 if (this.checkForm() === false) {
150 return; 150 return
151 } 151 }
152 152
153 const item = this.uploader.queue[0]; 153 const item = this.uploader.queue[0]
154 // TODO: wait for https://github.com/valor-software/ng2-file-upload/pull/242 154 // TODO: wait for https://github.com/valor-software/ng2-file-upload/pull/242
155 item.alias = 'videofile'; 155 item.alias = 'videofile'
156
157 // FIXME: remove
158 // Run detection change for progress bar
159 const interval = setInterval(() => { ; }, 250);
160 156
161 item.onSuccess = () => { 157 item.onSuccess = () => {
162 clearInterval(interval); 158 console.log('Video uploaded.')
163 159 this.notificationsService.success('Success', 'Video uploaded.')
164 console.log('Video uploaded.');
165 this.notificationsService.success('Success', 'Video uploaded.');
166
167 160
168 // Print all the videos once it's finished 161 // Print all the videos once it's finished
169 this.router.navigate(['/videos/list']); 162 this.router.navigate(['/videos/list'])
170 }; 163 }
171 164
172 item.onError = (response: string, status: number) => { 165 item.onError = (response: string, status: number) => {
173 clearInterval(interval);
174
175 // We need to handle manually these cases beceause we use the FileUpload component 166 // We need to handle manually these cases beceause we use the FileUpload component
176 if (status === 400) { 167 if (status === 400) {
177 this.error = response; 168 this.error = response
178 } else if (status === 401) { 169 } else if (status === 401) {
179 this.error = 'Access token was expired, refreshing token...'; 170 this.error = 'Access token was expired, refreshing token...'
180 this.authService.refreshAccessToken().subscribe( 171 this.authService.refreshAccessToken().subscribe(
181 () => { 172 () => {
182 // Update the uploader request header 173 // Update the uploader request header
183 this.uploader.authToken = this.authService.getRequestHeaderValue(); 174 this.uploader.authToken = this.authService.getRequestHeaderValue()
184 this.error += ' access token refreshed. Please retry your request.'; 175 this.error += ' access token refreshed. Please retry your request.'
185 } 176 }
186 ); 177 )
187 } else { 178 } else {
188 this.error = 'Unknow error'; 179 this.error = 'Unknow error'
189 console.error(this.error); 180 console.error(this.error)
190 } 181 }
191 }; 182 }
192 183
193 this.uploader.uploadAll(); 184 this.uploader.uploadAll()
194 } 185 }
195} 186}
diff --git a/client/src/app/videos/video-edit/video-update.component.ts b/client/src/app/videos/video-edit/video-update.component.ts
index 933132cc0..9ee7ca6a8 100644
--- a/client/src/app/videos/video-edit/video-update.component.ts
+++ b/client/src/app/videos/video-edit/video-update.component.ts
@@ -1,11 +1,11 @@
1import { Component, ElementRef, OnInit } from '@angular/core'; 1import { Component, ElementRef, OnInit } from '@angular/core'
2import { FormBuilder, FormGroup } from '@angular/forms'; 2import { FormBuilder, FormGroup } from '@angular/forms'
3import { ActivatedRoute, Router } from '@angular/router'; 3import { ActivatedRoute, Router } from '@angular/router'
4 4
5import { FileUploader } from 'ng2-file-upload/ng2-file-upload'; 5import { FileUploader } from 'ng2-file-upload/ng2-file-upload'
6import { NotificationsService } from 'angular2-notifications'; 6import { NotificationsService } from 'angular2-notifications'
7 7
8import { AuthService } from '../../core'; 8import { AuthService } from '../../core'
9import { 9import {
10 FormReactive, 10 FormReactive,
11 VIDEO_NAME, 11 VIDEO_NAME,
@@ -14,8 +14,8 @@ import {
14 VIDEO_LANGUAGE, 14 VIDEO_LANGUAGE,
15 VIDEO_DESCRIPTION, 15 VIDEO_DESCRIPTION,
16 VIDEO_TAGS 16 VIDEO_TAGS
17} from '../../shared'; 17} from '../../shared'
18import { Video, VideoService } from '../shared'; 18import { Video, VideoService } from '../shared'
19 19
20@Component({ 20@Component({
21 selector: 'my-videos-update', 21 selector: 'my-videos-update',
@@ -24,35 +24,35 @@ import { Video, VideoService } from '../shared';
24}) 24})
25 25
26export class VideoUpdateComponent extends FormReactive implements OnInit { 26export class VideoUpdateComponent extends FormReactive implements OnInit {
27 tags: string[] = []; 27 tags: string[] = []
28 videoCategories = []; 28 videoCategories = []
29 videoLicences = []; 29 videoLicences = []
30 videoLanguages = []; 30 videoLanguages = []
31 video: Video; 31 video: Video
32 32
33 tagValidators = VIDEO_TAGS.VALIDATORS; 33 tagValidators = VIDEO_TAGS.VALIDATORS
34 tagValidatorsMessages = VIDEO_TAGS.MESSAGES; 34 tagValidatorsMessages = VIDEO_TAGS.MESSAGES
35 35
36 error: string = null; 36 error: string = null
37 form: FormGroup; 37 form: FormGroup
38 formErrors = { 38 formErrors = {
39 name: '', 39 name: '',
40 category: '', 40 category: '',
41 licence: '', 41 licence: '',
42 language: '', 42 language: '',
43 description: '' 43 description: ''
44 }; 44 }
45 validationMessages = { 45 validationMessages = {
46 name: VIDEO_NAME.MESSAGES, 46 name: VIDEO_NAME.MESSAGES,
47 category: VIDEO_CATEGORY.MESSAGES, 47 category: VIDEO_CATEGORY.MESSAGES,
48 licence: VIDEO_LICENCE.MESSAGES, 48 licence: VIDEO_LICENCE.MESSAGES,
49 language: VIDEO_LANGUAGE.MESSAGES, 49 language: VIDEO_LANGUAGE.MESSAGES,
50 description: VIDEO_DESCRIPTION.MESSAGES 50 description: VIDEO_DESCRIPTION.MESSAGES
51 }; 51 }
52 52
53 fileError = ''; 53 fileError = ''
54 54
55 constructor( 55 constructor (
56 private authService: AuthService, 56 private authService: AuthService,
57 private elementRef: ElementRef, 57 private elementRef: ElementRef,
58 private formBuilder: FormBuilder, 58 private formBuilder: FormBuilder,
@@ -61,10 +61,10 @@ export class VideoUpdateComponent extends FormReactive implements OnInit {
61 private notificationsService: NotificationsService, 61 private notificationsService: NotificationsService,
62 private videoService: VideoService 62 private videoService: VideoService
63 ) { 63 ) {
64 super(); 64 super()
65 } 65 }
66 66
67 buildForm() { 67 buildForm () {
68 this.form = this.formBuilder.group({ 68 this.form = this.formBuilder.group({
69 name: [ '', VIDEO_NAME.VALIDATORS ], 69 name: [ '', VIDEO_NAME.VALIDATORS ],
70 nsfw: [ false ], 70 nsfw: [ false ],
@@ -73,60 +73,63 @@ export class VideoUpdateComponent extends FormReactive implements OnInit {
73 language: [ '', VIDEO_LANGUAGE.VALIDATORS ], 73 language: [ '', VIDEO_LANGUAGE.VALIDATORS ],
74 description: [ '', VIDEO_DESCRIPTION.VALIDATORS ], 74 description: [ '', VIDEO_DESCRIPTION.VALIDATORS ],
75 tags: [ '' ] 75 tags: [ '' ]
76 }); 76 })
77 77
78 this.form.valueChanges.subscribe(data => this.onValueChanged(data)); 78 this.form.valueChanges.subscribe(data => this.onValueChanged(data))
79 } 79 }
80 80
81 ngOnInit() { 81 ngOnInit () {
82 this.buildForm(); 82 this.buildForm()
83 83
84 this.videoCategories = this.videoService.videoCategories; 84 this.videoCategories = this.videoService.videoCategories
85 this.videoLicences = this.videoService.videoLicences; 85 this.videoLicences = this.videoService.videoLicences
86 this.videoLanguages = this.videoService.videoLanguages; 86 this.videoLanguages = this.videoService.videoLanguages
87 87
88 const id = this.route.snapshot.params['id']; 88 const id = this.route.snapshot.params['id']
89 this.videoService.getVideo(id) 89 this.videoService.getVideo(id)
90 .subscribe( 90 .subscribe(
91 video => { 91 video => {
92 this.video = video; 92 this.video = video
93 93
94 this.hydrateFormFromVideo(); 94 this.hydrateFormFromVideo()
95 }, 95 },
96 96
97 err => this.error = 'Cannot fetch video.' 97 err => {
98 ); 98 console.error(err)
99 this.error = 'Cannot fetch video.'
100 }
101 )
99 } 102 }
100 103
101 checkForm() { 104 checkForm () {
102 this.forceCheck(); 105 this.forceCheck()
103 106
104 return this.form.valid; 107 return this.form.valid
105 } 108 }
106 109
107 update() { 110 update () {
108 if (this.checkForm() === false) { 111 if (this.checkForm() === false) {
109 return; 112 return
110 } 113 }
111 114
112 this.video.patch(this.form.value); 115 this.video.patch(this.form.value)
113 116
114 this.videoService.updateVideo(this.video) 117 this.videoService.updateVideo(this.video)
115 .subscribe( 118 .subscribe(
116 () => { 119 () => {
117 this.notificationsService.success('Success', 'Video updated.'); 120 this.notificationsService.success('Success', 'Video updated.')
118 this.router.navigate([ '/videos/watch', this.video.id ]); 121 this.router.navigate([ '/videos/watch', this.video.id ])
119 }, 122 },
120 123
121 err => { 124 err => {
122 this.error = 'Cannot update the video.'; 125 this.error = 'Cannot update the video.'
123 console.error(err); 126 console.error(err)
124 } 127 }
125 ); 128 )
126 129
127 } 130 }
128 131
129 private hydrateFormFromVideo() { 132 private hydrateFormFromVideo () {
130 this.form.patchValue(this.video.toJSON()); 133 this.form.patchValue(this.video.toJSON())
131 } 134 }
132} 135}
diff --git a/client/src/app/videos/video-list/index.ts b/client/src/app/videos/video-list/index.ts
index 71d3b78e6..a490e6bb5 100644
--- a/client/src/app/videos/video-list/index.ts
+++ b/client/src/app/videos/video-list/index.ts
@@ -1,4 +1,4 @@
1export * from './loader.component'; 1export * from './loader.component'
2export * from './video-list.component'; 2export * from './video-list.component'
3export * from './video-miniature.component'; 3export * from './video-miniature.component'
4export * from './video-sort.component'; 4export * from './video-sort.component'
diff --git a/client/src/app/videos/video-list/loader.component.ts b/client/src/app/videos/video-list/loader.component.ts
index e72d2f3f3..e5780e0fa 100644
--- a/client/src/app/videos/video-list/loader.component.ts
+++ b/client/src/app/videos/video-list/loader.component.ts
@@ -1,4 +1,4 @@
1import { Component, Input } from '@angular/core'; 1import { Component, Input } from '@angular/core'
2 2
3@Component({ 3@Component({
4 selector: 'my-loader', 4 selector: 'my-loader',
@@ -7,5 +7,5 @@ import { Component, Input } from '@angular/core';
7}) 7})
8 8
9export class LoaderComponent { 9export class LoaderComponent {
10 @Input() loading: boolean; 10 @Input() loading: boolean
11} 11}
diff --git a/client/src/app/videos/video-list/video-list.component.html b/client/src/app/videos/video-list/video-list.component.html
index f80592279..680fba3f5 100644
--- a/client/src/app/videos/video-list/video-list.component.html
+++ b/client/src/app/videos/video-list/video-list.component.html
@@ -17,7 +17,7 @@
17 17
18 <my-video-miniature 18 <my-video-miniature
19 class="ng-animate" 19 class="ng-animate"
20 *ngFor="let video of videos" [video]="video" [user]="user" [currentSort]="sort" (removed)="onRemoved(video)" 20 *ngFor="let video of videos" [video]="video" [user]="user" [currentSort]="sort"
21 > 21 >
22 </my-video-miniature> 22 </my-video-miniature>
23</div> 23</div>
diff --git a/client/src/app/videos/video-list/video-list.component.ts b/client/src/app/videos/video-list/video-list.component.ts
index 16a40bdc4..0c36e5b08 100644
--- a/client/src/app/videos/video-list/video-list.component.ts
+++ b/client/src/app/videos/video-list/video-list.component.ts
@@ -1,17 +1,17 @@
1import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core'; 1import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core'
2import { ActivatedRoute, Router } from '@angular/router'; 2import { ActivatedRoute, Router } from '@angular/router'
3import { BehaviorSubject } from 'rxjs/BehaviorSubject'; 3import { BehaviorSubject } from 'rxjs/BehaviorSubject'
4 4
5import { NotificationsService } from 'angular2-notifications'; 5import { NotificationsService } from 'angular2-notifications'
6 6
7import { 7import {
8 SortField, 8 SortField,
9 Video, 9 Video,
10 VideoService 10 VideoService
11} from '../shared'; 11} from '../shared'
12import { AuthService, AuthUser } from '../../core'; 12import { AuthService, AuthUser } from '../../core'
13import { RestPagination, Search, SearchField } from '../../shared'; 13import { RestPagination, Search, SearchField } from '../../shared'
14import { SearchService } from '../../shared'; 14import { SearchService } from '../../shared'
15 15
16@Component({ 16@Component({
17 selector: 'my-videos-list', 17 selector: 'my-videos-list',
@@ -19,21 +19,21 @@ import { SearchService } from '../../shared';
19 templateUrl: './video-list.component.html' 19 templateUrl: './video-list.component.html'
20}) 20})
21export class VideoListComponent implements OnInit, OnDestroy { 21export class VideoListComponent implements OnInit, OnDestroy {
22 loading: BehaviorSubject<boolean> = new BehaviorSubject(false); 22 loading: BehaviorSubject<boolean> = new BehaviorSubject(false)
23 pagination: RestPagination = { 23 pagination: RestPagination = {
24 currentPage: 1, 24 currentPage: 1,
25 itemsPerPage: 25, 25 itemsPerPage: 25,
26 totalItems: null 26 totalItems: null
27 }; 27 }
28 sort: SortField; 28 sort: SortField
29 user: AuthUser = null; 29 user: AuthUser = null
30 videos: Video[] = []; 30 videos: Video[] = []
31 31
32 private search: Search; 32 private search: Search
33 private subActivatedRoute: any; 33 private subActivatedRoute: any
34 private subSearch: any; 34 private subSearch: any
35 35
36 constructor( 36 constructor (
37 private notificationsService: NotificationsService, 37 private notificationsService: NotificationsService,
38 private authService: AuthService, 38 private authService: AuthService,
39 private changeDetector: ChangeDetectorRef, 39 private changeDetector: ChangeDetectorRef,
@@ -43,114 +43,114 @@ export class VideoListComponent implements OnInit, OnDestroy {
43 private searchService: SearchService 43 private searchService: SearchService
44 ) {} 44 ) {}
45 45
46 ngOnInit() { 46 ngOnInit () {
47 if (this.authService.isLoggedIn()) { 47 if (this.authService.isLoggedIn()) {
48 this.user = AuthUser.load(); 48 this.user = AuthUser.load()
49 } 49 }
50 50
51 // Subscribe to route changes 51 // Subscribe to route changes
52 this.subActivatedRoute = this.route.params.subscribe(routeParams => { 52 this.subActivatedRoute = this.route.params.subscribe(routeParams => {
53 this.loadRouteParams(routeParams); 53 this.loadRouteParams(routeParams)
54 54
55 // Update the search service component 55 // Update the search service component
56 this.searchService.updateSearch.next(this.search); 56 this.searchService.updateSearch.next(this.search)
57 this.getVideos(); 57 this.getVideos()
58 }); 58 })
59 59
60 // Subscribe to search changes 60 // Subscribe to search changes
61 this.subSearch = this.searchService.searchUpdated.subscribe(search => { 61 this.subSearch = this.searchService.searchUpdated.subscribe(search => {
62 this.search = search; 62 this.search = search
63 // Reset pagination 63 // Reset pagination
64 this.pagination.currentPage = 1; 64 this.pagination.currentPage = 1
65 65
66 this.navigateToNewParams(); 66 this.navigateToNewParams()
67 }); 67 })
68 } 68 }
69 69
70 ngOnDestroy() { 70 ngOnDestroy () {
71 this.subActivatedRoute.unsubscribe(); 71 this.subActivatedRoute.unsubscribe()
72 this.subSearch.unsubscribe(); 72 this.subSearch.unsubscribe()
73 } 73 }
74 74
75 getVideos() { 75 getVideos () {
76 this.loading.next(true); 76 this.loading.next(true)
77 this.videos = []; 77 this.videos = []
78 78
79 let observable = null; 79 let observable = null
80 if (this.search.value) { 80 if (this.search.value) {
81 observable = this.videoService.searchVideos(this.search, this.pagination, this.sort); 81 observable = this.videoService.searchVideos(this.search, this.pagination, this.sort)
82 } else { 82 } else {
83 observable = this.videoService.getVideos(this.pagination, this.sort); 83 observable = this.videoService.getVideos(this.pagination, this.sort)
84 } 84 }
85 85
86 observable.subscribe( 86 observable.subscribe(
87 ({ videos, totalVideos }) => { 87 ({ videos, totalVideos }) => {
88 this.videos = videos; 88 this.videos = videos
89 this.pagination.totalItems = totalVideos; 89 this.pagination.totalItems = totalVideos
90 90
91 this.loading.next(false); 91 this.loading.next(false)
92 }, 92 },
93 error => this.notificationsService.error('Error', error.text) 93 error => this.notificationsService.error('Error', error.text)
94 ); 94 )
95 } 95 }
96 96
97 isThereNoVideo() { 97 isThereNoVideo () {
98 return !this.loading.getValue() && this.videos.length === 0; 98 return !this.loading.getValue() && this.videos.length === 0
99 } 99 }
100 100
101 onPageChanged(event: any) { 101 onPageChanged (event: any) {
102 // Be sure the current page is set 102 // Be sure the current page is set
103 this.pagination.currentPage = event.page; 103 this.pagination.currentPage = event.page
104 104
105 this.navigateToNewParams(); 105 this.navigateToNewParams()
106 } 106 }
107 107
108 onSort(sort: SortField) { 108 onSort (sort: SortField) {
109 this.sort = sort; 109 this.sort = sort
110 110
111 this.navigateToNewParams(); 111 this.navigateToNewParams()
112 } 112 }
113 113
114 private buildRouteParams() { 114 private buildRouteParams () {
115 // There is always a sort and a current page 115 // There is always a sort and a current page
116 const params: any = { 116 const params: any = {
117 sort: this.sort, 117 sort: this.sort,
118 page: this.pagination.currentPage 118 page: this.pagination.currentPage
119 }; 119 }
120 120
121 // Maybe there is a search 121 // Maybe there is a search
122 if (this.search.value) { 122 if (this.search.value) {
123 params.field = this.search.field; 123 params.field = this.search.field
124 params.search = this.search.value; 124 params.search = this.search.value
125 } 125 }
126 126
127 return params; 127 return params
128 } 128 }
129 129
130 private loadRouteParams(routeParams) { 130 private loadRouteParams (routeParams) {
131 if (routeParams['search'] !== undefined) { 131 if (routeParams['search'] !== undefined) {
132 this.search = { 132 this.search = {
133 value: routeParams['search'], 133 value: routeParams['search'],
134 field: <SearchField>routeParams['field'] 134 field: routeParams['field'] as SearchField
135 }; 135 }
136 } else { 136 } else {
137 this.search = { 137 this.search = {
138 value: '', 138 value: '',
139 field: 'name' 139 field: 'name'
140 }; 140 }
141 } 141 }
142 142
143 this.sort = <SortField>routeParams['sort'] || '-createdAt'; 143 this.sort = routeParams['sort'] as SortField || '-createdAt'
144 144
145 if (routeParams['page'] !== undefined) { 145 if (routeParams['page'] !== undefined) {
146 this.pagination.currentPage = parseInt(routeParams['page']); 146 this.pagination.currentPage = parseInt(routeParams['page'], 10)
147 } else { 147 } else {
148 this.pagination.currentPage = 1; 148 this.pagination.currentPage = 1
149 } 149 }
150 } 150 }
151 151
152 private navigateToNewParams() { 152 private navigateToNewParams () {
153 const routeParams = this.buildRouteParams(); 153 const routeParams = this.buildRouteParams()
154 this.router.navigate(['/videos/list', routeParams]); 154 this.router.navigate(['/videos/list', routeParams])
155 } 155 }
156} 156}
diff --git a/client/src/app/videos/video-list/video-miniature.component.ts b/client/src/app/videos/video-list/video-miniature.component.ts
index 28601ca7f..1cfeacf36 100644
--- a/client/src/app/videos/video-list/video-miniature.component.ts
+++ b/client/src/app/videos/video-list/video-miniature.component.ts
@@ -1,10 +1,10 @@
1import { Component, Input, Output, EventEmitter } from '@angular/core'; 1import { Component, Input, Output, EventEmitter } from '@angular/core'
2 2
3import { NotificationsService } from 'angular2-notifications'; 3import { NotificationsService } from 'angular2-notifications'
4 4
5import { ConfirmService, ConfigService } from '../../core'; 5import { ConfirmService, ConfigService } from '../../core'
6import { SortField, Video, VideoService } from '../shared'; 6import { SortField, Video, VideoService } from '../shared'
7import { User } from '../../shared'; 7import { User } from '../../shared'
8 8
9@Component({ 9@Component({
10 selector: 'my-video-miniature', 10 selector: 'my-video-miniature',
@@ -13,25 +13,26 @@ import { User } from '../../shared';
13}) 13})
14 14
15export class VideoMiniatureComponent { 15export class VideoMiniatureComponent {
16 @Input() currentSort: SortField; 16 @Input() currentSort: SortField
17 @Input() user: User; 17 @Input() user: User
18 @Input() video: Video; 18 @Input() video: Video
19 19
20 constructor( 20 constructor (
21 private notificationsService: NotificationsService, 21 private notificationsService: NotificationsService,
22 private confirmService: ConfirmService, 22 private confirmService: ConfirmService,
23 private configService: ConfigService, 23 private configService: ConfigService,
24 private videoService: VideoService 24 private videoService: VideoService
25 ) {} 25 ) {}
26 26
27 getVideoName() { 27 getVideoName () {
28 if (this.isVideoNSFWForThisUser()) 28 if (this.isVideoNSFWForThisUser()) {
29 return 'NSFW'; 29 return 'NSFW'
30 }
30 31
31 return this.video.name; 32 return this.video.name
32 } 33 }
33 34
34 isVideoNSFWForThisUser() { 35 isVideoNSFWForThisUser () {
35 return this.video.isVideoNSFWForUser(this.user); 36 return this.video.isVideoNSFWForUser(this.user)
36 } 37 }
37} 38}
diff --git a/client/src/app/videos/video-list/video-sort.component.ts b/client/src/app/videos/video-list/video-sort.component.ts
index 20979a395..64916bf16 100644
--- a/client/src/app/videos/video-list/video-sort.component.ts
+++ b/client/src/app/videos/video-list/video-sort.component.ts
@@ -1,6 +1,6 @@
1import { Component, EventEmitter, Input, Output } from '@angular/core'; 1import { Component, EventEmitter, Input, Output } from '@angular/core'
2 2
3import { SortField } from '../shared'; 3import { SortField } from '../shared'
4 4
5@Component({ 5@Component({
6 selector: 'my-video-sort', 6 selector: 'my-video-sort',
@@ -8,9 +8,9 @@ import { SortField } from '../shared';
8}) 8})
9 9
10export class VideoSortComponent { 10export class VideoSortComponent {
11 @Output() sort = new EventEmitter<any>(); 11 @Output() sort = new EventEmitter<any>()
12 12
13 @Input() currentSort: SortField; 13 @Input() currentSort: SortField
14 14
15 sortChoices: { [ P in SortField ]: string } = { 15 sortChoices: { [ P in SortField ]: string } = {
16 'name': 'Name - Asc', 16 'name': 'Name - Asc',
@@ -23,17 +23,17 @@ export class VideoSortComponent {
23 '-views': 'Views - Desc', 23 '-views': 'Views - Desc',
24 'likes': 'Likes - Asc', 24 'likes': 'Likes - Asc',
25 '-likes': 'Likes - Desc' 25 '-likes': 'Likes - Desc'
26 }; 26 }
27 27
28 get choiceKeys() { 28 get choiceKeys () {
29 return Object.keys(this.sortChoices); 29 return Object.keys(this.sortChoices)
30 } 30 }
31 31
32 getStringChoice(choiceKey: SortField) { 32 getStringChoice (choiceKey: SortField) {
33 return this.sortChoices[choiceKey]; 33 return this.sortChoices[choiceKey]
34 } 34 }
35 35
36 onSortChange() { 36 onSortChange () {
37 this.sort.emit(this.currentSort); 37 this.sort.emit(this.currentSort)
38 } 38 }
39} 39}
diff --git a/client/src/app/videos/video-watch/index.ts b/client/src/app/videos/video-watch/index.ts
index ed0ed2fc0..6e35262d3 100644
--- a/client/src/app/videos/video-watch/index.ts
+++ b/client/src/app/videos/video-watch/index.ts
@@ -1,5 +1,5 @@
1export * from './video-magnet.component'; 1export * from './video-magnet.component'
2export * from './video-share.component'; 2export * from './video-share.component'
3export * from './video-report.component'; 3export * from './video-report.component'
4export * from './video-watch.component'; 4export * from './video-watch.component'
5export * from './webtorrent.service'; 5export * from './webtorrent.service'
diff --git a/client/src/app/videos/video-watch/video-magnet.component.ts b/client/src/app/videos/video-watch/video-magnet.component.ts
index 894fa45fc..f9432e92c 100644
--- a/client/src/app/videos/video-watch/video-magnet.component.ts
+++ b/client/src/app/videos/video-watch/video-magnet.component.ts
@@ -1,27 +1,27 @@
1import { Component, Input, ViewChild } from '@angular/core'; 1import { Component, Input, ViewChild } from '@angular/core'
2 2
3import { ModalDirective } from 'ngx-bootstrap/modal'; 3import { ModalDirective } from 'ngx-bootstrap/modal'
4 4
5import { Video } from '../shared'; 5import { Video } from '../shared'
6 6
7@Component({ 7@Component({
8 selector: 'my-video-magnet', 8 selector: 'my-video-magnet',
9 templateUrl: './video-magnet.component.html' 9 templateUrl: './video-magnet.component.html'
10}) 10})
11export class VideoMagnetComponent { 11export class VideoMagnetComponent {
12 @Input() video: Video = null; 12 @Input() video: Video = null
13 13
14 @ViewChild('modal') modal: ModalDirective; 14 @ViewChild('modal') modal: ModalDirective
15 15
16 constructor() { 16 constructor () {
17 // empty 17 // empty
18 } 18 }
19 19
20 show() { 20 show () {
21 this.modal.show(); 21 this.modal.show()
22 } 22 }
23 23
24 hide() { 24 hide () {
25 this.modal.hide(); 25 this.modal.hide()
26 } 26 }
27} 27}
diff --git a/client/src/app/videos/video-watch/video-report.component.ts b/client/src/app/videos/video-watch/video-report.component.ts
index 528005b84..61213cd68 100644
--- a/client/src/app/videos/video-watch/video-report.component.ts
+++ b/client/src/app/videos/video-watch/video-report.component.ts
@@ -1,69 +1,69 @@
1import { Component, Input, OnInit, ViewChild } from '@angular/core'; 1import { Component, Input, OnInit, ViewChild } from '@angular/core'
2import { FormBuilder, FormGroup } from '@angular/forms'; 2import { FormBuilder, FormGroup } from '@angular/forms'
3 3
4import { ModalDirective } from 'ngx-bootstrap/modal'; 4import { ModalDirective } from 'ngx-bootstrap/modal'
5import { NotificationsService } from 'angular2-notifications'; 5import { NotificationsService } from 'angular2-notifications'
6 6
7import { FormReactive, VideoAbuseService, VIDEO_ABUSE_REASON } from '../../shared'; 7import { FormReactive, VideoAbuseService, VIDEO_ABUSE_REASON } from '../../shared'
8import { Video, VideoService } from '../shared'; 8import { Video, VideoService } from '../shared'
9 9
10@Component({ 10@Component({
11 selector: 'my-video-report', 11 selector: 'my-video-report',
12 templateUrl: './video-report.component.html' 12 templateUrl: './video-report.component.html'
13}) 13})
14export class VideoReportComponent extends FormReactive implements OnInit { 14export class VideoReportComponent extends FormReactive implements OnInit {
15 @Input() video: Video = null; 15 @Input() video: Video = null
16 16
17 @ViewChild('modal') modal: ModalDirective; 17 @ViewChild('modal') modal: ModalDirective
18 18
19 error: string = null; 19 error: string = null
20 form: FormGroup; 20 form: FormGroup
21 formErrors = { 21 formErrors = {
22 reason: '' 22 reason: ''
23 }; 23 }
24 validationMessages = { 24 validationMessages = {
25 reason: VIDEO_ABUSE_REASON.MESSAGES 25 reason: VIDEO_ABUSE_REASON.MESSAGES
26 }; 26 }
27 27
28 constructor( 28 constructor (
29 private formBuilder: FormBuilder, 29 private formBuilder: FormBuilder,
30 private videoAbuseService: VideoAbuseService, 30 private videoAbuseService: VideoAbuseService,
31 private notificationsService: NotificationsService 31 private notificationsService: NotificationsService
32 ) { 32 ) {
33 super(); 33 super()
34 } 34 }
35 35
36 ngOnInit() { 36 ngOnInit () {
37 this.buildForm(); 37 this.buildForm()
38 } 38 }
39 39
40 buildForm() { 40 buildForm () {
41 this.form = this.formBuilder.group({ 41 this.form = this.formBuilder.group({
42 reason: [ '', VIDEO_ABUSE_REASON.VALIDATORS ] 42 reason: [ '', VIDEO_ABUSE_REASON.VALIDATORS ]
43 }); 43 })
44 44
45 this.form.valueChanges.subscribe(data => this.onValueChanged(data)); 45 this.form.valueChanges.subscribe(data => this.onValueChanged(data))
46 } 46 }
47 47
48 show() { 48 show () {
49 this.modal.show(); 49 this.modal.show()
50 } 50 }
51 51
52 hide() { 52 hide () {
53 this.modal.hide(); 53 this.modal.hide()
54 } 54 }
55 55
56 report() { 56 report () {
57 const reason = this.form.value['reason']; 57 const reason = this.form.value['reason']
58 58
59 this.videoAbuseService.reportVideo(this.video.id, reason) 59 this.videoAbuseService.reportVideo(this.video.id, reason)
60 .subscribe( 60 .subscribe(
61 () => { 61 () => {
62 this.notificationsService.success('Success', 'Video reported.'); 62 this.notificationsService.success('Success', 'Video reported.')
63 this.hide(); 63 this.hide()
64 }, 64 },
65 65
66 err => this.notificationsService.error('Error', err.text) 66 err => this.notificationsService.error('Error', err.text)
67 ); 67 )
68 } 68 }
69} 69}
diff --git a/client/src/app/videos/video-watch/video-share.component.ts b/client/src/app/videos/video-watch/video-share.component.ts
index aa921afc2..bbd25f5ef 100644
--- a/client/src/app/videos/video-watch/video-share.component.ts
+++ b/client/src/app/videos/video-watch/video-share.component.ts
@@ -1,42 +1,42 @@
1import { Component, Input, ViewChild } from '@angular/core'; 1import { Component, Input, ViewChild } from '@angular/core'
2 2
3import { ModalDirective } from 'ngx-bootstrap/modal'; 3import { ModalDirective } from 'ngx-bootstrap/modal'
4 4
5import { Video } from '../shared'; 5import { Video } from '../shared'
6 6
7@Component({ 7@Component({
8 selector: 'my-video-share', 8 selector: 'my-video-share',
9 templateUrl: './video-share.component.html' 9 templateUrl: './video-share.component.html'
10}) 10})
11export class VideoShareComponent { 11export class VideoShareComponent {
12 @Input() video: Video = null; 12 @Input() video: Video = null
13 13
14 @ViewChild('modal') modal: ModalDirective; 14 @ViewChild('modal') modal: ModalDirective
15 15
16 constructor() { 16 constructor () {
17 // empty 17 // empty
18 } 18 }
19 19
20 show() { 20 show () {
21 this.modal.show(); 21 this.modal.show()
22 } 22 }
23 23
24 hide() { 24 hide () {
25 this.modal.hide(); 25 this.modal.hide()
26 } 26 }
27 27
28 getVideoIframeCode() { 28 getVideoIframeCode () {
29 return '<iframe width="560" height="315" ' + 29 return '<iframe width="560" height="315" ' +
30 'src="' + window.location.origin + '/videos/embed/' + this.video.id + '" ' + 30 'src="' + window.location.origin + '/videos/embed/' + this.video.id + '" ' +
31 'frameborder="0" allowfullscreen>' + 31 'frameborder="0" allowfullscreen>' +
32 '</iframe>'; 32 '</iframe>'
33 } 33 }
34 34
35 getVideoUrl() { 35 getVideoUrl () {
36 return window.location.href; 36 return window.location.href
37 } 37 }
38 38
39 notSecure() { 39 notSecure () {
40 return window.location.protocol === 'http:'; 40 return window.location.protocol === 'http:'
41 } 41 }
42} 42}
diff --git a/client/src/app/videos/video-watch/video-watch.component.ts b/client/src/app/videos/video-watch/video-watch.component.ts
index bcfebf2fd..4a547f7e4 100644
--- a/client/src/app/videos/video-watch/video-watch.component.ts
+++ b/client/src/app/videos/video-watch/video-watch.component.ts
@@ -1,18 +1,18 @@
1import { Component, ElementRef, NgZone, OnDestroy, OnInit, ViewChild } from '@angular/core'; 1import { Component, ElementRef, NgZone, OnDestroy, OnInit, ViewChild } from '@angular/core'
2import { ActivatedRoute, Router } from '@angular/router'; 2import { ActivatedRoute, Router } from '@angular/router'
3import { Observable } from 'rxjs/Observable'; 3import { Observable } from 'rxjs/Observable'
4import { Subscription } from 'rxjs/Subscription'; 4import { Subscription } from 'rxjs/Subscription'
5 5
6import * as videojs from 'video.js'; 6import * as videojs from 'video.js'
7import { MetaService } from '@nglibs/meta'; 7import { MetaService } from '@nglibs/meta'
8import { NotificationsService } from 'angular2-notifications'; 8import { NotificationsService } from 'angular2-notifications'
9 9
10import { AuthService, ConfirmService } from '../../core'; 10import { AuthService, ConfirmService } from '../../core'
11import { VideoMagnetComponent } from './video-magnet.component'; 11import { VideoMagnetComponent } from './video-magnet.component'
12import { VideoShareComponent } from './video-share.component'; 12import { VideoShareComponent } from './video-share.component'
13import { VideoReportComponent } from './video-report.component'; 13import { VideoReportComponent } from './video-report.component'
14import { RateType, Video, VideoService } from '../shared'; 14import { RateType, Video, VideoService } from '../shared'
15import { WebTorrentService } from './webtorrent.service'; 15import { WebTorrentService } from './webtorrent.service'
16 16
17@Component({ 17@Component({
18 selector: 'my-video-watch', 18 selector: 'my-video-watch',
@@ -21,30 +21,30 @@ import { WebTorrentService } from './webtorrent.service';
21}) 21})
22 22
23export class VideoWatchComponent implements OnInit, OnDestroy { 23export class VideoWatchComponent implements OnInit, OnDestroy {
24 private static LOADTIME_TOO_LONG = 20000; 24 private static LOADTIME_TOO_LONG = 20000
25 25
26 @ViewChild('videoMagnetModal') videoMagnetModal: VideoMagnetComponent; 26 @ViewChild('videoMagnetModal') videoMagnetModal: VideoMagnetComponent
27 @ViewChild('videoShareModal') videoShareModal: VideoShareComponent; 27 @ViewChild('videoShareModal') videoShareModal: VideoShareComponent
28 @ViewChild('videoReportModal') videoReportModal: VideoReportComponent; 28 @ViewChild('videoReportModal') videoReportModal: VideoReportComponent
29 29
30 downloadSpeed: number; 30 downloadSpeed: number
31 error = false; 31 error = false
32 loading = false; 32 loading = false
33 numPeers: number; 33 numPeers: number
34 player: videojs.Player; 34 player: videojs.Player
35 playerElement: Element; 35 playerElement: Element
36 uploadSpeed: number; 36 uploadSpeed: number
37 userRating: RateType = null; 37 userRating: RateType = null
38 video: Video = null; 38 video: Video = null
39 videoNotFound = false; 39 videoNotFound = false
40 40
41 private errorTimer: number; 41 private errorTimer: number
42 private paramsSub: Subscription; 42 private paramsSub: Subscription
43 private errorsSub: Subscription; 43 private errorsSub: Subscription
44 private warningsSub: Subscription; 44 private warningsSub: Subscription
45 private torrentInfosInterval: number; 45 private torrentInfosInterval: number
46 46
47 constructor( 47 constructor (
48 private elementRef: ElementRef, 48 private elementRef: ElementRef,
49 private ngZone: NgZone, 49 private ngZone: NgZone,
50 private route: ActivatedRoute, 50 private route: ActivatedRoute,
@@ -57,278 +57,281 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
57 private notificationsService: NotificationsService 57 private notificationsService: NotificationsService
58 ) {} 58 ) {}
59 59
60 ngOnInit() { 60 ngOnInit () {
61 this.paramsSub = this.route.params.subscribe(routeParams => { 61 this.paramsSub = this.route.params.subscribe(routeParams => {
62 let id = routeParams['id']; 62 let id = routeParams['id']
63 this.videoService.getVideo(id).subscribe( 63 this.videoService.getVideo(id).subscribe(
64 video => this.onVideoFetched(video), 64 video => this.onVideoFetched(video),
65 65
66 error => this.videoNotFound = true 66 error => {
67 ); 67 console.error(error)
68 }); 68 this.videoNotFound = true
69 }
70 )
71 })
69 72
70 this.playerElement = this.elementRef.nativeElement.querySelector('#video-container'); 73 this.playerElement = this.elementRef.nativeElement.querySelector('#video-container')
71 74
72 const videojsOptions = { 75 const videojsOptions = {
73 controls: true, 76 controls: true,
74 autoplay: false 77 autoplay: false
75 }; 78 }
76 79
77 const self = this; 80 const self = this
78 videojs(this.playerElement, videojsOptions, function () { 81 videojs(this.playerElement, videojsOptions, function () {
79 self.player = this; 82 self.player = this
80 }); 83 })
81 84
82 this.errorsSub = this.webTorrentService.errors.subscribe(err => this.notificationsService.error('Error', err.message)); 85 this.errorsSub = this.webTorrentService.errors.subscribe(err => this.notificationsService.error('Error', err.message))
83 this.warningsSub = this.webTorrentService.errors.subscribe(err => this.notificationsService.alert('Warning', err.message)); 86 this.warningsSub = this.webTorrentService.errors.subscribe(err => this.notificationsService.alert('Warning', err.message))
84 } 87 }
85 88
86 ngOnDestroy() { 89 ngOnDestroy () {
87 // Remove WebTorrent stuff 90 // Remove WebTorrent stuff
88 console.log('Removing video from webtorrent.'); 91 console.log('Removing video from webtorrent.')
89 window.clearInterval(this.torrentInfosInterval); 92 window.clearInterval(this.torrentInfosInterval)
90 window.clearTimeout(this.errorTimer); 93 window.clearTimeout(this.errorTimer)
91 94
92 if (this.video !== null && this.webTorrentService.has(this.video.magnetUri)) { 95 if (this.video !== null && this.webTorrentService.has(this.video.magnetUri)) {
93 this.webTorrentService.remove(this.video.magnetUri); 96 this.webTorrentService.remove(this.video.magnetUri)
94 } 97 }
95 98
96 // Remove player 99 // Remove player
97 videojs(this.playerElement).dispose(); 100 videojs(this.playerElement).dispose()
98 101
99 // Unsubscribe subscriptions 102 // Unsubscribe subscriptions
100 this.paramsSub.unsubscribe(); 103 this.paramsSub.unsubscribe()
101 this.errorsSub.unsubscribe(); 104 this.errorsSub.unsubscribe()
102 this.warningsSub.unsubscribe(); 105 this.warningsSub.unsubscribe()
103 } 106 }
104 107
105 loadVideo() { 108 loadVideo () {
106 // Reset the error 109 // Reset the error
107 this.error = false; 110 this.error = false
108 // We are loading the video 111 // We are loading the video
109 this.loading = true; 112 this.loading = true
110 113
111 console.log('Adding ' + this.video.magnetUri + '.'); 114 console.log('Adding ' + this.video.magnetUri + '.')
112 115
113 // The callback might never return if there are network issues 116 // The callback might never return if there are network issues
114 // So we create a timer to inform the user the load is abnormally long 117 // So we create a timer to inform the user the load is abnormally long
115 this.errorTimer = window.setTimeout(() => this.loadTooLong(), VideoWatchComponent.LOADTIME_TOO_LONG); 118 this.errorTimer = window.setTimeout(() => this.loadTooLong(), VideoWatchComponent.LOADTIME_TOO_LONG)
116 119
117 this.webTorrentService.add(this.video.magnetUri, (torrent) => { 120 this.webTorrentService.add(this.video.magnetUri, (torrent) => {
118 // Clear the error timer 121 // Clear the error timer
119 window.clearTimeout(this.errorTimer); 122 window.clearTimeout(this.errorTimer)
120 // Maybe the error was fired by the timer, so reset it 123 // Maybe the error was fired by the timer, so reset it
121 this.error = false; 124 this.error = false
122 125
123 // We are not loading the video anymore 126 // We are not loading the video anymore
124 this.loading = false; 127 this.loading = false
125 128
126 console.log('Added ' + this.video.magnetUri + '.'); 129 console.log('Added ' + this.video.magnetUri + '.')
127 torrent.files[0].renderTo(this.playerElement, { autoplay: true }, (err) => { 130 torrent.files[0].renderTo(this.playerElement, { autoplay: true }, (err) => {
128 if (err) { 131 if (err) {
129 this.notificationsService.error('Error', 'Cannot append the file in the video element.'); 132 this.notificationsService.error('Error', 'Cannot append the file in the video element.')
130 console.error(err); 133 console.error(err)
131 } 134 }
132 }); 135 })
133 136
134 this.runInProgress(torrent); 137 this.runInProgress(torrent)
135 }); 138 })
136 } 139 }
137 140
138 setLike() { 141 setLike () {
139 if (this.isUserLoggedIn() === false) return; 142 if (this.isUserLoggedIn() === false) return
140 // Already liked this video 143 // Already liked this video
141 if (this.userRating === 'like') return; 144 if (this.userRating === 'like') return
142 145
143 this.videoService.setVideoLike(this.video.id) 146 this.videoService.setVideoLike(this.video.id)
144 .subscribe( 147 .subscribe(
145 () => { 148 () => {
146 // Update the video like attribute 149 // Update the video like attribute
147 this.updateVideoRating(this.userRating, 'like'); 150 this.updateVideoRating(this.userRating, 'like')
148 this.userRating = 'like'; 151 this.userRating = 'like'
149 }, 152 },
150 153
151 err => this.notificationsService.error('Error', err.text) 154 err => this.notificationsService.error('Error', err.text)
152 ); 155 )
153 } 156 }
154 157
155 setDislike() { 158 setDislike () {
156 if (this.isUserLoggedIn() === false) return; 159 if (this.isUserLoggedIn() === false) return
157 // Already disliked this video 160 // Already disliked this video
158 if (this.userRating === 'dislike') return; 161 if (this.userRating === 'dislike') return
159 162
160 this.videoService.setVideoDislike(this.video.id) 163 this.videoService.setVideoDislike(this.video.id)
161 .subscribe( 164 .subscribe(
162 () => { 165 () => {
163 // Update the video dislike attribute 166 // Update the video dislike attribute
164 this.updateVideoRating(this.userRating, 'dislike'); 167 this.updateVideoRating(this.userRating, 'dislike')
165 this.userRating = 'dislike'; 168 this.userRating = 'dislike'
166 }, 169 },
167 170
168 err => this.notificationsService.error('Error', err.text) 171 err => this.notificationsService.error('Error', err.text)
169 ); 172 )
170 } 173 }
171 174
172 removeVideo(event: Event) { 175 removeVideo (event: Event) {
173 event.preventDefault(); 176 event.preventDefault()
174 177
175 this.confirmService.confirm('Do you really want to delete this video?', 'Delete').subscribe( 178 this.confirmService.confirm('Do you really want to delete this video?', 'Delete').subscribe(
176 res => { 179 res => {
177 if (res === false) return; 180 if (res === false) return
178 181
179 this.videoService.removeVideo(this.video.id) 182 this.videoService.removeVideo(this.video.id)
180 .subscribe( 183 .subscribe(
181 status => { 184 status => {
182 this.notificationsService.success('Success', `Video ${this.video.name} deleted.`); 185 this.notificationsService.success('Success', `Video ${this.video.name} deleted.`)
183 // Go back to the video-list. 186 // Go back to the video-list.
184 this.router.navigate(['/videos/list']); 187 this.router.navigate(['/videos/list'])
185 }, 188 },
186 189
187 error => this.notificationsService.error('Error', error.text) 190 error => this.notificationsService.error('Error', error.text)
188 ); 191 )
189 } 192 }
190 ); 193 )
191 } 194 }
192 195
193 blacklistVideo(event: Event) { 196 blacklistVideo (event: Event) {
194 event.preventDefault(); 197 event.preventDefault()
195 198
196 this.confirmService.confirm('Do you really want to blacklist this video ?', 'Blacklist').subscribe( 199 this.confirmService.confirm('Do you really want to blacklist this video ?', 'Blacklist').subscribe(
197 res => { 200 res => {
198 if (res === false) return; 201 if (res === false) return
199 202
200 this.videoService.blacklistVideo(this.video.id) 203 this.videoService.blacklistVideo(this.video.id)
201 .subscribe( 204 .subscribe(
202 status => { 205 status => {
203 this.notificationsService.success('Success', `Video ${this.video.name} had been blacklisted.`); 206 this.notificationsService.success('Success', `Video ${this.video.name} had been blacklisted.`)
204 this.router.navigate(['/videos/list']); 207 this.router.navigate(['/videos/list'])
205 }, 208 },
206 209
207 error => this.notificationsService.error('Error', error.text) 210 error => this.notificationsService.error('Error', error.text)
208 ); 211 )
209 } 212 }
210 ); 213 )
211 } 214 }
212 215
213 showReportModal(event: Event) { 216 showReportModal (event: Event) {
214 event.preventDefault(); 217 event.preventDefault()
215 this.videoReportModal.show(); 218 this.videoReportModal.show()
216 } 219 }
217 220
218 showShareModal() { 221 showShareModal () {
219 this.videoShareModal.show(); 222 this.videoShareModal.show()
220 } 223 }
221 224
222 showMagnetUriModal(event: Event) { 225 showMagnetUriModal (event: Event) {
223 event.preventDefault(); 226 event.preventDefault()
224 this.videoMagnetModal.show(); 227 this.videoMagnetModal.show()
225 } 228 }
226 229
227 isUserLoggedIn() { 230 isUserLoggedIn () {
228 return this.authService.isLoggedIn(); 231 return this.authService.isLoggedIn()
229 } 232 }
230 233
231 canUserUpdateVideo() { 234 canUserUpdateVideo () {
232 return this.video.isUpdatableBy(this.authService.getUser()); 235 return this.video.isUpdatableBy(this.authService.getUser())
233 } 236 }
234 237
235 isVideoRemovable() { 238 isVideoRemovable () {
236 return this.video.isRemovableBy(this.authService.getUser()); 239 return this.video.isRemovableBy(this.authService.getUser())
237 } 240 }
238 241
239 isVideoBlacklistable() { 242 isVideoBlacklistable () {
240 return this.video.isBlackistableBy(this.authService.getUser()); 243 return this.video.isBlackistableBy(this.authService.getUser())
241 } 244 }
242 245
243 private checkUserRating() { 246 private checkUserRating () {
244 // Unlogged users do not have ratings 247 // Unlogged users do not have ratings
245 if (this.isUserLoggedIn() === false) return; 248 if (this.isUserLoggedIn() === false) return
246 249
247 this.videoService.getUserVideoRating(this.video.id) 250 this.videoService.getUserVideoRating(this.video.id)
248 .subscribe( 251 .subscribe(
249 ratingObject => { 252 ratingObject => {
250 if (ratingObject) { 253 if (ratingObject) {
251 this.userRating = ratingObject.rating; 254 this.userRating = ratingObject.rating
252 } 255 }
253 }, 256 },
254 257
255 err => this.notificationsService.error('Error', err.text) 258 err => this.notificationsService.error('Error', err.text)
256 ); 259 )
257 } 260 }
258 261
259 private onVideoFetched(video: Video) { 262 private onVideoFetched (video: Video) {
260 this.video = video; 263 this.video = video
261 264
262 let observable; 265 let observable
263 if (this.video.isVideoNSFWForUser(this.authService.getUser())) { 266 if (this.video.isVideoNSFWForUser(this.authService.getUser())) {
264 observable = this.confirmService.confirm('This video is not safe for work. Are you sure you want to watch it?', 'NSFW'); 267 observable = this.confirmService.confirm('This video is not safe for work. Are you sure you want to watch it?', 'NSFW')
265 } else { 268 } else {
266 observable = Observable.of(true); 269 observable = Observable.of(true)
267 } 270 }
268 271
269 observable.subscribe( 272 observable.subscribe(
270 res => { 273 res => {
271 if (res === false) { 274 if (res === false) {
272 return this.router.navigate([ '/videos/list' ]); 275 return this.router.navigate([ '/videos/list' ])
273 } 276 }
274 277
275 this.setOpenGraphTags(); 278 this.setOpenGraphTags()
276 this.loadVideo(); 279 this.loadVideo()
277 this.checkUserRating(); 280 this.checkUserRating()
278 } 281 }
279 ); 282 )
280 } 283 }
281 284
282 private updateVideoRating(oldRating: RateType, newRating: RateType) { 285 private updateVideoRating (oldRating: RateType, newRating: RateType) {
283 let likesToIncrement = 0; 286 let likesToIncrement = 0
284 let dislikesToIncrement = 0; 287 let dislikesToIncrement = 0
285 288
286 if (oldRating) { 289 if (oldRating) {
287 if (oldRating === 'like') likesToIncrement--; 290 if (oldRating === 'like') likesToIncrement--
288 if (oldRating === 'dislike') dislikesToIncrement--; 291 if (oldRating === 'dislike') dislikesToIncrement--
289 } 292 }
290 293
291 if (newRating === 'like') likesToIncrement++; 294 if (newRating === 'like') likesToIncrement++
292 if (newRating === 'dislike') dislikesToIncrement++; 295 if (newRating === 'dislike') dislikesToIncrement++
293 296
294 this.video.likes += likesToIncrement; 297 this.video.likes += likesToIncrement
295 this.video.dislikes += dislikesToIncrement; 298 this.video.dislikes += dislikesToIncrement
296 } 299 }
297 300
298 private loadTooLong() { 301 private loadTooLong () {
299 this.error = true; 302 this.error = true
300 console.error('The video load seems to be abnormally long.'); 303 console.error('The video load seems to be abnormally long.')
301 } 304 }
302 305
303 private setOpenGraphTags() { 306 private setOpenGraphTags () {
304 this.metaService.setTitle(this.video.name); 307 this.metaService.setTitle(this.video.name)
305 308
306 this.metaService.setTag('og:type', 'video'); 309 this.metaService.setTag('og:type', 'video')
307 310
308 this.metaService.setTag('og:title', this.video.name); 311 this.metaService.setTag('og:title', this.video.name)
309 this.metaService.setTag('name', this.video.name); 312 this.metaService.setTag('name', this.video.name)
310 313
311 this.metaService.setTag('og:description', this.video.description); 314 this.metaService.setTag('og:description', this.video.description)
312 this.metaService.setTag('description', this.video.description); 315 this.metaService.setTag('description', this.video.description)
313 316
314 this.metaService.setTag('og:image', this.video.thumbnailPath); 317 this.metaService.setTag('og:image', this.video.thumbnailPath)
315 318
316 this.metaService.setTag('og:duration', this.video.duration.toString()); 319 this.metaService.setTag('og:duration', this.video.duration.toString())
317 320
318 this.metaService.setTag('og:site_name', 'PeerTube'); 321 this.metaService.setTag('og:site_name', 'PeerTube')
319 322
320 this.metaService.setTag('og:url', window.location.href); 323 this.metaService.setTag('og:url', window.location.href)
321 this.metaService.setTag('url', window.location.href); 324 this.metaService.setTag('url', window.location.href)
322 } 325 }
323 326
324 private runInProgress(torrent: any) { 327 private runInProgress (torrent: any) {
325 // Refresh each second 328 // Refresh each second
326 this.torrentInfosInterval = window.setInterval(() => { 329 this.torrentInfosInterval = window.setInterval(() => {
327 this.ngZone.run(() => { 330 this.ngZone.run(() => {
328 this.downloadSpeed = torrent.downloadSpeed; 331 this.downloadSpeed = torrent.downloadSpeed
329 this.numPeers = torrent.numPeers; 332 this.numPeers = torrent.numPeers
330 this.uploadSpeed = torrent.uploadSpeed; 333 this.uploadSpeed = torrent.uploadSpeed
331 }); 334 })
332 }, 1000); 335 }, 1000)
333 } 336 }
334} 337}
diff --git a/client/src/app/videos/video-watch/webtorrent.service.ts b/client/src/app/videos/video-watch/webtorrent.service.ts
index 8936e7992..211894bfd 100644
--- a/client/src/app/videos/video-watch/webtorrent.service.ts
+++ b/client/src/app/videos/video-watch/webtorrent.service.ts
@@ -1,33 +1,33 @@
1import { Injectable } from '@angular/core'; 1import { Injectable } from '@angular/core'
2import { Subject } from 'rxjs/Subject'; 2import { Subject } from 'rxjs/Subject'
3 3
4import * as WebTorrent from 'webtorrent'; 4import * as WebTorrent from 'webtorrent'
5 5
6@Injectable() 6@Injectable()
7export class WebTorrentService { 7export class WebTorrentService {
8 errors = new Subject<Error>(); 8 errors = new Subject<Error>()
9 warnings = new Subject<Error>(); 9 warnings = new Subject<Error>()
10 10
11 // TODO: use WebTorrent @type 11 // TODO: use WebTorrent @type
12 // private client: WebTorrent.Client; 12 // private client: WebTorrent.Client
13 private client: any; 13 private client: any
14 14
15 constructor() { 15 constructor () {
16 this.client = new WebTorrent({ dht: false }); 16 this.client = new WebTorrent({ dht: false })
17 17
18 this.client.on('error', (err) => this.errors.next(err)); 18 this.client.on('error', (err) => this.errors.next(err))
19 this.client.on('warning', (err) => this.warnings.next(err)); 19 this.client.on('warning', (err) => this.warnings.next(err))
20 } 20 }
21 21
22 add(magnetUri: string, callback: Function) { 22 add (magnetUri: string, callback: Function) {
23 return this.client.add(magnetUri, callback); 23 return this.client.add(magnetUri, callback)
24 } 24 }
25 25
26 remove(magnetUri: string) { 26 remove (magnetUri: string) {
27 return this.client.remove(magnetUri); 27 return this.client.remove(magnetUri)
28 } 28 }
29 29
30 has(magnetUri: string) { 30 has (magnetUri: string) {
31 return this.client.get(magnetUri) !== null; 31 return this.client.get(magnetUri) !== null
32 } 32 }
33} 33}
diff --git a/client/src/app/videos/videos-routing.module.ts b/client/src/app/videos/videos-routing.module.ts
index 70968b4d1..7d002abde 100644
--- a/client/src/app/videos/videos-routing.module.ts
+++ b/client/src/app/videos/videos-routing.module.ts
@@ -1,10 +1,10 @@
1import { NgModule } from '@angular/core'; 1import { NgModule } from '@angular/core'
2import { RouterModule, Routes } from '@angular/router'; 2import { RouterModule, Routes } from '@angular/router'
3 3
4import { VideoAddComponent, VideoUpdateComponent } from './video-edit'; 4import { VideoAddComponent, VideoUpdateComponent } from './video-edit'
5import { VideoListComponent } from './video-list'; 5import { VideoListComponent } from './video-list'
6import { VideosComponent } from './videos.component'; 6import { VideosComponent } from './videos.component'
7import { VideoWatchComponent } from './video-watch'; 7import { VideoWatchComponent } from './video-watch'
8 8
9const videosRoutes: Routes = [ 9const videosRoutes: Routes = [
10 { 10 {
@@ -48,7 +48,7 @@ const videosRoutes: Routes = [
48 } 48 }
49 ] 49 ]
50 } 50 }
51]; 51]
52 52
53@NgModule({ 53@NgModule({
54 imports: [ RouterModule.forChild(videosRoutes) ], 54 imports: [ RouterModule.forChild(videosRoutes) ],
diff --git a/client/src/app/videos/videos.component.ts b/client/src/app/videos/videos.component.ts
index 591e7523d..972c2221f 100644
--- a/client/src/app/videos/videos.component.ts
+++ b/client/src/app/videos/videos.component.ts
@@ -1,4 +1,4 @@
1import { Component } from '@angular/core'; 1import { Component } from '@angular/core'
2 2
3@Component({ 3@Component({
4 template: '<router-outlet></router-outlet>' 4 template: '<router-outlet></router-outlet>'
diff --git a/client/src/app/videos/videos.module.ts b/client/src/app/videos/videos.module.ts
index adfbe7031..75a8dd24f 100644
--- a/client/src/app/videos/videos.module.ts
+++ b/client/src/app/videos/videos.module.ts
@@ -1,20 +1,20 @@
1import { NgModule } from '@angular/core'; 1import { NgModule } from '@angular/core'
2 2
3import { TagInputModule } from 'ng2-tag-input'; 3import { TagInputModule } from 'ng2-tag-input'
4 4
5import { VideosRoutingModule } from './videos-routing.module'; 5import { VideosRoutingModule } from './videos-routing.module'
6import { VideosComponent } from './videos.component'; 6import { VideosComponent } from './videos.component'
7import { VideoAddComponent, VideoUpdateComponent } from './video-edit'; 7import { VideoAddComponent, VideoUpdateComponent } from './video-edit'
8import { LoaderComponent, VideoListComponent, VideoMiniatureComponent, VideoSortComponent } from './video-list'; 8import { LoaderComponent, VideoListComponent, VideoMiniatureComponent, VideoSortComponent } from './video-list'
9import { 9import {
10 VideoWatchComponent, 10 VideoWatchComponent,
11 VideoMagnetComponent, 11 VideoMagnetComponent,
12 VideoReportComponent, 12 VideoReportComponent,
13 VideoShareComponent, 13 VideoShareComponent,
14 WebTorrentService 14 WebTorrentService
15} from './video-watch'; 15} from './video-watch'
16import { VideoService } from './shared'; 16import { VideoService } from './shared'
17import { SharedModule } from '../shared'; 17import { SharedModule } from '../shared'
18 18
19@NgModule({ 19@NgModule({
20 imports: [ 20 imports: [
diff --git a/client/src/custom-typings.d.ts b/client/src/custom-typings.d.ts
index 67e069cc6..882d15d54 100644
--- a/client/src/custom-typings.d.ts
+++ b/client/src/custom-typings.d.ts
@@ -1,3 +1,5 @@
1/* tslint: disable */
2
1/* 3/*
2 * Custom Type Definitions 4 * Custom Type Definitions
3 * When including 3rd party modules you also need to include the type definition for the module 5 * When including 3rd party modules you also need to include the type definition for the module
diff --git a/client/src/main.browser.aot.ts b/client/src/main.browser.aot.ts
index 718cb3a80..9635d615b 100644
--- a/client/src/main.browser.aot.ts
+++ b/client/src/main.browser.aot.ts
@@ -1,3 +1,5 @@
1/* tslint: disable */
2
1import { platformBrowser } from '@angular/platform-browser'; 3import { platformBrowser } from '@angular/platform-browser';
2import { decorateModuleRef } from './app/environment'; 4import { decorateModuleRef } from './app/environment';
3 5
diff --git a/client/src/main.browser.ts b/client/src/main.browser.ts
index f627ba7df..aeae06e04 100644
--- a/client/src/main.browser.ts
+++ b/client/src/main.browser.ts
@@ -1,3 +1,5 @@
1/* tslint: disable */
2
1import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
2import { decorateModuleRef } from './app/environment'; 4import { decorateModuleRef } from './app/environment';
3 5
diff --git a/client/src/polyfills.browser.ts b/client/src/polyfills.browser.ts
index 5119cf4b9..b1a631655 100644
--- a/client/src/polyfills.browser.ts
+++ b/client/src/polyfills.browser.ts
@@ -1,3 +1,5 @@
1/* tslint: disable */
2
1// Polyfills 3// Polyfills
2// (these modules are what are in 'angular2/bundles/angular2-polyfills' so don't use that here) 4// (these modules are what are in 'angular2/bundles/angular2-polyfills' so don't use that here)
3 5
diff --git a/client/tslint.json b/client/tslint.json
index 50b90be65..cfad2a5d9 100644
--- a/client/tslint.json
+++ b/client/tslint.json
@@ -1,36 +1,18 @@
1{ 1{
2 "extends": "tslint-config-standard",
2 "rulesDirectory": ["./node_modules/codelyzer"], 3 "rulesDirectory": ["./node_modules/codelyzer"],
3 "rules": { 4 "rules": {
4 "class-name": true,
5 "no-inferrable-types": true, 5 "no-inferrable-types": true,
6 "curly": false,
7 "eofline": true, 6 "eofline": true,
8 "indent": ["spaces"], 7 "indent": ["spaces"],
9 "max-line-length": [true, 140], 8 "max-line-length": [true, 140],
9 "no-floating-promises": false,
10 "no-unused-variable": false, // Bug, wait TypeScript 2.4
10 "member-ordering": [true, 11 "member-ordering": [true,
11 "public-before-private", 12 "public-before-private",
12 "static-before-instance", 13 "static-before-instance",
13 "variables-before-functions" 14 "variables-before-functions"
14 ], 15 ],
15 "no-arg": true,
16 "no-construct": true,
17 "no-duplicate-variable": true,
18 "no-empty": true,
19 "no-eval": true,
20 "no-trailing-whitespace": true,
21 "no-unused-expression": true,
22 "no-use-before-declare": true,
23 "one-line": [true,
24 "check-open-brace",
25 "check-catch",
26 "check-else",
27 "check-whitespace"
28 ],
29 "quotemark": [true, "single"],
30 "semicolon": true,
31 "trailing-comma": true,
32 "triple-equals": true,
33 "variable-name": false,
34 16
35 "directive-selector": [true, "attribute", "my", "camelCase"], 17 "directive-selector": [true, "attribute", "my", "camelCase"],
36 "component-selector": [true, "element", "my", "kebab-case"], 18 "component-selector": [true, "element", "my", "kebab-case"],
@@ -48,23 +30,6 @@
48 "directive-class-suffix": true, 30 "directive-class-suffix": true,
49 "templates-use-public": true, 31 "templates-use-public": true,
50 "no-access-missing-member": true, 32 "no-access-missing-member": true,
51 "invoke-injectable": true, 33 "invoke-injectable": true
52
53 "typedef-whitespace": [ true,
54 {
55 "call-signature": "nospace",
56 "index-signature": "nospace",
57 "parameter": "nospace",
58 "property-declaration": "nospace",
59 "variable-declaration": "nospace"
60 }
61 ],
62 "whitespace": [ true,
63 "check-branch",
64 "check-decl",
65 "check-operator",
66 "check-separator",
67 "check-type"
68 ]
69 } 34 }
70} 35}
diff --git a/client/yarn.lock b/client/yarn.lock
index 68e4116ac..7fd6628b1 100644
--- a/client/yarn.lock
+++ b/client/yarn.lock
@@ -172,10 +172,11 @@ ajv@^4.7.0, ajv@^4.9.1:
172 json-stable-stringify "^1.0.1" 172 json-stable-stringify "^1.0.1"
173 173
174ajv@^5.0.0: 174ajv@^5.0.0:
175 version "5.1.5" 175 version "5.1.6"
176 resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.1.5.tgz#8734931b601f00d4feef7c65738d77d1b65d1f68" 176 resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.1.6.tgz#4b2f1a19dece93d57ac216037e3e9791c7dd1564"
177 dependencies: 177 dependencies:
178 co "^4.6.0" 178 co "^4.6.0"
179 json-schema-traverse "^0.3.0"
179 json-stable-stringify "^1.0.1" 180 json-stable-stringify "^1.0.1"
180 181
181align-text@^0.1.1, align-text@^0.1.3: 182align-text@^0.1.1, align-text@^0.1.3:
@@ -415,10 +416,14 @@ babel-runtime@^6.9.2:
415 core-js "^2.4.0" 416 core-js "^2.4.0"
416 regenerator-runtime "^0.10.0" 417 regenerator-runtime "^0.10.0"
417 418
418balanced-match@^0.4.1, balanced-match@^0.4.2: 419balanced-match@^0.4.2:
419 version "0.4.2" 420 version "0.4.2"
420 resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-0.4.2.tgz#cb3f3e3c732dc0f01ee70b403f302e61d7709838" 421 resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-0.4.2.tgz#cb3f3e3c732dc0f01ee70b403f302e61d7709838"
421 422
423balanced-match@^1.0.0:
424 version "1.0.0"
425 resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
426
422base64-js@^1.0.2: 427base64-js@^1.0.2:
423 version "1.2.0" 428 version "1.2.0"
424 resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.2.0.tgz#a39992d723584811982be5e290bb6a53d86700f1" 429 resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.2.0.tgz#a39992d723584811982be5e290bb6a53d86700f1"
@@ -591,10 +596,10 @@ bootstrap@^3.3.6:
591 resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-3.3.7.tgz#5a389394549f23330875a3b150656574f8a9eb71" 596 resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-3.3.7.tgz#5a389394549f23330875a3b150656574f8a9eb71"
592 597
593brace-expansion@^1.1.7: 598brace-expansion@^1.1.7:
594 version "1.1.7" 599 version "1.1.8"
595 resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.7.tgz#3effc3c50e000531fb720eaff80f0ae8ef23cf59" 600 resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.8.tgz#c07b211c7c952ec1f8efd51a77ef0d1d3990a292"
596 dependencies: 601 dependencies:
597 balanced-match "^0.4.1" 602 balanced-match "^1.0.0"
598 concat-map "0.0.1" 603 concat-map "0.0.1"
599 604
600braces@^1.8.2: 605braces@^1.8.2:
@@ -761,8 +766,8 @@ caniuse-api@^1.5.2:
761 lodash.uniq "^4.5.0" 766 lodash.uniq "^4.5.0"
762 767
763caniuse-db@^1.0.30000529, caniuse-db@^1.0.30000634, caniuse-db@^1.0.30000639: 768caniuse-db@^1.0.30000529, caniuse-db@^1.0.30000634, caniuse-db@^1.0.30000639:
764 version "1.0.30000683" 769 version "1.0.30000686"
765 resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000683.tgz#58b57ed1e0bb9da54eaf1462985147bbe16679fa" 770 resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000686.tgz#d55b479ed6e6402c1fd3f1fd8f46e694d86ea464"
766 771
767cardinal@^1.0.0: 772cardinal@^1.0.0:
768 version "1.0.0" 773 version "1.0.0"
@@ -830,14 +835,14 @@ circular-json@^0.3.1:
830 resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.1.tgz#be8b36aefccde8b3ca7aa2d6afc07a37242c0d2d" 835 resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.1.tgz#be8b36aefccde8b3ca7aa2d6afc07a37242c0d2d"
831 836
832clap@^1.0.9: 837clap@^1.0.9:
833 version "1.1.3" 838 version "1.2.0"
834 resolved "https://registry.yarnpkg.com/clap/-/clap-1.1.3.tgz#b3bd36e93dd4cbfb395a3c26896352445265c05b" 839 resolved "https://registry.yarnpkg.com/clap/-/clap-1.2.0.tgz#59c90fe3e137104746ff19469a27a634ff68c857"
835 dependencies: 840 dependencies:
836 chalk "^1.1.3" 841 chalk "^1.1.3"
837 842
838clean-css@4.1.x: 843clean-css@4.1.x:
839 version "4.1.3" 844 version "4.1.4"
840 resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-4.1.3.tgz#07cfe8980edb20d455ddc23aadcf1e04c6e509ce" 845 resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-4.1.4.tgz#eec8811db27457e0078d8ca921fa81b72fa82bf4"
841 dependencies: 846 dependencies:
842 source-map "0.5.x" 847 source-map "0.5.x"
843 848
@@ -880,14 +885,13 @@ cliui@^3.2.0:
880 strip-ansi "^3.0.1" 885 strip-ansi "^3.0.1"
881 wrap-ansi "^2.0.0" 886 wrap-ansi "^2.0.0"
882 887
883clone-deep@^0.2.4: 888clone-deep@^0.3.0:
884 version "0.2.4" 889 version "0.3.0"
885 resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-0.2.4.tgz#4e73dd09e9fb971cc38670c5dced9c1896481cc6" 890 resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-0.3.0.tgz#348c61ae9cdbe0edfe053d91ff4cc521d790ede8"
886 dependencies: 891 dependencies:
887 for-own "^0.1.3" 892 for-own "^1.0.0"
888 is-plain-object "^2.0.1" 893 is-plain-object "^2.0.1"
889 kind-of "^3.0.2" 894 kind-of "^3.2.2"
890 lazy-cache "^1.0.3"
891 shallow-clone "^0.1.2" 895 shallow-clone "^0.1.2"
892 896
893clone@^1.0.2: 897clone@^1.0.2:
@@ -1400,6 +1404,13 @@ doctrine@1.5.0, doctrine@^1.2.2:
1400 esutils "^2.0.2" 1404 esutils "^2.0.2"
1401 isarray "^1.0.0" 1405 isarray "^1.0.0"
1402 1406
1407doctrine@^0.7.2:
1408 version "0.7.2"
1409 resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-0.7.2.tgz#7cb860359ba3be90e040b26b729ce4bfa654c523"
1410 dependencies:
1411 esutils "^1.1.6"
1412 isarray "0.0.1"
1413
1403doctrine@^2.0.0: 1414doctrine@^2.0.0:
1404 version "2.0.0" 1415 version "2.0.0"
1405 resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.0.0.tgz#c73d8d2909d22291e1a007a395804da8b665fe63" 1416 resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.0.0.tgz#c73d8d2909d22291e1a007a395804da8b665fe63"
@@ -1776,6 +1787,10 @@ estree-walker@^0.3.0:
1776 version "0.3.1" 1787 version "0.3.1"
1777 resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-0.3.1.tgz#e6b1a51cf7292524e7237c312e5fe6660c1ce1aa" 1788 resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-0.3.1.tgz#e6b1a51cf7292524e7237c312e5fe6660c1ce1aa"
1778 1789
1790esutils@^1.1.6:
1791 version "1.1.6"
1792 resolved "https://registry.yarnpkg.com/esutils/-/esutils-1.1.6.tgz#c01ccaa9ae4b897c6d0c3e210ae52f3c7a844375"
1793
1779esutils@^2.0.2: 1794esutils@^2.0.2:
1780 version "2.0.2" 1795 version "2.0.2"
1781 resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b" 1796 resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b"
@@ -2008,12 +2023,18 @@ for-in@^1.0.1:
2008 version "1.0.2" 2023 version "1.0.2"
2009 resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" 2024 resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
2010 2025
2011for-own@^0.1.3, for-own@^0.1.4: 2026for-own@^0.1.4:
2012 version "0.1.5" 2027 version "0.1.5"
2013 resolved "https://registry.yarnpkg.com/for-own/-/for-own-0.1.5.tgz#5265c681a4f294dabbf17c9509b6763aa84510ce" 2028 resolved "https://registry.yarnpkg.com/for-own/-/for-own-0.1.5.tgz#5265c681a4f294dabbf17c9509b6763aa84510ce"
2014 dependencies: 2029 dependencies:
2015 for-in "^1.0.1" 2030 for-in "^1.0.1"
2016 2031
2032for-own@^1.0.0:
2033 version "1.0.0"
2034 resolved "https://registry.yarnpkg.com/for-own/-/for-own-1.0.0.tgz#c63332f415cedc4b04dbfe70cf836494c53cb44b"
2035 dependencies:
2036 for-in "^1.0.1"
2037
2017foreach@^2.0.5: 2038foreach@^2.0.5:
2018 version "2.0.5" 2039 version "2.0.5"
2019 resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99" 2040 resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99"
@@ -2064,11 +2085,11 @@ fs.realpath@^1.0.0:
2064 resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" 2085 resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
2065 2086
2066fsevents@^1.0.0: 2087fsevents@^1.0.0:
2067 version "1.1.1" 2088 version "1.1.2"
2068 resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.1.1.tgz#f19fd28f43eeaf761680e519a203c4d0b3d31aff" 2089 resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.1.2.tgz#3282b713fb3ad80ede0e9fcf4611b5aa6fc033f4"
2069 dependencies: 2090 dependencies:
2070 nan "^2.3.0" 2091 nan "^2.3.0"
2071 node-pre-gyp "^0.6.29" 2092 node-pre-gyp "^0.6.36"
2072 2093
2073fstream-ignore@^1.0.5: 2094fstream-ignore@^1.0.5:
2074 version "1.0.5" 2095 version "1.0.5"
@@ -2760,6 +2781,10 @@ json-loader@^0.5.4:
2760 version "0.5.4" 2781 version "0.5.4"
2761 resolved "https://registry.yarnpkg.com/json-loader/-/json-loader-0.5.4.tgz#8baa1365a632f58a3c46d20175fc6002c96e37de" 2782 resolved "https://registry.yarnpkg.com/json-loader/-/json-loader-0.5.4.tgz#8baa1365a632f58a3c46d20175fc6002c96e37de"
2762 2783
2784json-schema-traverse@^0.3.0:
2785 version "0.3.0"
2786 resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.3.0.tgz#0016c0b1ca1efe46d44d37541bcdfc19dcfae0db"
2787
2763json-schema@0.2.3: 2788json-schema@0.2.3:
2764 version "0.2.3" 2789 version "0.2.3"
2765 resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" 2790 resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13"
@@ -2841,7 +2866,7 @@ kind-of@^2.0.1:
2841 dependencies: 2866 dependencies:
2842 is-buffer "^1.0.2" 2867 is-buffer "^1.0.2"
2843 2868
2844kind-of@^3.0.2: 2869kind-of@^3.0.2, kind-of@^3.2.2:
2845 version "3.2.2" 2870 version "3.2.2"
2846 resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" 2871 resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64"
2847 dependencies: 2872 dependencies:
@@ -3508,7 +3533,7 @@ node-notifier@^4.1.0:
3508 shellwords "^0.1.0" 3533 shellwords "^0.1.0"
3509 which "^1.0.5" 3534 which "^1.0.5"
3510 3535
3511node-pre-gyp@^0.6.29: 3536node-pre-gyp@^0.6.36:
3512 version "0.6.36" 3537 version "0.6.36"
3513 resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.6.36.tgz#db604112cb74e0d477554e9b505b17abddfab786" 3538 resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.6.36.tgz#db604112cb74e0d477554e9b505b17abddfab786"
3514 dependencies: 3539 dependencies:
@@ -3880,10 +3905,14 @@ piece-length@^1.0.0:
3880 dependencies: 3905 dependencies:
3881 closest-to "~2.0.0" 3906 closest-to "~2.0.0"
3882 3907
3883pify@^2.0.0, pify@^2.3.0: 3908pify@^2.0.0:
3884 version "2.3.0" 3909 version "2.3.0"
3885 resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" 3910 resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
3886 3911
3912pify@^3.0.0:
3913 version "3.0.0"
3914 resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176"
3915
3887pinkie-promise@^2.0.0: 3916pinkie-promise@^2.0.0:
3888 version "2.0.1" 3917 version "2.0.1"
3889 resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa" 3918 resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa"
@@ -4172,8 +4201,8 @@ postcss@^5.0.10, postcss@^5.0.11, postcss@^5.0.12, postcss@^5.0.13, postcss@^5.0
4172 supports-color "^3.2.3" 4201 supports-color "^3.2.3"
4173 4202
4174postcss@^6.0.1: 4203postcss@^6.0.1:
4175 version "6.0.1" 4204 version "6.0.2"
4176 resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.1.tgz#000dbd1f8eef217aa368b9a212c5fc40b2a8f3f2" 4205 resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.2.tgz#5c4fea589f0ac3b00caa75b1cbc3a284195b7e5d"
4177 dependencies: 4206 dependencies:
4178 chalk "^1.1.3" 4207 chalk "^1.1.3"
4179 source-map "^0.5.6" 4208 source-map "^0.5.6"
@@ -4588,8 +4617,8 @@ resolve-from@^1.0.0:
4588 resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-1.0.1.tgz#26cbfe935d1aeeeabb29bc3fe5aeb01e93d44226" 4617 resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-1.0.1.tgz#26cbfe935d1aeeeabb29bc3fe5aeb01e93d44226"
4589 4618
4590resolve-url-loader@^2.0.0: 4619resolve-url-loader@^2.0.0:
4591 version "2.0.2" 4620 version "2.0.3"
4592 resolved "https://registry.yarnpkg.com/resolve-url-loader/-/resolve-url-loader-2.0.2.tgz#c465e97ea0a4791f3961f766cea775ff2e3ceb8c" 4621 resolved "https://registry.yarnpkg.com/resolve-url-loader/-/resolve-url-loader-2.0.3.tgz#f54cd1b040e8f0ab72b2cb32c9fbb8544152d9e9"
4593 dependencies: 4622 dependencies:
4594 adjust-sourcemap-loader "^1.1.0" 4623 adjust-sourcemap-loader "^1.1.0"
4595 camelcase "^4.0.0" 4624 camelcase "^4.0.0"
@@ -4710,14 +4739,14 @@ sass-graph@^2.1.1:
4710 yargs "^7.0.0" 4739 yargs "^7.0.0"
4711 4740
4712sass-loader@^6.0.3: 4741sass-loader@^6.0.3:
4713 version "6.0.5" 4742 version "6.0.6"
4714 resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-6.0.5.tgz#a847910f36442aa56c5985879d54eb519e24a328" 4743 resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-6.0.6.tgz#e9d5e6c1f155faa32a4b26d7a9b7107c225e40f9"
4715 dependencies: 4744 dependencies:
4716 async "^2.1.5" 4745 async "^2.1.5"
4717 clone-deep "^0.2.4" 4746 clone-deep "^0.3.0"
4718 loader-utils "^1.0.1" 4747 loader-utils "^1.0.1"
4719 lodash.tail "^4.1.1" 4748 lodash.tail "^4.1.1"
4720 pify "^2.3.0" 4749 pify "^3.0.0"
4721 4750
4722sass-resources-loader@^1.2.1: 4751sass-resources-loader@^1.2.1:
4723 version "1.2.1" 4752 version "1.2.1"
@@ -5093,8 +5122,8 @@ stream-browserify@^2.0.1:
5093 readable-stream "^2.0.2" 5122 readable-stream "^2.0.2"
5094 5123
5095stream-http@^2.3.1: 5124stream-http@^2.3.1:
5096 version "2.7.1" 5125 version "2.7.2"
5097 resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.7.1.tgz#546a51741ad5a6b07e9e31b0b10441a917df528a" 5126 resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.7.2.tgz#40a050ec8dc3b53b33d9909415c02c0bf1abfbad"
5098 dependencies: 5127 dependencies:
5099 builtin-status-codes "^3.0.0" 5128 builtin-status-codes "^3.0.0"
5100 inherits "^2.0.1" 5129 inherits "^2.0.1"
@@ -5380,10 +5409,24 @@ tsickle@^0.21.0:
5380 source-map "^0.5.6" 5409 source-map "^0.5.6"
5381 source-map-support "^0.4.2" 5410 source-map-support "^0.4.2"
5382 5411
5383tslib@^1.5.0, tslib@^1.6.0, tslib@^1.7.1: 5412tslib@^1.0.0, tslib@^1.5.0, tslib@^1.6.0, tslib@^1.7.1:
5384 version "1.7.1" 5413 version "1.7.1"
5385 resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.7.1.tgz#bc8004164691923a79fe8378bbeb3da2017538ec" 5414 resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.7.1.tgz#bc8004164691923a79fe8378bbeb3da2017538ec"
5386 5415
5416tslint-config-standard@^6.0.1:
5417 version "6.0.1"
5418 resolved "https://registry.yarnpkg.com/tslint-config-standard/-/tslint-config-standard-6.0.1.tgz#a04ba0a794759e877287056f549b081e47a56d6c"
5419 dependencies:
5420 tslint-eslint-rules "^4.0.0"
5421
5422tslint-eslint-rules@^4.0.0:
5423 version "4.1.1"
5424 resolved "https://registry.yarnpkg.com/tslint-eslint-rules/-/tslint-eslint-rules-4.1.1.tgz#7c30e7882f26bc276bff91d2384975c69daf88ba"
5425 dependencies:
5426 doctrine "^0.7.2"
5427 tslib "^1.0.0"
5428 tsutils "^1.4.0"
5429
5387tslint-loader@^3.3.0: 5430tslint-loader@^3.3.0:
5388 version "3.5.3" 5431 version "3.5.3"
5389 resolved "https://registry.yarnpkg.com/tslint-loader/-/tslint-loader-3.5.3.tgz#343f74122d94f356b689457d3f59f64a69ab606f" 5432 resolved "https://registry.yarnpkg.com/tslint-loader/-/tslint-loader-3.5.3.tgz#343f74122d94f356b689457d3f59f64a69ab606f"
@@ -5413,6 +5456,10 @@ tsml@1.0.1:
5413 version "1.0.1" 5456 version "1.0.1"
5414 resolved "https://registry.yarnpkg.com/tsml/-/tsml-1.0.1.tgz#89f8218b9d9e257f47d7f6b56d01c5a4d2c68fc3" 5457 resolved "https://registry.yarnpkg.com/tsml/-/tsml-1.0.1.tgz#89f8218b9d9e257f47d7f6b56d01c5a4d2c68fc3"
5415 5458
5459tsutils@^1.4.0:
5460 version "1.9.1"
5461 resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-1.9.1.tgz#b9f9ab44e55af9681831d5f28d0aeeaf5c750cb0"
5462
5416tsutils@^2.3.0: 5463tsutils@^2.3.0:
5417 version "2.4.0" 5464 version "2.4.0"
5418 resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.4.0.tgz#ad4ce6dba0e5a3edbddf8626b7ca040782189fea" 5465 resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.4.0.tgz#ad4ce6dba0e5a3edbddf8626b7ca040782189fea"
@@ -5458,20 +5505,20 @@ typedarray@^0.0.6:
5458 version "0.0.6" 5505 version "0.0.6"
5459 resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" 5506 resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
5460 5507
5461typescript@~2.3.0: 5508typescript@~2.4.0:
5462 version "2.3.4" 5509 version "2.4.0"
5463 resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.3.4.tgz#3d38321828231e434f287514959c37a82b629f42" 5510 resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.4.0.tgz#aef5a8d404beba36ad339abf079ddddfffba86dd"
5464 5511
5465uglify-js@3.0.x: 5512uglify-js@3.0.x:
5466 version "3.0.15" 5513 version "3.0.17"
5467 resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.0.15.tgz#aacb323a846b234602270dead8a32441a8806f42" 5514 resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.0.17.tgz#d228cd55c2df9b3d2f53f147568cb4cc4a72cc06"
5468 dependencies: 5515 dependencies:
5469 commander "~2.9.0" 5516 commander "~2.9.0"
5470 source-map "~0.5.1" 5517 source-map "~0.5.1"
5471 5518
5472uglify-js@^2.8.27: 5519uglify-js@^2.8.27:
5473 version "2.8.28" 5520 version "2.8.29"
5474 resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.8.28.tgz#e335032df9bb20dcb918f164589d5af47f38834a" 5521 resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.8.29.tgz#29c5733148057bb4e1f75df35b7a9cb72e6a59dd"
5475 dependencies: 5522 dependencies:
5476 source-map "~0.5.1" 5523 source-map "~0.5.1"
5477 yargs "~3.10.0" 5524 yargs "~3.10.0"
@@ -5533,8 +5580,8 @@ urix@^0.1.0, urix@~0.1.0:
5533 resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" 5580 resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72"
5534 5581
5535url-loader@^0.5.7: 5582url-loader@^0.5.7:
5536 version "0.5.8" 5583 version "0.5.9"
5537 resolved "https://registry.yarnpkg.com/url-loader/-/url-loader-0.5.8.tgz#b9183b1801e0f847718673673040bc9dc1c715c5" 5584 resolved "https://registry.yarnpkg.com/url-loader/-/url-loader-0.5.9.tgz#cc8fea82c7b906e7777019250869e569e995c295"
5538 dependencies: 5585 dependencies:
5539 loader-utils "^1.0.2" 5586 loader-utils "^1.0.2"
5540 mime "1.3.x" 5587 mime "1.3.x"