aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--client/package.json1
-rw-r--r--client/src/app/+admin/friends/friend-add/friend-add.component.ts2
-rw-r--r--client/src/app/+admin/friends/friend-list/friend-list.component.ts6
-rw-r--r--client/src/app/+admin/request-schedulers/request-schedulers-stats/request-schedulers-stats.component.ts2
-rw-r--r--client/src/app/+admin/users/user-list/user-list.component.ts4
-rw-r--r--client/src/app/+admin/video-abuses/video-abuse-list/video-abuse-list.component.ts2
-rw-r--r--client/src/app/app.component.ts3
-rw-r--r--client/src/app/shared/forms/form-validators/video.ts7
-rw-r--r--client/src/app/shared/rest/rest-extractor.service.ts18
-rw-r--r--client/src/app/shared/shared.module.ts4
-rw-r--r--client/src/app/videos/shared/video.service.ts25
-rw-r--r--client/src/app/videos/video-edit/video-add.component.html21
-rw-r--r--client/src/app/videos/video-edit/video-add.component.ts160
-rw-r--r--client/src/app/videos/video-edit/video-update.component.ts1
-rw-r--r--client/src/app/videos/video-watch/video-report.component.ts2
-rw-r--r--client/src/app/videos/video-watch/video-watch.component.ts6
-rw-r--r--client/yarn.lock4
-rw-r--r--server/middlewares/validators/pods.ts6
-rw-r--r--server/middlewares/validators/users.ts36
-rw-r--r--server/middlewares/validators/videos.ts42
20 files changed, 189 insertions, 163 deletions
diff --git a/client/package.json b/client/package.json
index caec34e44..8a82a294e 100644
--- a/client/package.json
+++ b/client/package.json
@@ -61,7 +61,6 @@
61 "intl": "^1.2.4", 61 "intl": "^1.2.4",
62 "json-loader": "^0.5.4", 62 "json-loader": "^0.5.4",
63 "ng-router-loader": "^2.0.0", 63 "ng-router-loader": "^2.0.0",
64 "ng2-file-upload": "^1.1.4-2",
65 "ngc-webpack": "3.2.2", 64 "ngc-webpack": "3.2.2",
66 "ngx-bootstrap": "1.9.1", 65 "ngx-bootstrap": "1.9.1",
67 "ngx-chips": "1.5.3", 66 "ngx-chips": "1.5.3",
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 e0b73dfa3..6580e1b88 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
@@ -98,7 +98,7 @@ export class FriendAddComponent implements OnInit {
98 setTimeout(() => this.router.navigate([ '/admin/friends/list' ]), 1000) 98 setTimeout(() => this.router.navigate([ '/admin/friends/list' ]), 1000)
99 }, 99 },
100 100
101 err => this.notificationsService.error('Error', err) 101 err => this.notificationsService.error('Error', err.message)
102 ) 102 )
103 } 103 }
104 ) 104 )
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 6a8bd492c..4af39c47e 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
@@ -40,7 +40,7 @@ export class FriendListComponent implements OnInit {
40 this.loadData() 40 this.loadData()
41 }, 41 },
42 42
43 err => this.notificationsService.error('Error', err) 43 err => this.notificationsService.error('Error', err.message)
44 ) 44 )
45 } 45 }
46 ) 46 )
@@ -59,7 +59,7 @@ export class FriendListComponent implements OnInit {
59 this.loadData() 59 this.loadData()
60 }, 60 },
61 61
62 err => this.notificationsService.error('Error', err) 62 err => this.notificationsService.error('Error', err.message)
63 ) 63 )
64 } 64 }
65 ) 65 )
@@ -72,7 +72,7 @@ export class FriendListComponent implements OnInit {
72 this.friends = resultList.data 72 this.friends = resultList.data
73 }, 73 },
74 74
75 err => this.notificationsService.error('Error', err) 75 err => this.notificationsService.error('Error', err.message)
76 ) 76 )
77 } 77 }
78} 78}
diff --git a/client/src/app/+admin/request-schedulers/request-schedulers-stats/request-schedulers-stats.component.ts b/client/src/app/+admin/request-schedulers/request-schedulers-stats/request-schedulers-stats.component.ts
index 1edeb31fe..1654827ab 100644
--- a/client/src/app/+admin/request-schedulers/request-schedulers-stats/request-schedulers-stats.component.ts
+++ b/client/src/app/+admin/request-schedulers/request-schedulers-stats/request-schedulers-stats.component.ts
@@ -57,7 +57,7 @@ export class RequestSchedulersStatsComponent implements OnInit, OnDestroy {
57 this.requestService.getStats().subscribe( 57 this.requestService.getStats().subscribe(
58 stats => this.stats = stats, 58 stats => this.stats = stats,
59 59
60 err => this.notificationsService.error('Error', err) 60 err => this.notificationsService.error('Error', err.message)
61 ) 61 )
62 } 62 }
63 63
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 c3fa55825..73b5c0ce9 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
@@ -47,7 +47,7 @@ export class UserListComponent extends RestTable implements OnInit {
47 this.loadData() 47 this.loadData()
48 }, 48 },
49 49
50 err => this.notificationsService.error('Error', err) 50 err => this.notificationsService.error('Error', err.message)
51 ) 51 )
52 } 52 }
53 ) 53 )
@@ -65,7 +65,7 @@ export class UserListComponent extends RestTable implements OnInit {
65 this.totalRecords = resultList.total 65 this.totalRecords = resultList.total
66 }, 66 },
67 67
68 err => this.notificationsService.error('Error', err) 68 err => this.notificationsService.error('Error', err.message)
69 ) 69 )
70 } 70 }
71} 71}
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 5a0131a81..92de11f0e 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
@@ -40,7 +40,7 @@ export class VideoAbuseListComponent extends RestTable implements OnInit {
40 this.totalRecords = resultList.total 40 this.totalRecords = resultList.total
41 }, 41 },
42 42
43 err => this.notificationsService.error('Error', err) 43 err => this.notificationsService.error('Error', err.message)
44 ) 44 )
45 } 45 }
46} 46}
diff --git a/client/src/app/app.component.ts b/client/src/app/app.component.ts
index 57bf64f69..ae86bc96f 100644
--- a/client/src/app/app.component.ts
+++ b/client/src/app/app.component.ts
@@ -31,8 +31,7 @@ export class AppComponent implements OnInit {
31 private authService: AuthService, 31 private authService: AuthService,
32 private configService: ConfigService, 32 private configService: ConfigService,
33 private userService: UserService, 33 private userService: UserService,
34 private videoService: VideoService, 34 private videoService: VideoService
35 viewContainerRef: ViewContainerRef
36 ) {} 35 ) {}
37 36
38 ngOnInit () { 37 ngOnInit () {
diff --git a/client/src/app/shared/forms/form-validators/video.ts b/client/src/app/shared/forms/form-validators/video.ts
index 6542cf8f6..32b1f1c2e 100644
--- a/client/src/app/shared/forms/form-validators/video.ts
+++ b/client/src/app/shared/forms/form-validators/video.ts
@@ -44,3 +44,10 @@ export const VIDEO_TAGS = {
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}
47
48export const VIDEO_FILE = {
49 VALIDATORS: [ Validators.required ],
50 MESSAGES: {
51 'required': 'Video file is required.'
52 }
53}
diff --git a/client/src/app/shared/rest/rest-extractor.service.ts b/client/src/app/shared/rest/rest-extractor.service.ts
index 32dad5c73..1f6222da8 100644
--- a/client/src/app/shared/rest/rest-extractor.service.ts
+++ b/client/src/app/shared/rest/rest-extractor.service.ts
@@ -40,19 +40,29 @@ export class RestExtractor {
40 handleError (err: HttpErrorResponse) { 40 handleError (err: HttpErrorResponse) {
41 let errorMessage 41 let errorMessage
42 42
43 console.log(err)
44
43 if (err.error instanceof Error) { 45 if (err.error instanceof Error) {
44 // A client-side or network error occurred. Handle it accordingly. 46 // A client-side or network error occurred. Handle it accordingly.
45 errorMessage = err.error.message 47 errorMessage = err.error.message
46 console.error('An error occurred:', errorMessage) 48 console.error('An error occurred:', errorMessage)
47 } else if (err.status !== undefined) { 49 } else if (err.status !== undefined) {
48 // The backend returned an unsuccessful response code. 50 const body = err.error
49 // The response body may contain clues as to what went wrong, 51 errorMessage = body.error
50 errorMessage = err.error
51 console.error(`Backend returned code ${err.status}, body was: ${errorMessage}`) 52 console.error(`Backend returned code ${err.status}, body was: ${errorMessage}`)
52 } else { 53 } else {
53 errorMessage = err 54 errorMessage = err
54 } 55 }
55 56
56 return Observable.throw(errorMessage) 57 const errorObj = {
58 message: errorMessage,
59 status: undefined
60 }
61
62 if (err.status) {
63 errorObj.status = err.status
64 }
65
66 return Observable.throw(errorObj)
57 } 67 }
58} 68}
diff --git a/client/src/app/shared/shared.module.ts b/client/src/app/shared/shared.module.ts
index 118ce822d..a28ac322d 100644
--- a/client/src/app/shared/shared.module.ts
+++ b/client/src/app/shared/shared.module.ts
@@ -10,7 +10,6 @@ import { 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'
14import { DataTableModule, SharedModule as PrimeSharedModule } from 'primeng/primeng' 13import { DataTableModule, SharedModule as PrimeSharedModule } from 'primeng/primeng'
15 14
16import { AUTH_INTERCEPTOR_PROVIDER } from './auth' 15import { AUTH_INTERCEPTOR_PROVIDER } from './auth'
@@ -32,8 +31,6 @@ import { VideoAbuseService } from './video-abuse'
32 PaginationModule.forRoot(), 31 PaginationModule.forRoot(),
33 ProgressbarModule.forRoot(), 32 ProgressbarModule.forRoot(),
34 33
35 FileUploadModule,
36
37 DataTableModule, 34 DataTableModule,
38 PrimeSharedModule 35 PrimeSharedModule
39 ], 36 ],
@@ -52,7 +49,6 @@ import { VideoAbuseService } from './video-abuse'
52 HttpClientModule, 49 HttpClientModule,
53 50
54 BsDropdownModule, 51 BsDropdownModule,
55 FileUploadModule,
56 ModalModule, 52 ModalModule,
57 PaginationModule, 53 PaginationModule,
58 ProgressbarModule, 54 ProgressbarModule,
diff --git a/client/src/app/videos/shared/video.service.ts b/client/src/app/videos/shared/video.service.ts
index b6d2a0666..cfce4cb16 100644
--- a/client/src/app/videos/shared/video.service.ts
+++ b/client/src/app/videos/shared/video.service.ts
@@ -2,7 +2,7 @@ import { 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'
5import { HttpClient, HttpParams } from '@angular/common/http' 5import { HttpClient, HttpParams, HttpRequest } from '@angular/common/http'
6 6
7import { Search } from '../../shared' 7import { Search } from '../../shared'
8import { SortField } from './sort-field.type' 8import { SortField } from './sort-field.type'
@@ -14,13 +14,14 @@ import {
14import { Video } from './video.model' 14import { Video } from './video.model'
15import { VideoPagination } from './video-pagination.model' 15import { VideoPagination } from './video-pagination.model'
16import { 16import {
17UserVideoRate, 17 VideoCreate,
18VideoRateType, 18 UserVideoRate,
19VideoUpdate, 19 VideoRateType,
20VideoAbuseCreate, 20 VideoUpdate,
21UserVideoRateUpdate, 21 VideoAbuseCreate,
22Video as VideoServerModel, 22 UserVideoRateUpdate,
23ResultList 23 Video as VideoServerModel,
24 ResultList
24} from '../../../../../shared' 25} from '../../../../../shared'
25 26
26@Injectable() 27@Injectable()
@@ -73,6 +74,14 @@ export class VideoService {
73 .catch(this.restExtractor.handleError) 74 .catch(this.restExtractor.handleError)
74 } 75 }
75 76
77 // uploadVideo (video: VideoCreate) {
78 uploadVideo (video: any) {
79 const req = new HttpRequest('POST', `${VideoService.BASE_VIDEO_URL}/upload`, video, { reportProgress: true })
80
81 return this.authHttp.request(req)
82 .catch(this.restExtractor.handleError)
83 }
84
76 getVideos (videoPagination: VideoPagination, sort: SortField) { 85 getVideos (videoPagination: VideoPagination, sort: SortField) {
77 const pagination = this.videoPaginationToRestPagination(videoPagination) 86 const pagination = this.videoPaginationToRestPagination(videoPagination)
78 87
diff --git a/client/src/app/videos/video-edit/video-add.component.html b/client/src/app/videos/video-edit/video-add.component.html
index 7ad671ae7..cf8fc2b80 100644
--- a/client/src/app/videos/video-edit/video-add.component.html
+++ b/client/src/app/videos/video-edit/video-add.component.html
@@ -3,7 +3,7 @@
3 3
4 <h3>Upload a video</h3> 4 <h3>Upload a video</h3>
5 5
6 <div *ngIf="error" class="alert alert-danger">{{ error }}</div> 6 <div *ngIf="error !== undefined" class="alert alert-danger">{{ error }}</div>
7 7
8 <form novalidate [formGroup]="form"> 8 <form novalidate [formGroup]="form">
9 <div class="form-group"> 9 <div class="form-group">
@@ -62,7 +62,7 @@
62 </div> 62 </div>
63 63
64 <div class="form-group"> 64 <div class="form-group">
65 <label for="tags" class="label-tags">Tags</label> <span class="little-information">(press enter to add the tag)</span> 65 <label class="label-tags">Tags</label> <span class="little-information">(press enter to add the tag)</span>
66 <tag-input 66 <tag-input
67 [ngModel]="tags" [validators]="tagValidators" [errorMessages]="tagValidatorsMessages" 67 [ngModel]="tags" [validators]="tagValidators" [errorMessages]="tagValidatorsMessages"
68 formControlName="tags" maxItems="3" modelAsStrings="true" 68 formControlName="tags" maxItems="3" modelAsStrings="true"
@@ -71,25 +71,22 @@
71 71
72 <div class="form-group"> 72 <div class="form-group">
73 <label for="videofile">File</label> 73 <label for="videofile">File</label>
74 <div class="btn btn-default btn-file" [ngClass]="{ 'disabled': filename !== null }" > 74 <div class="btn btn-default btn-file">
75 <span>Select the video...</span> 75 <span>Select the video...</span>
76 <input 76 <input #videofileInput type="file" name="videofile" id="videofile" (change)="fileChange($event)" />
77 type="file" name="videofile" id="videofile" 77 <input type="hidden" name="videofileHidden" formControlName="videofile"/>
78 ng2FileSelect [uploader]="uploader" [disabled]="filename !== null"
79 (change)="fileChanged()"
80 >
81 </div> 78 </div>
82 </div> 79 </div>
83 80
84 <div class="file-to-upload"> 81 <div class="file-to-upload">
85 <div class="file" *ngIf="uploader.queue.length > 0"> 82 <div class="file" *ngIf="filename">
86 <span class="filename">{{ filename }}</span> 83 <span class="filename">{{ filename }}</span>
87 <span class="glyphicon glyphicon-remove" (click)="removeFile()"></span> 84 <span class="glyphicon glyphicon-remove" (click)="removeFile()"></span>
88 </div> 85 </div>
89 </div> 86 </div>
90 87
91 <div *ngIf="fileError" class="alert alert-danger"> 88 <div *ngIf="formErrors.videofile" class="alert alert-danger">
92 {{ fileError }} 89 {{ formErrors.videofile }}
93 </div> 90 </div>
94 91
95 <div class="form-group"> 92 <div class="form-group">
@@ -105,7 +102,7 @@
105 </div> 102 </div>
106 103
107 <div class="progress"> 104 <div class="progress">
108 <progressbar [value]="uploader.progress" max="100"></progressbar> 105 <progressbar [value]="progressPercent" max="100"></progressbar>
109 </div> 106 </div>
110 107
111 <div class="form-group"> 108 <div class="form-group">
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 42b11cd08..537ef9bc3 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,9 @@
1import { Component, ElementRef, OnInit } from '@angular/core' 1import { Component, OnInit, ViewChild } 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'
6import { NotificationsService } from 'angular2-notifications' 5import { NotificationsService } from 'angular2-notifications'
7 6
8import { AuthService } from '../../core'
9import { 7import {
10 FormReactive, 8 FormReactive,
11 VIDEO_NAME, 9 VIDEO_NAME,
@@ -17,6 +15,8 @@ import {
17} from '../../shared' 15} from '../../shared'
18import { VideoService } from '../shared' 16import { VideoService } from '../shared'
19import { VideoCreate } from '../../../../../shared' 17import { VideoCreate } from '../../../../../shared'
18import { HttpEventType, HttpResponse } from '@angular/common/http'
19import { VIDEO_FILE } from '../../shared/forms/form-validators/video'
20 20
21@Component({ 21@Component({
22 selector: 'my-videos-add', 22 selector: 'my-videos-add',
@@ -25,8 +25,10 @@ import { VideoCreate } from '../../../../../shared'
25}) 25})
26 26
27export class VideoAddComponent extends FormReactive implements OnInit { 27export class VideoAddComponent extends FormReactive implements OnInit {
28 @ViewChild('videofileInput') videofileInput
29
30 progressPercent = 0
28 tags: string[] = [] 31 tags: string[] = []
29 uploader: FileUploader
30 videoCategories = [] 32 videoCategories = []
31 videoLicences = [] 33 videoLicences = []
32 videoLanguages = [] 34 videoLanguages = []
@@ -34,29 +36,26 @@ export class VideoAddComponent extends FormReactive implements OnInit {
34 tagValidators = VIDEO_TAGS.VALIDATORS 36 tagValidators = VIDEO_TAGS.VALIDATORS
35 tagValidatorsMessages = VIDEO_TAGS.MESSAGES 37 tagValidatorsMessages = VIDEO_TAGS.MESSAGES
36 38
37 error: string = null 39 error: string
38 form: FormGroup 40 form: FormGroup
39 formErrors = { 41 formErrors = {
40 name: '', 42 name: '',
41 category: '', 43 category: '',
42 licence: '', 44 licence: '',
43 language: '', 45 language: '',
44 description: '' 46 description: '',
47 videofile: ''
45 } 48 }
46 validationMessages = { 49 validationMessages = {
47 name: VIDEO_NAME.MESSAGES, 50 name: VIDEO_NAME.MESSAGES,
48 category: VIDEO_CATEGORY.MESSAGES, 51 category: VIDEO_CATEGORY.MESSAGES,
49 licence: VIDEO_LICENCE.MESSAGES, 52 licence: VIDEO_LICENCE.MESSAGES,
50 language: VIDEO_LANGUAGE.MESSAGES, 53 language: VIDEO_LANGUAGE.MESSAGES,
51 description: VIDEO_DESCRIPTION.MESSAGES 54 description: VIDEO_DESCRIPTION.MESSAGES,
55 videofile: VIDEO_FILE.MESSAGES
52 } 56 }
53 57
54 // Special error messages
55 fileError = ''
56
57 constructor ( 58 constructor (
58 private authService: AuthService,
59 private elementRef: ElementRef,
60 private formBuilder: FormBuilder, 59 private formBuilder: FormBuilder,
61 private router: Router, 60 private router: Router,
62 private notificationsService: NotificationsService, 61 private notificationsService: NotificationsService,
@@ -66,11 +65,7 @@ export class VideoAddComponent extends FormReactive implements OnInit {
66 } 65 }
67 66
68 get filename () { 67 get filename () {
69 if (this.uploader.queue.length === 0) { 68 return this.form.value['videofile']
70 return null
71 }
72
73 return this.uploader.queue[0].file.name
74 } 69 }
75 70
76 buildForm () { 71 buildForm () {
@@ -81,7 +76,8 @@ export class VideoAddComponent extends FormReactive implements OnInit {
81 licence: [ '', VIDEO_LICENCE.VALIDATORS ], 76 licence: [ '', VIDEO_LICENCE.VALIDATORS ],
82 language: [ '', VIDEO_LANGUAGE.VALIDATORS ], 77 language: [ '', VIDEO_LANGUAGE.VALIDATORS ],
83 description: [ '', VIDEO_DESCRIPTION.VALIDATORS ], 78 description: [ '', VIDEO_DESCRIPTION.VALIDATORS ],
84 tags: [ ''] 79 videofile: [ '', VIDEO_FILE.VALIDATORS ],
80 tags: [ '' ]
85 }) 81 })
86 82
87 this.form.valueChanges.subscribe(data => this.onValueChanged(data)) 83 this.form.valueChanges.subscribe(data => this.onValueChanged(data))
@@ -92,60 +88,24 @@ export class VideoAddComponent extends FormReactive implements OnInit {
92 this.videoLicences = this.videoService.videoLicences 88 this.videoLicences = this.videoService.videoLicences
93 this.videoLanguages = this.videoService.videoLanguages 89 this.videoLanguages = this.videoService.videoLanguages
94 90
95 this.uploader = new FileUploader({
96 authToken: this.authService.getRequestHeaderValue(),
97 queueLimit: 1,
98 url: API_URL + '/api/v1/videos/upload',
99 removeAfterUpload: true
100 })
101
102 this.uploader.onBuildItemForm = (item, form: FormData) => {
103 const formValue: VideoCreate = this.form.value
104
105 const name = formValue.name
106 const nsfw = formValue.nsfw
107 const category = formValue.category
108 const licence = formValue.licence
109 const language = formValue.language
110 const description = formValue.description
111 const tags = formValue.tags
112
113 form.append('name', name)
114 form.append('category', '' + category)
115 form.append('nsfw', '' + nsfw)
116 form.append('licence', '' + licence)
117
118 // Language is optional
119 if (language) {
120 form.append('language', '' + language)
121 }
122
123 form.append('description', description)
124
125 for (let i = 0; i < tags.length; i++) {
126 form.append(`tags[${i}]`, tags[i])
127 }
128 }
129
130 this.buildForm() 91 this.buildForm()
131 } 92 }
132 93
133 checkForm () { 94 // The goal is to keep reactive form validation (required field)
134 this.forceCheck() 95 // https://stackoverflow.com/a/44238894
135 96 fileChange ($event) {
136 if (this.filename === null) { 97 this.form.controls['videofile'].setValue($event.target.files[0].name)
137 this.fileError = 'You did not add a file.'
138 }
139
140 return this.form.valid === true && this.fileError === ''
141 } 98 }
142 99
143 fileChanged () { 100 removeFile () {
144 this.fileError = '' 101 this.videofileInput.nativeElement.value = ''
102 this.form.controls['videofile'].setValue('')
145 } 103 }
146 104
147 removeFile () { 105 checkForm () {
148 this.uploader.clearQueue() 106 this.forceCheck()
107
108 return this.form.valid
149 } 109 }
150 110
151 upload () { 111 upload () {
@@ -153,39 +113,49 @@ export class VideoAddComponent extends FormReactive implements OnInit {
153 return 113 return
154 } 114 }
155 115
156 const item = this.uploader.queue[0] 116 const formValue: VideoCreate = this.form.value
157 // TODO: wait for https://github.com/valor-software/ng2-file-upload/pull/242 117
158 item.alias = 'videofile' 118 const name = formValue.name
159 119 const nsfw = formValue.nsfw
160 item.onSuccess = () => { 120 const category = formValue.category
161 console.log('Video uploaded.') 121 const licence = formValue.licence
162 this.notificationsService.success('Success', 'Video uploaded.') 122 const language = formValue.language
163 123 const description = formValue.description
164 // Print all the videos once it's finished 124 const tags = formValue.tags
165 this.router.navigate(['/videos/list']) 125 const videofile = this.videofileInput.nativeElement.files[0]
126
127 const formData = new FormData()
128 formData.append('name', name)
129 formData.append('category', '' + category)
130 formData.append('nsfw', '' + nsfw)
131 formData.append('licence', '' + licence)
132 formData.append('videofile', videofile)
133
134 // Language is optional
135 if (language) {
136 formData.append('language', '' + language)
166 } 137 }
167 138
168 item.onError = (response: string, status: number) => { 139 formData.append('description', description)
169 // We need to handle manually these cases because we use the FileUpload component 140
170 if (status === 400) { 141 for (let i = 0; i < tags.length; i++) {
171 this.error = response 142 formData.append(`tags[${i}]`, tags[i])
172 } else if (status === 401) {
173 this.error = 'Access token was expired, refreshing token...'
174 this.authService.refreshAccessToken().subscribe(
175 () => {
176 // Update the uploader request header
177 this.uploader.authToken = this.authService.getRequestHeaderValue()
178 this.error += ' access token refreshed. Please retry your request.'
179 }
180 )
181 } else if (status === 403) {
182 this.error = 'Your video quota is reached, you can\'t upload this video.'
183 } else {
184 this.error = 'Unknown error'
185 console.error(this.error)
186 }
187 } 143 }
188 144
189 this.uploader.uploadAll() 145 this.videoService.uploadVideo(formData).subscribe(
146 event => {
147 if (event.type === HttpEventType.UploadProgress) {
148 this.progressPercent = Math.round(100 * event.loaded / event.total)
149 } else if (event instanceof HttpResponse) {
150 console.log('Video uploaded.')
151 this.notificationsService.success('Success', 'Video uploaded.')
152
153 // Display all the videos once it's finished
154 this.router.navigate([ '/videos/list '])
155 }
156 },
157
158 err => this.error = err.message
159 )
190 } 160 }
191} 161}
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 6c57a23d8..141ed3522 100644
--- a/client/src/app/videos/video-edit/video-update.component.ts
+++ b/client/src/app/videos/video-edit/video-update.component.ts
@@ -2,7 +2,6 @@ import { 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'
6import { NotificationsService } from 'angular2-notifications' 5import { NotificationsService } from 'angular2-notifications'
7 6
8import { AuthService } from '../../core' 7import { AuthService } from '../../core'
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 a5758060d..d9c83a640 100644
--- a/client/src/app/videos/video-watch/video-report.component.ts
+++ b/client/src/app/videos/video-watch/video-report.component.ts
@@ -63,7 +63,7 @@ export class VideoReportComponent extends FormReactive implements OnInit {
63 this.hide() 63 this.hide()
64 }, 64 },
65 65
66 err => this.notificationsService.error('Error', err) 66 err => this.notificationsService.error('Error', err.message)
67 ) 67 )
68 } 68 }
69} 69}
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 9cedc9c76..f5a47199d 100644
--- a/client/src/app/videos/video-watch/video-watch.component.ts
+++ b/client/src/app/videos/video-watch/video-watch.component.ts
@@ -158,7 +158,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
158 this.userRating = 'like' 158 this.userRating = 'like'
159 }, 159 },
160 160
161 err => this.notificationsService.error('Error', err) 161 err => this.notificationsService.error('Error', err.message)
162 ) 162 )
163 } 163 }
164 164
@@ -175,7 +175,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
175 this.userRating = 'dislike' 175 this.userRating = 'dislike'
176 }, 176 },
177 177
178 err => this.notificationsService.error('Error', err) 178 err => this.notificationsService.error('Error', err.message)
179 ) 179 )
180 } 180 }
181 181
@@ -275,7 +275,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
275 } 275 }
276 }, 276 },
277 277
278 err => this.notificationsService.error('Error', err) 278 err => this.notificationsService.error('Error', err.message)
279 ) 279 )
280 } 280 }
281 281
diff --git a/client/yarn.lock b/client/yarn.lock
index 3552dbf99..b61da8636 100644
--- a/client/yarn.lock
+++ b/client/yarn.lock
@@ -4436,10 +4436,6 @@ ng-router-loader@^2.0.0:
4436 loader-utils "^0.2.16" 4436 loader-utils "^0.2.16"
4437 recast "^0.11.20" 4437 recast "^0.11.20"
4438 4438
4439ng2-file-upload@^1.1.4-2:
4440 version "1.2.1"
4441 resolved "https://registry.yarnpkg.com/ng2-file-upload/-/ng2-file-upload-1.2.1.tgz#5563c5dfd6f43fbfbe815c206e343464a0a6a197"
4442
4443ng2-material-dropdown@0.7.10: 4439ng2-material-dropdown@0.7.10:
4444 version "0.7.10" 4440 version "0.7.10"
4445 resolved "https://registry.yarnpkg.com/ng2-material-dropdown/-/ng2-material-dropdown-0.7.10.tgz#093471f2a9cadd726cbcb120b0ad7818a54fa5ed" 4441 resolved "https://registry.yarnpkg.com/ng2-material-dropdown/-/ng2-material-dropdown-0.7.10.tgz#093471f2a9cadd726cbcb120b0ad7818a54fa5ed"
diff --git a/server/middlewares/validators/pods.ts b/server/middlewares/validators/pods.ts
index 4d0e054b0..3a0f56f6a 100644
--- a/server/middlewares/validators/pods.ts
+++ b/server/middlewares/validators/pods.ts
@@ -11,7 +11,11 @@ import { isTestInstance } from '../../helpers'
11function makeFriendsValidator (req: express.Request, res: express.Response, next: express.NextFunction) { 11function makeFriendsValidator (req: express.Request, res: express.Response, next: express.NextFunction) {
12 // Force https if the administrator wants to make friends 12 // Force https if the administrator wants to make friends
13 if (isTestInstance() === false && CONFIG.WEBSERVER.SCHEME === 'http') { 13 if (isTestInstance() === false && CONFIG.WEBSERVER.SCHEME === 'http') {
14 return res.status(400).send('Cannot make friends with a non HTTPS web server.') 14 return res.status(400)
15 .json({
16 error: 'Cannot make friends with a non HTTPS web server.'
17 })
18 .end()
15 } 19 }
16 20
17 req.checkBody('hosts', 'Should have an array of unique hosts').isEachUniqueHostValid() 21 req.checkBody('hosts', 'Should have an array of unique hosts').isEachUniqueHostValid()
diff --git a/server/middlewares/validators/users.ts b/server/middlewares/validators/users.ts
index aec6324bf..15c07c693 100644
--- a/server/middlewares/validators/users.ts
+++ b/server/middlewares/validators/users.ts
@@ -45,9 +45,13 @@ function usersRemoveValidator (req: express.Request, res: express.Response, next
45 return res.sendStatus(500) 45 return res.sendStatus(500)
46 } 46 }
47 47
48 if (user.username === 'root') return res.status(400).send('Cannot remove the root user') 48 if (user.username === 'root') {
49 return res.status(400)
50 .send({ error: 'Cannot remove the root user' })
51 .end()
52 }
49 53
50 next() 54 return next()
51 }) 55 })
52 }) 56 })
53} 57}
@@ -99,9 +103,13 @@ function usersVideoRatingValidator (req: express.Request, res: express.Response,
99 103
100 videoPromise 104 videoPromise
101 .then(video => { 105 .then(video => {
102 if (!video) return res.status(404).send('Video not found') 106 if (!video) {
107 return res.status(404)
108 .json({ error: 'Video not found' })
109 .end()
110 }
103 111
104 next() 112 return next()
105 }) 113 })
106 .catch(err => { 114 .catch(err => {
107 logger.error('Error in user request validator.', err) 115 logger.error('Error in user request validator.', err)
@@ -113,7 +121,9 @@ function usersVideoRatingValidator (req: express.Request, res: express.Response,
113function ensureUserRegistrationAllowed (req: express.Request, res: express.Response, next: express.NextFunction) { 121function ensureUserRegistrationAllowed (req: express.Request, res: express.Response, next: express.NextFunction) {
114 isSignupAllowed().then(allowed => { 122 isSignupAllowed().then(allowed => {
115 if (allowed === false) { 123 if (allowed === false) {
116 return res.status(403).send('User registration is not enabled or user limit is reached.') 124 return res.status(403)
125 .send({ error: 'User registration is not enabled or user limit is reached.' })
126 .end()
117 } 127 }
118 128
119 return next() 129 return next()
@@ -138,10 +148,14 @@ export {
138function checkUserExists (id: number, res: express.Response, callback: (err: Error, user: UserInstance) => void) { 148function checkUserExists (id: number, res: express.Response, callback: (err: Error, user: UserInstance) => void) {
139 db.User.loadById(id) 149 db.User.loadById(id)
140 .then(user => { 150 .then(user => {
141 if (!user) return res.status(404).send('User not found') 151 if (!user) {
152 return res.status(404)
153 .send({ error: 'User not found' })
154 .end()
155 }
142 156
143 res.locals.user = user 157 res.locals.user = user
144 callback(null, user) 158 return callback(null, user)
145 }) 159 })
146 .catch(err => { 160 .catch(err => {
147 logger.error('Error in user request validator.', err) 161 logger.error('Error in user request validator.', err)
@@ -152,9 +166,13 @@ function checkUserExists (id: number, res: express.Response, callback: (err: Err
152function checkUserDoesNotAlreadyExist (username: string, email: string, res: express.Response, callback: () => void) { 166function checkUserDoesNotAlreadyExist (username: string, email: string, res: express.Response, callback: () => void) {
153 db.User.loadByUsernameOrEmail(username, email) 167 db.User.loadByUsernameOrEmail(username, email)
154 .then(user => { 168 .then(user => {
155 if (user) return res.status(409).send('User already exists.') 169 if (user) {
170 return res.status(409)
171 .send({ error: 'User already exists.' })
172 .end()
173 }
156 174
157 callback() 175 return callback()
158 }) 176 })
159 .catch(err => { 177 .catch(err => {
160 logger.error('Error in usersAdd request validator.', err) 178 logger.error('Error in usersAdd request validator.', err)
diff --git a/server/middlewares/validators/videos.ts b/server/middlewares/validators/videos.ts
index 519e3d46c..213b4c46b 100644
--- a/server/middlewares/validators/videos.ts
+++ b/server/middlewares/validators/videos.ts
@@ -30,7 +30,9 @@ function videosAddValidator (req: express.Request, res: express.Response, next:
30 user.isAbleToUploadVideo(videoFile) 30 user.isAbleToUploadVideo(videoFile)
31 .then(isAble => { 31 .then(isAble => {
32 if (isAble === false) { 32 if (isAble === false) {
33 res.status(403).send('The user video quota is exceeded with this video.') 33 res.status(403)
34 .json({ error: 'The user video quota is exceeded with this video.' })
35 .end()
34 36
35 return undefined 37 return undefined
36 } 38 }
@@ -38,17 +40,23 @@ function videosAddValidator (req: express.Request, res: express.Response, next:
38 return db.Video.getDurationFromFile(videoFile.path) 40 return db.Video.getDurationFromFile(videoFile.path)
39 .catch(err => { 41 .catch(err => {
40 logger.error('Invalid input file in videosAddValidator.', err) 42 logger.error('Invalid input file in videosAddValidator.', err)
41 res.status(400).send('Invalid input file.') 43 res.status(400)
44 .json({ error: 'Invalid input file.' })
45 .end()
42 46
43 return undefined 47 return undefined
44 }) 48 })
45 }) 49 })
46 .then(duration => { 50 .then(duration => {
47 // Previous test failed, abort 51 // Previous test failed, abort
48 if (duration === undefined) return undefined 52 if (duration === undefined) return
49 53
50 if (!isVideoDurationValid('' + duration)) { 54 if (!isVideoDurationValid('' + duration)) {
51 return res.status(400).send('Duration of the video file is too big (max: ' + CONSTRAINTS_FIELDS.VIDEOS.DURATION.max + 's).') 55 return res.status(400)
56 .json({
57 error: 'Duration of the video file is too big (max: ' + CONSTRAINTS_FIELDS.VIDEOS.DURATION.max + 's).'
58 })
59 .end()
52 } 60 }
53 61
54 videoFile['duration'] = duration 62 videoFile['duration'] = duration
@@ -80,11 +88,15 @@ function videosUpdateValidator (req: express.Request, res: express.Response, nex
80 checkVideoExists(req.params.id, res, () => { 88 checkVideoExists(req.params.id, res, () => {
81 // We need to make additional checks 89 // We need to make additional checks
82 if (res.locals.video.isOwned() === false) { 90 if (res.locals.video.isOwned() === false) {
83 return res.status(403).send('Cannot update video of another pod') 91 return res.status(403)
92 .json({ error: 'Cannot update video of another pod' })
93 .end()
84 } 94 }
85 95
86 if (res.locals.video.Author.userId !== res.locals.oauth.token.User.id) { 96 if (res.locals.video.Author.userId !== res.locals.oauth.token.User.id) {
87 return res.status(403).send('Cannot update video of another user') 97 return res.status(403)
98 .json({ error: 'Cannot update video of another user' })
99 .end()
88 } 100 }
89 101
90 next() 102 next()
@@ -188,7 +200,11 @@ function checkVideoExists (id: string, res: express.Response, callback: () => vo
188 } 200 }
189 201
190 promise.then(video => { 202 promise.then(video => {
191 if (!video) return res.status(404).send('Video not found') 203 if (!video) {
204 return res.status(404)
205 .json({ error: 'Video not found' })
206 .end()
207 }
192 208
193 res.locals.video = video 209 res.locals.video = video
194 callback() 210 callback()
@@ -204,14 +220,18 @@ function checkUserCanDeleteVideo (userId: number, res: express.Response, callbac
204 db.User.loadById(userId) 220 db.User.loadById(userId)
205 .then(user => { 221 .then(user => {
206 if (res.locals.video.isOwned() === false) { 222 if (res.locals.video.isOwned() === false) {
207 return res.status(403).send('Cannot remove video of another pod, blacklist it') 223 return res.status(403)
224 .json({ error: 'Cannot remove video of another pod, blacklist it' })
225 .end()
208 } 226 }
209 227
210 // Check if the user can delete the video 228 // Check if the user can delete the video
211 // The user can delete it if s/he is an admin 229 // The user can delete it if s/he is an admin
212 // Or if s/he is the video's author 230 // Or if s/he is the video's author
213 if (user.isAdmin() === false && res.locals.video.Author.userId !== res.locals.oauth.token.User.id) { 231 if (user.isAdmin() === false && res.locals.video.Author.userId !== res.locals.oauth.token.User.id) {
214 return res.status(403).send('Cannot remove video of another user') 232 return res.status(403)
233 .json({ error: 'Cannot remove video of another user' })
234 .end()
215 } 235 }
216 236
217 // If we reach this comment, we can delete the video 237 // If we reach this comment, we can delete the video
@@ -225,7 +245,9 @@ function checkUserCanDeleteVideo (userId: number, res: express.Response, callbac
225 245
226function checkVideoIsBlacklistable (req: express.Request, res: express.Response, callback: () => void) { 246function checkVideoIsBlacklistable (req: express.Request, res: express.Response, callback: () => void) {
227 if (res.locals.video.isOwned() === true) { 247 if (res.locals.video.isOwned() === true) {
228 return res.status(403).send('Cannot blacklist a local video') 248 return res.status(403)
249 .json({ error: 'Cannot blacklist a local video' })
250 .end()
229 } 251 }
230 252
231 callback() 253 callback()