diff options
48 files changed, 545 insertions, 208 deletions
diff --git a/client/src/app/app.component.ts b/client/src/app/app.component.ts index 984470d69..bef1599fc 100644 --- a/client/src/app/app.component.ts +++ b/client/src/app/app.component.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | import { Component, OnInit, ViewContainerRef } from '@angular/core' | 1 | import { Component, OnInit } from '@angular/core' |
2 | import { Router } from '@angular/router' | 2 | import { Router } from '@angular/router' |
3 | 3 | ||
4 | import { AuthService, ServerService } from './core' | 4 | import { AuthService, ServerService } from './core' |
@@ -28,8 +28,7 @@ export class AppComponent implements OnInit { | |||
28 | constructor ( | 28 | constructor ( |
29 | private router: Router, | 29 | private router: Router, |
30 | private authService: AuthService, | 30 | private authService: AuthService, |
31 | private serverService: ServerService, | 31 | private serverService: ServerService |
32 | private userService: UserService | ||
33 | ) {} | 32 | ) {} |
34 | 33 | ||
35 | ngOnInit () { | 34 | ngOnInit () { |
@@ -45,6 +44,7 @@ export class AppComponent implements OnInit { | |||
45 | this.serverService.loadVideoCategories() | 44 | this.serverService.loadVideoCategories() |
46 | this.serverService.loadVideoLanguages() | 45 | this.serverService.loadVideoLanguages() |
47 | this.serverService.loadVideoLicences() | 46 | this.serverService.loadVideoLicences() |
47 | this.serverService.loadVideoPrivacies() | ||
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) { |
diff --git a/client/src/app/core/menu/menu.component.html b/client/src/app/core/menu/menu.component.html index 2d8aace54..fcde23fdd 100644 --- a/client/src/app/core/menu/menu.component.html +++ b/client/src/app/core/menu/menu.component.html | |||
@@ -23,6 +23,11 @@ | |||
23 | <span class="hidden-xs glyphicon glyphicon-user"></span> | 23 | <span class="hidden-xs glyphicon glyphicon-user"></span> |
24 | My account | 24 | My account |
25 | </a> | 25 | </a> |
26 | |||
27 | <a *ngIf="isLoggedIn" routerLink="/videos/mine" routerLinkActive="active"> | ||
28 | <span class="hidden-xs glyphicon glyphicon-folder-open"></span> | ||
29 | My videos | ||
30 | </a> | ||
26 | </div> | 31 | </div> |
27 | 32 | ||
28 | <div class="panel-block"> | 33 | <div class="panel-block"> |
diff --git a/client/src/app/core/server/server.service.ts b/client/src/app/core/server/server.service.ts index ae507afce..cbc4074c9 100644 --- a/client/src/app/core/server/server.service.ts +++ b/client/src/app/core/server/server.service.ts | |||
@@ -19,6 +19,7 @@ export class ServerService { | |||
19 | private videoCategories: Array<{ id: number, label: string }> = [] | 19 | private videoCategories: Array<{ id: number, label: string }> = [] |
20 | private videoLicences: Array<{ id: number, label: string }> = [] | 20 | private videoLicences: Array<{ id: number, label: string }> = [] |
21 | private videoLanguages: Array<{ id: number, label: string }> = [] | 21 | private videoLanguages: Array<{ id: number, label: string }> = [] |
22 | private videoPrivacies: Array<{ id: number, label: string }> = [] | ||
22 | 23 | ||
23 | constructor (private http: HttpClient) {} | 24 | constructor (private http: HttpClient) {} |
24 | 25 | ||
@@ -39,6 +40,10 @@ export class ServerService { | |||
39 | return this.loadVideoAttributeEnum('languages', this.videoLanguages) | 40 | return this.loadVideoAttributeEnum('languages', this.videoLanguages) |
40 | } | 41 | } |
41 | 42 | ||
43 | loadVideoPrivacies () { | ||
44 | return this.loadVideoAttributeEnum('privacies', this.videoPrivacies) | ||
45 | } | ||
46 | |||
42 | getConfig () { | 47 | getConfig () { |
43 | return this.config | 48 | return this.config |
44 | } | 49 | } |
@@ -55,7 +60,14 @@ export class ServerService { | |||
55 | return this.videoLanguages | 60 | return this.videoLanguages |
56 | } | 61 | } |
57 | 62 | ||
58 | private loadVideoAttributeEnum (attributeName: 'categories' | 'licences' | 'languages', hashToPopulate: { id: number, label: string }[]) { | 63 | getVideoPrivacies () { |
64 | return this.videoPrivacies | ||
65 | } | ||
66 | |||
67 | private loadVideoAttributeEnum ( | ||
68 | attributeName: 'categories' | 'licences' | 'languages' | 'privacies', | ||
69 | hashToPopulate: { id: number, label: string }[] | ||
70 | ) { | ||
59 | return this.http.get(ServerService.BASE_VIDEO_URL + attributeName) | 71 | return this.http.get(ServerService.BASE_VIDEO_URL + attributeName) |
60 | .subscribe(data => { | 72 | .subscribe(data => { |
61 | Object.keys(data) | 73 | Object.keys(data) |
diff --git a/client/src/app/shared/forms/form-validators/video.ts b/client/src/app/shared/forms/form-validators/video.ts index 434773501..65f11f5da 100644 --- a/client/src/app/shared/forms/form-validators/video.ts +++ b/client/src/app/shared/forms/form-validators/video.ts | |||
@@ -9,6 +9,13 @@ export const VIDEO_NAME = { | |||
9 | } | 9 | } |
10 | } | 10 | } |
11 | 11 | ||
12 | export const VIDEO_PRIVACY = { | ||
13 | VALIDATORS: [ Validators.required ], | ||
14 | MESSAGES: { | ||
15 | 'required': 'Video privacy is required.' | ||
16 | } | ||
17 | } | ||
18 | |||
12 | export const VIDEO_CATEGORY = { | 19 | export const VIDEO_CATEGORY = { |
13 | VALIDATORS: [ Validators.required ], | 20 | VALIDATORS: [ Validators.required ], |
14 | MESSAGES: { | 21 | MESSAGES: { |
diff --git a/client/src/app/shared/search/search-field.type.ts b/client/src/app/shared/search/search-field.type.ts index 63557898a..ff0bb8de1 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' | 'tags' | ||
diff --git a/client/src/app/shared/search/search.component.html b/client/src/app/shared/search/search.component.html index c6c6ff6a8..0302447d0 100644 --- a/client/src/app/shared/search/search.component.html +++ b/client/src/app/shared/search/search.component.html | |||
@@ -6,12 +6,12 @@ | |||
6 | 6 | ||
7 | <input | 7 | <input |
8 | type="text" id="search-video" name="search-video" class="form-control" placeholder="Search" class="form-control" | 8 | type="text" id="search-video" name="search-video" class="form-control" placeholder="Search" class="form-control" |
9 | [(ngModel)]="searchCriterias.value" (keyup.enter)="doSearch()" | 9 | [(ngModel)]="searchCriteria.value" (keyup.enter)="doSearch()" |
10 | > | 10 | > |
11 | 11 | ||
12 | <div class="input-group-btn" dropdown placement="bottom right"> | 12 | <div class="input-group-btn" dropdown placement="bottom right"> |
13 | <button id="simple-btn-keyboard-nav" type="button" class="btn btn-default" dropdownToggle> | 13 | <button id="simple-btn-keyboard-nav" type="button" class="btn btn-default" dropdownToggle> |
14 | {{ getStringChoice(searchCriterias.field) }} <span class="caret"></span> | 14 | {{ getStringChoice(searchCriteria.field) }} <span class="caret"></span> |
15 | </button> | 15 | </button> |
16 | <ul class="dropdown-menu dropdown-menu-right" role="menu" aria-labelledby="simple-btn-keyboard-nav" *dropdownMenu> | 16 | <ul class="dropdown-menu dropdown-menu-right" role="menu" aria-labelledby="simple-btn-keyboard-nav" *dropdownMenu> |
17 | <li *ngFor="let choice of choiceKeys" class="dropdown-item" role="menu-item"> | 17 | <li *ngFor="let choice of choiceKeys" class="dropdown-item" role="menu-item"> |
diff --git a/client/src/app/shared/search/search.component.ts b/client/src/app/shared/search/search.component.ts index ecce20666..6e2827fe3 100644 --- a/client/src/app/shared/search/search.component.ts +++ b/client/src/app/shared/search/search.component.ts | |||
@@ -16,10 +16,9 @@ export class SearchComponent implements OnInit { | |||
16 | name: 'Name', | 16 | name: 'Name', |
17 | author: 'Author', | 17 | author: 'Author', |
18 | host: 'Pod Host', | 18 | host: 'Pod Host', |
19 | magnetUri: 'Magnet URI', | ||
20 | tags: 'Tags' | 19 | tags: 'Tags' |
21 | } | 20 | } |
22 | searchCriterias: Search = { | 21 | searchCriteria: Search = { |
23 | field: 'name', | 22 | field: 'name', |
24 | value: '' | 23 | value: '' |
25 | } | 24 | } |
@@ -30,13 +29,13 @@ export class SearchComponent implements OnInit { | |||
30 | // Subscribe if the search changed | 29 | // Subscribe if the search changed |
31 | // Usually changed by videos list component | 30 | // Usually changed by videos list component |
32 | this.searchService.updateSearch.subscribe( | 31 | this.searchService.updateSearch.subscribe( |
33 | newSearchCriterias => { | 32 | newSearchCriteria => { |
34 | // Put a field by default | 33 | // Put a field by default |
35 | if (!newSearchCriterias.field) { | 34 | if (!newSearchCriteria.field) { |
36 | newSearchCriterias.field = 'name' | 35 | newSearchCriteria.field = 'name' |
37 | } | 36 | } |
38 | 37 | ||
39 | this.searchCriterias = newSearchCriterias | 38 | this.searchCriteria = newSearchCriteria |
40 | } | 39 | } |
41 | ) | 40 | ) |
42 | } | 41 | } |
@@ -49,9 +48,9 @@ export class SearchComponent implements OnInit { | |||
49 | $event.preventDefault() | 48 | $event.preventDefault() |
50 | $event.stopPropagation() | 49 | $event.stopPropagation() |
51 | 50 | ||
52 | this.searchCriterias.field = choice | 51 | this.searchCriteria.field = choice |
53 | 52 | ||
54 | if (this.searchCriterias.value) { | 53 | if (this.searchCriteria.value) { |
55 | this.doSearch() | 54 | this.doSearch() |
56 | } | 55 | } |
57 | } | 56 | } |
@@ -61,7 +60,7 @@ export class SearchComponent implements OnInit { | |||
61 | this.router.navigate([ '/videos/list' ]) | 60 | this.router.navigate([ '/videos/list' ]) |
62 | } | 61 | } |
63 | 62 | ||
64 | this.searchService.searchUpdated.next(this.searchCriterias) | 63 | this.searchService.searchUpdated.next(this.searchCriteria) |
65 | } | 64 | } |
66 | 65 | ||
67 | getStringChoice (choiceKey: SearchField) { | 66 | getStringChoice (choiceKey: SearchField) { |
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 a70788ed8..b4e0f9f7c 100644 --- a/client/src/app/videos/+video-edit/video-add.component.html +++ b/client/src/app/videos/+video-edit/video-add.component.html | |||
@@ -18,6 +18,18 @@ | |||
18 | </div> | 18 | </div> |
19 | 19 | ||
20 | <div class="form-group"> | 20 | <div class="form-group"> |
21 | <label for="privacy">Privacy</label> | ||
22 | <select class="form-control" id="privacy" formControlName="privacy"> | ||
23 | <option></option> | ||
24 | <option *ngFor="let privacy of videoPrivacies" [value]="privacy.id">{{ privacy.label }}</option> | ||
25 | </select> | ||
26 | |||
27 | <div *ngIf="formErrors.privacy" class="alert alert-danger"> | ||
28 | {{ formErrors.privacy }} | ||
29 | </div> | ||
30 | </div> | ||
31 | |||
32 | <div class="form-group"> | ||
21 | <input | 33 | <input |
22 | type="checkbox" id="nsfw" | 34 | type="checkbox" id="nsfw" |
23 | formControlName="nsfw" | 35 | formControlName="nsfw" |
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 5b5557ed9..c8094f792 100644 --- a/client/src/app/videos/+video-edit/video-add.component.ts +++ b/client/src/app/videos/+video-edit/video-add.component.ts | |||
@@ -13,7 +13,8 @@ import { | |||
13 | VIDEO_DESCRIPTION, | 13 | VIDEO_DESCRIPTION, |
14 | VIDEO_TAGS, | 14 | VIDEO_TAGS, |
15 | VIDEO_CHANNEL, | 15 | VIDEO_CHANNEL, |
16 | VIDEO_FILE | 16 | VIDEO_FILE, |
17 | VIDEO_PRIVACY | ||
17 | } from '../../shared' | 18 | } from '../../shared' |
18 | import { AuthService, ServerService } from '../../core' | 19 | import { AuthService, ServerService } from '../../core' |
19 | import { VideoService } from '../shared' | 20 | import { VideoService } from '../shared' |
@@ -34,6 +35,7 @@ export class VideoAddComponent extends FormReactive implements OnInit { | |||
34 | videoCategories = [] | 35 | videoCategories = [] |
35 | videoLicences = [] | 36 | videoLicences = [] |
36 | videoLanguages = [] | 37 | videoLanguages = [] |
38 | videoPrivacies = [] | ||
37 | userVideoChannels = [] | 39 | userVideoChannels = [] |
38 | 40 | ||
39 | tagValidators = VIDEO_TAGS.VALIDATORS | 41 | tagValidators = VIDEO_TAGS.VALIDATORS |
@@ -43,6 +45,7 @@ export class VideoAddComponent extends FormReactive implements OnInit { | |||
43 | form: FormGroup | 45 | form: FormGroup |
44 | formErrors = { | 46 | formErrors = { |
45 | name: '', | 47 | name: '', |
48 | privacy: '', | ||
46 | category: '', | 49 | category: '', |
47 | licence: '', | 50 | licence: '', |
48 | language: '', | 51 | language: '', |
@@ -52,6 +55,7 @@ export class VideoAddComponent extends FormReactive implements OnInit { | |||
52 | } | 55 | } |
53 | validationMessages = { | 56 | validationMessages = { |
54 | name: VIDEO_NAME.MESSAGES, | 57 | name: VIDEO_NAME.MESSAGES, |
58 | privacy: VIDEO_PRIVACY.MESSAGES, | ||
55 | category: VIDEO_CATEGORY.MESSAGES, | 59 | category: VIDEO_CATEGORY.MESSAGES, |
56 | licence: VIDEO_LICENCE.MESSAGES, | 60 | licence: VIDEO_LICENCE.MESSAGES, |
57 | language: VIDEO_LANGUAGE.MESSAGES, | 61 | language: VIDEO_LANGUAGE.MESSAGES, |
@@ -79,6 +83,7 @@ export class VideoAddComponent extends FormReactive implements OnInit { | |||
79 | this.form = this.formBuilder.group({ | 83 | this.form = this.formBuilder.group({ |
80 | name: [ '', VIDEO_NAME.VALIDATORS ], | 84 | name: [ '', VIDEO_NAME.VALIDATORS ], |
81 | nsfw: [ false ], | 85 | nsfw: [ false ], |
86 | privacy: [ '', VIDEO_PRIVACY.VALIDATORS ], | ||
82 | category: [ '', VIDEO_CATEGORY.VALIDATORS ], | 87 | category: [ '', VIDEO_CATEGORY.VALIDATORS ], |
83 | licence: [ '', VIDEO_LICENCE.VALIDATORS ], | 88 | licence: [ '', VIDEO_LICENCE.VALIDATORS ], |
84 | language: [ '', VIDEO_LANGUAGE.VALIDATORS ], | 89 | language: [ '', VIDEO_LANGUAGE.VALIDATORS ], |
@@ -95,6 +100,7 @@ export class VideoAddComponent extends FormReactive implements OnInit { | |||
95 | this.videoCategories = this.serverService.getVideoCategories() | 100 | this.videoCategories = this.serverService.getVideoCategories() |
96 | this.videoLicences = this.serverService.getVideoLicences() | 101 | this.videoLicences = this.serverService.getVideoLicences() |
97 | this.videoLanguages = this.serverService.getVideoLanguages() | 102 | this.videoLanguages = this.serverService.getVideoLanguages() |
103 | this.videoPrivacies = this.serverService.getVideoPrivacies() | ||
98 | 104 | ||
99 | this.buildForm() | 105 | this.buildForm() |
100 | 106 | ||
@@ -139,6 +145,7 @@ export class VideoAddComponent extends FormReactive implements OnInit { | |||
139 | const formValue: VideoCreate = this.form.value | 145 | const formValue: VideoCreate = this.form.value |
140 | 146 | ||
141 | const name = formValue.name | 147 | const name = formValue.name |
148 | const privacy = formValue.privacy | ||
142 | const nsfw = formValue.nsfw | 149 | const nsfw = formValue.nsfw |
143 | const category = formValue.category | 150 | const category = formValue.category |
144 | const licence = formValue.licence | 151 | const licence = formValue.licence |
@@ -150,6 +157,7 @@ export class VideoAddComponent extends FormReactive implements OnInit { | |||
150 | 157 | ||
151 | const formData = new FormData() | 158 | const formData = new FormData() |
152 | formData.append('name', name) | 159 | formData.append('name', name) |
160 | formData.append('privacy', privacy.toString()) | ||
153 | formData.append('category', '' + category) | 161 | formData.append('category', '' + category) |
154 | formData.append('nsfw', '' + nsfw) | 162 | formData.append('nsfw', '' + nsfw) |
155 | formData.append('licence', '' + licence) | 163 | formData.append('licence', '' + licence) |
diff --git a/client/src/app/videos/+video-edit/video-update.component.html b/client/src/app/videos/+video-edit/video-update.component.html index ec040630e..b9c6139b2 100644 --- a/client/src/app/videos/+video-edit/video-update.component.html +++ b/client/src/app/videos/+video-edit/video-update.component.html | |||
@@ -18,6 +18,18 @@ | |||
18 | </div> | 18 | </div> |
19 | 19 | ||
20 | <div class="form-group"> | 20 | <div class="form-group"> |
21 | <label for="privacy">Privacy</label> | ||
22 | <select class="form-control" id="privacy" formControlName="privacy"> | ||
23 | <option></option> | ||
24 | <option *ngFor="let privacy of videoPrivacies" [value]="privacy.id">{{ privacy.label }}</option> | ||
25 | </select> | ||
26 | |||
27 | <div *ngIf="formErrors.privacy" class="alert alert-danger"> | ||
28 | {{ formErrors.privacy }} | ||
29 | </div> | ||
30 | </div> | ||
31 | |||
32 | <div class="form-group"> | ||
21 | <input | 33 | <input |
22 | type="checkbox" id="nsfw" | 34 | type="checkbox" id="nsfw" |
23 | formControlName="nsfw" | 35 | formControlName="nsfw" |
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 6ced77f1a..be663575f 100644 --- a/client/src/app/videos/+video-edit/video-update.component.ts +++ b/client/src/app/videos/+video-edit/video-update.component.ts | |||
@@ -1,7 +1,6 @@ | |||
1 | import { Component, OnInit } from '@angular/core' | 1 | import { Component, OnInit } from '@angular/core' |
2 | import { FormBuilder, FormGroup } from '@angular/forms' | 2 | import { FormBuilder, FormGroup } from '@angular/forms' |
3 | import { ActivatedRoute, Router } from '@angular/router' | 3 | import { ActivatedRoute, Router } from '@angular/router' |
4 | import { Observable } from 'rxjs/Observable' | ||
5 | import 'rxjs/add/observable/forkJoin' | 4 | import 'rxjs/add/observable/forkJoin' |
6 | 5 | ||
7 | import { NotificationsService } from 'angular2-notifications' | 6 | import { NotificationsService } from 'angular2-notifications' |
@@ -14,9 +13,11 @@ import { | |||
14 | VIDEO_LICENCE, | 13 | VIDEO_LICENCE, |
15 | VIDEO_LANGUAGE, | 14 | VIDEO_LANGUAGE, |
16 | VIDEO_DESCRIPTION, | 15 | VIDEO_DESCRIPTION, |
17 | VIDEO_TAGS | 16 | VIDEO_TAGS, |
17 | VIDEO_PRIVACY | ||
18 | } from '../../shared' | 18 | } from '../../shared' |
19 | import { VideoEdit, VideoService } from '../shared' | 19 | import { VideoEdit, VideoService } from '../shared' |
20 | import { VideoPrivacy } from '../../../../../shared/models/videos/video-privacy.enum' | ||
20 | 21 | ||
21 | @Component({ | 22 | @Component({ |
22 | selector: 'my-videos-update', | 23 | selector: 'my-videos-update', |
@@ -29,6 +30,7 @@ export class VideoUpdateComponent extends FormReactive implements OnInit { | |||
29 | videoCategories = [] | 30 | videoCategories = [] |
30 | videoLicences = [] | 31 | videoLicences = [] |
31 | videoLanguages = [] | 32 | videoLanguages = [] |
33 | videoPrivacies = [] | ||
32 | video: VideoEdit | 34 | video: VideoEdit |
33 | 35 | ||
34 | tagValidators = VIDEO_TAGS.VALIDATORS | 36 | tagValidators = VIDEO_TAGS.VALIDATORS |
@@ -38,6 +40,7 @@ export class VideoUpdateComponent extends FormReactive implements OnInit { | |||
38 | form: FormGroup | 40 | form: FormGroup |
39 | formErrors = { | 41 | formErrors = { |
40 | name: '', | 42 | name: '', |
43 | privacy: '', | ||
41 | category: '', | 44 | category: '', |
42 | licence: '', | 45 | licence: '', |
43 | language: '', | 46 | language: '', |
@@ -45,6 +48,7 @@ export class VideoUpdateComponent extends FormReactive implements OnInit { | |||
45 | } | 48 | } |
46 | validationMessages = { | 49 | validationMessages = { |
47 | name: VIDEO_NAME.MESSAGES, | 50 | name: VIDEO_NAME.MESSAGES, |
51 | privacy: VIDEO_PRIVACY.MESSAGES, | ||
48 | category: VIDEO_CATEGORY.MESSAGES, | 52 | category: VIDEO_CATEGORY.MESSAGES, |
49 | licence: VIDEO_LICENCE.MESSAGES, | 53 | licence: VIDEO_LICENCE.MESSAGES, |
50 | language: VIDEO_LANGUAGE.MESSAGES, | 54 | language: VIDEO_LANGUAGE.MESSAGES, |
@@ -67,6 +71,7 @@ export class VideoUpdateComponent extends FormReactive implements OnInit { | |||
67 | buildForm () { | 71 | buildForm () { |
68 | this.form = this.formBuilder.group({ | 72 | this.form = this.formBuilder.group({ |
69 | name: [ '', VIDEO_NAME.VALIDATORS ], | 73 | name: [ '', VIDEO_NAME.VALIDATORS ], |
74 | privacy: [ '', VIDEO_PRIVACY.VALIDATORS ], | ||
70 | nsfw: [ false ], | 75 | nsfw: [ false ], |
71 | category: [ '', VIDEO_CATEGORY.VALIDATORS ], | 76 | category: [ '', VIDEO_CATEGORY.VALIDATORS ], |
72 | licence: [ '', VIDEO_LICENCE.VALIDATORS ], | 77 | licence: [ '', VIDEO_LICENCE.VALIDATORS ], |
@@ -84,6 +89,7 @@ export class VideoUpdateComponent extends FormReactive implements OnInit { | |||
84 | this.videoCategories = this.serverService.getVideoCategories() | 89 | this.videoCategories = this.serverService.getVideoCategories() |
85 | this.videoLicences = this.serverService.getVideoLicences() | 90 | this.videoLicences = this.serverService.getVideoLicences() |
86 | this.videoLanguages = this.serverService.getVideoLanguages() | 91 | this.videoLanguages = this.serverService.getVideoLanguages() |
92 | this.videoPrivacies = this.serverService.getVideoPrivacies() | ||
87 | 93 | ||
88 | const uuid: string = this.route.snapshot.params['uuid'] | 94 | const uuid: string = this.route.snapshot.params['uuid'] |
89 | 95 | ||
@@ -98,6 +104,16 @@ export class VideoUpdateComponent extends FormReactive implements OnInit { | |||
98 | video => { | 104 | video => { |
99 | this.video = new VideoEdit(video) | 105 | this.video = new VideoEdit(video) |
100 | 106 | ||
107 | // We cannot set private a video that was not private anymore | ||
108 | if (video.privacy !== VideoPrivacy.PRIVATE) { | ||
109 | const newVideoPrivacies = [] | ||
110 | for (const p of this.videoPrivacies) { | ||
111 | if (p.id !== VideoPrivacy.PRIVATE) newVideoPrivacies.push(p) | ||
112 | } | ||
113 | |||
114 | this.videoPrivacies = newVideoPrivacies | ||
115 | } | ||
116 | |||
101 | this.hydrateFormFromVideo() | 117 | this.hydrateFormFromVideo() |
102 | }, | 118 | }, |
103 | 119 | ||
diff --git a/client/src/app/videos/+video-watch/video-watch.component.html b/client/src/app/videos/+video-watch/video-watch.component.html index 71f986ccd..53648a8d8 100644 --- a/client/src/app/videos/+video-watch/video-watch.component.html +++ b/client/src/app/videos/+video-watch/video-watch.component.html | |||
@@ -22,7 +22,7 @@ | |||
22 | <div *ngIf="videoNotFound" id="video-not-found">Video not found :'(</div> | 22 | <div *ngIf="videoNotFound" id="video-not-found">Video not found :'(</div> |
23 | </div> | 23 | </div> |
24 | 24 | ||
25 | <!-- P2P informations --> | 25 | <!-- P2P information --> |
26 | <div id="torrent-info" class="row"> | 26 | <div id="torrent-info" class="row"> |
27 | <div id="torrent-info-download" class="col-md-4 col-sm-4 col-xs-4">Download: {{ downloadSpeed | bytes }}/s</div> | 27 | <div id="torrent-info-download" class="col-md-4 col-sm-4 col-xs-4">Download: {{ downloadSpeed | bytes }}/s</div> |
28 | <div id="torrent-info-upload" class="col-md-4 col-sm-4 col-xs-4">Upload: {{ uploadSpeed | bytes }}/s</div> | 28 | <div id="torrent-info-upload" class="col-md-4 col-sm-4 col-xs-4">Upload: {{ uploadSpeed | bytes }}/s</div> |
@@ -144,6 +144,15 @@ | |||
144 | <div class="video-details-attributes col-xs-4 col-md-3"> | 144 | <div class="video-details-attributes col-xs-4 col-md-3"> |
145 | <div class="video-details-attribute"> | 145 | <div class="video-details-attribute"> |
146 | <span class="video-details-attribute-label"> | 146 | <span class="video-details-attribute-label"> |
147 | Privacy: | ||
148 | </span> | ||
149 | <span class="video-details-attribute-value"> | ||
150 | {{ video.privacyLabel }} | ||
151 | </span> | ||
152 | </div> | ||
153 | |||
154 | <div class="video-details-attribute"> | ||
155 | <span class="video-details-attribute-label"> | ||
147 | Category: | 156 | Category: |
148 | </span> | 157 | </span> |
149 | <span class="video-details-attribute-value"> | 158 | <span class="video-details-attribute-value"> |
diff --git a/client/src/app/videos/shared/video-details.model.ts b/client/src/app/videos/shared/video-details.model.ts index 68ded5210..84f96a25f 100644 --- a/client/src/app/videos/shared/video-details.model.ts +++ b/client/src/app/videos/shared/video-details.model.ts | |||
@@ -5,7 +5,8 @@ import { | |||
5 | VideoFile, | 5 | VideoFile, |
6 | VideoChannel, | 6 | VideoChannel, |
7 | VideoResolution, | 7 | VideoResolution, |
8 | UserRight | 8 | UserRight, |
9 | VideoPrivacy | ||
9 | } from '../../../../../shared' | 10 | } from '../../../../../shared' |
10 | 11 | ||
11 | export class VideoDetails extends Video implements VideoDetailsServerModel { | 12 | export class VideoDetails extends Video implements VideoDetailsServerModel { |
@@ -41,10 +42,14 @@ export class VideoDetails extends Video implements VideoDetailsServerModel { | |||
41 | descriptionPath: string | 42 | descriptionPath: string |
42 | files: VideoFile[] | 43 | files: VideoFile[] |
43 | channel: VideoChannel | 44 | channel: VideoChannel |
45 | privacy: VideoPrivacy | ||
46 | privacyLabel: string | ||
44 | 47 | ||
45 | constructor (hash: VideoDetailsServerModel) { | 48 | constructor (hash: VideoDetailsServerModel) { |
46 | super(hash) | 49 | super(hash) |
47 | 50 | ||
51 | this.privacy = hash.privacy | ||
52 | this.privacyLabel = hash.privacyLabel | ||
48 | this.descriptionPath = hash.descriptionPath | 53 | this.descriptionPath = hash.descriptionPath |
49 | this.files = hash.files | 54 | this.files = hash.files |
50 | this.channel = hash.channel | 55 | this.channel = hash.channel |
diff --git a/client/src/app/videos/shared/video-edit.model.ts b/client/src/app/videos/shared/video-edit.model.ts index e0b7bf130..88d23a59f 100644 --- a/client/src/app/videos/shared/video-edit.model.ts +++ b/client/src/app/videos/shared/video-edit.model.ts | |||
@@ -1,4 +1,5 @@ | |||
1 | import { VideoDetails } from './video-details.model' | 1 | import { VideoDetails } from './video-details.model' |
2 | import { VideoPrivacy } from '../../../../../shared/models/videos/video-privacy.enum' | ||
2 | 3 | ||
3 | export class VideoEdit { | 4 | export class VideoEdit { |
4 | category: number | 5 | category: number |
@@ -9,6 +10,7 @@ export class VideoEdit { | |||
9 | tags: string[] | 10 | tags: string[] |
10 | nsfw: boolean | 11 | nsfw: boolean |
11 | channel: number | 12 | channel: number |
13 | privacy: VideoPrivacy | ||
12 | uuid?: string | 14 | uuid?: string |
13 | id?: number | 15 | id?: number |
14 | 16 | ||
@@ -23,6 +25,7 @@ export class VideoEdit { | |||
23 | this.tags = videoDetails.tags | 25 | this.tags = videoDetails.tags |
24 | this.nsfw = videoDetails.nsfw | 26 | this.nsfw = videoDetails.nsfw |
25 | this.channel = videoDetails.channel.id | 27 | this.channel = videoDetails.channel.id |
28 | this.privacy = videoDetails.privacy | ||
26 | } | 29 | } |
27 | 30 | ||
28 | patch (values: Object) { | 31 | patch (values: Object) { |
@@ -40,7 +43,8 @@ export class VideoEdit { | |||
40 | name: this.name, | 43 | name: this.name, |
41 | tags: this.tags, | 44 | tags: this.tags, |
42 | nsfw: this.nsfw, | 45 | nsfw: this.nsfw, |
43 | channel: this.channel | 46 | channel: this.channel, |
47 | privacy: this.privacy | ||
44 | } | 48 | } |
45 | } | 49 | } |
46 | } | 50 | } |
diff --git a/client/src/app/videos/shared/video.service.ts b/client/src/app/videos/shared/video.service.ts index 7d5372334..8459aa0d3 100644 --- a/client/src/app/videos/shared/video.service.ts +++ b/client/src/app/videos/shared/video.service.ts | |||
@@ -19,7 +19,6 @@ import { | |||
19 | UserVideoRate, | 19 | UserVideoRate, |
20 | VideoRateType, | 20 | VideoRateType, |
21 | VideoUpdate, | 21 | VideoUpdate, |
22 | VideoAbuseCreate, | ||
23 | UserVideoRateUpdate, | 22 | UserVideoRateUpdate, |
24 | Video as VideoServerModel, | 23 | Video as VideoServerModel, |
25 | VideoDetails as VideoDetailsServerModel, | 24 | VideoDetails as VideoDetailsServerModel, |
@@ -51,6 +50,7 @@ export class VideoService { | |||
51 | licence: video.licence, | 50 | licence: video.licence, |
52 | language, | 51 | language, |
53 | description: video.description, | 52 | description: video.description, |
53 | privacy: video.privacy, | ||
54 | tags: video.tags, | 54 | tags: video.tags, |
55 | nsfw: video.nsfw | 55 | nsfw: video.nsfw |
56 | } | 56 | } |
@@ -63,22 +63,35 @@ export class VideoService { | |||
63 | uploadVideo (video: FormData) { | 63 | uploadVideo (video: FormData) { |
64 | const req = new HttpRequest('POST', VideoService.BASE_VIDEO_URL + 'upload', video, { reportProgress: true }) | 64 | const req = new HttpRequest('POST', VideoService.BASE_VIDEO_URL + 'upload', video, { reportProgress: true }) |
65 | 65 | ||
66 | return this.authHttp.request(req) | 66 | return this.authHttp |
67 | .catch(this.restExtractor.handleError) | 67 | .request(req) |
68 | .catch(this.restExtractor.handleError) | ||
68 | } | 69 | } |
69 | 70 | ||
70 | getVideos (videoPagination: VideoPagination, sort: SortField) { | 71 | getMyVideos (videoPagination: VideoPagination, sort: SortField): Observable<{ videos: Video[], totalVideos: number}> { |
71 | const pagination = this.videoPaginationToRestPagination(videoPagination) | 72 | const pagination = this.videoPaginationToRestPagination(videoPagination) |
72 | 73 | ||
73 | let params = new HttpParams() | 74 | let params = new HttpParams() |
74 | params = this.restService.addRestGetParams(params, pagination, sort) | 75 | params = this.restService.addRestGetParams(params, pagination, sort) |
75 | 76 | ||
76 | return this.authHttp.get(VideoService.BASE_VIDEO_URL, { params }) | 77 | return this.authHttp.get(UserService.BASE_USERS_URL + '/me/videos', { params }) |
77 | .map(this.extractVideos) | 78 | .map(this.extractVideos) |
78 | .catch((res) => this.restExtractor.handleError(res)) | 79 | .catch((res) => this.restExtractor.handleError(res)) |
79 | } | 80 | } |
80 | 81 | ||
81 | searchVideos (search: Search, videoPagination: VideoPagination, sort: SortField) { | 82 | getVideos (videoPagination: VideoPagination, sort: SortField): Observable<{ videos: Video[], totalVideos: number}> { |
83 | const pagination = this.videoPaginationToRestPagination(videoPagination) | ||
84 | |||
85 | let params = new HttpParams() | ||
86 | params = this.restService.addRestGetParams(params, pagination, sort) | ||
87 | |||
88 | return this.authHttp | ||
89 | .get(VideoService.BASE_VIDEO_URL, { params }) | ||
90 | .map(this.extractVideos) | ||
91 | .catch((res) => this.restExtractor.handleError(res)) | ||
92 | } | ||
93 | |||
94 | searchVideos (search: Search, videoPagination: VideoPagination, sort: SortField): Observable<{ videos: Video[], totalVideos: number}> { | ||
82 | const url = VideoService.BASE_VIDEO_URL + 'search/' + encodeURIComponent(search.value) | 95 | const url = VideoService.BASE_VIDEO_URL + 'search/' + encodeURIComponent(search.value) |
83 | 96 | ||
84 | const pagination = this.videoPaginationToRestPagination(videoPagination) | 97 | const pagination = this.videoPaginationToRestPagination(videoPagination) |
@@ -88,15 +101,17 @@ export class VideoService { | |||
88 | 101 | ||
89 | if (search.field) params.set('field', search.field) | 102 | if (search.field) params.set('field', search.field) |
90 | 103 | ||
91 | return this.authHttp.get<ResultList<VideoServerModel>>(url, { params }) | 104 | return this.authHttp |
92 | .map(this.extractVideos) | 105 | .get<ResultList<VideoServerModel>>(url, { params }) |
93 | .catch((res) => this.restExtractor.handleError(res)) | 106 | .map(this.extractVideos) |
107 | .catch((res) => this.restExtractor.handleError(res)) | ||
94 | } | 108 | } |
95 | 109 | ||
96 | removeVideo (id: number) { | 110 | removeVideo (id: number) { |
97 | return this.authHttp.delete(VideoService.BASE_VIDEO_URL + id) | 111 | return this.authHttp |
98 | .map(this.restExtractor.extractDataBool) | 112 | .delete(VideoService.BASE_VIDEO_URL + id) |
99 | .catch((res) => this.restExtractor.handleError(res)) | 113 | .map(this.restExtractor.extractDataBool) |
114 | .catch((res) => this.restExtractor.handleError(res)) | ||
100 | } | 115 | } |
101 | 116 | ||
102 | loadCompleteDescription (descriptionPath: string) { | 117 | loadCompleteDescription (descriptionPath: string) { |
@@ -117,8 +132,9 @@ export class VideoService { | |||
117 | getUserVideoRating (id: number): Observable<UserVideoRate> { | 132 | getUserVideoRating (id: number): Observable<UserVideoRate> { |
118 | const url = UserService.BASE_USERS_URL + 'me/videos/' + id + '/rating' | 133 | const url = UserService.BASE_USERS_URL + 'me/videos/' + id + '/rating' |
119 | 134 | ||
120 | return this.authHttp.get(url) | 135 | return this.authHttp |
121 | .catch(res => this.restExtractor.handleError(res)) | 136 | .get(url) |
137 | .catch(res => this.restExtractor.handleError(res)) | ||
122 | } | 138 | } |
123 | 139 | ||
124 | private videoPaginationToRestPagination (videoPagination: VideoPagination) { | 140 | private videoPaginationToRestPagination (videoPagination: VideoPagination) { |
@@ -134,9 +150,10 @@ export class VideoService { | |||
134 | rating: rateType | 150 | rating: rateType |
135 | } | 151 | } |
136 | 152 | ||
137 | return this.authHttp.put(url, body) | 153 | return this.authHttp |
138 | .map(this.restExtractor.extractDataBool) | 154 | .put(url, body) |
139 | .catch(res => this.restExtractor.handleError(res)) | 155 | .map(this.restExtractor.extractDataBool) |
156 | .catch(res => this.restExtractor.handleError(res)) | ||
140 | } | 157 | } |
141 | 158 | ||
142 | private extractVideos (result: ResultList<VideoServerModel>) { | 159 | private extractVideos (result: ResultList<VideoServerModel>) { |
diff --git a/client/src/app/videos/video-list/index.ts b/client/src/app/videos/video-list/index.ts index a490e6bb5..ed2bb1657 100644 --- a/client/src/app/videos/video-list/index.ts +++ b/client/src/app/videos/video-list/index.ts | |||
@@ -1,4 +1,3 @@ | |||
1 | export * from './loader.component' | 1 | export * from './my-videos.component' |
2 | export * from './video-list.component' | 2 | export * from './video-list.component' |
3 | export * from './video-miniature.component' | 3 | export * from './shared' |
4 | export * from './video-sort.component' | ||
diff --git a/client/src/app/videos/video-list/my-videos.component.ts b/client/src/app/videos/video-list/my-videos.component.ts new file mode 100644 index 000000000..648741a40 --- /dev/null +++ b/client/src/app/videos/video-list/my-videos.component.ts | |||
@@ -0,0 +1,36 @@ | |||
1 | import { Component, OnDestroy, OnInit } from '@angular/core' | ||
2 | import { ActivatedRoute, Router } from '@angular/router' | ||
3 | |||
4 | import { NotificationsService } from 'angular2-notifications' | ||
5 | |||
6 | import { AbstractVideoList } from './shared' | ||
7 | import { VideoService } from '../shared' | ||
8 | |||
9 | @Component({ | ||
10 | selector: 'my-videos', | ||
11 | styleUrls: [ './shared/abstract-video-list.scss' ], | ||
12 | templateUrl: './shared/abstract-video-list.html' | ||
13 | }) | ||
14 | export class MyVideosComponent extends AbstractVideoList implements OnInit, OnDestroy { | ||
15 | |||
16 | constructor ( | ||
17 | protected router: Router, | ||
18 | protected route: ActivatedRoute, | ||
19 | protected notificationsService: NotificationsService, | ||
20 | private videoService: VideoService | ||
21 | ) { | ||
22 | super() | ||
23 | } | ||
24 | |||
25 | ngOnInit () { | ||
26 | super.ngOnInit() | ||
27 | } | ||
28 | |||
29 | ngOnDestroy () { | ||
30 | this.subActivatedRoute.unsubscribe() | ||
31 | } | ||
32 | |||
33 | getVideosObservable () { | ||
34 | return this.videoService.getMyVideos(this.pagination, this.sort) | ||
35 | } | ||
36 | } | ||
diff --git a/client/src/app/videos/video-list/video-list.component.html b/client/src/app/videos/video-list/shared/abstract-video-list.html index 680fba3f5..680fba3f5 100644 --- a/client/src/app/videos/video-list/video-list.component.html +++ b/client/src/app/videos/video-list/shared/abstract-video-list.html | |||
diff --git a/client/src/app/videos/video-list/video-list.component.scss b/client/src/app/videos/video-list/shared/abstract-video-list.scss index 4b4409602..4b4409602 100644 --- a/client/src/app/videos/video-list/video-list.component.scss +++ b/client/src/app/videos/video-list/shared/abstract-video-list.scss | |||
diff --git a/client/src/app/videos/video-list/shared/abstract-video-list.ts b/client/src/app/videos/video-list/shared/abstract-video-list.ts new file mode 100644 index 000000000..87d5bc48a --- /dev/null +++ b/client/src/app/videos/video-list/shared/abstract-video-list.ts | |||
@@ -0,0 +1,104 @@ | |||
1 | import { OnDestroy, OnInit } from '@angular/core' | ||
2 | import { ActivatedRoute, Router } from '@angular/router' | ||
3 | import { Subscription } from 'rxjs/Subscription' | ||
4 | import { BehaviorSubject } from 'rxjs/BehaviorSubject' | ||
5 | import { Observable } from 'rxjs/Observable' | ||
6 | |||
7 | import { NotificationsService } from 'angular2-notifications' | ||
8 | |||
9 | import { | ||
10 | SortField, | ||
11 | Video, | ||
12 | VideoPagination | ||
13 | } from '../../shared' | ||
14 | |||
15 | export abstract class AbstractVideoList implements OnInit, OnDestroy { | ||
16 | loading: BehaviorSubject<boolean> = new BehaviorSubject(false) | ||
17 | pagination: VideoPagination = { | ||
18 | currentPage: 1, | ||
19 | itemsPerPage: 25, | ||
20 | totalItems: null | ||
21 | } | ||
22 | sort: SortField | ||
23 | videos: Video[] = [] | ||
24 | |||
25 | protected notificationsService: NotificationsService | ||
26 | protected router: Router | ||
27 | protected route: ActivatedRoute | ||
28 | |||
29 | protected subActivatedRoute: Subscription | ||
30 | |||
31 | abstract getVideosObservable (): Observable<{ videos: Video[], totalVideos: number}> | ||
32 | |||
33 | ngOnInit () { | ||
34 | // Subscribe to route changes | ||
35 | this.subActivatedRoute = this.route.params.subscribe(routeParams => { | ||
36 | this.loadRouteParams(routeParams) | ||
37 | |||
38 | this.getVideos() | ||
39 | }) | ||
40 | } | ||
41 | |||
42 | ngOnDestroy () { | ||
43 | this.subActivatedRoute.unsubscribe() | ||
44 | } | ||
45 | |||
46 | getVideos () { | ||
47 | this.loading.next(true) | ||
48 | this.videos = [] | ||
49 | |||
50 | const observable = this.getVideosObservable() | ||
51 | |||
52 | observable.subscribe( | ||
53 | ({ videos, totalVideos }) => { | ||
54 | this.videos = videos | ||
55 | this.pagination.totalItems = totalVideos | ||
56 | |||
57 | this.loading.next(false) | ||
58 | }, | ||
59 | error => this.notificationsService.error('Error', error.text) | ||
60 | ) | ||
61 | } | ||
62 | |||
63 | isThereNoVideo () { | ||
64 | return !this.loading.getValue() && this.videos.length === 0 | ||
65 | } | ||
66 | |||
67 | onPageChanged (event: { page: number }) { | ||
68 | // Be sure the current page is set | ||
69 | this.pagination.currentPage = event.page | ||
70 | |||
71 | this.navigateToNewParams() | ||
72 | } | ||
73 | |||
74 | onSort (sort: SortField) { | ||
75 | this.sort = sort | ||
76 | |||
77 | this.navigateToNewParams() | ||
78 | } | ||
79 | |||
80 | protected buildRouteParams () { | ||
81 | // There is always a sort and a current page | ||
82 | const params = { | ||
83 | sort: this.sort, | ||
84 | page: this.pagination.currentPage | ||
85 | } | ||
86 | |||
87 | return params | ||
88 | } | ||
89 | |||
90 | protected loadRouteParams (routeParams: { [ key: string ]: any }) { | ||
91 | this.sort = routeParams['sort'] as SortField || '-createdAt' | ||
92 | |||
93 | if (routeParams['page'] !== undefined) { | ||
94 | this.pagination.currentPage = parseInt(routeParams['page'], 10) | ||
95 | } else { | ||
96 | this.pagination.currentPage = 1 | ||
97 | } | ||
98 | } | ||
99 | |||
100 | protected navigateToNewParams () { | ||
101 | const routeParams = this.buildRouteParams() | ||
102 | this.router.navigate([ '/videos/list', routeParams ]) | ||
103 | } | ||
104 | } | ||
diff --git a/client/src/app/videos/video-list/shared/index.ts b/client/src/app/videos/video-list/shared/index.ts new file mode 100644 index 000000000..2c9804e6d --- /dev/null +++ b/client/src/app/videos/video-list/shared/index.ts | |||
@@ -0,0 +1,4 @@ | |||
1 | export * from './abstract-video-list' | ||
2 | export * from './loader.component' | ||
3 | export * from './video-miniature.component' | ||
4 | export * from './video-sort.component' | ||
diff --git a/client/src/app/videos/video-list/loader.component.html b/client/src/app/videos/video-list/shared/loader.component.html index 38d06950e..38d06950e 100644 --- a/client/src/app/videos/video-list/loader.component.html +++ b/client/src/app/videos/video-list/shared/loader.component.html | |||
diff --git a/client/src/app/videos/video-list/loader.component.ts b/client/src/app/videos/video-list/shared/loader.component.ts index f37d70c85..f37d70c85 100644 --- a/client/src/app/videos/video-list/loader.component.ts +++ b/client/src/app/videos/video-list/shared/loader.component.ts | |||
diff --git a/client/src/app/videos/video-list/video-miniature.component.html b/client/src/app/videos/video-list/shared/video-miniature.component.html index abe87025f..abe87025f 100644 --- a/client/src/app/videos/video-list/video-miniature.component.html +++ b/client/src/app/videos/video-list/shared/video-miniature.component.html | |||
diff --git a/client/src/app/videos/video-list/video-miniature.component.scss b/client/src/app/videos/video-list/shared/video-miniature.component.scss index 066792d10..066792d10 100644 --- a/client/src/app/videos/video-list/video-miniature.component.scss +++ b/client/src/app/videos/video-list/shared/video-miniature.component.scss | |||
diff --git a/client/src/app/videos/video-list/video-miniature.component.ts b/client/src/app/videos/video-list/shared/video-miniature.component.ts index 18434dad2..e5a87907b 100644 --- a/client/src/app/videos/video-list/video-miniature.component.ts +++ b/client/src/app/videos/video-list/shared/video-miniature.component.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | import { Component, Input } from '@angular/core' | 1 | import { Component, Input } from '@angular/core' |
2 | 2 | ||
3 | import { SortField, Video } from '../shared' | 3 | import { SortField, Video } from '../../shared' |
4 | import { User } from '../../shared' | 4 | import { User } from '../../../shared' |
5 | 5 | ||
6 | @Component({ | 6 | @Component({ |
7 | selector: 'my-video-miniature', | 7 | selector: 'my-video-miniature', |
diff --git a/client/src/app/videos/video-list/video-sort.component.html b/client/src/app/videos/video-list/shared/video-sort.component.html index 3bece0b22..3bece0b22 100644 --- a/client/src/app/videos/video-list/video-sort.component.html +++ b/client/src/app/videos/video-list/shared/video-sort.component.html | |||
diff --git a/client/src/app/videos/video-list/video-sort.component.ts b/client/src/app/videos/video-list/shared/video-sort.component.ts index 64916bf16..8aa89d32b 100644 --- a/client/src/app/videos/video-list/video-sort.component.ts +++ b/client/src/app/videos/video-list/shared/video-sort.component.ts | |||
@@ -1,6 +1,6 @@ | |||
1 | import { Component, EventEmitter, Input, Output } from '@angular/core' | 1 | import { Component, EventEmitter, Input, Output } from '@angular/core' |
2 | 2 | ||
3 | import { SortField } from '../shared' | 3 | import { SortField } from '../../shared' |
4 | 4 | ||
5 | @Component({ | 5 | @Component({ |
6 | selector: 'my-video-sort', | 6 | selector: 'my-video-sort', |
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 bf6f60215..784162679 100644 --- a/client/src/app/videos/video-list/video-list.component.ts +++ b/client/src/app/videos/video-list/video-list.component.ts | |||
@@ -1,51 +1,33 @@ | |||
1 | import { Component, OnDestroy, OnInit } from '@angular/core' | 1 | import { Component, OnDestroy, OnInit } from '@angular/core' |
2 | import { ActivatedRoute, Router } from '@angular/router' | 2 | import { ActivatedRoute, Router } from '@angular/router' |
3 | import { Subscription } from 'rxjs/Subscription' | 3 | import { Subscription } from 'rxjs/Subscription' |
4 | import { BehaviorSubject } from 'rxjs/BehaviorSubject' | ||
5 | 4 | ||
6 | import { NotificationsService } from 'angular2-notifications' | 5 | import { NotificationsService } from 'angular2-notifications' |
7 | 6 | ||
8 | import { AuthService } from '../../core' | 7 | import { VideoService } from '../shared' |
9 | import { | 8 | import { Search, SearchField, SearchService } from '../../shared' |
10 | SortField, | 9 | import { AbstractVideoList } from './shared' |
11 | Video, | ||
12 | VideoService, | ||
13 | VideoPagination | ||
14 | } from '../shared' | ||
15 | import { Search, SearchField, SearchService, User } from '../../shared' | ||
16 | 10 | ||
17 | @Component({ | 11 | @Component({ |
18 | selector: 'my-videos-list', | 12 | selector: 'my-videos-list', |
19 | styleUrls: [ './video-list.component.scss' ], | 13 | styleUrls: [ './shared/abstract-video-list.scss' ], |
20 | templateUrl: './video-list.component.html' | 14 | templateUrl: './shared/abstract-video-list.html' |
21 | }) | 15 | }) |
22 | export class VideoListComponent implements OnInit, OnDestroy { | 16 | export class VideoListComponent extends AbstractVideoList implements OnInit, OnDestroy { |
23 | loading: BehaviorSubject<boolean> = new BehaviorSubject(false) | ||
24 | pagination: VideoPagination = { | ||
25 | currentPage: 1, | ||
26 | itemsPerPage: 25, | ||
27 | totalItems: null | ||
28 | } | ||
29 | sort: SortField | ||
30 | user: User | ||
31 | videos: Video[] = [] | ||
32 | |||
33 | private search: Search | 17 | private search: Search |
34 | private subActivatedRoute: Subscription | ||
35 | private subSearch: Subscription | 18 | private subSearch: Subscription |
36 | 19 | ||
37 | constructor ( | 20 | constructor ( |
38 | private authService: AuthService, | 21 | protected router: Router, |
39 | private notificationsService: NotificationsService, | 22 | protected route: ActivatedRoute, |
40 | private router: Router, | 23 | protected notificationsService: NotificationsService, |
41 | private route: ActivatedRoute, | ||
42 | private videoService: VideoService, | 24 | private videoService: VideoService, |
43 | private searchService: SearchService | 25 | private searchService: SearchService |
44 | ) {} | 26 | ) { |
27 | super() | ||
28 | } | ||
45 | 29 | ||
46 | ngOnInit () { | 30 | ngOnInit () { |
47 | this.user = this.authService.getUser() | ||
48 | |||
49 | // Subscribe to route changes | 31 | // Subscribe to route changes |
50 | this.subActivatedRoute = this.route.params.subscribe(routeParams => { | 32 | this.subActivatedRoute = this.route.params.subscribe(routeParams => { |
51 | this.loadRouteParams(routeParams) | 33 | this.loadRouteParams(routeParams) |
@@ -66,14 +48,12 @@ export class VideoListComponent implements OnInit, OnDestroy { | |||
66 | } | 48 | } |
67 | 49 | ||
68 | ngOnDestroy () { | 50 | ngOnDestroy () { |
69 | this.subActivatedRoute.unsubscribe() | 51 | super.ngOnDestroy() |
52 | |||
70 | this.subSearch.unsubscribe() | 53 | this.subSearch.unsubscribe() |
71 | } | 54 | } |
72 | 55 | ||
73 | getVideos () { | 56 | getVideosObservable () { |
74 | this.loading.next(true) | ||
75 | this.videos = [] | ||
76 | |||
77 | let observable = null | 57 | let observable = null |
78 | if (this.search.value) { | 58 | if (this.search.value) { |
79 | observable = this.videoService.searchVideos(this.search, this.pagination, this.sort) | 59 | observable = this.videoService.searchVideos(this.search, this.pagination, this.sort) |
@@ -81,40 +61,11 @@ export class VideoListComponent implements OnInit, OnDestroy { | |||
81 | observable = this.videoService.getVideos(this.pagination, this.sort) | 61 | observable = this.videoService.getVideos(this.pagination, this.sort) |
82 | } | 62 | } |
83 | 63 | ||
84 | observable.subscribe( | 64 | return observable |
85 | ({ videos, totalVideos }) => { | ||
86 | this.videos = videos | ||
87 | this.pagination.totalItems = totalVideos | ||
88 | |||
89 | this.loading.next(false) | ||
90 | }, | ||
91 | error => this.notificationsService.error('Error', error.text) | ||
92 | ) | ||
93 | } | ||
94 | |||
95 | isThereNoVideo () { | ||
96 | return !this.loading.getValue() && this.videos.length === 0 | ||
97 | } | ||
98 | |||
99 | onPageChanged (event: { page: number }) { | ||
100 | // Be sure the current page is set | ||
101 | this.pagination.currentPage = event.page | ||
102 | |||
103 | this.navigateToNewParams() | ||
104 | } | 65 | } |
105 | 66 | ||
106 | onSort (sort: SortField) { | 67 | protected buildRouteParams () { |
107 | this.sort = sort | 68 | const params = super.buildRouteParams() |
108 | |||
109 | this.navigateToNewParams() | ||
110 | } | ||
111 | |||
112 | private buildRouteParams () { | ||
113 | // There is always a sort and a current page | ||
114 | const params = { | ||
115 | sort: this.sort, | ||
116 | page: this.pagination.currentPage | ||
117 | } | ||
118 | 69 | ||
119 | // Maybe there is a search | 70 | // Maybe there is a search |
120 | if (this.search.value) { | 71 | if (this.search.value) { |
@@ -125,7 +76,9 @@ export class VideoListComponent implements OnInit, OnDestroy { | |||
125 | return params | 76 | return params |
126 | } | 77 | } |
127 | 78 | ||
128 | private loadRouteParams (routeParams: { [ key: string ]: any }) { | 79 | protected loadRouteParams (routeParams: { [ key: string ]: any }) { |
80 | super.loadRouteParams(routeParams) | ||
81 | |||
129 | if (routeParams['search'] !== undefined) { | 82 | if (routeParams['search'] !== undefined) { |
130 | this.search = { | 83 | this.search = { |
131 | value: routeParams['search'], | 84 | value: routeParams['search'], |
@@ -137,18 +90,5 @@ export class VideoListComponent implements OnInit, OnDestroy { | |||
137 | field: 'name' | 90 | field: 'name' |
138 | } | 91 | } |
139 | } | 92 | } |
140 | |||
141 | this.sort = routeParams['sort'] as SortField || '-createdAt' | ||
142 | |||
143 | if (routeParams['page'] !== undefined) { | ||
144 | this.pagination.currentPage = parseInt(routeParams['page'], 10) | ||
145 | } else { | ||
146 | this.pagination.currentPage = 1 | ||
147 | } | ||
148 | } | ||
149 | |||
150 | private navigateToNewParams () { | ||
151 | const routeParams = this.buildRouteParams() | ||
152 | this.router.navigate([ '/videos/list', routeParams ]) | ||
153 | } | 93 | } |
154 | } | 94 | } |
diff --git a/client/src/app/videos/videos-routing.module.ts b/client/src/app/videos/videos-routing.module.ts index d3869748b..3ca3e5486 100644 --- a/client/src/app/videos/videos-routing.module.ts +++ b/client/src/app/videos/videos-routing.module.ts | |||
@@ -3,7 +3,7 @@ import { RouterModule, Routes } from '@angular/router' | |||
3 | 3 | ||
4 | import { MetaGuard } from '@ngx-meta/core' | 4 | import { MetaGuard } from '@ngx-meta/core' |
5 | 5 | ||
6 | import { VideoListComponent } from './video-list' | 6 | import { VideoListComponent, MyVideosComponent } from './video-list' |
7 | import { VideosComponent } from './videos.component' | 7 | import { VideosComponent } from './videos.component' |
8 | 8 | ||
9 | const videosRoutes: Routes = [ | 9 | const videosRoutes: Routes = [ |
@@ -13,6 +13,15 @@ const videosRoutes: Routes = [ | |||
13 | canActivateChild: [ MetaGuard ], | 13 | canActivateChild: [ MetaGuard ], |
14 | children: [ | 14 | children: [ |
15 | { | 15 | { |
16 | path: 'mine', | ||
17 | component: MyVideosComponent, | ||
18 | data: { | ||
19 | meta: { | ||
20 | title: 'My videos' | ||
21 | } | ||
22 | } | ||
23 | }, | ||
24 | { | ||
16 | path: 'list', | 25 | path: 'list', |
17 | component: VideoListComponent, | 26 | component: VideoListComponent, |
18 | data: { | 27 | data: { |
diff --git a/client/src/app/videos/videos.module.ts b/client/src/app/videos/videos.module.ts index 3a0c3feac..ecc351b65 100644 --- a/client/src/app/videos/videos.module.ts +++ b/client/src/app/videos/videos.module.ts | |||
@@ -2,7 +2,13 @@ import { NgModule } from '@angular/core' | |||
2 | 2 | ||
3 | import { VideosRoutingModule } from './videos-routing.module' | 3 | import { VideosRoutingModule } from './videos-routing.module' |
4 | import { VideosComponent } from './videos.component' | 4 | import { VideosComponent } from './videos.component' |
5 | import { LoaderComponent, VideoListComponent, VideoMiniatureComponent, VideoSortComponent } from './video-list' | 5 | import { |
6 | LoaderComponent, | ||
7 | VideoListComponent, | ||
8 | MyVideosComponent, | ||
9 | VideoMiniatureComponent, | ||
10 | VideoSortComponent | ||
11 | } from './video-list' | ||
6 | import { VideoService } from './shared' | 12 | import { VideoService } from './shared' |
7 | import { SharedModule } from '../shared' | 13 | import { SharedModule } from '../shared' |
8 | 14 | ||
@@ -16,6 +22,7 @@ import { SharedModule } from '../shared' | |||
16 | VideosComponent, | 22 | VideosComponent, |
17 | 23 | ||
18 | VideoListComponent, | 24 | VideoListComponent, |
25 | MyVideosComponent, | ||
19 | VideoMiniatureComponent, | 26 | VideoMiniatureComponent, |
20 | VideoSortComponent, | 27 | VideoSortComponent, |
21 | 28 | ||
diff --git a/client/src/sass/video-js-custom.scss b/client/src/sass/video-js-custom.scss index c5f668f17..6ad21988e 100644 --- a/client/src/sass/video-js-custom.scss +++ b/client/src/sass/video-js-custom.scss | |||
@@ -334,71 +334,34 @@ $slider-bg-color: lighten($primary-background-color, 33%); | |||
334 | 334 | ||
335 | // Thanks: https://projects.lukehaas.me/css-loaders/ | 335 | // Thanks: https://projects.lukehaas.me/css-loaders/ |
336 | .vjs-loading-spinner { | 336 | .vjs-loading-spinner { |
337 | border: none; | 337 | margin: -25px 0 0 -25px; |
338 | opacity: 1; | 338 | position: absolute; |
339 | top: 50%; | ||
340 | left: 50%; | ||
339 | font-size: 10px; | 341 | font-size: 10px; |
340 | text-indent: -9999em; | ||
341 | width: 5em; | ||
342 | height: 5em; | ||
343 | border-radius: 50%; | ||
344 | background: #ffffff; | ||
345 | background: -moz-linear-gradient(left, #ffffff 10%, rgba(255, 255, 255, 0) 42%); | ||
346 | background: -webkit-linear-gradient(left, #ffffff 10%, rgba(255, 255, 255, 0) 42%); | ||
347 | background: -o-linear-gradient(left, #ffffff 10%, rgba(255, 255, 255, 0) 42%); | ||
348 | background: -ms-linear-gradient(left, #ffffff 10%, rgba(255, 255, 255, 0) 42%); | ||
349 | background: linear-gradient(to right, #ffffff 10%, rgba(255, 255, 255, 0) 42%); | ||
350 | position: relative; | 342 | position: relative; |
351 | -webkit-animation: load3 1.4s infinite linear; | 343 | text-indent: -9999em; |
352 | animation: load3 1.4s infinite linear; | 344 | border: 0.7em solid rgba(255, 255, 255, 0.2); |
353 | -webkit-transform: translateZ(0); | 345 | border-left-color: #ffffff; |
354 | -ms-transform: translateZ(0); | ||
355 | transform: translateZ(0); | 346 | transform: translateZ(0); |
347 | animation: spinner 1.4s infinite linear; | ||
356 | 348 | ||
357 | &:before { | 349 | &:before { |
358 | width: 50%; | ||
359 | height: 50%; | ||
360 | background: #ffffff; | ||
361 | border-radius: 100% 0 0 0; | ||
362 | position: absolute; | ||
363 | top: 0; | ||
364 | left: 0; | ||
365 | content: ''; | ||
366 | animation: none !important; | 350 | animation: none !important; |
367 | margin: 0 !important; | ||
368 | } | 351 | } |
369 | 352 | ||
370 | &:after { | 353 | &:after { |
371 | background: #000; | ||
372 | width: 75%; | ||
373 | height: 75%; | ||
374 | border-radius: 50%; | 354 | border-radius: 50%; |
375 | content: ''; | 355 | width: 6em; |
376 | margin: auto; | 356 | height: 6em; |
377 | position: absolute; | ||
378 | top: 0; | ||
379 | left: 0; | ||
380 | bottom: 0; | ||
381 | right: 0; | ||
382 | animation: none !important; | 357 | animation: none !important; |
383 | } | 358 | } |
384 | 359 | ||
385 | @-webkit-keyframes load3 { | 360 | @keyframes spinner { |
386 | 0% { | ||
387 | -webkit-transform: rotate(0deg); | ||
388 | transform: rotate(0deg); | ||
389 | } | ||
390 | 100% { | ||
391 | -webkit-transform: rotate(360deg); | ||
392 | transform: rotate(360deg); | ||
393 | } | ||
394 | } | ||
395 | @keyframes load3 { | ||
396 | 0% { | 361 | 0% { |
397 | -webkit-transform: rotate(0deg); | ||
398 | transform: rotate(0deg); | 362 | transform: rotate(0deg); |
399 | } | 363 | } |
400 | 100% { | 364 | 100% { |
401 | -webkit-transform: rotate(360deg); | ||
402 | transform: rotate(360deg); | 365 | transform: rotate(360deg); |
403 | } | 366 | } |
404 | } | 367 | } |
diff --git a/server/controllers/api/remote/videos.ts b/server/controllers/api/remote/videos.ts index 3ecc62ada..cba47f0a1 100644 --- a/server/controllers/api/remote/videos.ts +++ b/server/controllers/api/remote/videos.ts | |||
@@ -267,7 +267,8 @@ async function addRemoteVideo (videoToCreateData: RemoteVideoCreateData, fromPod | |||
267 | views: videoToCreateData.views, | 267 | views: videoToCreateData.views, |
268 | likes: videoToCreateData.likes, | 268 | likes: videoToCreateData.likes, |
269 | dislikes: videoToCreateData.dislikes, | 269 | dislikes: videoToCreateData.dislikes, |
270 | remote: true | 270 | remote: true, |
271 | privacy: videoToCreateData.privacy | ||
271 | } | 272 | } |
272 | 273 | ||
273 | const video = db.Video.build(videoData) | 274 | const video = db.Video.build(videoData) |
@@ -334,6 +335,7 @@ async function updateRemoteVideo (videoAttributesToUpdate: RemoteVideoUpdateData | |||
334 | videoInstance.set('views', videoAttributesToUpdate.views) | 335 | videoInstance.set('views', videoAttributesToUpdate.views) |
335 | videoInstance.set('likes', videoAttributesToUpdate.likes) | 336 | videoInstance.set('likes', videoAttributesToUpdate.likes) |
336 | videoInstance.set('dislikes', videoAttributesToUpdate.dislikes) | 337 | videoInstance.set('dislikes', videoAttributesToUpdate.dislikes) |
338 | videoInstance.set('privacy', videoAttributesToUpdate.privacy) | ||
337 | 339 | ||
338 | await videoInstance.save(sequelizeOptions) | 340 | await videoInstance.save(sequelizeOptions) |
339 | 341 | ||
diff --git a/server/controllers/api/users.ts b/server/controllers/api/users.ts index fdc9b0c87..dcd407fdf 100644 --- a/server/controllers/api/users.ts +++ b/server/controllers/api/users.ts | |||
@@ -30,6 +30,8 @@ import { | |||
30 | } from '../../../shared' | 30 | } from '../../../shared' |
31 | import { createUserAuthorAndChannel } from '../../lib' | 31 | import { createUserAuthorAndChannel } from '../../lib' |
32 | import { UserInstance } from '../../models' | 32 | import { UserInstance } from '../../models' |
33 | import { videosSortValidator } from '../../middlewares/validators/sort' | ||
34 | import { setVideosSort } from '../../middlewares/sort' | ||
33 | 35 | ||
34 | const usersRouter = express.Router() | 36 | const usersRouter = express.Router() |
35 | 37 | ||
@@ -38,6 +40,15 @@ usersRouter.get('/me', | |||
38 | asyncMiddleware(getUserInformation) | 40 | asyncMiddleware(getUserInformation) |
39 | ) | 41 | ) |
40 | 42 | ||
43 | usersRouter.get('/me/videos', | ||
44 | authenticate, | ||
45 | paginationValidator, | ||
46 | videosSortValidator, | ||
47 | setVideosSort, | ||
48 | setPagination, | ||
49 | asyncMiddleware(getUserVideos) | ||
50 | ) | ||
51 | |||
41 | usersRouter.get('/me/videos/:videoId/rating', | 52 | usersRouter.get('/me/videos/:videoId/rating', |
42 | authenticate, | 53 | authenticate, |
43 | usersVideoRatingValidator, | 54 | usersVideoRatingValidator, |
@@ -101,6 +112,13 @@ export { | |||
101 | 112 | ||
102 | // --------------------------------------------------------------------------- | 113 | // --------------------------------------------------------------------------- |
103 | 114 | ||
115 | async function getUserVideos (req: express.Request, res: express.Response, next: express.NextFunction) { | ||
116 | const user = res.locals.oauth.token.User | ||
117 | const resultList = await db.Video.listUserVideosForApi(user.id ,req.query.start, req.query.count, req.query.sort) | ||
118 | |||
119 | return res.json(getFormattedObjects(resultList.data, resultList.total)) | ||
120 | } | ||
121 | |||
104 | async function createUserRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) { | 122 | async function createUserRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) { |
105 | const options = { | 123 | const options = { |
106 | arguments: [ req, res ], | 124 | arguments: [ req, res ], |
@@ -146,13 +164,14 @@ async function registerUser (req: express.Request, res: express.Response, next: | |||
146 | } | 164 | } |
147 | 165 | ||
148 | async function getUserInformation (req: express.Request, res: express.Response, next: express.NextFunction) { | 166 | async function getUserInformation (req: express.Request, res: express.Response, next: express.NextFunction) { |
167 | // We did not load channels in res.locals.user | ||
149 | const user = await db.User.loadByUsernameAndPopulateChannels(res.locals.oauth.token.user.username) | 168 | const user = await db.User.loadByUsernameAndPopulateChannels(res.locals.oauth.token.user.username) |
150 | 169 | ||
151 | return res.json(user.toFormattedJSON()) | 170 | return res.json(user.toFormattedJSON()) |
152 | } | 171 | } |
153 | 172 | ||
154 | function getUser (req: express.Request, res: express.Response, next: express.NextFunction) { | 173 | function getUser (req: express.Request, res: express.Response, next: express.NextFunction) { |
155 | return res.json(res.locals.user.toFormattedJSON()) | 174 | return res.json(res.locals.oauth.token.User.toFormattedJSON()) |
156 | } | 175 | } |
157 | 176 | ||
158 | async function getUserVideoRating (req: express.Request, res: express.Response, next: express.NextFunction) { | 177 | async function getUserVideoRating (req: express.Request, res: express.Response, next: express.NextFunction) { |
diff --git a/server/controllers/api/videos/index.ts b/server/controllers/api/videos/index.ts index 49f0e4630..4dd09917b 100644 --- a/server/controllers/api/videos/index.ts +++ b/server/controllers/api/videos/index.ts | |||
@@ -9,7 +9,8 @@ import { | |||
9 | REQUEST_VIDEO_EVENT_TYPES, | 9 | REQUEST_VIDEO_EVENT_TYPES, |
10 | VIDEO_CATEGORIES, | 10 | VIDEO_CATEGORIES, |
11 | VIDEO_LICENCES, | 11 | VIDEO_LICENCES, |
12 | VIDEO_LANGUAGES | 12 | VIDEO_LANGUAGES, |
13 | VIDEO_PRIVACIES | ||
13 | } from '../../../initializers' | 14 | } from '../../../initializers' |
14 | import { | 15 | import { |
15 | addEventToRemoteVideo, | 16 | addEventToRemoteVideo, |
@@ -43,7 +44,7 @@ import { | |||
43 | resetSequelizeInstance | 44 | resetSequelizeInstance |
44 | } from '../../../helpers' | 45 | } from '../../../helpers' |
45 | import { VideoInstance } from '../../../models' | 46 | import { VideoInstance } from '../../../models' |
46 | import { VideoCreate, VideoUpdate } from '../../../../shared' | 47 | import { VideoCreate, VideoUpdate, VideoPrivacy } from '../../../../shared' |
47 | 48 | ||
48 | import { abuseVideoRouter } from './abuse' | 49 | import { abuseVideoRouter } from './abuse' |
49 | import { blacklistRouter } from './blacklist' | 50 | import { blacklistRouter } from './blacklist' |
@@ -84,6 +85,7 @@ videosRouter.use('/', videoChannelRouter) | |||
84 | videosRouter.get('/categories', listVideoCategories) | 85 | videosRouter.get('/categories', listVideoCategories) |
85 | videosRouter.get('/licences', listVideoLicences) | 86 | videosRouter.get('/licences', listVideoLicences) |
86 | videosRouter.get('/languages', listVideoLanguages) | 87 | videosRouter.get('/languages', listVideoLanguages) |
88 | videosRouter.get('/privacies', listVideoPrivacies) | ||
87 | 89 | ||
88 | videosRouter.get('/', | 90 | videosRouter.get('/', |
89 | paginationValidator, | 91 | paginationValidator, |
@@ -149,6 +151,10 @@ function listVideoLanguages (req: express.Request, res: express.Response) { | |||
149 | res.json(VIDEO_LANGUAGES) | 151 | res.json(VIDEO_LANGUAGES) |
150 | } | 152 | } |
151 | 153 | ||
154 | function listVideoPrivacies (req: express.Request, res: express.Response) { | ||
155 | res.json(VIDEO_PRIVACIES) | ||
156 | } | ||
157 | |||
152 | // Wrapper to video add that retry the function if there is a database error | 158 | // Wrapper to video add that retry the function if there is a database error |
153 | // We need this because we run the transaction in SERIALIZABLE isolation that can fail | 159 | // We need this because we run the transaction in SERIALIZABLE isolation that can fail |
154 | async function addVideoRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) { | 160 | async function addVideoRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) { |
@@ -179,6 +185,7 @@ async function addVideo (req: express.Request, res: express.Response, videoPhysi | |||
179 | language: videoInfo.language, | 185 | language: videoInfo.language, |
180 | nsfw: videoInfo.nsfw, | 186 | nsfw: videoInfo.nsfw, |
181 | description: videoInfo.description, | 187 | description: videoInfo.description, |
188 | privacy: videoInfo.privacy, | ||
182 | duration: videoPhysicalFile['duration'], // duration was added by a previous middleware | 189 | duration: videoPhysicalFile['duration'], // duration was added by a previous middleware |
183 | channelId: res.locals.videoChannel.id | 190 | channelId: res.locals.videoChannel.id |
184 | } | 191 | } |
@@ -240,6 +247,8 @@ async function addVideo (req: express.Request, res: express.Response, videoPhysi | |||
240 | 247 | ||
241 | // Let transcoding job send the video to friends because the video file extension might change | 248 | // Let transcoding job send the video to friends because the video file extension might change |
242 | if (CONFIG.TRANSCODING.ENABLED === true) return undefined | 249 | if (CONFIG.TRANSCODING.ENABLED === true) return undefined |
250 | // Don't send video to remote pods, it is private | ||
251 | if (video.privacy === VideoPrivacy.PRIVATE) return undefined | ||
243 | 252 | ||
244 | const remoteVideo = await video.toAddRemoteJSON() | 253 | const remoteVideo = await video.toAddRemoteJSON() |
245 | // Now we'll add the video's meta data to our friends | 254 | // Now we'll add the video's meta data to our friends |
@@ -264,6 +273,7 @@ async function updateVideo (req: express.Request, res: express.Response) { | |||
264 | const videoInstance = res.locals.video | 273 | const videoInstance = res.locals.video |
265 | const videoFieldsSave = videoInstance.toJSON() | 274 | const videoFieldsSave = videoInstance.toJSON() |
266 | const videoInfoToUpdate: VideoUpdate = req.body | 275 | const videoInfoToUpdate: VideoUpdate = req.body |
276 | const wasPrivateVideo = videoInstance.privacy === VideoPrivacy.PRIVATE | ||
267 | 277 | ||
268 | try { | 278 | try { |
269 | await db.sequelize.transaction(async t => { | 279 | await db.sequelize.transaction(async t => { |
@@ -276,6 +286,7 @@ async function updateVideo (req: express.Request, res: express.Response) { | |||
276 | if (videoInfoToUpdate.licence !== undefined) videoInstance.set('licence', videoInfoToUpdate.licence) | 286 | if (videoInfoToUpdate.licence !== undefined) videoInstance.set('licence', videoInfoToUpdate.licence) |
277 | if (videoInfoToUpdate.language !== undefined) videoInstance.set('language', videoInfoToUpdate.language) | 287 | if (videoInfoToUpdate.language !== undefined) videoInstance.set('language', videoInfoToUpdate.language) |
278 | if (videoInfoToUpdate.nsfw !== undefined) videoInstance.set('nsfw', videoInfoToUpdate.nsfw) | 288 | if (videoInfoToUpdate.nsfw !== undefined) videoInstance.set('nsfw', videoInfoToUpdate.nsfw) |
289 | if (videoInfoToUpdate.privacy !== undefined) videoInstance.set('privacy', videoInfoToUpdate.privacy) | ||
279 | if (videoInfoToUpdate.description !== undefined) videoInstance.set('description', videoInfoToUpdate.description) | 290 | if (videoInfoToUpdate.description !== undefined) videoInstance.set('description', videoInfoToUpdate.description) |
280 | 291 | ||
281 | await videoInstance.save(sequelizeOptions) | 292 | await videoInstance.save(sequelizeOptions) |
@@ -287,10 +298,17 @@ async function updateVideo (req: express.Request, res: express.Response) { | |||
287 | videoInstance.Tags = tagInstances | 298 | videoInstance.Tags = tagInstances |
288 | } | 299 | } |
289 | 300 | ||
290 | const json = videoInstance.toUpdateRemoteJSON() | ||
291 | |||
292 | // Now we'll update the video's meta data to our friends | 301 | // Now we'll update the video's meta data to our friends |
293 | return updateVideoToFriends(json, t) | 302 | if (wasPrivateVideo === false) { |
303 | const json = videoInstance.toUpdateRemoteJSON() | ||
304 | return updateVideoToFriends(json, t) | ||
305 | } | ||
306 | |||
307 | // Video is not private anymore, send a create action to remote pods | ||
308 | if (wasPrivateVideo === true && videoInstance.privacy !== VideoPrivacy.PRIVATE) { | ||
309 | const remoteVideo = await videoInstance.toAddRemoteJSON() | ||
310 | return addVideoToFriends(remoteVideo, t) | ||
311 | } | ||
294 | }) | 312 | }) |
295 | 313 | ||
296 | logger.info('Video with name %s and uuid %s updated.', videoInstance.name, videoInstance.uuid) | 314 | logger.info('Video with name %s and uuid %s updated.', videoInstance.name, videoInstance.uuid) |
diff --git a/server/helpers/custom-validators/videos.ts b/server/helpers/custom-validators/videos.ts index 5b9102275..f3fdcaf2d 100644 --- a/server/helpers/custom-validators/videos.ts +++ b/server/helpers/custom-validators/videos.ts | |||
@@ -11,6 +11,7 @@ import { | |||
11 | VIDEO_LICENCES, | 11 | VIDEO_LICENCES, |
12 | VIDEO_LANGUAGES, | 12 | VIDEO_LANGUAGES, |
13 | VIDEO_RATE_TYPES, | 13 | VIDEO_RATE_TYPES, |
14 | VIDEO_PRIVACIES, | ||
14 | database as db | 15 | database as db |
15 | } from '../../initializers' | 16 | } from '../../initializers' |
16 | import { isUserUsernameValid } from './users' | 17 | import { isUserUsernameValid } from './users' |
@@ -36,6 +37,15 @@ function isVideoLicenceValid (value: number) { | |||
36 | return VIDEO_LICENCES[value] !== undefined | 37 | return VIDEO_LICENCES[value] !== undefined |
37 | } | 38 | } |
38 | 39 | ||
40 | function isVideoPrivacyValid (value: string) { | ||
41 | return VIDEO_PRIVACIES[value] !== undefined | ||
42 | } | ||
43 | |||
44 | // Maybe we don't know the remote privacy setting, but that doesn't matter | ||
45 | function isRemoteVideoPrivacyValid (value: string) { | ||
46 | return validator.isInt('' + value) | ||
47 | } | ||
48 | |||
39 | // Maybe we don't know the remote licence, but that doesn't matter | 49 | // Maybe we don't know the remote licence, but that doesn't matter |
40 | function isRemoteVideoLicenceValid (value: string) { | 50 | function isRemoteVideoLicenceValid (value: string) { |
41 | return validator.isInt('' + value) | 51 | return validator.isInt('' + value) |
@@ -195,6 +205,8 @@ export { | |||
195 | isVideoDislikesValid, | 205 | isVideoDislikesValid, |
196 | isVideoEventCountValid, | 206 | isVideoEventCountValid, |
197 | isVideoFileSizeValid, | 207 | isVideoFileSizeValid, |
208 | isVideoPrivacyValid, | ||
209 | isRemoteVideoPrivacyValid, | ||
198 | isVideoFileResolutionValid, | 210 | isVideoFileResolutionValid, |
199 | checkVideoExists, | 211 | checkVideoExists, |
200 | isRemoteVideoCategoryValid, | 212 | isRemoteVideoCategoryValid, |
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts index adccb9f41..d349abaf0 100644 --- a/server/initializers/constants.ts +++ b/server/initializers/constants.ts | |||
@@ -12,10 +12,11 @@ import { | |||
12 | RemoteVideoRequestType, | 12 | RemoteVideoRequestType, |
13 | JobState | 13 | JobState |
14 | } from '../../shared/models' | 14 | } from '../../shared/models' |
15 | import { VideoPrivacy } from '../../shared/models/videos/video-privacy.enum' | ||
15 | 16 | ||
16 | // --------------------------------------------------------------------------- | 17 | // --------------------------------------------------------------------------- |
17 | 18 | ||
18 | const LAST_MIGRATION_VERSION = 90 | 19 | const LAST_MIGRATION_VERSION = 95 |
19 | 20 | ||
20 | // --------------------------------------------------------------------------- | 21 | // --------------------------------------------------------------------------- |
21 | 22 | ||
@@ -196,6 +197,12 @@ const VIDEO_LANGUAGES = { | |||
196 | 14: 'Italian' | 197 | 14: 'Italian' |
197 | } | 198 | } |
198 | 199 | ||
200 | const VIDEO_PRIVACIES = { | ||
201 | [VideoPrivacy.PUBLIC]: 'Public', | ||
202 | [VideoPrivacy.UNLISTED]: 'Unlisted', | ||
203 | [VideoPrivacy.PRIVATE]: 'Private' | ||
204 | } | ||
205 | |||
199 | // --------------------------------------------------------------------------- | 206 | // --------------------------------------------------------------------------- |
200 | 207 | ||
201 | // Score a pod has when we create it as a friend | 208 | // Score a pod has when we create it as a friend |
@@ -394,6 +401,7 @@ export { | |||
394 | THUMBNAILS_SIZE, | 401 | THUMBNAILS_SIZE, |
395 | VIDEO_CATEGORIES, | 402 | VIDEO_CATEGORIES, |
396 | VIDEO_LANGUAGES, | 403 | VIDEO_LANGUAGES, |
404 | VIDEO_PRIVACIES, | ||
397 | VIDEO_LICENCES, | 405 | VIDEO_LICENCES, |
398 | VIDEO_RATE_TYPES | 406 | VIDEO_RATE_TYPES |
399 | } | 407 | } |
diff --git a/server/initializers/migrations/0095-videos-privacy.ts b/server/initializers/migrations/0095-videos-privacy.ts new file mode 100644 index 000000000..4c2bf91d0 --- /dev/null +++ b/server/initializers/migrations/0095-videos-privacy.ts | |||
@@ -0,0 +1,35 @@ | |||
1 | import * as Sequelize from 'sequelize' | ||
2 | |||
3 | async function up (utils: { | ||
4 | transaction: Sequelize.Transaction, | ||
5 | queryInterface: Sequelize.QueryInterface, | ||
6 | sequelize: Sequelize.Sequelize, | ||
7 | db: any | ||
8 | }): Promise<void> { | ||
9 | const q = utils.queryInterface | ||
10 | |||
11 | const data = { | ||
12 | type: Sequelize.INTEGER, | ||
13 | defaultValue: null, | ||
14 | allowNull: true | ||
15 | } | ||
16 | await q.addColumn('Videos', 'privacy', data) | ||
17 | |||
18 | const query = 'UPDATE "Videos" SET "privacy" = 1' | ||
19 | const options = { | ||
20 | type: Sequelize.QueryTypes.BULKUPDATE | ||
21 | } | ||
22 | await utils.sequelize.query(query, options) | ||
23 | |||
24 | data.allowNull = false | ||
25 | await q.changeColumn('Videos', 'privacy', data) | ||
26 | } | ||
27 | |||
28 | function down (options) { | ||
29 | throw new Error('Not implemented.') | ||
30 | } | ||
31 | |||
32 | export { | ||
33 | up, | ||
34 | down | ||
35 | } | ||
diff --git a/server/middlewares/validators/videos.ts b/server/middlewares/validators/videos.ts index 0c07404c5..e197d4606 100644 --- a/server/middlewares/validators/videos.ts +++ b/server/middlewares/validators/videos.ts | |||
@@ -20,9 +20,10 @@ import { | |||
20 | isVideoRatingTypeValid, | 20 | isVideoRatingTypeValid, |
21 | getDurationFromVideoFile, | 21 | getDurationFromVideoFile, |
22 | checkVideoExists, | 22 | checkVideoExists, |
23 | isIdValid | 23 | isIdValid, |
24 | isVideoPrivacyValid | ||
24 | } from '../../helpers' | 25 | } from '../../helpers' |
25 | import { UserRight } from '../../../shared' | 26 | import { UserRight, VideoPrivacy } from '../../../shared' |
26 | 27 | ||
27 | const videosAddValidator = [ | 28 | const videosAddValidator = [ |
28 | body('videofile').custom((value, { req }) => isVideoFile(req.files)).withMessage( | 29 | body('videofile').custom((value, { req }) => isVideoFile(req.files)).withMessage( |
@@ -36,6 +37,7 @@ const videosAddValidator = [ | |||
36 | body('nsfw').custom(isVideoNSFWValid).withMessage('Should have a valid NSFW attribute'), | 37 | body('nsfw').custom(isVideoNSFWValid).withMessage('Should have a valid NSFW attribute'), |
37 | body('description').custom(isVideoDescriptionValid).withMessage('Should have a valid description'), | 38 | body('description').custom(isVideoDescriptionValid).withMessage('Should have a valid description'), |
38 | body('channelId').custom(isIdValid).withMessage('Should have correct video channel id'), | 39 | body('channelId').custom(isIdValid).withMessage('Should have correct video channel id'), |
40 | body('privacy').custom(isVideoPrivacyValid).withMessage('Should have correct video privacy'), | ||
39 | body('tags').optional().custom(isVideoTagsValid).withMessage('Should have correct tags'), | 41 | body('tags').optional().custom(isVideoTagsValid).withMessage('Should have correct tags'), |
40 | 42 | ||
41 | (req: express.Request, res: express.Response, next: express.NextFunction) => { | 43 | (req: express.Request, res: express.Response, next: express.NextFunction) => { |
@@ -110,6 +112,7 @@ const videosUpdateValidator = [ | |||
110 | body('licence').optional().custom(isVideoLicenceValid).withMessage('Should have a valid licence'), | 112 | body('licence').optional().custom(isVideoLicenceValid).withMessage('Should have a valid licence'), |
111 | body('language').optional().custom(isVideoLanguageValid).withMessage('Should have a valid language'), | 113 | body('language').optional().custom(isVideoLanguageValid).withMessage('Should have a valid language'), |
112 | body('nsfw').optional().custom(isVideoNSFWValid).withMessage('Should have a valid NSFW attribute'), | 114 | body('nsfw').optional().custom(isVideoNSFWValid).withMessage('Should have a valid NSFW attribute'), |
115 | body('privacy').custom(isVideoPrivacyValid).withMessage('Should have correct video privacy'), | ||
113 | body('description').optional().custom(isVideoDescriptionValid).withMessage('Should have a valid description'), | 116 | body('description').optional().custom(isVideoDescriptionValid).withMessage('Should have a valid description'), |
114 | body('tags').optional().custom(isVideoTagsValid).withMessage('Should have correct tags'), | 117 | body('tags').optional().custom(isVideoTagsValid).withMessage('Should have correct tags'), |
115 | 118 | ||
@@ -118,19 +121,27 @@ const videosUpdateValidator = [ | |||
118 | 121 | ||
119 | checkErrors(req, res, () => { | 122 | checkErrors(req, res, () => { |
120 | checkVideoExists(req.params.id, res, () => { | 123 | checkVideoExists(req.params.id, res, () => { |
124 | const video = res.locals.video | ||
125 | |||
121 | // We need to make additional checks | 126 | // We need to make additional checks |
122 | if (res.locals.video.isOwned() === false) { | 127 | if (video.isOwned() === false) { |
123 | return res.status(403) | 128 | return res.status(403) |
124 | .json({ error: 'Cannot update video of another pod' }) | 129 | .json({ error: 'Cannot update video of another pod' }) |
125 | .end() | 130 | .end() |
126 | } | 131 | } |
127 | 132 | ||
128 | if (res.locals.video.VideoChannel.Author.userId !== res.locals.oauth.token.User.id) { | 133 | if (video.VideoChannel.Author.userId !== res.locals.oauth.token.User.id) { |
129 | return res.status(403) | 134 | return res.status(403) |
130 | .json({ error: 'Cannot update video of another user' }) | 135 | .json({ error: 'Cannot update video of another user' }) |
131 | .end() | 136 | .end() |
132 | } | 137 | } |
133 | 138 | ||
139 | if (video.privacy !== VideoPrivacy.PRIVATE && req.body.privacy === VideoPrivacy.PRIVATE) { | ||
140 | return res.status(409) | ||
141 | .json({ error: 'Cannot set "private" a video that was not private anymore.' }) | ||
142 | .end() | ||
143 | } | ||
144 | |||
134 | next() | 145 | next() |
135 | }) | 146 | }) |
136 | }) | 147 | }) |
diff --git a/server/models/video/video-interface.ts b/server/models/video/video-interface.ts index 587652f45..cfe65f9aa 100644 --- a/server/models/video/video-interface.ts +++ b/server/models/video/video-interface.ts | |||
@@ -49,6 +49,7 @@ export namespace VideoMethods { | |||
49 | export type ListOwnedByAuthor = (author: string) => Promise<VideoInstance[]> | 49 | export type ListOwnedByAuthor = (author: string) => Promise<VideoInstance[]> |
50 | 50 | ||
51 | export type ListForApi = (start: number, count: number, sort: string) => Promise< ResultList<VideoInstance> > | 51 | export type ListForApi = (start: number, count: number, sort: string) => Promise< ResultList<VideoInstance> > |
52 | export type ListUserVideosForApi = (userId: number, start: number, count: number, sort: string) => Promise< ResultList<VideoInstance> > | ||
52 | export type SearchAndPopulateAuthorAndPodAndTags = ( | 53 | export type SearchAndPopulateAuthorAndPodAndTags = ( |
53 | value: string, | 54 | value: string, |
54 | field: string, | 55 | field: string, |
@@ -75,6 +76,7 @@ export interface VideoClass { | |||
75 | generateThumbnailFromData: VideoMethods.GenerateThumbnailFromData | 76 | generateThumbnailFromData: VideoMethods.GenerateThumbnailFromData |
76 | list: VideoMethods.List | 77 | list: VideoMethods.List |
77 | listForApi: VideoMethods.ListForApi | 78 | listForApi: VideoMethods.ListForApi |
79 | listUserVideosForApi: VideoMethods.ListUserVideosForApi | ||
78 | listOwnedAndPopulateAuthorAndTags: VideoMethods.ListOwnedAndPopulateAuthorAndTags | 80 | listOwnedAndPopulateAuthorAndTags: VideoMethods.ListOwnedAndPopulateAuthorAndTags |
79 | listOwnedByAuthor: VideoMethods.ListOwnedByAuthor | 81 | listOwnedByAuthor: VideoMethods.ListOwnedByAuthor |
80 | load: VideoMethods.Load | 82 | load: VideoMethods.Load |
@@ -97,6 +99,7 @@ export interface VideoAttributes { | |||
97 | nsfw: boolean | 99 | nsfw: boolean |
98 | description: string | 100 | description: string |
99 | duration: number | 101 | duration: number |
102 | privacy: number | ||
100 | views?: number | 103 | views?: number |
101 | likes?: number | 104 | likes?: number |
102 | dislikes?: number | 105 | dislikes?: number |
diff --git a/server/models/video/video.ts b/server/models/video/video.ts index 1877c506a..2c1bd6b6e 100644 --- a/server/models/video/video.ts +++ b/server/models/video/video.ts | |||
@@ -18,6 +18,7 @@ import { | |||
18 | isVideoNSFWValid, | 18 | isVideoNSFWValid, |
19 | isVideoDescriptionValid, | 19 | isVideoDescriptionValid, |
20 | isVideoDurationValid, | 20 | isVideoDurationValid, |
21 | isVideoPrivacyValid, | ||
21 | readFileBufferPromise, | 22 | readFileBufferPromise, |
22 | unlinkPromise, | 23 | unlinkPromise, |
23 | renamePromise, | 24 | renamePromise, |
@@ -38,10 +39,11 @@ import { | |||
38 | THUMBNAILS_SIZE, | 39 | THUMBNAILS_SIZE, |
39 | PREVIEWS_SIZE, | 40 | PREVIEWS_SIZE, |
40 | CONSTRAINTS_FIELDS, | 41 | CONSTRAINTS_FIELDS, |
41 | API_VERSION | 42 | API_VERSION, |
43 | VIDEO_PRIVACIES | ||
42 | } from '../../initializers' | 44 | } from '../../initializers' |
43 | import { removeVideoToFriends } from '../../lib' | 45 | import { removeVideoToFriends } from '../../lib' |
44 | import { VideoResolution } from '../../../shared' | 46 | import { VideoResolution, VideoPrivacy } from '../../../shared' |
45 | import { VideoFileInstance, VideoFileModel } from './video-file-interface' | 47 | import { VideoFileInstance, VideoFileModel } from './video-file-interface' |
46 | 48 | ||
47 | import { addMethodsToModel, getSort } from '../utils' | 49 | import { addMethodsToModel, getSort } from '../utils' |
@@ -79,6 +81,7 @@ let getTruncatedDescription: VideoMethods.GetTruncatedDescription | |||
79 | let generateThumbnailFromData: VideoMethods.GenerateThumbnailFromData | 81 | let generateThumbnailFromData: VideoMethods.GenerateThumbnailFromData |
80 | let list: VideoMethods.List | 82 | let list: VideoMethods.List |
81 | let listForApi: VideoMethods.ListForApi | 83 | let listForApi: VideoMethods.ListForApi |
84 | let listUserVideosForApi: VideoMethods.ListUserVideosForApi | ||
82 | let loadByHostAndUUID: VideoMethods.LoadByHostAndUUID | 85 | let loadByHostAndUUID: VideoMethods.LoadByHostAndUUID |
83 | let listOwnedAndPopulateAuthorAndTags: VideoMethods.ListOwnedAndPopulateAuthorAndTags | 86 | let listOwnedAndPopulateAuthorAndTags: VideoMethods.ListOwnedAndPopulateAuthorAndTags |
84 | let listOwnedByAuthor: VideoMethods.ListOwnedByAuthor | 87 | let listOwnedByAuthor: VideoMethods.ListOwnedByAuthor |
@@ -146,6 +149,16 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da | |||
146 | } | 149 | } |
147 | } | 150 | } |
148 | }, | 151 | }, |
152 | privacy: { | ||
153 | type: DataTypes.INTEGER, | ||
154 | allowNull: false, | ||
155 | validate: { | ||
156 | privacyValid: value => { | ||
157 | const res = isVideoPrivacyValid(value) | ||
158 | if (res === false) throw new Error('Video privacy is not valid.') | ||
159 | } | ||
160 | } | ||
161 | }, | ||
149 | nsfw: { | 162 | nsfw: { |
150 | type: DataTypes.BOOLEAN, | 163 | type: DataTypes.BOOLEAN, |
151 | allowNull: false, | 164 | allowNull: false, |
@@ -245,6 +258,7 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da | |||
245 | generateThumbnailFromData, | 258 | generateThumbnailFromData, |
246 | list, | 259 | list, |
247 | listForApi, | 260 | listForApi, |
261 | listUserVideosForApi, | ||
248 | listOwnedAndPopulateAuthorAndTags, | 262 | listOwnedAndPopulateAuthorAndTags, |
249 | listOwnedByAuthor, | 263 | listOwnedByAuthor, |
250 | load, | 264 | load, |
@@ -501,7 +515,13 @@ toFormattedJSON = function (this: VideoInstance) { | |||
501 | toFormattedDetailsJSON = function (this: VideoInstance) { | 515 | toFormattedDetailsJSON = function (this: VideoInstance) { |
502 | const formattedJson = this.toFormattedJSON() | 516 | const formattedJson = this.toFormattedJSON() |
503 | 517 | ||
518 | // Maybe our pod is not up to date and there are new privacy settings since our version | ||
519 | let privacyLabel = VIDEO_PRIVACIES[this.privacy] | ||
520 | if (!privacyLabel) privacyLabel = 'Unknown' | ||
521 | |||
504 | const detailsJson = { | 522 | const detailsJson = { |
523 | privacyLabel, | ||
524 | privacy: this.privacy, | ||
505 | descriptionPath: this.getDescriptionPath(), | 525 | descriptionPath: this.getDescriptionPath(), |
506 | channel: this.VideoChannel.toFormattedJSON(), | 526 | channel: this.VideoChannel.toFormattedJSON(), |
507 | files: [] | 527 | files: [] |
@@ -555,6 +575,7 @@ toAddRemoteJSON = function (this: VideoInstance) { | |||
555 | views: this.views, | 575 | views: this.views, |
556 | likes: this.likes, | 576 | likes: this.likes, |
557 | dislikes: this.dislikes, | 577 | dislikes: this.dislikes, |
578 | privacy: this.privacy, | ||
558 | files: [] | 579 | files: [] |
559 | } | 580 | } |
560 | 581 | ||
@@ -587,6 +608,7 @@ toUpdateRemoteJSON = function (this: VideoInstance) { | |||
587 | views: this.views, | 608 | views: this.views, |
588 | likes: this.likes, | 609 | likes: this.likes, |
589 | dislikes: this.dislikes, | 610 | dislikes: this.dislikes, |
611 | privacy: this.privacy, | ||
590 | files: [] | 612 | files: [] |
591 | } | 613 | } |
592 | 614 | ||
@@ -746,8 +768,39 @@ list = function () { | |||
746 | return Video.findAll(query) | 768 | return Video.findAll(query) |
747 | } | 769 | } |
748 | 770 | ||
771 | listUserVideosForApi = function (userId: number, start: number, count: number, sort: string) { | ||
772 | const query = { | ||
773 | distinct: true, | ||
774 | offset: start, | ||
775 | limit: count, | ||
776 | order: [ getSort(sort), [ Video['sequelize'].models.Tag, 'name', 'ASC' ] ], | ||
777 | include: [ | ||
778 | { | ||
779 | model: Video['sequelize'].models.VideoChannel, | ||
780 | required: true, | ||
781 | include: [ | ||
782 | { | ||
783 | model: Video['sequelize'].models.Author, | ||
784 | where: { | ||
785 | userId | ||
786 | }, | ||
787 | required: true | ||
788 | } | ||
789 | ] | ||
790 | }, | ||
791 | Video['sequelize'].models.Tag | ||
792 | ] | ||
793 | } | ||
794 | |||
795 | return Video.findAndCountAll(query).then(({ rows, count }) => { | ||
796 | return { | ||
797 | data: rows, | ||
798 | total: count | ||
799 | } | ||
800 | }) | ||
801 | } | ||
802 | |||
749 | listForApi = function (start: number, count: number, sort: string) { | 803 | listForApi = function (start: number, count: number, sort: string) { |
750 | // Exclude blacklisted videos from the list | ||
751 | const query = { | 804 | const query = { |
752 | distinct: true, | 805 | distinct: true, |
753 | offset: start, | 806 | offset: start, |
@@ -768,8 +821,7 @@ listForApi = function (start: number, count: number, sort: string) { | |||
768 | } | 821 | } |
769 | ] | 822 | ] |
770 | }, | 823 | }, |
771 | Video['sequelize'].models.Tag, | 824 | Video['sequelize'].models.Tag |
772 | Video['sequelize'].models.VideoFile | ||
773 | ], | 825 | ], |
774 | where: createBaseVideosWhere() | 826 | where: createBaseVideosWhere() |
775 | } | 827 | } |
@@ -969,10 +1021,6 @@ searchAndPopulateAuthorAndPodAndTags = function (value: string, field: string, s | |||
969 | model: Video['sequelize'].models.Tag | 1021 | model: Video['sequelize'].models.Tag |
970 | } | 1022 | } |
971 | 1023 | ||
972 | const videoFileInclude: Sequelize.IncludeOptions = { | ||
973 | model: Video['sequelize'].models.VideoFile | ||
974 | } | ||
975 | |||
976 | const query: Sequelize.FindOptions<VideoAttributes> = { | 1024 | const query: Sequelize.FindOptions<VideoAttributes> = { |
977 | distinct: true, | 1025 | distinct: true, |
978 | where: createBaseVideosWhere(), | 1026 | where: createBaseVideosWhere(), |
@@ -981,12 +1029,7 @@ searchAndPopulateAuthorAndPodAndTags = function (value: string, field: string, s | |||
981 | order: [ getSort(sort), [ Video['sequelize'].models.Tag, 'name', 'ASC' ] ] | 1029 | order: [ getSort(sort), [ Video['sequelize'].models.Tag, 'name', 'ASC' ] ] |
982 | } | 1030 | } |
983 | 1031 | ||
984 | // Make an exact search with the magnet | 1032 | if (field === 'tags') { |
985 | if (field === 'magnetUri') { | ||
986 | videoFileInclude.where = { | ||
987 | infoHash: magnetUtil.decode(value).infoHash | ||
988 | } | ||
989 | } else if (field === 'tags') { | ||
990 | const escapedValue = Video['sequelize'].escape('%' + value + '%') | 1033 | const escapedValue = Video['sequelize'].escape('%' + value + '%') |
991 | query.where['id'][Sequelize.Op.in] = Video['sequelize'].literal( | 1034 | query.where['id'][Sequelize.Op.in] = Video['sequelize'].literal( |
992 | `(SELECT "VideoTags"."videoId" | 1035 | `(SELECT "VideoTags"."videoId" |
@@ -1016,7 +1059,7 @@ searchAndPopulateAuthorAndPodAndTags = function (value: string, field: string, s | |||
1016 | } | 1059 | } |
1017 | 1060 | ||
1018 | query.include = [ | 1061 | query.include = [ |
1019 | videoChannelInclude, tagInclude, videoFileInclude | 1062 | videoChannelInclude, tagInclude |
1020 | ] | 1063 | ] |
1021 | 1064 | ||
1022 | return Video.findAndCountAll(query).then(({ rows, count }) => { | 1065 | return Video.findAndCountAll(query).then(({ rows, count }) => { |
@@ -1035,7 +1078,8 @@ function createBaseVideosWhere () { | |||
1035 | [Sequelize.Op.notIn]: Video['sequelize'].literal( | 1078 | [Sequelize.Op.notIn]: Video['sequelize'].literal( |
1036 | '(SELECT "BlacklistedVideos"."videoId" FROM "BlacklistedVideos")' | 1079 | '(SELECT "BlacklistedVideos"."videoId" FROM "BlacklistedVideos")' |
1037 | ) | 1080 | ) |
1038 | } | 1081 | }, |
1082 | privacy: VideoPrivacy.PUBLIC | ||
1039 | } | 1083 | } |
1040 | } | 1084 | } |
1041 | 1085 | ||
diff --git a/shared/models/pods/remote-video/remote-video-create-request.model.ts b/shared/models/pods/remote-video/remote-video-create-request.model.ts index cb20dfa03..9a382e654 100644 --- a/shared/models/pods/remote-video/remote-video-create-request.model.ts +++ b/shared/models/pods/remote-video/remote-video-create-request.model.ts | |||
@@ -16,6 +16,7 @@ export interface RemoteVideoCreateData { | |||
16 | views: number | 16 | views: number |
17 | likes: number | 17 | likes: number |
18 | dislikes: number | 18 | dislikes: number |
19 | privacy: number | ||
19 | thumbnailData: string | 20 | thumbnailData: string |
20 | files: { | 21 | files: { |
21 | infoHash: string | 22 | infoHash: string |
diff --git a/shared/models/pods/remote-video/remote-video-update-request.model.ts b/shared/models/pods/remote-video/remote-video-update-request.model.ts index 8439cfa24..924489c75 100644 --- a/shared/models/pods/remote-video/remote-video-update-request.model.ts +++ b/shared/models/pods/remote-video/remote-video-update-request.model.ts | |||
@@ -15,6 +15,7 @@ export interface RemoteVideoUpdateData { | |||
15 | views: number | 15 | views: number |
16 | likes: number | 16 | likes: number |
17 | dislikes: number | 17 | dislikes: number |
18 | privacy: number | ||
18 | files: { | 19 | files: { |
19 | infoHash: string | 20 | infoHash: string |
20 | extname: string | 21 | extname: string |
diff --git a/shared/models/videos/index.ts b/shared/models/videos/index.ts index 2a3912f06..14a10f5d8 100644 --- a/shared/models/videos/index.ts +++ b/shared/models/videos/index.ts | |||
@@ -8,6 +8,7 @@ export * from './video-channel-create.model' | |||
8 | export * from './video-channel-update.model' | 8 | export * from './video-channel-update.model' |
9 | export * from './video-channel.model' | 9 | export * from './video-channel.model' |
10 | export * from './video-create.model' | 10 | export * from './video-create.model' |
11 | export * from './video-privacy.enum' | ||
11 | export * from './video-rate.type' | 12 | export * from './video-rate.type' |
12 | export * from './video-resolution.enum' | 13 | export * from './video-resolution.enum' |
13 | export * from './video-update.model' | 14 | export * from './video-update.model' |
diff --git a/shared/models/videos/video-create.model.ts b/shared/models/videos/video-create.model.ts index 4d0e83520..e537c38a8 100644 --- a/shared/models/videos/video-create.model.ts +++ b/shared/models/videos/video-create.model.ts | |||
@@ -1,3 +1,5 @@ | |||
1 | import { VideoPrivacy } from './video-privacy.enum' | ||
2 | |||
1 | export interface VideoCreate { | 3 | export interface VideoCreate { |
2 | category: number | 4 | category: number |
3 | licence: number | 5 | licence: number |
@@ -7,4 +9,5 @@ export interface VideoCreate { | |||
7 | nsfw: boolean | 9 | nsfw: boolean |
8 | name: string | 10 | name: string |
9 | tags: string[] | 11 | tags: string[] |
12 | privacy: VideoPrivacy | ||
10 | } | 13 | } |
diff --git a/shared/models/videos/video-privacy.enum.ts b/shared/models/videos/video-privacy.enum.ts new file mode 100644 index 000000000..29888c7b8 --- /dev/null +++ b/shared/models/videos/video-privacy.enum.ts | |||
@@ -0,0 +1,5 @@ | |||
1 | export enum VideoPrivacy { | ||
2 | PUBLIC = 1, | ||
3 | UNLISTED = 2, | ||
4 | PRIVATE = 3 | ||
5 | } | ||
diff --git a/shared/models/videos/video-update.model.ts b/shared/models/videos/video-update.model.ts index 29a82621b..0cf38fe6e 100644 --- a/shared/models/videos/video-update.model.ts +++ b/shared/models/videos/video-update.model.ts | |||
@@ -1,9 +1,12 @@ | |||
1 | import { VideoPrivacy } from './video-privacy.enum' | ||
2 | |||
1 | export interface VideoUpdate { | 3 | export interface VideoUpdate { |
2 | name?: string | 4 | name?: string |
3 | category?: number | 5 | category?: number |
4 | licence?: number | 6 | licence?: number |
5 | language?: number | 7 | language?: number |
6 | description?: string | 8 | description?: string |
9 | privacy?: VideoPrivacy | ||
7 | tags?: string[] | 10 | tags?: string[] |
8 | nsfw?: boolean | 11 | nsfw?: boolean |
9 | } | 12 | } |
diff --git a/shared/models/videos/video.model.ts b/shared/models/videos/video.model.ts index 1490d345c..2f4ee2462 100644 --- a/shared/models/videos/video.model.ts +++ b/shared/models/videos/video.model.ts | |||
@@ -1,4 +1,5 @@ | |||
1 | import { VideoChannel } from './video-channel.model' | 1 | import { VideoChannel } from './video-channel.model' |
2 | import { VideoPrivacy } from './video-privacy.enum' | ||
2 | 3 | ||
3 | export interface VideoFile { | 4 | export interface VideoFile { |
4 | magnetUri: string | 5 | magnetUri: string |
@@ -37,7 +38,9 @@ export interface Video { | |||
37 | } | 38 | } |
38 | 39 | ||
39 | export interface VideoDetails extends Video { | 40 | export interface VideoDetails extends Video { |
40 | descriptionPath: string, | 41 | privacy: VideoPrivacy |
42 | privacyLabel: string | ||
43 | descriptionPath: string | ||
41 | channel: VideoChannel | 44 | channel: VideoChannel |
42 | files: VideoFile[] | 45 | files: VideoFile[] |
43 | } | 46 | } |