diff options
-rw-r--r-- | client/src/app/+admin/overview/videos/video-admin.service.ts | 24 | ||||
-rw-r--r-- | client/src/app/shared/shared-main/video/video.service.ts | 3 | ||||
-rw-r--r-- | client/src/app/shared/shared-video-miniature/video-filters.model.ts | 9 | ||||
-rw-r--r-- | server/controllers/client.ts | 2 | ||||
-rw-r--r-- | server/helpers/query.ts | 1 | ||||
-rw-r--r-- | server/middlewares/validators/videos/videos.ts | 13 | ||||
-rw-r--r-- | server/models/video/sql/videos-id-list-query-builder.ts | 18 | ||||
-rw-r--r-- | server/models/video/video.ts | 15 | ||||
-rw-r--r-- | server/tests/api/check-params/videos-common-filters.ts | 22 | ||||
-rw-r--r-- | server/tests/api/videos/videos-common-filters.ts | 8 | ||||
-rw-r--r-- | shared/core-utils/videos/index.ts | 1 | ||||
-rw-r--r-- | shared/core-utils/videos/privacy.ts | 9 | ||||
-rw-r--r-- | shared/models/search/videos-common-query.model.ts | 3 | ||||
-rw-r--r-- | shared/models/videos/video-include.enum.ts | 7 | ||||
-rw-r--r-- | support/doc/api/openapi.yaml | 20 |
15 files changed, 125 insertions, 30 deletions
diff --git a/client/src/app/+admin/overview/videos/video-admin.service.ts b/client/src/app/+admin/overview/videos/video-admin.service.ts index f80de7acd..6a0e8dade 100644 --- a/client/src/app/+admin/overview/videos/video-admin.service.ts +++ b/client/src/app/+admin/overview/videos/video-admin.service.ts | |||
@@ -5,7 +5,8 @@ import { Injectable } from '@angular/core' | |||
5 | import { RestExtractor, RestPagination, RestService } from '@app/core' | 5 | import { RestExtractor, RestPagination, RestService } from '@app/core' |
6 | import { AdvancedInputFilter } from '@app/shared/shared-forms' | 6 | import { AdvancedInputFilter } from '@app/shared/shared-forms' |
7 | import { CommonVideoParams, Video, VideoService } from '@app/shared/shared-main' | 7 | import { CommonVideoParams, Video, VideoService } from '@app/shared/shared-main' |
8 | import { ResultList, VideoInclude } from '@shared/models' | 8 | import { ResultList, VideoInclude, VideoPrivacy } from '@shared/models' |
9 | import { getAllPrivacies } from '@shared/core-utils' | ||
9 | 10 | ||
10 | @Injectable() | 11 | @Injectable() |
11 | export class VideoAdminService { | 12 | export class VideoAdminService { |
@@ -96,6 +97,10 @@ export class VideoAdminService { | |||
96 | { | 97 | { |
97 | value: 'excludeMuted', | 98 | value: 'excludeMuted', |
98 | label: $localize`Exclude muted accounts` | 99 | label: $localize`Exclude muted accounts` |
100 | }, | ||
101 | { | ||
102 | value: 'excludePublic', | ||
103 | label: $localize`Exclude public videos` | ||
99 | } | 104 | } |
100 | ] | 105 | ] |
101 | } | 106 | } |
@@ -105,11 +110,12 @@ export class VideoAdminService { | |||
105 | private buildAdminParamsFromSearch (search: string, params: HttpParams) { | 110 | private buildAdminParamsFromSearch (search: string, params: HttpParams) { |
106 | let include = VideoInclude.BLACKLISTED | | 111 | let include = VideoInclude.BLACKLISTED | |
107 | VideoInclude.BLOCKED_OWNER | | 112 | VideoInclude.BLOCKED_OWNER | |
108 | VideoInclude.HIDDEN_PRIVACY | | ||
109 | VideoInclude.NOT_PUBLISHED_STATE | | 113 | VideoInclude.NOT_PUBLISHED_STATE | |
110 | VideoInclude.FILES | 114 | VideoInclude.FILES |
111 | 115 | ||
112 | if (!search) return this.restService.addObjectParams(params, { include }) | 116 | let privacyOneOf = getAllPrivacies() |
117 | |||
118 | if (!search) return this.restService.addObjectParams(params, { include, privacyOneOf }) | ||
113 | 119 | ||
114 | const filters = this.restService.parseQueryStringFilter(search, { | 120 | const filters = this.restService.parseQueryStringFilter(search, { |
115 | isLocal: { | 121 | isLocal: { |
@@ -131,6 +137,10 @@ export class VideoAdminService { | |||
131 | excludeMuted: { | 137 | excludeMuted: { |
132 | prefix: 'excludeMuted', | 138 | prefix: 'excludeMuted', |
133 | handler: () => true | 139 | handler: () => true |
140 | }, | ||
141 | excludePublic: { | ||
142 | prefix: 'excludePublic', | ||
143 | handler: () => true | ||
134 | } | 144 | } |
135 | }) | 145 | }) |
136 | 146 | ||
@@ -140,6 +150,12 @@ export class VideoAdminService { | |||
140 | filters.excludeMuted = undefined | 150 | filters.excludeMuted = undefined |
141 | } | 151 | } |
142 | 152 | ||
143 | return this.restService.addObjectParams(params, { ...filters, include }) | 153 | if (filters.excludePublic) { |
154 | privacyOneOf = [ VideoPrivacy.PRIVATE, VideoPrivacy.UNLISTED, VideoPrivacy.INTERNAL ] | ||
155 | |||
156 | filters.excludePublic = undefined | ||
157 | } | ||
158 | |||
159 | return this.restService.addObjectParams(params, { ...filters, include, privacyOneOf }) | ||
144 | } | 160 | } |
145 | } | 161 | } |
diff --git a/client/src/app/shared/shared-main/video/video.service.ts b/client/src/app/shared/shared-main/video/video.service.ts index 6edcc3fe0..570e8e3be 100644 --- a/client/src/app/shared/shared-main/video/video.service.ts +++ b/client/src/app/shared/shared-main/video/video.service.ts | |||
@@ -38,6 +38,7 @@ export type CommonVideoParams = { | |||
38 | isLocal?: boolean | 38 | isLocal?: boolean |
39 | categoryOneOf?: number[] | 39 | categoryOneOf?: number[] |
40 | languageOneOf?: string[] | 40 | languageOneOf?: string[] |
41 | privacyOneOf?: VideoPrivacy[] | ||
41 | isLive?: boolean | 42 | isLive?: boolean |
42 | skipCount?: boolean | 43 | skipCount?: boolean |
43 | 44 | ||
@@ -392,6 +393,7 @@ export class VideoService { | |||
392 | include, | 393 | include, |
393 | categoryOneOf, | 394 | categoryOneOf, |
394 | languageOneOf, | 395 | languageOneOf, |
396 | privacyOneOf, | ||
395 | skipCount, | 397 | skipCount, |
396 | nsfwPolicy, | 398 | nsfwPolicy, |
397 | isLive, | 399 | isLive, |
@@ -413,6 +415,7 @@ export class VideoService { | |||
413 | if (nsfwPolicy) newParams = newParams.set('nsfw', this.nsfwPolicyToParam(nsfwPolicy)) | 415 | if (nsfwPolicy) newParams = newParams.set('nsfw', this.nsfwPolicyToParam(nsfwPolicy)) |
414 | if (languageOneOf) newParams = this.restService.addArrayParams(newParams, 'languageOneOf', languageOneOf) | 416 | if (languageOneOf) newParams = this.restService.addArrayParams(newParams, 'languageOneOf', languageOneOf) |
415 | if (categoryOneOf) newParams = this.restService.addArrayParams(newParams, 'categoryOneOf', categoryOneOf) | 417 | if (categoryOneOf) newParams = this.restService.addArrayParams(newParams, 'categoryOneOf', categoryOneOf) |
418 | if (privacyOneOf) newParams = this.restService.addArrayParams(newParams, 'privacyOneOf', privacyOneOf) | ||
416 | 419 | ||
417 | return newParams | 420 | return newParams |
418 | } | 421 | } |
diff --git a/client/src/app/shared/shared-video-miniature/video-filters.model.ts b/client/src/app/shared/shared-video-miniature/video-filters.model.ts index 5ad7cf3f7..982880b0e 100644 --- a/client/src/app/shared/shared-video-miniature/video-filters.model.ts +++ b/client/src/app/shared/shared-video-miniature/video-filters.model.ts | |||
@@ -1,6 +1,6 @@ | |||
1 | import { intoArray, toBoolean } from '@app/helpers' | 1 | import { intoArray, toBoolean } from '@app/helpers' |
2 | import { AttributesOnly } from '@shared/core-utils' | 2 | import { AttributesOnly, getAllPrivacies } from '@shared/core-utils' |
3 | import { BooleanBothQuery, NSFWPolicyType, VideoInclude, VideoSortField } from '@shared/models' | 3 | import { BooleanBothQuery, NSFWPolicyType, VideoInclude, VideoPrivacy, VideoSortField } from '@shared/models' |
4 | 4 | ||
5 | type VideoFiltersKeys = { | 5 | type VideoFiltersKeys = { |
6 | [ id in keyof AttributesOnly<VideoFilters> ]: any | 6 | [ id in keyof AttributesOnly<VideoFilters> ]: any |
@@ -198,13 +198,15 @@ export class VideoFilters { | |||
198 | toVideosAPIObject () { | 198 | toVideosAPIObject () { |
199 | let isLocal: boolean | 199 | let isLocal: boolean |
200 | let include: VideoInclude | 200 | let include: VideoInclude |
201 | let privacyOneOf: VideoPrivacy[] | ||
201 | 202 | ||
202 | if (this.scope === 'local') { | 203 | if (this.scope === 'local') { |
203 | isLocal = true | 204 | isLocal = true |
204 | } | 205 | } |
205 | 206 | ||
206 | if (this.allVideos) { | 207 | if (this.allVideos) { |
207 | include = VideoInclude.NOT_PUBLISHED_STATE | VideoInclude.HIDDEN_PRIVACY | 208 | include = VideoInclude.NOT_PUBLISHED_STATE |
209 | privacyOneOf = getAllPrivacies() | ||
208 | } | 210 | } |
209 | 211 | ||
210 | let isLive: boolean | 212 | let isLive: boolean |
@@ -219,6 +221,7 @@ export class VideoFilters { | |||
219 | search: this.search, | 221 | search: this.search, |
220 | isLocal, | 222 | isLocal, |
221 | include, | 223 | include, |
224 | privacyOneOf, | ||
222 | isLive | 225 | isLive |
223 | } | 226 | } |
224 | } | 227 | } |
diff --git a/server/controllers/client.ts b/server/controllers/client.ts index 0a27ace76..703166c01 100644 --- a/server/controllers/client.ts +++ b/server/controllers/client.ts | |||
@@ -10,7 +10,7 @@ import { HttpStatusCode } from '@shared/models' | |||
10 | import { root } from '../helpers/core-utils' | 10 | import { root } from '../helpers/core-utils' |
11 | import { STATIC_MAX_AGE } from '../initializers/constants' | 11 | import { STATIC_MAX_AGE } from '../initializers/constants' |
12 | import { ClientHtml, sendHTML, serveIndexHTML } from '../lib/client-html' | 12 | import { ClientHtml, sendHTML, serveIndexHTML } from '../lib/client-html' |
13 | import { asyncMiddleware, disableRobots, embedCSP } from '../middlewares' | 13 | import { asyncMiddleware, embedCSP } from '../middlewares' |
14 | 14 | ||
15 | const clientsRouter = express.Router() | 15 | const clientsRouter = express.Router() |
16 | 16 | ||
diff --git a/server/helpers/query.ts b/server/helpers/query.ts index 97bbdfc65..1142d02e4 100644 --- a/server/helpers/query.ts +++ b/server/helpers/query.ts | |||
@@ -16,6 +16,7 @@ function pickCommonVideoQuery (query: VideosCommonQueryAfterSanitize) { | |||
16 | 'categoryOneOf', | 16 | 'categoryOneOf', |
17 | 'licenceOneOf', | 17 | 'licenceOneOf', |
18 | 'languageOneOf', | 18 | 'languageOneOf', |
19 | 'privacyOneOf', | ||
19 | 'tagsOneOf', | 20 | 'tagsOneOf', |
20 | 'tagsAllOf', | 21 | 'tagsAllOf', |
21 | 'isLocal', | 22 | 'isLocal', |
diff --git a/server/middlewares/validators/videos/videos.ts b/server/middlewares/validators/videos/videos.ts index 53643635c..4916decbf 100644 --- a/server/middlewares/validators/videos/videos.ts +++ b/server/middlewares/validators/videos/videos.ts | |||
@@ -7,6 +7,7 @@ import { isAbleToUploadVideo } from '@server/lib/user' | |||
7 | import { getServerActor } from '@server/models/application/application' | 7 | import { getServerActor } from '@server/models/application/application' |
8 | import { ExpressPromiseHandler } from '@server/types/express' | 8 | import { ExpressPromiseHandler } from '@server/types/express' |
9 | import { MUserAccountId, MVideoFullLight } from '@server/types/models' | 9 | import { MUserAccountId, MVideoFullLight } from '@server/types/models' |
10 | import { getAllPrivacies } from '@shared/core-utils' | ||
10 | import { VideoInclude } from '@shared/models' | 11 | import { VideoInclude } from '@shared/models' |
11 | import { ServerErrorCode, UserRight, VideoPrivacy } from '../../../../shared' | 12 | import { ServerErrorCode, UserRight, VideoPrivacy } from '../../../../shared' |
12 | import { HttpStatusCode } from '../../../../shared/models/http/http-error-codes' | 13 | import { HttpStatusCode } from '../../../../shared/models/http/http-error-codes' |
@@ -487,6 +488,10 @@ const commonVideosFiltersValidator = [ | |||
487 | .optional() | 488 | .optional() |
488 | .customSanitizer(toArray) | 489 | .customSanitizer(toArray) |
489 | .custom(isStringArray).withMessage('Should have a valid one of language array'), | 490 | .custom(isStringArray).withMessage('Should have a valid one of language array'), |
491 | query('privacyOneOf') | ||
492 | .optional() | ||
493 | .customSanitizer(toArray) | ||
494 | .custom(isNumberArray).withMessage('Should have a valid one of privacy array'), | ||
490 | query('tagsOneOf') | 495 | query('tagsOneOf') |
491 | .optional() | 496 | .optional() |
492 | .customSanitizer(toArray) | 497 | .customSanitizer(toArray) |
@@ -536,10 +541,12 @@ const commonVideosFiltersValidator = [ | |||
536 | // FIXME: deprecated in 4.0, to remove | 541 | // FIXME: deprecated in 4.0, to remove |
537 | { | 542 | { |
538 | if (req.query.filter === 'all-local') { | 543 | if (req.query.filter === 'all-local') { |
539 | req.query.include = VideoInclude.NOT_PUBLISHED_STATE | VideoInclude.HIDDEN_PRIVACY | 544 | req.query.include = VideoInclude.NOT_PUBLISHED_STATE |
540 | req.query.isLocal = true | 545 | req.query.isLocal = true |
546 | req.query.privacyOneOf = getAllPrivacies() | ||
541 | } else if (req.query.filter === 'all') { | 547 | } else if (req.query.filter === 'all') { |
542 | req.query.include = VideoInclude.NOT_PUBLISHED_STATE | VideoInclude.HIDDEN_PRIVACY | 548 | req.query.include = VideoInclude.NOT_PUBLISHED_STATE |
549 | req.query.privacyOneOf = getAllPrivacies() | ||
543 | } else if (req.query.filter === 'local') { | 550 | } else if (req.query.filter === 'local') { |
544 | req.query.isLocal = true | 551 | req.query.isLocal = true |
545 | } | 552 | } |
@@ -550,7 +557,7 @@ const commonVideosFiltersValidator = [ | |||
550 | const user = res.locals.oauth?.token.User | 557 | const user = res.locals.oauth?.token.User |
551 | 558 | ||
552 | if ((!user || user.hasRight(UserRight.SEE_ALL_VIDEOS) !== true)) { | 559 | if ((!user || user.hasRight(UserRight.SEE_ALL_VIDEOS) !== true)) { |
553 | if (req.query.include) { | 560 | if (req.query.include || req.query.privacyOneOf) { |
554 | return res.fail({ | 561 | return res.fail({ |
555 | status: HttpStatusCode.UNAUTHORIZED_401, | 562 | status: HttpStatusCode.UNAUTHORIZED_401, |
556 | message: 'You are not allowed to see all videos.' | 563 | message: 'You are not allowed to see all videos.' |
diff --git a/server/models/video/sql/videos-id-list-query-builder.ts b/server/models/video/sql/videos-id-list-query-builder.ts index 4a882e790..d825225ab 100644 --- a/server/models/video/sql/videos-id-list-query-builder.ts +++ b/server/models/video/sql/videos-id-list-query-builder.ts | |||
@@ -40,6 +40,7 @@ export type BuildVideosListQueryOptions = { | |||
40 | languageOneOf?: string[] | 40 | languageOneOf?: string[] |
41 | tagsOneOf?: string[] | 41 | tagsOneOf?: string[] |
42 | tagsAllOf?: string[] | 42 | tagsAllOf?: string[] |
43 | privacyOneOf?: VideoPrivacy[] | ||
43 | 44 | ||
44 | uuids?: string[] | 45 | uuids?: string[] |
45 | 46 | ||
@@ -138,11 +139,6 @@ export class VideosIdListQueryBuilder extends AbstractRunQuery { | |||
138 | this.whereStateAvailable() | 139 | this.whereStateAvailable() |
139 | } | 140 | } |
140 | 141 | ||
141 | // Only list videos with the appropriate priavcy | ||
142 | if (!(options.include & VideoInclude.HIDDEN_PRIVACY)) { | ||
143 | this.wherePrivacyAvailable(options.user) | ||
144 | } | ||
145 | |||
146 | if (options.videoPlaylistId) { | 142 | if (options.videoPlaylistId) { |
147 | this.joinPlaylist(options.videoPlaylistId) | 143 | this.joinPlaylist(options.videoPlaylistId) |
148 | } | 144 | } |
@@ -187,6 +183,13 @@ export class VideosIdListQueryBuilder extends AbstractRunQuery { | |||
187 | this.whereTagsAllOf(options.tagsAllOf) | 183 | this.whereTagsAllOf(options.tagsAllOf) |
188 | } | 184 | } |
189 | 185 | ||
186 | if (options.privacyOneOf) { | ||
187 | this.wherePrivacyOneOf(options.privacyOneOf) | ||
188 | } else { | ||
189 | // Only list videos with the appropriate priavcy | ||
190 | this.wherePrivacyAvailable(options.user) | ||
191 | } | ||
192 | |||
190 | if (options.uuids) { | 193 | if (options.uuids) { |
191 | this.whereUUIDs(options.uuids) | 194 | this.whereUUIDs(options.uuids) |
192 | } | 195 | } |
@@ -435,6 +438,11 @@ export class VideosIdListQueryBuilder extends AbstractRunQuery { | |||
435 | ) | 438 | ) |
436 | } | 439 | } |
437 | 440 | ||
441 | private wherePrivacyOneOf (privacyOneOf: VideoPrivacy[]) { | ||
442 | this.and.push('"video"."privacy" IN (:privacyOneOf)') | ||
443 | this.replacements.privacyOneOf = privacyOneOf | ||
444 | } | ||
445 | |||
438 | private whereUUIDs (uuids: string[]) { | 446 | private whereUUIDs (uuids: string[]) { |
439 | this.and.push('"video"."uuid" IN (' + createSafeIn(this.sequelize, uuids) + ')') | 447 | this.and.push('"video"."uuid" IN (' + createSafeIn(this.sequelize, uuids) + ')') |
440 | } | 448 | } |
diff --git a/server/models/video/video.ts b/server/models/video/video.ts index 003741da0..69d009e04 100644 --- a/server/models/video/video.ts +++ b/server/models/video/video.ts | |||
@@ -1041,6 +1041,7 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> { | |||
1041 | languageOneOf?: string[] | 1041 | languageOneOf?: string[] |
1042 | tagsOneOf?: string[] | 1042 | tagsOneOf?: string[] |
1043 | tagsAllOf?: string[] | 1043 | tagsAllOf?: string[] |
1044 | privacyOneOf?: VideoPrivacy[] | ||
1044 | 1045 | ||
1045 | accountId?: number | 1046 | accountId?: number |
1046 | videoChannelId?: number | 1047 | videoChannelId?: number |
@@ -1059,6 +1060,7 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> { | |||
1059 | search?: string | 1060 | search?: string |
1060 | }) { | 1061 | }) { |
1061 | VideoModel.throwIfPrivateIncludeWithoutUser(options.include, options.user) | 1062 | VideoModel.throwIfPrivateIncludeWithoutUser(options.include, options.user) |
1063 | VideoModel.throwIfPrivacyOneOfWithoutUser(options.privacyOneOf, options.user) | ||
1062 | 1064 | ||
1063 | const trendingDays = options.sort.endsWith('trending') | 1065 | const trendingDays = options.sort.endsWith('trending') |
1064 | ? CONFIG.TRENDING.VIDEOS.INTERVAL_DAYS | 1066 | ? CONFIG.TRENDING.VIDEOS.INTERVAL_DAYS |
@@ -1082,6 +1084,7 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> { | |||
1082 | 'languageOneOf', | 1084 | 'languageOneOf', |
1083 | 'tagsOneOf', | 1085 | 'tagsOneOf', |
1084 | 'tagsAllOf', | 1086 | 'tagsAllOf', |
1087 | 'privacyOneOf', | ||
1085 | 'isLocal', | 1088 | 'isLocal', |
1086 | 'include', | 1089 | 'include', |
1087 | 'displayOnlyForFollower', | 1090 | 'displayOnlyForFollower', |
@@ -1119,6 +1122,7 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> { | |||
1119 | languageOneOf?: string[] | 1122 | languageOneOf?: string[] |
1120 | tagsOneOf?: string[] | 1123 | tagsOneOf?: string[] |
1121 | tagsAllOf?: string[] | 1124 | tagsAllOf?: string[] |
1125 | privacyOneOf?: VideoPrivacy[] | ||
1122 | 1126 | ||
1123 | displayOnlyForFollower: DisplayOnlyForFollowerOptions | null | 1127 | displayOnlyForFollower: DisplayOnlyForFollowerOptions | null |
1124 | 1128 | ||
@@ -1140,6 +1144,7 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> { | |||
1140 | uuids?: string[] | 1144 | uuids?: string[] |
1141 | }) { | 1145 | }) { |
1142 | VideoModel.throwIfPrivateIncludeWithoutUser(options.include, options.user) | 1146 | VideoModel.throwIfPrivateIncludeWithoutUser(options.include, options.user) |
1147 | VideoModel.throwIfPrivacyOneOfWithoutUser(options.privacyOneOf, options.user) | ||
1143 | 1148 | ||
1144 | const serverActor = await getServerActor() | 1149 | const serverActor = await getServerActor() |
1145 | 1150 | ||
@@ -1153,6 +1158,7 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> { | |||
1153 | 'languageOneOf', | 1158 | 'languageOneOf', |
1154 | 'tagsOneOf', | 1159 | 'tagsOneOf', |
1155 | 'tagsAllOf', | 1160 | 'tagsAllOf', |
1161 | 'privacyOneOf', | ||
1156 | 'user', | 1162 | 'user', |
1157 | 'isLocal', | 1163 | 'isLocal', |
1158 | 'host', | 1164 | 'host', |
@@ -1510,14 +1516,19 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> { | |||
1510 | 1516 | ||
1511 | private static throwIfPrivateIncludeWithoutUser (include: VideoInclude, user: MUserAccountId) { | 1517 | private static throwIfPrivateIncludeWithoutUser (include: VideoInclude, user: MUserAccountId) { |
1512 | if (VideoModel.isPrivateInclude(include) && !user?.hasRight(UserRight.SEE_ALL_VIDEOS)) { | 1518 | if (VideoModel.isPrivateInclude(include) && !user?.hasRight(UserRight.SEE_ALL_VIDEOS)) { |
1513 | throw new Error('Try to filter all-local but no user has not the see all videos right') | 1519 | throw new Error('Try to filter all-local but user cannot see all videos') |
1520 | } | ||
1521 | } | ||
1522 | |||
1523 | private static throwIfPrivacyOneOfWithoutUser (privacyOneOf: VideoPrivacy[], user: MUserAccountId) { | ||
1524 | if (privacyOneOf && !user?.hasRight(UserRight.SEE_ALL_VIDEOS)) { | ||
1525 | throw new Error('Try to choose video privacies but user cannot see all videos') | ||
1514 | } | 1526 | } |
1515 | } | 1527 | } |
1516 | 1528 | ||
1517 | private static isPrivateInclude (include: VideoInclude) { | 1529 | private static isPrivateInclude (include: VideoInclude) { |
1518 | return include & VideoInclude.BLACKLISTED || | 1530 | return include & VideoInclude.BLACKLISTED || |
1519 | include & VideoInclude.BLOCKED_OWNER || | 1531 | include & VideoInclude.BLOCKED_OWNER || |
1520 | include & VideoInclude.HIDDEN_PRIVACY || | ||
1521 | include & VideoInclude.NOT_PUBLISHED_STATE | 1532 | include & VideoInclude.NOT_PUBLISHED_STATE |
1522 | } | 1533 | } |
1523 | 1534 | ||
diff --git a/server/tests/api/check-params/videos-common-filters.ts b/server/tests/api/check-params/videos-common-filters.ts index afe42b0d5..f2b5bee8e 100644 --- a/server/tests/api/check-params/videos-common-filters.ts +++ b/server/tests/api/check-params/videos-common-filters.ts | |||
@@ -9,7 +9,7 @@ import { | |||
9 | setAccessTokensToServers, | 9 | setAccessTokensToServers, |
10 | setDefaultVideoChannel | 10 | setDefaultVideoChannel |
11 | } from '@shared/extra-utils' | 11 | } from '@shared/extra-utils' |
12 | import { HttpStatusCode, UserRole, VideoInclude } from '@shared/models' | 12 | import { HttpStatusCode, UserRole, VideoInclude, VideoPrivacy } from '@shared/models' |
13 | 13 | ||
14 | describe('Test video filters validators', function () { | 14 | describe('Test video filters validators', function () { |
15 | let server: PeerTubeServer | 15 | let server: PeerTubeServer |
@@ -112,7 +112,7 @@ describe('Test video filters validators', function () { | |||
112 | 112 | ||
113 | const validIncludes = [ | 113 | const validIncludes = [ |
114 | VideoInclude.NONE, | 114 | VideoInclude.NONE, |
115 | VideoInclude.HIDDEN_PRIVACY, | 115 | VideoInclude.BLOCKED_OWNER, |
116 | VideoInclude.NOT_PUBLISHED_STATE | VideoInclude.BLACKLISTED | 116 | VideoInclude.NOT_PUBLISHED_STATE | VideoInclude.BLACKLISTED |
117 | ] | 117 | ] |
118 | 118 | ||
@@ -120,6 +120,7 @@ describe('Test video filters validators', function () { | |||
120 | token?: string | 120 | token?: string |
121 | isLocal?: boolean | 121 | isLocal?: boolean |
122 | include?: VideoInclude | 122 | include?: VideoInclude |
123 | privacyOneOf?: VideoPrivacy[] | ||
123 | expectedStatus: HttpStatusCode | 124 | expectedStatus: HttpStatusCode |
124 | }) { | 125 | }) { |
125 | const paths = [ | 126 | const paths = [ |
@@ -136,6 +137,7 @@ describe('Test video filters validators', function () { | |||
136 | token: options.token || server.accessToken, | 137 | token: options.token || server.accessToken, |
137 | query: { | 138 | query: { |
138 | isLocal: options.isLocal, | 139 | isLocal: options.isLocal, |
140 | privacyOneOf: options.privacyOneOf, | ||
139 | include: options.include | 141 | include: options.include |
140 | }, | 142 | }, |
141 | expectedStatus: options.expectedStatus | 143 | expectedStatus: options.expectedStatus |
@@ -143,6 +145,22 @@ describe('Test video filters validators', function () { | |||
143 | } | 145 | } |
144 | } | 146 | } |
145 | 147 | ||
148 | it('Should fail with a bad privacyOneOf', async function () { | ||
149 | await testEndpoints({ privacyOneOf: [ 'toto' ] as any, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) | ||
150 | }) | ||
151 | |||
152 | it('Should succeed with a good privacyOneOf', async function () { | ||
153 | await testEndpoints({ privacyOneOf: [ VideoPrivacy.INTERNAL ], expectedStatus: HttpStatusCode.OK_200 }) | ||
154 | }) | ||
155 | |||
156 | it('Should fail to use privacyOneOf with a simple user', async function () { | ||
157 | await testEndpoints({ | ||
158 | privacyOneOf: [ VideoPrivacy.INTERNAL ], | ||
159 | token: userAccessToken, | ||
160 | expectedStatus: HttpStatusCode.UNAUTHORIZED_401 | ||
161 | }) | ||
162 | }) | ||
163 | |||
146 | it('Should fail with a bad include', async function () { | 164 | it('Should fail with a bad include', async function () { |
147 | await testEndpoints({ include: 'toto' as any, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) | 165 | await testEndpoints({ include: 'toto' as any, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) |
148 | }) | 166 | }) |
diff --git a/server/tests/api/videos/videos-common-filters.ts b/server/tests/api/videos/videos-common-filters.ts index 4f22d4ac3..ca5f42173 100644 --- a/server/tests/api/videos/videos-common-filters.ts +++ b/server/tests/api/videos/videos-common-filters.ts | |||
@@ -138,6 +138,7 @@ describe('Test videos filter', function () { | |||
138 | hasWebtorrentFiles?: boolean | 138 | hasWebtorrentFiles?: boolean |
139 | hasHLSFiles?: boolean | 139 | hasHLSFiles?: boolean |
140 | include?: VideoInclude | 140 | include?: VideoInclude |
141 | privacyOneOf?: VideoPrivacy[] | ||
141 | category?: number | 142 | category?: number |
142 | tagsAllOf?: string[] | 143 | tagsAllOf?: string[] |
143 | token?: string | 144 | token?: string |
@@ -148,7 +149,7 @@ describe('Test videos filter', function () { | |||
148 | path: options.path, | 149 | path: options.path, |
149 | token: options.token ?? options.server.accessToken, | 150 | token: options.token ?? options.server.accessToken, |
150 | query: { | 151 | query: { |
151 | ...pick(options, [ 'isLocal', 'include', 'category', 'tagsAllOf', 'hasWebtorrentFiles', 'hasHLSFiles' ]), | 152 | ...pick(options, [ 'isLocal', 'include', 'category', 'tagsAllOf', 'hasWebtorrentFiles', 'hasHLSFiles', 'privacyOneOf' ]), |
152 | 153 | ||
153 | sort: 'createdAt' | 154 | sort: 'createdAt' |
154 | }, | 155 | }, |
@@ -162,6 +163,7 @@ describe('Test videos filter', function () { | |||
162 | server: PeerTubeServer | 163 | server: PeerTubeServer |
163 | isLocal?: boolean | 164 | isLocal?: boolean |
164 | include?: VideoInclude | 165 | include?: VideoInclude |
166 | privacyOneOf?: VideoPrivacy[] | ||
165 | token?: string | 167 | token?: string |
166 | expectedStatus?: HttpStatusCode | 168 | expectedStatus?: HttpStatusCode |
167 | }) { | 169 | }) { |
@@ -195,7 +197,7 @@ describe('Test videos filter', function () { | |||
195 | server, | 197 | server, |
196 | token, | 198 | token, |
197 | isLocal: true, | 199 | isLocal: true, |
198 | include: VideoInclude.HIDDEN_PRIVACY | 200 | privacyOneOf: [ VideoPrivacy.UNLISTED, VideoPrivacy.PUBLIC, VideoPrivacy.PRIVATE ] |
199 | }) | 201 | }) |
200 | 202 | ||
201 | for (const names of namesResults) { | 203 | for (const names of namesResults) { |
@@ -216,7 +218,7 @@ describe('Test videos filter', function () { | |||
216 | const [ channelVideos, accountVideos, videos, searchVideos ] = await getVideosNames({ | 218 | const [ channelVideos, accountVideos, videos, searchVideos ] = await getVideosNames({ |
217 | server, | 219 | server, |
218 | token, | 220 | token, |
219 | include: VideoInclude.HIDDEN_PRIVACY | 221 | privacyOneOf: [ VideoPrivacy.UNLISTED, VideoPrivacy.PUBLIC, VideoPrivacy.PRIVATE ] |
220 | }) | 222 | }) |
221 | 223 | ||
222 | expect(channelVideos).to.have.lengthOf(3) | 224 | expect(channelVideos).to.have.lengthOf(3) |
diff --git a/shared/core-utils/videos/index.ts b/shared/core-utils/videos/index.ts index 5a1145f1a..620e3a716 100644 --- a/shared/core-utils/videos/index.ts +++ b/shared/core-utils/videos/index.ts | |||
@@ -1 +1,2 @@ | |||
1 | export * from './bitrate' | 1 | export * from './bitrate' |
2 | export * from './privacy' | ||
diff --git a/shared/core-utils/videos/privacy.ts b/shared/core-utils/videos/privacy.ts new file mode 100644 index 000000000..7d3b67d50 --- /dev/null +++ b/shared/core-utils/videos/privacy.ts | |||
@@ -0,0 +1,9 @@ | |||
1 | import { VideoPrivacy } from '../../models/videos/video-privacy.enum' | ||
2 | |||
3 | function getAllPrivacies () { | ||
4 | return [ VideoPrivacy.PUBLIC, VideoPrivacy.INTERNAL, VideoPrivacy.PRIVATE, VideoPrivacy.UNLISTED ] | ||
5 | } | ||
6 | |||
7 | export { | ||
8 | getAllPrivacies | ||
9 | } | ||
diff --git a/shared/models/search/videos-common-query.model.ts b/shared/models/search/videos-common-query.model.ts index e9edb91b0..2cbf7b014 100644 --- a/shared/models/search/videos-common-query.model.ts +++ b/shared/models/search/videos-common-query.model.ts | |||
@@ -1,3 +1,4 @@ | |||
1 | import { VideoPrivacy } from '@shared/models' | ||
1 | import { VideoInclude } from '../videos/video-include.enum' | 2 | import { VideoInclude } from '../videos/video-include.enum' |
2 | import { BooleanBothQuery } from './boolean-both-query.model' | 3 | import { BooleanBothQuery } from './boolean-both-query.model' |
3 | 4 | ||
@@ -23,6 +24,8 @@ export interface VideosCommonQuery { | |||
23 | 24 | ||
24 | languageOneOf?: string[] | 25 | languageOneOf?: string[] |
25 | 26 | ||
27 | privacyOneOf?: VideoPrivacy[] | ||
28 | |||
26 | tagsOneOf?: string[] | 29 | tagsOneOf?: string[] |
27 | tagsAllOf?: string[] | 30 | tagsAllOf?: string[] |
28 | 31 | ||
diff --git a/shared/models/videos/video-include.enum.ts b/shared/models/videos/video-include.enum.ts index 72fa8cd30..7e16b129a 100644 --- a/shared/models/videos/video-include.enum.ts +++ b/shared/models/videos/video-include.enum.ts | |||
@@ -1,8 +1,7 @@ | |||
1 | export const enum VideoInclude { | 1 | export const enum VideoInclude { |
2 | NONE = 0, | 2 | NONE = 0, |
3 | NOT_PUBLISHED_STATE = 1 << 0, | 3 | NOT_PUBLISHED_STATE = 1 << 0, |
4 | HIDDEN_PRIVACY = 1 << 1, | 4 | BLACKLISTED = 1 << 1, |
5 | BLACKLISTED = 1 << 2, | 5 | BLOCKED_OWNER = 1 << 2, |
6 | BLOCKED_OWNER = 1 << 3, | 6 | FILES = 1 << 3 |
7 | FILES = 1 << 4 | ||
8 | } | 7 | } |
diff --git a/support/doc/api/openapi.yaml b/support/doc/api/openapi.yaml index 13757152c..88a089fc7 100644 --- a/support/doc/api/openapi.yaml +++ b/support/doc/api/openapi.yaml | |||
@@ -369,6 +369,7 @@ paths: | |||
369 | - $ref: '#/components/parameters/nsfw' | 369 | - $ref: '#/components/parameters/nsfw' |
370 | - $ref: '#/components/parameters/isLocal' | 370 | - $ref: '#/components/parameters/isLocal' |
371 | - $ref: '#/components/parameters/include' | 371 | - $ref: '#/components/parameters/include' |
372 | - $ref: '#/components/parameters/privacyOneOf' | ||
372 | - $ref: '#/components/parameters/hasHLSFiles' | 373 | - $ref: '#/components/parameters/hasHLSFiles' |
373 | - $ref: '#/components/parameters/hasWebtorrentFiles' | 374 | - $ref: '#/components/parameters/hasWebtorrentFiles' |
374 | - $ref: '#/components/parameters/skipCount' | 375 | - $ref: '#/components/parameters/skipCount' |
@@ -1305,6 +1306,7 @@ paths: | |||
1305 | - $ref: '#/components/parameters/nsfw' | 1306 | - $ref: '#/components/parameters/nsfw' |
1306 | - $ref: '#/components/parameters/isLocal' | 1307 | - $ref: '#/components/parameters/isLocal' |
1307 | - $ref: '#/components/parameters/include' | 1308 | - $ref: '#/components/parameters/include' |
1309 | - $ref: '#/components/parameters/privacyOneOf' | ||
1308 | - $ref: '#/components/parameters/hasHLSFiles' | 1310 | - $ref: '#/components/parameters/hasHLSFiles' |
1309 | - $ref: '#/components/parameters/hasWebtorrentFiles' | 1311 | - $ref: '#/components/parameters/hasWebtorrentFiles' |
1310 | - $ref: '#/components/parameters/skipCount' | 1312 | - $ref: '#/components/parameters/skipCount' |
@@ -1628,6 +1630,7 @@ paths: | |||
1628 | - $ref: '#/components/parameters/nsfw' | 1630 | - $ref: '#/components/parameters/nsfw' |
1629 | - $ref: '#/components/parameters/isLocal' | 1631 | - $ref: '#/components/parameters/isLocal' |
1630 | - $ref: '#/components/parameters/include' | 1632 | - $ref: '#/components/parameters/include' |
1633 | - $ref: '#/components/parameters/privacyOneOf' | ||
1631 | - $ref: '#/components/parameters/hasHLSFiles' | 1634 | - $ref: '#/components/parameters/hasHLSFiles' |
1632 | - $ref: '#/components/parameters/hasWebtorrentFiles' | 1635 | - $ref: '#/components/parameters/hasWebtorrentFiles' |
1633 | - $ref: '#/components/parameters/skipCount' | 1636 | - $ref: '#/components/parameters/skipCount' |
@@ -2867,6 +2870,7 @@ paths: | |||
2867 | - $ref: '#/components/parameters/nsfw' | 2870 | - $ref: '#/components/parameters/nsfw' |
2868 | - $ref: '#/components/parameters/isLocal' | 2871 | - $ref: '#/components/parameters/isLocal' |
2869 | - $ref: '#/components/parameters/include' | 2872 | - $ref: '#/components/parameters/include' |
2873 | - $ref: '#/components/parameters/privacyOneOf' | ||
2870 | - $ref: '#/components/parameters/hasHLSFiles' | 2874 | - $ref: '#/components/parameters/hasHLSFiles' |
2871 | - $ref: '#/components/parameters/hasWebtorrentFiles' | 2875 | - $ref: '#/components/parameters/hasWebtorrentFiles' |
2872 | - $ref: '#/components/parameters/skipCount' | 2876 | - $ref: '#/components/parameters/skipCount' |
@@ -3590,6 +3594,7 @@ paths: | |||
3590 | - $ref: '#/components/parameters/nsfw' | 3594 | - $ref: '#/components/parameters/nsfw' |
3591 | - $ref: '#/components/parameters/isLocal' | 3595 | - $ref: '#/components/parameters/isLocal' |
3592 | - $ref: '#/components/parameters/include' | 3596 | - $ref: '#/components/parameters/include' |
3597 | - $ref: '#/components/parameters/privacyOneOf' | ||
3593 | - $ref: '#/components/parameters/hasHLSFiles' | 3598 | - $ref: '#/components/parameters/hasHLSFiles' |
3594 | - $ref: '#/components/parameters/hasWebtorrentFiles' | 3599 | - $ref: '#/components/parameters/hasWebtorrentFiles' |
3595 | - $ref: '#/components/parameters/skipCount' | 3600 | - $ref: '#/components/parameters/skipCount' |
@@ -4095,6 +4100,7 @@ paths: | |||
4095 | - $ref: '#/components/parameters/nsfw' | 4100 | - $ref: '#/components/parameters/nsfw' |
4096 | - $ref: '#/components/parameters/isLocal' | 4101 | - $ref: '#/components/parameters/isLocal' |
4097 | - $ref: '#/components/parameters/include' | 4102 | - $ref: '#/components/parameters/include' |
4103 | - $ref: '#/components/parameters/privacyOneOf' | ||
4098 | - $ref: '#/components/parameters/hasHLSFiles' | 4104 | - $ref: '#/components/parameters/hasHLSFiles' |
4099 | - $ref: '#/components/parameters/hasWebtorrentFiles' | 4105 | - $ref: '#/components/parameters/hasWebtorrentFiles' |
4100 | responses: | 4106 | responses: |
@@ -4179,6 +4185,7 @@ paths: | |||
4179 | - $ref: '#/components/parameters/nsfw' | 4185 | - $ref: '#/components/parameters/nsfw' |
4180 | - $ref: '#/components/parameters/isLocal' | 4186 | - $ref: '#/components/parameters/isLocal' |
4181 | - $ref: '#/components/parameters/include' | 4187 | - $ref: '#/components/parameters/include' |
4188 | - $ref: '#/components/parameters/privacyOneOf' | ||
4182 | - $ref: '#/components/parameters/hasHLSFiles' | 4189 | - $ref: '#/components/parameters/hasHLSFiles' |
4183 | - $ref: '#/components/parameters/hasWebtorrentFiles' | 4190 | - $ref: '#/components/parameters/hasWebtorrentFiles' |
4184 | responses: | 4191 | responses: |
@@ -4834,6 +4841,13 @@ components: | |||
4834 | schema: | 4841 | schema: |
4835 | type: boolean | 4842 | type: boolean |
4836 | description: '**PeerTube >= 4.0** Display only videos that have WebTorrent files' | 4843 | description: '**PeerTube >= 4.0** Display only videos that have WebTorrent files' |
4844 | privacyOneOf: | ||
4845 | name: privacyOneOf | ||
4846 | in: query | ||
4847 | required: false | ||
4848 | schema: | ||
4849 | $ref: '#/components/schemas/VideoPrivacySet' | ||
4850 | description: '**PeerTube >= 4.0** Display only videos in this specific privacy/privacies' | ||
4837 | include: | 4851 | include: |
4838 | name: include | 4852 | name: include |
4839 | in: query | 4853 | in: query |
@@ -4853,11 +4867,11 @@ components: | |||
4853 | 4867 | ||
4854 | - `1` NOT_PUBLISHED_STATE | 4868 | - `1` NOT_PUBLISHED_STATE |
4855 | 4869 | ||
4856 | - `2` HIDDEN_PRIVACY | 4870 | - `2` BLACKLISTED |
4857 | 4871 | ||
4858 | - `4` BLACKLISTED | 4872 | - `4` BLOCKED_OWNER |
4859 | 4873 | ||
4860 | - `8` BLOCKED | 4874 | - `8` FILES |
4861 | subscriptionsUris: | 4875 | subscriptionsUris: |
4862 | name: uris | 4876 | name: uris |
4863 | in: query | 4877 | in: query |