Add video privacy setting
This commit is contained in:
parent
b7a485121d
commit
fd45e8f43c
|
@ -1,4 +1,4 @@
|
|||
import { Component, OnInit, ViewContainerRef } from '@angular/core'
|
||||
import { Component, OnInit } from '@angular/core'
|
||||
import { Router } from '@angular/router'
|
||||
|
||||
import { AuthService, ServerService } from './core'
|
||||
|
@ -28,8 +28,7 @@ export class AppComponent implements OnInit {
|
|||
constructor (
|
||||
private router: Router,
|
||||
private authService: AuthService,
|
||||
private serverService: ServerService,
|
||||
private userService: UserService
|
||||
private serverService: ServerService
|
||||
) {}
|
||||
|
||||
ngOnInit () {
|
||||
|
@ -45,6 +44,7 @@ export class AppComponent implements OnInit {
|
|||
this.serverService.loadVideoCategories()
|
||||
this.serverService.loadVideoLanguages()
|
||||
this.serverService.loadVideoLicences()
|
||||
this.serverService.loadVideoPrivacies()
|
||||
|
||||
// Do not display menu on small screens
|
||||
if (window.innerWidth < 600) {
|
||||
|
|
|
@ -23,6 +23,11 @@
|
|||
<span class="hidden-xs glyphicon glyphicon-user"></span>
|
||||
My account
|
||||
</a>
|
||||
|
||||
<a *ngIf="isLoggedIn" routerLink="/videos/mine" routerLinkActive="active">
|
||||
<span class="hidden-xs glyphicon glyphicon-folder-open"></span>
|
||||
My videos
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="panel-block">
|
||||
|
|
|
@ -19,6 +19,7 @@ export class ServerService {
|
|||
private videoCategories: Array<{ id: number, label: string }> = []
|
||||
private videoLicences: Array<{ id: number, label: string }> = []
|
||||
private videoLanguages: Array<{ id: number, label: string }> = []
|
||||
private videoPrivacies: Array<{ id: number, label: string }> = []
|
||||
|
||||
constructor (private http: HttpClient) {}
|
||||
|
||||
|
@ -39,6 +40,10 @@ export class ServerService {
|
|||
return this.loadVideoAttributeEnum('languages', this.videoLanguages)
|
||||
}
|
||||
|
||||
loadVideoPrivacies () {
|
||||
return this.loadVideoAttributeEnum('privacies', this.videoPrivacies)
|
||||
}
|
||||
|
||||
getConfig () {
|
||||
return this.config
|
||||
}
|
||||
|
@ -55,7 +60,14 @@ export class ServerService {
|
|||
return this.videoLanguages
|
||||
}
|
||||
|
||||
private loadVideoAttributeEnum (attributeName: 'categories' | 'licences' | 'languages', hashToPopulate: { id: number, label: string }[]) {
|
||||
getVideoPrivacies () {
|
||||
return this.videoPrivacies
|
||||
}
|
||||
|
||||
private loadVideoAttributeEnum (
|
||||
attributeName: 'categories' | 'licences' | 'languages' | 'privacies',
|
||||
hashToPopulate: { id: number, label: string }[]
|
||||
) {
|
||||
return this.http.get(ServerService.BASE_VIDEO_URL + attributeName)
|
||||
.subscribe(data => {
|
||||
Object.keys(data)
|
||||
|
|
|
@ -9,6 +9,13 @@ export const VIDEO_NAME = {
|
|||
}
|
||||
}
|
||||
|
||||
export const VIDEO_PRIVACY = {
|
||||
VALIDATORS: [ Validators.required ],
|
||||
MESSAGES: {
|
||||
'required': 'Video privacy is required.'
|
||||
}
|
||||
}
|
||||
|
||||
export const VIDEO_CATEGORY = {
|
||||
VALIDATORS: [ Validators.required ],
|
||||
MESSAGES: {
|
||||
|
|
|
@ -1 +1 @@
|
|||
export type SearchField = 'name' | 'author' | 'host' | 'magnetUri' | 'tags'
|
||||
export type SearchField = 'name' | 'author' | 'host' | 'tags'
|
||||
|
|
|
@ -6,12 +6,12 @@
|
|||
|
||||
<input
|
||||
type="text" id="search-video" name="search-video" class="form-control" placeholder="Search" class="form-control"
|
||||
[(ngModel)]="searchCriterias.value" (keyup.enter)="doSearch()"
|
||||
[(ngModel)]="searchCriteria.value" (keyup.enter)="doSearch()"
|
||||
>
|
||||
|
||||
<div class="input-group-btn" dropdown placement="bottom right">
|
||||
<button id="simple-btn-keyboard-nav" type="button" class="btn btn-default" dropdownToggle>
|
||||
{{ getStringChoice(searchCriterias.field) }} <span class="caret"></span>
|
||||
{{ getStringChoice(searchCriteria.field) }} <span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu dropdown-menu-right" role="menu" aria-labelledby="simple-btn-keyboard-nav" *dropdownMenu>
|
||||
<li *ngFor="let choice of choiceKeys" class="dropdown-item" role="menu-item">
|
||||
|
|
|
@ -16,10 +16,9 @@ export class SearchComponent implements OnInit {
|
|||
name: 'Name',
|
||||
author: 'Author',
|
||||
host: 'Pod Host',
|
||||
magnetUri: 'Magnet URI',
|
||||
tags: 'Tags'
|
||||
}
|
||||
searchCriterias: Search = {
|
||||
searchCriteria: Search = {
|
||||
field: 'name',
|
||||
value: ''
|
||||
}
|
||||
|
@ -30,13 +29,13 @@ export class SearchComponent implements OnInit {
|
|||
// Subscribe if the search changed
|
||||
// Usually changed by videos list component
|
||||
this.searchService.updateSearch.subscribe(
|
||||
newSearchCriterias => {
|
||||
newSearchCriteria => {
|
||||
// Put a field by default
|
||||
if (!newSearchCriterias.field) {
|
||||
newSearchCriterias.field = 'name'
|
||||
if (!newSearchCriteria.field) {
|
||||
newSearchCriteria.field = 'name'
|
||||
}
|
||||
|
||||
this.searchCriterias = newSearchCriterias
|
||||
this.searchCriteria = newSearchCriteria
|
||||
}
|
||||
)
|
||||
}
|
||||
|
@ -49,9 +48,9 @@ export class SearchComponent implements OnInit {
|
|||
$event.preventDefault()
|
||||
$event.stopPropagation()
|
||||
|
||||
this.searchCriterias.field = choice
|
||||
this.searchCriteria.field = choice
|
||||
|
||||
if (this.searchCriterias.value) {
|
||||
if (this.searchCriteria.value) {
|
||||
this.doSearch()
|
||||
}
|
||||
}
|
||||
|
@ -61,7 +60,7 @@ export class SearchComponent implements OnInit {
|
|||
this.router.navigate([ '/videos/list' ])
|
||||
}
|
||||
|
||||
this.searchService.searchUpdated.next(this.searchCriterias)
|
||||
this.searchService.searchUpdated.next(this.searchCriteria)
|
||||
}
|
||||
|
||||
getStringChoice (choiceKey: SearchField) {
|
||||
|
|
|
@ -17,6 +17,18 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="privacy">Privacy</label>
|
||||
<select class="form-control" id="privacy" formControlName="privacy">
|
||||
<option></option>
|
||||
<option *ngFor="let privacy of videoPrivacies" [value]="privacy.id">{{ privacy.label }}</option>
|
||||
</select>
|
||||
|
||||
<div *ngIf="formErrors.privacy" class="alert alert-danger">
|
||||
{{ formErrors.privacy }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<input
|
||||
type="checkbox" id="nsfw"
|
||||
|
|
|
@ -13,7 +13,8 @@ import {
|
|||
VIDEO_DESCRIPTION,
|
||||
VIDEO_TAGS,
|
||||
VIDEO_CHANNEL,
|
||||
VIDEO_FILE
|
||||
VIDEO_FILE,
|
||||
VIDEO_PRIVACY
|
||||
} from '../../shared'
|
||||
import { AuthService, ServerService } from '../../core'
|
||||
import { VideoService } from '../shared'
|
||||
|
@ -34,6 +35,7 @@ export class VideoAddComponent extends FormReactive implements OnInit {
|
|||
videoCategories = []
|
||||
videoLicences = []
|
||||
videoLanguages = []
|
||||
videoPrivacies = []
|
||||
userVideoChannels = []
|
||||
|
||||
tagValidators = VIDEO_TAGS.VALIDATORS
|
||||
|
@ -43,6 +45,7 @@ export class VideoAddComponent extends FormReactive implements OnInit {
|
|||
form: FormGroup
|
||||
formErrors = {
|
||||
name: '',
|
||||
privacy: '',
|
||||
category: '',
|
||||
licence: '',
|
||||
language: '',
|
||||
|
@ -52,6 +55,7 @@ export class VideoAddComponent extends FormReactive implements OnInit {
|
|||
}
|
||||
validationMessages = {
|
||||
name: VIDEO_NAME.MESSAGES,
|
||||
privacy: VIDEO_PRIVACY.MESSAGES,
|
||||
category: VIDEO_CATEGORY.MESSAGES,
|
||||
licence: VIDEO_LICENCE.MESSAGES,
|
||||
language: VIDEO_LANGUAGE.MESSAGES,
|
||||
|
@ -79,6 +83,7 @@ export class VideoAddComponent extends FormReactive implements OnInit {
|
|||
this.form = this.formBuilder.group({
|
||||
name: [ '', VIDEO_NAME.VALIDATORS ],
|
||||
nsfw: [ false ],
|
||||
privacy: [ '', VIDEO_PRIVACY.VALIDATORS ],
|
||||
category: [ '', VIDEO_CATEGORY.VALIDATORS ],
|
||||
licence: [ '', VIDEO_LICENCE.VALIDATORS ],
|
||||
language: [ '', VIDEO_LANGUAGE.VALIDATORS ],
|
||||
|
@ -95,6 +100,7 @@ export class VideoAddComponent extends FormReactive implements OnInit {
|
|||
this.videoCategories = this.serverService.getVideoCategories()
|
||||
this.videoLicences = this.serverService.getVideoLicences()
|
||||
this.videoLanguages = this.serverService.getVideoLanguages()
|
||||
this.videoPrivacies = this.serverService.getVideoPrivacies()
|
||||
|
||||
this.buildForm()
|
||||
|
||||
|
@ -139,6 +145,7 @@ export class VideoAddComponent extends FormReactive implements OnInit {
|
|||
const formValue: VideoCreate = this.form.value
|
||||
|
||||
const name = formValue.name
|
||||
const privacy = formValue.privacy
|
||||
const nsfw = formValue.nsfw
|
||||
const category = formValue.category
|
||||
const licence = formValue.licence
|
||||
|
@ -150,6 +157,7 @@ export class VideoAddComponent extends FormReactive implements OnInit {
|
|||
|
||||
const formData = new FormData()
|
||||
formData.append('name', name)
|
||||
formData.append('privacy', privacy.toString())
|
||||
formData.append('category', '' + category)
|
||||
formData.append('nsfw', '' + nsfw)
|
||||
formData.append('licence', '' + licence)
|
||||
|
|
|
@ -17,6 +17,18 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="privacy">Privacy</label>
|
||||
<select class="form-control" id="privacy" formControlName="privacy">
|
||||
<option></option>
|
||||
<option *ngFor="let privacy of videoPrivacies" [value]="privacy.id">{{ privacy.label }}</option>
|
||||
</select>
|
||||
|
||||
<div *ngIf="formErrors.privacy" class="alert alert-danger">
|
||||
{{ formErrors.privacy }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<input
|
||||
type="checkbox" id="nsfw"
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import { Component, OnInit } from '@angular/core'
|
||||
import { FormBuilder, FormGroup } from '@angular/forms'
|
||||
import { ActivatedRoute, Router } from '@angular/router'
|
||||
import { Observable } from 'rxjs/Observable'
|
||||
import 'rxjs/add/observable/forkJoin'
|
||||
|
||||
import { NotificationsService } from 'angular2-notifications'
|
||||
|
@ -14,9 +13,11 @@ import {
|
|||
VIDEO_LICENCE,
|
||||
VIDEO_LANGUAGE,
|
||||
VIDEO_DESCRIPTION,
|
||||
VIDEO_TAGS
|
||||
VIDEO_TAGS,
|
||||
VIDEO_PRIVACY
|
||||
} from '../../shared'
|
||||
import { VideoEdit, VideoService } from '../shared'
|
||||
import { VideoPrivacy } from '../../../../../shared/models/videos/video-privacy.enum'
|
||||
|
||||
@Component({
|
||||
selector: 'my-videos-update',
|
||||
|
@ -29,6 +30,7 @@ export class VideoUpdateComponent extends FormReactive implements OnInit {
|
|||
videoCategories = []
|
||||
videoLicences = []
|
||||
videoLanguages = []
|
||||
videoPrivacies = []
|
||||
video: VideoEdit
|
||||
|
||||
tagValidators = VIDEO_TAGS.VALIDATORS
|
||||
|
@ -38,6 +40,7 @@ export class VideoUpdateComponent extends FormReactive implements OnInit {
|
|||
form: FormGroup
|
||||
formErrors = {
|
||||
name: '',
|
||||
privacy: '',
|
||||
category: '',
|
||||
licence: '',
|
||||
language: '',
|
||||
|
@ -45,6 +48,7 @@ export class VideoUpdateComponent extends FormReactive implements OnInit {
|
|||
}
|
||||
validationMessages = {
|
||||
name: VIDEO_NAME.MESSAGES,
|
||||
privacy: VIDEO_PRIVACY.MESSAGES,
|
||||
category: VIDEO_CATEGORY.MESSAGES,
|
||||
licence: VIDEO_LICENCE.MESSAGES,
|
||||
language: VIDEO_LANGUAGE.MESSAGES,
|
||||
|
@ -67,6 +71,7 @@ export class VideoUpdateComponent extends FormReactive implements OnInit {
|
|||
buildForm () {
|
||||
this.form = this.formBuilder.group({
|
||||
name: [ '', VIDEO_NAME.VALIDATORS ],
|
||||
privacy: [ '', VIDEO_PRIVACY.VALIDATORS ],
|
||||
nsfw: [ false ],
|
||||
category: [ '', VIDEO_CATEGORY.VALIDATORS ],
|
||||
licence: [ '', VIDEO_LICENCE.VALIDATORS ],
|
||||
|
@ -84,6 +89,7 @@ export class VideoUpdateComponent extends FormReactive implements OnInit {
|
|||
this.videoCategories = this.serverService.getVideoCategories()
|
||||
this.videoLicences = this.serverService.getVideoLicences()
|
||||
this.videoLanguages = this.serverService.getVideoLanguages()
|
||||
this.videoPrivacies = this.serverService.getVideoPrivacies()
|
||||
|
||||
const uuid: string = this.route.snapshot.params['uuid']
|
||||
|
||||
|
@ -98,6 +104,16 @@ export class VideoUpdateComponent extends FormReactive implements OnInit {
|
|||
video => {
|
||||
this.video = new VideoEdit(video)
|
||||
|
||||
// We cannot set private a video that was not private anymore
|
||||
if (video.privacy !== VideoPrivacy.PRIVATE) {
|
||||
const newVideoPrivacies = []
|
||||
for (const p of this.videoPrivacies) {
|
||||
if (p.id !== VideoPrivacy.PRIVATE) newVideoPrivacies.push(p)
|
||||
}
|
||||
|
||||
this.videoPrivacies = newVideoPrivacies
|
||||
}
|
||||
|
||||
this.hydrateFormFromVideo()
|
||||
},
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
<div *ngIf="videoNotFound" id="video-not-found">Video not found :'(</div>
|
||||
</div>
|
||||
|
||||
<!-- P2P informations -->
|
||||
<!-- P2P information -->
|
||||
<div id="torrent-info" class="row">
|
||||
<div id="torrent-info-download" class="col-md-4 col-sm-4 col-xs-4">Download: {{ downloadSpeed | bytes }}/s</div>
|
||||
<div id="torrent-info-upload" class="col-md-4 col-sm-4 col-xs-4">Upload: {{ uploadSpeed | bytes }}/s</div>
|
||||
|
@ -142,6 +142,15 @@
|
|||
</div>
|
||||
|
||||
<div class="video-details-attributes col-xs-4 col-md-3">
|
||||
<div class="video-details-attribute">
|
||||
<span class="video-details-attribute-label">
|
||||
Privacy:
|
||||
</span>
|
||||
<span class="video-details-attribute-value">
|
||||
{{ video.privacyLabel }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="video-details-attribute">
|
||||
<span class="video-details-attribute-label">
|
||||
Category:
|
||||
|
|
|
@ -5,7 +5,8 @@ import {
|
|||
VideoFile,
|
||||
VideoChannel,
|
||||
VideoResolution,
|
||||
UserRight
|
||||
UserRight,
|
||||
VideoPrivacy
|
||||
} from '../../../../../shared'
|
||||
|
||||
export class VideoDetails extends Video implements VideoDetailsServerModel {
|
||||
|
@ -41,10 +42,14 @@ export class VideoDetails extends Video implements VideoDetailsServerModel {
|
|||
descriptionPath: string
|
||||
files: VideoFile[]
|
||||
channel: VideoChannel
|
||||
privacy: VideoPrivacy
|
||||
privacyLabel: string
|
||||
|
||||
constructor (hash: VideoDetailsServerModel) {
|
||||
super(hash)
|
||||
|
||||
this.privacy = hash.privacy
|
||||
this.privacyLabel = hash.privacyLabel
|
||||
this.descriptionPath = hash.descriptionPath
|
||||
this.files = hash.files
|
||||
this.channel = hash.channel
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { VideoDetails } from './video-details.model'
|
||||
import { VideoPrivacy } from '../../../../../shared/models/videos/video-privacy.enum'
|
||||
|
||||
export class VideoEdit {
|
||||
category: number
|
||||
|
@ -9,6 +10,7 @@ export class VideoEdit {
|
|||
tags: string[]
|
||||
nsfw: boolean
|
||||
channel: number
|
||||
privacy: VideoPrivacy
|
||||
uuid?: string
|
||||
id?: number
|
||||
|
||||
|
@ -23,6 +25,7 @@ export class VideoEdit {
|
|||
this.tags = videoDetails.tags
|
||||
this.nsfw = videoDetails.nsfw
|
||||
this.channel = videoDetails.channel.id
|
||||
this.privacy = videoDetails.privacy
|
||||
}
|
||||
|
||||
patch (values: Object) {
|
||||
|
@ -40,7 +43,8 @@ export class VideoEdit {
|
|||
name: this.name,
|
||||
tags: this.tags,
|
||||
nsfw: this.nsfw,
|
||||
channel: this.channel
|
||||
channel: this.channel,
|
||||
privacy: this.privacy
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,7 +19,6 @@ import {
|
|||
UserVideoRate,
|
||||
VideoRateType,
|
||||
VideoUpdate,
|
||||
VideoAbuseCreate,
|
||||
UserVideoRateUpdate,
|
||||
Video as VideoServerModel,
|
||||
VideoDetails as VideoDetailsServerModel,
|
||||
|
@ -51,6 +50,7 @@ export class VideoService {
|
|||
licence: video.licence,
|
||||
language,
|
||||
description: video.description,
|
||||
privacy: video.privacy,
|
||||
tags: video.tags,
|
||||
nsfw: video.nsfw
|
||||
}
|
||||
|
@ -63,22 +63,35 @@ export class VideoService {
|
|||
uploadVideo (video: FormData) {
|
||||
const req = new HttpRequest('POST', VideoService.BASE_VIDEO_URL + 'upload', video, { reportProgress: true })
|
||||
|
||||
return this.authHttp.request(req)
|
||||
.catch(this.restExtractor.handleError)
|
||||
return this.authHttp
|
||||
.request(req)
|
||||
.catch(this.restExtractor.handleError)
|
||||
}
|
||||
|
||||
getVideos (videoPagination: VideoPagination, sort: SortField) {
|
||||
getMyVideos (videoPagination: VideoPagination, sort: SortField): Observable<{ videos: Video[], totalVideos: number}> {
|
||||
const pagination = this.videoPaginationToRestPagination(videoPagination)
|
||||
|
||||
let params = new HttpParams()
|
||||
params = this.restService.addRestGetParams(params, pagination, sort)
|
||||
|
||||
return this.authHttp.get(VideoService.BASE_VIDEO_URL, { params })
|
||||
.map(this.extractVideos)
|
||||
.catch((res) => this.restExtractor.handleError(res))
|
||||
return this.authHttp.get(UserService.BASE_USERS_URL + '/me/videos', { params })
|
||||
.map(this.extractVideos)
|
||||
.catch((res) => this.restExtractor.handleError(res))
|
||||
}
|
||||
|
||||
searchVideos (search: Search, videoPagination: VideoPagination, sort: SortField) {
|
||||
getVideos (videoPagination: VideoPagination, sort: SortField): Observable<{ videos: Video[], totalVideos: number}> {
|
||||
const pagination = this.videoPaginationToRestPagination(videoPagination)
|
||||
|
||||
let params = new HttpParams()
|
||||
params = this.restService.addRestGetParams(params, pagination, sort)
|
||||
|
||||
return this.authHttp
|
||||
.get(VideoService.BASE_VIDEO_URL, { params })
|
||||
.map(this.extractVideos)
|
||||
.catch((res) => this.restExtractor.handleError(res))
|
||||
}
|
||||
|
||||
searchVideos (search: Search, videoPagination: VideoPagination, sort: SortField): Observable<{ videos: Video[], totalVideos: number}> {
|
||||
const url = VideoService.BASE_VIDEO_URL + 'search/' + encodeURIComponent(search.value)
|
||||
|
||||
const pagination = this.videoPaginationToRestPagination(videoPagination)
|
||||
|
@ -88,15 +101,17 @@ export class VideoService {
|
|||
|
||||
if (search.field) params.set('field', search.field)
|
||||
|
||||
return this.authHttp.get<ResultList<VideoServerModel>>(url, { params })
|
||||
.map(this.extractVideos)
|
||||
.catch((res) => this.restExtractor.handleError(res))
|
||||
return this.authHttp
|
||||
.get<ResultList<VideoServerModel>>(url, { params })
|
||||
.map(this.extractVideos)
|
||||
.catch((res) => this.restExtractor.handleError(res))
|
||||
}
|
||||
|
||||
removeVideo (id: number) {
|
||||
return this.authHttp.delete(VideoService.BASE_VIDEO_URL + id)
|
||||
.map(this.restExtractor.extractDataBool)
|
||||
.catch((res) => this.restExtractor.handleError(res))
|
||||
return this.authHttp
|
||||
.delete(VideoService.BASE_VIDEO_URL + id)
|
||||
.map(this.restExtractor.extractDataBool)
|
||||
.catch((res) => this.restExtractor.handleError(res))
|
||||
}
|
||||
|
||||
loadCompleteDescription (descriptionPath: string) {
|
||||
|
@ -117,8 +132,9 @@ export class VideoService {
|
|||
getUserVideoRating (id: number): Observable<UserVideoRate> {
|
||||
const url = UserService.BASE_USERS_URL + 'me/videos/' + id + '/rating'
|
||||
|
||||
return this.authHttp.get(url)
|
||||
.catch(res => this.restExtractor.handleError(res))
|
||||
return this.authHttp
|
||||
.get(url)
|
||||
.catch(res => this.restExtractor.handleError(res))
|
||||
}
|
||||
|
||||
private videoPaginationToRestPagination (videoPagination: VideoPagination) {
|
||||
|
@ -134,9 +150,10 @@ export class VideoService {
|
|||
rating: rateType
|
||||
}
|
||||
|
||||
return this.authHttp.put(url, body)
|
||||
.map(this.restExtractor.extractDataBool)
|
||||
.catch(res => this.restExtractor.handleError(res))
|
||||
return this.authHttp
|
||||
.put(url, body)
|
||||
.map(this.restExtractor.extractDataBool)
|
||||
.catch(res => this.restExtractor.handleError(res))
|
||||
}
|
||||
|
||||
private extractVideos (result: ResultList<VideoServerModel>) {
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
export * from './loader.component'
|
||||
export * from './my-videos.component'
|
||||
export * from './video-list.component'
|
||||
export * from './video-miniature.component'
|
||||
export * from './video-sort.component'
|
||||
export * from './shared'
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
import { Component, OnDestroy, OnInit } from '@angular/core'
|
||||
import { ActivatedRoute, Router } from '@angular/router'
|
||||
|
||||
import { NotificationsService } from 'angular2-notifications'
|
||||
|
||||
import { AbstractVideoList } from './shared'
|
||||
import { VideoService } from '../shared'
|
||||
|
||||
@Component({
|
||||
selector: 'my-videos',
|
||||
styleUrls: [ './shared/abstract-video-list.scss' ],
|
||||
templateUrl: './shared/abstract-video-list.html'
|
||||
})
|
||||
export class MyVideosComponent extends AbstractVideoList implements OnInit, OnDestroy {
|
||||
|
||||
constructor (
|
||||
protected router: Router,
|
||||
protected route: ActivatedRoute,
|
||||
protected notificationsService: NotificationsService,
|
||||
private videoService: VideoService
|
||||
) {
|
||||
super()
|
||||
}
|
||||
|
||||
ngOnInit () {
|
||||
super.ngOnInit()
|
||||
}
|
||||
|
||||
ngOnDestroy () {
|
||||
this.subActivatedRoute.unsubscribe()
|
||||
}
|
||||
|
||||
getVideosObservable () {
|
||||
return this.videoService.getMyVideos(this.pagination, this.sort)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,104 @@
|
|||
import { OnDestroy, OnInit } from '@angular/core'
|
||||
import { ActivatedRoute, Router } from '@angular/router'
|
||||
import { Subscription } from 'rxjs/Subscription'
|
||||
import { BehaviorSubject } from 'rxjs/BehaviorSubject'
|
||||
import { Observable } from 'rxjs/Observable'
|
||||
|
||||
import { NotificationsService } from 'angular2-notifications'
|
||||
|
||||
import {
|
||||
SortField,
|
||||
Video,
|
||||
VideoPagination
|
||||
} from '../../shared'
|
||||
|
||||
export abstract class AbstractVideoList implements OnInit, OnDestroy {
|
||||
loading: BehaviorSubject<boolean> = new BehaviorSubject(false)
|
||||
pagination: VideoPagination = {
|
||||
currentPage: 1,
|
||||
itemsPerPage: 25,
|
||||
totalItems: null
|
||||
}
|
||||
sort: SortField
|
||||
videos: Video[] = []
|
||||
|
||||
protected notificationsService: NotificationsService
|
||||
protected router: Router
|
||||
protected route: ActivatedRoute
|
||||
|
||||
protected subActivatedRoute: Subscription
|
||||
|
||||
abstract getVideosObservable (): Observable<{ videos: Video[], totalVideos: number}>
|
||||
|
||||
ngOnInit () {
|
||||
// Subscribe to route changes
|
||||
this.subActivatedRoute = this.route.params.subscribe(routeParams => {
|
||||
this.loadRouteParams(routeParams)
|
||||
|
||||
this.getVideos()
|
||||
})
|
||||
}
|
||||
|
||||
ngOnDestroy () {
|
||||
this.subActivatedRoute.unsubscribe()
|
||||
}
|
||||
|
||||
getVideos () {
|
||||
this.loading.next(true)
|
||||
this.videos = []
|
||||
|
||||
const observable = this.getVideosObservable()
|
||||
|
||||
observable.subscribe(
|
||||
({ videos, totalVideos }) => {
|
||||
this.videos = videos
|
||||
this.pagination.totalItems = totalVideos
|
||||
|
||||
this.loading.next(false)
|
||||
},
|
||||
error => this.notificationsService.error('Error', error.text)
|
||||
)
|
||||
}
|
||||
|
||||
isThereNoVideo () {
|
||||
return !this.loading.getValue() && this.videos.length === 0
|
||||
}
|
||||
|
||||
onPageChanged (event: { page: number }) {
|
||||
// Be sure the current page is set
|
||||
this.pagination.currentPage = event.page
|
||||
|
||||
this.navigateToNewParams()
|
||||
}
|
||||
|
||||
onSort (sort: SortField) {
|
||||
this.sort = sort
|
||||
|
||||
this.navigateToNewParams()
|
||||
}
|
||||
|
||||
protected buildRouteParams () {
|
||||
// There is always a sort and a current page
|
||||
const params = {
|
||||
sort: this.sort,
|
||||
page: this.pagination.currentPage
|
||||
}
|
||||
|
||||
return params
|
||||
}
|
||||
|
||||
protected loadRouteParams (routeParams: { [ key: string ]: any }) {
|
||||
this.sort = routeParams['sort'] as SortField || '-createdAt'
|
||||
|
||||
if (routeParams['page'] !== undefined) {
|
||||
this.pagination.currentPage = parseInt(routeParams['page'], 10)
|
||||
} else {
|
||||
this.pagination.currentPage = 1
|
||||
}
|
||||
}
|
||||
|
||||
protected navigateToNewParams () {
|
||||
const routeParams = this.buildRouteParams()
|
||||
this.router.navigate([ '/videos/list', routeParams ])
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
export * from './abstract-video-list'
|
||||
export * from './loader.component'
|
||||
export * from './video-miniature.component'
|
||||
export * from './video-sort.component'
|
|
@ -1,7 +1,7 @@
|
|||
import { Component, Input } from '@angular/core'
|
||||
|
||||
import { SortField, Video } from '../shared'
|
||||
import { User } from '../../shared'
|
||||
import { SortField, Video } from '../../shared'
|
||||
import { User } from '../../../shared'
|
||||
|
||||
@Component({
|
||||
selector: 'my-video-miniature',
|
|
@ -1,6 +1,6 @@
|
|||
import { Component, EventEmitter, Input, Output } from '@angular/core'
|
||||
|
||||
import { SortField } from '../shared'
|
||||
import { SortField } from '../../shared'
|
||||
|
||||
@Component({
|
||||
selector: 'my-video-sort',
|
|
@ -1,51 +1,33 @@
|
|||
import { Component, OnDestroy, OnInit } from '@angular/core'
|
||||
import { ActivatedRoute, Router } from '@angular/router'
|
||||
import { Subscription } from 'rxjs/Subscription'
|
||||
import { BehaviorSubject } from 'rxjs/BehaviorSubject'
|
||||
|
||||
import { NotificationsService } from 'angular2-notifications'
|
||||
|
||||
import { AuthService } from '../../core'
|
||||
import {
|
||||
SortField,
|
||||
Video,
|
||||
VideoService,
|
||||
VideoPagination
|
||||
} from '../shared'
|
||||
import { Search, SearchField, SearchService, User } from '../../shared'
|
||||
import { VideoService } from '../shared'
|
||||
import { Search, SearchField, SearchService } from '../../shared'
|
||||
import { AbstractVideoList } from './shared'
|
||||
|
||||
@Component({
|
||||
selector: 'my-videos-list',
|
||||
styleUrls: [ './video-list.component.scss' ],
|
||||
templateUrl: './video-list.component.html'
|
||||
styleUrls: [ './shared/abstract-video-list.scss' ],
|
||||
templateUrl: './shared/abstract-video-list.html'
|
||||
})
|
||||
export class VideoListComponent implements OnInit, OnDestroy {
|
||||
loading: BehaviorSubject<boolean> = new BehaviorSubject(false)
|
||||
pagination: VideoPagination = {
|
||||
currentPage: 1,
|
||||
itemsPerPage: 25,
|
||||
totalItems: null
|
||||
}
|
||||
sort: SortField
|
||||
user: User
|
||||
videos: Video[] = []
|
||||
|
||||
export class VideoListComponent extends AbstractVideoList implements OnInit, OnDestroy {
|
||||
private search: Search
|
||||
private subActivatedRoute: Subscription
|
||||
private subSearch: Subscription
|
||||
|
||||
constructor (
|
||||
private authService: AuthService,
|
||||
private notificationsService: NotificationsService,
|
||||
private router: Router,
|
||||
private route: ActivatedRoute,
|
||||
protected router: Router,
|
||||
protected route: ActivatedRoute,
|
||||
protected notificationsService: NotificationsService,
|
||||
private videoService: VideoService,
|
||||
private searchService: SearchService
|
||||
) {}
|
||||
) {
|
||||
super()
|
||||
}
|
||||
|
||||
ngOnInit () {
|
||||
this.user = this.authService.getUser()
|
||||
|
||||
// Subscribe to route changes
|
||||
this.subActivatedRoute = this.route.params.subscribe(routeParams => {
|
||||
this.loadRouteParams(routeParams)
|
||||
|
@ -66,14 +48,12 @@ export class VideoListComponent implements OnInit, OnDestroy {
|
|||
}
|
||||
|
||||
ngOnDestroy () {
|
||||
this.subActivatedRoute.unsubscribe()
|
||||
super.ngOnDestroy()
|
||||
|
||||
this.subSearch.unsubscribe()
|
||||
}
|
||||
|
||||
getVideos () {
|
||||
this.loading.next(true)
|
||||
this.videos = []
|
||||
|
||||
getVideosObservable () {
|
||||
let observable = null
|
||||
if (this.search.value) {
|
||||
observable = this.videoService.searchVideos(this.search, this.pagination, this.sort)
|
||||
|
@ -81,40 +61,11 @@ export class VideoListComponent implements OnInit, OnDestroy {
|
|||
observable = this.videoService.getVideos(this.pagination, this.sort)
|
||||
}
|
||||
|
||||
observable.subscribe(
|
||||
({ videos, totalVideos }) => {
|
||||
this.videos = videos
|
||||
this.pagination.totalItems = totalVideos
|
||||
|
||||
this.loading.next(false)
|
||||
},
|
||||
error => this.notificationsService.error('Error', error.text)
|
||||
)
|
||||
return observable
|
||||
}
|
||||
|
||||
isThereNoVideo () {
|
||||
return !this.loading.getValue() && this.videos.length === 0
|
||||
}
|
||||
|
||||
onPageChanged (event: { page: number }) {
|
||||
// Be sure the current page is set
|
||||
this.pagination.currentPage = event.page
|
||||
|
||||
this.navigateToNewParams()
|
||||
}
|
||||
|
||||
onSort (sort: SortField) {
|
||||
this.sort = sort
|
||||
|
||||
this.navigateToNewParams()
|
||||
}
|
||||
|
||||
private buildRouteParams () {
|
||||
// There is always a sort and a current page
|
||||
const params = {
|
||||
sort: this.sort,
|
||||
page: this.pagination.currentPage
|
||||
}
|
||||
protected buildRouteParams () {
|
||||
const params = super.buildRouteParams()
|
||||
|
||||
// Maybe there is a search
|
||||
if (this.search.value) {
|
||||
|
@ -125,7 +76,9 @@ export class VideoListComponent implements OnInit, OnDestroy {
|
|||
return params
|
||||
}
|
||||
|
||||
private loadRouteParams (routeParams: { [ key: string ]: any }) {
|
||||
protected loadRouteParams (routeParams: { [ key: string ]: any }) {
|
||||
super.loadRouteParams(routeParams)
|
||||
|
||||
if (routeParams['search'] !== undefined) {
|
||||
this.search = {
|
||||
value: routeParams['search'],
|
||||
|
@ -137,18 +90,5 @@ export class VideoListComponent implements OnInit, OnDestroy {
|
|||
field: 'name'
|
||||
}
|
||||
}
|
||||
|
||||
this.sort = routeParams['sort'] as SortField || '-createdAt'
|
||||
|
||||
if (routeParams['page'] !== undefined) {
|
||||
this.pagination.currentPage = parseInt(routeParams['page'], 10)
|
||||
} else {
|
||||
this.pagination.currentPage = 1
|
||||
}
|
||||
}
|
||||
|
||||
private navigateToNewParams () {
|
||||
const routeParams = this.buildRouteParams()
|
||||
this.router.navigate([ '/videos/list', routeParams ])
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ import { RouterModule, Routes } from '@angular/router'
|
|||
|
||||
import { MetaGuard } from '@ngx-meta/core'
|
||||
|
||||
import { VideoListComponent } from './video-list'
|
||||
import { VideoListComponent, MyVideosComponent } from './video-list'
|
||||
import { VideosComponent } from './videos.component'
|
||||
|
||||
const videosRoutes: Routes = [
|
||||
|
@ -12,6 +12,15 @@ const videosRoutes: Routes = [
|
|||
component: VideosComponent,
|
||||
canActivateChild: [ MetaGuard ],
|
||||
children: [
|
||||
{
|
||||
path: 'mine',
|
||||
component: MyVideosComponent,
|
||||
data: {
|
||||
meta: {
|
||||
title: 'My videos'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'list',
|
||||
component: VideoListComponent,
|
||||
|
|
|
@ -2,7 +2,13 @@ import { NgModule } from '@angular/core'
|
|||
|
||||
import { VideosRoutingModule } from './videos-routing.module'
|
||||
import { VideosComponent } from './videos.component'
|
||||
import { LoaderComponent, VideoListComponent, VideoMiniatureComponent, VideoSortComponent } from './video-list'
|
||||
import {
|
||||
LoaderComponent,
|
||||
VideoListComponent,
|
||||
MyVideosComponent,
|
||||
VideoMiniatureComponent,
|
||||
VideoSortComponent
|
||||
} from './video-list'
|
||||
import { VideoService } from './shared'
|
||||
import { SharedModule } from '../shared'
|
||||
|
||||
|
@ -16,6 +22,7 @@ import { SharedModule } from '../shared'
|
|||
VideosComponent,
|
||||
|
||||
VideoListComponent,
|
||||
MyVideosComponent,
|
||||
VideoMiniatureComponent,
|
||||
VideoSortComponent,
|
||||
|
||||
|
|
|
@ -334,71 +334,34 @@ $slider-bg-color: lighten($primary-background-color, 33%);
|
|||
|
||||
// Thanks: https://projects.lukehaas.me/css-loaders/
|
||||
.vjs-loading-spinner {
|
||||
border: none;
|
||||
opacity: 1;
|
||||
margin: -25px 0 0 -25px;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
font-size: 10px;
|
||||
text-indent: -9999em;
|
||||
width: 5em;
|
||||
height: 5em;
|
||||
border-radius: 50%;
|
||||
background: #ffffff;
|
||||
background: -moz-linear-gradient(left, #ffffff 10%, rgba(255, 255, 255, 0) 42%);
|
||||
background: -webkit-linear-gradient(left, #ffffff 10%, rgba(255, 255, 255, 0) 42%);
|
||||
background: -o-linear-gradient(left, #ffffff 10%, rgba(255, 255, 255, 0) 42%);
|
||||
background: -ms-linear-gradient(left, #ffffff 10%, rgba(255, 255, 255, 0) 42%);
|
||||
background: linear-gradient(to right, #ffffff 10%, rgba(255, 255, 255, 0) 42%);
|
||||
position: relative;
|
||||
-webkit-animation: load3 1.4s infinite linear;
|
||||
animation: load3 1.4s infinite linear;
|
||||
-webkit-transform: translateZ(0);
|
||||
-ms-transform: translateZ(0);
|
||||
text-indent: -9999em;
|
||||
border: 0.7em solid rgba(255, 255, 255, 0.2);
|
||||
border-left-color: #ffffff;
|
||||
transform: translateZ(0);
|
||||
animation: spinner 1.4s infinite linear;
|
||||
|
||||
&:before {
|
||||
width: 50%;
|
||||
height: 50%;
|
||||
background: #ffffff;
|
||||
border-radius: 100% 0 0 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
content: '';
|
||||
animation: none !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
&:after {
|
||||
background: #000;
|
||||
width: 75%;
|
||||
height: 75%;
|
||||
border-radius: 50%;
|
||||
content: '';
|
||||
margin: auto;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
width: 6em;
|
||||
height: 6em;
|
||||
animation: none !important;
|
||||
}
|
||||
|
||||
@-webkit-keyframes load3 {
|
||||
@keyframes spinner {
|
||||
0% {
|
||||
-webkit-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
-webkit-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
@keyframes load3 {
|
||||
0% {
|
||||
-webkit-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
-webkit-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -267,7 +267,8 @@ async function addRemoteVideo (videoToCreateData: RemoteVideoCreateData, fromPod
|
|||
views: videoToCreateData.views,
|
||||
likes: videoToCreateData.likes,
|
||||
dislikes: videoToCreateData.dislikes,
|
||||
remote: true
|
||||
remote: true,
|
||||
privacy: videoToCreateData.privacy
|
||||
}
|
||||
|
||||
const video = db.Video.build(videoData)
|
||||
|
@ -334,6 +335,7 @@ async function updateRemoteVideo (videoAttributesToUpdate: RemoteVideoUpdateData
|
|||
videoInstance.set('views', videoAttributesToUpdate.views)
|
||||
videoInstance.set('likes', videoAttributesToUpdate.likes)
|
||||
videoInstance.set('dislikes', videoAttributesToUpdate.dislikes)
|
||||
videoInstance.set('privacy', videoAttributesToUpdate.privacy)
|
||||
|
||||
await videoInstance.save(sequelizeOptions)
|
||||
|
||||
|
|
|
@ -30,6 +30,8 @@ import {
|
|||
} from '../../../shared'
|
||||
import { createUserAuthorAndChannel } from '../../lib'
|
||||
import { UserInstance } from '../../models'
|
||||
import { videosSortValidator } from '../../middlewares/validators/sort'
|
||||
import { setVideosSort } from '../../middlewares/sort'
|
||||
|
||||
const usersRouter = express.Router()
|
||||
|
||||
|
@ -38,6 +40,15 @@ usersRouter.get('/me',
|
|||
asyncMiddleware(getUserInformation)
|
||||
)
|
||||
|
||||
usersRouter.get('/me/videos',
|
||||
authenticate,
|
||||
paginationValidator,
|
||||
videosSortValidator,
|
||||
setVideosSort,
|
||||
setPagination,
|
||||
asyncMiddleware(getUserVideos)
|
||||
)
|
||||
|
||||
usersRouter.get('/me/videos/:videoId/rating',
|
||||
authenticate,
|
||||
usersVideoRatingValidator,
|
||||
|
@ -101,6 +112,13 @@ export {
|
|||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async function getUserVideos (req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||
const user = res.locals.oauth.token.User
|
||||
const resultList = await db.Video.listUserVideosForApi(user.id ,req.query.start, req.query.count, req.query.sort)
|
||||
|
||||
return res.json(getFormattedObjects(resultList.data, resultList.total))
|
||||
}
|
||||
|
||||
async function createUserRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||
const options = {
|
||||
arguments: [ req, res ],
|
||||
|
@ -146,13 +164,14 @@ async function registerUser (req: express.Request, res: express.Response, next:
|
|||
}
|
||||
|
||||
async function getUserInformation (req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||
// We did not load channels in res.locals.user
|
||||
const user = await db.User.loadByUsernameAndPopulateChannels(res.locals.oauth.token.user.username)
|
||||
|
||||
return res.json(user.toFormattedJSON())
|
||||
}
|
||||
|
||||
function getUser (req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||
return res.json(res.locals.user.toFormattedJSON())
|
||||
return res.json(res.locals.oauth.token.User.toFormattedJSON())
|
||||
}
|
||||
|
||||
async function getUserVideoRating (req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||
|
|
|
@ -9,7 +9,8 @@ import {
|
|||
REQUEST_VIDEO_EVENT_TYPES,
|
||||
VIDEO_CATEGORIES,
|
||||
VIDEO_LICENCES,
|
||||
VIDEO_LANGUAGES
|
||||
VIDEO_LANGUAGES,
|
||||
VIDEO_PRIVACIES
|
||||
} from '../../../initializers'
|
||||
import {
|
||||
addEventToRemoteVideo,
|
||||
|
@ -43,7 +44,7 @@ import {
|
|||
resetSequelizeInstance
|
||||
} from '../../../helpers'
|
||||
import { VideoInstance } from '../../../models'
|
||||
import { VideoCreate, VideoUpdate } from '../../../../shared'
|
||||
import { VideoCreate, VideoUpdate, VideoPrivacy } from '../../../../shared'
|
||||
|
||||
import { abuseVideoRouter } from './abuse'
|
||||
import { blacklistRouter } from './blacklist'
|
||||
|
@ -84,6 +85,7 @@ videosRouter.use('/', videoChannelRouter)
|
|||
videosRouter.get('/categories', listVideoCategories)
|
||||
videosRouter.get('/licences', listVideoLicences)
|
||||
videosRouter.get('/languages', listVideoLanguages)
|
||||
videosRouter.get('/privacies', listVideoPrivacies)
|
||||
|
||||
videosRouter.get('/',
|
||||
paginationValidator,
|
||||
|
@ -149,6 +151,10 @@ function listVideoLanguages (req: express.Request, res: express.Response) {
|
|||
res.json(VIDEO_LANGUAGES)
|
||||
}
|
||||
|
||||
function listVideoPrivacies (req: express.Request, res: express.Response) {
|
||||
res.json(VIDEO_PRIVACIES)
|
||||
}
|
||||
|
||||
// Wrapper to video add that retry the function if there is a database error
|
||||
// We need this because we run the transaction in SERIALIZABLE isolation that can fail
|
||||
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
|
|||
language: videoInfo.language,
|
||||
nsfw: videoInfo.nsfw,
|
||||
description: videoInfo.description,
|
||||
privacy: videoInfo.privacy,
|
||||
duration: videoPhysicalFile['duration'], // duration was added by a previous middleware
|
||||
channelId: res.locals.videoChannel.id
|
||||
}
|
||||
|
@ -240,6 +247,8 @@ async function addVideo (req: express.Request, res: express.Response, videoPhysi
|
|||
|
||||
// Let transcoding job send the video to friends because the video file extension might change
|
||||
if (CONFIG.TRANSCODING.ENABLED === true) return undefined
|
||||
// Don't send video to remote pods, it is private
|
||||
if (video.privacy === VideoPrivacy.PRIVATE) return undefined
|
||||
|
||||
const remoteVideo = await video.toAddRemoteJSON()
|
||||
// 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) {
|
|||
const videoInstance = res.locals.video
|
||||
const videoFieldsSave = videoInstance.toJSON()
|
||||
const videoInfoToUpdate: VideoUpdate = req.body
|
||||
const wasPrivateVideo = videoInstance.privacy === VideoPrivacy.PRIVATE
|
||||
|
||||
try {
|
||||
await db.sequelize.transaction(async t => {
|
||||
|
@ -276,6 +286,7 @@ async function updateVideo (req: express.Request, res: express.Response) {
|
|||
if (videoInfoToUpdate.licence !== undefined) videoInstance.set('licence', videoInfoToUpdate.licence)
|
||||
if (videoInfoToUpdate.language !== undefined) videoInstance.set('language', videoInfoToUpdate.language)
|
||||
if (videoInfoToUpdate.nsfw !== undefined) videoInstance.set('nsfw', videoInfoToUpdate.nsfw)
|
||||
if (videoInfoToUpdate.privacy !== undefined) videoInstance.set('privacy', videoInfoToUpdate.privacy)
|
||||
if (videoInfoToUpdate.description !== undefined) videoInstance.set('description', videoInfoToUpdate.description)
|
||||
|
||||
await videoInstance.save(sequelizeOptions)
|
||||
|
@ -287,10 +298,17 @@ async function updateVideo (req: express.Request, res: express.Response) {
|
|||
videoInstance.Tags = tagInstances
|
||||
}
|
||||
|
||||
const json = videoInstance.toUpdateRemoteJSON()
|
||||
|
||||
// Now we'll update the video's meta data to our friends
|
||||
return updateVideoToFriends(json, t)
|
||||
if (wasPrivateVideo === false) {
|
||||
const json = videoInstance.toUpdateRemoteJSON()
|
||||
return updateVideoToFriends(json, t)
|
||||
}
|
||||
|
||||
// Video is not private anymore, send a create action to remote pods
|
||||
if (wasPrivateVideo === true && videoInstance.privacy !== VideoPrivacy.PRIVATE) {
|
||||
const remoteVideo = await videoInstance.toAddRemoteJSON()
|
||||
return addVideoToFriends(remoteVideo, t)
|
||||
}
|
||||
})
|
||||
|
||||
logger.info('Video with name %s and uuid %s updated.', videoInstance.name, videoInstance.uuid)
|
||||
|
|
|
@ -11,6 +11,7 @@ import {
|
|||
VIDEO_LICENCES,
|
||||
VIDEO_LANGUAGES,
|
||||
VIDEO_RATE_TYPES,
|
||||
VIDEO_PRIVACIES,
|
||||
database as db
|
||||
} from '../../initializers'
|
||||
import { isUserUsernameValid } from './users'
|
||||
|
@ -36,6 +37,15 @@ function isVideoLicenceValid (value: number) {
|
|||
return VIDEO_LICENCES[value] !== undefined
|
||||
}
|
||||
|
||||
function isVideoPrivacyValid (value: string) {
|
||||
return VIDEO_PRIVACIES[value] !== undefined
|
||||
}
|
||||
|
||||
// Maybe we don't know the remote privacy setting, but that doesn't matter
|
||||
function isRemoteVideoPrivacyValid (value: string) {
|
||||
return validator.isInt('' + value)
|
||||
}
|
||||
|
||||
// Maybe we don't know the remote licence, but that doesn't matter
|
||||
function isRemoteVideoLicenceValid (value: string) {
|
||||
return validator.isInt('' + value)
|
||||
|
@ -195,6 +205,8 @@ export {
|
|||
isVideoDislikesValid,
|
||||
isVideoEventCountValid,
|
||||
isVideoFileSizeValid,
|
||||
isVideoPrivacyValid,
|
||||
isRemoteVideoPrivacyValid,
|
||||
isVideoFileResolutionValid,
|
||||
checkVideoExists,
|
||||
isRemoteVideoCategoryValid,
|
||||
|
|
|
@ -12,10 +12,11 @@ import {
|
|||
RemoteVideoRequestType,
|
||||
JobState
|
||||
} from '../../shared/models'
|
||||
import { VideoPrivacy } from '../../shared/models/videos/video-privacy.enum'
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const LAST_MIGRATION_VERSION = 90
|
||||
const LAST_MIGRATION_VERSION = 95
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
|
@ -196,6 +197,12 @@ const VIDEO_LANGUAGES = {
|
|||
14: 'Italian'
|
||||
}
|
||||
|
||||
const VIDEO_PRIVACIES = {
|
||||
[VideoPrivacy.PUBLIC]: 'Public',
|
||||
[VideoPrivacy.UNLISTED]: 'Unlisted',
|
||||
[VideoPrivacy.PRIVATE]: 'Private'
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// Score a pod has when we create it as a friend
|
||||
|
@ -394,6 +401,7 @@ export {
|
|||
THUMBNAILS_SIZE,
|
||||
VIDEO_CATEGORIES,
|
||||
VIDEO_LANGUAGES,
|
||||
VIDEO_PRIVACIES,
|
||||
VIDEO_LICENCES,
|
||||
VIDEO_RATE_TYPES
|
||||
}
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction,
|
||||
queryInterface: Sequelize.QueryInterface,
|
||||
sequelize: Sequelize.Sequelize,
|
||||
db: any
|
||||
}): Promise<void> {
|
||||
const q = utils.queryInterface
|
||||
|
||||
const data = {
|
||||
type: Sequelize.INTEGER,
|
||||
defaultValue: null,
|
||||
allowNull: true
|
||||
}
|
||||
await q.addColumn('Videos', 'privacy', data)
|
||||
|
||||
const query = 'UPDATE "Videos" SET "privacy" = 1'
|
||||
const options = {
|
||||
type: Sequelize.QueryTypes.BULKUPDATE
|
||||
}
|
||||
await utils.sequelize.query(query, options)
|
||||
|
||||
data.allowNull = false
|
||||
await q.changeColumn('Videos', 'privacy', data)
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
|
@ -20,9 +20,10 @@ import {
|
|||
isVideoRatingTypeValid,
|
||||
getDurationFromVideoFile,
|
||||
checkVideoExists,
|
||||
isIdValid
|
||||
isIdValid,
|
||||
isVideoPrivacyValid
|
||||
} from '../../helpers'
|
||||
import { UserRight } from '../../../shared'
|
||||
import { UserRight, VideoPrivacy } from '../../../shared'
|
||||
|
||||
const videosAddValidator = [
|
||||
body('videofile').custom((value, { req }) => isVideoFile(req.files)).withMessage(
|
||||
|
@ -36,6 +37,7 @@ const videosAddValidator = [
|
|||
body('nsfw').custom(isVideoNSFWValid).withMessage('Should have a valid NSFW attribute'),
|
||||
body('description').custom(isVideoDescriptionValid).withMessage('Should have a valid description'),
|
||||
body('channelId').custom(isIdValid).withMessage('Should have correct video channel id'),
|
||||
body('privacy').custom(isVideoPrivacyValid).withMessage('Should have correct video privacy'),
|
||||
body('tags').optional().custom(isVideoTagsValid).withMessage('Should have correct tags'),
|
||||
|
||||
(req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||
|
@ -110,6 +112,7 @@ const videosUpdateValidator = [
|
|||
body('licence').optional().custom(isVideoLicenceValid).withMessage('Should have a valid licence'),
|
||||
body('language').optional().custom(isVideoLanguageValid).withMessage('Should have a valid language'),
|
||||
body('nsfw').optional().custom(isVideoNSFWValid).withMessage('Should have a valid NSFW attribute'),
|
||||
body('privacy').custom(isVideoPrivacyValid).withMessage('Should have correct video privacy'),
|
||||
body('description').optional().custom(isVideoDescriptionValid).withMessage('Should have a valid description'),
|
||||
body('tags').optional().custom(isVideoTagsValid).withMessage('Should have correct tags'),
|
||||
|
||||
|
@ -118,19 +121,27 @@ const videosUpdateValidator = [
|
|||
|
||||
checkErrors(req, res, () => {
|
||||
checkVideoExists(req.params.id, res, () => {
|
||||
const video = res.locals.video
|
||||
|
||||
// We need to make additional checks
|
||||
if (res.locals.video.isOwned() === false) {
|
||||
if (video.isOwned() === false) {
|
||||
return res.status(403)
|
||||
.json({ error: 'Cannot update video of another pod' })
|
||||
.end()
|
||||
}
|
||||
|
||||
if (res.locals.video.VideoChannel.Author.userId !== res.locals.oauth.token.User.id) {
|
||||
if (video.VideoChannel.Author.userId !== res.locals.oauth.token.User.id) {
|
||||
return res.status(403)
|
||||
.json({ error: 'Cannot update video of another user' })
|
||||
.end()
|
||||
}
|
||||
|
||||
if (video.privacy !== VideoPrivacy.PRIVATE && req.body.privacy === VideoPrivacy.PRIVATE) {
|
||||
return res.status(409)
|
||||
.json({ error: 'Cannot set "private" a video that was not private anymore.' })
|
||||
.end()
|
||||
}
|
||||
|
||||
next()
|
||||
})
|
||||
})
|
||||
|
|
|
@ -49,6 +49,7 @@ export namespace VideoMethods {
|
|||
export type ListOwnedByAuthor = (author: string) => Promise<VideoInstance[]>
|
||||
|
||||
export type ListForApi = (start: number, count: number, sort: string) => Promise< ResultList<VideoInstance> >
|
||||
export type ListUserVideosForApi = (userId: number, start: number, count: number, sort: string) => Promise< ResultList<VideoInstance> >
|
||||
export type SearchAndPopulateAuthorAndPodAndTags = (
|
||||
value: string,
|
||||
field: string,
|
||||
|
@ -75,6 +76,7 @@ export interface VideoClass {
|
|||
generateThumbnailFromData: VideoMethods.GenerateThumbnailFromData
|
||||
list: VideoMethods.List
|
||||
listForApi: VideoMethods.ListForApi
|
||||
listUserVideosForApi: VideoMethods.ListUserVideosForApi
|
||||
listOwnedAndPopulateAuthorAndTags: VideoMethods.ListOwnedAndPopulateAuthorAndTags
|
||||
listOwnedByAuthor: VideoMethods.ListOwnedByAuthor
|
||||
load: VideoMethods.Load
|
||||
|
@ -97,6 +99,7 @@ export interface VideoAttributes {
|
|||
nsfw: boolean
|
||||
description: string
|
||||
duration: number
|
||||
privacy: number
|
||||
views?: number
|
||||
likes?: number
|
||||
dislikes?: number
|
||||
|
|
|
@ -18,6 +18,7 @@ import {
|
|||
isVideoNSFWValid,
|
||||
isVideoDescriptionValid,
|
||||
isVideoDurationValid,
|
||||
isVideoPrivacyValid,
|
||||
readFileBufferPromise,
|
||||
unlinkPromise,
|
||||
renamePromise,
|
||||
|
@ -38,10 +39,11 @@ import {
|
|||
THUMBNAILS_SIZE,
|
||||
PREVIEWS_SIZE,
|
||||
CONSTRAINTS_FIELDS,
|
||||
API_VERSION
|
||||
API_VERSION,
|
||||
VIDEO_PRIVACIES
|
||||
} from '../../initializers'
|
||||
import { removeVideoToFriends } from '../../lib'
|
||||
import { VideoResolution } from '../../../shared'
|
||||
import { VideoResolution, VideoPrivacy } from '../../../shared'
|
||||
import { VideoFileInstance, VideoFileModel } from './video-file-interface'
|
||||
|
||||
import { addMethodsToModel, getSort } from '../utils'
|
||||
|
@ -79,6 +81,7 @@ let getTruncatedDescription: VideoMethods.GetTruncatedDescription
|
|||
let generateThumbnailFromData: VideoMethods.GenerateThumbnailFromData
|
||||
let list: VideoMethods.List
|
||||
let listForApi: VideoMethods.ListForApi
|
||||
let listUserVideosForApi: VideoMethods.ListUserVideosForApi
|
||||
let loadByHostAndUUID: VideoMethods.LoadByHostAndUUID
|
||||
let listOwnedAndPopulateAuthorAndTags: VideoMethods.ListOwnedAndPopulateAuthorAndTags
|
||||
let listOwnedByAuthor: VideoMethods.ListOwnedByAuthor
|
||||
|
@ -146,6 +149,16 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da
|
|||
}
|
||||
}
|
||||
},
|
||||
privacy: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
validate: {
|
||||
privacyValid: value => {
|
||||
const res = isVideoPrivacyValid(value)
|
||||
if (res === false) throw new Error('Video privacy is not valid.')
|
||||
}
|
||||
}
|
||||
},
|
||||
nsfw: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
allowNull: false,
|
||||
|
@ -245,6 +258,7 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da
|
|||
generateThumbnailFromData,
|
||||
list,
|
||||
listForApi,
|
||||
listUserVideosForApi,
|
||||
listOwnedAndPopulateAuthorAndTags,
|
||||
listOwnedByAuthor,
|
||||
load,
|
||||
|
@ -501,7 +515,13 @@ toFormattedJSON = function (this: VideoInstance) {
|
|||
toFormattedDetailsJSON = function (this: VideoInstance) {
|
||||
const formattedJson = this.toFormattedJSON()
|
||||
|
||||
// Maybe our pod is not up to date and there are new privacy settings since our version
|
||||
let privacyLabel = VIDEO_PRIVACIES[this.privacy]
|
||||
if (!privacyLabel) privacyLabel = 'Unknown'
|
||||
|
||||
const detailsJson = {
|
||||
privacyLabel,
|
||||
privacy: this.privacy,
|
||||
descriptionPath: this.getDescriptionPath(),
|
||||
channel: this.VideoChannel.toFormattedJSON(),
|
||||
files: []
|
||||
|
@ -555,6 +575,7 @@ toAddRemoteJSON = function (this: VideoInstance) {
|
|||
views: this.views,
|
||||
likes: this.likes,
|
||||
dislikes: this.dislikes,
|
||||
privacy: this.privacy,
|
||||
files: []
|
||||
}
|
||||
|
||||
|
@ -587,6 +608,7 @@ toUpdateRemoteJSON = function (this: VideoInstance) {
|
|||
views: this.views,
|
||||
likes: this.likes,
|
||||
dislikes: this.dislikes,
|
||||
privacy: this.privacy,
|
||||
files: []
|
||||
}
|
||||
|
||||
|
@ -746,8 +768,39 @@ list = function () {
|
|||
return Video.findAll(query)
|
||||
}
|
||||
|
||||
listUserVideosForApi = function (userId: number, start: number, count: number, sort: string) {
|
||||
const query = {
|
||||
distinct: true,
|
||||
offset: start,
|
||||
limit: count,
|
||||
order: [ getSort(sort), [ Video['sequelize'].models.Tag, 'name', 'ASC' ] ],
|
||||
include: [
|
||||
{
|
||||
model: Video['sequelize'].models.VideoChannel,
|
||||
required: true,
|
||||
include: [
|
||||
{
|
||||
model: Video['sequelize'].models.Author,
|
||||
where: {
|
||||
userId
|
||||
},
|
||||
required: true
|
||||
}
|
||||
]
|
||||
},
|
||||
Video['sequelize'].models.Tag
|
||||
]
|
||||
}
|
||||
|
||||
return Video.findAndCountAll(query).then(({ rows, count }) => {
|
||||
return {
|
||||
data: rows,
|
||||
total: count
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
listForApi = function (start: number, count: number, sort: string) {
|
||||
// Exclude blacklisted videos from the list
|
||||
const query = {
|
||||
distinct: true,
|
||||
offset: start,
|
||||
|
@ -768,8 +821,7 @@ listForApi = function (start: number, count: number, sort: string) {
|
|||
}
|
||||
]
|
||||
},
|
||||
Video['sequelize'].models.Tag,
|
||||
Video['sequelize'].models.VideoFile
|
||||
Video['sequelize'].models.Tag
|
||||
],
|
||||
where: createBaseVideosWhere()
|
||||
}
|
||||
|
@ -969,10 +1021,6 @@ searchAndPopulateAuthorAndPodAndTags = function (value: string, field: string, s
|
|||
model: Video['sequelize'].models.Tag
|
||||
}
|
||||
|
||||
const videoFileInclude: Sequelize.IncludeOptions = {
|
||||
model: Video['sequelize'].models.VideoFile
|
||||
}
|
||||
|
||||
const query: Sequelize.FindOptions<VideoAttributes> = {
|
||||
distinct: true,
|
||||
where: createBaseVideosWhere(),
|
||||
|
@ -981,12 +1029,7 @@ searchAndPopulateAuthorAndPodAndTags = function (value: string, field: string, s
|
|||
order: [ getSort(sort), [ Video['sequelize'].models.Tag, 'name', 'ASC' ] ]
|
||||
}
|
||||
|
||||
// Make an exact search with the magnet
|
||||
if (field === 'magnetUri') {
|
||||
videoFileInclude.where = {
|
||||
infoHash: magnetUtil.decode(value).infoHash
|
||||
}
|
||||
} else if (field === 'tags') {
|
||||
if (field === 'tags') {
|
||||
const escapedValue = Video['sequelize'].escape('%' + value + '%')
|
||||
query.where['id'][Sequelize.Op.in] = Video['sequelize'].literal(
|
||||
`(SELECT "VideoTags"."videoId"
|
||||
|
@ -1016,7 +1059,7 @@ searchAndPopulateAuthorAndPodAndTags = function (value: string, field: string, s
|
|||
}
|
||||
|
||||
query.include = [
|
||||
videoChannelInclude, tagInclude, videoFileInclude
|
||||
videoChannelInclude, tagInclude
|
||||
]
|
||||
|
||||
return Video.findAndCountAll(query).then(({ rows, count }) => {
|
||||
|
@ -1035,7 +1078,8 @@ function createBaseVideosWhere () {
|
|||
[Sequelize.Op.notIn]: Video['sequelize'].literal(
|
||||
'(SELECT "BlacklistedVideos"."videoId" FROM "BlacklistedVideos")'
|
||||
)
|
||||
}
|
||||
},
|
||||
privacy: VideoPrivacy.PUBLIC
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ export interface RemoteVideoCreateData {
|
|||
views: number
|
||||
likes: number
|
||||
dislikes: number
|
||||
privacy: number
|
||||
thumbnailData: string
|
||||
files: {
|
||||
infoHash: string
|
||||
|
|
|
@ -15,6 +15,7 @@ export interface RemoteVideoUpdateData {
|
|||
views: number
|
||||
likes: number
|
||||
dislikes: number
|
||||
privacy: number
|
||||
files: {
|
||||
infoHash: string
|
||||
extname: string
|
||||
|
|
|
@ -8,6 +8,7 @@ export * from './video-channel-create.model'
|
|||
export * from './video-channel-update.model'
|
||||
export * from './video-channel.model'
|
||||
export * from './video-create.model'
|
||||
export * from './video-privacy.enum'
|
||||
export * from './video-rate.type'
|
||||
export * from './video-resolution.enum'
|
||||
export * from './video-update.model'
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { VideoPrivacy } from './video-privacy.enum'
|
||||
|
||||
export interface VideoCreate {
|
||||
category: number
|
||||
licence: number
|
||||
|
@ -7,4 +9,5 @@ export interface VideoCreate {
|
|||
nsfw: boolean
|
||||
name: string
|
||||
tags: string[]
|
||||
privacy: VideoPrivacy
|
||||
}
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
export enum VideoPrivacy {
|
||||
PUBLIC = 1,
|
||||
UNLISTED = 2,
|
||||
PRIVATE = 3
|
||||
}
|
|
@ -1,9 +1,12 @@
|
|||
import { VideoPrivacy } from './video-privacy.enum'
|
||||
|
||||
export interface VideoUpdate {
|
||||
name?: string
|
||||
category?: number
|
||||
licence?: number
|
||||
language?: number
|
||||
description?: string
|
||||
privacy?: VideoPrivacy
|
||||
tags?: string[]
|
||||
nsfw?: boolean
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { VideoChannel } from './video-channel.model'
|
||||
import { VideoPrivacy } from './video-privacy.enum'
|
||||
|
||||
export interface VideoFile {
|
||||
magnetUri: string
|
||||
|
@ -37,7 +38,9 @@ export interface Video {
|
|||
}
|
||||
|
||||
export interface VideoDetails extends Video {
|
||||
descriptionPath: string,
|
||||
privacy: VideoPrivacy
|
||||
privacyLabel: string
|
||||
descriptionPath: string
|
||||
channel: VideoChannel
|
||||
files: VideoFile[]
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue