Add video privacy setting

pull/125/head
Chocobozzz 2017-10-31 11:52:52 +01:00
parent b7a485121d
commit fd45e8f43c
No known key found for this signature in database
GPG Key ID: 583A612D890159BE
48 changed files with 545 additions and 208 deletions

View File

@ -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) {

View File

@ -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">

View File

@ -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)

View File

@ -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: {

View File

@ -1 +1 @@
export type SearchField = 'name' | 'author' | 'host' | 'magnetUri' | 'tags'
export type SearchField = 'name' | 'author' | 'host' | 'tags'

View File

@ -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">

View File

@ -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) {

View File

@ -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"

View File

@ -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)

View File

@ -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"

View File

@ -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()
},

View File

@ -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:

View File

@ -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

View File

@ -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
}
}
}

View File

@ -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>) {

View File

@ -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'

View File

@ -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)
}
}

View File

@ -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 ])
}
}

View File

@ -0,0 +1,4 @@
export * from './abstract-video-list'
export * from './loader.component'
export * from './video-miniature.component'
export * from './video-sort.component'

View File

@ -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',

View File

@ -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',

View File

@ -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 ])
}
}

View File

@ -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,

View File

@ -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,

View File

@ -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);
}
}

View File

@ -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)

View File

@ -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) {

View File

@ -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)

View File

@ -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,

View File

@ -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
}

View File

@ -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
}

View File

@ -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()
})
})

View File

@ -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

View File

@ -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
}
}

View File

@ -16,6 +16,7 @@ export interface RemoteVideoCreateData {
views: number
likes: number
dislikes: number
privacy: number
thumbnailData: string
files: {
infoHash: string

View File

@ -15,6 +15,7 @@ export interface RemoteVideoUpdateData {
views: number
likes: number
dislikes: number
privacy: number
files: {
infoHash: string
extname: string

View File

@ -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'

View File

@ -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
}

View File

@ -0,0 +1,5 @@
export enum VideoPrivacy {
PUBLIC = 1,
UNLISTED = 2,
PRIVATE = 3
}

View File

@ -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
}

View File

@ -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[]
}