Deprecate filter video query
Introduce include and isLocal instead
This commit is contained in:
parent
e4611b5491
commit
2760b454a7
|
@ -63,10 +63,17 @@
|
||||||
<my-video-cell [video]="video"></my-video-cell>
|
<my-video-cell [video]="video"></my-video-cell>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td>
|
<td class="badges">
|
||||||
<span class="badge badge-blue" i18n>{{ video.privacy.label }}</span>
|
<span [ngClass]="getPrivacyBadgeClass(video.privacy.id)" class="badge" i18n>{{ video.privacy.label }}</span>
|
||||||
|
|
||||||
<span *ngIf="video.nsfw" class="badge badge-red" i18n>NSFW</span>
|
<span *ngIf="video.nsfw" class="badge badge-red" i18n>NSFW</span>
|
||||||
<span *ngIf="video.blocked" class="badge badge-red" i18n>NSFW</span>
|
|
||||||
|
<span *ngIf="isUnpublished(video.state.id)" class="badge badge-yellow" i18n>Not published yet</span>
|
||||||
|
|
||||||
|
<span *ngIf="isAccountBlocked(video)" class="badge badge-red" i18n>Account muted</span>
|
||||||
|
<span *ngIf="isServerBlocked(video)" class="badge badge-red" i18n>Server muted</span>
|
||||||
|
|
||||||
|
<span *ngIf="isVideoBlocked(video)" class="badge badge-red" i18n>Blocked</span>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td>
|
<td>
|
||||||
|
|
|
@ -7,4 +7,6 @@ my-embed {
|
||||||
|
|
||||||
.badge {
|
.badge {
|
||||||
@include table-badge;
|
@include table-badge;
|
||||||
|
|
||||||
|
margin-right: 5px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { Component, OnInit } from '@angular/core'
|
||||||
import { ActivatedRoute, Router } from '@angular/router'
|
import { ActivatedRoute, Router } from '@angular/router'
|
||||||
import { AuthService, ConfirmService, Notifier, RestPagination, RestTable } from '@app/core'
|
import { AuthService, ConfirmService, Notifier, RestPagination, RestTable } from '@app/core'
|
||||||
import { DropdownAction, Video, VideoService } from '@app/shared/shared-main'
|
import { DropdownAction, Video, VideoService } from '@app/shared/shared-main'
|
||||||
import { UserRight } from '@shared/models'
|
import { UserRight, VideoPrivacy, VideoState } from '@shared/models'
|
||||||
import { AdvancedInputFilter } from '@app/shared/shared-forms'
|
import { AdvancedInputFilter } from '@app/shared/shared-forms'
|
||||||
import { VideoActionsDisplayType } from '@app/shared/shared-video-miniature'
|
import { VideoActionsDisplayType } from '@app/shared/shared-video-miniature'
|
||||||
|
|
||||||
|
@ -28,8 +28,12 @@ export class VideoListComponent extends RestTable implements OnInit {
|
||||||
title: $localize`Advanced filters`,
|
title: $localize`Advanced filters`,
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
queryParams: { search: 'local:true' },
|
queryParams: { search: 'isLocal:false' },
|
||||||
label: $localize`Only local videos`
|
label: $localize`Remote videos`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
queryParams: { search: 'isLocal:true' },
|
||||||
|
label: $localize`Local videos`
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -88,6 +92,28 @@ export class VideoListComponent extends RestTable implements OnInit {
|
||||||
this.reloadData()
|
this.reloadData()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getPrivacyBadgeClass (privacy: VideoPrivacy) {
|
||||||
|
if (privacy === VideoPrivacy.PUBLIC) return 'badge-blue'
|
||||||
|
|
||||||
|
return 'badge-yellow'
|
||||||
|
}
|
||||||
|
|
||||||
|
isUnpublished (state: VideoState) {
|
||||||
|
return state !== VideoState.PUBLISHED
|
||||||
|
}
|
||||||
|
|
||||||
|
isAccountBlocked (video: Video) {
|
||||||
|
return video.blockedOwner
|
||||||
|
}
|
||||||
|
|
||||||
|
isServerBlocked (video: Video) {
|
||||||
|
return video.blockedServer
|
||||||
|
}
|
||||||
|
|
||||||
|
isVideoBlocked (video: Video) {
|
||||||
|
return video.blacklisted
|
||||||
|
}
|
||||||
|
|
||||||
protected reloadData () {
|
protected reloadData () {
|
||||||
this.selectedVideos = []
|
this.selectedVideos = []
|
||||||
|
|
||||||
|
|
|
@ -24,5 +24,5 @@
|
||||||
|
|
||||||
<div class="alert alert-danger" *ngIf="video?.blacklisted">
|
<div class="alert alert-danger" *ngIf="video?.blacklisted">
|
||||||
<div class="blocked-label" i18n>This video is blocked.</div>
|
<div class="blocked-label" i18n>This video is blocked.</div>
|
||||||
{{ video.blockedReason }}
|
{{ video.blacklistedReason }}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -85,7 +85,7 @@ export class VideosListCommonPageComponent implements OnInit, OnDestroy, Disable
|
||||||
getSyndicationItems (filters: VideoFilters) {
|
getSyndicationItems (filters: VideoFilters) {
|
||||||
const result = filters.toVideosAPIObject()
|
const result = filters.toVideosAPIObject()
|
||||||
|
|
||||||
return this.videoService.getVideoFeedUrls(result.sort, result.filter)
|
return this.videoService.getVideoFeedUrls(result.sort, result.isLocal)
|
||||||
}
|
}
|
||||||
|
|
||||||
onFiltersChanged (filters: VideoFilters) {
|
onFiltersChanged (filters: VideoFilters) {
|
||||||
|
|
|
@ -7,7 +7,6 @@ import {
|
||||||
ContainerMarkupData,
|
ContainerMarkupData,
|
||||||
EmbedMarkupData,
|
EmbedMarkupData,
|
||||||
PlaylistMiniatureMarkupData,
|
PlaylistMiniatureMarkupData,
|
||||||
VideoFilter,
|
|
||||||
VideoMiniatureMarkupData,
|
VideoMiniatureMarkupData,
|
||||||
VideosListMarkupData
|
VideosListMarkupData
|
||||||
} from '@shared/models'
|
} from '@shared/models'
|
||||||
|
@ -193,7 +192,7 @@ export class CustomMarkupService {
|
||||||
|
|
||||||
isLive: this.buildBoolean(data.isLive),
|
isLive: this.buildBoolean(data.isLive),
|
||||||
|
|
||||||
filter: this.buildBoolean(data.onlyLocal) ? 'local' as VideoFilter : undefined
|
isLocal: this.buildBoolean(data.onlyLocal) ? true : undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
this.dynamicElementService.setModel(component, model)
|
this.dynamicElementService.setModel(component, model)
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { finalize } from 'rxjs/operators'
|
import { finalize } from 'rxjs/operators'
|
||||||
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'
|
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'
|
||||||
import { AuthService, Notifier } from '@app/core'
|
import { AuthService, Notifier } from '@app/core'
|
||||||
import { VideoFilter, VideoSortField } from '@shared/models'
|
import { VideoSortField } from '@shared/models'
|
||||||
import { Video, VideoService } from '../../shared-main'
|
import { Video, VideoService } from '../../shared-main'
|
||||||
import { MiniatureDisplayOptions } from '../../shared-video-miniature'
|
import { MiniatureDisplayOptions } from '../../shared-video-miniature'
|
||||||
import { CustomMarkupComponent } from './shared'
|
import { CustomMarkupComponent } from './shared'
|
||||||
|
@ -21,7 +21,7 @@ export class VideosListMarkupComponent implements CustomMarkupComponent, OnInit
|
||||||
@Input() languageOneOf: string[]
|
@Input() languageOneOf: string[]
|
||||||
@Input() count: number
|
@Input() count: number
|
||||||
@Input() onlyDisplayTitle: boolean
|
@Input() onlyDisplayTitle: boolean
|
||||||
@Input() filter: VideoFilter
|
@Input() isLocal: boolean
|
||||||
@Input() isLive: boolean
|
@Input() isLive: boolean
|
||||||
@Input() maxRows: number
|
@Input() maxRows: number
|
||||||
@Input() channelHandle: string
|
@Input() channelHandle: string
|
||||||
|
@ -86,7 +86,7 @@ export class VideosListMarkupComponent implements CustomMarkupComponent, OnInit
|
||||||
},
|
},
|
||||||
categoryOneOf: this.categoryOneOf,
|
categoryOneOf: this.categoryOneOf,
|
||||||
languageOneOf: this.languageOneOf,
|
languageOneOf: this.languageOneOf,
|
||||||
filter: this.filter,
|
isLocal: this.isLocal,
|
||||||
isLive: this.isLive,
|
isLive: this.isLive,
|
||||||
sort: this.sort as VideoSortField,
|
sort: this.sort as VideoSortField,
|
||||||
account: { nameWithHost: this.accountHandle },
|
account: { nameWithHost: this.accountHandle },
|
||||||
|
|
|
@ -65,8 +65,12 @@ export class Video implements VideoServerModel {
|
||||||
waitTranscoding?: boolean
|
waitTranscoding?: boolean
|
||||||
state?: VideoConstant<VideoState>
|
state?: VideoConstant<VideoState>
|
||||||
scheduledUpdate?: VideoScheduleUpdate
|
scheduledUpdate?: VideoScheduleUpdate
|
||||||
|
|
||||||
blacklisted?: boolean
|
blacklisted?: boolean
|
||||||
blockedReason?: string
|
blacklistedReason?: string
|
||||||
|
|
||||||
|
blockedOwner?: boolean
|
||||||
|
blockedServer?: boolean
|
||||||
|
|
||||||
account: {
|
account: {
|
||||||
id: number
|
id: number
|
||||||
|
@ -163,7 +167,10 @@ export class Video implements VideoServerModel {
|
||||||
if (this.state) this.state.label = peertubeTranslate(this.state.label, translations)
|
if (this.state) this.state.label = peertubeTranslate(this.state.label, translations)
|
||||||
|
|
||||||
this.blacklisted = hash.blacklisted
|
this.blacklisted = hash.blacklisted
|
||||||
this.blockedReason = hash.blacklistedReason
|
this.blacklistedReason = hash.blacklistedReason
|
||||||
|
|
||||||
|
this.blockedOwner = hash.blockedOwner
|
||||||
|
this.blockedServer = hash.blockedServer
|
||||||
|
|
||||||
this.userHistory = hash.userHistory
|
this.userHistory = hash.userHistory
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@ import {
|
||||||
VideoConstant,
|
VideoConstant,
|
||||||
VideoDetails as VideoDetailsServerModel,
|
VideoDetails as VideoDetailsServerModel,
|
||||||
VideoFileMetadata,
|
VideoFileMetadata,
|
||||||
VideoFilter,
|
VideoInclude,
|
||||||
VideoPrivacy,
|
VideoPrivacy,
|
||||||
VideoSortField,
|
VideoSortField,
|
||||||
VideoUpdate
|
VideoUpdate
|
||||||
|
@ -34,11 +34,13 @@ import { Video } from './video.model'
|
||||||
export type CommonVideoParams = {
|
export type CommonVideoParams = {
|
||||||
videoPagination?: ComponentPaginationLight
|
videoPagination?: ComponentPaginationLight
|
||||||
sort: VideoSortField | SortMeta
|
sort: VideoSortField | SortMeta
|
||||||
filter?: VideoFilter
|
include?: VideoInclude
|
||||||
|
isLocal?: boolean
|
||||||
categoryOneOf?: number[]
|
categoryOneOf?: number[]
|
||||||
languageOneOf?: string[]
|
languageOneOf?: string[]
|
||||||
isLive?: boolean
|
isLive?: boolean
|
||||||
skipCount?: boolean
|
skipCount?: boolean
|
||||||
|
|
||||||
// FIXME: remove?
|
// FIXME: remove?
|
||||||
nsfwPolicy?: NSFWPolicyType
|
nsfwPolicy?: NSFWPolicyType
|
||||||
nsfw?: BooleanBothQuery
|
nsfw?: BooleanBothQuery
|
||||||
|
@ -202,12 +204,14 @@ export class VideoService {
|
||||||
}
|
}
|
||||||
|
|
||||||
getAdminVideos (
|
getAdminVideos (
|
||||||
parameters: Omit<CommonVideoParams, 'filter'> & { pagination: RestPagination, search?: string }
|
parameters: CommonVideoParams & { pagination: RestPagination, search?: string }
|
||||||
): Observable<ResultList<Video>> {
|
): Observable<ResultList<Video>> {
|
||||||
const { pagination, search } = parameters
|
const { pagination, search } = parameters
|
||||||
|
|
||||||
|
const include = VideoInclude.BLACKLISTED | VideoInclude.BLOCKED_OWNER | VideoInclude.HIDDEN_PRIVACY | VideoInclude.NOT_PUBLISHED_STATE
|
||||||
|
|
||||||
let params = new HttpParams()
|
let params = new HttpParams()
|
||||||
params = this.buildCommonVideosParams({ params, ...parameters })
|
params = this.buildCommonVideosParams({ params, include, ...parameters })
|
||||||
|
|
||||||
params = params.set('start', pagination.start.toString())
|
params = params.set('start', pagination.start.toString())
|
||||||
.set('count', pagination.count.toString())
|
.set('count', pagination.count.toString())
|
||||||
|
@ -216,8 +220,6 @@ export class VideoService {
|
||||||
params = this.buildAdminParamsFromSearch(search, params)
|
params = this.buildAdminParamsFromSearch(search, params)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!params.has('filter')) params = params.set('filter', 'all')
|
|
||||||
|
|
||||||
return this.authHttp
|
return this.authHttp
|
||||||
.get<ResultList<Video>>(VideoService.BASE_VIDEO_URL, { params })
|
.get<ResultList<Video>>(VideoService.BASE_VIDEO_URL, { params })
|
||||||
.pipe(
|
.pipe(
|
||||||
|
@ -266,10 +268,10 @@ export class VideoService {
|
||||||
return feeds
|
return feeds
|
||||||
}
|
}
|
||||||
|
|
||||||
getVideoFeedUrls (sort: VideoSortField, filter?: VideoFilter, categoryOneOf?: number[]) {
|
getVideoFeedUrls (sort: VideoSortField, isLocal: boolean, categoryOneOf?: number[]) {
|
||||||
let params = this.restService.addRestGetParams(new HttpParams(), undefined, sort)
|
let params = this.restService.addRestGetParams(new HttpParams(), undefined, sort)
|
||||||
|
|
||||||
if (filter) params = params.set('filter', filter)
|
if (isLocal) params = params.set('isLocal', isLocal)
|
||||||
|
|
||||||
if (categoryOneOf) {
|
if (categoryOneOf) {
|
||||||
for (const c of categoryOneOf) {
|
for (const c of categoryOneOf) {
|
||||||
|
@ -425,7 +427,8 @@ export class VideoService {
|
||||||
params,
|
params,
|
||||||
videoPagination,
|
videoPagination,
|
||||||
sort,
|
sort,
|
||||||
filter,
|
isLocal,
|
||||||
|
include,
|
||||||
categoryOneOf,
|
categoryOneOf,
|
||||||
languageOneOf,
|
languageOneOf,
|
||||||
skipCount,
|
skipCount,
|
||||||
|
@ -440,9 +443,10 @@ export class VideoService {
|
||||||
|
|
||||||
let newParams = this.restService.addRestGetParams(params, pagination, sort)
|
let newParams = this.restService.addRestGetParams(params, pagination, sort)
|
||||||
|
|
||||||
if (filter) newParams = newParams.set('filter', filter)
|
|
||||||
if (skipCount) newParams = newParams.set('skipCount', skipCount + '')
|
if (skipCount) newParams = newParams.set('skipCount', skipCount + '')
|
||||||
|
|
||||||
|
if (isLocal) newParams = newParams.set('isLocal', isLocal)
|
||||||
|
if (include) newParams = newParams.set('include', include)
|
||||||
if (isLive) newParams = newParams.set('isLive', isLive)
|
if (isLive) newParams = newParams.set('isLive', isLive)
|
||||||
if (nsfw) newParams = newParams.set('nsfw', nsfw)
|
if (nsfw) newParams = newParams.set('nsfw', nsfw)
|
||||||
if (nsfwPolicy) newParams = newParams.set('nsfw', this.nsfwPolicyToParam(nsfwPolicy))
|
if (nsfwPolicy) newParams = newParams.set('nsfw', this.nsfwPolicyToParam(nsfwPolicy))
|
||||||
|
@ -454,13 +458,9 @@ export class VideoService {
|
||||||
|
|
||||||
private buildAdminParamsFromSearch (search: string, params: HttpParams) {
|
private buildAdminParamsFromSearch (search: string, params: HttpParams) {
|
||||||
const filters = this.restService.parseQueryStringFilter(search, {
|
const filters = this.restService.parseQueryStringFilter(search, {
|
||||||
filter: {
|
isLocal: {
|
||||||
prefix: 'local:',
|
prefix: 'isLocal:',
|
||||||
handler: v => {
|
isBoolean: true
|
||||||
if (v === 'true') return 'all-local'
|
|
||||||
|
|
||||||
return 'all'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -61,7 +61,7 @@ export class VideoBlockComponent extends FormReactive implements OnInit {
|
||||||
this.hide()
|
this.hide()
|
||||||
|
|
||||||
this.video.blacklisted = true
|
this.video.blacklisted = true
|
||||||
this.video.blockedReason = reason
|
this.video.blacklistedReason = reason
|
||||||
|
|
||||||
this.videoBlocked.emit()
|
this.videoBlocked.emit()
|
||||||
},
|
},
|
||||||
|
|
|
@ -188,7 +188,7 @@ export class VideoActionsDropdownComponent implements OnChanges {
|
||||||
this.notifier.success($localize`Video ${this.video.name} unblocked.`)
|
this.notifier.success($localize`Video ${this.video.name} unblocked.`)
|
||||||
|
|
||||||
this.video.blacklisted = false
|
this.video.blacklisted = false
|
||||||
this.video.blockedReason = null
|
this.video.blacklistedReason = null
|
||||||
|
|
||||||
this.videoUnblocked.emit()
|
this.videoUnblocked.emit()
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { intoArray, toBoolean } from '@app/helpers'
|
import { intoArray, toBoolean } from '@app/helpers'
|
||||||
import { AttributesOnly } from '@shared/core-utils'
|
import { AttributesOnly } from '@shared/core-utils'
|
||||||
import { BooleanBothQuery, NSFWPolicyType, VideoFilter, VideoSortField } from '@shared/models'
|
import { BooleanBothQuery, NSFWPolicyType, VideoInclude, VideoSortField } from '@shared/models'
|
||||||
|
|
||||||
type VideoFiltersKeys = {
|
type VideoFiltersKeys = {
|
||||||
[ id in keyof AttributesOnly<VideoFilters> ]: any
|
[ id in keyof AttributesOnly<VideoFilters> ]: any
|
||||||
|
@ -196,14 +196,15 @@ export class VideoFilters {
|
||||||
}
|
}
|
||||||
|
|
||||||
toVideosAPIObject () {
|
toVideosAPIObject () {
|
||||||
let filter: VideoFilter
|
let isLocal: boolean
|
||||||
|
let include: VideoInclude
|
||||||
|
|
||||||
if (this.scope === 'local' && this.allVideos) {
|
if (this.scope === 'local') {
|
||||||
filter = 'all-local'
|
isLocal = true
|
||||||
} else if (this.scope === 'federated' && this.allVideos) {
|
}
|
||||||
filter = 'all'
|
|
||||||
} else if (this.scope === 'local') {
|
if (this.allVideos) {
|
||||||
filter = 'local'
|
include = VideoInclude.NOT_PUBLISHED_STATE | VideoInclude.HIDDEN_PRIVACY
|
||||||
}
|
}
|
||||||
|
|
||||||
let isLive: boolean
|
let isLive: boolean
|
||||||
|
@ -216,7 +217,8 @@ export class VideoFilters {
|
||||||
languageOneOf: this.languageOneOf,
|
languageOneOf: this.languageOneOf,
|
||||||
categoryOneOf: this.categoryOneOf,
|
categoryOneOf: this.categoryOneOf,
|
||||||
search: this.search,
|
search: this.search,
|
||||||
filter,
|
isLocal,
|
||||||
|
include,
|
||||||
isLive
|
isLive
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,7 +55,7 @@
|
||||||
|
|
||||||
<div *ngIf="displayOptions.blacklistInfo && video.blacklisted" class="video-info-blocked">
|
<div *ngIf="displayOptions.blacklistInfo && video.blacklisted" class="video-info-blocked">
|
||||||
<span class="blocked-label" i18n>Blocked</span>
|
<span class="blocked-label" i18n>Blocked</span>
|
||||||
<span class="blocked-reason" *ngIf="video.blockedReason">{{ video.blockedReason }}</span>
|
<span class="blocked-reason" *ngIf="video.blacklistedReason">{{ video.blacklistedReason }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div i18n *ngIf="displayOptions.nsfw && video.nsfw" class="video-info-nsfw">
|
<div i18n *ngIf="displayOptions.nsfw && video.nsfw" class="video-info-nsfw">
|
||||||
|
|
|
@ -2,6 +2,7 @@ import express from 'express'
|
||||||
import { pickCommonVideoQuery } from '@server/helpers/query'
|
import { pickCommonVideoQuery } from '@server/helpers/query'
|
||||||
import { ActorFollowModel } from '@server/models/actor/actor-follow'
|
import { ActorFollowModel } from '@server/models/actor/actor-follow'
|
||||||
import { getServerActor } from '@server/models/application/application'
|
import { getServerActor } from '@server/models/application/application'
|
||||||
|
import { guessAdditionalAttributesFromQuery } from '@server/models/video/formatter/video-format-utils'
|
||||||
import { buildNSFWFilter, getCountVideos, isUserAbleToSearchRemoteURI } from '../../helpers/express-utils'
|
import { buildNSFWFilter, getCountVideos, isUserAbleToSearchRemoteURI } from '../../helpers/express-utils'
|
||||||
import { getFormattedObjects } from '../../helpers/utils'
|
import { getFormattedObjects } from '../../helpers/utils'
|
||||||
import { JobQueue } from '../../lib/job-queue'
|
import { JobQueue } from '../../lib/job-queue'
|
||||||
|
@ -169,17 +170,24 @@ async function listAccountPlaylists (req: express.Request, res: express.Response
|
||||||
}
|
}
|
||||||
|
|
||||||
async function listAccountVideos (req: express.Request, res: express.Response) {
|
async function listAccountVideos (req: express.Request, res: express.Response) {
|
||||||
|
const serverActor = await getServerActor()
|
||||||
|
|
||||||
const account = res.locals.account
|
const account = res.locals.account
|
||||||
const followerActorId = isUserAbleToSearchRemoteURI(res) ? null : undefined
|
|
||||||
|
const displayOnlyForFollower = isUserAbleToSearchRemoteURI(res)
|
||||||
|
? null
|
||||||
|
: {
|
||||||
|
actorId: serverActor.id,
|
||||||
|
orLocalVideos: true
|
||||||
|
}
|
||||||
|
|
||||||
const countVideos = getCountVideos(req)
|
const countVideos = getCountVideos(req)
|
||||||
const query = pickCommonVideoQuery(req.query)
|
const query = pickCommonVideoQuery(req.query)
|
||||||
|
|
||||||
const apiOptions = await Hooks.wrapObject({
|
const apiOptions = await Hooks.wrapObject({
|
||||||
...query,
|
...query,
|
||||||
|
|
||||||
followerActorId,
|
displayOnlyForFollower,
|
||||||
search: req.query.search,
|
|
||||||
includeLocalVideos: true,
|
|
||||||
nsfw: buildNSFWFilter(res, query.nsfw),
|
nsfw: buildNSFWFilter(res, query.nsfw),
|
||||||
withFiles: false,
|
withFiles: false,
|
||||||
accountId: account.id,
|
accountId: account.id,
|
||||||
|
@ -193,7 +201,7 @@ async function listAccountVideos (req: express.Request, res: express.Response) {
|
||||||
'filter:api.accounts.videos.list.result'
|
'filter:api.accounts.videos.list.result'
|
||||||
)
|
)
|
||||||
|
|
||||||
return res.json(getFormattedObjects(resultList.data, resultList.total))
|
return res.json(getFormattedObjects(resultList.data, resultList.total, guessAdditionalAttributesFromQuery(query)))
|
||||||
}
|
}
|
||||||
|
|
||||||
async function listAccountRatings (req: express.Request, res: express.Response) {
|
async function listAccountRatings (req: express.Request, res: express.Response) {
|
||||||
|
|
|
@ -8,6 +8,7 @@ import { buildNSFWFilter } from '../../helpers/express-utils'
|
||||||
import { MEMOIZE_TTL, OVERVIEWS } from '../../initializers/constants'
|
import { MEMOIZE_TTL, OVERVIEWS } from '../../initializers/constants'
|
||||||
import { asyncMiddleware, optionalAuthenticate, videosOverviewValidator } from '../../middlewares'
|
import { asyncMiddleware, optionalAuthenticate, videosOverviewValidator } from '../../middlewares'
|
||||||
import { TagModel } from '../../models/video/tag'
|
import { TagModel } from '../../models/video/tag'
|
||||||
|
import { getServerActor } from '@server/models/application/application'
|
||||||
|
|
||||||
const overviewsRouter = express.Router()
|
const overviewsRouter = express.Router()
|
||||||
|
|
||||||
|
@ -109,11 +110,16 @@ async function getVideos (
|
||||||
res: express.Response,
|
res: express.Response,
|
||||||
where: { videoChannelId?: number, tagsOneOf?: string[], categoryOneOf?: number[] }
|
where: { videoChannelId?: number, tagsOneOf?: string[], categoryOneOf?: number[] }
|
||||||
) {
|
) {
|
||||||
|
const serverActor = await getServerActor()
|
||||||
|
|
||||||
const query = await Hooks.wrapObject({
|
const query = await Hooks.wrapObject({
|
||||||
start: 0,
|
start: 0,
|
||||||
count: 12,
|
count: 12,
|
||||||
sort: '-createdAt',
|
sort: '-createdAt',
|
||||||
includeLocalVideos: true,
|
displayOnlyForFollower: {
|
||||||
|
actorId: serverActor.id,
|
||||||
|
orLocalVideos: true
|
||||||
|
},
|
||||||
nsfw: buildNSFWFilter(res),
|
nsfw: buildNSFWFilter(res),
|
||||||
user: res.locals.oauth ? res.locals.oauth.token.User : undefined,
|
user: res.locals.oauth ? res.locals.oauth.token.User : undefined,
|
||||||
withFiles: false,
|
withFiles: false,
|
||||||
|
|
|
@ -7,6 +7,8 @@ import { WEBSERVER } from '@server/initializers/constants'
|
||||||
import { getOrCreateAPVideo } from '@server/lib/activitypub/videos'
|
import { getOrCreateAPVideo } from '@server/lib/activitypub/videos'
|
||||||
import { Hooks } from '@server/lib/plugins/hooks'
|
import { Hooks } from '@server/lib/plugins/hooks'
|
||||||
import { buildMutedForSearchIndex, isSearchIndexSearch, isURISearch } from '@server/lib/search'
|
import { buildMutedForSearchIndex, isSearchIndexSearch, isURISearch } from '@server/lib/search'
|
||||||
|
import { getServerActor } from '@server/models/application/application'
|
||||||
|
import { guessAdditionalAttributesFromQuery } from '@server/models/video/formatter/video-format-utils'
|
||||||
import { HttpStatusCode, ResultList, Video } from '@shared/models'
|
import { HttpStatusCode, ResultList, Video } from '@shared/models'
|
||||||
import { VideosSearchQueryAfterSanitize } from '../../../../shared/models/search'
|
import { VideosSearchQueryAfterSanitize } from '../../../../shared/models/search'
|
||||||
import { buildNSFWFilter, isUserAbleToSearchRemoteURI } from '../../../helpers/express-utils'
|
import { buildNSFWFilter, isUserAbleToSearchRemoteURI } from '../../../helpers/express-utils'
|
||||||
|
@ -100,11 +102,15 @@ async function searchVideosIndex (query: VideosSearchQueryAfterSanitize, res: ex
|
||||||
}
|
}
|
||||||
|
|
||||||
async function searchVideosDB (query: VideosSearchQueryAfterSanitize, res: express.Response) {
|
async function searchVideosDB (query: VideosSearchQueryAfterSanitize, res: express.Response) {
|
||||||
|
const serverActor = await getServerActor()
|
||||||
|
|
||||||
const apiOptions = await Hooks.wrapObject({
|
const apiOptions = await Hooks.wrapObject({
|
||||||
...query,
|
...query,
|
||||||
|
|
||||||
includeLocalVideos: true,
|
displayOnlyForFollower: {
|
||||||
filter: query.filter,
|
actorId: serverActor.id,
|
||||||
|
orLocalVideos: true
|
||||||
|
},
|
||||||
|
|
||||||
nsfw: buildNSFWFilter(res, query.nsfw),
|
nsfw: buildNSFWFilter(res, query.nsfw),
|
||||||
user: res.locals.oauth
|
user: res.locals.oauth
|
||||||
|
@ -118,7 +124,7 @@ async function searchVideosDB (query: VideosSearchQueryAfterSanitize, res: expre
|
||||||
'filter:api.search.videos.local.list.result'
|
'filter:api.search.videos.local.list.result'
|
||||||
)
|
)
|
||||||
|
|
||||||
return res.json(getFormattedObjects(resultList.data, resultList.total))
|
return res.json(getFormattedObjects(resultList.data, resultList.total, guessAdditionalAttributesFromQuery(query)))
|
||||||
}
|
}
|
||||||
|
|
||||||
async function searchVideoURI (url: string, res: express.Response) {
|
async function searchVideoURI (url: string, res: express.Response) {
|
||||||
|
|
|
@ -2,6 +2,7 @@ import 'multer'
|
||||||
import express from 'express'
|
import express from 'express'
|
||||||
import { pickCommonVideoQuery } from '@server/helpers/query'
|
import { pickCommonVideoQuery } from '@server/helpers/query'
|
||||||
import { sendUndoFollow } from '@server/lib/activitypub/send'
|
import { sendUndoFollow } from '@server/lib/activitypub/send'
|
||||||
|
import { guessAdditionalAttributesFromQuery } from '@server/models/video/formatter/video-format-utils'
|
||||||
import { VideoChannelModel } from '@server/models/video/video-channel'
|
import { VideoChannelModel } from '@server/models/video/video-channel'
|
||||||
import { HttpStatusCode } from '../../../../shared/models/http/http-error-codes'
|
import { HttpStatusCode } from '../../../../shared/models/http/http-error-codes'
|
||||||
import { buildNSFWFilter, getCountVideos } from '../../../helpers/express-utils'
|
import { buildNSFWFilter, getCountVideos } from '../../../helpers/express-utils'
|
||||||
|
@ -175,13 +176,15 @@ async function getUserSubscriptionVideos (req: express.Request, res: express.Res
|
||||||
const resultList = await VideoModel.listForApi({
|
const resultList = await VideoModel.listForApi({
|
||||||
...query,
|
...query,
|
||||||
|
|
||||||
includeLocalVideos: false,
|
displayOnlyForFollower: {
|
||||||
|
actorId: user.Account.Actor.id,
|
||||||
|
orLocalVideos: false
|
||||||
|
},
|
||||||
nsfw: buildNSFWFilter(res, query.nsfw),
|
nsfw: buildNSFWFilter(res, query.nsfw),
|
||||||
withFiles: false,
|
withFiles: false,
|
||||||
followerActorId: user.Account.Actor.id,
|
|
||||||
user,
|
user,
|
||||||
countVideos
|
countVideos
|
||||||
})
|
})
|
||||||
|
|
||||||
return res.json(getFormattedObjects(resultList.data, resultList.total))
|
return res.json(getFormattedObjects(resultList.data, resultList.total, guessAdditionalAttributesFromQuery(query)))
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { pickCommonVideoQuery } from '@server/helpers/query'
|
||||||
import { Hooks } from '@server/lib/plugins/hooks'
|
import { Hooks } from '@server/lib/plugins/hooks'
|
||||||
import { ActorFollowModel } from '@server/models/actor/actor-follow'
|
import { ActorFollowModel } from '@server/models/actor/actor-follow'
|
||||||
import { getServerActor } from '@server/models/application/application'
|
import { getServerActor } from '@server/models/application/application'
|
||||||
|
import { guessAdditionalAttributesFromQuery } from '@server/models/video/formatter/video-format-utils'
|
||||||
import { MChannelBannerAccountDefault } from '@server/types/models'
|
import { MChannelBannerAccountDefault } from '@server/types/models'
|
||||||
import { ActorImageType, VideoChannelCreate, VideoChannelUpdate } from '../../../shared'
|
import { ActorImageType, VideoChannelCreate, VideoChannelUpdate } from '../../../shared'
|
||||||
import { HttpStatusCode } from '../../../shared/models/http/http-error-codes'
|
import { HttpStatusCode } from '../../../shared/models/http/http-error-codes'
|
||||||
|
@ -327,16 +328,24 @@ async function listVideoChannelPlaylists (req: express.Request, res: express.Res
|
||||||
}
|
}
|
||||||
|
|
||||||
async function listVideoChannelVideos (req: express.Request, res: express.Response) {
|
async function listVideoChannelVideos (req: express.Request, res: express.Response) {
|
||||||
|
const serverActor = await getServerActor()
|
||||||
|
|
||||||
const videoChannelInstance = res.locals.videoChannel
|
const videoChannelInstance = res.locals.videoChannel
|
||||||
const followerActorId = isUserAbleToSearchRemoteURI(res) ? null : undefined
|
|
||||||
|
const displayOnlyForFollower = isUserAbleToSearchRemoteURI(res)
|
||||||
|
? null
|
||||||
|
: {
|
||||||
|
actorId: serverActor.id,
|
||||||
|
orLocalVideos: true
|
||||||
|
}
|
||||||
|
|
||||||
const countVideos = getCountVideos(req)
|
const countVideos = getCountVideos(req)
|
||||||
const query = pickCommonVideoQuery(req.query)
|
const query = pickCommonVideoQuery(req.query)
|
||||||
|
|
||||||
const apiOptions = await Hooks.wrapObject({
|
const apiOptions = await Hooks.wrapObject({
|
||||||
...query,
|
...query,
|
||||||
|
|
||||||
followerActorId,
|
displayOnlyForFollower,
|
||||||
includeLocalVideos: true,
|
|
||||||
nsfw: buildNSFWFilter(res, query.nsfw),
|
nsfw: buildNSFWFilter(res, query.nsfw),
|
||||||
withFiles: false,
|
withFiles: false,
|
||||||
videoChannelId: videoChannelInstance.id,
|
videoChannelId: videoChannelInstance.id,
|
||||||
|
@ -350,7 +359,7 @@ async function listVideoChannelVideos (req: express.Request, res: express.Respon
|
||||||
'filter:api.video-channels.videos.list.result'
|
'filter:api.video-channels.videos.list.result'
|
||||||
)
|
)
|
||||||
|
|
||||||
return res.json(getFormattedObjects(resultList.data, resultList.total))
|
return res.json(getFormattedObjects(resultList.data, resultList.total, guessAdditionalAttributesFromQuery(query)))
|
||||||
}
|
}
|
||||||
|
|
||||||
async function listVideoChannelFollowers (req: express.Request, res: express.Response) {
|
async function listVideoChannelFollowers (req: express.Request, res: express.Response) {
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { doJSONRequest } from '@server/helpers/requests'
|
||||||
import { LiveManager } from '@server/lib/live'
|
import { LiveManager } from '@server/lib/live'
|
||||||
import { openapiOperationDoc } from '@server/middlewares/doc'
|
import { openapiOperationDoc } from '@server/middlewares/doc'
|
||||||
import { getServerActor } from '@server/models/application/application'
|
import { getServerActor } from '@server/models/application/application'
|
||||||
|
import { guessAdditionalAttributesFromQuery } from '@server/models/video/formatter/video-format-utils'
|
||||||
import { MVideoAccountLight } from '@server/types/models'
|
import { MVideoAccountLight } from '@server/types/models'
|
||||||
import { HttpStatusCode } from '../../../../shared/models'
|
import { HttpStatusCode } from '../../../../shared/models'
|
||||||
import { auditLoggerFactory, getAuditIdFromRes, VideoAuditView } from '../../../helpers/audit-logger'
|
import { auditLoggerFactory, getAuditIdFromRes, VideoAuditView } from '../../../helpers/audit-logger'
|
||||||
|
@ -211,13 +212,18 @@ async function getVideoFileMetadata (req: express.Request, res: express.Response
|
||||||
}
|
}
|
||||||
|
|
||||||
async function listVideos (req: express.Request, res: express.Response) {
|
async function listVideos (req: express.Request, res: express.Response) {
|
||||||
|
const serverActor = await getServerActor()
|
||||||
|
|
||||||
const query = pickCommonVideoQuery(req.query)
|
const query = pickCommonVideoQuery(req.query)
|
||||||
const countVideos = getCountVideos(req)
|
const countVideos = getCountVideos(req)
|
||||||
|
|
||||||
const apiOptions = await Hooks.wrapObject({
|
const apiOptions = await Hooks.wrapObject({
|
||||||
...query,
|
...query,
|
||||||
|
|
||||||
includeLocalVideos: true,
|
displayOnlyForFollower: {
|
||||||
|
actorId: serverActor.id,
|
||||||
|
orLocalVideos: true
|
||||||
|
},
|
||||||
nsfw: buildNSFWFilter(res, query.nsfw),
|
nsfw: buildNSFWFilter(res, query.nsfw),
|
||||||
withFiles: false,
|
withFiles: false,
|
||||||
user: res.locals.oauth ? res.locals.oauth.token.User : undefined,
|
user: res.locals.oauth ? res.locals.oauth.token.User : undefined,
|
||||||
|
@ -230,7 +236,7 @@ async function listVideos (req: express.Request, res: express.Response) {
|
||||||
'filter:api.videos.list.result'
|
'filter:api.videos.list.result'
|
||||||
)
|
)
|
||||||
|
|
||||||
return res.json(getFormattedObjects(resultList.data, resultList.total))
|
return res.json(getFormattedObjects(resultList.data, resultList.total, guessAdditionalAttributesFromQuery(query)))
|
||||||
}
|
}
|
||||||
|
|
||||||
async function removeVideo (_req: express.Request, res: express.Response) {
|
async function removeVideo (_req: express.Request, res: express.Response) {
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { getServerActor } from '@server/models/application/application'
|
||||||
import express from 'express'
|
import express from 'express'
|
||||||
import { truncate } from 'lodash'
|
import { truncate } from 'lodash'
|
||||||
import { SitemapStream, streamToPromise } from 'sitemap'
|
import { SitemapStream, streamToPromise } from 'sitemap'
|
||||||
|
@ -63,13 +64,18 @@ async function getSitemapAccountUrls () {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getSitemapLocalVideoUrls () {
|
async function getSitemapLocalVideoUrls () {
|
||||||
|
const serverActor = await getServerActor()
|
||||||
|
|
||||||
const { data } = await VideoModel.listForApi({
|
const { data } = await VideoModel.listForApi({
|
||||||
start: 0,
|
start: 0,
|
||||||
count: undefined,
|
count: undefined,
|
||||||
sort: 'createdAt',
|
sort: 'createdAt',
|
||||||
includeLocalVideos: true,
|
displayOnlyForFollower: {
|
||||||
|
actorId: serverActor.id,
|
||||||
|
orLocalVideos: true
|
||||||
|
},
|
||||||
|
isLocal: true,
|
||||||
nsfw: buildNSFWFilter(),
|
nsfw: buildNSFWFilter(),
|
||||||
filter: 'local',
|
|
||||||
withFiles: false,
|
withFiles: false,
|
||||||
countVideos: false
|
countVideos: false
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import express from 'express'
|
import express from 'express'
|
||||||
import Feed from 'pfeed'
|
import Feed from 'pfeed'
|
||||||
|
import { getServerActor } from '@server/models/application/application'
|
||||||
import { getCategoryLabel } from '@server/models/video/formatter/video-format-utils'
|
import { getCategoryLabel } from '@server/models/video/formatter/video-format-utils'
|
||||||
import { VideoFilter } from '../../shared/models/videos/video-query.type'
|
|
||||||
import { buildNSFWFilter } from '../helpers/express-utils'
|
import { buildNSFWFilter } from '../helpers/express-utils'
|
||||||
import { CONFIG } from '../initializers/config'
|
import { CONFIG } from '../initializers/config'
|
||||||
import { FEEDS, PREVIEWS_SIZE, ROUTE_CACHE_LIFETIME, WEBSERVER } from '../initializers/constants'
|
import { FEEDS, PREVIEWS_SIZE, ROUTE_CACHE_LIFETIME, WEBSERVER } from '../initializers/constants'
|
||||||
|
@ -160,13 +160,18 @@ async function generateVideoFeed (req: express.Request, res: express.Response) {
|
||||||
videoChannelId: videoChannel ? videoChannel.id : null
|
videoChannelId: videoChannel ? videoChannel.id : null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const server = await getServerActor()
|
||||||
const { data } = await VideoModel.listForApi({
|
const { data } = await VideoModel.listForApi({
|
||||||
start,
|
start,
|
||||||
count: FEEDS.COUNT,
|
count: FEEDS.COUNT,
|
||||||
sort: req.query.sort,
|
sort: req.query.sort,
|
||||||
includeLocalVideos: true,
|
displayOnlyForFollower: {
|
||||||
|
actorId: server.id,
|
||||||
|
orLocalVideos: true
|
||||||
|
},
|
||||||
nsfw,
|
nsfw,
|
||||||
filter: req.query.filter as VideoFilter,
|
isLocal: req.query.isLocal,
|
||||||
|
include: req.query.include,
|
||||||
withFiles: true,
|
withFiles: true,
|
||||||
countVideos: false,
|
countVideos: false,
|
||||||
...options
|
...options
|
||||||
|
@ -196,14 +201,18 @@ async function generateVideoFeedForSubscriptions (req: express.Request, res: exp
|
||||||
start,
|
start,
|
||||||
count: FEEDS.COUNT,
|
count: FEEDS.COUNT,
|
||||||
sort: req.query.sort,
|
sort: req.query.sort,
|
||||||
includeLocalVideos: false,
|
|
||||||
nsfw,
|
nsfw,
|
||||||
filter: req.query.filter as VideoFilter,
|
|
||||||
|
isLocal: req.query.isLocal,
|
||||||
|
include: req.query.include,
|
||||||
|
|
||||||
withFiles: true,
|
withFiles: true,
|
||||||
countVideos: false,
|
countVideos: false,
|
||||||
|
|
||||||
followerActorId: res.locals.user.Account.Actor.id,
|
displayOnlyForFollower: {
|
||||||
|
actorId: res.locals.user.Account.Actor.id,
|
||||||
|
orLocalVideos: false
|
||||||
|
},
|
||||||
user: res.locals.user
|
user: res.locals.user
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { UploadFilesForCheck } from 'express'
|
||||||
import { values } from 'lodash'
|
import { values } from 'lodash'
|
||||||
import magnetUtil from 'magnet-uri'
|
import magnetUtil from 'magnet-uri'
|
||||||
import validator from 'validator'
|
import validator from 'validator'
|
||||||
|
import { VideoInclude } from '@shared/models'
|
||||||
import { VideoFilter, VideoPrivacy, VideoRateType } from '../../../shared'
|
import { VideoFilter, VideoPrivacy, VideoRateType } from '../../../shared'
|
||||||
import {
|
import {
|
||||||
CONSTRAINTS_FIELDS,
|
CONSTRAINTS_FIELDS,
|
||||||
|
@ -21,6 +22,10 @@ function isVideoFilterValid (filter: VideoFilter) {
|
||||||
return filter === 'local' || filter === 'all-local' || filter === 'all'
|
return filter === 'local' || filter === 'all-local' || filter === 'all'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isVideoIncludeValid (include: VideoInclude) {
|
||||||
|
return exists(include) && validator.isInt('' + include)
|
||||||
|
}
|
||||||
|
|
||||||
function isVideoCategoryValid (value: any) {
|
function isVideoCategoryValid (value: any) {
|
||||||
return value === null || VIDEO_CATEGORIES[value] !== undefined
|
return value === null || VIDEO_CATEGORIES[value] !== undefined
|
||||||
}
|
}
|
||||||
|
@ -146,6 +151,7 @@ export {
|
||||||
isVideoOriginallyPublishedAtValid,
|
isVideoOriginallyPublishedAtValid,
|
||||||
isVideoMagnetUriValid,
|
isVideoMagnetUriValid,
|
||||||
isVideoStateValid,
|
isVideoStateValid,
|
||||||
|
isVideoIncludeValid,
|
||||||
isVideoViewsValid,
|
isVideoViewsValid,
|
||||||
isVideoRatingTypeValid,
|
isVideoRatingTypeValid,
|
||||||
isVideoFileExtnameValid,
|
isVideoFileExtnameValid,
|
||||||
|
|
|
@ -18,8 +18,10 @@ function pickCommonVideoQuery (query: VideosCommonQueryAfterSanitize) {
|
||||||
'languageOneOf',
|
'languageOneOf',
|
||||||
'tagsOneOf',
|
'tagsOneOf',
|
||||||
'tagsAllOf',
|
'tagsAllOf',
|
||||||
'filter',
|
'isLocal',
|
||||||
'skipCount'
|
'include',
|
||||||
|
'skipCount',
|
||||||
|
'search'
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,7 +31,6 @@ function pickSearchVideoQuery (query: VideosSearchQueryAfterSanitize) {
|
||||||
|
|
||||||
...pick(query, [
|
...pick(query, [
|
||||||
'searchTarget',
|
'searchTarget',
|
||||||
'search',
|
|
||||||
'host',
|
'host',
|
||||||
'startDate',
|
'startDate',
|
||||||
'endDate',
|
'endDate',
|
||||||
|
|
|
@ -7,6 +7,7 @@ import { isAbleToUploadVideo } from '@server/lib/user'
|
||||||
import { getServerActor } from '@server/models/application/application'
|
import { getServerActor } from '@server/models/application/application'
|
||||||
import { ExpressPromiseHandler } from '@server/types/express'
|
import { ExpressPromiseHandler } from '@server/types/express'
|
||||||
import { MUserAccountId, MVideoFullLight } from '@server/types/models'
|
import { MUserAccountId, MVideoFullLight } from '@server/types/models'
|
||||||
|
import { VideoInclude } from '@shared/models'
|
||||||
import { ServerErrorCode, UserRight, VideoPrivacy } from '../../../../shared'
|
import { ServerErrorCode, UserRight, VideoPrivacy } from '../../../../shared'
|
||||||
import { HttpStatusCode } from '../../../../shared/models/http/http-error-codes'
|
import { HttpStatusCode } from '../../../../shared/models/http/http-error-codes'
|
||||||
import {
|
import {
|
||||||
|
@ -30,6 +31,7 @@ import {
|
||||||
isVideoFileSizeValid,
|
isVideoFileSizeValid,
|
||||||
isVideoFilterValid,
|
isVideoFilterValid,
|
||||||
isVideoImage,
|
isVideoImage,
|
||||||
|
isVideoIncludeValid,
|
||||||
isVideoLanguageValid,
|
isVideoLanguageValid,
|
||||||
isVideoLicenceValid,
|
isVideoLicenceValid,
|
||||||
isVideoNameValid,
|
isVideoNameValid,
|
||||||
|
@ -487,6 +489,13 @@ const commonVideosFiltersValidator = [
|
||||||
query('filter')
|
query('filter')
|
||||||
.optional()
|
.optional()
|
||||||
.custom(isVideoFilterValid).withMessage('Should have a valid filter attribute'),
|
.custom(isVideoFilterValid).withMessage('Should have a valid filter attribute'),
|
||||||
|
query('include')
|
||||||
|
.optional()
|
||||||
|
.custom(isVideoIncludeValid).withMessage('Should have a valid include attribute'),
|
||||||
|
query('isLocal')
|
||||||
|
.optional()
|
||||||
|
.customSanitizer(toBooleanOrNull)
|
||||||
|
.custom(isBooleanValid).withMessage('Should have a valid local boolean'),
|
||||||
query('skipCount')
|
query('skipCount')
|
||||||
.optional()
|
.optional()
|
||||||
.customSanitizer(toBooleanOrNull)
|
.customSanitizer(toBooleanOrNull)
|
||||||
|
@ -500,11 +509,23 @@ const commonVideosFiltersValidator = [
|
||||||
|
|
||||||
if (areValidationErrors(req, res)) return
|
if (areValidationErrors(req, res)) return
|
||||||
|
|
||||||
const user = res.locals.oauth ? res.locals.oauth.token.User : undefined
|
// FIXME: deprecated in 4.0, to remove
|
||||||
if (
|
{
|
||||||
(req.query.filter === 'all-local' || req.query.filter === 'all') &&
|
if (req.query.filter === 'all-local') {
|
||||||
(!user || user.hasRight(UserRight.SEE_ALL_VIDEOS) === false)
|
req.query.include = VideoInclude.NOT_PUBLISHED_STATE | VideoInclude.HIDDEN_PRIVACY
|
||||||
) {
|
req.query.isLocal = true
|
||||||
|
} else if (req.query.filter === 'all') {
|
||||||
|
req.query.include = VideoInclude.NOT_PUBLISHED_STATE | VideoInclude.HIDDEN_PRIVACY
|
||||||
|
} else if (req.query.filter === 'local') {
|
||||||
|
req.query.isLocal = true
|
||||||
|
}
|
||||||
|
|
||||||
|
req.query.filter = undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = res.locals.oauth?.token.User
|
||||||
|
|
||||||
|
if (req.query.include && (!user || user.hasRight(UserRight.SEE_ALL_VIDEOS) !== true)) {
|
||||||
res.fail({
|
res.fail({
|
||||||
status: HttpStatusCode.UNAUTHORIZED_401,
|
status: HttpStatusCode.UNAUTHORIZED_401,
|
||||||
message: 'You are not allowed to see all local videos.'
|
message: 'You are not allowed to see all local videos.'
|
||||||
|
|
|
@ -228,10 +228,10 @@ export class AccountModel extends Model<Partial<AttributesOnly<AccountModel>>> {
|
||||||
name: 'targetAccountId',
|
name: 'targetAccountId',
|
||||||
allowNull: false
|
allowNull: false
|
||||||
},
|
},
|
||||||
as: 'BlockedAccounts',
|
as: 'BlockedBy',
|
||||||
onDelete: 'CASCADE'
|
onDelete: 'CASCADE'
|
||||||
})
|
})
|
||||||
BlockedAccounts: AccountBlocklistModel[]
|
BlockedBy: AccountBlocklistModel[]
|
||||||
|
|
||||||
@BeforeDestroy
|
@BeforeDestroy
|
||||||
static async sendDeleteIfOwned (instance: AccountModel, options) {
|
static async sendDeleteIfOwned (instance: AccountModel, options) {
|
||||||
|
@ -457,6 +457,6 @@ export class AccountModel extends Model<Partial<AttributesOnly<AccountModel>>> {
|
||||||
}
|
}
|
||||||
|
|
||||||
isBlocked () {
|
isBlocked () {
|
||||||
return this.BlockedAccounts && this.BlockedAccounts.length !== 0
|
return this.BlockedBy && this.BlockedBy.length !== 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,7 +50,7 @@ export class ServerModel extends Model<Partial<AttributesOnly<ServerModel>>> {
|
||||||
},
|
},
|
||||||
onDelete: 'CASCADE'
|
onDelete: 'CASCADE'
|
||||||
})
|
})
|
||||||
BlockedByAccounts: ServerBlocklistModel[]
|
BlockedBy: ServerBlocklistModel[]
|
||||||
|
|
||||||
static load (id: number, transaction?: Transaction): Promise<MServer> {
|
static load (id: number, transaction?: Transaction): Promise<MServer> {
|
||||||
const query = {
|
const query = {
|
||||||
|
@ -81,7 +81,7 @@ export class ServerModel extends Model<Partial<AttributesOnly<ServerModel>>> {
|
||||||
}
|
}
|
||||||
|
|
||||||
isBlocked () {
|
isBlocked () {
|
||||||
return this.BlockedByAccounts && this.BlockedByAccounts.length !== 0
|
return this.BlockedBy && this.BlockedBy.length !== 0
|
||||||
}
|
}
|
||||||
|
|
||||||
toFormattedJSON (this: MServerFormattable) {
|
toFormattedJSON (this: MServerFormattable) {
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { MUserAccountId, MUserId } from '@server/types/models'
|
||||||
import { AttributesOnly } from '@shared/core-utils'
|
import { AttributesOnly } from '@shared/core-utils'
|
||||||
import { VideoModel } from '../video/video'
|
import { VideoModel } from '../video/video'
|
||||||
import { UserModel } from './user'
|
import { UserModel } from './user'
|
||||||
|
import { getServerActor } from '../application/application'
|
||||||
|
|
||||||
@Table({
|
@Table({
|
||||||
tableName: 'userVideoHistory',
|
tableName: 'userVideoHistory',
|
||||||
|
@ -56,14 +57,19 @@ export class UserVideoHistoryModel extends Model<Partial<AttributesOnly<UserVide
|
||||||
})
|
})
|
||||||
User: UserModel
|
User: UserModel
|
||||||
|
|
||||||
static listForApi (user: MUserAccountId, start: number, count: number, search?: string) {
|
static async listForApi (user: MUserAccountId, start: number, count: number, search?: string) {
|
||||||
|
const serverActor = await getServerActor()
|
||||||
|
|
||||||
return VideoModel.listForApi({
|
return VideoModel.listForApi({
|
||||||
start,
|
start,
|
||||||
count,
|
count,
|
||||||
search,
|
search,
|
||||||
sort: '-"userVideoHistory"."updatedAt"',
|
sort: '-"userVideoHistory"."updatedAt"',
|
||||||
nsfw: null, // All
|
nsfw: null, // All
|
||||||
includeLocalVideos: true,
|
displayOnlyForFollower: {
|
||||||
|
actorId: serverActor.id,
|
||||||
|
orLocalVideos: true
|
||||||
|
},
|
||||||
withFiles: false,
|
withFiles: false,
|
||||||
user,
|
user,
|
||||||
historyOfUser: user
|
historyOfUser: user
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
import { uuidToShort } from '@server/helpers/uuid'
|
import { uuidToShort } from '@server/helpers/uuid'
|
||||||
import { generateMagnetUri } from '@server/helpers/webtorrent'
|
import { generateMagnetUri } from '@server/helpers/webtorrent'
|
||||||
import { getLocalVideoFileMetadataUrl } from '@server/lib/video-urls'
|
import { getLocalVideoFileMetadataUrl } from '@server/lib/video-urls'
|
||||||
|
import { VideosCommonQueryAfterSanitize } from '@shared/models'
|
||||||
import { VideoFile } from '@shared/models/videos/video-file.model'
|
import { VideoFile } from '@shared/models/videos/video-file.model'
|
||||||
import { ActivityTagObject, ActivityUrlObject, VideoObject } from '../../../../shared/models/activitypub/objects'
|
import { ActivityTagObject, ActivityUrlObject, VideoObject } from '../../../../shared/models/activitypub/objects'
|
||||||
import { Video, VideoDetails } from '../../../../shared/models/videos'
|
import { Video, VideoDetails, VideoInclude } from '../../../../shared/models/videos'
|
||||||
import { VideoStreamingPlaylist } from '../../../../shared/models/videos/video-streaming-playlist.model'
|
import { VideoStreamingPlaylist } from '../../../../shared/models/videos/video-streaming-playlist.model'
|
||||||
import { isArray } from '../../../helpers/custom-validators/misc'
|
import { isArray } from '../../../helpers/custom-validators/misc'
|
||||||
import {
|
import {
|
||||||
|
@ -22,6 +23,7 @@ import {
|
||||||
getLocalVideoSharesActivityPubUrl
|
getLocalVideoSharesActivityPubUrl
|
||||||
} from '../../../lib/activitypub/url'
|
} from '../../../lib/activitypub/url'
|
||||||
import {
|
import {
|
||||||
|
MServer,
|
||||||
MStreamingPlaylistRedundanciesOpt,
|
MStreamingPlaylistRedundanciesOpt,
|
||||||
MVideo,
|
MVideo,
|
||||||
MVideoAP,
|
MVideoAP,
|
||||||
|
@ -34,15 +36,31 @@ import { VideoCaptionModel } from '../video-caption'
|
||||||
|
|
||||||
export type VideoFormattingJSONOptions = {
|
export type VideoFormattingJSONOptions = {
|
||||||
completeDescription?: boolean
|
completeDescription?: boolean
|
||||||
additionalAttributes: {
|
|
||||||
|
additionalAttributes?: {
|
||||||
state?: boolean
|
state?: boolean
|
||||||
waitTranscoding?: boolean
|
waitTranscoding?: boolean
|
||||||
scheduledUpdate?: boolean
|
scheduledUpdate?: boolean
|
||||||
blacklistInfo?: boolean
|
blacklistInfo?: boolean
|
||||||
|
blockedOwner?: boolean
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function videoModelToFormattedJSON (video: MVideoFormattable, options?: VideoFormattingJSONOptions): Video {
|
function guessAdditionalAttributesFromQuery (query: VideosCommonQueryAfterSanitize): VideoFormattingJSONOptions {
|
||||||
|
if (!query || !query.include) return {}
|
||||||
|
|
||||||
|
return {
|
||||||
|
additionalAttributes: {
|
||||||
|
state: !!(query.include & VideoInclude.NOT_PUBLISHED_STATE),
|
||||||
|
waitTranscoding: !!(query.include & VideoInclude.NOT_PUBLISHED_STATE),
|
||||||
|
scheduledUpdate: !!(query.include & VideoInclude.NOT_PUBLISHED_STATE),
|
||||||
|
blacklistInfo: !!(query.include & VideoInclude.BLACKLISTED),
|
||||||
|
blockedOwner: !!(query.include & VideoInclude.BLOCKED_OWNER)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function videoModelToFormattedJSON (video: MVideoFormattable, options: VideoFormattingJSONOptions = {}): Video {
|
||||||
const userHistory = isArray(video.UserVideoHistories) ? video.UserVideoHistories[0] : undefined
|
const userHistory = isArray(video.UserVideoHistories) ? video.UserVideoHistories[0] : undefined
|
||||||
|
|
||||||
const videoObject: Video = {
|
const videoObject: Video = {
|
||||||
|
@ -101,29 +119,35 @@ function videoModelToFormattedJSON (video: MVideoFormattable, options?: VideoFor
|
||||||
pluginData: (video as any).pluginData
|
pluginData: (video as any).pluginData
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options) {
|
const add = options.additionalAttributes
|
||||||
if (options.additionalAttributes.state === true) {
|
if (add?.state === true) {
|
||||||
videoObject.state = {
|
videoObject.state = {
|
||||||
id: video.state,
|
id: video.state,
|
||||||
label: getStateLabel(video.state)
|
label: getStateLabel(video.state)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (options.additionalAttributes.waitTranscoding === true) {
|
if (add?.waitTranscoding === true) {
|
||||||
videoObject.waitTranscoding = video.waitTranscoding
|
videoObject.waitTranscoding = video.waitTranscoding
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.additionalAttributes.scheduledUpdate === true && video.ScheduleVideoUpdate) {
|
if (add?.scheduledUpdate === true && video.ScheduleVideoUpdate) {
|
||||||
videoObject.scheduledUpdate = {
|
videoObject.scheduledUpdate = {
|
||||||
updateAt: video.ScheduleVideoUpdate.updateAt,
|
updateAt: video.ScheduleVideoUpdate.updateAt,
|
||||||
privacy: video.ScheduleVideoUpdate.privacy || undefined
|
privacy: video.ScheduleVideoUpdate.privacy || undefined
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (options.additionalAttributes.blacklistInfo === true) {
|
if (add?.blacklistInfo === true) {
|
||||||
videoObject.blacklisted = !!video.VideoBlacklist
|
videoObject.blacklisted = !!video.VideoBlacklist
|
||||||
videoObject.blacklistedReason = video.VideoBlacklist ? video.VideoBlacklist.reason : null
|
videoObject.blacklistedReason = video.VideoBlacklist ? video.VideoBlacklist.reason : null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (add?.blockedOwner === true) {
|
||||||
|
videoObject.blockedOwner = video.VideoChannel.Account.isBlocked()
|
||||||
|
|
||||||
|
const server = video.VideoChannel.Account.Actor.Server as MServer
|
||||||
|
videoObject.blockedServer = !!(server?.isBlocked())
|
||||||
}
|
}
|
||||||
|
|
||||||
return videoObject
|
return videoObject
|
||||||
|
@ -464,6 +488,8 @@ export {
|
||||||
videoModelToActivityPubObject,
|
videoModelToActivityPubObject,
|
||||||
getActivityStreamDuration,
|
getActivityStreamDuration,
|
||||||
|
|
||||||
|
guessAdditionalAttributesFromQuery,
|
||||||
|
|
||||||
getCategoryLabel,
|
getCategoryLabel,
|
||||||
getLicenceLabel,
|
getLicenceLabel,
|
||||||
getLanguageLabel,
|
getLanguageLabel,
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { createSafeIn } from '@server/models/utils'
|
||||||
|
import { MUserAccountId } from '@server/types/models'
|
||||||
import validator from 'validator'
|
import validator from 'validator'
|
||||||
import { AbstractVideosQueryBuilder } from './abstract-videos-query-builder'
|
import { AbstractVideosQueryBuilder } from './abstract-videos-query-builder'
|
||||||
import { VideoTables } from './video-tables'
|
import { VideoTables } from './video-tables'
|
||||||
|
@ -188,6 +190,32 @@ export class AbstractVideosModelQueryBuilder extends AbstractVideosQueryBuilder
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected includeBlockedOwnerAndServer (serverAccountId: number, user?: MUserAccountId) {
|
||||||
|
const blockerIds = [ serverAccountId ]
|
||||||
|
if (user) blockerIds.push(user.Account.id)
|
||||||
|
|
||||||
|
const inClause = createSafeIn(this.sequelize, blockerIds)
|
||||||
|
|
||||||
|
this.addJoin(
|
||||||
|
'LEFT JOIN "accountBlocklist" AS "VideoChannel->Account->AccountBlocklist" ' +
|
||||||
|
'ON "VideoChannel->Account"."id" = "VideoChannel->Account->AccountBlocklist"."targetAccountId" ' +
|
||||||
|
'AND "VideoChannel->Account->AccountBlocklist"."accountId" IN (' + inClause + ')'
|
||||||
|
)
|
||||||
|
|
||||||
|
this.addJoin(
|
||||||
|
'LEFT JOIN "serverBlocklist" AS "VideoChannel->Account->Actor->Server->ServerBlocklist" ' +
|
||||||
|
'ON "VideoChannel->Account->Actor->Server->ServerBlocklist"."targetServerId" = "VideoChannel->Account->Actor"."serverId" ' +
|
||||||
|
'AND "VideoChannel->Account->Actor->Server->ServerBlocklist"."accountId" IN (' + inClause + ') '
|
||||||
|
)
|
||||||
|
|
||||||
|
this.attributes = {
|
||||||
|
...this.attributes,
|
||||||
|
|
||||||
|
...this.buildAttributesObject('VideoChannel->Account->AccountBlocklist', this.tables.getBlocklistAttributes()),
|
||||||
|
...this.buildAttributesObject('VideoChannel->Account->Actor->Server->ServerBlocklist', this.tables.getBlocklistAttributes())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected includeScheduleUpdate () {
|
protected includeScheduleUpdate () {
|
||||||
this.addJoin(
|
this.addJoin(
|
||||||
'LEFT OUTER JOIN "scheduleVideoUpdate" AS "ScheduleVideoUpdate" ON "video"."id" = "ScheduleVideoUpdate"."videoId"'
|
'LEFT OUTER JOIN "scheduleVideoUpdate" AS "ScheduleVideoUpdate" ON "video"."id" = "ScheduleVideoUpdate"."videoId"'
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
|
|
||||||
import { AccountModel } from '@server/models/account/account'
|
import { AccountModel } from '@server/models/account/account'
|
||||||
|
import { AccountBlocklistModel } from '@server/models/account/account-blocklist'
|
||||||
import { ActorModel } from '@server/models/actor/actor'
|
import { ActorModel } from '@server/models/actor/actor'
|
||||||
import { ActorImageModel } from '@server/models/actor/actor-image'
|
import { ActorImageModel } from '@server/models/actor/actor-image'
|
||||||
import { VideoRedundancyModel } from '@server/models/redundancy/video-redundancy'
|
import { VideoRedundancyModel } from '@server/models/redundancy/video-redundancy'
|
||||||
import { ServerModel } from '@server/models/server/server'
|
import { ServerModel } from '@server/models/server/server'
|
||||||
|
import { ServerBlocklistModel } from '@server/models/server/server-blocklist'
|
||||||
import { TrackerModel } from '@server/models/server/tracker'
|
import { TrackerModel } from '@server/models/server/tracker'
|
||||||
import { UserVideoHistoryModel } from '@server/models/user/user-video-history'
|
import { UserVideoHistoryModel } from '@server/models/user/user-video-history'
|
||||||
|
import { VideoInclude } from '@shared/models'
|
||||||
import { ScheduleVideoUpdateModel } from '../../schedule-video-update'
|
import { ScheduleVideoUpdateModel } from '../../schedule-video-update'
|
||||||
import { TagModel } from '../../tag'
|
import { TagModel } from '../../tag'
|
||||||
import { ThumbnailModel } from '../../thumbnail'
|
import { ThumbnailModel } from '../../thumbnail'
|
||||||
|
@ -33,6 +36,8 @@ export class VideoModelBuilder {
|
||||||
private thumbnailsDone: Set<any>
|
private thumbnailsDone: Set<any>
|
||||||
private historyDone: Set<any>
|
private historyDone: Set<any>
|
||||||
private blacklistDone: Set<any>
|
private blacklistDone: Set<any>
|
||||||
|
private accountBlocklistDone: Set<any>
|
||||||
|
private serverBlocklistDone: Set<any>
|
||||||
private liveDone: Set<any>
|
private liveDone: Set<any>
|
||||||
private redundancyDone: Set<any>
|
private redundancyDone: Set<any>
|
||||||
private scheduleVideoUpdateDone: Set<any>
|
private scheduleVideoUpdateDone: Set<any>
|
||||||
|
@ -51,7 +56,14 @@ export class VideoModelBuilder {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
buildVideosFromRows (rows: SQLRow[], rowsWebTorrentFiles?: SQLRow[], rowsStreamingPlaylist?: SQLRow[]) {
|
buildVideosFromRows (options: {
|
||||||
|
rows: SQLRow[]
|
||||||
|
include?: VideoInclude
|
||||||
|
rowsWebTorrentFiles?: SQLRow[]
|
||||||
|
rowsStreamingPlaylist?: SQLRow[]
|
||||||
|
}) {
|
||||||
|
const { rows, rowsWebTorrentFiles, rowsStreamingPlaylist, include } = options
|
||||||
|
|
||||||
this.reinit()
|
this.reinit()
|
||||||
|
|
||||||
for (const row of rows) {
|
for (const row of rows) {
|
||||||
|
@ -77,6 +89,15 @@ export class VideoModelBuilder {
|
||||||
this.setBlacklisted(row, videoModel)
|
this.setBlacklisted(row, videoModel)
|
||||||
this.setScheduleVideoUpdate(row, videoModel)
|
this.setScheduleVideoUpdate(row, videoModel)
|
||||||
this.setLive(row, videoModel)
|
this.setLive(row, videoModel)
|
||||||
|
} else {
|
||||||
|
if (include & VideoInclude.BLACKLISTED) {
|
||||||
|
this.setBlacklisted(row, videoModel)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (include & VideoInclude.BLOCKED_OWNER) {
|
||||||
|
this.setBlockedOwner(row, videoModel)
|
||||||
|
this.setBlockedServer(row, videoModel)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,15 +112,18 @@ export class VideoModelBuilder {
|
||||||
this.videoStreamingPlaylistMemo = {}
|
this.videoStreamingPlaylistMemo = {}
|
||||||
this.videoFileMemo = {}
|
this.videoFileMemo = {}
|
||||||
|
|
||||||
this.thumbnailsDone = new Set<number>()
|
this.thumbnailsDone = new Set()
|
||||||
this.historyDone = new Set<number>()
|
this.historyDone = new Set()
|
||||||
this.blacklistDone = new Set<number>()
|
this.blacklistDone = new Set()
|
||||||
this.liveDone = new Set<number>()
|
this.liveDone = new Set()
|
||||||
this.redundancyDone = new Set<number>()
|
this.redundancyDone = new Set()
|
||||||
this.scheduleVideoUpdateDone = new Set<number>()
|
this.scheduleVideoUpdateDone = new Set()
|
||||||
|
|
||||||
this.trackersDone = new Set<string>()
|
this.accountBlocklistDone = new Set()
|
||||||
this.tagsDone = new Set<string>()
|
this.serverBlocklistDone = new Set()
|
||||||
|
|
||||||
|
this.trackersDone = new Set()
|
||||||
|
this.tagsDone = new Set()
|
||||||
|
|
||||||
this.videos = []
|
this.videos = []
|
||||||
}
|
}
|
||||||
|
@ -162,6 +186,8 @@ export class VideoModelBuilder {
|
||||||
const accountModel = new AccountModel(this.grab(row, this.tables.getAccountAttributes(), 'VideoChannel.Account'), this.buildOpts)
|
const accountModel = new AccountModel(this.grab(row, this.tables.getAccountAttributes(), 'VideoChannel.Account'), this.buildOpts)
|
||||||
accountModel.Actor = this.buildActor(row, 'VideoChannel.Account')
|
accountModel.Actor = this.buildActor(row, 'VideoChannel.Account')
|
||||||
|
|
||||||
|
accountModel.BlockedBy = []
|
||||||
|
|
||||||
channelModel.Account = accountModel
|
channelModel.Account = accountModel
|
||||||
|
|
||||||
videoModel.VideoChannel = channelModel
|
videoModel.VideoChannel = channelModel
|
||||||
|
@ -180,6 +206,8 @@ export class VideoModelBuilder {
|
||||||
? new ServerModel(this.grab(row, this.tables.getServerAttributes(), serverPrefix), this.buildOpts)
|
? new ServerModel(this.grab(row, this.tables.getServerAttributes(), serverPrefix), this.buildOpts)
|
||||||
: null
|
: null
|
||||||
|
|
||||||
|
if (serverModel) serverModel.BlockedBy = []
|
||||||
|
|
||||||
const actorModel = new ActorModel(this.grab(row, this.tables.getActorAttributes(), actorPrefix), this.buildOpts)
|
const actorModel = new ActorModel(this.grab(row, this.tables.getActorAttributes(), actorPrefix), this.buildOpts)
|
||||||
actorModel.Avatar = avatarModel
|
actorModel.Avatar = avatarModel
|
||||||
actorModel.Server = serverModel
|
actorModel.Server = serverModel
|
||||||
|
@ -297,6 +325,32 @@ export class VideoModelBuilder {
|
||||||
this.blacklistDone.add(id)
|
this.blacklistDone.add(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private setBlockedOwner (row: SQLRow, videoModel: VideoModel) {
|
||||||
|
const id = row['VideoChannel.Account.AccountBlocklist.id']
|
||||||
|
if (!id) return
|
||||||
|
|
||||||
|
const key = `${videoModel.id}-${id}`
|
||||||
|
if (this.accountBlocklistDone.has(key)) return
|
||||||
|
|
||||||
|
const attributes = this.grab(row, this.tables.getBlocklistAttributes(), 'VideoChannel.Account.AccountBlocklist')
|
||||||
|
videoModel.VideoChannel.Account.BlockedBy.push(new AccountBlocklistModel(attributes, this.buildOpts))
|
||||||
|
|
||||||
|
this.accountBlocklistDone.add(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
private setBlockedServer (row: SQLRow, videoModel: VideoModel) {
|
||||||
|
const id = row['VideoChannel.Account.Actor.Server.ServerBlocklist.id']
|
||||||
|
if (!id || this.serverBlocklistDone.has(id)) return
|
||||||
|
|
||||||
|
const key = `${videoModel.id}-${id}`
|
||||||
|
if (this.serverBlocklistDone.has(key)) return
|
||||||
|
|
||||||
|
const attributes = this.grab(row, this.tables.getBlocklistAttributes(), 'VideoChannel.Account.Actor.Server.ServerBlocklist')
|
||||||
|
videoModel.VideoChannel.Account.Actor.Server.BlockedBy.push(new ServerBlocklistModel(attributes, this.buildOpts))
|
||||||
|
|
||||||
|
this.serverBlocklistDone.add(key)
|
||||||
|
}
|
||||||
|
|
||||||
private setScheduleVideoUpdate (row: SQLRow, videoModel: VideoModel) {
|
private setScheduleVideoUpdate (row: SQLRow, videoModel: VideoModel) {
|
||||||
const id = row['ScheduleVideoUpdate.id']
|
const id = row['ScheduleVideoUpdate.id']
|
||||||
if (!id || this.scheduleVideoUpdateDone.has(id)) return
|
if (!id || this.scheduleVideoUpdateDone.has(id)) return
|
||||||
|
|
|
@ -139,6 +139,10 @@ export class VideoTables {
|
||||||
return [ 'id', 'reason', 'unfederated' ]
|
return [ 'id', 'reason', 'unfederated' ]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getBlocklistAttributes () {
|
||||||
|
return [ 'id' ]
|
||||||
|
}
|
||||||
|
|
||||||
getScheduleUpdateAttributes () {
|
getScheduleUpdateAttributes () {
|
||||||
return [
|
return [
|
||||||
'id',
|
'id',
|
||||||
|
|
|
@ -62,7 +62,11 @@ export class VideosModelGetQueryBuilder {
|
||||||
: Promise.resolve(undefined)
|
: Promise.resolve(undefined)
|
||||||
])
|
])
|
||||||
|
|
||||||
const videos = this.videoModelBuilder.buildVideosFromRows(videoRows, webtorrentFilesRows, streamingPlaylistFilesRows)
|
const videos = this.videoModelBuilder.buildVideosFromRows({
|
||||||
|
rows: videoRows,
|
||||||
|
rowsWebTorrentFiles: webtorrentFilesRows,
|
||||||
|
rowsStreamingPlaylist: streamingPlaylistFilesRows
|
||||||
|
})
|
||||||
|
|
||||||
if (videos.length > 1) {
|
if (videos.length > 1) {
|
||||||
throw new Error('Video results is more than ')
|
throw new Error('Video results is more than ')
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { exists } from '@server/helpers/custom-validators/misc'
|
||||||
import { WEBSERVER } from '@server/initializers/constants'
|
import { WEBSERVER } from '@server/initializers/constants'
|
||||||
import { buildDirectionAndField, createSafeIn } from '@server/models/utils'
|
import { buildDirectionAndField, createSafeIn } from '@server/models/utils'
|
||||||
import { MUserAccountId, MUserId } from '@server/types/models'
|
import { MUserAccountId, MUserId } from '@server/types/models'
|
||||||
import { VideoFilter, VideoPrivacy, VideoState } from '@shared/models'
|
import { VideoInclude, VideoPrivacy, VideoState } from '@shared/models'
|
||||||
import { AbstractVideosQueryBuilder } from './shared/abstract-videos-query-builder'
|
import { AbstractVideosQueryBuilder } from './shared/abstract-videos-query-builder'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -13,21 +13,27 @@ import { AbstractVideosQueryBuilder } from './shared/abstract-videos-query-build
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
export type DisplayOnlyForFollowerOptions = {
|
||||||
|
actorId: number
|
||||||
|
orLocalVideos: boolean
|
||||||
|
}
|
||||||
|
|
||||||
export type BuildVideosListQueryOptions = {
|
export type BuildVideosListQueryOptions = {
|
||||||
attributes?: string[]
|
attributes?: string[]
|
||||||
|
|
||||||
serverAccountId: number
|
serverAccountIdForBlock: number
|
||||||
followerActorId: number
|
|
||||||
includeLocalVideos: boolean
|
displayOnlyForFollower: DisplayOnlyForFollowerOptions
|
||||||
|
|
||||||
count: number
|
count: number
|
||||||
start: number
|
start: number
|
||||||
sort: string
|
sort: string
|
||||||
|
|
||||||
nsfw?: boolean
|
nsfw?: boolean
|
||||||
filter?: VideoFilter
|
|
||||||
host?: string
|
host?: string
|
||||||
isLive?: boolean
|
isLive?: boolean
|
||||||
|
isLocal?: boolean
|
||||||
|
include?: VideoInclude
|
||||||
|
|
||||||
categoryOneOf?: number[]
|
categoryOneOf?: number[]
|
||||||
licenceOneOf?: number[]
|
licenceOneOf?: number[]
|
||||||
|
@ -101,6 +107,7 @@ export class VideosIdListQueryBuilder extends AbstractVideosQueryBuilder {
|
||||||
|
|
||||||
getIdsListQueryAndSort (options: BuildVideosListQueryOptions) {
|
getIdsListQueryAndSort (options: BuildVideosListQueryOptions) {
|
||||||
this.buildIdsListQuery(options)
|
this.buildIdsListQuery(options)
|
||||||
|
|
||||||
return { query: this.query, sort: this.sort, replacements: this.replacements }
|
return { query: this.query, sort: this.sort, replacements: this.replacements }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,23 +123,30 @@ export class VideosIdListQueryBuilder extends AbstractVideosQueryBuilder {
|
||||||
'INNER JOIN "actor" "accountActor" ON "account"."actorId" = "accountActor"."id"'
|
'INNER JOIN "actor" "accountActor" ON "account"."actorId" = "accountActor"."id"'
|
||||||
])
|
])
|
||||||
|
|
||||||
this.whereNotBlacklisted()
|
if (!(options.include & VideoInclude.BLACKLISTED)) {
|
||||||
|
this.whereNotBlacklisted()
|
||||||
if (options.serverAccountId) {
|
|
||||||
this.whereNotBlocked(options.serverAccountId, options.user)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only list public/published videos
|
if (options.serverAccountIdForBlock && !(options.include & VideoInclude.BLOCKED_OWNER)) {
|
||||||
if (!options.filter || (options.filter !== 'all-local' && options.filter !== 'all')) {
|
this.whereNotBlocked(options.serverAccountIdForBlock, options.user)
|
||||||
this.whereStateAndPrivacyAvailable(options.user)
|
}
|
||||||
|
|
||||||
|
// Only list published videos
|
||||||
|
if (!(options.include & VideoInclude.NOT_PUBLISHED_STATE)) {
|
||||||
|
this.whereStateAvailable()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only list videos with the appropriate priavcy
|
||||||
|
if (!(options.include & VideoInclude.HIDDEN_PRIVACY)) {
|
||||||
|
this.wherePrivacyAvailable(options.user)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.videoPlaylistId) {
|
if (options.videoPlaylistId) {
|
||||||
this.joinPlaylist(options.videoPlaylistId)
|
this.joinPlaylist(options.videoPlaylistId)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.filter && (options.filter === 'local' || options.filter === 'all-local')) {
|
if (exists(options.isLocal)) {
|
||||||
this.whereOnlyLocal()
|
this.whereLocal(options.isLocal)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.host) {
|
if (options.host) {
|
||||||
|
@ -147,8 +161,8 @@ export class VideosIdListQueryBuilder extends AbstractVideosQueryBuilder {
|
||||||
this.whereChannelId(options.videoChannelId)
|
this.whereChannelId(options.videoChannelId)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.followerActorId) {
|
if (options.displayOnlyForFollower) {
|
||||||
this.whereFollowerActorId(options.followerActorId, options.includeLocalVideos)
|
this.whereFollowerActorId(options.displayOnlyForFollower)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.withFiles === true) {
|
if (options.withFiles === true) {
|
||||||
|
@ -282,12 +296,14 @@ export class VideosIdListQueryBuilder extends AbstractVideosQueryBuilder {
|
||||||
this.replacements.videoPlaylistId = playlistId
|
this.replacements.videoPlaylistId = playlistId
|
||||||
}
|
}
|
||||||
|
|
||||||
private whereStateAndPrivacyAvailable (user?: MUserAccountId) {
|
private whereStateAvailable () {
|
||||||
this.and.push(
|
this.and.push(
|
||||||
`("video"."state" = ${VideoState.PUBLISHED} OR ` +
|
`("video"."state" = ${VideoState.PUBLISHED} OR ` +
|
||||||
`("video"."state" = ${VideoState.TO_TRANSCODE} AND "video"."waitTranscoding" IS false))`
|
`("video"."state" = ${VideoState.TO_TRANSCODE} AND "video"."waitTranscoding" IS false))`
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private wherePrivacyAvailable (user?: MUserAccountId) {
|
||||||
if (user) {
|
if (user) {
|
||||||
this.and.push(
|
this.and.push(
|
||||||
`("video"."privacy" = ${VideoPrivacy.PUBLIC} OR "video"."privacy" = ${VideoPrivacy.INTERNAL})`
|
`("video"."privacy" = ${VideoPrivacy.PUBLIC} OR "video"."privacy" = ${VideoPrivacy.INTERNAL})`
|
||||||
|
@ -299,8 +315,10 @@ export class VideosIdListQueryBuilder extends AbstractVideosQueryBuilder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private whereOnlyLocal () {
|
private whereLocal (isLocal: boolean) {
|
||||||
this.and.push('"video"."remote" IS FALSE')
|
const isRemote = isLocal ? 'FALSE' : 'TRUE'
|
||||||
|
|
||||||
|
this.and.push('"video"."remote" IS ' + isRemote)
|
||||||
}
|
}
|
||||||
|
|
||||||
private whereHost (host: string) {
|
private whereHost (host: string) {
|
||||||
|
@ -326,7 +344,7 @@ export class VideosIdListQueryBuilder extends AbstractVideosQueryBuilder {
|
||||||
this.replacements.videoChannelId = channelId
|
this.replacements.videoChannelId = channelId
|
||||||
}
|
}
|
||||||
|
|
||||||
private whereFollowerActorId (followerActorId: number, includeLocalVideos: boolean) {
|
private whereFollowerActorId (options: { actorId: number, orLocalVideos: boolean }) {
|
||||||
let query =
|
let query =
|
||||||
'(' +
|
'(' +
|
||||||
' EXISTS (' + // Videos shared by actors we follow
|
' EXISTS (' + // Videos shared by actors we follow
|
||||||
|
@ -342,14 +360,14 @@ export class VideosIdListQueryBuilder extends AbstractVideosQueryBuilder {
|
||||||
' AND "actorFollow"."state" = \'accepted\'' +
|
' AND "actorFollow"."state" = \'accepted\'' +
|
||||||
' )'
|
' )'
|
||||||
|
|
||||||
if (includeLocalVideos) {
|
if (options.orLocalVideos) {
|
||||||
query += ' OR "video"."remote" IS FALSE'
|
query += ' OR "video"."remote" IS FALSE'
|
||||||
}
|
}
|
||||||
|
|
||||||
query += ')'
|
query += ')'
|
||||||
|
|
||||||
this.and.push(query)
|
this.and.push(query)
|
||||||
this.replacements.followerActorId = followerActorId
|
this.replacements.followerActorId = options.actorId
|
||||||
}
|
}
|
||||||
|
|
||||||
private whereFileExists () {
|
private whereFileExists () {
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { VideoInclude } from '@shared/models'
|
||||||
import { Sequelize } from 'sequelize'
|
import { Sequelize } from 'sequelize'
|
||||||
import { AbstractVideosModelQueryBuilder } from './shared/abstract-videos-model-query-builder'
|
import { AbstractVideosModelQueryBuilder } from './shared/abstract-videos-model-query-builder'
|
||||||
import { VideoModelBuilder } from './shared/video-model-builder'
|
import { VideoModelBuilder } from './shared/video-model-builder'
|
||||||
|
@ -28,7 +29,7 @@ export class VideosModelListQueryBuilder extends AbstractVideosModelQueryBuilder
|
||||||
this.buildListQueryFromIdsQuery(options)
|
this.buildListQueryFromIdsQuery(options)
|
||||||
|
|
||||||
return this.runQuery()
|
return this.runQuery()
|
||||||
.then(rows => this.videoModelBuilder.buildVideosFromRows(rows))
|
.then(rows => this.videoModelBuilder.buildVideosFromRows({ rows, include: options.include }))
|
||||||
}
|
}
|
||||||
|
|
||||||
private buildInnerQuery (options: BuildVideosListQueryOptions) {
|
private buildInnerQuery (options: BuildVideosListQueryOptions) {
|
||||||
|
@ -64,6 +65,14 @@ export class VideosModelListQueryBuilder extends AbstractVideosModelQueryBuilder
|
||||||
this.includePlaylist(options.videoPlaylistId)
|
this.includePlaylist(options.videoPlaylistId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (options.include & VideoInclude.BLACKLISTED) {
|
||||||
|
this.includeBlacklisted()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.include & VideoInclude.BLOCKED_OWNER) {
|
||||||
|
this.includeBlockedOwnerAndServer(options.serverAccountIdForBlock, options.user)
|
||||||
|
}
|
||||||
|
|
||||||
const select = this.buildSelect()
|
const select = this.buildSelect()
|
||||||
|
|
||||||
this.query = `${select} FROM (${this.innerQuery}) AS "tmp" ${this.joins} ${this.innerSort}`
|
this.query = `${select} FROM (${this.innerQuery}) AS "tmp" ${this.joins} ${this.innerSort}`
|
||||||
|
|
|
@ -34,12 +34,12 @@ import { VideoPathManager } from '@server/lib/video-path-manager'
|
||||||
import { getServerActor } from '@server/models/application/application'
|
import { getServerActor } from '@server/models/application/application'
|
||||||
import { ModelCache } from '@server/models/model-cache'
|
import { ModelCache } from '@server/models/model-cache'
|
||||||
import { AttributesOnly, buildVideoEmbedPath, buildVideoWatchPath, pick } from '@shared/core-utils'
|
import { AttributesOnly, buildVideoEmbedPath, buildVideoWatchPath, pick } from '@shared/core-utils'
|
||||||
|
import { VideoInclude } from '@shared/models'
|
||||||
import { VideoFile } from '@shared/models/videos/video-file.model'
|
import { VideoFile } from '@shared/models/videos/video-file.model'
|
||||||
import { ResultList, UserRight, VideoPrivacy, VideoState } from '../../../shared'
|
import { ResultList, UserRight, VideoPrivacy, VideoState } from '../../../shared'
|
||||||
import { VideoObject } from '../../../shared/models/activitypub/objects'
|
import { VideoObject } from '../../../shared/models/activitypub/objects'
|
||||||
import { Video, VideoDetails, VideoRateType, VideoStorage } from '../../../shared/models/videos'
|
import { Video, VideoDetails, VideoRateType, VideoStorage } from '../../../shared/models/videos'
|
||||||
import { ThumbnailType } from '../../../shared/models/videos/thumbnail.type'
|
import { ThumbnailType } from '../../../shared/models/videos/thumbnail.type'
|
||||||
import { VideoFilter } from '../../../shared/models/videos/video-query.type'
|
|
||||||
import { VideoStreamingPlaylistType } from '../../../shared/models/videos/video-streaming-playlist.type'
|
import { VideoStreamingPlaylistType } from '../../../shared/models/videos/video-streaming-playlist.type'
|
||||||
import { peertubeTruncate } from '../../helpers/core-utils'
|
import { peertubeTruncate } from '../../helpers/core-utils'
|
||||||
import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc'
|
import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc'
|
||||||
|
@ -106,7 +106,7 @@ import {
|
||||||
} from './formatter/video-format-utils'
|
} from './formatter/video-format-utils'
|
||||||
import { ScheduleVideoUpdateModel } from './schedule-video-update'
|
import { ScheduleVideoUpdateModel } from './schedule-video-update'
|
||||||
import { VideosModelGetQueryBuilder } from './sql/video-model-get-query-builder'
|
import { VideosModelGetQueryBuilder } from './sql/video-model-get-query-builder'
|
||||||
import { BuildVideosListQueryOptions, VideosIdListQueryBuilder } from './sql/videos-id-list-query-builder'
|
import { BuildVideosListQueryOptions, DisplayOnlyForFollowerOptions, VideosIdListQueryBuilder } from './sql/videos-id-list-query-builder'
|
||||||
import { VideosModelListQueryBuilder } from './sql/videos-model-list-query-builder'
|
import { VideosModelListQueryBuilder } from './sql/videos-model-list-query-builder'
|
||||||
import { TagModel } from './tag'
|
import { TagModel } from './tag'
|
||||||
import { ThumbnailModel } from './thumbnail'
|
import { ThumbnailModel } from './thumbnail'
|
||||||
|
@ -145,35 +145,6 @@ export type ForAPIOptions = {
|
||||||
withAccountBlockerIds?: number[]
|
withAccountBlockerIds?: number[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AvailableForListIDsOptions = {
|
|
||||||
serverAccountId: number
|
|
||||||
followerActorId: number
|
|
||||||
includeLocalVideos: boolean
|
|
||||||
|
|
||||||
attributesType?: 'none' | 'id' | 'all'
|
|
||||||
|
|
||||||
filter?: VideoFilter
|
|
||||||
categoryOneOf?: number[]
|
|
||||||
nsfw?: boolean
|
|
||||||
licenceOneOf?: number[]
|
|
||||||
languageOneOf?: string[]
|
|
||||||
tagsOneOf?: string[]
|
|
||||||
tagsAllOf?: string[]
|
|
||||||
|
|
||||||
withFiles?: boolean
|
|
||||||
|
|
||||||
accountId?: number
|
|
||||||
videoChannelId?: number
|
|
||||||
|
|
||||||
videoPlaylistId?: number
|
|
||||||
|
|
||||||
trendingDays?: number
|
|
||||||
user?: MUserAccountId
|
|
||||||
historyOfUser?: MUserId
|
|
||||||
|
|
||||||
baseWhere?: WhereOptions[]
|
|
||||||
}
|
|
||||||
|
|
||||||
@Scopes(() => ({
|
@Scopes(() => ({
|
||||||
[ScopeNames.WITH_IMMUTABLE_ATTRIBUTES]: {
|
[ScopeNames.WITH_IMMUTABLE_ATTRIBUTES]: {
|
||||||
attributes: [ 'id', 'url', 'uuid', 'remote' ]
|
attributes: [ 'id', 'url', 'uuid', 'remote' ]
|
||||||
|
@ -1054,10 +1025,10 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> {
|
||||||
sort: string
|
sort: string
|
||||||
|
|
||||||
nsfw: boolean
|
nsfw: boolean
|
||||||
filter?: VideoFilter
|
|
||||||
isLive?: boolean
|
isLive?: boolean
|
||||||
|
isLocal?: boolean
|
||||||
|
include?: VideoInclude
|
||||||
|
|
||||||
includeLocalVideos: boolean
|
|
||||||
withFiles: boolean
|
withFiles: boolean
|
||||||
|
|
||||||
categoryOneOf?: number[]
|
categoryOneOf?: number[]
|
||||||
|
@ -1069,7 +1040,7 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> {
|
||||||
accountId?: number
|
accountId?: number
|
||||||
videoChannelId?: number
|
videoChannelId?: number
|
||||||
|
|
||||||
followerActorId?: number
|
displayOnlyForFollower: DisplayOnlyForFollowerOptions | null
|
||||||
|
|
||||||
videoPlaylistId?: number
|
videoPlaylistId?: number
|
||||||
|
|
||||||
|
@ -1082,7 +1053,7 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> {
|
||||||
|
|
||||||
search?: string
|
search?: string
|
||||||
}) {
|
}) {
|
||||||
if ((options.filter === 'all-local' || options.filter === 'all') && !options.user.hasRight(UserRight.SEE_ALL_VIDEOS)) {
|
if (options.include && !options.user.hasRight(UserRight.SEE_ALL_VIDEOS)) {
|
||||||
throw new Error('Try to filter all-local but no user has not the see all videos right')
|
throw new Error('Try to filter all-local but no user has not the see all videos right')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1096,11 +1067,6 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> {
|
||||||
|
|
||||||
const serverActor = await getServerActor()
|
const serverActor = await getServerActor()
|
||||||
|
|
||||||
// followerActorId === null has a meaning, so just check undefined
|
|
||||||
const followerActorId = options.followerActorId !== undefined
|
|
||||||
? options.followerActorId
|
|
||||||
: serverActor.id
|
|
||||||
|
|
||||||
const queryOptions = {
|
const queryOptions = {
|
||||||
...pick(options, [
|
...pick(options, [
|
||||||
'start',
|
'start',
|
||||||
|
@ -1113,19 +1079,19 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> {
|
||||||
'languageOneOf',
|
'languageOneOf',
|
||||||
'tagsOneOf',
|
'tagsOneOf',
|
||||||
'tagsAllOf',
|
'tagsAllOf',
|
||||||
'filter',
|
'isLocal',
|
||||||
|
'include',
|
||||||
|
'displayOnlyForFollower',
|
||||||
'withFiles',
|
'withFiles',
|
||||||
'accountId',
|
'accountId',
|
||||||
'videoChannelId',
|
'videoChannelId',
|
||||||
'videoPlaylistId',
|
'videoPlaylistId',
|
||||||
'includeLocalVideos',
|
|
||||||
'user',
|
'user',
|
||||||
'historyOfUser',
|
'historyOfUser',
|
||||||
'search'
|
'search'
|
||||||
]),
|
]),
|
||||||
|
|
||||||
followerActorId,
|
serverAccountIdForBlock: serverActor.Account.id,
|
||||||
serverAccountId: serverActor.Account.id,
|
|
||||||
trendingDays,
|
trendingDays,
|
||||||
trendingAlgorithm
|
trendingAlgorithm
|
||||||
}
|
}
|
||||||
|
@ -1137,7 +1103,6 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> {
|
||||||
start: number
|
start: number
|
||||||
count: number
|
count: number
|
||||||
sort: string
|
sort: string
|
||||||
includeLocalVideos: boolean
|
|
||||||
search?: string
|
search?: string
|
||||||
host?: string
|
host?: string
|
||||||
startDate?: string // ISO 8601
|
startDate?: string // ISO 8601
|
||||||
|
@ -1146,6 +1111,8 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> {
|
||||||
originallyPublishedEndDate?: string
|
originallyPublishedEndDate?: string
|
||||||
nsfw?: boolean
|
nsfw?: boolean
|
||||||
isLive?: boolean
|
isLive?: boolean
|
||||||
|
isLocal?: boolean
|
||||||
|
include?: VideoInclude
|
||||||
categoryOneOf?: number[]
|
categoryOneOf?: number[]
|
||||||
licenceOneOf?: number[]
|
licenceOneOf?: number[]
|
||||||
languageOneOf?: string[]
|
languageOneOf?: string[]
|
||||||
|
@ -1154,14 +1121,14 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> {
|
||||||
durationMin?: number // seconds
|
durationMin?: number // seconds
|
||||||
durationMax?: number // seconds
|
durationMax?: number // seconds
|
||||||
user?: MUserAccountId
|
user?: MUserAccountId
|
||||||
filter?: VideoFilter
|
|
||||||
uuids?: string[]
|
uuids?: string[]
|
||||||
|
displayOnlyForFollower: DisplayOnlyForFollowerOptions | null
|
||||||
}) {
|
}) {
|
||||||
const serverActor = await getServerActor()
|
const serverActor = await getServerActor()
|
||||||
|
|
||||||
const queryOptions = {
|
const queryOptions = {
|
||||||
...pick(options, [
|
...pick(options, [
|
||||||
'includeLocalVideos',
|
'include',
|
||||||
'nsfw',
|
'nsfw',
|
||||||
'isLive',
|
'isLive',
|
||||||
'categoryOneOf',
|
'categoryOneOf',
|
||||||
|
@ -1170,7 +1137,7 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> {
|
||||||
'tagsOneOf',
|
'tagsOneOf',
|
||||||
'tagsAllOf',
|
'tagsAllOf',
|
||||||
'user',
|
'user',
|
||||||
'filter',
|
'isLocal',
|
||||||
'host',
|
'host',
|
||||||
'start',
|
'start',
|
||||||
'count',
|
'count',
|
||||||
|
@ -1182,11 +1149,10 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> {
|
||||||
'durationMin',
|
'durationMin',
|
||||||
'durationMax',
|
'durationMax',
|
||||||
'uuids',
|
'uuids',
|
||||||
'search'
|
'search',
|
||||||
|
'displayOnlyForFollower'
|
||||||
]),
|
]),
|
||||||
|
serverAccountIdForBlock: serverActor.Account.id
|
||||||
followerActorId: serverActor.id,
|
|
||||||
serverAccountId: serverActor.Account.id
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return VideoModel.getAvailableForApi(queryOptions)
|
return VideoModel.getAvailableForApi(queryOptions)
|
||||||
|
@ -1369,12 +1335,17 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> {
|
||||||
// Sequelize could return null...
|
// Sequelize could return null...
|
||||||
if (!totalLocalVideoViews) totalLocalVideoViews = 0
|
if (!totalLocalVideoViews) totalLocalVideoViews = 0
|
||||||
|
|
||||||
|
const serverActor = await getServerActor()
|
||||||
|
|
||||||
const { total: totalVideos } = await VideoModel.listForApi({
|
const { total: totalVideos } = await VideoModel.listForApi({
|
||||||
start: 0,
|
start: 0,
|
||||||
count: 0,
|
count: 0,
|
||||||
sort: '-publishedAt',
|
sort: '-publishedAt',
|
||||||
nsfw: buildNSFWFilter(),
|
nsfw: buildNSFWFilter(),
|
||||||
includeLocalVideos: true,
|
displayOnlyForFollower: {
|
||||||
|
actorId: serverActor.id,
|
||||||
|
orLocalVideos: true
|
||||||
|
},
|
||||||
withFiles: false
|
withFiles: false
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -1455,7 +1426,6 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> {
|
||||||
// threshold corresponds to how many video the field should have to be returned
|
// threshold corresponds to how many video the field should have to be returned
|
||||||
static async getRandomFieldSamples (field: 'category' | 'channelId', threshold: number, count: number) {
|
static async getRandomFieldSamples (field: 'category' | 'channelId', threshold: number, count: number) {
|
||||||
const serverActor = await getServerActor()
|
const serverActor = await getServerActor()
|
||||||
const followerActorId = serverActor.id
|
|
||||||
|
|
||||||
const queryOptions: BuildVideosListQueryOptions = {
|
const queryOptions: BuildVideosListQueryOptions = {
|
||||||
attributes: [ `"${field}"` ],
|
attributes: [ `"${field}"` ],
|
||||||
|
@ -1464,9 +1434,11 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> {
|
||||||
start: 0,
|
start: 0,
|
||||||
sort: 'random',
|
sort: 'random',
|
||||||
count,
|
count,
|
||||||
serverAccountId: serverActor.Account.id,
|
serverAccountIdForBlock: serverActor.Account.id,
|
||||||
followerActorId,
|
displayOnlyForFollower: {
|
||||||
includeLocalVideos: true
|
actorId: serverActor.id,
|
||||||
|
orLocalVideos: true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const queryBuilder = new VideosIdListQueryBuilder(VideoModel.sequelize)
|
const queryBuilder = new VideosIdListQueryBuilder(VideoModel.sequelize)
|
||||||
|
|
|
@ -27,6 +27,6 @@ import './video-comments'
|
||||||
import './video-imports'
|
import './video-imports'
|
||||||
import './video-playlists'
|
import './video-playlists'
|
||||||
import './videos'
|
import './videos'
|
||||||
import './videos-filter'
|
import './videos-common-filters'
|
||||||
import './videos-history'
|
import './videos-history'
|
||||||
import './videos-overviews'
|
import './videos-overviews'
|
||||||
|
|
|
@ -0,0 +1,203 @@
|
||||||
|
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
|
||||||
|
|
||||||
|
import 'mocha'
|
||||||
|
import {
|
||||||
|
cleanupTests,
|
||||||
|
createSingleServer,
|
||||||
|
makeGetRequest,
|
||||||
|
PeerTubeServer,
|
||||||
|
setAccessTokensToServers,
|
||||||
|
setDefaultVideoChannel
|
||||||
|
} from '@shared/extra-utils'
|
||||||
|
import { HttpStatusCode, UserRole, VideoInclude } from '@shared/models'
|
||||||
|
|
||||||
|
describe('Test video filters validators', function () {
|
||||||
|
let server: PeerTubeServer
|
||||||
|
let userAccessToken: string
|
||||||
|
let moderatorAccessToken: string
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------
|
||||||
|
|
||||||
|
before(async function () {
|
||||||
|
this.timeout(30000)
|
||||||
|
|
||||||
|
server = await createSingleServer(1)
|
||||||
|
|
||||||
|
await setAccessTokensToServers([ server ])
|
||||||
|
await setDefaultVideoChannel([ server ])
|
||||||
|
|
||||||
|
const user = { username: 'user1', password: 'my super password' }
|
||||||
|
await server.users.create({ username: user.username, password: user.password })
|
||||||
|
userAccessToken = await server.login.getAccessToken(user)
|
||||||
|
|
||||||
|
const moderator = { username: 'moderator', password: 'my super password' }
|
||||||
|
await server.users.create({ username: moderator.username, password: moderator.password, role: UserRole.MODERATOR })
|
||||||
|
|
||||||
|
moderatorAccessToken = await server.login.getAccessToken(moderator)
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('When setting a deprecated video filter', function () {
|
||||||
|
|
||||||
|
async function testEndpoints (token: string, filter: string, expectedStatus: HttpStatusCode) {
|
||||||
|
const paths = [
|
||||||
|
'/api/v1/video-channels/root_channel/videos',
|
||||||
|
'/api/v1/accounts/root/videos',
|
||||||
|
'/api/v1/videos',
|
||||||
|
'/api/v1/search/videos'
|
||||||
|
]
|
||||||
|
|
||||||
|
for (const path of paths) {
|
||||||
|
await makeGetRequest({
|
||||||
|
url: server.url,
|
||||||
|
path,
|
||||||
|
token,
|
||||||
|
query: {
|
||||||
|
filter
|
||||||
|
},
|
||||||
|
expectedStatus
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
it('Should fail with a bad filter', async function () {
|
||||||
|
await testEndpoints(server.accessToken, 'bad-filter', HttpStatusCode.BAD_REQUEST_400)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should succeed with a good filter', async function () {
|
||||||
|
await testEndpoints(server.accessToken, 'local', HttpStatusCode.OK_200)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should fail to list all-local/all with a simple user', async function () {
|
||||||
|
await testEndpoints(userAccessToken, 'all-local', HttpStatusCode.UNAUTHORIZED_401)
|
||||||
|
await testEndpoints(userAccessToken, 'all', HttpStatusCode.UNAUTHORIZED_401)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should succeed to list all-local/all with a moderator', async function () {
|
||||||
|
await testEndpoints(moderatorAccessToken, 'all-local', HttpStatusCode.OK_200)
|
||||||
|
await testEndpoints(moderatorAccessToken, 'all', HttpStatusCode.OK_200)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should succeed to list all-local/all with an admin', async function () {
|
||||||
|
await testEndpoints(server.accessToken, 'all-local', HttpStatusCode.OK_200)
|
||||||
|
await testEndpoints(server.accessToken, 'all', HttpStatusCode.OK_200)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Because we cannot authenticate the user on the RSS endpoint
|
||||||
|
it('Should fail on the feeds endpoint with the all-local/all filter', async function () {
|
||||||
|
for (const filter of [ 'all', 'all-local' ]) {
|
||||||
|
await makeGetRequest({
|
||||||
|
url: server.url,
|
||||||
|
path: '/feeds/videos.json',
|
||||||
|
expectedStatus: HttpStatusCode.UNAUTHORIZED_401,
|
||||||
|
query: {
|
||||||
|
filter
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should succeed on the feeds endpoint with the local filter', async function () {
|
||||||
|
await makeGetRequest({
|
||||||
|
url: server.url,
|
||||||
|
path: '/feeds/videos.json',
|
||||||
|
expectedStatus: HttpStatusCode.OK_200,
|
||||||
|
query: {
|
||||||
|
filter: 'local'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('When setting video filters', function () {
|
||||||
|
|
||||||
|
const validIncludes = [
|
||||||
|
VideoInclude.NONE,
|
||||||
|
VideoInclude.HIDDEN_PRIVACY,
|
||||||
|
VideoInclude.NOT_PUBLISHED_STATE | VideoInclude.BLACKLISTED
|
||||||
|
]
|
||||||
|
|
||||||
|
async function testEndpoints (options: {
|
||||||
|
token?: string
|
||||||
|
isLocal?: boolean
|
||||||
|
include?: VideoInclude
|
||||||
|
expectedStatus: HttpStatusCode
|
||||||
|
}) {
|
||||||
|
const paths = [
|
||||||
|
'/api/v1/video-channels/root_channel/videos',
|
||||||
|
'/api/v1/accounts/root/videos',
|
||||||
|
'/api/v1/videos',
|
||||||
|
'/api/v1/search/videos'
|
||||||
|
]
|
||||||
|
|
||||||
|
for (const path of paths) {
|
||||||
|
await makeGetRequest({
|
||||||
|
url: server.url,
|
||||||
|
path,
|
||||||
|
token: options.token || server.accessToken,
|
||||||
|
query: {
|
||||||
|
isLocal: options.isLocal,
|
||||||
|
include: options.include
|
||||||
|
},
|
||||||
|
expectedStatus: options.expectedStatus
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
it('Should fail with a bad include', async function () {
|
||||||
|
await testEndpoints({ include: 'toto' as any, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should succeed with a good include', async function () {
|
||||||
|
for (const include of validIncludes) {
|
||||||
|
await testEndpoints({ include, expectedStatus: HttpStatusCode.OK_200 })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should fail to include more videos with a simple user', async function () {
|
||||||
|
for (const include of validIncludes) {
|
||||||
|
await testEndpoints({ token: userAccessToken, include, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should succeed to list all local/all with a moderator', async function () {
|
||||||
|
for (const include of validIncludes) {
|
||||||
|
await testEndpoints({ token: moderatorAccessToken, include, expectedStatus: HttpStatusCode.OK_200 })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should succeed to list all local/all with an admin', async function () {
|
||||||
|
for (const include of validIncludes) {
|
||||||
|
await testEndpoints({ token: server.accessToken, include, expectedStatus: HttpStatusCode.OK_200 })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Because we cannot authenticate the user on the RSS endpoint
|
||||||
|
it('Should fail on the feeds endpoint with the all filter', async function () {
|
||||||
|
for (const include of [ VideoInclude.NOT_PUBLISHED_STATE ]) {
|
||||||
|
await makeGetRequest({
|
||||||
|
url: server.url,
|
||||||
|
path: '/feeds/videos.json',
|
||||||
|
expectedStatus: HttpStatusCode.UNAUTHORIZED_401,
|
||||||
|
query: {
|
||||||
|
include
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should succeed on the feeds endpoint with the local filter', async function () {
|
||||||
|
await makeGetRequest({
|
||||||
|
url: server.url,
|
||||||
|
path: '/feeds/videos.json',
|
||||||
|
expectedStatus: HttpStatusCode.OK_200,
|
||||||
|
query: {
|
||||||
|
isLocal: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
after(async function () {
|
||||||
|
await cleanupTests([ server ])
|
||||||
|
})
|
||||||
|
})
|
|
@ -1,114 +0,0 @@
|
||||||
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
|
|
||||||
|
|
||||||
import 'mocha'
|
|
||||||
import {
|
|
||||||
cleanupTests,
|
|
||||||
createSingleServer,
|
|
||||||
makeGetRequest,
|
|
||||||
PeerTubeServer,
|
|
||||||
setAccessTokensToServers,
|
|
||||||
setDefaultVideoChannel
|
|
||||||
} from '@shared/extra-utils'
|
|
||||||
import { HttpStatusCode, UserRole } from '@shared/models'
|
|
||||||
|
|
||||||
async function testEndpoints (server: PeerTubeServer, token: string, filter: string, expectedStatus: HttpStatusCode) {
|
|
||||||
const paths = [
|
|
||||||
'/api/v1/video-channels/root_channel/videos',
|
|
||||||
'/api/v1/accounts/root/videos',
|
|
||||||
'/api/v1/videos',
|
|
||||||
'/api/v1/search/videos'
|
|
||||||
]
|
|
||||||
|
|
||||||
for (const path of paths) {
|
|
||||||
await makeGetRequest({
|
|
||||||
url: server.url,
|
|
||||||
path,
|
|
||||||
token,
|
|
||||||
query: {
|
|
||||||
filter
|
|
||||||
},
|
|
||||||
expectedStatus
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('Test video filters validators', function () {
|
|
||||||
let server: PeerTubeServer
|
|
||||||
let userAccessToken: string
|
|
||||||
let moderatorAccessToken: string
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------
|
|
||||||
|
|
||||||
before(async function () {
|
|
||||||
this.timeout(30000)
|
|
||||||
|
|
||||||
server = await createSingleServer(1)
|
|
||||||
|
|
||||||
await setAccessTokensToServers([ server ])
|
|
||||||
await setDefaultVideoChannel([ server ])
|
|
||||||
|
|
||||||
const user = { username: 'user1', password: 'my super password' }
|
|
||||||
await server.users.create({ username: user.username, password: user.password })
|
|
||||||
userAccessToken = await server.login.getAccessToken(user)
|
|
||||||
|
|
||||||
const moderator = { username: 'moderator', password: 'my super password' }
|
|
||||||
await server.users.create({ username: moderator.username, password: moderator.password, role: UserRole.MODERATOR })
|
|
||||||
|
|
||||||
moderatorAccessToken = await server.login.getAccessToken(moderator)
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('When setting a video filter', function () {
|
|
||||||
|
|
||||||
it('Should fail with a bad filter', async function () {
|
|
||||||
await testEndpoints(server, server.accessToken, 'bad-filter', HttpStatusCode.BAD_REQUEST_400)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Should succeed with a good filter', async function () {
|
|
||||||
await testEndpoints(server, server.accessToken, 'local', HttpStatusCode.OK_200)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Should fail to list all-local/all with a simple user', async function () {
|
|
||||||
await testEndpoints(server, userAccessToken, 'all-local', HttpStatusCode.UNAUTHORIZED_401)
|
|
||||||
await testEndpoints(server, userAccessToken, 'all', HttpStatusCode.UNAUTHORIZED_401)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Should succeed to list all-local/all with a moderator', async function () {
|
|
||||||
await testEndpoints(server, moderatorAccessToken, 'all-local', HttpStatusCode.OK_200)
|
|
||||||
await testEndpoints(server, moderatorAccessToken, 'all', HttpStatusCode.OK_200)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Should succeed to list all-local/all with an admin', async function () {
|
|
||||||
await testEndpoints(server, server.accessToken, 'all-local', HttpStatusCode.OK_200)
|
|
||||||
await testEndpoints(server, server.accessToken, 'all', HttpStatusCode.OK_200)
|
|
||||||
})
|
|
||||||
|
|
||||||
// Because we cannot authenticate the user on the RSS endpoint
|
|
||||||
it('Should fail on the feeds endpoint with the all-local/all filter', async function () {
|
|
||||||
for (const filter of [ 'all', 'all-local' ]) {
|
|
||||||
await makeGetRequest({
|
|
||||||
url: server.url,
|
|
||||||
path: '/feeds/videos.json',
|
|
||||||
expectedStatus: HttpStatusCode.UNAUTHORIZED_401,
|
|
||||||
query: {
|
|
||||||
filter
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Should succeed on the feeds endpoint with the local filter', async function () {
|
|
||||||
await makeGetRequest({
|
|
||||||
url: server.url,
|
|
||||||
path: '/feeds/videos.json',
|
|
||||||
expectedStatus: HttpStatusCode.OK_200,
|
|
||||||
query: {
|
|
||||||
filter: 'local'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
after(async function () {
|
|
||||||
await cleanupTests([ server ])
|
|
||||||
})
|
|
||||||
})
|
|
|
@ -15,7 +15,7 @@ import './video-playlist-thumbnails'
|
||||||
import './video-privacy'
|
import './video-privacy'
|
||||||
import './video-schedule-update'
|
import './video-schedule-update'
|
||||||
import './video-transcoder'
|
import './video-transcoder'
|
||||||
import './videos-filter'
|
import './videos-common-filters'
|
||||||
import './videos-history'
|
import './videos-history'
|
||||||
import './videos-overview'
|
import './videos-overview'
|
||||||
import './videos-views-cleaner'
|
import './videos-views-cleaner'
|
||||||
|
|
|
@ -349,7 +349,7 @@ describe('Test multiple servers', function () {
|
||||||
|
|
||||||
describe('It should list local videos', function () {
|
describe('It should list local videos', function () {
|
||||||
it('Should list only local videos on server 1', async function () {
|
it('Should list only local videos on server 1', async function () {
|
||||||
const { data, total } = await servers[0].videos.list({ filter: 'local' })
|
const { data, total } = await servers[0].videos.list({ isLocal: true })
|
||||||
|
|
||||||
expect(total).to.equal(1)
|
expect(total).to.equal(1)
|
||||||
expect(data).to.be.an('array')
|
expect(data).to.be.an('array')
|
||||||
|
@ -358,7 +358,7 @@ describe('Test multiple servers', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should list only local videos on server 2', async function () {
|
it('Should list only local videos on server 2', async function () {
|
||||||
const { data, total } = await servers[1].videos.list({ filter: 'local' })
|
const { data, total } = await servers[1].videos.list({ isLocal: true })
|
||||||
|
|
||||||
expect(total).to.equal(1)
|
expect(total).to.equal(1)
|
||||||
expect(data).to.be.an('array')
|
expect(data).to.be.an('array')
|
||||||
|
@ -367,7 +367,7 @@ describe('Test multiple servers', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should list only local videos on server 3', async function () {
|
it('Should list only local videos on server 3', async function () {
|
||||||
const { data, total } = await servers[2].videos.list({ filter: 'local' })
|
const { data, total } = await servers[2].videos.list({ isLocal: true })
|
||||||
|
|
||||||
expect(total).to.equal(2)
|
expect(total).to.equal(2)
|
||||||
expect(data).to.be.an('array')
|
expect(data).to.be.an('array')
|
||||||
|
|
|
@ -354,19 +354,6 @@ describe('Test a single server', function () {
|
||||||
await server.videos.update({ id: videoId, attributes })
|
await server.videos.update({ id: videoId, attributes })
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should filter by tags and category', async function () {
|
|
||||||
{
|
|
||||||
const { data, total } = await server.videos.list({ tagsAllOf: [ 'tagup1', 'tagup2' ], categoryOneOf: [ 4 ] })
|
|
||||||
expect(total).to.equal(1)
|
|
||||||
expect(data[0].name).to.equal('my super video updated')
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
const { total } = await server.videos.list({ tagsAllOf: [ 'tagup1', 'tagup2' ], categoryOneOf: [ 3 ] })
|
|
||||||
expect(total).to.equal(0)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Should have the video updated', async function () {
|
it('Should have the video updated', async function () {
|
||||||
this.timeout(60000)
|
this.timeout(60000)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,403 @@
|
||||||
|
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
|
||||||
|
|
||||||
|
import 'mocha'
|
||||||
|
import { expect } from 'chai'
|
||||||
|
import { pick } from '@shared/core-utils'
|
||||||
|
import {
|
||||||
|
cleanupTests,
|
||||||
|
createMultipleServers,
|
||||||
|
doubleFollow,
|
||||||
|
makeGetRequest,
|
||||||
|
PeerTubeServer,
|
||||||
|
setAccessTokensToServers,
|
||||||
|
setDefaultVideoChannel,
|
||||||
|
waitJobs
|
||||||
|
} from '@shared/extra-utils'
|
||||||
|
import { HttpStatusCode, UserRole, Video, VideoInclude, VideoPrivacy } from '@shared/models'
|
||||||
|
|
||||||
|
describe('Test videos filter', function () {
|
||||||
|
let servers: PeerTubeServer[]
|
||||||
|
let paths: string[]
|
||||||
|
let remotePaths: string[]
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------
|
||||||
|
|
||||||
|
before(async function () {
|
||||||
|
this.timeout(160000)
|
||||||
|
|
||||||
|
servers = await createMultipleServers(2)
|
||||||
|
|
||||||
|
await setAccessTokensToServers(servers)
|
||||||
|
await setDefaultVideoChannel(servers)
|
||||||
|
|
||||||
|
for (const server of servers) {
|
||||||
|
const moderator = { username: 'moderator', password: 'my super password' }
|
||||||
|
await server.users.create({ username: moderator.username, password: moderator.password, role: UserRole.MODERATOR })
|
||||||
|
server['moderatorAccessToken'] = await server.login.getAccessToken(moderator)
|
||||||
|
|
||||||
|
await server.videos.upload({ attributes: { name: 'public ' + server.serverNumber } })
|
||||||
|
|
||||||
|
{
|
||||||
|
const attributes = { name: 'unlisted ' + server.serverNumber, privacy: VideoPrivacy.UNLISTED }
|
||||||
|
await server.videos.upload({ attributes })
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const attributes = { name: 'private ' + server.serverNumber, privacy: VideoPrivacy.PRIVATE }
|
||||||
|
await server.videos.upload({ attributes })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await doubleFollow(servers[0], servers[1])
|
||||||
|
|
||||||
|
paths = [
|
||||||
|
`/api/v1/video-channels/root_channel/videos`,
|
||||||
|
`/api/v1/accounts/root/videos`,
|
||||||
|
'/api/v1/videos',
|
||||||
|
'/api/v1/search/videos'
|
||||||
|
]
|
||||||
|
|
||||||
|
remotePaths = [
|
||||||
|
`/api/v1/video-channels/root_channel@${servers[1].host}/videos`,
|
||||||
|
`/api/v1/accounts/root@${servers[1].host}/videos`,
|
||||||
|
'/api/v1/videos',
|
||||||
|
'/api/v1/search/videos'
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Check deprecated videos filter', function () {
|
||||||
|
|
||||||
|
async function getVideosNames (server: PeerTubeServer, token: string, filter: string, expectedStatus = HttpStatusCode.OK_200) {
|
||||||
|
const videosResults: Video[][] = []
|
||||||
|
|
||||||
|
for (const path of paths) {
|
||||||
|
const res = await makeGetRequest({
|
||||||
|
url: server.url,
|
||||||
|
path,
|
||||||
|
token,
|
||||||
|
query: {
|
||||||
|
sort: 'createdAt',
|
||||||
|
filter
|
||||||
|
},
|
||||||
|
expectedStatus
|
||||||
|
})
|
||||||
|
|
||||||
|
videosResults.push(res.body.data.map(v => v.name))
|
||||||
|
}
|
||||||
|
|
||||||
|
return videosResults
|
||||||
|
}
|
||||||
|
|
||||||
|
it('Should display local videos', async function () {
|
||||||
|
for (const server of servers) {
|
||||||
|
const namesResults = await getVideosNames(server, server.accessToken, 'local')
|
||||||
|
for (const names of namesResults) {
|
||||||
|
expect(names).to.have.lengthOf(1)
|
||||||
|
expect(names[0]).to.equal('public ' + server.serverNumber)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should display all local videos by the admin or the moderator', async function () {
|
||||||
|
for (const server of servers) {
|
||||||
|
for (const token of [ server.accessToken, server['moderatorAccessToken'] ]) {
|
||||||
|
|
||||||
|
const namesResults = await getVideosNames(server, token, 'all-local')
|
||||||
|
for (const names of namesResults) {
|
||||||
|
expect(names).to.have.lengthOf(3)
|
||||||
|
|
||||||
|
expect(names[0]).to.equal('public ' + server.serverNumber)
|
||||||
|
expect(names[1]).to.equal('unlisted ' + server.serverNumber)
|
||||||
|
expect(names[2]).to.equal('private ' + server.serverNumber)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should display all videos by the admin or the moderator', async function () {
|
||||||
|
for (const server of servers) {
|
||||||
|
for (const token of [ server.accessToken, server['moderatorAccessToken'] ]) {
|
||||||
|
|
||||||
|
const [ channelVideos, accountVideos, videos, searchVideos ] = await getVideosNames(server, token, 'all')
|
||||||
|
expect(channelVideos).to.have.lengthOf(3)
|
||||||
|
expect(accountVideos).to.have.lengthOf(3)
|
||||||
|
|
||||||
|
expect(videos).to.have.lengthOf(5)
|
||||||
|
expect(searchVideos).to.have.lengthOf(5)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Check videos filters', function () {
|
||||||
|
|
||||||
|
async function listVideos (options: {
|
||||||
|
server: PeerTubeServer
|
||||||
|
path: string
|
||||||
|
isLocal?: boolean
|
||||||
|
include?: VideoInclude
|
||||||
|
category?: number
|
||||||
|
tagsAllOf?: string[]
|
||||||
|
token?: string
|
||||||
|
expectedStatus?: HttpStatusCode
|
||||||
|
}) {
|
||||||
|
const res = await makeGetRequest({
|
||||||
|
url: options.server.url,
|
||||||
|
path: options.path,
|
||||||
|
token: options.token ?? options.server.accessToken,
|
||||||
|
query: {
|
||||||
|
...pick(options, [ 'isLocal', 'include', 'category', 'tagsAllOf' ]),
|
||||||
|
|
||||||
|
sort: 'createdAt'
|
||||||
|
},
|
||||||
|
expectedStatus: options.expectedStatus ?? HttpStatusCode.OK_200
|
||||||
|
})
|
||||||
|
|
||||||
|
return res.body.data as Video[]
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getVideosNames (options: {
|
||||||
|
server: PeerTubeServer
|
||||||
|
isLocal?: boolean
|
||||||
|
include?: VideoInclude
|
||||||
|
token?: string
|
||||||
|
expectedStatus?: HttpStatusCode
|
||||||
|
}) {
|
||||||
|
const videosResults: string[][] = []
|
||||||
|
|
||||||
|
for (const path of paths) {
|
||||||
|
const videos = await listVideos({ ...options, path })
|
||||||
|
|
||||||
|
videosResults.push(videos.map(v => v.name))
|
||||||
|
}
|
||||||
|
|
||||||
|
return videosResults
|
||||||
|
}
|
||||||
|
|
||||||
|
it('Should display local videos', async function () {
|
||||||
|
for (const server of servers) {
|
||||||
|
const namesResults = await getVideosNames({ server, isLocal: true })
|
||||||
|
|
||||||
|
for (const names of namesResults) {
|
||||||
|
expect(names).to.have.lengthOf(1)
|
||||||
|
expect(names[0]).to.equal('public ' + server.serverNumber)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should display local videos with hidden privacy by the admin or the moderator', async function () {
|
||||||
|
for (const server of servers) {
|
||||||
|
for (const token of [ server.accessToken, server['moderatorAccessToken'] ]) {
|
||||||
|
|
||||||
|
const namesResults = await getVideosNames({
|
||||||
|
server,
|
||||||
|
token,
|
||||||
|
isLocal: true,
|
||||||
|
include: VideoInclude.HIDDEN_PRIVACY
|
||||||
|
})
|
||||||
|
|
||||||
|
for (const names of namesResults) {
|
||||||
|
expect(names).to.have.lengthOf(3)
|
||||||
|
|
||||||
|
expect(names[0]).to.equal('public ' + server.serverNumber)
|
||||||
|
expect(names[1]).to.equal('unlisted ' + server.serverNumber)
|
||||||
|
expect(names[2]).to.equal('private ' + server.serverNumber)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should display all videos by the admin or the moderator', async function () {
|
||||||
|
for (const server of servers) {
|
||||||
|
for (const token of [ server.accessToken, server['moderatorAccessToken'] ]) {
|
||||||
|
|
||||||
|
const [ channelVideos, accountVideos, videos, searchVideos ] = await getVideosNames({
|
||||||
|
server,
|
||||||
|
token,
|
||||||
|
include: VideoInclude.HIDDEN_PRIVACY
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(channelVideos).to.have.lengthOf(3)
|
||||||
|
expect(accountVideos).to.have.lengthOf(3)
|
||||||
|
|
||||||
|
expect(videos).to.have.lengthOf(5)
|
||||||
|
expect(searchVideos).to.have.lengthOf(5)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should display only remote videos', async function () {
|
||||||
|
this.timeout(40000)
|
||||||
|
|
||||||
|
await servers[1].videos.upload({ attributes: { name: 'remote video' } })
|
||||||
|
|
||||||
|
await waitJobs(servers)
|
||||||
|
|
||||||
|
const finder = (videos: Video[]) => videos.find(v => v.name === 'remote video')
|
||||||
|
|
||||||
|
for (const path of remotePaths) {
|
||||||
|
{
|
||||||
|
const videos = await listVideos({ server: servers[0], path })
|
||||||
|
const video = finder(videos)
|
||||||
|
expect(video).to.exist
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const videos = await listVideos({ server: servers[0], path, isLocal: false })
|
||||||
|
const video = finder(videos)
|
||||||
|
expect(video).to.exist
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const videos = await listVideos({ server: servers[0], path, isLocal: true })
|
||||||
|
const video = finder(videos)
|
||||||
|
expect(video).to.not.exist
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should include not published videos', async function () {
|
||||||
|
await servers[0].config.enableLive({ allowReplay: false, transcoding: false })
|
||||||
|
await servers[0].live.create({ fields: { name: 'live video', channelId: servers[0].store.channel.id, privacy: VideoPrivacy.PUBLIC } })
|
||||||
|
|
||||||
|
const finder = (videos: Video[]) => videos.find(v => v.name === 'live video')
|
||||||
|
|
||||||
|
for (const path of paths) {
|
||||||
|
{
|
||||||
|
const videos = await listVideos({ server: servers[0], path })
|
||||||
|
const video = finder(videos)
|
||||||
|
expect(video).to.not.exist
|
||||||
|
expect(videos[0].state).to.not.exist
|
||||||
|
expect(videos[0].waitTranscoding).to.not.exist
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const videos = await listVideos({ server: servers[0], path, include: VideoInclude.NOT_PUBLISHED_STATE })
|
||||||
|
const video = finder(videos)
|
||||||
|
expect(video).to.exist
|
||||||
|
expect(video.state).to.exist
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should include blacklisted videos', async function () {
|
||||||
|
const { id } = await servers[0].videos.upload({ attributes: { name: 'blacklisted' } })
|
||||||
|
|
||||||
|
await servers[0].blacklist.add({ videoId: id })
|
||||||
|
|
||||||
|
const finder = (videos: Video[]) => videos.find(v => v.name === 'blacklisted')
|
||||||
|
|
||||||
|
for (const path of paths) {
|
||||||
|
{
|
||||||
|
const videos = await listVideos({ server: servers[0], path })
|
||||||
|
const video = finder(videos)
|
||||||
|
expect(video).to.not.exist
|
||||||
|
expect(videos[0].blacklisted).to.not.exist
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const videos = await listVideos({ server: servers[0], path, include: VideoInclude.BLACKLISTED })
|
||||||
|
const video = finder(videos)
|
||||||
|
expect(video).to.exist
|
||||||
|
expect(video.blacklisted).to.be.true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should include videos from muted account', async function () {
|
||||||
|
const finder = (videos: Video[]) => videos.find(v => v.name === 'remote video')
|
||||||
|
|
||||||
|
await servers[0].blocklist.addToServerBlocklist({ account: 'root@' + servers[1].host })
|
||||||
|
|
||||||
|
for (const path of remotePaths) {
|
||||||
|
{
|
||||||
|
const videos = await listVideos({ server: servers[0], path })
|
||||||
|
const video = finder(videos)
|
||||||
|
expect(video).to.not.exist
|
||||||
|
|
||||||
|
// Some paths won't have videos
|
||||||
|
if (videos[0]) {
|
||||||
|
expect(videos[0].blockedOwner).to.not.exist
|
||||||
|
expect(videos[0].blockedServer).to.not.exist
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const videos = await listVideos({ server: servers[0], path, include: VideoInclude.BLOCKED_OWNER })
|
||||||
|
|
||||||
|
const video = finder(videos)
|
||||||
|
expect(video).to.exist
|
||||||
|
expect(video.blockedServer).to.be.false
|
||||||
|
expect(video.blockedOwner).to.be.true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await servers[0].blocklist.removeFromServerBlocklist({ account: 'root@' + servers[1].host })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should include videos from muted server', async function () {
|
||||||
|
const finder = (videos: Video[]) => videos.find(v => v.name === 'remote video')
|
||||||
|
|
||||||
|
await servers[0].blocklist.addToServerBlocklist({ server: servers[1].host })
|
||||||
|
|
||||||
|
for (const path of remotePaths) {
|
||||||
|
{
|
||||||
|
const videos = await listVideos({ server: servers[0], path })
|
||||||
|
const video = finder(videos)
|
||||||
|
expect(video).to.not.exist
|
||||||
|
|
||||||
|
// Some paths won't have videos
|
||||||
|
if (videos[0]) {
|
||||||
|
expect(videos[0].blockedOwner).to.not.exist
|
||||||
|
expect(videos[0].blockedServer).to.not.exist
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const videos = await listVideos({ server: servers[0], path, include: VideoInclude.BLOCKED_OWNER })
|
||||||
|
const video = finder(videos)
|
||||||
|
expect(video).to.exist
|
||||||
|
expect(video.blockedServer).to.be.true
|
||||||
|
expect(video.blockedOwner).to.be.false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await servers[0].blocklist.removeFromServerBlocklist({ server: servers[1].host })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should filter by tags and category', async function () {
|
||||||
|
await servers[0].videos.upload({ attributes: { name: 'tag filter', tags: [ 'tag1', 'tag2' ] } })
|
||||||
|
await servers[0].videos.upload({ attributes: { name: 'tag filter with category', tags: [ 'tag3' ], category: 4 } })
|
||||||
|
|
||||||
|
for (const path of paths) {
|
||||||
|
{
|
||||||
|
|
||||||
|
const videos = await listVideos({ server: servers[0], path, tagsAllOf: [ 'tag1', 'tag2' ] })
|
||||||
|
expect(videos).to.have.lengthOf(1)
|
||||||
|
expect(videos[0].name).to.equal('tag filter')
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const videos = await listVideos({ server: servers[0], path, tagsAllOf: [ 'tag1', 'tag3' ] })
|
||||||
|
expect(videos).to.have.lengthOf(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const { data, total } = await servers[0].videos.list({ tagsAllOf: [ 'tag3' ], categoryOneOf: [ 4 ] })
|
||||||
|
expect(total).to.equal(1)
|
||||||
|
expect(data[0].name).to.equal('tag filter with category')
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const { total } = await servers[0].videos.list({ tagsAllOf: [ 'tag4' ], categoryOneOf: [ 4 ] })
|
||||||
|
expect(total).to.equal(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
after(async function () {
|
||||||
|
await cleanupTests(servers)
|
||||||
|
})
|
||||||
|
})
|
|
@ -1,122 +0,0 @@
|
||||||
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
|
|
||||||
|
|
||||||
import 'mocha'
|
|
||||||
import { expect } from 'chai'
|
|
||||||
import {
|
|
||||||
cleanupTests,
|
|
||||||
createMultipleServers,
|
|
||||||
doubleFollow,
|
|
||||||
makeGetRequest,
|
|
||||||
PeerTubeServer,
|
|
||||||
setAccessTokensToServers
|
|
||||||
} from '@shared/extra-utils'
|
|
||||||
import { HttpStatusCode, UserRole, Video, VideoPrivacy } from '@shared/models'
|
|
||||||
|
|
||||||
async function getVideosNames (server: PeerTubeServer, token: string, filter: string, expectedStatus = HttpStatusCode.OK_200) {
|
|
||||||
const paths = [
|
|
||||||
'/api/v1/video-channels/root_channel/videos',
|
|
||||||
'/api/v1/accounts/root/videos',
|
|
||||||
'/api/v1/videos',
|
|
||||||
'/api/v1/search/videos'
|
|
||||||
]
|
|
||||||
|
|
||||||
const videosResults: Video[][] = []
|
|
||||||
|
|
||||||
for (const path of paths) {
|
|
||||||
const res = await makeGetRequest({
|
|
||||||
url: server.url,
|
|
||||||
path,
|
|
||||||
token,
|
|
||||||
query: {
|
|
||||||
sort: 'createdAt',
|
|
||||||
filter
|
|
||||||
},
|
|
||||||
expectedStatus
|
|
||||||
})
|
|
||||||
|
|
||||||
videosResults.push(res.body.data.map(v => v.name))
|
|
||||||
}
|
|
||||||
|
|
||||||
return videosResults
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('Test videos filter', function () {
|
|
||||||
let servers: PeerTubeServer[]
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------
|
|
||||||
|
|
||||||
before(async function () {
|
|
||||||
this.timeout(160000)
|
|
||||||
|
|
||||||
servers = await createMultipleServers(2)
|
|
||||||
|
|
||||||
await setAccessTokensToServers(servers)
|
|
||||||
|
|
||||||
for (const server of servers) {
|
|
||||||
const moderator = { username: 'moderator', password: 'my super password' }
|
|
||||||
await server.users.create({ username: moderator.username, password: moderator.password, role: UserRole.MODERATOR })
|
|
||||||
server['moderatorAccessToken'] = await server.login.getAccessToken(moderator)
|
|
||||||
|
|
||||||
await server.videos.upload({ attributes: { name: 'public ' + server.serverNumber } })
|
|
||||||
|
|
||||||
{
|
|
||||||
const attributes = { name: 'unlisted ' + server.serverNumber, privacy: VideoPrivacy.UNLISTED }
|
|
||||||
await server.videos.upload({ attributes })
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
const attributes = { name: 'private ' + server.serverNumber, privacy: VideoPrivacy.PRIVATE }
|
|
||||||
await server.videos.upload({ attributes })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await doubleFollow(servers[0], servers[1])
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('Check videos filter', function () {
|
|
||||||
|
|
||||||
it('Should display local videos', async function () {
|
|
||||||
for (const server of servers) {
|
|
||||||
const namesResults = await getVideosNames(server, server.accessToken, 'local')
|
|
||||||
for (const names of namesResults) {
|
|
||||||
expect(names).to.have.lengthOf(1)
|
|
||||||
expect(names[0]).to.equal('public ' + server.serverNumber)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Should display all local videos by the admin or the moderator', async function () {
|
|
||||||
for (const server of servers) {
|
|
||||||
for (const token of [ server.accessToken, server['moderatorAccessToken'] ]) {
|
|
||||||
|
|
||||||
const namesResults = await getVideosNames(server, token, 'all-local')
|
|
||||||
for (const names of namesResults) {
|
|
||||||
expect(names).to.have.lengthOf(3)
|
|
||||||
|
|
||||||
expect(names[0]).to.equal('public ' + server.serverNumber)
|
|
||||||
expect(names[1]).to.equal('unlisted ' + server.serverNumber)
|
|
||||||
expect(names[2]).to.equal('private ' + server.serverNumber)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Should display all videos by the admin or the moderator', async function () {
|
|
||||||
for (const server of servers) {
|
|
||||||
for (const token of [ server.accessToken, server['moderatorAccessToken'] ]) {
|
|
||||||
|
|
||||||
const [ channelVideos, accountVideos, videos, searchVideos ] = await getVideosNames(server, token, 'all')
|
|
||||||
expect(channelVideos).to.have.lengthOf(3)
|
|
||||||
expect(accountVideos).to.have.lengthOf(3)
|
|
||||||
|
|
||||||
expect(videos).to.have.lengthOf(5)
|
|
||||||
expect(searchVideos).to.have.lengthOf(5)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
after(async function () {
|
|
||||||
await cleanupTests(servers)
|
|
||||||
})
|
|
||||||
})
|
|
|
@ -84,7 +84,7 @@ export type MAccountSummary =
|
||||||
|
|
||||||
export type MAccountSummaryBlocks =
|
export type MAccountSummaryBlocks =
|
||||||
MAccountSummary &
|
MAccountSummary &
|
||||||
Use<'BlockedAccounts', MAccountBlocklistId[]>
|
Use<'BlockedByAccounts', MAccountBlocklistId[]>
|
||||||
|
|
||||||
export type MAccountAPI =
|
export type MAccountAPI =
|
||||||
MAccount &
|
MAccount &
|
||||||
|
|
|
@ -18,8 +18,7 @@ import {
|
||||||
VideoDetails,
|
VideoDetails,
|
||||||
VideoFileMetadata,
|
VideoFileMetadata,
|
||||||
VideoPrivacy,
|
VideoPrivacy,
|
||||||
VideosCommonQuery,
|
VideosCommonQuery
|
||||||
VideosWithSearchCommonQuery
|
|
||||||
} from '@shared/models'
|
} from '@shared/models'
|
||||||
import { buildAbsoluteFixturePath, wait } from '../miscs'
|
import { buildAbsoluteFixturePath, wait } from '../miscs'
|
||||||
import { unwrapBody } from '../requests'
|
import { unwrapBody } from '../requests'
|
||||||
|
@ -246,7 +245,7 @@ export class VideosCommand extends AbstractCommand {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
listByAccount (options: OverrideCommandOptions & VideosWithSearchCommonQuery & {
|
listByAccount (options: OverrideCommandOptions & VideosCommonQuery & {
|
||||||
handle: string
|
handle: string
|
||||||
}) {
|
}) {
|
||||||
const { handle, search } = options
|
const { handle, search } = options
|
||||||
|
@ -262,7 +261,7 @@ export class VideosCommand extends AbstractCommand {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
listByChannel (options: OverrideCommandOptions & VideosWithSearchCommonQuery & {
|
listByChannel (options: OverrideCommandOptions & VideosCommonQuery & {
|
||||||
handle: string
|
handle: string
|
||||||
}) {
|
}) {
|
||||||
const { handle } = options
|
const { handle } = options
|
||||||
|
@ -605,7 +604,8 @@ export class VideosCommand extends AbstractCommand {
|
||||||
'languageOneOf',
|
'languageOneOf',
|
||||||
'tagsOneOf',
|
'tagsOneOf',
|
||||||
'tagsAllOf',
|
'tagsAllOf',
|
||||||
'filter',
|
'isLocal',
|
||||||
|
'include',
|
||||||
'skipCount'
|
'skipCount'
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { VideoFilter } from '../videos'
|
import { VideoInclude } from '../videos/video-include.enum'
|
||||||
import { BooleanBothQuery } from './boolean-both-query.model'
|
import { BooleanBothQuery } from './boolean-both-query.model'
|
||||||
|
|
||||||
// These query parameters can be used with any endpoint that list videos
|
// These query parameters can be used with any endpoint that list videos
|
||||||
|
@ -11,6 +11,12 @@ export interface VideosCommonQuery {
|
||||||
|
|
||||||
isLive?: boolean
|
isLive?: boolean
|
||||||
|
|
||||||
|
// FIXME: deprecated in 4.0 in favour of isLocal and include, to remove
|
||||||
|
filter?: never
|
||||||
|
|
||||||
|
isLocal?: boolean
|
||||||
|
include?: VideoInclude
|
||||||
|
|
||||||
categoryOneOf?: number[]
|
categoryOneOf?: number[]
|
||||||
|
|
||||||
licenceOneOf?: number[]
|
licenceOneOf?: number[]
|
||||||
|
@ -20,17 +26,16 @@ export interface VideosCommonQuery {
|
||||||
tagsOneOf?: string[]
|
tagsOneOf?: string[]
|
||||||
tagsAllOf?: string[]
|
tagsAllOf?: string[]
|
||||||
|
|
||||||
filter?: VideoFilter
|
|
||||||
|
|
||||||
skipCount?: boolean
|
skipCount?: boolean
|
||||||
|
|
||||||
|
search?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface VideosCommonQueryAfterSanitize extends VideosCommonQuery {
|
export interface VideosCommonQueryAfterSanitize extends VideosCommonQuery {
|
||||||
start: number
|
start: number
|
||||||
count: number
|
count: number
|
||||||
sort: string
|
sort: string
|
||||||
}
|
|
||||||
|
|
||||||
export interface VideosWithSearchCommonQuery extends VideosCommonQuery {
|
// FIXME: deprecated in 4.0, to remove
|
||||||
search?: string
|
filter?: never
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,4 +23,7 @@ export interface VideosSearchQueryAfterSanitize extends VideosSearchQuery {
|
||||||
start: number
|
start: number
|
||||||
count: number
|
count: number
|
||||||
sort: string
|
sort: string
|
||||||
|
|
||||||
|
// FIXME: deprecated in 4.0, to remove
|
||||||
|
filter?: never
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,8 @@ export * from './video-file-metadata.model'
|
||||||
export * from './video-file.model'
|
export * from './video-file.model'
|
||||||
|
|
||||||
export * from './video-privacy.enum'
|
export * from './video-privacy.enum'
|
||||||
export * from './video-query.type'
|
export * from './video-filter.type'
|
||||||
|
export * from './video-include.enum'
|
||||||
export * from './video-rate.type'
|
export * from './video-rate.type'
|
||||||
export * from './video-resolution.enum'
|
export * from './video-resolution.enum'
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
export const enum VideoInclude {
|
||||||
|
NONE = 0,
|
||||||
|
NOT_PUBLISHED_STATE = 1 << 0,
|
||||||
|
HIDDEN_PRIVACY = 1 << 1,
|
||||||
|
BLACKLISTED = 1 << 2,
|
||||||
|
BLOCKED_OWNER = 1 << 3
|
||||||
|
}
|
|
@ -43,13 +43,6 @@ export interface Video {
|
||||||
dislikes: number
|
dislikes: number
|
||||||
nsfw: boolean
|
nsfw: boolean
|
||||||
|
|
||||||
waitTranscoding?: boolean
|
|
||||||
state?: VideoConstant<VideoState>
|
|
||||||
scheduledUpdate?: VideoScheduleUpdate
|
|
||||||
|
|
||||||
blacklisted?: boolean
|
|
||||||
blacklistedReason?: string
|
|
||||||
|
|
||||||
account: AccountSummary
|
account: AccountSummary
|
||||||
channel: VideoChannelSummary
|
channel: VideoChannelSummary
|
||||||
|
|
||||||
|
@ -58,6 +51,17 @@ export interface Video {
|
||||||
}
|
}
|
||||||
|
|
||||||
pluginData?: any
|
pluginData?: any
|
||||||
|
|
||||||
|
// Additional attributes dependending on the query
|
||||||
|
waitTranscoding?: boolean
|
||||||
|
state?: VideoConstant<VideoState>
|
||||||
|
scheduledUpdate?: VideoScheduleUpdate
|
||||||
|
|
||||||
|
blacklisted?: boolean
|
||||||
|
blacklistedReason?: string
|
||||||
|
|
||||||
|
blockedOwner?: boolean
|
||||||
|
blockedServer?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface VideoDetails extends Video {
|
export interface VideoDetails extends Video {
|
||||||
|
@ -70,7 +74,7 @@ export interface VideoDetails extends Video {
|
||||||
commentsEnabled: boolean
|
commentsEnabled: boolean
|
||||||
downloadEnabled: boolean
|
downloadEnabled: boolean
|
||||||
|
|
||||||
// Not optional in details (unlike in Video)
|
// Not optional in details (unlike in parent Video)
|
||||||
waitTranscoding: boolean
|
waitTranscoding: boolean
|
||||||
state: VideoConstant<VideoState>
|
state: VideoConstant<VideoState>
|
||||||
|
|
||||||
|
|
|
@ -367,7 +367,8 @@ paths:
|
||||||
- $ref: '#/components/parameters/licenceOneOf'
|
- $ref: '#/components/parameters/licenceOneOf'
|
||||||
- $ref: '#/components/parameters/languageOneOf'
|
- $ref: '#/components/parameters/languageOneOf'
|
||||||
- $ref: '#/components/parameters/nsfw'
|
- $ref: '#/components/parameters/nsfw'
|
||||||
- $ref: '#/components/parameters/filter'
|
- $ref: '#/components/parameters/isLocal'
|
||||||
|
- $ref: '#/components/parameters/include'
|
||||||
- $ref: '#/components/parameters/skipCount'
|
- $ref: '#/components/parameters/skipCount'
|
||||||
- $ref: '#/components/parameters/start'
|
- $ref: '#/components/parameters/start'
|
||||||
- $ref: '#/components/parameters/count'
|
- $ref: '#/components/parameters/count'
|
||||||
|
@ -1300,7 +1301,8 @@ paths:
|
||||||
- $ref: '#/components/parameters/licenceOneOf'
|
- $ref: '#/components/parameters/licenceOneOf'
|
||||||
- $ref: '#/components/parameters/languageOneOf'
|
- $ref: '#/components/parameters/languageOneOf'
|
||||||
- $ref: '#/components/parameters/nsfw'
|
- $ref: '#/components/parameters/nsfw'
|
||||||
- $ref: '#/components/parameters/filter'
|
- $ref: '#/components/parameters/isLocal'
|
||||||
|
- $ref: '#/components/parameters/include'
|
||||||
- $ref: '#/components/parameters/skipCount'
|
- $ref: '#/components/parameters/skipCount'
|
||||||
- $ref: '#/components/parameters/start'
|
- $ref: '#/components/parameters/start'
|
||||||
- $ref: '#/components/parameters/count'
|
- $ref: '#/components/parameters/count'
|
||||||
|
@ -1620,7 +1622,8 @@ paths:
|
||||||
- $ref: '#/components/parameters/licenceOneOf'
|
- $ref: '#/components/parameters/licenceOneOf'
|
||||||
- $ref: '#/components/parameters/languageOneOf'
|
- $ref: '#/components/parameters/languageOneOf'
|
||||||
- $ref: '#/components/parameters/nsfw'
|
- $ref: '#/components/parameters/nsfw'
|
||||||
- $ref: '#/components/parameters/filter'
|
- $ref: '#/components/parameters/isLocal'
|
||||||
|
- $ref: '#/components/parameters/include'
|
||||||
- $ref: '#/components/parameters/skipCount'
|
- $ref: '#/components/parameters/skipCount'
|
||||||
- $ref: '#/components/parameters/start'
|
- $ref: '#/components/parameters/start'
|
||||||
- $ref: '#/components/parameters/count'
|
- $ref: '#/components/parameters/count'
|
||||||
|
@ -2856,7 +2859,8 @@ paths:
|
||||||
- $ref: '#/components/parameters/licenceOneOf'
|
- $ref: '#/components/parameters/licenceOneOf'
|
||||||
- $ref: '#/components/parameters/languageOneOf'
|
- $ref: '#/components/parameters/languageOneOf'
|
||||||
- $ref: '#/components/parameters/nsfw'
|
- $ref: '#/components/parameters/nsfw'
|
||||||
- $ref: '#/components/parameters/filter'
|
- $ref: '#/components/parameters/isLocal'
|
||||||
|
- $ref: '#/components/parameters/include'
|
||||||
- $ref: '#/components/parameters/skipCount'
|
- $ref: '#/components/parameters/skipCount'
|
||||||
- $ref: '#/components/parameters/start'
|
- $ref: '#/components/parameters/start'
|
||||||
- $ref: '#/components/parameters/count'
|
- $ref: '#/components/parameters/count'
|
||||||
|
@ -3576,7 +3580,8 @@ paths:
|
||||||
- $ref: '#/components/parameters/licenceOneOf'
|
- $ref: '#/components/parameters/licenceOneOf'
|
||||||
- $ref: '#/components/parameters/languageOneOf'
|
- $ref: '#/components/parameters/languageOneOf'
|
||||||
- $ref: '#/components/parameters/nsfw'
|
- $ref: '#/components/parameters/nsfw'
|
||||||
- $ref: '#/components/parameters/filter'
|
- $ref: '#/components/parameters/isLocal'
|
||||||
|
- $ref: '#/components/parameters/include'
|
||||||
- $ref: '#/components/parameters/skipCount'
|
- $ref: '#/components/parameters/skipCount'
|
||||||
- $ref: '#/components/parameters/start'
|
- $ref: '#/components/parameters/start'
|
||||||
- $ref: '#/components/parameters/count'
|
- $ref: '#/components/parameters/count'
|
||||||
|
@ -4078,7 +4083,8 @@ paths:
|
||||||
type: string
|
type: string
|
||||||
- $ref: '#/components/parameters/sort'
|
- $ref: '#/components/parameters/sort'
|
||||||
- $ref: '#/components/parameters/nsfw'
|
- $ref: '#/components/parameters/nsfw'
|
||||||
- $ref: '#/components/parameters/filter'
|
- $ref: '#/components/parameters/isLocal'
|
||||||
|
- $ref: '#/components/parameters/include'
|
||||||
responses:
|
responses:
|
||||||
'204':
|
'204':
|
||||||
description: successful operation
|
description: successful operation
|
||||||
|
@ -4159,7 +4165,8 @@ paths:
|
||||||
required: true
|
required: true
|
||||||
- $ref: '#/components/parameters/sort'
|
- $ref: '#/components/parameters/sort'
|
||||||
- $ref: '#/components/parameters/nsfw'
|
- $ref: '#/components/parameters/nsfw'
|
||||||
- $ref: '#/components/parameters/filter'
|
- $ref: '#/components/parameters/isLocal'
|
||||||
|
- $ref: '#/components/parameters/include'
|
||||||
responses:
|
responses:
|
||||||
'204':
|
'204':
|
||||||
description: successful operation
|
description: successful operation
|
||||||
|
@ -4792,20 +4799,37 @@ components:
|
||||||
enum:
|
enum:
|
||||||
- 'true'
|
- 'true'
|
||||||
- 'false'
|
- 'false'
|
||||||
filter:
|
isLocal:
|
||||||
name: filter
|
name: isLocal
|
||||||
in: query
|
in: query
|
||||||
required: false
|
required: false
|
||||||
description: >
|
|
||||||
Special filters which might require special rights:
|
|
||||||
* `local` - only videos local to the instance
|
|
||||||
* `all-local` - only videos local to the instance, but showing private and unlisted videos (requires Admin privileges)
|
|
||||||
* `all` - all videos, showing private and unlisted videos (requires Admin privileges)
|
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: boolean
|
||||||
|
description: 'Display only local or remote videos'
|
||||||
|
include:
|
||||||
|
name: include
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
enum:
|
enum:
|
||||||
- local
|
- 0
|
||||||
- all-local
|
- 1
|
||||||
|
- 2
|
||||||
|
- 4
|
||||||
|
- 8
|
||||||
|
description: >
|
||||||
|
Include additional videos in results (can be combined using bitwise or operator)
|
||||||
|
|
||||||
|
- `0` NONE
|
||||||
|
|
||||||
|
- `1` NOT_PUBLISHED_STATE
|
||||||
|
|
||||||
|
- `2` HIDDEN_PRIVACY
|
||||||
|
|
||||||
|
- `4` BLACKLISTED
|
||||||
|
|
||||||
|
- `8` BLOCKED
|
||||||
subscriptionsUris:
|
subscriptionsUris:
|
||||||
name: uris
|
name: uris
|
||||||
in: query
|
in: query
|
||||||
|
@ -6995,7 +7019,7 @@ components:
|
||||||
enum:
|
enum:
|
||||||
- 0
|
- 0
|
||||||
- 1
|
- 1
|
||||||
- 3
|
- 2
|
||||||
Notification:
|
Notification:
|
||||||
properties:
|
properties:
|
||||||
id:
|
id:
|
||||||
|
|
Loading…
Reference in New Issue