aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2021-06-28 17:30:59 +0200
committerChocobozzz <chocobozzz@cpy.re>2021-06-29 14:56:35 +0200
commitd4a8e7a65f97bb3257facc13e1ae8ffdbad61ddb (patch)
treea4cb07318100031951c3dffc61f4f2cb95d2cbd0
parent62ddc31a9e4b92d7d27898ccfc363f68ab044139 (diff)
downloadPeerTube-d4a8e7a65f97bb3257facc13e1ae8ffdbad61ddb.tar.gz
PeerTube-d4a8e7a65f97bb3257facc13e1ae8ffdbad61ddb.tar.zst
PeerTube-d4a8e7a65f97bb3257facc13e1ae8ffdbad61ddb.zip
Support short uuid for GET video/playlist
-rw-r--r--client/src/app/+admin/moderation/video-block-list/video-block-list.component.ts2
-rw-r--r--client/src/app/+my-library/my-video-imports/my-video-imports.component.ts6
-rw-r--r--client/src/app/+remote-interaction/remote-interaction.component.ts4
-rw-r--r--client/src/app/+search/shared/abstract-lazy-load.resolver.ts2
-rw-r--r--client/src/app/+search/shared/playlist-lazy-load.resolver.ts2
-rw-r--r--client/src/app/+search/shared/video-lazy-load.resolver.ts2
-rw-r--r--client/src/app/+videos/+video-edit/video-add-components/video-go-live.component.ts6
-rw-r--r--client/src/app/+videos/+video-edit/video-add-components/video-upload.component.ts12
-rw-r--r--client/src/app/+videos/+video-edit/video-update.component.html2
-rw-r--r--client/src/app/+videos/+video-edit/video-update.component.ts8
-rw-r--r--client/src/app/+videos/+video-watch/comment/video-comment.component.html4
-rw-r--r--client/src/app/+videos/+video-watch/comment/video-comments.component.ts2
-rw-r--r--client/src/app/+videos/+video-watch/video-watch.component.ts26
-rw-r--r--client/src/app/shared/shared-abuse-list/abuse-list-table.component.ts4
-rw-r--r--client/src/app/shared/shared-main/angular/link.component.ts2
-rw-r--r--client/src/app/shared/shared-main/users/user-notification.model.ts3
-rw-r--r--client/src/app/shared/shared-main/video/video.model.ts15
-rw-r--r--client/src/app/shared/shared-share-modal/video-share.component.ts7
-rw-r--r--client/src/app/shared/shared-thumbnail/video-thumbnail.component.ts4
-rw-r--r--client/src/app/shared/shared-video-comment/video-comment.model.ts4
-rw-r--r--client/src/app/shared/shared-video-comment/video-comment.service.ts7
-rw-r--r--client/src/app/shared/shared-video-miniature/video-miniature.component.ts4
-rw-r--r--client/src/app/shared/shared-video-playlist/video-playlist-element-miniature.component.ts2
-rw-r--r--client/src/app/shared/shared-video-playlist/video-playlist-miniature.component.ts2
-rw-r--r--client/src/app/shared/shared-video-playlist/video-playlist.model.ts8
-rw-r--r--package.json2
-rw-r--r--server/controllers/api/users/token.ts4
-rw-r--r--server/controllers/api/video-playlist.ts4
-rw-r--r--server/controllers/api/videos/live.ts7
-rw-r--r--server/controllers/api/videos/upload.ts2
-rw-r--r--server/helpers/custom-validators/misc.ts84
-rw-r--r--server/helpers/image-utils.ts4
-rw-r--r--server/helpers/uuid.ts32
-rw-r--r--server/initializers/migrations/0080-video-channels.ts4
-rw-r--r--server/initializers/migrations/0345-video-playlists.ts4
-rw-r--r--server/initializers/migrations/0560-user-feed-token.ts4
-rw-r--r--server/lib/activitypub/actors/shared/object-to-model-attributes.ts4
-rw-r--r--server/lib/client-html.ts9
-rw-r--r--server/lib/local-actor.ts4
-rw-r--r--server/lib/user.ts4
-rw-r--r--server/middlewares/validators/abuse.ts3
-rw-r--r--server/middlewares/validators/feeds.ts8
-rw-r--r--server/middlewares/validators/index.ts2
-rw-r--r--server/middlewares/validators/oembed.ts4
-rw-r--r--server/middlewares/validators/redundancy.ts23
-rw-r--r--server/middlewares/validators/shared/utils.ts19
-rw-r--r--server/middlewares/validators/users.ts6
-rw-r--r--server/middlewares/validators/videos/video-blacklist.ts14
-rw-r--r--server/middlewares/validators/videos/video-captions.ts18
-rw-r--r--server/middlewares/validators/videos/video-comments.ts31
-rw-r--r--server/middlewares/validators/videos/video-live.ts14
-rw-r--r--server/middlewares/validators/videos/video-ownership-changes.ts10
-rw-r--r--server/middlewares/validators/videos/video-playlists.ts41
-rw-r--r--server/middlewares/validators/videos/video-rates.ts7
-rw-r--r--server/middlewares/validators/videos/video-shares.ts10
-rw-r--r--server/middlewares/validators/videos/video-watch.ts9
-rw-r--r--server/middlewares/validators/videos/videos.ts17
-rw-r--r--server/models/video/formatter/video-format-utils.ts3
-rw-r--r--server/models/video/video-caption.ts4
-rw-r--r--server/models/video/video-playlist.ts6
-rw-r--r--server/tests/api/activitypub/client.ts98
-rw-r--r--server/tests/api/check-params/abuses.ts2
-rw-r--r--server/tests/api/check-params/live.ts40
-rw-r--r--server/tests/api/check-params/redundancy.ts38
-rw-r--r--server/tests/api/check-params/users.ts12
-rw-r--r--server/tests/api/check-params/video-blacklist.ts12
-rw-r--r--server/tests/api/check-params/video-captions.ts38
-rw-r--r--server/tests/api/check-params/video-comments.ts31
-rw-r--r--server/tests/api/check-params/video-playlists.ts64
-rw-r--r--server/tests/api/check-params/videos.ts52
-rw-r--r--server/tests/api/notifications/moderation-notifications.ts30
-rw-r--r--server/tests/api/notifications/user-notifications.ts10
-rw-r--r--server/tests/api/server/handle-down.ts2
-rw-r--r--server/tests/api/videos/video-playlists.ts71
-rw-r--r--server/tests/api/videos/video-privacy.ts351
-rw-r--r--server/tests/cli/prune-storage.ts30
-rw-r--r--server/tests/client.ts129
-rw-r--r--server/tools/peertube-repl.ts5
-rw-r--r--shared/extra-utils/server/servers.ts1
-rw-r--r--shared/extra-utils/videos/videos.ts6
-rw-r--r--shared/models/common/index.ts1
-rw-r--r--shared/models/common/result-list.model.ts (renamed from shared/models/result-list.model.ts)0
-rw-r--r--shared/models/index.ts14
-rw-r--r--shared/models/moderation/abuse/abuse-create.model.ts2
-rw-r--r--shared/models/tokens/index.ts1
-rw-r--r--shared/models/tokens/oauth-client-local.model.ts (renamed from shared/models/oauth-client-local.model.ts)0
-rw-r--r--shared/models/videos/index.ts1
-rw-r--r--shared/models/videos/playlist/index.ts1
-rw-r--r--shared/models/videos/playlist/video-playlist-create-result.model.ts5
-rw-r--r--shared/models/videos/playlist/video-playlist.model.ts2
-rw-r--r--shared/models/videos/video-create-result.model.ts5
-rw-r--r--shared/models/videos/video.model.ts2
-rw-r--r--support/doc/api/openapi.yaml17
-rw-r--r--yarn.lock8
94 files changed, 999 insertions, 643 deletions
diff --git a/client/src/app/+admin/moderation/video-block-list/video-block-list.component.ts b/client/src/app/+admin/moderation/video-block-list/video-block-list.component.ts
index 63143d0f9..08500ef5c 100644
--- a/client/src/app/+admin/moderation/video-block-list/video-block-list.component.ts
+++ b/client/src/app/+admin/moderation/video-block-list/video-block-list.component.ts
@@ -122,7 +122,7 @@ export class VideoBlockListComponent extends RestTable implements OnInit {
122 } 122 }
123 123
124 getVideoUrl (videoBlock: VideoBlacklist) { 124 getVideoUrl (videoBlock: VideoBlacklist) {
125 return Video.buildClientUrl(videoBlock.video.uuid) 125 return Video.buildWatchUrl(videoBlock.video)
126 } 126 }
127 127
128 toHtml (text: string) { 128 toHtml (text: string) {
diff --git a/client/src/app/+my-library/my-video-imports/my-video-imports.component.ts b/client/src/app/+my-library/my-video-imports/my-video-imports.component.ts
index bb9d70524..68254526a 100644
--- a/client/src/app/+my-library/my-video-imports/my-video-imports.component.ts
+++ b/client/src/app/+my-library/my-video-imports/my-video-imports.component.ts
@@ -1,7 +1,7 @@
1import { SortMeta } from 'primeng/api' 1import { SortMeta } from 'primeng/api'
2import { Component, OnInit } from '@angular/core' 2import { Component, OnInit } from '@angular/core'
3import { Notifier, RestPagination, RestTable } from '@app/core' 3import { Notifier, RestPagination, RestTable } from '@app/core'
4import { VideoImportService } from '@app/shared/shared-main' 4import { Video, VideoImportService } from '@app/shared/shared-main'
5import { VideoImport, VideoImportState } from '@shared/models' 5import { VideoImport, VideoImportState } from '@shared/models'
6 6
7@Component({ 7@Component({
@@ -55,11 +55,11 @@ export class MyVideoImportsComponent extends RestTable implements OnInit {
55 } 55 }
56 56
57 getVideoUrl (video: { uuid: string }) { 57 getVideoUrl (video: { uuid: string }) {
58 return '/w/' + video.uuid 58 return Video.buildWatchUrl(video)
59 } 59 }
60 60
61 getEditVideoUrl (video: { uuid: string }) { 61 getEditVideoUrl (video: { uuid: string }) {
62 return '/videos/update/' + video.uuid 62 return Video.buildUpdateUrl(video)
63 } 63 }
64 64
65 protected reloadData () { 65 protected reloadData () {
diff --git a/client/src/app/+remote-interaction/remote-interaction.component.ts b/client/src/app/+remote-interaction/remote-interaction.component.ts
index 6ddf5b58d..293f7edad 100644
--- a/client/src/app/+remote-interaction/remote-interaction.component.ts
+++ b/client/src/app/+remote-interaction/remote-interaction.component.ts
@@ -1,7 +1,7 @@
1import { forkJoin } from 'rxjs' 1import { forkJoin } from 'rxjs'
2import { Component, OnInit } from '@angular/core' 2import { Component, OnInit } from '@angular/core'
3import { ActivatedRoute, Router } from '@angular/router' 3import { ActivatedRoute, Router } from '@angular/router'
4import { VideoChannel } from '@app/shared/shared-main' 4import { Video, VideoChannel } from '@app/shared/shared-main'
5import { SearchService } from '@app/shared/shared-search' 5import { SearchService } from '@app/shared/shared-search'
6 6
7@Component({ 7@Component({
@@ -39,7 +39,7 @@ export class RemoteInteractionComponent implements OnInit {
39 if (videoResult.data.length !== 0) { 39 if (videoResult.data.length !== 0) {
40 const video = videoResult.data[0] 40 const video = videoResult.data[0]
41 41
42 redirectUrl = '/w/' + video.uuid 42 redirectUrl = Video.buildWatchUrl(video)
43 } else if (channelResult.data.length !== 0) { 43 } else if (channelResult.data.length !== 0) {
44 const channel = new VideoChannel(channelResult.data[0]) 44 const channel = new VideoChannel(channelResult.data[0])
45 45
diff --git a/client/src/app/+search/shared/abstract-lazy-load.resolver.ts b/client/src/app/+search/shared/abstract-lazy-load.resolver.ts
index 31240f451..b18a5b64d 100644
--- a/client/src/app/+search/shared/abstract-lazy-load.resolver.ts
+++ b/client/src/app/+search/shared/abstract-lazy-load.resolver.ts
@@ -1,7 +1,7 @@
1import { Observable } from 'rxjs' 1import { Observable } from 'rxjs'
2import { map } from 'rxjs/operators' 2import { map } from 'rxjs/operators'
3import { ActivatedRouteSnapshot, Resolve, Router } from '@angular/router' 3import { ActivatedRouteSnapshot, Resolve, Router } from '@angular/router'
4import { ResultList } from '@shared/models/result-list.model' 4import { ResultList } from '@shared/models'
5 5
6export abstract class AbstractLazyLoadResolver <T> implements Resolve<any> { 6export abstract class AbstractLazyLoadResolver <T> implements Resolve<any> {
7 protected router: Router 7 protected router: Router
diff --git a/client/src/app/+search/shared/playlist-lazy-load.resolver.ts b/client/src/app/+search/shared/playlist-lazy-load.resolver.ts
index 14ae798df..3310e9627 100644
--- a/client/src/app/+search/shared/playlist-lazy-load.resolver.ts
+++ b/client/src/app/+search/shared/playlist-lazy-load.resolver.ts
@@ -19,6 +19,6 @@ export class PlaylistLazyLoadResolver extends AbstractLazyLoadResolver<VideoPlay
19 } 19 }
20 20
21 protected buildUrl (playlist: VideoPlaylist) { 21 protected buildUrl (playlist: VideoPlaylist) {
22 return '/w/p/' + playlist.uuid 22 return VideoPlaylist.buildWatchUrl(playlist)
23 } 23 }
24} 24}
diff --git a/client/src/app/+search/shared/video-lazy-load.resolver.ts b/client/src/app/+search/shared/video-lazy-load.resolver.ts
index 12b5b2e82..69a3eb159 100644
--- a/client/src/app/+search/shared/video-lazy-load.resolver.ts
+++ b/client/src/app/+search/shared/video-lazy-load.resolver.ts
@@ -19,6 +19,6 @@ export class VideoLazyLoadResolver extends AbstractLazyLoadResolver<Video> {
19 } 19 }
20 20
21 protected buildUrl (video: Video) { 21 protected buildUrl (video: Video) {
22 return '/w/' + video.uuid 22 return Video.buildWatchUrl(video)
23 } 23 }
24} 24}
diff --git a/client/src/app/+videos/+video-edit/video-add-components/video-go-live.component.ts b/client/src/app/+videos/+video-edit/video-add-components/video-go-live.component.ts
index 4c39b276a..01c9fcb16 100644
--- a/client/src/app/+videos/+video-edit/video-add-components/video-go-live.component.ts
+++ b/client/src/app/+videos/+video-edit/video-add-components/video-go-live.component.ts
@@ -1,11 +1,11 @@
1 1
2import { forkJoin } from 'rxjs' 2import { forkJoin } from 'rxjs'
3import { AfterViewChecked, AfterViewInit, Component, EventEmitter, OnInit, Output } from '@angular/core' 3import { AfterViewInit, Component, EventEmitter, OnInit, Output } from '@angular/core'
4import { Router } from '@angular/router' 4import { Router } from '@angular/router'
5import { AuthService, CanComponentDeactivate, HooksService, Notifier, ServerService } from '@app/core' 5import { AuthService, CanComponentDeactivate, HooksService, Notifier, ServerService } from '@app/core'
6import { scrollToTop } from '@app/helpers' 6import { scrollToTop } from '@app/helpers'
7import { FormValidatorService } from '@app/shared/shared-forms' 7import { FormValidatorService } from '@app/shared/shared-forms'
8import { VideoCaptionService, VideoEdit, VideoService } from '@app/shared/shared-main' 8import { Video, VideoCaptionService, VideoEdit, VideoService } from '@app/shared/shared-main'
9import { LiveVideoService } from '@app/shared/shared-video-live' 9import { LiveVideoService } from '@app/shared/shared-video-live'
10import { LoadingBarService } from '@ngx-loading-bar/core' 10import { LoadingBarService } from '@ngx-loading-bar/core'
11import { LiveVideo, LiveVideoCreate, LiveVideoUpdate, PeerTubeProblemDocument, ServerErrorCode, VideoPrivacy } from '@shared/models' 11import { LiveVideo, LiveVideoCreate, LiveVideoUpdate, PeerTubeProblemDocument, ServerErrorCode, VideoPrivacy } from '@shared/models'
@@ -127,7 +127,7 @@ export class VideoGoLiveComponent extends VideoSend implements OnInit, AfterView
127 () => { 127 () => {
128 this.notifier.success($localize`Live published.`) 128 this.notifier.success($localize`Live published.`)
129 129
130 this.router.navigate(['/w', video.uuid]) 130 this.router.navigateByUrl(Video.buildWatchUrl(video))
131 }, 131 },
132 132
133 err => { 133 err => {
diff --git a/client/src/app/+videos/+video-edit/video-add-components/video-upload.component.ts b/client/src/app/+videos/+video-edit/video-add-components/video-upload.component.ts
index f383662a1..ec027f257 100644
--- a/client/src/app/+videos/+video-edit/video-add-components/video-upload.component.ts
+++ b/client/src/app/+videos/+video-edit/video-add-components/video-upload.component.ts
@@ -1,16 +1,16 @@
1import { UploadState, UploadxOptions, UploadxService } from 'ngx-uploadx'
2import { HttpErrorResponse, HttpEventType, HttpHeaders } from '@angular/common/http'
1import { AfterViewInit, Component, ElementRef, EventEmitter, OnDestroy, OnInit, Output, ViewChild } from '@angular/core' 3import { AfterViewInit, Component, ElementRef, EventEmitter, OnDestroy, OnInit, Output, ViewChild } from '@angular/core'
2import { Router } from '@angular/router' 4import { Router } from '@angular/router'
3import { UploadxOptions, UploadState, UploadxService } from 'ngx-uploadx'
4import { UploaderXFormData } from './uploaderx-form-data'
5import { AuthService, CanComponentDeactivate, HooksService, Notifier, ServerService, UserService } from '@app/core' 5import { AuthService, CanComponentDeactivate, HooksService, Notifier, ServerService, UserService } from '@app/core'
6import { scrollToTop, genericUploadErrorHandler } from '@app/helpers' 6import { genericUploadErrorHandler, scrollToTop } from '@app/helpers'
7import { FormValidatorService } from '@app/shared/shared-forms' 7import { FormValidatorService } from '@app/shared/shared-forms'
8import { BytesPipe, VideoCaptionService, VideoEdit, VideoService } from '@app/shared/shared-main' 8import { BytesPipe, Video, VideoCaptionService, VideoEdit, VideoService } from '@app/shared/shared-main'
9import { LoadingBarService } from '@ngx-loading-bar/core' 9import { LoadingBarService } from '@ngx-loading-bar/core'
10import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes' 10import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes'
11import { VideoPrivacy } from '@shared/models' 11import { VideoPrivacy } from '@shared/models'
12import { UploaderXFormData } from './uploaderx-form-data'
12import { VideoSend } from './video-send' 13import { VideoSend } from './video-send'
13import { HttpErrorResponse, HttpEventType, HttpHeaders } from '@angular/common/http'
14 14
15@Component({ 15@Component({
16 selector: 'my-video-upload', 16 selector: 'my-video-upload',
@@ -243,7 +243,7 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy
243 this.isUploadingVideo = false 243 this.isUploadingVideo = false
244 244
245 this.notifier.success($localize`Video published.`) 245 this.notifier.success($localize`Video published.`)
246 this.router.navigate([ '/w', video.uuid ]) 246 this.router.navigateByUrl(Video.buildWatchUrl(video))
247 }, 247 },
248 248
249 err => { 249 err => {
diff --git a/client/src/app/+videos/+video-edit/video-update.component.html b/client/src/app/+videos/+video-edit/video-update.component.html
index 9629081e3..33e3ddd14 100644
--- a/client/src/app/+videos/+video-edit/video-update.component.html
+++ b/client/src/app/+videos/+video-edit/video-update.component.html
@@ -1,7 +1,7 @@
1<div class="margin-content"> 1<div class="margin-content">
2 <div class="title-page title-page-single"> 2 <div class="title-page title-page-single">
3 <span class="mr-1" i18n>Update</span> 3 <span class="mr-1" i18n>Update</span>
4 <a [routerLink]="[ '/w', video.uuid ]">{{ video?.name }}</a> 4 <a [routerLink]="getVideoUrl()">{{ video?.name }}</a>
5 </div> 5 </div>
6 6
7 <form novalidate [formGroup]="form"> 7 <form novalidate [formGroup]="form">
diff --git a/client/src/app/+videos/+video-edit/video-update.component.ts b/client/src/app/+videos/+video-edit/video-update.component.ts
index 574669a23..1534eee82 100644
--- a/client/src/app/+videos/+video-edit/video-update.component.ts
+++ b/client/src/app/+videos/+video-edit/video-update.component.ts
@@ -5,7 +5,7 @@ import { Component, HostListener, OnInit } from '@angular/core'
5import { ActivatedRoute, Router } from '@angular/router' 5import { ActivatedRoute, Router } from '@angular/router'
6import { Notifier } from '@app/core' 6import { Notifier } from '@app/core'
7import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' 7import { FormReactive, FormValidatorService } from '@app/shared/shared-forms'
8import { VideoCaptionEdit, VideoCaptionService, VideoDetails, VideoEdit, VideoService } from '@app/shared/shared-main' 8import { Video, VideoCaptionEdit, VideoCaptionService, VideoDetails, VideoEdit, VideoService } from '@app/shared/shared-main'
9import { LiveVideoService } from '@app/shared/shared-video-live' 9import { LiveVideoService } from '@app/shared/shared-video-live'
10import { LoadingBarService } from '@ngx-loading-bar/core' 10import { LoadingBarService } from '@ngx-loading-bar/core'
11import { LiveVideo, LiveVideoUpdate, VideoPrivacy } from '@shared/models' 11import { LiveVideo, LiveVideoUpdate, VideoPrivacy } from '@shared/models'
@@ -156,7 +156,7 @@ export class VideoUpdateComponent extends FormReactive implements OnInit {
156 this.isUpdatingVideo = false 156 this.isUpdatingVideo = false
157 this.loadingBar.useRef().complete() 157 this.loadingBar.useRef().complete()
158 this.notifier.success($localize`Video updated.`) 158 this.notifier.success($localize`Video updated.`)
159 this.router.navigate([ '/w', this.video.uuid ]) 159 this.router.navigateByUrl(Video.buildWatchUrl(this.video))
160 }, 160 },
161 161
162 err => { 162 err => {
@@ -175,4 +175,8 @@ export class VideoUpdateComponent extends FormReactive implements OnInit {
175 pluginData: this.video.pluginData 175 pluginData: this.video.pluginData
176 }) 176 })
177 } 177 }
178
179 getVideoUrl () {
180 return Video.buildWatchUrl(this.videoDetails)
181 }
178} 182}
diff --git a/client/src/app/+videos/+video-watch/comment/video-comment.component.html b/client/src/app/+videos/+video-watch/comment/video-comment.component.html
index 06548edc8..d8b944b35 100644
--- a/client/src/app/+videos/+video-watch/comment/video-comment.component.html
+++ b/client/src/app/+videos/+video-watch/comment/video-comment.component.html
@@ -20,7 +20,7 @@
20 </a> 20 </a>
21 </div> 21 </div>
22 22
23 <a [routerLink]="['/w', video.uuid, { 'threadId': comment.threadId }]" class="comment-date" [title]="comment.createdAt"> 23 <a [routerLink]="['/w', video.shortUUID, { 'threadId': comment.threadId }]" class="comment-date" [title]="comment.createdAt">
24 {{ comment.createdAt | myFromNow }} 24 {{ comment.createdAt | myFromNow }}
25 </a> 25 </a>
26 </div> 26 </div>
@@ -45,7 +45,7 @@
45 <ng-container *ngIf="comment.isDeleted"> 45 <ng-container *ngIf="comment.isDeleted">
46 <div class="comment-account-date"> 46 <div class="comment-account-date">
47 <span class="comment-account" i18n>Deleted</span> 47 <span class="comment-account" i18n>Deleted</span>
48 <a [routerLink]="['/w', video.uuid, { 'threadId': comment.threadId }]" 48 <a [routerLink]="['/w', video.shortUUID, { 'threadId': comment.threadId }]"
49 class="comment-date">{{ comment.createdAt | myFromNow }}</a> 49 class="comment-date">{{ comment.createdAt | myFromNow }}</a>
50 </div> 50 </div>
51 51
diff --git a/client/src/app/+videos/+video-watch/comment/video-comments.component.ts b/client/src/app/+videos/+video-watch/comment/video-comments.component.ts
index 210236b61..2c39e63fb 100644
--- a/client/src/app/+videos/+video-watch/comment/video-comments.component.ts
+++ b/client/src/app/+videos/+video-watch/comment/video-comments.component.ts
@@ -247,7 +247,7 @@ export class VideoCommentsComponent implements OnInit, OnChanges, OnDestroy {
247 this.componentPagination.totalItems = null 247 this.componentPagination.totalItems = null
248 this.totalNotDeletedComments = null 248 this.totalNotDeletedComments = null
249 249
250 this.syndicationItems = this.videoCommentService.getVideoCommentsFeeds(this.video.uuid) 250 this.syndicationItems = this.videoCommentService.getVideoCommentsFeeds(this.video)
251 this.loadMoreThreads() 251 this.loadMoreThreads()
252 } 252 }
253 } 253 }
diff --git a/client/src/app/+videos/+video-watch/video-watch.component.ts b/client/src/app/+videos/+video-watch/video-watch.component.ts
index a444dc51f..12b0baebe 100644
--- a/client/src/app/+videos/+video-watch/video-watch.component.ts
+++ b/client/src/app/+videos/+video-watch/video-watch.component.ts
@@ -312,7 +312,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
312 312
313 getVideoUrl () { 313 getVideoUrl () {
314 if (!this.video.url) { 314 if (!this.video.url) {
315 return this.video.originInstanceUrl + VideoDetails.buildClientUrl(this.video.uuid) 315 return this.video.originInstanceUrl + VideoDetails.buildWatchUrl(this.video)
316 } 316 }
317 return this.video.url 317 return this.video.url
318 } 318 }
@@ -415,7 +415,10 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
415 415
416 private loadVideo (videoId: string) { 416 private loadVideo (videoId: string) {
417 // Video did not change 417 // Video did not change
418 if (this.video && this.video.uuid === videoId) return 418 if (
419 this.video &&
420 (this.video.uuid === videoId || this.video.shortUUID === videoId)
421 ) return
419 422
420 if (this.player) this.player.pause() 423 if (this.player) this.player.pause()
421 424
@@ -489,7 +492,10 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
489 492
490 private loadPlaylist (playlistId: string) { 493 private loadPlaylist (playlistId: string) {
491 // Playlist did not change 494 // Playlist did not change
492 if (this.playlist && this.playlist.uuid === playlistId) return 495 if (
496 this.playlist &&
497 (this.playlist.uuid === playlistId || this.playlist.shortUUID === playlistId)
498 ) return
493 499
494 this.playlistService.getVideoPlaylist(playlistId) 500 this.playlistService.getVideoPlaylist(playlistId)
495 .pipe( 501 .pipe(
@@ -772,13 +778,13 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
772 778
773 private flushPlayer () { 779 private flushPlayer () {
774 // Remove player if it exists 780 // Remove player if it exists
775 if (this.player) { 781 if (!this.player) return
776 try { 782
777 this.player.dispose() 783 try {
778 this.player = undefined 784 this.player.dispose()
779 } catch (err) { 785 this.player = undefined
780 console.error('Cannot dispose player.', err) 786 } catch (err) {
781 } 787 console.error('Cannot dispose player.', err)
782 } 788 }
783 } 789 }
784 790
diff --git a/client/src/app/shared/shared-abuse-list/abuse-list-table.component.ts b/client/src/app/shared/shared-abuse-list/abuse-list-table.component.ts
index 07b9dddba..67aa0e399 100644
--- a/client/src/app/shared/shared-abuse-list/abuse-list-table.component.ts
+++ b/client/src/app/shared/shared-abuse-list/abuse-list-table.component.ts
@@ -116,11 +116,11 @@ export class AbuseListTableComponent extends RestTable implements OnInit {
116 } 116 }
117 117
118 getVideoUrl (abuse: AdminAbuse) { 118 getVideoUrl (abuse: AdminAbuse) {
119 return Video.buildClientUrl(abuse.video.uuid) 119 return Video.buildWatchUrl(abuse.video)
120 } 120 }
121 121
122 getCommentUrl (abuse: AdminAbuse) { 122 getCommentUrl (abuse: AdminAbuse) {
123 return Video.buildClientUrl(abuse.comment.video.uuid) + ';threadId=' + abuse.comment.threadId 123 return Video.buildWatchUrl(abuse.comment.video) + ';threadId=' + abuse.comment.threadId
124 } 124 }
125 125
126 getAccountUrl (abuse: ProcessedAbuse) { 126 getAccountUrl (abuse: ProcessedAbuse) {
diff --git a/client/src/app/shared/shared-main/angular/link.component.ts b/client/src/app/shared/shared-main/angular/link.component.ts
index 76d1201b9..597a16871 100644
--- a/client/src/app/shared/shared-main/angular/link.component.ts
+++ b/client/src/app/shared/shared-main/angular/link.component.ts
@@ -6,7 +6,7 @@ import { Component, Input, ViewEncapsulation } from '@angular/core'
6 templateUrl: './link.component.html' 6 templateUrl: './link.component.html'
7}) 7})
8export class LinkComponent { 8export class LinkComponent {
9 @Input() internalLink?: any[] 9 @Input() internalLink?: string | any[]
10 10
11 @Input() href?: string 11 @Input() href?: string
12 @Input() target?: string 12 @Input() target?: string
diff --git a/client/src/app/shared/shared-main/users/user-notification.model.ts b/client/src/app/shared/shared-main/users/user-notification.model.ts
index c80bc13b0..4c15eb981 100644
--- a/client/src/app/shared/shared-main/users/user-notification.model.ts
+++ b/client/src/app/shared/shared-main/users/user-notification.model.ts
@@ -12,6 +12,7 @@ import {
12 UserRight, 12 UserRight,
13 VideoInfo 13 VideoInfo
14} from '@shared/models' 14} from '@shared/models'
15import { Video } from '../video'
15 16
16export class UserNotification implements UserNotificationServer { 17export class UserNotification implements UserNotificationServer {
17 id: number 18 id: number
@@ -238,7 +239,7 @@ export class UserNotification implements UserNotificationServer {
238 } 239 }
239 240
240 private buildVideoUrl (video: { uuid: string }) { 241 private buildVideoUrl (video: { uuid: string }) {
241 return '/w/' + video.uuid 242 return Video.buildWatchUrl(video)
242 } 243 }
243 244
244 private buildAccountUrl (account: { name: string, host: string }) { 245 private buildAccountUrl (account: { name: string, host: string }) {
diff --git a/client/src/app/shared/shared-main/video/video.model.ts b/client/src/app/shared/shared-main/video/video.model.ts
index ab8ed9051..f0a4a3f37 100644
--- a/client/src/app/shared/shared-main/video/video.model.ts
+++ b/client/src/app/shared/shared-main/video/video.model.ts
@@ -26,12 +26,18 @@ export class Video implements VideoServerModel {
26 licence: VideoConstant<number> 26 licence: VideoConstant<number>
27 language: VideoConstant<string> 27 language: VideoConstant<string>
28 privacy: VideoConstant<VideoPrivacy> 28 privacy: VideoConstant<VideoPrivacy>
29
29 description: string 30 description: string
31
30 duration: number 32 duration: number
31 durationLabel: string 33 durationLabel: string
34
32 id: number 35 id: number
33 uuid: string 36 uuid: string
37 shortUUID: string
38
34 isLocal: boolean 39 isLocal: boolean
40
35 name: string 41 name: string
36 serverHost: string 42 serverHost: string
37 thumbnailPath: string 43 thumbnailPath: string
@@ -85,8 +91,12 @@ export class Video implements VideoServerModel {
85 91
86 pluginData?: any 92 pluginData?: any
87 93
88 static buildClientUrl (videoUUID: string) { 94 static buildWatchUrl (video: Partial<Pick<Video, 'uuid' | 'shortUUID'>>) {
89 return '/w/' + videoUUID 95 return '/w/' + (video.shortUUID || video.uuid)
96 }
97
98 static buildUpdateUrl (video: Pick<Video, 'uuid'>) {
99 return '/videos/update/' + video.uuid
90 } 100 }
91 101
92 constructor (hash: VideoServerModel, translations = {}) { 102 constructor (hash: VideoServerModel, translations = {}) {
@@ -109,6 +119,7 @@ export class Video implements VideoServerModel {
109 119
110 this.id = hash.id 120 this.id = hash.id
111 this.uuid = hash.uuid 121 this.uuid = hash.uuid
122 this.shortUUID = hash.shortUUID
112 123
113 this.isLocal = hash.isLocal 124 this.isLocal = hash.isLocal
114 this.name = hash.name 125 this.name = hash.name
diff --git a/client/src/app/shared/shared-share-modal/video-share.component.ts b/client/src/app/shared/shared-share-modal/video-share.component.ts
index 2a73e6166..a41ff248b 100644
--- a/client/src/app/shared/shared-share-modal/video-share.component.ts
+++ b/client/src/app/shared/shared-share-modal/video-share.component.ts
@@ -1,5 +1,5 @@
1import { Component, ElementRef, Input, ViewChild } from '@angular/core' 1import { Component, ElementRef, Input, ViewChild } from '@angular/core'
2import { VideoDetails } from '@app/shared/shared-main' 2import { Video, VideoDetails } from '@app/shared/shared-main'
3import { VideoPlaylist } from '@app/shared/shared-video-playlist' 3import { VideoPlaylist } from '@app/shared/shared-video-playlist'
4import { NgbModal } from '@ng-bootstrap/ng-bootstrap' 4import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
5import { VideoCaption } from '@shared/models' 5import { VideoCaption } from '@shared/models'
@@ -98,14 +98,15 @@ export class VideoShareComponent {
98 98
99 getVideoUrl () { 99 getVideoUrl () {
100 let baseUrl = this.customizations.originUrl ? this.video.originInstanceUrl : window.location.origin 100 let baseUrl = this.customizations.originUrl ? this.video.originInstanceUrl : window.location.origin
101 baseUrl += '/w/' + this.video.uuid 101 baseUrl += Video.buildWatchUrl(this.video)
102
102 const options = this.getVideoOptions(baseUrl) 103 const options = this.getVideoOptions(baseUrl)
103 104
104 return buildVideoLink(options) 105 return buildVideoLink(options)
105 } 106 }
106 107
107 getPlaylistUrl () { 108 getPlaylistUrl () {
108 const base = window.location.origin + '/w/p/' + this.playlist.uuid 109 const base = window.location.origin + VideoPlaylist.buildWatchUrl(this.playlist)
109 110
110 if (!this.includeVideoInPlaylist) return base 111 if (!this.includeVideoInPlaylist) return base
111 112
diff --git a/client/src/app/shared/shared-thumbnail/video-thumbnail.component.ts b/client/src/app/shared/shared-thumbnail/video-thumbnail.component.ts
index d5583c29f..ad5d30db2 100644
--- a/client/src/app/shared/shared-thumbnail/video-thumbnail.component.ts
+++ b/client/src/app/shared/shared-thumbnail/video-thumbnail.component.ts
@@ -12,7 +12,7 @@ export class VideoThumbnailComponent {
12 @Input() video: Video 12 @Input() video: Video
13 @Input() nsfw = false 13 @Input() nsfw = false
14 14
15 @Input() videoRouterLink: any[] 15 @Input() videoRouterLink: string | any[]
16 @Input() queryParams: { [ p: string ]: any } 16 @Input() queryParams: { [ p: string ]: any }
17 @Input() videoHref: string 17 @Input() videoHref: string
18 @Input() videoTarget: string 18 @Input() videoTarget: string
@@ -57,7 +57,7 @@ export class VideoThumbnailComponent {
57 getVideoRouterLink () { 57 getVideoRouterLink () {
58 if (this.videoRouterLink) return this.videoRouterLink 58 if (this.videoRouterLink) return this.videoRouterLink
59 59
60 return [ '/w', this.video.uuid ] 60 return Video.buildWatchUrl(this.video)
61 } 61 }
62 62
63 onWatchLaterClick (event: Event) { 63 onWatchLaterClick (event: Event) {
diff --git a/client/src/app/shared/shared-video-comment/video-comment.model.ts b/client/src/app/shared/shared-video-comment/video-comment.model.ts
index 94d6c5fa8..ba0f57e8f 100644
--- a/client/src/app/shared/shared-video-comment/video-comment.model.ts
+++ b/client/src/app/shared/shared-video-comment/video-comment.model.ts
@@ -1,5 +1,5 @@
1import { getAbsoluteAPIUrl } from '@app/helpers' 1import { getAbsoluteAPIUrl } from '@app/helpers'
2import { Account, Actor } from '@app/shared/shared-main' 2import { Account, Actor, Video } from '@app/shared/shared-main'
3import { Account as AccountInterface, VideoComment as VideoCommentServerModel, VideoCommentAdmin as VideoCommentAdminServerModel } from '@shared/models' 3import { Account as AccountInterface, VideoComment as VideoCommentServerModel, VideoCommentAdmin as VideoCommentAdminServerModel } from '@shared/models'
4 4
5export class VideoComment implements VideoCommentServerModel { 5export class VideoComment implements VideoCommentServerModel {
@@ -85,7 +85,7 @@ export class VideoCommentAdmin implements VideoCommentAdminServerModel {
85 id: hash.video.id, 85 id: hash.video.id,
86 uuid: hash.video.uuid, 86 uuid: hash.video.uuid,
87 name: hash.video.name, 87 name: hash.video.name,
88 localUrl: '/w/' + hash.video.uuid 88 localUrl: Video.buildWatchUrl(hash.video)
89 } 89 }
90 90
91 this.localUrl = this.video.localUrl + ';threadId=' + this.threadId 91 this.localUrl = this.video.localUrl + ';threadId=' + this.threadId
diff --git a/client/src/app/shared/shared-video-comment/video-comment.service.ts b/client/src/app/shared/shared-video-comment/video-comment.service.ts
index c5aeb3c12..4f1452116 100644
--- a/client/src/app/shared/shared-video-comment/video-comment.service.ts
+++ b/client/src/app/shared/shared-video-comment/video-comment.service.ts
@@ -9,6 +9,7 @@ import {
9 FeedFormat, 9 FeedFormat,
10 ResultList, 10 ResultList,
11 ThreadsResultList, 11 ThreadsResultList,
12 Video,
12 VideoComment as VideoCommentServerModel, 13 VideoComment as VideoCommentServerModel,
13 VideoCommentAdmin, 14 VideoCommentAdmin,
14 VideoCommentCreate, 15 VideoCommentCreate,
@@ -127,7 +128,7 @@ export class VideoCommentService {
127 ) 128 )
128 } 129 }
129 130
130 getVideoCommentsFeeds (videoUUID?: string) { 131 getVideoCommentsFeeds (video: Pick<Video, 'uuid'>) {
131 const feeds = [ 132 const feeds = [
132 { 133 {
133 format: FeedFormat.RSS, 134 format: FeedFormat.RSS,
@@ -146,9 +147,9 @@ export class VideoCommentService {
146 } 147 }
147 ] 148 ]
148 149
149 if (videoUUID !== undefined) { 150 if (video !== undefined) {
150 for (const feed of feeds) { 151 for (const feed of feeds) {
151 feed.url += '?videoId=' + videoUUID 152 feed.url += '?videoId=' + video.uuid
152 } 153 }
153 } 154 }
154 155
diff --git a/client/src/app/shared/shared-video-miniature/video-miniature.component.ts b/client/src/app/shared/shared-video-miniature/video-miniature.component.ts
index fe161c977..67e0de6a2 100644
--- a/client/src/app/shared/shared-video-miniature/video-miniature.component.ts
+++ b/client/src/app/shared/shared-video-miniature/video-miniature.component.ts
@@ -85,7 +85,7 @@ export class VideoMiniatureComponent implements OnInit {
85 playlistElementId?: number 85 playlistElementId?: number
86 } 86 }
87 87
88 videoRouterLink: any[] = [] 88 videoRouterLink: string | any[] = []
89 videoHref: string 89 videoHref: string
90 videoTarget: string 90 videoTarget: string
91 91
@@ -120,7 +120,7 @@ export class VideoMiniatureComponent implements OnInit {
120 120
121 buildVideoLink () { 121 buildVideoLink () {
122 if (this.videoLinkType === 'internal' || !this.video.url) { 122 if (this.videoLinkType === 'internal' || !this.video.url) {
123 this.videoRouterLink = [ '/w', this.video.uuid ] 123 this.videoRouterLink = Video.buildWatchUrl(this.video)
124 return 124 return
125 } 125 }
126 126
diff --git a/client/src/app/shared/shared-video-playlist/video-playlist-element-miniature.component.ts b/client/src/app/shared/shared-video-playlist/video-playlist-element-miniature.component.ts
index 57eab4dfd..d99170e4e 100644
--- a/client/src/app/shared/shared-video-playlist/video-playlist-element-miniature.component.ts
+++ b/client/src/app/shared/shared-video-playlist/video-playlist-element-miniature.component.ts
@@ -66,7 +66,7 @@ export class VideoPlaylistElementMiniatureComponent implements OnInit {
66 buildRouterLink () { 66 buildRouterLink () {
67 if (!this.playlist) return null 67 if (!this.playlist) return null
68 68
69 return [ '/w/p', this.playlist.uuid ] 69 return VideoPlaylist.buildWatchUrl(this.playlist)
70 } 70 }
71 71
72 buildRouterQuery () { 72 buildRouterQuery () {
diff --git a/client/src/app/shared/shared-video-playlist/video-playlist-miniature.component.ts b/client/src/app/shared/shared-video-playlist/video-playlist-miniature.component.ts
index 8de5092a9..c80ea2e6b 100644
--- a/client/src/app/shared/shared-video-playlist/video-playlist-miniature.component.ts
+++ b/client/src/app/shared/shared-video-playlist/video-playlist-miniature.component.ts
@@ -39,7 +39,7 @@ export class VideoPlaylistMiniatureComponent implements OnInit {
39 } 39 }
40 40
41 if (this.linkType === 'internal' || !this.playlist.url) { 41 if (this.linkType === 'internal' || !this.playlist.url) {
42 this.routerLink = [ '/w/p', this.playlist.uuid ] 42 this.routerLink = VideoPlaylist.buildWatchUrl(this.playlist)
43 return 43 return
44 } 44 }
45 45
diff --git a/client/src/app/shared/shared-video-playlist/video-playlist.model.ts b/client/src/app/shared/shared-video-playlist/video-playlist.model.ts
index d67f372f4..d96b70922 100644
--- a/client/src/app/shared/shared-video-playlist/video-playlist.model.ts
+++ b/client/src/app/shared/shared-video-playlist/video-playlist.model.ts
@@ -13,6 +13,8 @@ import {
13export class VideoPlaylist implements ServerVideoPlaylist { 13export class VideoPlaylist implements ServerVideoPlaylist {
14 id: number 14 id: number
15 uuid: string 15 uuid: string
16 shortUUID: string
17
16 isLocal: boolean 18 isLocal: boolean
17 19
18 url: string 20 url: string
@@ -41,11 +43,17 @@ export class VideoPlaylist implements ServerVideoPlaylist {
41 43
42 videoChannelBy?: string 44 videoChannelBy?: string
43 45
46 static buildWatchUrl (playlist: Pick<VideoPlaylist, 'uuid' | 'shortUUID'>) {
47 return '/w/p/' + (playlist.uuid || playlist.shortUUID)
48 }
49
44 constructor (hash: ServerVideoPlaylist, translations: {}) { 50 constructor (hash: ServerVideoPlaylist, translations: {}) {
45 const absoluteAPIUrl = getAbsoluteAPIUrl() 51 const absoluteAPIUrl = getAbsoluteAPIUrl()
46 52
47 this.id = hash.id 53 this.id = hash.id
48 this.uuid = hash.uuid 54 this.uuid = hash.uuid
55 this.shortUUID = hash.shortUUID
56
49 this.url = hash.url 57 this.url = hash.url
50 this.isLocal = hash.isLocal 58 this.isLocal = hash.isLocal
51 59
diff --git a/package.json b/package.json
index 9fa1e4a0f..dcecd3940 100644
--- a/package.json
+++ b/package.json
@@ -131,6 +131,7 @@
131 "sanitize-html": "2.x", 131 "sanitize-html": "2.x",
132 "sequelize": "6.6.2", 132 "sequelize": "6.6.2",
133 "sequelize-typescript": "^2.0.0-beta.1", 133 "sequelize-typescript": "^2.0.0-beta.1",
134 "short-uuid": "^4.2.0",
134 "sitemap": "^7.0.0", 135 "sitemap": "^7.0.0",
135 "socket.io": "^4.0.1", 136 "socket.io": "^4.0.1",
136 "sql-formatter": "^4.0.0-beta.0", 137 "sql-formatter": "^4.0.0-beta.0",
@@ -138,7 +139,6 @@
138 "tsconfig-paths": "^3.9.0", 139 "tsconfig-paths": "^3.9.0",
139 "tslib": "^2.0.0", 140 "tslib": "^2.0.0",
140 "useragent": "^2.3.0", 141 "useragent": "^2.3.0",
141 "uuid": "^8.1.0",
142 "validator": "^13.0.0", 142 "validator": "^13.0.0",
143 "webfinger.js": "^2.6.6", 143 "webfinger.js": "^2.6.6",
144 "webtorrent": "^1.0.0", 144 "webtorrent": "^1.0.0",
diff --git a/server/controllers/api/users/token.ts b/server/controllers/api/users/token.ts
index e636f44f6..b405ddbf4 100644
--- a/server/controllers/api/users/token.ts
+++ b/server/controllers/api/users/token.ts
@@ -1,7 +1,7 @@
1import * as express from 'express' 1import * as express from 'express'
2import * as RateLimit from 'express-rate-limit' 2import * as RateLimit from 'express-rate-limit'
3import { v4 as uuidv4 } from 'uuid'
4import { logger } from '@server/helpers/logger' 3import { logger } from '@server/helpers/logger'
4import { buildUUID } from '@server/helpers/uuid'
5import { CONFIG } from '@server/initializers/config' 5import { CONFIG } from '@server/initializers/config'
6import { getAuthNameFromRefreshGrant, getBypassFromExternalAuth, getBypassFromPasswordGrant } from '@server/lib/auth/external-auth' 6import { getAuthNameFromRefreshGrant, getBypassFromExternalAuth, getBypassFromPasswordGrant } from '@server/lib/auth/external-auth'
7import { handleOAuthToken } from '@server/lib/auth/oauth' 7import { handleOAuthToken } from '@server/lib/auth/oauth'
@@ -107,7 +107,7 @@ function getScopedTokens (req: express.Request, res: express.Response) {
107async function renewScopedTokens (req: express.Request, res: express.Response) { 107async function renewScopedTokens (req: express.Request, res: express.Response) {
108 const user = res.locals.oauth.token.user 108 const user = res.locals.oauth.token.user
109 109
110 user.feedToken = uuidv4() 110 user.feedToken = buildUUID()
111 await user.save() 111 await user.save()
112 112
113 return res.json({ 113 return res.json({
diff --git a/server/controllers/api/video-playlist.ts b/server/controllers/api/video-playlist.ts
index 5c4aa50ac..87a6f6bbe 100644
--- a/server/controllers/api/video-playlist.ts
+++ b/server/controllers/api/video-playlist.ts
@@ -1,6 +1,8 @@
1import * as express from 'express' 1import * as express from 'express'
2import { join } from 'path' 2import { join } from 'path'
3import { uuidToShort } from '@server/helpers/uuid'
3import { scheduleRefreshIfNeeded } from '@server/lib/activitypub/playlists' 4import { scheduleRefreshIfNeeded } from '@server/lib/activitypub/playlists'
5import { Hooks } from '@server/lib/plugins/hooks'
4import { getServerActor } from '@server/models/application/application' 6import { getServerActor } from '@server/models/application/application'
5import { MVideoPlaylistFull, MVideoPlaylistThumbnail, MVideoThumbnail } from '@server/types/models' 7import { MVideoPlaylistFull, MVideoPlaylistThumbnail, MVideoThumbnail } from '@server/types/models'
6import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' 8import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes'
@@ -43,7 +45,6 @@ import {
43import { AccountModel } from '../../models/account/account' 45import { AccountModel } from '../../models/account/account'
44import { VideoPlaylistModel } from '../../models/video/video-playlist' 46import { VideoPlaylistModel } from '../../models/video/video-playlist'
45import { VideoPlaylistElementModel } from '../../models/video/video-playlist-element' 47import { VideoPlaylistElementModel } from '../../models/video/video-playlist-element'
46import { Hooks } from '@server/lib/plugins/hooks'
47 48
48const reqThumbnailFile = createReqFiles([ 'thumbnailfile' ], MIMETYPES.IMAGE.MIMETYPE_EXT, { thumbnailfile: CONFIG.STORAGE.TMP_DIR }) 49const reqThumbnailFile = createReqFiles([ 'thumbnailfile' ], MIMETYPES.IMAGE.MIMETYPE_EXT, { thumbnailfile: CONFIG.STORAGE.TMP_DIR })
49 50
@@ -199,6 +200,7 @@ async function addVideoPlaylist (req: express.Request, res: express.Response) {
199 return res.json({ 200 return res.json({
200 videoPlaylist: { 201 videoPlaylist: {
201 id: videoPlaylistCreated.id, 202 id: videoPlaylistCreated.id,
203 shortUUID: uuidToShort(videoPlaylistCreated.uuid),
202 uuid: videoPlaylistCreated.uuid 204 uuid: videoPlaylistCreated.uuid
203 } 205 }
204 }) 206 })
diff --git a/server/controllers/api/videos/live.ts b/server/controllers/api/videos/live.ts
index 61fa979c4..d8c51c2d4 100644
--- a/server/controllers/api/videos/live.ts
+++ b/server/controllers/api/videos/live.ts
@@ -1,6 +1,6 @@
1import * as express from 'express' 1import * as express from 'express'
2import { v4 as uuidv4 } from 'uuid'
3import { createReqFiles } from '@server/helpers/express-utils' 2import { createReqFiles } from '@server/helpers/express-utils'
3import { buildUUID, uuidToShort } from '@server/helpers/uuid'
4import { CONFIG } from '@server/initializers/config' 4import { CONFIG } from '@server/initializers/config'
5import { ASSETS_PATH, MIMETYPES } from '@server/initializers/constants' 5import { ASSETS_PATH, MIMETYPES } from '@server/initializers/constants'
6import { getLocalVideoActivityPubUrl } from '@server/lib/activitypub/url' 6import { getLocalVideoActivityPubUrl } from '@server/lib/activitypub/url'
@@ -11,12 +11,12 @@ import { videoLiveAddValidator, videoLiveGetValidator, videoLiveUpdateValidator
11import { VideoLiveModel } from '@server/models/video/video-live' 11import { VideoLiveModel } from '@server/models/video/video-live'
12import { MVideoDetails, MVideoFullLight } from '@server/types/models' 12import { MVideoDetails, MVideoFullLight } from '@server/types/models'
13import { LiveVideoCreate, LiveVideoUpdate, VideoState } from '../../../../shared' 13import { LiveVideoCreate, LiveVideoUpdate, VideoState } from '../../../../shared'
14import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes'
14import { logger } from '../../../helpers/logger' 15import { logger } from '../../../helpers/logger'
15import { sequelizeTypescript } from '../../../initializers/database' 16import { sequelizeTypescript } from '../../../initializers/database'
16import { updateVideoMiniatureFromExisting } from '../../../lib/thumbnail' 17import { updateVideoMiniatureFromExisting } from '../../../lib/thumbnail'
17import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate } from '../../../middlewares' 18import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate } from '../../../middlewares'
18import { VideoModel } from '../../../models/video/video' 19import { VideoModel } from '../../../models/video/video'
19import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes'
20 20
21const liveRouter = express.Router() 21const liveRouter = express.Router()
22 22
@@ -94,7 +94,7 @@ async function addLiveVideo (req: express.Request, res: express.Response) {
94 const videoLive = new VideoLiveModel() 94 const videoLive = new VideoLiveModel()
95 videoLive.saveReplay = videoInfo.saveReplay || false 95 videoLive.saveReplay = videoInfo.saveReplay || false
96 videoLive.permanentLive = videoInfo.permanentLive || false 96 videoLive.permanentLive = videoInfo.permanentLive || false
97 videoLive.streamKey = uuidv4() 97 videoLive.streamKey = buildUUID()
98 98
99 const [ thumbnailModel, previewModel ] = await buildVideoThumbnailsFromReq({ 99 const [ thumbnailModel, previewModel ] = await buildVideoThumbnailsFromReq({
100 video, 100 video,
@@ -138,6 +138,7 @@ async function addLiveVideo (req: express.Request, res: express.Response) {
138 return res.json({ 138 return res.json({
139 video: { 139 video: {
140 id: videoCreated.id, 140 id: videoCreated.id,
141 shortUUID: uuidToShort(videoCreated.uuid),
141 uuid: videoCreated.uuid 142 uuid: videoCreated.uuid
142 } 143 }
143 }) 144 })
diff --git a/server/controllers/api/videos/upload.ts b/server/controllers/api/videos/upload.ts
index e767492bc..bcd21ac99 100644
--- a/server/controllers/api/videos/upload.ts
+++ b/server/controllers/api/videos/upload.ts
@@ -2,6 +2,7 @@ import * as express from 'express'
2import { move } from 'fs-extra' 2import { move } from 'fs-extra'
3import { getLowercaseExtension } from '@server/helpers/core-utils' 3import { getLowercaseExtension } from '@server/helpers/core-utils'
4import { deleteResumableUploadMetaFile, getResumableUploadPath } from '@server/helpers/upload' 4import { deleteResumableUploadMetaFile, getResumableUploadPath } from '@server/helpers/upload'
5import { uuidToShort } from '@server/helpers/uuid'
5import { createTorrentAndSetInfoHash } from '@server/helpers/webtorrent' 6import { createTorrentAndSetInfoHash } from '@server/helpers/webtorrent'
6import { getLocalVideoActivityPubUrl } from '@server/lib/activitypub/url' 7import { getLocalVideoActivityPubUrl } from '@server/lib/activitypub/url'
7import { addOptimizeOrMergeAudioJob, buildLocalVideoFromReq, buildVideoThumbnailsFromReq, setVideoTags } from '@server/lib/video' 8import { addOptimizeOrMergeAudioJob, buildLocalVideoFromReq, buildVideoThumbnailsFromReq, setVideoTags } from '@server/lib/video'
@@ -218,6 +219,7 @@ async function addVideo (options: {
218 return res.json({ 219 return res.json({
219 video: { 220 video: {
220 id: videoCreated.id, 221 id: videoCreated.id,
222 shortUUID: uuidToShort(videoCreated.uuid),
221 uuid: videoCreated.uuid 223 uuid: videoCreated.uuid
222 } 224 }
223 }) 225 })
diff --git a/server/helpers/custom-validators/misc.ts b/server/helpers/custom-validators/misc.ts
index 229e9f03c..528bfcfb8 100644
--- a/server/helpers/custom-validators/misc.ts
+++ b/server/helpers/custom-validators/misc.ts
@@ -2,6 +2,7 @@ import 'multer'
2import { UploadFilesForCheck } from 'express' 2import { UploadFilesForCheck } from 'express'
3import { sep } from 'path' 3import { sep } from 'path'
4import validator from 'validator' 4import validator from 'validator'
5import { isShortUUID, shortToUUID } from '../uuid'
5 6
6function exists (value: any) { 7function exists (value: any) {
7 return value !== undefined && value !== null 8 return value !== undefined && value !== null
@@ -50,42 +51,7 @@ function isIntOrNull (value: any) {
50 return value === null || validator.isInt('' + value) 51 return value === null || validator.isInt('' + value)
51} 52}
52 53
53function toIntOrNull (value: string) { 54// ---------------------------------------------------------------------------
54 const v = toValueOrNull(value)
55
56 if (v === null || v === undefined) return v
57 if (typeof v === 'number') return v
58
59 return validator.toInt('' + v)
60}
61
62function toBooleanOrNull (value: any) {
63 const v = toValueOrNull(value)
64
65 if (v === null || v === undefined) return v
66 if (typeof v === 'boolean') return v
67
68 return validator.toBoolean('' + v)
69}
70
71function toValueOrNull (value: string) {
72 if (value === 'null') return null
73
74 return value
75}
76
77function toArray (value: any) {
78 if (value && isArray(value) === false) return [ value ]
79
80 return value
81}
82
83function toIntArray (value: any) {
84 if (!value) return []
85 if (isArray(value) === false) return [ validator.toInt(value) ]
86
87 return value.map(v => validator.toInt(v))
88}
89 55
90function isFileFieldValid ( 56function isFileFieldValid (
91 files: { [ fieldname: string ]: Express.Multer.File[] } | Express.Multer.File[], 57 files: { [ fieldname: string ]: Express.Multer.File[] } | Express.Multer.File[],
@@ -160,6 +126,51 @@ function isFileValid (
160 126
161// --------------------------------------------------------------------------- 127// ---------------------------------------------------------------------------
162 128
129function toCompleteUUID (value: string) {
130 if (isShortUUID(value)) return shortToUUID(value)
131
132 return value
133}
134
135function toIntOrNull (value: string) {
136 const v = toValueOrNull(value)
137
138 if (v === null || v === undefined) return v
139 if (typeof v === 'number') return v
140
141 return validator.toInt('' + v)
142}
143
144function toBooleanOrNull (value: any) {
145 const v = toValueOrNull(value)
146
147 if (v === null || v === undefined) return v
148 if (typeof v === 'boolean') return v
149
150 return validator.toBoolean('' + v)
151}
152
153function toValueOrNull (value: string) {
154 if (value === 'null') return null
155
156 return value
157}
158
159function toArray (value: any) {
160 if (value && isArray(value) === false) return [ value ]
161
162 return value
163}
164
165function toIntArray (value: any) {
166 if (!value) return []
167 if (isArray(value) === false) return [ validator.toInt(value) ]
168
169 return value.map(v => validator.toInt(v))
170}
171
172// ---------------------------------------------------------------------------
173
163export { 174export {
164 exists, 175 exists,
165 isArrayOf, 176 isArrayOf,
@@ -169,6 +180,7 @@ export {
169 isIdValid, 180 isIdValid,
170 isSafePath, 181 isSafePath,
171 isUUIDValid, 182 isUUIDValid,
183 toCompleteUUID,
172 isIdOrUUIDValid, 184 isIdOrUUIDValid,
173 isDateValid, 185 isDateValid,
174 toValueOrNull, 186 toValueOrNull,
diff --git a/server/helpers/image-utils.ts b/server/helpers/image-utils.ts
index 122fb009d..c76ed545b 100644
--- a/server/helpers/image-utils.ts
+++ b/server/helpers/image-utils.ts
@@ -1,12 +1,12 @@
1import { copy, readFile, remove, rename } from 'fs-extra' 1import { copy, readFile, remove, rename } from 'fs-extra'
2import * as Jimp from 'jimp' 2import * as Jimp from 'jimp'
3import { v4 as uuidv4 } from 'uuid'
4import { getLowercaseExtension } from './core-utils' 3import { getLowercaseExtension } from './core-utils'
5import { convertWebPToJPG, processGIF } from './ffmpeg-utils' 4import { convertWebPToJPG, processGIF } from './ffmpeg-utils'
6import { logger } from './logger' 5import { logger } from './logger'
6import { buildUUID } from './uuid'
7 7
8function generateImageFilename (extension = '.jpg') { 8function generateImageFilename (extension = '.jpg') {
9 return uuidv4() + extension 9 return buildUUID() + extension
10} 10}
11 11
12async function processImage ( 12async function processImage (
diff --git a/server/helpers/uuid.ts b/server/helpers/uuid.ts
new file mode 100644
index 000000000..3eb06c773
--- /dev/null
+++ b/server/helpers/uuid.ts
@@ -0,0 +1,32 @@
1import * as short from 'short-uuid'
2
3const translator = short()
4
5function buildUUID () {
6 return short.uuid()
7}
8
9function uuidToShort (uuid: string) {
10 if (!uuid) return uuid
11
12 return translator.fromUUID(uuid)
13}
14
15function shortToUUID (shortUUID: string) {
16 if (!shortUUID) return shortUUID
17
18 return translator.toUUID(shortUUID)
19}
20
21function isShortUUID (value: string) {
22 if (!value) return false
23
24 return value.length === translator.maxLength
25}
26
27export {
28 buildUUID,
29 uuidToShort,
30 shortToUUID,
31 isShortUUID
32}
diff --git a/server/initializers/migrations/0080-video-channels.ts b/server/initializers/migrations/0080-video-channels.ts
index 883224cb0..0e6952350 100644
--- a/server/initializers/migrations/0080-video-channels.ts
+++ b/server/initializers/migrations/0080-video-channels.ts
@@ -1,5 +1,5 @@
1import { buildUUID } from '@server/helpers/uuid'
1import * as Sequelize from 'sequelize' 2import * as Sequelize from 'sequelize'
2import { v4 as uuidv4 } from 'uuid'
3 3
4async function up (utils: { 4async function up (utils: {
5 transaction: Sequelize.Transaction 5 transaction: Sequelize.Transaction
@@ -23,7 +23,7 @@ async function up (utils: {
23 { 23 {
24 const authors = await utils.db.Author.findAll() 24 const authors = await utils.db.Author.findAll()
25 for (const author of authors) { 25 for (const author of authors) {
26 author.uuid = uuidv4() 26 author.uuid = buildUUID()
27 await author.save() 27 await author.save()
28 } 28 }
29 } 29 }
diff --git a/server/initializers/migrations/0345-video-playlists.ts b/server/initializers/migrations/0345-video-playlists.ts
index 89a14a6ee..8dd631dff 100644
--- a/server/initializers/migrations/0345-video-playlists.ts
+++ b/server/initializers/migrations/0345-video-playlists.ts
@@ -1,6 +1,6 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2import { buildUUID } from '@server/helpers/uuid'
2import { VideoPlaylistPrivacy, VideoPlaylistType } from '../../../shared/models/videos' 3import { VideoPlaylistPrivacy, VideoPlaylistType } from '../../../shared/models/videos'
3import { v4 as uuidv4 } from 'uuid'
4import { WEBSERVER } from '../constants' 4import { WEBSERVER } from '../constants'
5 5
6async function up (utils: { 6async function up (utils: {
@@ -57,7 +57,7 @@ CREATE TABLE IF NOT EXISTS "videoPlaylistElement"
57 const usernames = userResult.map(r => r.username) 57 const usernames = userResult.map(r => r.username)
58 58
59 for (const username of usernames) { 59 for (const username of usernames) {
60 const uuid = uuidv4() 60 const uuid = buildUUID()
61 61
62 const baseUrl = WEBSERVER.URL + '/video-playlists/' + uuid 62 const baseUrl = WEBSERVER.URL + '/video-playlists/' + uuid
63 const query = ` 63 const query = `
diff --git a/server/initializers/migrations/0560-user-feed-token.ts b/server/initializers/migrations/0560-user-feed-token.ts
index 7c61def17..042301352 100644
--- a/server/initializers/migrations/0560-user-feed-token.ts
+++ b/server/initializers/migrations/0560-user-feed-token.ts
@@ -1,5 +1,5 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2import { v4 as uuidv4 } from 'uuid' 2import { buildUUID } from '@server/helpers/uuid'
3 3
4async function up (utils: { 4async function up (utils: {
5 transaction: Sequelize.Transaction 5 transaction: Sequelize.Transaction
@@ -26,7 +26,7 @@ async function up (utils: {
26 const users = await utils.sequelize.query<any>(query, options) 26 const users = await utils.sequelize.query<any>(query, options)
27 27
28 for (const user of users) { 28 for (const user of users) {
29 const queryUpdate = `UPDATE "user" SET "feedToken" = '${uuidv4()}' WHERE id = ${user.id}` 29 const queryUpdate = `UPDATE "user" SET "feedToken" = '${buildUUID()}' WHERE id = ${user.id}`
30 await utils.sequelize.query(queryUpdate) 30 await utils.sequelize.query(queryUpdate)
31 } 31 }
32 } 32 }
diff --git a/server/lib/activitypub/actors/shared/object-to-model-attributes.ts b/server/lib/activitypub/actors/shared/object-to-model-attributes.ts
index f53b98448..1612b3ad0 100644
--- a/server/lib/activitypub/actors/shared/object-to-model-attributes.ts
+++ b/server/lib/activitypub/actors/shared/object-to-model-attributes.ts
@@ -1,6 +1,6 @@
1import { v4 as uuidv4 } from 'uuid'
2import { getLowercaseExtension } from '@server/helpers/core-utils' 1import { getLowercaseExtension } from '@server/helpers/core-utils'
3import { isActivityPubUrlValid } from '@server/helpers/custom-validators/activitypub/misc' 2import { isActivityPubUrlValid } from '@server/helpers/custom-validators/activitypub/misc'
3import { buildUUID } from '@server/helpers/uuid'
4import { MIMETYPES } from '@server/initializers/constants' 4import { MIMETYPES } from '@server/initializers/constants'
5import { ActorModel } from '@server/models/actor/actor' 5import { ActorModel } from '@server/models/actor/actor'
6import { FilteredModelAttributes } from '@server/types' 6import { FilteredModelAttributes } from '@server/types'
@@ -51,7 +51,7 @@ function getImageInfoFromObject (actorObject: ActivityPubActor, type: ActorImage
51 if (!extension) return undefined 51 if (!extension) return undefined
52 52
53 return { 53 return {
54 name: uuidv4() + extension, 54 name: buildUUID() + extension,
55 fileUrl: icon.url, 55 fileUrl: icon.url,
56 height: icon.height, 56 height: icon.height,
57 width: icon.width, 57 width: icon.width,
diff --git a/server/lib/client-html.ts b/server/lib/client-html.ts
index 0191b55ef..72194416d 100644
--- a/server/lib/client-html.ts
+++ b/server/lib/client-html.ts
@@ -27,6 +27,7 @@ import { VideoChannelModel } from '../models/video/video-channel'
27import { VideoPlaylistModel } from '../models/video/video-playlist' 27import { VideoPlaylistModel } from '../models/video/video-playlist'
28import { MAccountActor, MChannelActor } from '../types/models' 28import { MAccountActor, MChannelActor } from '../types/models'
29import { ServerConfigManager } from './server-config-manager' 29import { ServerConfigManager } from './server-config-manager'
30import { toCompleteUUID } from '@server/helpers/custom-validators/misc'
30 31
31type Tags = { 32type Tags = {
32 ogType: string 33 ogType: string
@@ -78,7 +79,9 @@ class ClientHtml {
78 return customHtml 79 return customHtml
79 } 80 }
80 81
81 static async getWatchHTMLPage (videoId: string, req: express.Request, res: express.Response) { 82 static async getWatchHTMLPage (videoIdArg: string, req: express.Request, res: express.Response) {
83 const videoId = toCompleteUUID(videoIdArg)
84
82 // Let Angular application handle errors 85 // Let Angular application handle errors
83 if (!validator.isInt(videoId) && !validator.isUUID(videoId, 4)) { 86 if (!validator.isInt(videoId) && !validator.isUUID(videoId, 4)) {
84 res.status(HttpStatusCode.NOT_FOUND_404) 87 res.status(HttpStatusCode.NOT_FOUND_404)
@@ -136,7 +139,9 @@ class ClientHtml {
136 return customHtml 139 return customHtml
137 } 140 }
138 141
139 static async getWatchPlaylistHTMLPage (videoPlaylistId: string, req: express.Request, res: express.Response) { 142 static async getWatchPlaylistHTMLPage (videoPlaylistIdArg: string, req: express.Request, res: express.Response) {
143 const videoPlaylistId = toCompleteUUID(videoPlaylistIdArg)
144
140 // Let Angular application handle errors 145 // Let Angular application handle errors
141 if (!validator.isInt(videoPlaylistId) && !validator.isUUID(videoPlaylistId, 4)) { 146 if (!validator.isInt(videoPlaylistId) && !validator.isUUID(videoPlaylistId, 4)) {
142 res.status(HttpStatusCode.NOT_FOUND_404) 147 res.status(HttpStatusCode.NOT_FOUND_404)
diff --git a/server/lib/local-actor.ts b/server/lib/local-actor.ts
index 2d2bd43a1..77667f6b0 100644
--- a/server/lib/local-actor.ts
+++ b/server/lib/local-actor.ts
@@ -2,8 +2,8 @@ import 'multer'
2import { queue } from 'async' 2import { queue } from 'async'
3import * as LRUCache from 'lru-cache' 3import * as LRUCache from 'lru-cache'
4import { join } from 'path' 4import { join } from 'path'
5import { v4 as uuidv4 } from 'uuid'
6import { getLowercaseExtension } from '@server/helpers/core-utils' 5import { getLowercaseExtension } from '@server/helpers/core-utils'
6import { buildUUID } from '@server/helpers/uuid'
7import { ActorModel } from '@server/models/actor/actor' 7import { ActorModel } from '@server/models/actor/actor'
8import { ActivityPubActorType, ActorImageType } from '@shared/models' 8import { ActivityPubActorType, ActorImageType } from '@shared/models'
9import { retryTransactionWrapper } from '../helpers/database-utils' 9import { retryTransactionWrapper } from '../helpers/database-utils'
@@ -44,7 +44,7 @@ async function updateLocalActorImageFile (
44 44
45 const extension = getLowercaseExtension(imagePhysicalFile.filename) 45 const extension = getLowercaseExtension(imagePhysicalFile.filename)
46 46
47 const imageName = uuidv4() + extension 47 const imageName = buildUUID() + extension
48 const destination = join(CONFIG.STORAGE.ACTOR_IMAGES, imageName) 48 const destination = join(CONFIG.STORAGE.ACTOR_IMAGES, imageName)
49 await processImage(imagePhysicalFile.path, destination, imageSize) 49 await processImage(imagePhysicalFile.path, destination, imageSize)
50 50
diff --git a/server/lib/user.ts b/server/lib/user.ts
index 8820e8243..936403692 100644
--- a/server/lib/user.ts
+++ b/server/lib/user.ts
@@ -1,5 +1,5 @@
1import { Transaction } from 'sequelize/types' 1import { Transaction } from 'sequelize/types'
2import { v4 as uuidv4 } from 'uuid' 2import { buildUUID } from '@server/helpers/uuid'
3import { UserModel } from '@server/models/user/user' 3import { UserModel } from '@server/models/user/user'
4import { MActorDefault } from '@server/types/models/actor' 4import { MActorDefault } from '@server/types/models/actor'
5import { ActivityPubActorType } from '../../shared/models/activitypub' 5import { ActivityPubActorType } from '../../shared/models/activitypub'
@@ -210,7 +210,7 @@ async function buildChannelAttributes (user: MUser, transaction?: Transaction, c
210 210
211 // Conflict, generate uuid instead 211 // Conflict, generate uuid instead
212 const actor = await ActorModel.loadLocalByName(channelName, transaction) 212 const actor = await ActorModel.loadLocalByName(channelName, transaction)
213 if (actor) channelName = uuidv4() 213 if (actor) channelName = buildUUID()
214 214
215 const videoChannelDisplayName = `Main ${user.username} channel` 215 const videoChannelDisplayName = `Main ${user.username} channel`
216 216
diff --git a/server/middlewares/validators/abuse.ts b/server/middlewares/validators/abuse.ts
index 56c97747c..c048bc6af 100644
--- a/server/middlewares/validators/abuse.ts
+++ b/server/middlewares/validators/abuse.ts
@@ -12,7 +12,7 @@ import {
12 isAbuseTimestampValid, 12 isAbuseTimestampValid,
13 isAbuseVideoIsValid 13 isAbuseVideoIsValid
14} from '@server/helpers/custom-validators/abuses' 14} from '@server/helpers/custom-validators/abuses'
15import { exists, isIdOrUUIDValid, isIdValid, toIntOrNull } from '@server/helpers/custom-validators/misc' 15import { exists, isIdOrUUIDValid, isIdValid, toCompleteUUID, toIntOrNull } from '@server/helpers/custom-validators/misc'
16import { logger } from '@server/helpers/logger' 16import { logger } from '@server/helpers/logger'
17import { AbuseMessageModel } from '@server/models/abuse/abuse-message' 17import { AbuseMessageModel } from '@server/models/abuse/abuse-message'
18import { AbuseCreate, UserRight } from '@shared/models' 18import { AbuseCreate, UserRight } from '@shared/models'
@@ -27,6 +27,7 @@ const abuseReportValidator = [
27 27
28 body('video.id') 28 body('video.id')
29 .optional() 29 .optional()
30 .customSanitizer(toCompleteUUID)
30 .custom(isIdOrUUIDValid) 31 .custom(isIdOrUUIDValid)
31 .withMessage('Should have a valid videoId'), 32 .withMessage('Should have a valid videoId'),
32 body('video.startAt') 33 body('video.startAt')
diff --git a/server/middlewares/validators/feeds.ts b/server/middlewares/validators/feeds.ts
index 51e6d6fff..51b8fdd19 100644
--- a/server/middlewares/validators/feeds.ts
+++ b/server/middlewares/validators/feeds.ts
@@ -1,8 +1,9 @@
1import * as express from 'express' 1import * as express from 'express'
2import { param, query } from 'express-validator' 2import { param, query } from 'express-validator'
3
3import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' 4import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes'
4import { isValidRSSFeed } from '../../helpers/custom-validators/feeds' 5import { isValidRSSFeed } from '../../helpers/custom-validators/feeds'
5import { exists, isIdOrUUIDValid, isIdValid } from '../../helpers/custom-validators/misc' 6import { exists, isIdOrUUIDValid, isIdValid, toCompleteUUID } from '../../helpers/custom-validators/misc'
6import { logger } from '../../helpers/logger' 7import { logger } from '../../helpers/logger'
7import { 8import {
8 areValidationErrors, 9 areValidationErrors,
@@ -98,7 +99,10 @@ const videoSubscriptionFeedsValidator = [
98] 99]
99 100
100const videoCommentsFeedsValidator = [ 101const videoCommentsFeedsValidator = [
101 query('videoId').optional().custom(isIdOrUUIDValid), 102 query('videoId')
103 .customSanitizer(toCompleteUUID)
104 .optional()
105 .custom(isIdOrUUIDValid),
102 106
103 async (req: express.Request, res: express.Response, next: express.NextFunction) => { 107 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
104 logger.debug('Checking feeds parameters', { parameters: req.query }) 108 logger.debug('Checking feeds parameters', { parameters: req.query })
diff --git a/server/middlewares/validators/index.ts b/server/middlewares/validators/index.ts
index 24faeea3e..94a3c2dea 100644
--- a/server/middlewares/validators/index.ts
+++ b/server/middlewares/validators/index.ts
@@ -11,7 +11,7 @@ export * from './sort'
11export * from './users' 11export * from './users'
12export * from './user-subscriptions' 12export * from './user-subscriptions'
13export * from './videos' 13export * from './videos'
14export * from './webfinger'
15export * from './search' 14export * from './search'
16export * from './server' 15export * from './server'
17export * from './user-history' 16export * from './user-history'
17export * from './webfinger'
diff --git a/server/middlewares/validators/oembed.ts b/server/middlewares/validators/oembed.ts
index e1015d7fd..0a82e6932 100644
--- a/server/middlewares/validators/oembed.ts
+++ b/server/middlewares/validators/oembed.ts
@@ -6,7 +6,7 @@ import { VideoPlaylistModel } from '@server/models/video/video-playlist'
6import { VideoPlaylistPrivacy, VideoPrivacy } from '@shared/models' 6import { VideoPlaylistPrivacy, VideoPrivacy } from '@shared/models'
7import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' 7import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes'
8import { isTestInstance } from '../../helpers/core-utils' 8import { isTestInstance } from '../../helpers/core-utils'
9import { isIdOrUUIDValid } from '../../helpers/custom-validators/misc' 9import { isIdOrUUIDValid, toCompleteUUID } from '../../helpers/custom-validators/misc'
10import { logger } from '../../helpers/logger' 10import { logger } from '../../helpers/logger'
11import { WEBSERVER } from '../../initializers/constants' 11import { WEBSERVER } from '../../initializers/constants'
12import { areValidationErrors } from './shared' 12import { areValidationErrors } from './shared'
@@ -79,7 +79,7 @@ const oembedValidator = [
79 }) 79 })
80 } 80 }
81 81
82 const elementId = matches[1] 82 const elementId = toCompleteUUID(matches[1])
83 if (isIdOrUUIDValid(elementId) === false) { 83 if (isIdOrUUIDValid(elementId) === false) {
84 return res.fail({ message: 'Invalid video or playlist id.' }) 84 return res.fail({ message: 'Invalid video or playlist id.' })
85 } 85 }
diff --git a/server/middlewares/validators/redundancy.ts b/server/middlewares/validators/redundancy.ts
index da24f4c9b..116c8c611 100644
--- a/server/middlewares/validators/redundancy.ts
+++ b/server/middlewares/validators/redundancy.ts
@@ -2,15 +2,24 @@ import * as express from 'express'
2import { body, param, query } from 'express-validator' 2import { body, param, query } from 'express-validator'
3import { isVideoRedundancyTarget } from '@server/helpers/custom-validators/video-redundancies' 3import { isVideoRedundancyTarget } from '@server/helpers/custom-validators/video-redundancies'
4import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' 4import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes'
5import { exists, isBooleanValid, isIdOrUUIDValid, isIdValid, toBooleanOrNull, toIntOrNull } from '../../helpers/custom-validators/misc' 5import {
6 exists,
7 isBooleanValid,
8 isIdOrUUIDValid,
9 isIdValid,
10 toBooleanOrNull,
11 toCompleteUUID,
12 toIntOrNull
13} from '../../helpers/custom-validators/misc'
6import { isHostValid } from '../../helpers/custom-validators/servers' 14import { isHostValid } from '../../helpers/custom-validators/servers'
7import { logger } from '../../helpers/logger' 15import { logger } from '../../helpers/logger'
8import { VideoRedundancyModel } from '../../models/redundancy/video-redundancy' 16import { VideoRedundancyModel } from '../../models/redundancy/video-redundancy'
9import { ServerModel } from '../../models/server/server' 17import { ServerModel } from '../../models/server/server'
10import { areValidationErrors, doesVideoExist } from './shared' 18import { areValidationErrors, doesVideoExist, isValidVideoIdParam } from './shared'
11 19
12const videoFileRedundancyGetValidator = [ 20const videoFileRedundancyGetValidator = [
13 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid video id'), 21 isValidVideoIdParam('videoId'),
22
14 param('resolution') 23 param('resolution')
15 .customSanitizer(toIntOrNull) 24 .customSanitizer(toIntOrNull)
16 .custom(exists).withMessage('Should have a valid resolution'), 25 .custom(exists).withMessage('Should have a valid resolution'),
@@ -56,9 +65,8 @@ const videoFileRedundancyGetValidator = [
56] 65]
57 66
58const videoPlaylistRedundancyGetValidator = [ 67const videoPlaylistRedundancyGetValidator = [
59 param('videoId') 68 isValidVideoIdParam('videoId'),
60 .custom(isIdOrUUIDValid) 69
61 .not().isEmpty().withMessage('Should have a valid video id'),
62 param('streamingPlaylistType') 70 param('streamingPlaylistType')
63 .customSanitizer(toIntOrNull) 71 .customSanitizer(toIntOrNull)
64 .custom(exists).withMessage('Should have a valid streaming playlist type'), 72 .custom(exists).withMessage('Should have a valid streaming playlist type'),
@@ -135,7 +143,8 @@ const listVideoRedundanciesValidator = [
135 143
136const addVideoRedundancyValidator = [ 144const addVideoRedundancyValidator = [
137 body('videoId') 145 body('videoId')
138 .custom(isIdValid) 146 .customSanitizer(toCompleteUUID)
147 .custom(isIdOrUUIDValid)
139 .withMessage('Should have a valid video id'), 148 .withMessage('Should have a valid video id'),
140 149
141 async (req: express.Request, res: express.Response, next: express.NextFunction) => { 150 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
diff --git a/server/middlewares/validators/shared/utils.ts b/server/middlewares/validators/shared/utils.ts
index d3e4870a9..4f08560af 100644
--- a/server/middlewares/validators/shared/utils.ts
+++ b/server/middlewares/validators/shared/utils.ts
@@ -1,5 +1,6 @@
1import * as express from 'express' 1import * as express from 'express'
2import { query, validationResult } from 'express-validator' 2import { param, query, validationResult } from 'express-validator'
3import { isIdOrUUIDValid, toCompleteUUID } from '@server/helpers/custom-validators/misc'
3import { logger } from '../../../helpers/logger' 4import { logger } from '../../../helpers/logger'
4 5
5function areValidationErrors (req: express.Request, res: express.Response) { 6function areValidationErrors (req: express.Request, res: express.Response) {
@@ -41,10 +42,24 @@ function createSortableColumns (sortableColumns: string[]) {
41 return sortableColumns.concat(sortableColumnDesc) 42 return sortableColumns.concat(sortableColumnDesc)
42} 43}
43 44
45function isValidVideoIdParam (paramName: string) {
46 return param(paramName)
47 .customSanitizer(toCompleteUUID)
48 .custom(isIdOrUUIDValid).withMessage('Should have a valid video id')
49}
50
51function isValidPlaylistIdParam (paramName: string) {
52 return param(paramName)
53 .customSanitizer(toCompleteUUID)
54 .custom(isIdOrUUIDValid).withMessage('Should have a valid playlist id')
55}
56
44// --------------------------------------------------------------------------- 57// ---------------------------------------------------------------------------
45 58
46export { 59export {
47 areValidationErrors, 60 areValidationErrors,
48 checkSort, 61 checkSort,
49 createSortableColumns 62 createSortableColumns,
63 isValidVideoIdParam,
64 isValidPlaylistIdParam
50} 65}
diff --git a/server/middlewares/validators/users.ts b/server/middlewares/validators/users.ts
index 218633b8d..698d7d814 100644
--- a/server/middlewares/validators/users.ts
+++ b/server/middlewares/validators/users.ts
@@ -7,7 +7,7 @@ import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-code
7import { UserRole } from '../../../shared/models/users' 7import { UserRole } from '../../../shared/models/users'
8import { UserRegister } from '../../../shared/models/users/user-register.model' 8import { UserRegister } from '../../../shared/models/users/user-register.model'
9import { isActorPreferredUsernameValid } from '../../helpers/custom-validators/activitypub/actor' 9import { isActorPreferredUsernameValid } from '../../helpers/custom-validators/activitypub/actor'
10import { isIdOrUUIDValid, toBooleanOrNull, toIntOrNull } from '../../helpers/custom-validators/misc' 10import { toBooleanOrNull, toIntOrNull } from '../../helpers/custom-validators/misc'
11import { isThemeNameValid } from '../../helpers/custom-validators/plugins' 11import { isThemeNameValid } from '../../helpers/custom-validators/plugins'
12import { 12import {
13 isNoInstanceConfigWarningModal, 13 isNoInstanceConfigWarningModal,
@@ -35,7 +35,7 @@ import { Redis } from '../../lib/redis'
35import { isSignupAllowed, isSignupAllowedForCurrentIP } from '../../lib/signup' 35import { isSignupAllowed, isSignupAllowedForCurrentIP } from '../../lib/signup'
36import { ActorModel } from '../../models/actor/actor' 36import { ActorModel } from '../../models/actor/actor'
37import { UserModel } from '../../models/user/user' 37import { UserModel } from '../../models/user/user'
38import { areValidationErrors, doesVideoExist } from './shared' 38import { areValidationErrors, doesVideoExist, isValidVideoIdParam } from './shared'
39 39
40const usersListValidator = [ 40const usersListValidator = [
41 query('blocked') 41 query('blocked')
@@ -302,7 +302,7 @@ const usersGetValidator = [
302] 302]
303 303
304const usersVideoRatingValidator = [ 304const usersVideoRatingValidator = [
305 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid video id'), 305 isValidVideoIdParam('videoId'),
306 306
307 async (req: express.Request, res: express.Response, next: express.NextFunction) => { 307 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
308 logger.debug('Checking usersVideoRating parameters', { parameters: req.params }) 308 logger.debug('Checking usersVideoRating parameters', { parameters: req.params })
diff --git a/server/middlewares/validators/videos/video-blacklist.ts b/server/middlewares/validators/videos/video-blacklist.ts
index 7374ba774..21141d84d 100644
--- a/server/middlewares/validators/videos/video-blacklist.ts
+++ b/server/middlewares/validators/videos/video-blacklist.ts
@@ -1,13 +1,13 @@
1import * as express from 'express' 1import * as express from 'express'
2import { body, param, query } from 'express-validator' 2import { body, query } from 'express-validator'
3import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes' 3import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes'
4import { isBooleanValid, isIdOrUUIDValid, toBooleanOrNull, toIntOrNull } from '../../../helpers/custom-validators/misc' 4import { isBooleanValid, toBooleanOrNull, toIntOrNull } from '../../../helpers/custom-validators/misc'
5import { isVideoBlacklistReasonValid, isVideoBlacklistTypeValid } from '../../../helpers/custom-validators/video-blacklist' 5import { isVideoBlacklistReasonValid, isVideoBlacklistTypeValid } from '../../../helpers/custom-validators/video-blacklist'
6import { logger } from '../../../helpers/logger' 6import { logger } from '../../../helpers/logger'
7import { areValidationErrors, doesVideoBlacklistExist, doesVideoExist } from '../shared' 7import { areValidationErrors, doesVideoBlacklistExist, doesVideoExist, isValidVideoIdParam } from '../shared'
8 8
9const videosBlacklistRemoveValidator = [ 9const videosBlacklistRemoveValidator = [
10 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'), 10 isValidVideoIdParam('videoId'),
11 11
12 async (req: express.Request, res: express.Response, next: express.NextFunction) => { 12 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
13 logger.debug('Checking blacklistRemove parameters.', { parameters: req.params }) 13 logger.debug('Checking blacklistRemove parameters.', { parameters: req.params })
@@ -21,7 +21,8 @@ const videosBlacklistRemoveValidator = [
21] 21]
22 22
23const videosBlacklistAddValidator = [ 23const videosBlacklistAddValidator = [
24 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'), 24 isValidVideoIdParam('videoId'),
25
25 body('unfederate') 26 body('unfederate')
26 .optional() 27 .optional()
27 .customSanitizer(toBooleanOrNull) 28 .customSanitizer(toBooleanOrNull)
@@ -49,7 +50,8 @@ const videosBlacklistAddValidator = [
49] 50]
50 51
51const videosBlacklistUpdateValidator = [ 52const videosBlacklistUpdateValidator = [
52 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'), 53 isValidVideoIdParam('videoId'),
54
53 body('reason') 55 body('reason')
54 .optional() 56 .optional()
55 .custom(isVideoBlacklistReasonValid).withMessage('Should have a valid reason'), 57 .custom(isVideoBlacklistReasonValid).withMessage('Should have a valid reason'),
diff --git a/server/middlewares/validators/videos/video-captions.ts b/server/middlewares/validators/videos/video-captions.ts
index 2295e049a..2946f3e15 100644
--- a/server/middlewares/validators/videos/video-captions.ts
+++ b/server/middlewares/validators/videos/video-captions.ts
@@ -1,16 +1,18 @@
1import * as express from 'express' 1import * as express from 'express'
2import { body, param } from 'express-validator' 2import { body, param } from 'express-validator'
3import { UserRight } from '../../../../shared' 3import { UserRight } from '../../../../shared'
4import { isIdOrUUIDValid } from '../../../helpers/custom-validators/misc'
5import { isVideoCaptionFile, isVideoCaptionLanguageValid } from '../../../helpers/custom-validators/video-captions' 4import { isVideoCaptionFile, isVideoCaptionLanguageValid } from '../../../helpers/custom-validators/video-captions'
6import { cleanUpReqFiles } from '../../../helpers/express-utils' 5import { cleanUpReqFiles } from '../../../helpers/express-utils'
7import { logger } from '../../../helpers/logger' 6import { logger } from '../../../helpers/logger'
8import { CONSTRAINTS_FIELDS, MIMETYPES } from '../../../initializers/constants' 7import { CONSTRAINTS_FIELDS, MIMETYPES } from '../../../initializers/constants'
9import { areValidationErrors, checkUserCanManageVideo, doesVideoCaptionExist, doesVideoExist } from '../shared' 8import { areValidationErrors, checkUserCanManageVideo, doesVideoCaptionExist, doesVideoExist, isValidVideoIdParam } from '../shared'
10 9
11const addVideoCaptionValidator = [ 10const addVideoCaptionValidator = [
12 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid video id'), 11 isValidVideoIdParam('videoId'),
13 param('captionLanguage').custom(isVideoCaptionLanguageValid).not().isEmpty().withMessage('Should have a valid caption language'), 12
13 param('captionLanguage')
14 .custom(isVideoCaptionLanguageValid).not().isEmpty().withMessage('Should have a valid caption language'),
15
14 body('captionfile') 16 body('captionfile')
15 .custom((_, { req }) => isVideoCaptionFile(req.files, 'captionfile')) 17 .custom((_, { req }) => isVideoCaptionFile(req.files, 'captionfile'))
16 .withMessage( 18 .withMessage(
@@ -34,8 +36,10 @@ const addVideoCaptionValidator = [
34] 36]
35 37
36const deleteVideoCaptionValidator = [ 38const deleteVideoCaptionValidator = [
37 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid video id'), 39 isValidVideoIdParam('videoId'),
38 param('captionLanguage').custom(isVideoCaptionLanguageValid).not().isEmpty().withMessage('Should have a valid caption language'), 40
41 param('captionLanguage')
42 .custom(isVideoCaptionLanguageValid).not().isEmpty().withMessage('Should have a valid caption language'),
39 43
40 async (req: express.Request, res: express.Response, next: express.NextFunction) => { 44 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
41 logger.debug('Checking deleteVideoCaption parameters', { parameters: req.params }) 45 logger.debug('Checking deleteVideoCaption parameters', { parameters: req.params })
@@ -53,7 +57,7 @@ const deleteVideoCaptionValidator = [
53] 57]
54 58
55const listVideoCaptionsValidator = [ 59const listVideoCaptionsValidator = [
56 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid video id'), 60 isValidVideoIdParam('videoId'),
57 61
58 async (req: express.Request, res: express.Response, next: express.NextFunction) => { 62 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
59 logger.debug('Checking listVideoCaptions parameters', { parameters: req.params }) 63 logger.debug('Checking listVideoCaptions parameters', { parameters: req.params })
diff --git a/server/middlewares/validators/videos/video-comments.ts b/server/middlewares/validators/videos/video-comments.ts
index 1451ab988..885506ebe 100644
--- a/server/middlewares/validators/videos/video-comments.ts
+++ b/server/middlewares/validators/videos/video-comments.ts
@@ -3,13 +3,13 @@ import { body, param, query } from 'express-validator'
3import { MUserAccountUrl } from '@server/types/models' 3import { MUserAccountUrl } from '@server/types/models'
4import { UserRight } from '../../../../shared' 4import { UserRight } from '../../../../shared'
5import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes' 5import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes'
6import { exists, isBooleanValid, isIdOrUUIDValid, isIdValid, toBooleanOrNull } from '../../../helpers/custom-validators/misc' 6import { exists, isBooleanValid, isIdValid, toBooleanOrNull } from '../../../helpers/custom-validators/misc'
7import { isValidVideoCommentText } from '../../../helpers/custom-validators/video-comments' 7import { isValidVideoCommentText } from '../../../helpers/custom-validators/video-comments'
8import { logger } from '../../../helpers/logger' 8import { logger } from '../../../helpers/logger'
9import { AcceptResult, isLocalVideoCommentReplyAccepted, isLocalVideoThreadAccepted } from '../../../lib/moderation' 9import { AcceptResult, isLocalVideoCommentReplyAccepted, isLocalVideoThreadAccepted } from '../../../lib/moderation'
10import { Hooks } from '../../../lib/plugins/hooks' 10import { Hooks } from '../../../lib/plugins/hooks'
11import { MCommentOwnerVideoReply, MVideo, MVideoFullLight } from '../../../types/models/video' 11import { MCommentOwnerVideoReply, MVideo, MVideoFullLight } from '../../../types/models/video'
12import { areValidationErrors, doesVideoCommentExist, doesVideoCommentThreadExist, doesVideoExist } from '../shared' 12import { areValidationErrors, doesVideoCommentExist, doesVideoCommentThreadExist, doesVideoExist, isValidVideoIdParam } from '../shared'
13 13
14const listVideoCommentsValidator = [ 14const listVideoCommentsValidator = [
15 query('isLocal') 15 query('isLocal')
@@ -40,7 +40,7 @@ const listVideoCommentsValidator = [
40] 40]
41 41
42const listVideoCommentThreadsValidator = [ 42const listVideoCommentThreadsValidator = [
43 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'), 43 isValidVideoIdParam('videoId'),
44 44
45 async (req: express.Request, res: express.Response, next: express.NextFunction) => { 45 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
46 logger.debug('Checking listVideoCommentThreads parameters.', { parameters: req.params }) 46 logger.debug('Checking listVideoCommentThreads parameters.', { parameters: req.params })
@@ -53,8 +53,10 @@ const listVideoCommentThreadsValidator = [
53] 53]
54 54
55const listVideoThreadCommentsValidator = [ 55const listVideoThreadCommentsValidator = [
56 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'), 56 isValidVideoIdParam('videoId'),
57 param('threadId').custom(isIdValid).not().isEmpty().withMessage('Should have a valid threadId'), 57
58 param('threadId')
59 .custom(isIdValid).not().isEmpty().withMessage('Should have a valid threadId'),
58 60
59 async (req: express.Request, res: express.Response, next: express.NextFunction) => { 61 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
60 logger.debug('Checking listVideoThreadComments parameters.', { parameters: req.params }) 62 logger.debug('Checking listVideoThreadComments parameters.', { parameters: req.params })
@@ -68,8 +70,10 @@ const listVideoThreadCommentsValidator = [
68] 70]
69 71
70const addVideoCommentThreadValidator = [ 72const addVideoCommentThreadValidator = [
71 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'), 73 isValidVideoIdParam('videoId'),
72 body('text').custom(isValidVideoCommentText).not().isEmpty().withMessage('Should have a valid comment text'), 74
75 body('text')
76 .custom(isValidVideoCommentText).not().isEmpty().withMessage('Should have a valid comment text'),
73 77
74 async (req: express.Request, res: express.Response, next: express.NextFunction) => { 78 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
75 logger.debug('Checking addVideoCommentThread parameters.', { parameters: req.params, body: req.body }) 79 logger.debug('Checking addVideoCommentThread parameters.', { parameters: req.params, body: req.body })
@@ -84,8 +88,10 @@ const addVideoCommentThreadValidator = [
84] 88]
85 89
86const addVideoCommentReplyValidator = [ 90const addVideoCommentReplyValidator = [
87 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'), 91 isValidVideoIdParam('videoId'),
92
88 param('commentId').custom(isIdValid).not().isEmpty().withMessage('Should have a valid commentId'), 93 param('commentId').custom(isIdValid).not().isEmpty().withMessage('Should have a valid commentId'),
94
89 body('text').custom(isValidVideoCommentText).not().isEmpty().withMessage('Should have a valid comment text'), 95 body('text').custom(isValidVideoCommentText).not().isEmpty().withMessage('Should have a valid comment text'),
90 96
91 async (req: express.Request, res: express.Response, next: express.NextFunction) => { 97 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
@@ -102,8 +108,10 @@ const addVideoCommentReplyValidator = [
102] 108]
103 109
104const videoCommentGetValidator = [ 110const videoCommentGetValidator = [
105 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'), 111 isValidVideoIdParam('videoId'),
106 param('commentId').custom(isIdValid).not().isEmpty().withMessage('Should have a valid commentId'), 112
113 param('commentId')
114 .custom(isIdValid).not().isEmpty().withMessage('Should have a valid commentId'),
107 115
108 async (req: express.Request, res: express.Response, next: express.NextFunction) => { 116 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
109 logger.debug('Checking videoCommentGetValidator parameters.', { parameters: req.params }) 117 logger.debug('Checking videoCommentGetValidator parameters.', { parameters: req.params })
@@ -117,7 +125,8 @@ const videoCommentGetValidator = [
117] 125]
118 126
119const removeVideoCommentValidator = [ 127const removeVideoCommentValidator = [
120 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'), 128 isValidVideoIdParam('videoId'),
129
121 param('commentId').custom(isIdValid).not().isEmpty().withMessage('Should have a valid commentId'), 130 param('commentId').custom(isIdValid).not().isEmpty().withMessage('Should have a valid commentId'),
122 131
123 async (req: express.Request, res: express.Response, next: express.NextFunction) => { 132 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
diff --git a/server/middlewares/validators/videos/video-live.ts b/server/middlewares/validators/videos/video-live.ts
index b058ff5c1..7cfb935e3 100644
--- a/server/middlewares/validators/videos/video-live.ts
+++ b/server/middlewares/validators/videos/video-live.ts
@@ -1,5 +1,5 @@
1import * as express from 'express' 1import * as express from 'express'
2import { body, param } from 'express-validator' 2import { body } from 'express-validator'
3import { CONSTRAINTS_FIELDS } from '@server/initializers/constants' 3import { CONSTRAINTS_FIELDS } from '@server/initializers/constants'
4import { isLocalLiveVideoAccepted } from '@server/lib/moderation' 4import { isLocalLiveVideoAccepted } from '@server/lib/moderation'
5import { Hooks } from '@server/lib/plugins/hooks' 5import { Hooks } from '@server/lib/plugins/hooks'
@@ -7,16 +7,22 @@ import { VideoModel } from '@server/models/video/video'
7import { VideoLiveModel } from '@server/models/video/video-live' 7import { VideoLiveModel } from '@server/models/video/video-live'
8import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes' 8import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes'
9import { ServerErrorCode, UserRight, VideoState } from '@shared/models' 9import { ServerErrorCode, UserRight, VideoState } from '@shared/models'
10import { isBooleanValid, isIdOrUUIDValid, isIdValid, toBooleanOrNull, toIntOrNull } from '../../../helpers/custom-validators/misc' 10import { isBooleanValid, isIdValid, toBooleanOrNull, toIntOrNull } from '../../../helpers/custom-validators/misc'
11import { isVideoNameValid } from '../../../helpers/custom-validators/videos' 11import { isVideoNameValid } from '../../../helpers/custom-validators/videos'
12import { cleanUpReqFiles } from '../../../helpers/express-utils' 12import { cleanUpReqFiles } from '../../../helpers/express-utils'
13import { logger } from '../../../helpers/logger' 13import { logger } from '../../../helpers/logger'
14import { CONFIG } from '../../../initializers/config' 14import { CONFIG } from '../../../initializers/config'
15import { areValidationErrors, checkUserCanManageVideo, doesVideoChannelOfAccountExist, doesVideoExist } from '../shared' 15import {
16 areValidationErrors,
17 checkUserCanManageVideo,
18 doesVideoChannelOfAccountExist,
19 doesVideoExist,
20 isValidVideoIdParam
21} from '../shared'
16import { getCommonVideoEditAttributes } from './videos' 22import { getCommonVideoEditAttributes } from './videos'
17 23
18const videoLiveGetValidator = [ 24const videoLiveGetValidator = [
19 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'), 25 isValidVideoIdParam('videoId'),
20 26
21 async (req: express.Request, res: express.Response, next: express.NextFunction) => { 27 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
22 logger.debug('Checking videoLiveGetValidator parameters', { parameters: req.params, user: res.locals.oauth.token.User.username }) 28 logger.debug('Checking videoLiveGetValidator parameters', { parameters: req.params, user: res.locals.oauth.token.User.username })
diff --git a/server/middlewares/validators/videos/video-ownership-changes.ts b/server/middlewares/validators/videos/video-ownership-changes.ts
index 120b0469c..54ac46c99 100644
--- a/server/middlewares/validators/videos/video-ownership-changes.ts
+++ b/server/middlewares/validators/videos/video-ownership-changes.ts
@@ -1,6 +1,6 @@
1import * as express from 'express' 1import * as express from 'express'
2import { param } from 'express-validator' 2import { param } from 'express-validator'
3import { isIdOrUUIDValid } from '@server/helpers/custom-validators/misc' 3import { isIdValid } from '@server/helpers/custom-validators/misc'
4import { checkUserCanTerminateOwnershipChange } from '@server/helpers/custom-validators/video-ownership' 4import { checkUserCanTerminateOwnershipChange } from '@server/helpers/custom-validators/video-ownership'
5import { logger } from '@server/helpers/logger' 5import { logger } from '@server/helpers/logger'
6import { isAbleToUploadVideo } from '@server/lib/user' 6import { isAbleToUploadVideo } from '@server/lib/user'
@@ -13,11 +13,12 @@ import {
13 checkUserCanManageVideo, 13 checkUserCanManageVideo,
14 doesChangeVideoOwnershipExist, 14 doesChangeVideoOwnershipExist,
15 doesVideoChannelOfAccountExist, 15 doesVideoChannelOfAccountExist,
16 doesVideoExist 16 doesVideoExist,
17 isValidVideoIdParam
17} from '../shared' 18} from '../shared'
18 19
19const videosChangeOwnershipValidator = [ 20const videosChangeOwnershipValidator = [
20 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'), 21 isValidVideoIdParam('videoId'),
21 22
22 async (req: express.Request, res: express.Response, next: express.NextFunction) => { 23 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
23 logger.debug('Checking changeOwnership parameters', { parameters: req.params }) 24 logger.debug('Checking changeOwnership parameters', { parameters: req.params })
@@ -40,7 +41,8 @@ const videosChangeOwnershipValidator = [
40] 41]
41 42
42const videosTerminateChangeOwnershipValidator = [ 43const videosTerminateChangeOwnershipValidator = [
43 param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'), 44 param('id')
45 .custom(isIdValid).withMessage('Should have a valid id'),
44 46
45 async (req: express.Request, res: express.Response, next: express.NextFunction) => { 47 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
46 logger.debug('Checking changeOwnership parameters', { parameters: req.params }) 48 logger.debug('Checking changeOwnership parameters', { parameters: req.params })
diff --git a/server/middlewares/validators/videos/video-playlists.ts b/server/middlewares/validators/videos/video-playlists.ts
index 0d2e6e90c..5ee7ee0ce 100644
--- a/server/middlewares/validators/videos/video-playlists.ts
+++ b/server/middlewares/validators/videos/video-playlists.ts
@@ -11,6 +11,7 @@ import {
11 isIdOrUUIDValid, 11 isIdOrUUIDValid,
12 isIdValid, 12 isIdValid,
13 isUUIDValid, 13 isUUIDValid,
14 toCompleteUUID,
14 toIntArray, 15 toIntArray,
15 toIntOrNull, 16 toIntOrNull,
16 toValueOrNull 17 toValueOrNull
@@ -29,7 +30,14 @@ import { CONSTRAINTS_FIELDS } from '../../../initializers/constants'
29import { VideoPlaylistElementModel } from '../../../models/video/video-playlist-element' 30import { VideoPlaylistElementModel } from '../../../models/video/video-playlist-element'
30import { MVideoPlaylist } from '../../../types/models/video/video-playlist' 31import { MVideoPlaylist } from '../../../types/models/video/video-playlist'
31import { authenticatePromiseIfNeeded } from '../../auth' 32import { authenticatePromiseIfNeeded } from '../../auth'
32import { areValidationErrors, doesVideoChannelIdExist, doesVideoExist, doesVideoPlaylistExist, VideoPlaylistFetchType } from '../shared' 33import {
34 areValidationErrors,
35 doesVideoChannelIdExist,
36 doesVideoExist,
37 doesVideoPlaylistExist,
38 isValidPlaylistIdParam,
39 VideoPlaylistFetchType
40} from '../shared'
33 41
34const videoPlaylistsAddValidator = getCommonPlaylistEditAttributes().concat([ 42const videoPlaylistsAddValidator = getCommonPlaylistEditAttributes().concat([
35 body('displayName') 43 body('displayName')
@@ -43,10 +51,13 @@ const videoPlaylistsAddValidator = getCommonPlaylistEditAttributes().concat([
43 const body: VideoPlaylistCreate = req.body 51 const body: VideoPlaylistCreate = req.body
44 if (body.videoChannelId && !await doesVideoChannelIdExist(body.videoChannelId, res)) return cleanUpReqFiles(req) 52 if (body.videoChannelId && !await doesVideoChannelIdExist(body.videoChannelId, res)) return cleanUpReqFiles(req)
45 53
46 if (body.privacy === VideoPlaylistPrivacy.PUBLIC && !body.videoChannelId) { 54 if (
55 !body.videoChannelId &&
56 (body.privacy === VideoPlaylistPrivacy.PUBLIC || body.privacy === VideoPlaylistPrivacy.UNLISTED)
57 ) {
47 cleanUpReqFiles(req) 58 cleanUpReqFiles(req)
48 59
49 return res.fail({ message: 'Cannot set "public" a playlist that is not assigned to a channel.' }) 60 return res.fail({ message: 'Cannot set "public" or "unlisted" a playlist that is not assigned to a channel.' })
50 } 61 }
51 62
52 return next() 63 return next()
@@ -54,8 +65,7 @@ const videoPlaylistsAddValidator = getCommonPlaylistEditAttributes().concat([
54]) 65])
55 66
56const videoPlaylistsUpdateValidator = getCommonPlaylistEditAttributes().concat([ 67const videoPlaylistsUpdateValidator = getCommonPlaylistEditAttributes().concat([
57 param('playlistId') 68 isValidPlaylistIdParam('playlistId'),
58 .custom(isIdOrUUIDValid).withMessage('Should have a valid playlist id/uuid'),
59 69
60 body('displayName') 70 body('displayName')
61 .optional() 71 .optional()
@@ -101,8 +111,7 @@ const videoPlaylistsUpdateValidator = getCommonPlaylistEditAttributes().concat([
101]) 111])
102 112
103const videoPlaylistsDeleteValidator = [ 113const videoPlaylistsDeleteValidator = [
104 param('playlistId') 114 isValidPlaylistIdParam('playlistId'),
105 .custom(isIdOrUUIDValid).withMessage('Should have a valid playlist id/uuid'),
106 115
107 async (req: express.Request, res: express.Response, next: express.NextFunction) => { 116 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
108 logger.debug('Checking videoPlaylistsDeleteValidator parameters', { parameters: req.params }) 117 logger.debug('Checking videoPlaylistsDeleteValidator parameters', { parameters: req.params })
@@ -126,8 +135,7 @@ const videoPlaylistsDeleteValidator = [
126 135
127const videoPlaylistsGetValidator = (fetchType: VideoPlaylistFetchType) => { 136const videoPlaylistsGetValidator = (fetchType: VideoPlaylistFetchType) => {
128 return [ 137 return [
129 param('playlistId') 138 isValidPlaylistIdParam('playlistId'),
130 .custom(isIdOrUUIDValid).withMessage('Should have a valid playlist id/uuid'),
131 139
132 async (req: express.Request, res: express.Response, next: express.NextFunction) => { 140 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
133 logger.debug('Checking videoPlaylistsGetValidator parameters', { parameters: req.params }) 141 logger.debug('Checking videoPlaylistsGetValidator parameters', { parameters: req.params })
@@ -184,9 +192,10 @@ const videoPlaylistsSearchValidator = [
184] 192]
185 193
186const videoPlaylistsAddVideoValidator = [ 194const videoPlaylistsAddVideoValidator = [
187 param('playlistId') 195 isValidPlaylistIdParam('playlistId'),
188 .custom(isIdOrUUIDValid).withMessage('Should have a valid playlist id/uuid'), 196
189 body('videoId') 197 body('videoId')
198 .customSanitizer(toCompleteUUID)
190 .custom(isIdOrUUIDValid).withMessage('Should have a valid video id/uuid'), 199 .custom(isIdOrUUIDValid).withMessage('Should have a valid video id/uuid'),
191 body('startTimestamp') 200 body('startTimestamp')
192 .optional() 201 .optional()
@@ -214,9 +223,9 @@ const videoPlaylistsAddVideoValidator = [
214] 223]
215 224
216const videoPlaylistsUpdateOrRemoveVideoValidator = [ 225const videoPlaylistsUpdateOrRemoveVideoValidator = [
217 param('playlistId') 226 isValidPlaylistIdParam('playlistId'),
218 .custom(isIdOrUUIDValid).withMessage('Should have a valid playlist id/uuid'),
219 param('playlistElementId') 227 param('playlistElementId')
228 .customSanitizer(toCompleteUUID)
220 .custom(isIdValid).withMessage('Should have an element id/uuid'), 229 .custom(isIdValid).withMessage('Should have an element id/uuid'),
221 body('startTimestamp') 230 body('startTimestamp')
222 .optional() 231 .optional()
@@ -251,8 +260,7 @@ const videoPlaylistsUpdateOrRemoveVideoValidator = [
251] 260]
252 261
253const videoPlaylistElementAPGetValidator = [ 262const videoPlaylistElementAPGetValidator = [
254 param('playlistId') 263 isValidPlaylistIdParam('playlistId'),
255 .custom(isIdOrUUIDValid).withMessage('Should have a valid playlist id/uuid'),
256 param('playlistElementId') 264 param('playlistElementId')
257 .custom(isIdValid).withMessage('Should have an playlist element id'), 265 .custom(isIdValid).withMessage('Should have an playlist element id'),
258 266
@@ -287,8 +295,7 @@ const videoPlaylistElementAPGetValidator = [
287] 295]
288 296
289const videoPlaylistsReorderVideosValidator = [ 297const videoPlaylistsReorderVideosValidator = [
290 param('playlistId') 298 isValidPlaylistIdParam('playlistId'),
291 .custom(isIdOrUUIDValid).withMessage('Should have a valid playlist id/uuid'),
292 body('startPosition') 299 body('startPosition')
293 .isInt({ min: 1 }).withMessage('Should have a valid start position'), 300 .isInt({ min: 1 }).withMessage('Should have a valid start position'),
294 body('insertAfterPosition') 301 body('insertAfterPosition')
diff --git a/server/middlewares/validators/videos/video-rates.ts b/server/middlewares/validators/videos/video-rates.ts
index 4a802e75e..5d5dfb222 100644
--- a/server/middlewares/validators/videos/video-rates.ts
+++ b/server/middlewares/validators/videos/video-rates.ts
@@ -3,15 +3,16 @@ import { body, param, query } from 'express-validator'
3import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes' 3import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes'
4import { VideoRateType } from '../../../../shared/models/videos' 4import { VideoRateType } from '../../../../shared/models/videos'
5import { isAccountNameValid } from '../../../helpers/custom-validators/accounts' 5import { isAccountNameValid } from '../../../helpers/custom-validators/accounts'
6import { isIdOrUUIDValid, isIdValid } from '../../../helpers/custom-validators/misc' 6import { isIdValid } from '../../../helpers/custom-validators/misc'
7import { isRatingValid } from '../../../helpers/custom-validators/video-rates' 7import { isRatingValid } from '../../../helpers/custom-validators/video-rates'
8import { isVideoRatingTypeValid } from '../../../helpers/custom-validators/videos' 8import { isVideoRatingTypeValid } from '../../../helpers/custom-validators/videos'
9import { logger } from '../../../helpers/logger' 9import { logger } from '../../../helpers/logger'
10import { AccountVideoRateModel } from '../../../models/account/account-video-rate' 10import { AccountVideoRateModel } from '../../../models/account/account-video-rate'
11import { areValidationErrors, doesVideoExist } from '../shared' 11import { areValidationErrors, doesVideoExist, isValidVideoIdParam } from '../shared'
12 12
13const videoUpdateRateValidator = [ 13const videoUpdateRateValidator = [
14 param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'), 14 isValidVideoIdParam('id'),
15
15 body('rating').custom(isVideoRatingTypeValid).withMessage('Should have a valid rate type'), 16 body('rating').custom(isVideoRatingTypeValid).withMessage('Should have a valid rate type'),
16 17
17 async (req: express.Request, res: express.Response, next: express.NextFunction) => { 18 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
diff --git a/server/middlewares/validators/videos/video-shares.ts b/server/middlewares/validators/videos/video-shares.ts
index cc2f66e94..7e54b6fc0 100644
--- a/server/middlewares/validators/videos/video-shares.ts
+++ b/server/middlewares/validators/videos/video-shares.ts
@@ -1,14 +1,16 @@
1import * as express from 'express' 1import * as express from 'express'
2import { param } from 'express-validator' 2import { param } from 'express-validator'
3import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes' 3import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes'
4import { isIdOrUUIDValid, isIdValid } from '../../../helpers/custom-validators/misc' 4import { isIdValid } from '../../../helpers/custom-validators/misc'
5import { logger } from '../../../helpers/logger' 5import { logger } from '../../../helpers/logger'
6import { VideoShareModel } from '../../../models/video/video-share' 6import { VideoShareModel } from '../../../models/video/video-share'
7import { areValidationErrors, doesVideoExist } from '../shared' 7import { areValidationErrors, doesVideoExist, isValidVideoIdParam } from '../shared'
8 8
9const videosShareValidator = [ 9const videosShareValidator = [
10 param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'), 10 isValidVideoIdParam('id'),
11 param('actorId').custom(isIdValid).not().isEmpty().withMessage('Should have a valid actor id'), 11
12 param('actorId')
13 .custom(isIdValid).not().isEmpty().withMessage('Should have a valid actor id'),
12 14
13 async (req: express.Request, res: express.Response, next: express.NextFunction) => { 15 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
14 logger.debug('Checking videoShare parameters', { parameters: req.params }) 16 logger.debug('Checking videoShare parameters', { parameters: req.params })
diff --git a/server/middlewares/validators/videos/video-watch.ts b/server/middlewares/validators/videos/video-watch.ts
index ef8b89ece..43306f7cd 100644
--- a/server/middlewares/validators/videos/video-watch.ts
+++ b/server/middlewares/validators/videos/video-watch.ts
@@ -1,12 +1,13 @@
1import * as express from 'express' 1import * as express from 'express'
2import { body, param } from 'express-validator' 2import { body } from 'express-validator'
3import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes' 3import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes'
4import { isIdOrUUIDValid, toIntOrNull } from '../../../helpers/custom-validators/misc' 4import { toIntOrNull } from '../../../helpers/custom-validators/misc'
5import { logger } from '../../../helpers/logger' 5import { logger } from '../../../helpers/logger'
6import { areValidationErrors, doesVideoExist } from '../shared' 6import { areValidationErrors, doesVideoExist, isValidVideoIdParam } from '../shared'
7 7
8const videoWatchingValidator = [ 8const videoWatchingValidator = [
9 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'), 9 isValidVideoIdParam('videoId'),
10
10 body('currentTime') 11 body('currentTime')
11 .customSanitizer(toIntOrNull) 12 .customSanitizer(toIntOrNull)
12 .isInt().withMessage('Should have correct current time'), 13 .isInt().withMessage('Should have correct current time'),
diff --git a/server/middlewares/validators/videos/videos.ts b/server/middlewares/validators/videos/videos.ts
index 8201e80c3..49e10e2b5 100644
--- a/server/middlewares/validators/videos/videos.ts
+++ b/server/middlewares/validators/videos/videos.ts
@@ -12,7 +12,6 @@ import {
12 isBooleanValid, 12 isBooleanValid,
13 isDateValid, 13 isDateValid,
14 isFileFieldValid, 14 isFileFieldValid,
15 isIdOrUUIDValid,
16 isIdValid, 15 isIdValid,
17 isUUIDValid, 16 isUUIDValid,
18 toArray, 17 toArray,
@@ -53,7 +52,8 @@ import {
53 checkUserCanManageVideo, 52 checkUserCanManageVideo,
54 doesVideoChannelOfAccountExist, 53 doesVideoChannelOfAccountExist,
55 doesVideoExist, 54 doesVideoExist,
56 doesVideoFileOfVideoExist 55 doesVideoFileOfVideoExist,
56 isValidVideoIdParam
57} from '../shared' 57} from '../shared'
58 58
59const videosAddLegacyValidator = getCommonVideoEditAttributes().concat([ 59const videosAddLegacyValidator = getCommonVideoEditAttributes().concat([
@@ -195,7 +195,8 @@ const videosAddResumableInitValidator = getCommonVideoEditAttributes().concat([
195]) 195])
196 196
197const videosUpdateValidator = getCommonVideoEditAttributes().concat([ 197const videosUpdateValidator = getCommonVideoEditAttributes().concat([
198 param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'), 198 isValidVideoIdParam('id'),
199
199 body('name') 200 body('name')
200 .optional() 201 .optional()
201 .trim() 202 .trim()
@@ -258,7 +259,7 @@ const videosCustomGetValidator = (
258 authenticateInQuery = false 259 authenticateInQuery = false
259) => { 260) => {
260 return [ 261 return [
261 param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'), 262 isValidVideoIdParam('id'),
262 263
263 async (req: express.Request, res: express.Response, next: express.NextFunction) => { 264 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
264 logger.debug('Checking videosGet parameters', { parameters: req.params }) 265 logger.debug('Checking videosGet parameters', { parameters: req.params })
@@ -309,8 +310,10 @@ const videosGetValidator = videosCustomGetValidator('all')
309const videosDownloadValidator = videosCustomGetValidator('all', true) 310const videosDownloadValidator = videosCustomGetValidator('all', true)
310 311
311const videoFileMetadataGetValidator = getCommonVideoEditAttributes().concat([ 312const videoFileMetadataGetValidator = getCommonVideoEditAttributes().concat([
312 param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'), 313 isValidVideoIdParam('id'),
313 param('videoFileId').custom(isIdValid).not().isEmpty().withMessage('Should have a valid videoFileId'), 314
315 param('videoFileId')
316 .custom(isIdValid).not().isEmpty().withMessage('Should have a valid videoFileId'),
314 317
315 async (req: express.Request, res: express.Response, next: express.NextFunction) => { 318 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
316 logger.debug('Checking videoFileMetadataGet parameters', { parameters: req.params }) 319 logger.debug('Checking videoFileMetadataGet parameters', { parameters: req.params })
@@ -323,7 +326,7 @@ const videoFileMetadataGetValidator = getCommonVideoEditAttributes().concat([
323]) 326])
324 327
325const videosRemoveValidator = [ 328const videosRemoveValidator = [
326 param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'), 329 isValidVideoIdParam('id'),
327 330
328 async (req: express.Request, res: express.Response, next: express.NextFunction) => { 331 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
329 logger.debug('Checking videosRemove parameters', { parameters: req.params }) 332 logger.debug('Checking videosRemove parameters', { parameters: req.params })
diff --git a/server/models/video/formatter/video-format-utils.ts b/server/models/video/formatter/video-format-utils.ts
index 8880c0450..672c671b8 100644
--- a/server/models/video/formatter/video-format-utils.ts
+++ b/server/models/video/formatter/video-format-utils.ts
@@ -1,3 +1,4 @@
1import { uuidToShort } from '@server/helpers/uuid'
1import { generateMagnetUri } from '@server/helpers/webtorrent' 2import { generateMagnetUri } from '@server/helpers/webtorrent'
2import { getLocalVideoFileMetadataUrl } from '@server/lib/video-paths' 3import { getLocalVideoFileMetadataUrl } from '@server/lib/video-paths'
3import { VideoFile } from '@shared/models/videos/video-file.model' 4import { VideoFile } from '@shared/models/videos/video-file.model'
@@ -47,6 +48,8 @@ function videoModelToFormattedJSON (video: MVideoFormattable, options?: VideoFor
47 const videoObject: Video = { 48 const videoObject: Video = {
48 id: video.id, 49 id: video.id,
49 uuid: video.uuid, 50 uuid: video.uuid,
51 shortUUID: uuidToShort(video.uuid),
52
50 name: video.name, 53 name: video.name,
51 category: { 54 category: {
52 id: video.category, 55 id: video.category,
diff --git a/server/models/video/video-caption.ts b/server/models/video/video-caption.ts
index 5ec944b9e..d24be56c3 100644
--- a/server/models/video/video-caption.ts
+++ b/server/models/video/video-caption.ts
@@ -15,7 +15,7 @@ import {
15 Table, 15 Table,
16 UpdatedAt 16 UpdatedAt
17} from 'sequelize-typescript' 17} from 'sequelize-typescript'
18import { v4 as uuidv4 } from 'uuid' 18import { buildUUID } from '@server/helpers/uuid'
19import { MVideo, MVideoCaption, MVideoCaptionFormattable, MVideoCaptionVideo } from '@server/types/models' 19import { MVideo, MVideoCaption, MVideoCaptionFormattable, MVideoCaptionVideo } from '@server/types/models'
20import { AttributesOnly } from '@shared/core-utils' 20import { AttributesOnly } from '@shared/core-utils'
21import { VideoCaption } from '../../../shared/models/videos/caption/video-caption.model' 21import { VideoCaption } from '../../../shared/models/videos/caption/video-caption.model'
@@ -182,7 +182,7 @@ export class VideoCaptionModel extends Model<Partial<AttributesOnly<VideoCaption
182 } 182 }
183 183
184 static generateCaptionName (language: string) { 184 static generateCaptionName (language: string) {
185 return `${uuidv4()}-${language}.vtt` 185 return `${buildUUID()}-${language}.vtt`
186 } 186 }
187 187
188 isOwned () { 188 isOwned () {
diff --git a/server/models/video/video-playlist.ts b/server/models/video/video-playlist.ts
index 7aa6b6c6e..af81c9906 100644
--- a/server/models/video/video-playlist.ts
+++ b/server/models/video/video-playlist.ts
@@ -17,8 +17,8 @@ import {
17 Table, 17 Table,
18 UpdatedAt 18 UpdatedAt
19} from 'sequelize-typescript' 19} from 'sequelize-typescript'
20import { v4 as uuidv4 } from 'uuid'
21import { setAsUpdated } from '@server/helpers/database-utils' 20import { setAsUpdated } from '@server/helpers/database-utils'
21import { buildUUID, uuidToShort } from '@server/helpers/uuid'
22import { MAccountId, MChannelId } from '@server/types/models' 22import { MAccountId, MChannelId } from '@server/types/models'
23import { AttributesOnly } from '@shared/core-utils' 23import { AttributesOnly } from '@shared/core-utils'
24import { ActivityIconObject } from '../../../shared/models/activitypub/objects' 24import { ActivityIconObject } from '../../../shared/models/activitypub/objects'
@@ -545,7 +545,7 @@ export class VideoPlaylistModel extends Model<Partial<AttributesOnly<VideoPlayli
545 generateThumbnailName () { 545 generateThumbnailName () {
546 const extension = '.jpg' 546 const extension = '.jpg'
547 547
548 return 'playlist-' + uuidv4() + extension 548 return 'playlist-' + buildUUID() + extension
549 } 549 }
550 550
551 getThumbnailUrl () { 551 getThumbnailUrl () {
@@ -617,6 +617,8 @@ export class VideoPlaylistModel extends Model<Partial<AttributesOnly<VideoPlayli
617 return { 617 return {
618 id: this.id, 618 id: this.id,
619 uuid: this.uuid, 619 uuid: this.uuid,
620 shortUUID: uuidToShort(this.uuid),
621
620 isLocal: this.isOwned(), 622 isLocal: this.isOwned(),
621 623
622 url: this.url, 624 url: this.url,
diff --git a/server/tests/api/activitypub/client.ts b/server/tests/api/activitypub/client.ts
index b6c538e19..be94e219c 100644
--- a/server/tests/api/activitypub/client.ts
+++ b/server/tests/api/activitypub/client.ts
@@ -1,23 +1,65 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai'
4import 'mocha' 3import 'mocha'
4import * as chai from 'chai'
5import { VideoPlaylistPrivacy } from '@shared/models'
6import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes'
5import { 7import {
6 cleanupTests, 8 cleanupTests,
9 createVideoPlaylist,
7 doubleFollow, 10 doubleFollow,
8 flushAndRunMultipleServers, 11 flushAndRunMultipleServers,
9 makeActivityPubGetRequest, 12 makeActivityPubGetRequest,
10 ServerInfo, 13 ServerInfo,
11 setAccessTokensToServers, 14 setAccessTokensToServers,
12 uploadVideo 15 setDefaultVideoChannel,
16 uploadVideoAndGetId
13} from '../../../../shared/extra-utils' 17} from '../../../../shared/extra-utils'
14import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes'
15 18
16const expect = chai.expect 19const expect = chai.expect
17 20
18describe('Test activitypub', function () { 21describe('Test activitypub', function () {
19 let servers: ServerInfo[] = [] 22 let servers: ServerInfo[] = []
20 let videoUUID: string 23 let video: { id: number, uuid: string, shortUUID: string }
24 let playlist: { id: number, uuid: string, shortUUID: string }
25
26 async function testAccount (path: string) {
27 const res = await makeActivityPubGetRequest(servers[0].url, path)
28 const object = res.body
29
30 expect(object.type).to.equal('Person')
31 expect(object.id).to.equal('http://localhost:' + servers[0].port + '/accounts/root')
32 expect(object.name).to.equal('root')
33 expect(object.preferredUsername).to.equal('root')
34 }
35
36 async function testChannel (path: string) {
37 const res = await makeActivityPubGetRequest(servers[0].url, path)
38 const object = res.body
39
40 expect(object.type).to.equal('Group')
41 expect(object.id).to.equal('http://localhost:' + servers[0].port + '/video-channels/root_channel')
42 expect(object.name).to.equal('Main root channel')
43 expect(object.preferredUsername).to.equal('root_channel')
44 }
45
46 async function testVideo (path: string) {
47 const res = await makeActivityPubGetRequest(servers[0].url, path)
48 const object = res.body
49
50 expect(object.type).to.equal('Video')
51 expect(object.id).to.equal('http://localhost:' + servers[0].port + '/videos/watch/' + video.uuid)
52 expect(object.name).to.equal('video')
53 }
54
55 async function testPlaylist (path: string) {
56 const res = await makeActivityPubGetRequest(servers[0].url, path)
57 const object = res.body
58
59 expect(object.type).to.equal('Playlist')
60 expect(object.id).to.equal('http://localhost:' + servers[0].port + '/video-playlists/' + playlist.uuid)
61 expect(object.name).to.equal('playlist')
62 }
21 63
22 before(async function () { 64 before(async function () {
23 this.timeout(30000) 65 this.timeout(30000)
@@ -25,38 +67,56 @@ describe('Test activitypub', function () {
25 servers = await flushAndRunMultipleServers(2) 67 servers = await flushAndRunMultipleServers(2)
26 68
27 await setAccessTokensToServers(servers) 69 await setAccessTokensToServers(servers)
70 await setDefaultVideoChannel(servers)
28 71
29 { 72 {
30 const res = await uploadVideo(servers[0].url, servers[0].accessToken, { name: 'video' }) 73 video = await uploadVideoAndGetId({ server: servers[0], videoName: 'video' })
31 videoUUID = res.body.video.uuid 74 }
75
76 {
77 const playlistAttrs = { displayName: 'playlist', privacy: VideoPlaylistPrivacy.PUBLIC, videoChannelId: servers[0].videoChannel.id }
78 const resCreate = await createVideoPlaylist({ url: servers[0].url, token: servers[0].accessToken, playlistAttrs })
79 playlist = resCreate.body.videoPlaylist
32 } 80 }
33 81
34 await doubleFollow(servers[0], servers[1]) 82 await doubleFollow(servers[0], servers[1])
35 }) 83 })
36 84
37 it('Should return the account object', async function () { 85 it('Should return the account object', async function () {
38 const res = await makeActivityPubGetRequest(servers[0].url, '/accounts/root') 86 await testAccount('/accounts/root')
39 const object = res.body 87 await testAccount('/a/root')
88 })
40 89
41 expect(object.type).to.equal('Person') 90 it('Should return the channel object', async function () {
42 expect(object.id).to.equal('http://localhost:' + servers[0].port + '/accounts/root') 91 await testChannel('/video-channels/root_channel')
43 expect(object.name).to.equal('root') 92 await testChannel('/c/root_channel')
44 expect(object.preferredUsername).to.equal('root')
45 }) 93 })
46 94
47 it('Should return the video object', async function () { 95 it('Should return the video object', async function () {
48 const res = await makeActivityPubGetRequest(servers[0].url, '/videos/watch/' + videoUUID) 96 await testVideo('/videos/watch/' + video.id)
49 const object = res.body 97 await testVideo('/videos/watch/' + video.uuid)
98 await testVideo('/videos/watch/' + video.shortUUID)
99 await testVideo('/w/' + video.id)
100 await testVideo('/w/' + video.uuid)
101 await testVideo('/w/' + video.shortUUID)
102 })
50 103
51 expect(object.type).to.equal('Video') 104 it('Should return the playlist object', async function () {
52 expect(object.id).to.equal('http://localhost:' + servers[0].port + '/videos/watch/' + videoUUID) 105 await testPlaylist('/video-playlists/' + playlist.id)
53 expect(object.name).to.equal('video') 106 await testPlaylist('/video-playlists/' + playlist.uuid)
107 await testPlaylist('/video-playlists/' + playlist.shortUUID)
108 await testPlaylist('/w/p/' + playlist.id)
109 await testPlaylist('/w/p/' + playlist.uuid)
110 await testPlaylist('/w/p/' + playlist.shortUUID)
111 await testPlaylist('/videos/watch/playlist/' + playlist.id)
112 await testPlaylist('/videos/watch/playlist/' + playlist.uuid)
113 await testPlaylist('/videos/watch/playlist/' + playlist.shortUUID)
54 }) 114 })
55 115
56 it('Should redirect to the origin video object', async function () { 116 it('Should redirect to the origin video object', async function () {
57 const res = await makeActivityPubGetRequest(servers[1].url, '/videos/watch/' + videoUUID, HttpStatusCode.FOUND_302) 117 const res = await makeActivityPubGetRequest(servers[1].url, '/videos/watch/' + video.uuid, HttpStatusCode.FOUND_302)
58 118
59 expect(res.header.location).to.equal('http://localhost:' + servers[0].port + '/videos/watch/' + videoUUID) 119 expect(res.header.location).to.equal('http://localhost:' + servers[0].port + '/videos/watch/' + video.uuid)
60 }) 120 })
61 121
62 after(async function () { 122 after(async function () {
diff --git a/server/tests/api/check-params/abuses.ts b/server/tests/api/check-params/abuses.ts
index 2aa09334c..2054776cc 100644
--- a/server/tests/api/check-params/abuses.ts
+++ b/server/tests/api/check-params/abuses.ts
@@ -258,7 +258,7 @@ describe('Test abuses API validators', function () {
258 }) 258 })
259 259
260 it('Should succeed with the correct parameters (basic)', async function () { 260 it('Should succeed with the correct parameters (basic)', async function () {
261 const fields: AbuseCreate = { video: { id: server.video.id }, reason: 'my super reason' } 261 const fields: AbuseCreate = { video: { id: server.video.shortUUID }, reason: 'my super reason' }
262 262
263 const res = await makePostBodyRequest({ 263 const res = await makePostBodyRequest({
264 url: server.url, 264 url: server.url,
diff --git a/server/tests/api/check-params/live.ts b/server/tests/api/check-params/live.ts
index 32233c9da..933d8abf2 100644
--- a/server/tests/api/check-params/live.ts
+++ b/server/tests/api/check-params/live.ts
@@ -2,7 +2,7 @@
2 2
3import 'mocha' 3import 'mocha'
4import { omit } from 'lodash' 4import { omit } from 'lodash'
5import { LiveVideo, VideoPrivacy } from '@shared/models' 5import { LiveVideo, VideoCreateResult, VideoPrivacy } from '@shared/models'
6import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes' 6import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes'
7import { 7import {
8 buildAbsoluteFixturePath, 8 buildAbsoluteFixturePath,
@@ -31,7 +31,7 @@ describe('Test video lives API validator', function () {
31 let server: ServerInfo 31 let server: ServerInfo
32 let userAccessToken = '' 32 let userAccessToken = ''
33 let channelId: number 33 let channelId: number
34 let videoId: number 34 let video: VideoCreateResult
35 let videoIdNotLive: number 35 let videoIdNotLive: number
36 36
37 // --------------------------------------------------------------- 37 // ---------------------------------------------------------------
@@ -230,7 +230,7 @@ describe('Test video lives API validator', function () {
230 statusCodeExpected: HttpStatusCode.OK_200 230 statusCodeExpected: HttpStatusCode.OK_200
231 }) 231 })
232 232
233 videoId = res.body.video.id 233 video = res.body.video
234 }) 234 })
235 235
236 it('Should forbid if live is disabled', async function () { 236 it('Should forbid if live is disabled', async function () {
@@ -326,15 +326,15 @@ describe('Test video lives API validator', function () {
326 describe('When getting live information', function () { 326 describe('When getting live information', function () {
327 327
328 it('Should fail without access token', async function () { 328 it('Should fail without access token', async function () {
329 await getLive(server.url, '', videoId, HttpStatusCode.UNAUTHORIZED_401) 329 await getLive(server.url, '', video.id, HttpStatusCode.UNAUTHORIZED_401)
330 }) 330 })
331 331
332 it('Should fail with a bad access token', async function () { 332 it('Should fail with a bad access token', async function () {
333 await getLive(server.url, 'toto', videoId, HttpStatusCode.UNAUTHORIZED_401) 333 await getLive(server.url, 'toto', video.id, HttpStatusCode.UNAUTHORIZED_401)
334 }) 334 })
335 335
336 it('Should fail with access token of another user', async function () { 336 it('Should fail with access token of another user', async function () {
337 await getLive(server.url, userAccessToken, videoId, HttpStatusCode.FORBIDDEN_403) 337 await getLive(server.url, userAccessToken, video.id, HttpStatusCode.FORBIDDEN_403)
338 }) 338 })
339 339
340 it('Should fail with a bad video id', async function () { 340 it('Should fail with a bad video id', async function () {
@@ -350,22 +350,23 @@ describe('Test video lives API validator', function () {
350 }) 350 })
351 351
352 it('Should succeed with the correct params', async function () { 352 it('Should succeed with the correct params', async function () {
353 await getLive(server.url, server.accessToken, videoId) 353 await getLive(server.url, server.accessToken, video.id)
354 await getLive(server.url, server.accessToken, video.shortUUID)
354 }) 355 })
355 }) 356 })
356 357
357 describe('When updating live information', async function () { 358 describe('When updating live information', async function () {
358 359
359 it('Should fail without access token', async function () { 360 it('Should fail without access token', async function () {
360 await updateLive(server.url, '', videoId, {}, HttpStatusCode.UNAUTHORIZED_401) 361 await updateLive(server.url, '', video.id, {}, HttpStatusCode.UNAUTHORIZED_401)
361 }) 362 })
362 363
363 it('Should fail with a bad access token', async function () { 364 it('Should fail with a bad access token', async function () {
364 await updateLive(server.url, 'toto', videoId, {}, HttpStatusCode.UNAUTHORIZED_401) 365 await updateLive(server.url, 'toto', video.id, {}, HttpStatusCode.UNAUTHORIZED_401)
365 }) 366 })
366 367
367 it('Should fail with access token of another user', async function () { 368 it('Should fail with access token of another user', async function () {
368 await updateLive(server.url, userAccessToken, videoId, {}, HttpStatusCode.FORBIDDEN_403) 369 await updateLive(server.url, userAccessToken, video.id, {}, HttpStatusCode.FORBIDDEN_403)
369 }) 370 })
370 371
371 it('Should fail with a bad video id', async function () { 372 it('Should fail with a bad video id', async function () {
@@ -383,11 +384,12 @@ describe('Test video lives API validator', function () {
383 it('Should fail with save replay and permanent live set to true', async function () { 384 it('Should fail with save replay and permanent live set to true', async function () {
384 const fields = { saveReplay: true, permanentLive: true } 385 const fields = { saveReplay: true, permanentLive: true }
385 386
386 await updateLive(server.url, server.accessToken, videoId, fields, HttpStatusCode.BAD_REQUEST_400) 387 await updateLive(server.url, server.accessToken, video.id, fields, HttpStatusCode.BAD_REQUEST_400)
387 }) 388 })
388 389
389 it('Should succeed with the correct params', async function () { 390 it('Should succeed with the correct params', async function () {
390 await updateLive(server.url, server.accessToken, videoId, { saveReplay: false }) 391 await updateLive(server.url, server.accessToken, video.id, { saveReplay: false })
392 await updateLive(server.url, server.accessToken, video.shortUUID, { saveReplay: false })
391 }) 393 })
392 394
393 it('Should fail to update replay status if replay is not allowed on the instance', async function () { 395 it('Should fail to update replay status if replay is not allowed on the instance', async function () {
@@ -398,19 +400,19 @@ describe('Test video lives API validator', function () {
398 } 400 }
399 }) 401 })
400 402
401 await updateLive(server.url, server.accessToken, videoId, { saveReplay: true }, HttpStatusCode.FORBIDDEN_403) 403 await updateLive(server.url, server.accessToken, video.id, { saveReplay: true }, HttpStatusCode.FORBIDDEN_403)
402 }) 404 })
403 405
404 it('Should fail to update a live if it has already started', async function () { 406 it('Should fail to update a live if it has already started', async function () {
405 this.timeout(40000) 407 this.timeout(40000)
406 408
407 const resLive = await getLive(server.url, server.accessToken, videoId) 409 const resLive = await getLive(server.url, server.accessToken, video.id)
408 const live: LiveVideo = resLive.body 410 const live: LiveVideo = resLive.body
409 411
410 const command = sendRTMPStream(live.rtmpUrl, live.streamKey) 412 const command = sendRTMPStream(live.rtmpUrl, live.streamKey)
411 413
412 await waitUntilLivePublished(server.url, server.accessToken, videoId) 414 await waitUntilLivePublished(server.url, server.accessToken, video.id)
413 await updateLive(server.url, server.accessToken, videoId, {}, HttpStatusCode.BAD_REQUEST_400) 415 await updateLive(server.url, server.accessToken, video.id, {}, HttpStatusCode.BAD_REQUEST_400)
414 416
415 await stopFfmpeg(command) 417 await stopFfmpeg(command)
416 }) 418 })
@@ -418,14 +420,14 @@ describe('Test video lives API validator', function () {
418 it('Should fail to stream twice in the save live', async function () { 420 it('Should fail to stream twice in the save live', async function () {
419 this.timeout(40000) 421 this.timeout(40000)
420 422
421 const resLive = await getLive(server.url, server.accessToken, videoId) 423 const resLive = await getLive(server.url, server.accessToken, video.id)
422 const live: LiveVideo = resLive.body 424 const live: LiveVideo = resLive.body
423 425
424 const command = sendRTMPStream(live.rtmpUrl, live.streamKey) 426 const command = sendRTMPStream(live.rtmpUrl, live.streamKey)
425 427
426 await waitUntilLivePublished(server.url, server.accessToken, videoId) 428 await waitUntilLivePublished(server.url, server.accessToken, video.id)
427 429
428 await runAndTestFfmpegStreamError(server.url, server.accessToken, videoId, true) 430 await runAndTestFfmpegStreamError(server.url, server.accessToken, video.id, true)
429 431
430 await stopFfmpeg(command) 432 await stopFfmpeg(command)
431 }) 433 })
diff --git a/server/tests/api/check-params/redundancy.ts b/server/tests/api/check-params/redundancy.ts
index 71be50a6f..dac6938de 100644
--- a/server/tests/api/check-params/redundancy.ts
+++ b/server/tests/api/check-params/redundancy.ts
@@ -1,7 +1,8 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4 4import { VideoCreateResult } from '@shared/models'
5import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes'
5import { 6import {
6 checkBadCountPagination, 7 checkBadCountPagination,
7 checkBadSortPagination, 8 checkBadSortPagination,
@@ -9,20 +10,24 @@ import {
9 cleanupTests, 10 cleanupTests,
10 createUser, 11 createUser,
11 doubleFollow, 12 doubleFollow,
12 flushAndRunMultipleServers, makeDeleteRequest, 13 flushAndRunMultipleServers,
13 makeGetRequest, makePostBodyRequest, 14 getVideo,
15 makeDeleteRequest,
16 makeGetRequest,
17 makePostBodyRequest,
14 makePutBodyRequest, 18 makePutBodyRequest,
15 ServerInfo, 19 ServerInfo,
16 setAccessTokensToServers, uploadVideoAndGetId, 20 setAccessTokensToServers,
17 userLogin, waitJobs, getVideoIdFromUUID 21 uploadVideoAndGetId,
22 userLogin,
23 waitJobs
18} from '../../../../shared/extra-utils' 24} from '../../../../shared/extra-utils'
19import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes'
20 25
21describe('Test server redundancy API validators', function () { 26describe('Test server redundancy API validators', function () {
22 let servers: ServerInfo[] 27 let servers: ServerInfo[]
23 let userAccessToken = null 28 let userAccessToken = null
24 let videoIdLocal: number 29 let videoIdLocal: number
25 let videoIdRemote: number 30 let videoRemote: VideoCreateResult
26 31
27 // --------------------------------------------------------------- 32 // ---------------------------------------------------------------
28 33
@@ -48,7 +53,8 @@ describe('Test server redundancy API validators', function () {
48 53
49 await waitJobs(servers) 54 await waitJobs(servers)
50 55
51 videoIdRemote = await getVideoIdFromUUID(servers[0].url, remoteUUID) 56 const resVideo = await getVideo(servers[0].url, remoteUUID)
57 videoRemote = resVideo.body
52 }) 58 })
53 59
54 describe('When listing redundancies', function () { 60 describe('When listing redundancies', function () {
@@ -131,7 +137,13 @@ describe('Test server redundancy API validators', function () {
131 }) 137 })
132 138
133 it('Should succeed with the correct params', async function () { 139 it('Should succeed with the correct params', async function () {
134 await makePostBodyRequest({ url, path, token, fields: { videoId: videoIdRemote }, statusCodeExpected: HttpStatusCode.NO_CONTENT_204 }) 140 await makePostBodyRequest({
141 url,
142 path,
143 token,
144 fields: { videoId: videoRemote.shortUUID },
145 statusCodeExpected: HttpStatusCode.NO_CONTENT_204
146 })
135 }) 147 })
136 148
137 it('Should fail if the video is already duplicated', async function () { 149 it('Should fail if the video is already duplicated', async function () {
@@ -139,7 +151,13 @@ describe('Test server redundancy API validators', function () {
139 151
140 await waitJobs(servers) 152 await waitJobs(servers)
141 153
142 await makePostBodyRequest({ url, path, token, fields: { videoId: videoIdRemote }, statusCodeExpected: HttpStatusCode.CONFLICT_409 }) 154 await makePostBodyRequest({
155 url,
156 path,
157 token,
158 fields: { videoId: videoRemote.uuid },
159 statusCodeExpected: HttpStatusCode.CONFLICT_409
160 })
143 }) 161 })
144 }) 162 })
145 163
diff --git a/server/tests/api/check-params/users.ts b/server/tests/api/check-params/users.ts
index 36482ee17..70a872ce5 100644
--- a/server/tests/api/check-params/users.ts
+++ b/server/tests/api/check-params/users.ts
@@ -2,7 +2,7 @@
2 2
3import 'mocha' 3import 'mocha'
4import { omit } from 'lodash' 4import { omit } from 'lodash'
5import { User, UserRole } from '../../../../shared' 5import { User, UserRole, VideoCreateResult } from '../../../../shared'
6import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes' 6import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes'
7import { 7import {
8 addVideoChannel, 8 addVideoChannel,
@@ -45,7 +45,7 @@ describe('Test users API validators', function () {
45 let userId: number 45 let userId: number
46 let rootId: number 46 let rootId: number
47 let moderatorId: number 47 let moderatorId: number
48 let videoId: number 48 let video: VideoCreateResult
49 let server: ServerInfo 49 let server: ServerInfo
50 let serverWithRegistrationDisabled: ServerInfo 50 let serverWithRegistrationDisabled: ServerInfo
51 let userAccessToken = '' 51 let userAccessToken = ''
@@ -126,7 +126,7 @@ describe('Test users API validators', function () {
126 126
127 { 127 {
128 const res = await uploadVideo(server.url, server.accessToken, {}) 128 const res = await uploadVideo(server.url, server.accessToken, {})
129 videoId = res.body.video.id 129 video = res.body.video
130 } 130 }
131 131
132 { 132 {
@@ -829,7 +829,7 @@ describe('Test users API validators', function () {
829 829
830 describe('When getting my video rating', function () { 830 describe('When getting my video rating', function () {
831 it('Should fail with a non authenticated user', async function () { 831 it('Should fail with a non authenticated user', async function () {
832 await getMyUserVideoRating(server.url, 'fake_token', videoId, HttpStatusCode.UNAUTHORIZED_401) 832 await getMyUserVideoRating(server.url, 'fake_token', video.id, HttpStatusCode.UNAUTHORIZED_401)
833 }) 833 })
834 834
835 it('Should fail with an incorrect video uuid', async function () { 835 it('Should fail with an incorrect video uuid', async function () {
@@ -841,7 +841,9 @@ describe('Test users API validators', function () {
841 }) 841 })
842 842
843 it('Should succeed with the correct parameters', async function () { 843 it('Should succeed with the correct parameters', async function () {
844 await getMyUserVideoRating(server.url, server.accessToken, videoId) 844 await getMyUserVideoRating(server.url, server.accessToken, video.id)
845 await getMyUserVideoRating(server.url, server.accessToken, video.uuid)
846 await getMyUserVideoRating(server.url, server.accessToken, video.shortUUID)
845 }) 847 })
846 }) 848 })
847 849
diff --git a/server/tests/api/check-params/video-blacklist.ts b/server/tests/api/check-params/video-blacklist.ts
index 3d4837d58..ce7f5fa17 100644
--- a/server/tests/api/check-params/video-blacklist.ts
+++ b/server/tests/api/check-params/video-blacklist.ts
@@ -191,7 +191,7 @@ describe('Test video blacklist API validators', function () {
191 }) 191 })
192 192
193 it('Should succeed with the correct params', async function () { 193 it('Should succeed with the correct params', async function () {
194 const path = basePath + servers[0].video.uuid + '/blacklist' 194 const path = basePath + servers[0].video.shortUUID + '/blacklist'
195 const fields = { reason: 'hello' } 195 const fields = { reason: 'hello' }
196 196
197 await makePutBodyRequest({ 197 await makePutBodyRequest({
@@ -222,10 +222,14 @@ describe('Test video blacklist API validators', function () {
222 }) 222 })
223 223
224 it('Should succeed with an admin', async function () { 224 it('Should succeed with an admin', async function () {
225 const res = await getVideoWithToken(servers[0].url, servers[0].accessToken, servers[0].video.uuid, HttpStatusCode.OK_200) 225 const video = servers[0].video
226 const video: VideoDetails = res.body
227 226
228 expect(video.blacklisted).to.be.true 227 for (const id of [ video.id, video.uuid, video.shortUUID ]) {
228 const res = await getVideoWithToken(servers[0].url, servers[0].accessToken, id, HttpStatusCode.OK_200)
229 const video: VideoDetails = res.body
230
231 expect(video.blacklisted).to.be.true
232 }
229 }) 233 })
230 }) 234 })
231 235
diff --git a/server/tests/api/check-params/video-captions.ts b/server/tests/api/check-params/video-captions.ts
index 1ce2202d2..c0595c04d 100644
--- a/server/tests/api/check-params/video-captions.ts
+++ b/server/tests/api/check-params/video-captions.ts
@@ -1,7 +1,7 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4 4import { VideoCreateResult } from '@shared/models'
5import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes' 5import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes'
6import { 6import {
7 buildAbsoluteFixturePath, 7 buildAbsoluteFixturePath,
@@ -23,7 +23,7 @@ describe('Test video captions API validator', function () {
23 23
24 let server: ServerInfo 24 let server: ServerInfo
25 let userAccessToken: string 25 let userAccessToken: string
26 let videoUUID: string 26 let video: VideoCreateResult
27 27
28 // --------------------------------------------------------------- 28 // ---------------------------------------------------------------
29 29
@@ -36,7 +36,7 @@ describe('Test video captions API validator', function () {
36 36
37 { 37 {
38 const res = await uploadVideo(server.url, server.accessToken, {}) 38 const res = await uploadVideo(server.url, server.accessToken, {})
39 videoUUID = res.body.video.uuid 39 video = res.body.video
40 } 40 }
41 41
42 { 42 {
@@ -79,7 +79,7 @@ describe('Test video captions API validator', function () {
79 }) 79 })
80 80
81 it('Should fail with a missing language in path', async function () { 81 it('Should fail with a missing language in path', async function () {
82 const captionPath = path + videoUUID + '/captions' 82 const captionPath = path + video.uuid + '/captions'
83 await makeUploadRequest({ 83 await makeUploadRequest({
84 method: 'PUT', 84 method: 'PUT',
85 url: server.url, 85 url: server.url,
@@ -91,7 +91,7 @@ describe('Test video captions API validator', function () {
91 }) 91 })
92 92
93 it('Should fail with an unknown language', async function () { 93 it('Should fail with an unknown language', async function () {
94 const captionPath = path + videoUUID + '/captions/15' 94 const captionPath = path + video.uuid + '/captions/15'
95 await makeUploadRequest({ 95 await makeUploadRequest({
96 method: 'PUT', 96 method: 'PUT',
97 url: server.url, 97 url: server.url,
@@ -103,7 +103,7 @@ describe('Test video captions API validator', function () {
103 }) 103 })
104 104
105 it('Should fail without access token', async function () { 105 it('Should fail without access token', async function () {
106 const captionPath = path + videoUUID + '/captions/fr' 106 const captionPath = path + video.uuid + '/captions/fr'
107 await makeUploadRequest({ 107 await makeUploadRequest({
108 method: 'PUT', 108 method: 'PUT',
109 url: server.url, 109 url: server.url,
@@ -115,7 +115,7 @@ describe('Test video captions API validator', function () {
115 }) 115 })
116 116
117 it('Should fail with a bad access token', async function () { 117 it('Should fail with a bad access token', async function () {
118 const captionPath = path + videoUUID + '/captions/fr' 118 const captionPath = path + video.uuid + '/captions/fr'
119 await makeUploadRequest({ 119 await makeUploadRequest({
120 method: 'PUT', 120 method: 'PUT',
121 url: server.url, 121 url: server.url,
@@ -133,7 +133,7 @@ describe('Test video captions API validator', function () {
133 // 'captionfile': buildAbsoluteFixturePath('subtitle-bad.txt') 133 // 'captionfile': buildAbsoluteFixturePath('subtitle-bad.txt')
134 // } 134 // }
135 // 135 //
136 // const captionPath = path + videoUUID + '/captions/fr' 136 // const captionPath = path + video.uuid + '/captions/fr'
137 // await makeUploadRequest({ 137 // await makeUploadRequest({
138 // method: 'PUT', 138 // method: 'PUT',
139 // url: server.url, 139 // url: server.url,
@@ -151,7 +151,7 @@ describe('Test video captions API validator', function () {
151 // url: server.url, 151 // url: server.url,
152 // accessToken: server.accessToken, 152 // accessToken: server.accessToken,
153 // language: 'zh', 153 // language: 'zh',
154 // videoId: videoUUID, 154 // videoId: video.uuid,
155 // fixture: 'subtitle-bad.txt', 155 // fixture: 'subtitle-bad.txt',
156 // mimeType: 'application/octet-stream', 156 // mimeType: 'application/octet-stream',
157 // statusCodeExpected: HttpStatusCode.BAD_REQUEST_400 157 // statusCodeExpected: HttpStatusCode.BAD_REQUEST_400
@@ -163,7 +163,7 @@ describe('Test video captions API validator', function () {
163 url: server.url, 163 url: server.url,
164 accessToken: server.accessToken, 164 accessToken: server.accessToken,
165 language: 'zh', 165 language: 'zh',
166 videoId: videoUUID, 166 videoId: video.uuid,
167 fixture: 'subtitle-good.srt', 167 fixture: 'subtitle-good.srt',
168 mimeType: 'application/octet-stream' 168 mimeType: 'application/octet-stream'
169 }) 169 })
@@ -175,7 +175,7 @@ describe('Test video captions API validator', function () {
175 // 'captionfile': buildAbsoluteFixturePath('subtitle-bad.srt') 175 // 'captionfile': buildAbsoluteFixturePath('subtitle-bad.srt')
176 // } 176 // }
177 // 177 //
178 // const captionPath = path + videoUUID + '/captions/fr' 178 // const captionPath = path + video.uuid + '/captions/fr'
179 // await makeUploadRequest({ 179 // await makeUploadRequest({
180 // method: 'PUT', 180 // method: 'PUT',
181 // url: server.url, 181 // url: server.url,
@@ -188,7 +188,7 @@ describe('Test video captions API validator', function () {
188 // }) 188 // })
189 189
190 it('Should success with the correct parameters', async function () { 190 it('Should success with the correct parameters', async function () {
191 const captionPath = path + videoUUID + '/captions/fr' 191 const captionPath = path + video.uuid + '/captions/fr'
192 await makeUploadRequest({ 192 await makeUploadRequest({
193 method: 'PUT', 193 method: 'PUT',
194 url: server.url, 194 url: server.url,
@@ -215,7 +215,7 @@ describe('Test video captions API validator', function () {
215 }) 215 })
216 216
217 it('Should success with the correct parameters', async function () { 217 it('Should success with the correct parameters', async function () {
218 await makeGetRequest({ url: server.url, path: path + videoUUID + '/captions', statusCodeExpected: HttpStatusCode.OK_200 }) 218 await makeGetRequest({ url: server.url, path: path + video.shortUUID + '/captions', statusCodeExpected: HttpStatusCode.OK_200 })
219 }) 219 })
220 }) 220 })
221 221
@@ -246,27 +246,27 @@ describe('Test video captions API validator', function () {
246 }) 246 })
247 247
248 it('Should fail with a missing language', async function () { 248 it('Should fail with a missing language', async function () {
249 const captionPath = path + videoUUID + '/captions' 249 const captionPath = path + video.shortUUID + '/captions'
250 await makeDeleteRequest({ url: server.url, path: captionPath, token: server.accessToken }) 250 await makeDeleteRequest({ url: server.url, path: captionPath, token: server.accessToken })
251 }) 251 })
252 252
253 it('Should fail with an unknown language', async function () { 253 it('Should fail with an unknown language', async function () {
254 const captionPath = path + videoUUID + '/captions/15' 254 const captionPath = path + video.shortUUID + '/captions/15'
255 await makeDeleteRequest({ url: server.url, path: captionPath, token: server.accessToken }) 255 await makeDeleteRequest({ url: server.url, path: captionPath, token: server.accessToken })
256 }) 256 })
257 257
258 it('Should fail without access token', async function () { 258 it('Should fail without access token', async function () {
259 const captionPath = path + videoUUID + '/captions/fr' 259 const captionPath = path + video.shortUUID + '/captions/fr'
260 await makeDeleteRequest({ url: server.url, path: captionPath, statusCodeExpected: HttpStatusCode.UNAUTHORIZED_401 }) 260 await makeDeleteRequest({ url: server.url, path: captionPath, statusCodeExpected: HttpStatusCode.UNAUTHORIZED_401 })
261 }) 261 })
262 262
263 it('Should fail with a bad access token', async function () { 263 it('Should fail with a bad access token', async function () {
264 const captionPath = path + videoUUID + '/captions/fr' 264 const captionPath = path + video.shortUUID + '/captions/fr'
265 await makeDeleteRequest({ url: server.url, path: captionPath, token: 'coucou', statusCodeExpected: HttpStatusCode.UNAUTHORIZED_401 }) 265 await makeDeleteRequest({ url: server.url, path: captionPath, token: 'coucou', statusCodeExpected: HttpStatusCode.UNAUTHORIZED_401 })
266 }) 266 })
267 267
268 it('Should fail with another user', async function () { 268 it('Should fail with another user', async function () {
269 const captionPath = path + videoUUID + '/captions/fr' 269 const captionPath = path + video.shortUUID + '/captions/fr'
270 await makeDeleteRequest({ 270 await makeDeleteRequest({
271 url: server.url, 271 url: server.url,
272 path: captionPath, 272 path: captionPath,
@@ -276,7 +276,7 @@ describe('Test video captions API validator', function () {
276 }) 276 })
277 277
278 it('Should success with the correct parameters', async function () { 278 it('Should success with the correct parameters', async function () {
279 const captionPath = path + videoUUID + '/captions/fr' 279 const captionPath = path + video.shortUUID + '/captions/fr'
280 await makeDeleteRequest({ 280 await makeDeleteRequest({
281 url: server.url, 281 url: server.url,
282 path: captionPath, 282 path: captionPath,
diff --git a/server/tests/api/check-params/video-comments.ts b/server/tests/api/check-params/video-comments.ts
index 659a10c41..a38420851 100644
--- a/server/tests/api/check-params/video-comments.ts
+++ b/server/tests/api/check-params/video-comments.ts
@@ -1,7 +1,9 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai'
4import 'mocha' 3import 'mocha'
4import * as chai from 'chai'
5import { VideoCreateResult } from '@shared/models'
6import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes'
5import { 7import {
6 cleanupTests, 8 cleanupTests,
7 createUser, 9 createUser,
@@ -20,7 +22,6 @@ import {
20 checkBadStartPagination 22 checkBadStartPagination
21} from '../../../../shared/extra-utils/requests/check-api-params' 23} from '../../../../shared/extra-utils/requests/check-api-params'
22import { addVideoCommentThread } from '../../../../shared/extra-utils/videos/video-comments' 24import { addVideoCommentThread } from '../../../../shared/extra-utils/videos/video-comments'
23import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes'
24 25
25const expect = chai.expect 26const expect = chai.expect
26 27
@@ -28,7 +29,7 @@ describe('Test video comments API validator', function () {
28 let pathThread: string 29 let pathThread: string
29 let pathComment: string 30 let pathComment: string
30 let server: ServerInfo 31 let server: ServerInfo
31 let videoUUID: string 32 let video: VideoCreateResult
32 let userAccessToken: string 33 let userAccessToken: string
33 let userAccessToken2: string 34 let userAccessToken2: string
34 let commentId: number 35 let commentId: number
@@ -44,14 +45,14 @@ describe('Test video comments API validator', function () {
44 45
45 { 46 {
46 const res = await uploadVideo(server.url, server.accessToken, {}) 47 const res = await uploadVideo(server.url, server.accessToken, {})
47 videoUUID = res.body.video.uuid 48 video = res.body.video
48 pathThread = '/api/v1/videos/' + videoUUID + '/comment-threads' 49 pathThread = '/api/v1/videos/' + video.uuid + '/comment-threads'
49 } 50 }
50 51
51 { 52 {
52 const res = await addVideoCommentThread(server.url, server.accessToken, videoUUID, 'coucou') 53 const res = await addVideoCommentThread(server.url, server.accessToken, video.uuid, 'coucou')
53 commentId = res.body.comment.id 54 commentId = res.body.comment.id
54 pathComment = '/api/v1/videos/' + videoUUID + '/comments/' + commentId 55 pathComment = '/api/v1/videos/' + video.uuid + '/comments/' + commentId
55 } 56 }
56 57
57 { 58 {
@@ -101,7 +102,7 @@ describe('Test video comments API validator', function () {
101 it('Should fail with an incorrect thread id', async function () { 102 it('Should fail with an incorrect thread id', async function () {
102 await makeGetRequest({ 103 await makeGetRequest({
103 url: server.url, 104 url: server.url,
104 path: '/api/v1/videos/' + videoUUID + '/comment-threads/156', 105 path: '/api/v1/videos/' + video.shortUUID + '/comment-threads/156',
105 statusCodeExpected: HttpStatusCode.NOT_FOUND_404 106 statusCodeExpected: HttpStatusCode.NOT_FOUND_404
106 }) 107 })
107 }) 108 })
@@ -109,7 +110,7 @@ describe('Test video comments API validator', function () {
109 it('Should success with the correct params', async function () { 110 it('Should success with the correct params', async function () {
110 await makeGetRequest({ 111 await makeGetRequest({
111 url: server.url, 112 url: server.url,
112 path: '/api/v1/videos/' + videoUUID + '/comment-threads/' + commentId, 113 path: '/api/v1/videos/' + video.shortUUID + '/comment-threads/' + commentId,
113 statusCodeExpected: HttpStatusCode.OK_200 114 statusCodeExpected: HttpStatusCode.OK_200
114 }) 115 })
115 }) 116 })
@@ -225,7 +226,7 @@ describe('Test video comments API validator', function () {
225 }) 226 })
226 227
227 it('Should fail with an incorrect comment', async function () { 228 it('Should fail with an incorrect comment', async function () {
228 const path = '/api/v1/videos/' + videoUUID + '/comments/124' 229 const path = '/api/v1/videos/' + video.uuid + '/comments/124'
229 const fields = { 230 const fields = {
230 text: 'super comment' 231 text: 'super comment'
231 } 232 }
@@ -272,7 +273,7 @@ describe('Test video comments API validator', function () {
272 }) 273 })
273 274
274 it('Should fail with an incorrect comment', async function () { 275 it('Should fail with an incorrect comment', async function () {
275 const path = '/api/v1/videos/' + videoUUID + '/comments/124' 276 const path = '/api/v1/videos/' + video.uuid + '/comments/124'
276 await makeDeleteRequest({ url: server.url, path, token: server.accessToken, statusCodeExpected: HttpStatusCode.NOT_FOUND_404 }) 277 await makeDeleteRequest({ url: server.url, path, token: server.accessToken, statusCodeExpected: HttpStatusCode.NOT_FOUND_404 })
277 }) 278 })
278 279
@@ -280,11 +281,11 @@ describe('Test video comments API validator', function () {
280 let commentToDelete: number 281 let commentToDelete: number
281 282
282 { 283 {
283 const res = await addVideoCommentThread(server.url, userAccessToken, videoUUID, 'hello') 284 const res = await addVideoCommentThread(server.url, userAccessToken, video.uuid, 'hello')
284 commentToDelete = res.body.comment.id 285 commentToDelete = res.body.comment.id
285 } 286 }
286 287
287 const path = '/api/v1/videos/' + videoUUID + '/comments/' + commentToDelete 288 const path = '/api/v1/videos/' + video.uuid + '/comments/' + commentToDelete
288 289
289 await makeDeleteRequest({ url: server.url, path, token: userAccessToken2, statusCodeExpected: HttpStatusCode.FORBIDDEN_403 }) 290 await makeDeleteRequest({ url: server.url, path, token: userAccessToken2, statusCodeExpected: HttpStatusCode.FORBIDDEN_403 })
290 await makeDeleteRequest({ url: server.url, path, token: userAccessToken, statusCodeExpected: HttpStatusCode.NO_CONTENT_204 }) 291 await makeDeleteRequest({ url: server.url, path, token: userAccessToken, statusCodeExpected: HttpStatusCode.NO_CONTENT_204 })
@@ -323,8 +324,8 @@ describe('Test video comments API validator', function () {
323 describe('When a video has comments disabled', function () { 324 describe('When a video has comments disabled', function () {
324 before(async function () { 325 before(async function () {
325 const res = await uploadVideo(server.url, server.accessToken, { commentsEnabled: false }) 326 const res = await uploadVideo(server.url, server.accessToken, { commentsEnabled: false })
326 videoUUID = res.body.video.uuid 327 video = res.body.video
327 pathThread = '/api/v1/videos/' + videoUUID + '/comment-threads' 328 pathThread = '/api/v1/videos/' + video.uuid + '/comment-threads'
328 }) 329 })
329 330
330 it('Should return an empty thread list', async function () { 331 it('Should return an empty thread list', async function () {
diff --git a/server/tests/api/check-params/video-playlists.ts b/server/tests/api/check-params/video-playlists.ts
index bbea88354..18253d11a 100644
--- a/server/tests/api/check-params/video-playlists.ts
+++ b/server/tests/api/check-params/video-playlists.ts
@@ -1,8 +1,13 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4import { VideoPlaylistCreateResult, VideoPlaylistPrivacy, VideoPlaylistType } from '@shared/models'
5import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes'
4import { 6import {
5 addVideoInPlaylist, 7 addVideoInPlaylist,
8 checkBadCountPagination,
9 checkBadSortPagination,
10 checkBadStartPagination,
6 cleanupTests, 11 cleanupTests,
7 createVideoPlaylist, 12 createVideoPlaylist,
8 deleteVideoPlaylist, 13 deleteVideoPlaylist,
@@ -21,20 +26,14 @@ import {
21 updateVideoPlaylistElement, 26 updateVideoPlaylistElement,
22 uploadVideoAndGetId 27 uploadVideoAndGetId
23} from '../../../../shared/extra-utils' 28} from '../../../../shared/extra-utils'
24import {
25 checkBadCountPagination,
26 checkBadSortPagination,
27 checkBadStartPagination
28} from '../../../../shared/extra-utils/requests/check-api-params'
29import { VideoPlaylistPrivacy } from '../../../../shared/models/videos/playlist/video-playlist-privacy.model'
30import { VideoPlaylistType } from '../../../../shared/models/videos/playlist/video-playlist-type.model'
31import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes'
32 29
33describe('Test video playlists API validator', function () { 30describe('Test video playlists API validator', function () {
34 let server: ServerInfo 31 let server: ServerInfo
35 let userAccessToken: string 32 let userAccessToken: string
36 let playlistUUID: string 33
34 let playlist: VideoPlaylistCreateResult
37 let privatePlaylistUUID: string 35 let privatePlaylistUUID: string
36
38 let watchLaterPlaylistId: number 37 let watchLaterPlaylistId: number
39 let videoId: number 38 let videoId: number
40 let playlistElementId: number 39 let playlistElementId: number
@@ -67,7 +66,7 @@ describe('Test video playlists API validator', function () {
67 videoChannelId: server.videoChannel.id 66 videoChannelId: server.videoChannel.id
68 } 67 }
69 }) 68 })
70 playlistUUID = res.body.videoPlaylist.uuid 69 playlist = res.body.videoPlaylist
71 } 70 }
72 71
73 { 72 {
@@ -150,15 +149,15 @@ describe('Test video playlists API validator', function () {
150 const path = '/api/v1/video-playlists/' 149 const path = '/api/v1/video-playlists/'
151 150
152 it('Should fail with a bad start pagination', async function () { 151 it('Should fail with a bad start pagination', async function () {
153 await checkBadStartPagination(server.url, path + playlistUUID + '/videos', server.accessToken) 152 await checkBadStartPagination(server.url, path + playlist.shortUUID + '/videos', server.accessToken)
154 }) 153 })
155 154
156 it('Should fail with a bad count pagination', async function () { 155 it('Should fail with a bad count pagination', async function () {
157 await checkBadCountPagination(server.url, path + playlistUUID + '/videos', server.accessToken) 156 await checkBadCountPagination(server.url, path + playlist.shortUUID + '/videos', server.accessToken)
158 }) 157 })
159 158
160 it('Should success with the correct parameters', async function () { 159 it('Should success with the correct parameters', async function () {
161 await makeGetRequest({ url: server.url, path: path + playlistUUID + '/videos', statusCodeExpected: HttpStatusCode.OK_200 }) 160 await makeGetRequest({ url: server.url, path: path + playlist.shortUUID + '/videos', statusCodeExpected: HttpStatusCode.OK_200 })
162 }) 161 })
163 }) 162 })
164 163
@@ -177,6 +176,7 @@ describe('Test video playlists API validator', function () {
177 token: server.accessToken, 176 token: server.accessToken,
178 playlistAttrs: { 177 playlistAttrs: {
179 displayName: 'super playlist', 178 displayName: 'super playlist',
179 videoChannelId: server.videoChannel.id,
180 privacy: VideoPlaylistPrivacy.UNLISTED 180 privacy: VideoPlaylistPrivacy.UNLISTED
181 } 181 }
182 }) 182 })
@@ -187,7 +187,7 @@ describe('Test video playlists API validator', function () {
187 }) 187 })
188 188
189 it('Should succeed with the correct params', async function () { 189 it('Should succeed with the correct params', async function () {
190 await getVideoPlaylist(server.url, playlistUUID, HttpStatusCode.OK_200) 190 await getVideoPlaylist(server.url, playlist.uuid, HttpStatusCode.OK_200)
191 }) 191 })
192 }) 192 })
193 193
@@ -213,7 +213,7 @@ describe('Test video playlists API validator', function () {
213 const params = getBase({}, { token: null, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 }) 213 const params = getBase({}, { token: null, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
214 214
215 await createVideoPlaylist(params) 215 await createVideoPlaylist(params)
216 await updateVideoPlaylist(getUpdate(params, playlistUUID)) 216 await updateVideoPlaylist(getUpdate(params, playlist.shortUUID))
217 }) 217 })
218 218
219 it('Should fail without displayName', async function () { 219 it('Should fail without displayName', async function () {
@@ -226,42 +226,42 @@ describe('Test video playlists API validator', function () {
226 const params = getBase({ displayName: 's'.repeat(300) }) 226 const params = getBase({ displayName: 's'.repeat(300) })
227 227
228 await createVideoPlaylist(params) 228 await createVideoPlaylist(params)
229 await updateVideoPlaylist(getUpdate(params, playlistUUID)) 229 await updateVideoPlaylist(getUpdate(params, playlist.shortUUID))
230 }) 230 })
231 231
232 it('Should fail with an incorrect description', async function () { 232 it('Should fail with an incorrect description', async function () {
233 const params = getBase({ description: 't' }) 233 const params = getBase({ description: 't' })
234 234
235 await createVideoPlaylist(params) 235 await createVideoPlaylist(params)
236 await updateVideoPlaylist(getUpdate(params, playlistUUID)) 236 await updateVideoPlaylist(getUpdate(params, playlist.shortUUID))
237 }) 237 })
238 238
239 it('Should fail with an incorrect privacy', async function () { 239 it('Should fail with an incorrect privacy', async function () {
240 const params = getBase({ privacy: 45 }) 240 const params = getBase({ privacy: 45 })
241 241
242 await createVideoPlaylist(params) 242 await createVideoPlaylist(params)
243 await updateVideoPlaylist(getUpdate(params, playlistUUID)) 243 await updateVideoPlaylist(getUpdate(params, playlist.shortUUID))
244 }) 244 })
245 245
246 it('Should fail with an unknown video channel id', async function () { 246 it('Should fail with an unknown video channel id', async function () {
247 const params = getBase({ videoChannelId: 42 }, { expectedStatus: HttpStatusCode.NOT_FOUND_404 }) 247 const params = getBase({ videoChannelId: 42 }, { expectedStatus: HttpStatusCode.NOT_FOUND_404 })
248 248
249 await createVideoPlaylist(params) 249 await createVideoPlaylist(params)
250 await updateVideoPlaylist(getUpdate(params, playlistUUID)) 250 await updateVideoPlaylist(getUpdate(params, playlist.shortUUID))
251 }) 251 })
252 252
253 it('Should fail with an incorrect thumbnail file', async function () { 253 it('Should fail with an incorrect thumbnail file', async function () {
254 const params = getBase({ thumbnailfile: 'video_short.mp4' }) 254 const params = getBase({ thumbnailfile: 'video_short.mp4' })
255 255
256 await createVideoPlaylist(params) 256 await createVideoPlaylist(params)
257 await updateVideoPlaylist(getUpdate(params, playlistUUID)) 257 await updateVideoPlaylist(getUpdate(params, playlist.shortUUID))
258 }) 258 })
259 259
260 it('Should fail with a thumbnail file too big', async function () { 260 it('Should fail with a thumbnail file too big', async function () {
261 const params = getBase({ thumbnailfile: 'preview-big.png' }) 261 const params = getBase({ thumbnailfile: 'preview-big.png' })
262 262
263 await createVideoPlaylist(params) 263 await createVideoPlaylist(params)
264 await updateVideoPlaylist(getUpdate(params, playlistUUID)) 264 await updateVideoPlaylist(getUpdate(params, playlist.shortUUID))
265 }) 265 })
266 266
267 it('Should fail to set "public" a playlist not assigned to a channel', async function () { 267 it('Should fail to set "public" a playlist not assigned to a channel', async function () {
@@ -272,8 +272,8 @@ describe('Test video playlists API validator', function () {
272 await createVideoPlaylist(params) 272 await createVideoPlaylist(params)
273 await createVideoPlaylist(params2) 273 await createVideoPlaylist(params2)
274 await updateVideoPlaylist(getUpdate(params, privatePlaylistUUID)) 274 await updateVideoPlaylist(getUpdate(params, privatePlaylistUUID))
275 await updateVideoPlaylist(getUpdate(params2, playlistUUID)) 275 await updateVideoPlaylist(getUpdate(params2, playlist.shortUUID))
276 await updateVideoPlaylist(getUpdate(params3, playlistUUID)) 276 await updateVideoPlaylist(getUpdate(params3, playlist.shortUUID))
277 }) 277 })
278 278
279 it('Should fail with an unknown playlist to update', async function () { 279 it('Should fail with an unknown playlist to update', async function () {
@@ -286,7 +286,7 @@ describe('Test video playlists API validator', function () {
286 it('Should fail to update a playlist of another user', async function () { 286 it('Should fail to update a playlist of another user', async function () {
287 await updateVideoPlaylist(getUpdate( 287 await updateVideoPlaylist(getUpdate(
288 getBase({}, { token: userAccessToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 }), 288 getBase({}, { token: userAccessToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 }),
289 playlistUUID 289 playlist.shortUUID
290 )) 290 ))
291 }) 291 })
292 292
@@ -305,7 +305,7 @@ describe('Test video playlists API validator', function () {
305 305
306 { 306 {
307 const params = getBase({}, { expectedStatus: HttpStatusCode.NO_CONTENT_204 }) 307 const params = getBase({}, { expectedStatus: HttpStatusCode.NO_CONTENT_204 })
308 await updateVideoPlaylist(getUpdate(params, playlistUUID)) 308 await updateVideoPlaylist(getUpdate(params, playlist.shortUUID))
309 } 309 }
310 }) 310 })
311 }) 311 })
@@ -316,7 +316,7 @@ describe('Test video playlists API validator', function () {
316 expectedStatus: HttpStatusCode.BAD_REQUEST_400, 316 expectedStatus: HttpStatusCode.BAD_REQUEST_400,
317 url: server.url, 317 url: server.url,
318 token: server.accessToken, 318 token: server.accessToken,
319 playlistId: playlistUUID, 319 playlistId: playlist.id,
320 elementAttrs: Object.assign({ 320 elementAttrs: Object.assign({
321 videoId, 321 videoId,
322 startTimestamp: 2, 322 startTimestamp: 2,
@@ -381,7 +381,7 @@ describe('Test video playlists API validator', function () {
381 stopTimestamp: 2 381 stopTimestamp: 2
382 }, elementAttrs), 382 }, elementAttrs),
383 playlistElementId, 383 playlistElementId,
384 playlistId: playlistUUID, 384 playlistId: playlist.id,
385 expectedStatus: HttpStatusCode.BAD_REQUEST_400 385 expectedStatus: HttpStatusCode.BAD_REQUEST_400
386 }, wrapper) 386 }, wrapper)
387 } 387 }
@@ -451,7 +451,7 @@ describe('Test video playlists API validator', function () {
451 return Object.assign({ 451 return Object.assign({
452 url: server.url, 452 url: server.url,
453 token: server.accessToken, 453 token: server.accessToken,
454 playlistId: playlistUUID, 454 playlistId: playlist.shortUUID,
455 elementAttrs: Object.assign({ 455 elementAttrs: Object.assign({
456 startPosition: 1, 456 startPosition: 1,
457 insertAfterPosition: 2, 457 insertAfterPosition: 2,
@@ -469,7 +469,7 @@ describe('Test video playlists API validator', function () {
469 await addVideoInPlaylist({ 469 await addVideoInPlaylist({
470 url: server.url, 470 url: server.url,
471 token: server.accessToken, 471 token: server.accessToken,
472 playlistId: playlistUUID, 472 playlistId: playlist.shortUUID,
473 elementAttrs: { videoId: id } 473 elementAttrs: { videoId: id }
474 }) 474 })
475 } 475 }
@@ -606,7 +606,7 @@ describe('Test video playlists API validator', function () {
606 url: server.url, 606 url: server.url,
607 token: server.accessToken, 607 token: server.accessToken,
608 playlistElementId, 608 playlistElementId,
609 playlistId: playlistUUID, 609 playlistId: playlist.uuid,
610 expectedStatus: HttpStatusCode.BAD_REQUEST_400 610 expectedStatus: HttpStatusCode.BAD_REQUEST_400
611 }, wrapper) 611 }, wrapper)
612 } 612 }
@@ -662,7 +662,7 @@ describe('Test video playlists API validator', function () {
662 }) 662 })
663 663
664 it('Should fail with a playlist of another user', async function () { 664 it('Should fail with a playlist of another user', async function () {
665 await deleteVideoPlaylist(server.url, userAccessToken, playlistUUID, HttpStatusCode.FORBIDDEN_403) 665 await deleteVideoPlaylist(server.url, userAccessToken, playlist.uuid, HttpStatusCode.FORBIDDEN_403)
666 }) 666 })
667 667
668 it('Should fail with the watch later playlist', async function () { 668 it('Should fail with the watch later playlist', async function () {
@@ -670,7 +670,7 @@ describe('Test video playlists API validator', function () {
670 }) 670 })
671 671
672 it('Should succeed with the correct params', async function () { 672 it('Should succeed with the correct params', async function () {
673 await deleteVideoPlaylist(server.url, server.accessToken, playlistUUID) 673 await deleteVideoPlaylist(server.url, server.accessToken, playlist.uuid)
674 }) 674 })
675 }) 675 })
676 676
diff --git a/server/tests/api/check-params/videos.ts b/server/tests/api/check-params/videos.ts
index a6eecb13a..4d7a9a23b 100644
--- a/server/tests/api/check-params/videos.ts
+++ b/server/tests/api/check-params/videos.ts
@@ -5,7 +5,7 @@ import * as chai from 'chai'
5import { omit } from 'lodash' 5import { omit } from 'lodash'
6import { join } from 'path' 6import { join } from 'path'
7import { randomInt } from '@shared/core-utils' 7import { randomInt } from '@shared/core-utils'
8import { PeerTubeProblemDocument } from '@shared/models' 8import { PeerTubeProblemDocument, VideoCreateResult } from '@shared/models'
9import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes' 9import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes'
10import { 10import {
11 checkUploadVideoParam, 11 checkUploadVideoParam,
@@ -42,7 +42,7 @@ describe('Test videos API validator', function () {
42 let accountName: string 42 let accountName: string
43 let channelId: number 43 let channelId: number
44 let channelName: string 44 let channelName: string
45 let videoId 45 let video: VideoCreateResult
46 46
47 // --------------------------------------------------------------- 47 // ---------------------------------------------------------------
48 48
@@ -490,7 +490,7 @@ describe('Test videos API validator', function () {
490 490
491 before(async function () { 491 before(async function () {
492 const res = await getVideosList(server.url) 492 const res = await getVideosList(server.url)
493 videoId = res.body.data[0].uuid 493 video = res.body.data[0]
494 }) 494 })
495 495
496 it('Should fail with nothing', async function () { 496 it('Should fail with nothing', async function () {
@@ -518,79 +518,79 @@ describe('Test videos API validator', function () {
518 it('Should fail with a long name', async function () { 518 it('Should fail with a long name', async function () {
519 const fields = immutableAssign(baseCorrectParams, { name: 'super'.repeat(65) }) 519 const fields = immutableAssign(baseCorrectParams, { name: 'super'.repeat(65) })
520 520
521 await makePutBodyRequest({ url: server.url, path: path + videoId, token: server.accessToken, fields }) 521 await makePutBodyRequest({ url: server.url, path: path + video.shortUUID, token: server.accessToken, fields })
522 }) 522 })
523 523
524 it('Should fail with a bad category', async function () { 524 it('Should fail with a bad category', async function () {
525 const fields = immutableAssign(baseCorrectParams, { category: 125 }) 525 const fields = immutableAssign(baseCorrectParams, { category: 125 })
526 526
527 await makePutBodyRequest({ url: server.url, path: path + videoId, token: server.accessToken, fields }) 527 await makePutBodyRequest({ url: server.url, path: path + video.shortUUID, token: server.accessToken, fields })
528 }) 528 })
529 529
530 it('Should fail with a bad licence', async function () { 530 it('Should fail with a bad licence', async function () {
531 const fields = immutableAssign(baseCorrectParams, { licence: 125 }) 531 const fields = immutableAssign(baseCorrectParams, { licence: 125 })
532 532
533 await makePutBodyRequest({ url: server.url, path: path + videoId, token: server.accessToken, fields }) 533 await makePutBodyRequest({ url: server.url, path: path + video.shortUUID, token: server.accessToken, fields })
534 }) 534 })
535 535
536 it('Should fail with a bad language', async function () { 536 it('Should fail with a bad language', async function () {
537 const fields = immutableAssign(baseCorrectParams, { language: 'a'.repeat(15) }) 537 const fields = immutableAssign(baseCorrectParams, { language: 'a'.repeat(15) })
538 538
539 await makePutBodyRequest({ url: server.url, path: path + videoId, token: server.accessToken, fields }) 539 await makePutBodyRequest({ url: server.url, path: path + video.shortUUID, token: server.accessToken, fields })
540 }) 540 })
541 541
542 it('Should fail with a long description', async function () { 542 it('Should fail with a long description', async function () {
543 const fields = immutableAssign(baseCorrectParams, { description: 'super'.repeat(2500) }) 543 const fields = immutableAssign(baseCorrectParams, { description: 'super'.repeat(2500) })
544 544
545 await makePutBodyRequest({ url: server.url, path: path + videoId, token: server.accessToken, fields }) 545 await makePutBodyRequest({ url: server.url, path: path + video.shortUUID, token: server.accessToken, fields })
546 }) 546 })
547 547
548 it('Should fail with a long support text', async function () { 548 it('Should fail with a long support text', async function () {
549 const fields = immutableAssign(baseCorrectParams, { support: 'super'.repeat(201) }) 549 const fields = immutableAssign(baseCorrectParams, { support: 'super'.repeat(201) })
550 550
551 await makePutBodyRequest({ url: server.url, path: path + videoId, token: server.accessToken, fields }) 551 await makePutBodyRequest({ url: server.url, path: path + video.shortUUID, token: server.accessToken, fields })
552 }) 552 })
553 553
554 it('Should fail with a bad channel', async function () { 554 it('Should fail with a bad channel', async function () {
555 const fields = immutableAssign(baseCorrectParams, { channelId: 545454 }) 555 const fields = immutableAssign(baseCorrectParams, { channelId: 545454 })
556 556
557 await makePutBodyRequest({ url: server.url, path: path + videoId, token: server.accessToken, fields }) 557 await makePutBodyRequest({ url: server.url, path: path + video.shortUUID, token: server.accessToken, fields })
558 }) 558 })
559 559
560 it('Should fail with too many tags', async function () { 560 it('Should fail with too many tags', async function () {
561 const fields = immutableAssign(baseCorrectParams, { tags: [ 'tag1', 'tag2', 'tag3', 'tag4', 'tag5', 'tag6' ] }) 561 const fields = immutableAssign(baseCorrectParams, { tags: [ 'tag1', 'tag2', 'tag3', 'tag4', 'tag5', 'tag6' ] })
562 562
563 await makePutBodyRequest({ url: server.url, path: path + videoId, token: server.accessToken, fields }) 563 await makePutBodyRequest({ url: server.url, path: path + video.shortUUID, token: server.accessToken, fields })
564 }) 564 })
565 565
566 it('Should fail with a tag length too low', async function () { 566 it('Should fail with a tag length too low', async function () {
567 const fields = immutableAssign(baseCorrectParams, { tags: [ 'tag1', 't' ] }) 567 const fields = immutableAssign(baseCorrectParams, { tags: [ 'tag1', 't' ] })
568 568
569 await makePutBodyRequest({ url: server.url, path: path + videoId, token: server.accessToken, fields }) 569 await makePutBodyRequest({ url: server.url, path: path + video.shortUUID, token: server.accessToken, fields })
570 }) 570 })
571 571
572 it('Should fail with a tag length too big', async function () { 572 it('Should fail with a tag length too big', async function () {
573 const fields = immutableAssign(baseCorrectParams, { tags: [ 'tag1', 'my_super_tag_too_long_long_long_long_long_long' ] }) 573 const fields = immutableAssign(baseCorrectParams, { tags: [ 'tag1', 'my_super_tag_too_long_long_long_long_long_long' ] })
574 574
575 await makePutBodyRequest({ url: server.url, path: path + videoId, token: server.accessToken, fields }) 575 await makePutBodyRequest({ url: server.url, path: path + video.shortUUID, token: server.accessToken, fields })
576 }) 576 })
577 577
578 it('Should fail with a bad schedule update (miss updateAt)', async function () { 578 it('Should fail with a bad schedule update (miss updateAt)', async function () {
579 const fields = immutableAssign(baseCorrectParams, { scheduleUpdate: { privacy: VideoPrivacy.PUBLIC } }) 579 const fields = immutableAssign(baseCorrectParams, { scheduleUpdate: { privacy: VideoPrivacy.PUBLIC } })
580 580
581 await makePutBodyRequest({ url: server.url, path: path + videoId, token: server.accessToken, fields }) 581 await makePutBodyRequest({ url: server.url, path: path + video.shortUUID, token: server.accessToken, fields })
582 }) 582 })
583 583
584 it('Should fail with a bad schedule update (wrong updateAt)', async function () { 584 it('Should fail with a bad schedule update (wrong updateAt)', async function () {
585 const fields = immutableAssign(baseCorrectParams, { scheduleUpdate: { updateAt: 'toto', privacy: VideoPrivacy.PUBLIC } }) 585 const fields = immutableAssign(baseCorrectParams, { scheduleUpdate: { updateAt: 'toto', privacy: VideoPrivacy.PUBLIC } })
586 586
587 await makePutBodyRequest({ url: server.url, path: path + videoId, token: server.accessToken, fields }) 587 await makePutBodyRequest({ url: server.url, path: path + video.shortUUID, token: server.accessToken, fields })
588 }) 588 })
589 589
590 it('Should fail with a bad originally published at param', async function () { 590 it('Should fail with a bad originally published at param', async function () {
591 const fields = immutableAssign(baseCorrectParams, { originallyPublishedAt: 'toto' }) 591 const fields = immutableAssign(baseCorrectParams, { originallyPublishedAt: 'toto' })
592 592
593 await makePutBodyRequest({ url: server.url, path: path + videoId, token: server.accessToken, fields }) 593 await makePutBodyRequest({ url: server.url, path: path + video.shortUUID, token: server.accessToken, fields })
594 }) 594 })
595 595
596 it('Should fail with an incorrect thumbnail file', async function () { 596 it('Should fail with an incorrect thumbnail file', async function () {
@@ -602,7 +602,7 @@ describe('Test videos API validator', function () {
602 await makeUploadRequest({ 602 await makeUploadRequest({
603 url: server.url, 603 url: server.url,
604 method: 'PUT', 604 method: 'PUT',
605 path: path + videoId, 605 path: path + video.shortUUID,
606 token: server.accessToken, 606 token: server.accessToken,
607 fields, 607 fields,
608 attaches 608 attaches
@@ -618,7 +618,7 @@ describe('Test videos API validator', function () {
618 await makeUploadRequest({ 618 await makeUploadRequest({
619 url: server.url, 619 url: server.url,
620 method: 'PUT', 620 method: 'PUT',
621 path: path + videoId, 621 path: path + video.shortUUID,
622 token: server.accessToken, 622 token: server.accessToken,
623 fields, 623 fields,
624 attaches 624 attaches
@@ -634,7 +634,7 @@ describe('Test videos API validator', function () {
634 await makeUploadRequest({ 634 await makeUploadRequest({
635 url: server.url, 635 url: server.url,
636 method: 'PUT', 636 method: 'PUT',
637 path: path + videoId, 637 path: path + video.shortUUID,
638 token: server.accessToken, 638 token: server.accessToken,
639 fields, 639 fields,
640 attaches 640 attaches
@@ -650,7 +650,7 @@ describe('Test videos API validator', function () {
650 await makeUploadRequest({ 650 await makeUploadRequest({
651 url: server.url, 651 url: server.url,
652 method: 'PUT', 652 method: 'PUT',
653 path: path + videoId, 653 path: path + video.shortUUID,
654 token: server.accessToken, 654 token: server.accessToken,
655 fields, 655 fields,
656 attaches 656 attaches
@@ -662,7 +662,7 @@ describe('Test videos API validator', function () {
662 662
663 await makePutBodyRequest({ 663 await makePutBodyRequest({
664 url: server.url, 664 url: server.url,
665 path: path + videoId, 665 path: path + video.shortUUID,
666 token: userAccessToken, 666 token: userAccessToken,
667 fields, 667 fields,
668 statusCodeExpected: HttpStatusCode.FORBIDDEN_403 668 statusCodeExpected: HttpStatusCode.FORBIDDEN_403
@@ -674,7 +674,7 @@ describe('Test videos API validator', function () {
674 it('Shoud report the appropriate error', async function () { 674 it('Shoud report the appropriate error', async function () {
675 const fields = immutableAssign(baseCorrectParams, { licence: 125 }) 675 const fields = immutableAssign(baseCorrectParams, { licence: 125 })
676 676
677 const res = await makePutBodyRequest({ url: server.url, path: path + videoId, token: server.accessToken, fields }) 677 const res = await makePutBodyRequest({ url: server.url, path: path + video.shortUUID, token: server.accessToken, fields })
678 const error = res.body as PeerTubeProblemDocument 678 const error = res.body as PeerTubeProblemDocument
679 679
680 expect(error.docs).to.equal('https://docs.joinpeertube.org/api-rest-reference.html#operation/putVideo') 680 expect(error.docs).to.equal('https://docs.joinpeertube.org/api-rest-reference.html#operation/putVideo')
@@ -694,7 +694,7 @@ describe('Test videos API validator', function () {
694 694
695 await makePutBodyRequest({ 695 await makePutBodyRequest({
696 url: server.url, 696 url: server.url,
697 path: path + videoId, 697 path: path + video.shortUUID,
698 token: server.accessToken, 698 token: server.accessToken,
699 fields, 699 fields,
700 statusCodeExpected: HttpStatusCode.NO_CONTENT_204 700 statusCodeExpected: HttpStatusCode.NO_CONTENT_204
@@ -739,7 +739,7 @@ describe('Test videos API validator', function () {
739 }) 739 })
740 740
741 it('Should succeed with the correct parameters', async function () { 741 it('Should succeed with the correct parameters', async function () {
742 await getVideo(server.url, videoId) 742 await getVideo(server.url, video.shortUUID)
743 }) 743 })
744 }) 744 })
745 745
@@ -810,7 +810,7 @@ describe('Test videos API validator', function () {
810 }) 810 })
811 811
812 it('Should fail with a video of another user without the appropriate right', async function () { 812 it('Should fail with a video of another user without the appropriate right', async function () {
813 await removeVideo(server.url, userAccessToken, videoId, HttpStatusCode.FORBIDDEN_403) 813 await removeVideo(server.url, userAccessToken, video.uuid, HttpStatusCode.FORBIDDEN_403)
814 }) 814 })
815 815
816 it('Should fail with a video of another server') 816 it('Should fail with a video of another server')
@@ -832,7 +832,7 @@ describe('Test videos API validator', function () {
832 }) 832 })
833 833
834 it('Should succeed with the correct parameters', async function () { 834 it('Should succeed with the correct parameters', async function () {
835 await removeVideo(server.url, server.accessToken, videoId) 835 await removeVideo(server.url, server.accessToken, video.uuid)
836 }) 836 })
837 }) 837 })
838 838
diff --git a/server/tests/api/notifications/moderation-notifications.ts b/server/tests/api/notifications/moderation-notifications.ts
index 4ce6675b6..3425480ae 100644
--- a/server/tests/api/notifications/moderation-notifications.ts
+++ b/server/tests/api/notifications/moderation-notifications.ts
@@ -1,7 +1,7 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4import { v4 as uuidv4 } from 'uuid' 4import { buildUUID } from '@server/helpers/uuid'
5import { AbuseState } from '@shared/models' 5import { AbuseState } from '@shared/models'
6import { 6import {
7 addAbuseMessage, 7 addAbuseMessage,
@@ -85,7 +85,7 @@ describe('Test moderation notifications', function () {
85 it('Should send a notification to moderators on local video abuse', async function () { 85 it('Should send a notification to moderators on local video abuse', async function () {
86 this.timeout(20000) 86 this.timeout(20000)
87 87
88 const name = 'video for abuse ' + uuidv4() 88 const name = 'video for abuse ' + buildUUID()
89 const resVideo = await uploadVideo(servers[0].url, userAccessToken, { name }) 89 const resVideo = await uploadVideo(servers[0].url, userAccessToken, { name })
90 const video = resVideo.body.video 90 const video = resVideo.body.video
91 91
@@ -98,7 +98,7 @@ describe('Test moderation notifications', function () {
98 it('Should send a notification to moderators on remote video abuse', async function () { 98 it('Should send a notification to moderators on remote video abuse', async function () {
99 this.timeout(20000) 99 this.timeout(20000)
100 100
101 const name = 'video for abuse ' + uuidv4() 101 const name = 'video for abuse ' + buildUUID()
102 const resVideo = await uploadVideo(servers[0].url, userAccessToken, { name }) 102 const resVideo = await uploadVideo(servers[0].url, userAccessToken, { name })
103 const video = resVideo.body.video 103 const video = resVideo.body.video
104 104
@@ -114,10 +114,10 @@ describe('Test moderation notifications', function () {
114 it('Should send a notification to moderators on local comment abuse', async function () { 114 it('Should send a notification to moderators on local comment abuse', async function () {
115 this.timeout(20000) 115 this.timeout(20000)
116 116
117 const name = 'video for abuse ' + uuidv4() 117 const name = 'video for abuse ' + buildUUID()
118 const resVideo = await uploadVideo(servers[0].url, userAccessToken, { name }) 118 const resVideo = await uploadVideo(servers[0].url, userAccessToken, { name })
119 const video = resVideo.body.video 119 const video = resVideo.body.video
120 const resComment = await addVideoCommentThread(servers[0].url, userAccessToken, video.id, 'comment abuse ' + uuidv4()) 120 const resComment = await addVideoCommentThread(servers[0].url, userAccessToken, video.id, 'comment abuse ' + buildUUID())
121 const comment = resComment.body.comment 121 const comment = resComment.body.comment
122 122
123 await waitJobs(servers) 123 await waitJobs(servers)
@@ -131,10 +131,10 @@ describe('Test moderation notifications', function () {
131 it('Should send a notification to moderators on remote comment abuse', async function () { 131 it('Should send a notification to moderators on remote comment abuse', async function () {
132 this.timeout(20000) 132 this.timeout(20000)
133 133
134 const name = 'video for abuse ' + uuidv4() 134 const name = 'video for abuse ' + buildUUID()
135 const resVideo = await uploadVideo(servers[0].url, userAccessToken, { name }) 135 const resVideo = await uploadVideo(servers[0].url, userAccessToken, { name })
136 const video = resVideo.body.video 136 const video = resVideo.body.video
137 await addVideoCommentThread(servers[0].url, userAccessToken, video.id, 'comment abuse ' + uuidv4()) 137 await addVideoCommentThread(servers[0].url, userAccessToken, video.id, 'comment abuse ' + buildUUID())
138 138
139 await waitJobs(servers) 139 await waitJobs(servers)
140 140
@@ -188,7 +188,7 @@ describe('Test moderation notifications', function () {
188 token: userAccessToken 188 token: userAccessToken
189 } 189 }
190 190
191 const name = 'abuse ' + uuidv4() 191 const name = 'abuse ' + buildUUID()
192 const resVideo = await uploadVideo(servers[0].url, userAccessToken, { name }) 192 const resVideo = await uploadVideo(servers[0].url, userAccessToken, { name })
193 const video = resVideo.body.video 193 const video = resVideo.body.video
194 194
@@ -236,7 +236,7 @@ describe('Test moderation notifications', function () {
236 token: servers[0].accessToken 236 token: servers[0].accessToken
237 } 237 }
238 238
239 const name = 'abuse ' + uuidv4() 239 const name = 'abuse ' + buildUUID()
240 const resVideo = await uploadVideo(servers[0].url, userAccessToken, { name }) 240 const resVideo = await uploadVideo(servers[0].url, userAccessToken, { name })
241 const video = resVideo.body.video 241 const video = resVideo.body.video
242 242
@@ -307,7 +307,7 @@ describe('Test moderation notifications', function () {
307 it('Should send a notification to video owner on blacklist', async function () { 307 it('Should send a notification to video owner on blacklist', async function () {
308 this.timeout(10000) 308 this.timeout(10000)
309 309
310 const name = 'video for abuse ' + uuidv4() 310 const name = 'video for abuse ' + buildUUID()
311 const resVideo = await uploadVideo(servers[0].url, userAccessToken, { name }) 311 const resVideo = await uploadVideo(servers[0].url, userAccessToken, { name })
312 const uuid = resVideo.body.video.uuid 312 const uuid = resVideo.body.video.uuid
313 313
@@ -320,7 +320,7 @@ describe('Test moderation notifications', function () {
320 it('Should send a notification to video owner on unblacklist', async function () { 320 it('Should send a notification to video owner on unblacklist', async function () {
321 this.timeout(10000) 321 this.timeout(10000)
322 322
323 const name = 'video for abuse ' + uuidv4() 323 const name = 'video for abuse ' + buildUUID()
324 const resVideo = await uploadVideo(servers[0].url, userAccessToken, { name }) 324 const resVideo = await uploadVideo(servers[0].url, userAccessToken, { name })
325 const uuid = resVideo.body.video.uuid 325 const uuid = resVideo.body.video.uuid
326 326
@@ -507,7 +507,7 @@ describe('Test moderation notifications', function () {
507 it('Should send notification to moderators on new video with auto-blacklist', async function () { 507 it('Should send notification to moderators on new video with auto-blacklist', async function () {
508 this.timeout(40000) 508 this.timeout(40000)
509 509
510 videoName = 'video with auto-blacklist ' + uuidv4() 510 videoName = 'video with auto-blacklist ' + buildUUID()
511 const resVideo = await uploadVideo(servers[0].url, userAccessToken, { name: videoName }) 511 const resVideo = await uploadVideo(servers[0].url, userAccessToken, { name: videoName })
512 videoUUID = resVideo.body.video.uuid 512 videoUUID = resVideo.body.video.uuid
513 513
@@ -553,7 +553,7 @@ describe('Test moderation notifications', function () {
553 553
554 const updateAt = new Date(new Date().getTime() + 1000000) 554 const updateAt = new Date(new Date().getTime() + 1000000)
555 555
556 const name = 'video with auto-blacklist and future schedule ' + uuidv4() 556 const name = 'video with auto-blacklist and future schedule ' + buildUUID()
557 557
558 const data = { 558 const data = {
559 name, 559 name,
@@ -586,7 +586,7 @@ describe('Test moderation notifications', function () {
586 // In 2 seconds 586 // In 2 seconds
587 const updateAt = new Date(new Date().getTime() + 2000) 587 const updateAt = new Date(new Date().getTime() + 2000)
588 588
589 const name = 'video with schedule done and still auto-blacklisted ' + uuidv4() 589 const name = 'video with schedule done and still auto-blacklisted ' + buildUUID()
590 590
591 const data = { 591 const data = {
592 name, 592 name,
@@ -609,7 +609,7 @@ describe('Test moderation notifications', function () {
609 it('Should not send a notification to moderators on new video without auto-blacklist', async function () { 609 it('Should not send a notification to moderators on new video without auto-blacklist', async function () {
610 this.timeout(60000) 610 this.timeout(60000)
611 611
612 const name = 'video without auto-blacklist ' + uuidv4() 612 const name = 'video without auto-blacklist ' + buildUUID()
613 613
614 // admin with blacklist right will not be auto-blacklisted 614 // admin with blacklist right will not be auto-blacklisted
615 const resVideo = await uploadVideo(servers[0].url, servers[0].accessToken, { name }) 615 const resVideo = await uploadVideo(servers[0].url, servers[0].accessToken, { name })
diff --git a/server/tests/api/notifications/user-notifications.ts b/server/tests/api/notifications/user-notifications.ts
index 7e88d979b..e981c1718 100644
--- a/server/tests/api/notifications/user-notifications.ts
+++ b/server/tests/api/notifications/user-notifications.ts
@@ -2,7 +2,7 @@
2 2
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
5import { v4 as uuidv4 } from 'uuid' 5import { buildUUID } from '@server/helpers/uuid'
6import { 6import {
7 cleanupTests, 7 cleanupTests,
8 updateMyUser, 8 updateMyUser,
@@ -207,7 +207,7 @@ describe('Test user notifications', function () {
207 it('Should send a new video notification after a video import', async function () { 207 it('Should send a new video notification after a video import', async function () {
208 this.timeout(100000) 208 this.timeout(100000)
209 209
210 const name = 'video import ' + uuidv4() 210 const name = 'video import ' + buildUUID()
211 211
212 const attributes = { 212 const attributes = {
213 name, 213 name,
@@ -278,7 +278,7 @@ describe('Test user notifications', function () {
278 it('Should send a notification when an imported video is transcoded', async function () { 278 it('Should send a notification when an imported video is transcoded', async function () {
279 this.timeout(50000) 279 this.timeout(50000)
280 280
281 const name = 'video import ' + uuidv4() 281 const name = 'video import ' + buildUUID()
282 282
283 const attributes = { 283 const attributes = {
284 name, 284 name,
@@ -347,7 +347,7 @@ describe('Test user notifications', function () {
347 it('Should send a notification when the video import failed', async function () { 347 it('Should send a notification when the video import failed', async function () {
348 this.timeout(70000) 348 this.timeout(70000)
349 349
350 const name = 'video import ' + uuidv4() 350 const name = 'video import ' + buildUUID()
351 351
352 const attributes = { 352 const attributes = {
353 name, 353 name,
@@ -365,7 +365,7 @@ describe('Test user notifications', function () {
365 it('Should send a notification when the video import succeeded', async function () { 365 it('Should send a notification when the video import succeeded', async function () {
366 this.timeout(70000) 366 this.timeout(70000)
367 367
368 const name = 'video import ' + uuidv4() 368 const name = 'video import ' + buildUUID()
369 369
370 const attributes = { 370 const attributes = {
371 name, 371 name,
diff --git a/server/tests/api/server/handle-down.ts b/server/tests/api/server/handle-down.ts
index fe4a0e100..d57d72f5e 100644
--- a/server/tests/api/server/handle-down.ts
+++ b/server/tests/api/server/handle-down.ts
@@ -47,7 +47,7 @@ describe('Test handle downs', function () {
47 let missedVideo2: Video 47 let missedVideo2: Video
48 let unlistedVideo: Video 48 let unlistedVideo: Video
49 49
50 const videoIdsServer1: number[] = [] 50 const videoIdsServer1: string[] = []
51 51
52 const videoAttributes = { 52 const videoAttributes = {
53 name: 'my super name for server 1', 53 name: 'my super name for server 1',
diff --git a/server/tests/api/videos/video-playlists.ts b/server/tests/api/videos/video-playlists.ts
index 9dad58c8c..da8de054b 100644
--- a/server/tests/api/videos/video-playlists.ts
+++ b/server/tests/api/videos/video-playlists.ts
@@ -56,7 +56,7 @@ import {
56 removeServerFromServerBlocklist 56 removeServerFromServerBlocklist
57} from '../../../../shared/extra-utils/users/blocklist' 57} from '../../../../shared/extra-utils/users/blocklist'
58import { User } from '../../../../shared/models/users' 58import { User } from '../../../../shared/models/users'
59import { VideoPrivacy } from '../../../../shared/models/videos' 59import { VideoPlaylistCreateResult, VideoPrivacy } from '../../../../shared/models/videos'
60import { VideoExistInPlaylist } from '../../../../shared/models/videos/playlist/video-exist-in-playlist.model' 60import { VideoExistInPlaylist } from '../../../../shared/models/videos/playlist/video-exist-in-playlist.model'
61import { VideoPlaylistElement, VideoPlaylistElementType } from '../../../../shared/models/videos/playlist/video-playlist-element.model' 61import { VideoPlaylistElement, VideoPlaylistElementType } from '../../../../shared/models/videos/playlist/video-playlist-element.model'
62import { VideoPlaylistPrivacy } from '../../../../shared/models/videos/playlist/video-playlist-privacy.model' 62import { VideoPlaylistPrivacy } from '../../../../shared/models/videos/playlist/video-playlist-privacy.model'
@@ -427,31 +427,45 @@ describe('Test video playlists', function () {
427 expect(data).to.have.lengthOf(0) 427 expect(data).to.have.lengthOf(0)
428 } 428 }
429 }) 429 })
430 })
430 431
431 it('Should not list unlisted or private playlists', async function () { 432 describe('Playlist rights', function () {
433 let unlistedPlaylist: VideoPlaylistCreateResult
434 let privatePlaylist: VideoPlaylistCreateResult
435
436 before(async function () {
432 this.timeout(30000) 437 this.timeout(30000)
433 438
434 await createVideoPlaylist({ 439 {
435 url: servers[1].url, 440 const res = await createVideoPlaylist({
436 token: servers[1].accessToken, 441 url: servers[1].url,
437 playlistAttrs: { 442 token: servers[1].accessToken,
438 displayName: 'playlist unlisted', 443 playlistAttrs: {
439 privacy: VideoPlaylistPrivacy.UNLISTED 444 displayName: 'playlist unlisted',
440 } 445 privacy: VideoPlaylistPrivacy.UNLISTED,
441 }) 446 videoChannelId: servers[1].videoChannel.id
447 }
448 })
449 unlistedPlaylist = res.body.videoPlaylist
450 }
442 451
443 await createVideoPlaylist({ 452 {
444 url: servers[1].url, 453 const res = await createVideoPlaylist({
445 token: servers[1].accessToken, 454 url: servers[1].url,
446 playlistAttrs: { 455 token: servers[1].accessToken,
447 displayName: 'playlist private', 456 playlistAttrs: {
448 privacy: VideoPlaylistPrivacy.PRIVATE 457 displayName: 'playlist private',
449 } 458 privacy: VideoPlaylistPrivacy.PRIVATE
450 }) 459 }
460 })
461 privatePlaylist = res.body.videoPlaylist
462 }
451 463
452 await waitJobs(servers) 464 await waitJobs(servers)
453 await wait(3000) 465 await wait(3000)
466 })
454 467
468 it('Should not list unlisted or private playlists', async function () {
455 for (const server of servers) { 469 for (const server of servers) {
456 const results = [ 470 const results = [
457 await getAccountPlaylistsList(server.url, 'root@localhost:' + servers[1].port, 0, 5, '-createdAt'), 471 await getAccountPlaylistsList(server.url, 'root@localhost:' + servers[1].port, 0, 5, '-createdAt'),
@@ -469,6 +483,27 @@ describe('Test video playlists', function () {
469 } 483 }
470 } 484 }
471 }) 485 })
486
487 it('Should not get unlisted playlist using only the id', async function () {
488 await getVideoPlaylist(servers[1].url, unlistedPlaylist.id, 404)
489 })
490
491 it('Should get unlisted plyaylist using uuid or shortUUID', async function () {
492 await getVideoPlaylist(servers[1].url, unlistedPlaylist.uuid)
493 await getVideoPlaylist(servers[1].url, unlistedPlaylist.shortUUID)
494 })
495
496 it('Should not get private playlist without token', async function () {
497 for (const id of [ privatePlaylist.id, privatePlaylist.uuid, privatePlaylist.shortUUID ]) {
498 await getVideoPlaylist(servers[1].url, id, 401)
499 }
500 })
501
502 it('Should get private playlist with a token', async function () {
503 for (const id of [ privatePlaylist.id, privatePlaylist.uuid, privatePlaylist.shortUUID ]) {
504 await getVideoPlaylistWithToken(servers[1].url, servers[1].accessToken, id)
505 }
506 })
472 }) 507 })
473 508
474 describe('Update playlists', function () { 509 describe('Update playlists', function () {
diff --git a/server/tests/api/videos/video-privacy.ts b/server/tests/api/videos/video-privacy.ts
index fed6ca0e0..950aeb7cf 100644
--- a/server/tests/api/videos/video-privacy.ts
+++ b/server/tests/api/videos/video-privacy.ts
@@ -1,8 +1,9 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai'
4import 'mocha' 3import 'mocha'
5import { VideoPrivacy } from '../../../../shared/models/videos/video-privacy.enum' 4import * as chai from 'chai'
5import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes'
6import { Video, VideoCreateResult } from '@shared/models'
6import { 7import {
7 cleanupTests, 8 cleanupTests,
8 flushAndRunServer, 9 flushAndRunServer,
@@ -13,12 +14,11 @@ import {
13 uploadVideo 14 uploadVideo
14} from '../../../../shared/extra-utils/index' 15} from '../../../../shared/extra-utils/index'
15import { doubleFollow } from '../../../../shared/extra-utils/server/follows' 16import { doubleFollow } from '../../../../shared/extra-utils/server/follows'
17import { waitJobs } from '../../../../shared/extra-utils/server/jobs'
16import { userLogin } from '../../../../shared/extra-utils/users/login' 18import { userLogin } from '../../../../shared/extra-utils/users/login'
17import { createUser } from '../../../../shared/extra-utils/users/users' 19import { createUser } from '../../../../shared/extra-utils/users/users'
18import { getMyVideos, getVideo, getVideoWithToken, updateVideo } from '../../../../shared/extra-utils/videos/videos' 20import { getMyVideos, getVideo, getVideoWithToken, updateVideo } from '../../../../shared/extra-utils/videos/videos'
19import { waitJobs } from '../../../../shared/extra-utils/server/jobs' 21import { VideoPrivacy } from '../../../../shared/models/videos/video-privacy.enum'
20import { Video } from '@shared/models'
21import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes'
22 22
23const expect = chai.expect 23const expect = chai.expect
24 24
@@ -32,7 +32,7 @@ describe('Test video privacy', function () {
32 let internalVideoId: number 32 let internalVideoId: number
33 let internalVideoUUID: string 33 let internalVideoUUID: string
34 34
35 let unlistedVideoUUID: string 35 let unlistedVideo: VideoCreateResult
36 let nonFederatedUnlistedVideoUUID: string 36 let nonFederatedUnlistedVideoUUID: string
37 37
38 let now: number 38 let now: number
@@ -59,231 +59,246 @@ describe('Test video privacy', function () {
59 await doubleFollow(servers[0], servers[1]) 59 await doubleFollow(servers[0], servers[1])
60 }) 60 })
61 61
62 it('Should upload a private and internal videos on server 1', async function () { 62 describe('Private and internal videos', function () {
63 this.timeout(10000)
64 63
65 for (const privacy of [ VideoPrivacy.PRIVATE, VideoPrivacy.INTERNAL ]) { 64 it('Should upload a private and internal videos on server 1', async function () {
66 const attributes = { privacy } 65 this.timeout(10000)
67 await uploadVideo(servers[0].url, servers[0].accessToken, attributes)
68 }
69 66
70 await waitJobs(servers) 67 for (const privacy of [ VideoPrivacy.PRIVATE, VideoPrivacy.INTERNAL ]) {
71 }) 68 const attributes = { privacy }
69 await uploadVideo(servers[0].url, servers[0].accessToken, attributes)
70 }
72 71
73 it('Should not have these private and internal videos on server 2', async function () { 72 await waitJobs(servers)
74 const res = await getVideosList(servers[1].url) 73 })
75 74
76 expect(res.body.total).to.equal(0) 75 it('Should not have these private and internal videos on server 2', async function () {
77 expect(res.body.data).to.have.lengthOf(0) 76 const res = await getVideosList(servers[1].url)
78 })
79 77
80 it('Should not list the private and internal videos for an unauthenticated user on server 1', async function () { 78 expect(res.body.total).to.equal(0)
81 const res = await getVideosList(servers[0].url) 79 expect(res.body.data).to.have.lengthOf(0)
80 })
82 81
83 expect(res.body.total).to.equal(0) 82 it('Should not list the private and internal videos for an unauthenticated user on server 1', async function () {
84 expect(res.body.data).to.have.lengthOf(0) 83 const res = await getVideosList(servers[0].url)
85 }) 84
85 expect(res.body.total).to.equal(0)
86 expect(res.body.data).to.have.lengthOf(0)
87 })
86 88
87 it('Should not list the private video and list the internal video for an authenticated user on server 1', async function () { 89 it('Should not list the private video and list the internal video for an authenticated user on server 1', async function () {
88 const res = await getVideosListWithToken(servers[0].url, servers[0].accessToken) 90 const res = await getVideosListWithToken(servers[0].url, servers[0].accessToken)
89 91
90 expect(res.body.total).to.equal(1) 92 expect(res.body.total).to.equal(1)
91 expect(res.body.data).to.have.lengthOf(1) 93 expect(res.body.data).to.have.lengthOf(1)
92 94
93 expect(res.body.data[0].privacy.id).to.equal(VideoPrivacy.INTERNAL) 95 expect(res.body.data[0].privacy.id).to.equal(VideoPrivacy.INTERNAL)
94 }) 96 })
95 97
96 it('Should list my (private and internal) videos', async function () { 98 it('Should list my (private and internal) videos', async function () {
97 const res = await getMyVideos(servers[0].url, servers[0].accessToken, 0, 10) 99 const res = await getMyVideos(servers[0].url, servers[0].accessToken, 0, 10)
98 100
99 expect(res.body.total).to.equal(2) 101 expect(res.body.total).to.equal(2)
100 expect(res.body.data).to.have.lengthOf(2) 102 expect(res.body.data).to.have.lengthOf(2)
101 103
102 const videos: Video[] = res.body.data 104 const videos: Video[] = res.body.data
103 105
104 const privateVideo = videos.find(v => v.privacy.id === VideoPrivacy.PRIVATE) 106 const privateVideo = videos.find(v => v.privacy.id === VideoPrivacy.PRIVATE)
105 privateVideoId = privateVideo.id 107 privateVideoId = privateVideo.id
106 privateVideoUUID = privateVideo.uuid 108 privateVideoUUID = privateVideo.uuid
107 109
108 const internalVideo = videos.find(v => v.privacy.id === VideoPrivacy.INTERNAL) 110 const internalVideo = videos.find(v => v.privacy.id === VideoPrivacy.INTERNAL)
109 internalVideoId = internalVideo.id 111 internalVideoId = internalVideo.id
110 internalVideoUUID = internalVideo.uuid 112 internalVideoUUID = internalVideo.uuid
111 }) 113 })
112 114
113 it('Should not be able to watch the private/internal video with non authenticated user', async function () { 115 it('Should not be able to watch the private/internal video with non authenticated user', async function () {
114 await getVideo(servers[0].url, privateVideoUUID, HttpStatusCode.UNAUTHORIZED_401) 116 await getVideo(servers[0].url, privateVideoUUID, HttpStatusCode.UNAUTHORIZED_401)
115 await getVideo(servers[0].url, internalVideoUUID, HttpStatusCode.UNAUTHORIZED_401) 117 await getVideo(servers[0].url, internalVideoUUID, HttpStatusCode.UNAUTHORIZED_401)
116 }) 118 })
117 119
118 it('Should not be able to watch the private video with another user', async function () { 120 it('Should not be able to watch the private video with another user', async function () {
119 this.timeout(10000) 121 this.timeout(10000)
120 122
121 const user = { 123 const user = {
122 username: 'hello', 124 username: 'hello',
123 password: 'super password' 125 password: 'super password'
124 } 126 }
125 await createUser({ url: servers[0].url, accessToken: servers[0].accessToken, username: user.username, password: user.password }) 127 await createUser({ url: servers[0].url, accessToken: servers[0].accessToken, username: user.username, password: user.password })
126 128
127 anotherUserToken = await userLogin(servers[0], user) 129 anotherUserToken = await userLogin(servers[0], user)
128 await getVideoWithToken(servers[0].url, anotherUserToken, privateVideoUUID, HttpStatusCode.FORBIDDEN_403) 130 await getVideoWithToken(servers[0].url, anotherUserToken, privateVideoUUID, HttpStatusCode.FORBIDDEN_403)
129 }) 131 })
130 132
131 it('Should be able to watch the internal video with another user', async function () { 133 it('Should be able to watch the internal video with another user', async function () {
132 await getVideoWithToken(servers[0].url, anotherUserToken, internalVideoUUID, HttpStatusCode.OK_200) 134 await getVideoWithToken(servers[0].url, anotherUserToken, internalVideoUUID, HttpStatusCode.OK_200)
133 }) 135 })
134 136
135 it('Should be able to watch the private video with the correct user', async function () { 137 it('Should be able to watch the private video with the correct user', async function () {
136 await getVideoWithToken(servers[0].url, servers[0].accessToken, privateVideoUUID, HttpStatusCode.OK_200) 138 await getVideoWithToken(servers[0].url, servers[0].accessToken, privateVideoUUID, HttpStatusCode.OK_200)
139 })
137 }) 140 })
138 141
139 it('Should upload an unlisted video on server 2', async function () { 142 describe('Unlisted videos', function () {
140 this.timeout(60000)
141 143
142 const attributes = { 144 it('Should upload an unlisted video on server 2', async function () {
143 name: 'unlisted video', 145 this.timeout(60000)
144 privacy: VideoPrivacy.UNLISTED
145 }
146 await uploadVideo(servers[1].url, servers[1].accessToken, attributes)
147 146
148 // Server 2 has transcoding enabled 147 const attributes = {
149 await waitJobs(servers) 148 name: 'unlisted video',
150 }) 149 privacy: VideoPrivacy.UNLISTED
150 }
151 await uploadVideo(servers[1].url, servers[1].accessToken, attributes)
151 152
152 it('Should not have this unlisted video listed on server 1 and 2', async function () { 153 // Server 2 has transcoding enabled
153 for (const server of servers) { 154 await waitJobs(servers)
154 const res = await getVideosList(server.url) 155 })
155 156
156 expect(res.body.total).to.equal(0) 157 it('Should not have this unlisted video listed on server 1 and 2', async function () {
157 expect(res.body.data).to.have.lengthOf(0) 158 for (const server of servers) {
158 } 159 const res = await getVideosList(server.url)
159 })
160 160
161 it('Should list my (unlisted) videos', async function () { 161 expect(res.body.total).to.equal(0)
162 const res = await getMyVideos(servers[1].url, servers[1].accessToken, 0, 1) 162 expect(res.body.data).to.have.lengthOf(0)
163 }
164 })
163 165
164 expect(res.body.total).to.equal(1) 166 it('Should list my (unlisted) videos', async function () {
165 expect(res.body.data).to.have.lengthOf(1) 167 const res = await getMyVideos(servers[1].url, servers[1].accessToken, 0, 1)
166 168
167 unlistedVideoUUID = res.body.data[0].uuid 169 expect(res.body.total).to.equal(1)
168 }) 170 expect(res.body.data).to.have.lengthOf(1)
169 171
170 it('Should be able to get this unlisted video', async function () { 172 unlistedVideo = res.body.data[0]
171 for (const server of servers) { 173 })
172 const res = await getVideo(server.url, unlistedVideoUUID)
173 174
174 expect(res.body.name).to.equal('unlisted video') 175 it('Should not be able to get this unlisted video using its id', async function () {
175 } 176 await getVideo(servers[1].url, unlistedVideo.id, 404)
176 }) 177 })
177 178
178 it('Should upload a non-federating unlisted video to server 1', async function () { 179 it('Should be able to get this unlisted video using its uuid/shortUUID', async function () {
179 this.timeout(30000) 180 for (const server of servers) {
181 for (const id of [ unlistedVideo.uuid, unlistedVideo.shortUUID ]) {
182 const res = await getVideo(server.url, id)
180 183
181 const attributes = { 184 expect(res.body.name).to.equal('unlisted video')
182 name: 'unlisted video', 185 }
183 privacy: VideoPrivacy.UNLISTED 186 }
184 } 187 })
185 await uploadVideo(servers[0].url, servers[0].accessToken, attributes)
186 188
187 await waitJobs(servers) 189 it('Should upload a non-federating unlisted video to server 1', async function () {
188 }) 190 this.timeout(30000)
191
192 const attributes = {
193 name: 'unlisted video',
194 privacy: VideoPrivacy.UNLISTED
195 }
196 await uploadVideo(servers[0].url, servers[0].accessToken, attributes)
189 197
190 it('Should list my new unlisted video', async function () { 198 await waitJobs(servers)
191 const res = await getMyVideos(servers[0].url, servers[0].accessToken, 0, 3) 199 })
192 200
193 expect(res.body.total).to.equal(3) 201 it('Should list my new unlisted video', async function () {
194 expect(res.body.data).to.have.lengthOf(3) 202 const res = await getMyVideos(servers[0].url, servers[0].accessToken, 0, 3)
195 203
196 nonFederatedUnlistedVideoUUID = res.body.data[0].uuid 204 expect(res.body.total).to.equal(3)
197 }) 205 expect(res.body.data).to.have.lengthOf(3)
198 206
199 it('Should be able to get non-federated unlisted video from origin', async function () { 207 nonFederatedUnlistedVideoUUID = res.body.data[0].uuid
200 const res = await getVideo(servers[0].url, nonFederatedUnlistedVideoUUID) 208 })
201 209
202 expect(res.body.name).to.equal('unlisted video') 210 it('Should be able to get non-federated unlisted video from origin', async function () {
203 }) 211 const res = await getVideo(servers[0].url, nonFederatedUnlistedVideoUUID)
204 212
205 it('Should not be able to get non-federated unlisted video from federated server', async function () { 213 expect(res.body.name).to.equal('unlisted video')
206 await getVideo(servers[1].url, nonFederatedUnlistedVideoUUID, HttpStatusCode.NOT_FOUND_404) 214 })
215
216 it('Should not be able to get non-federated unlisted video from federated server', async function () {
217 await getVideo(servers[1].url, nonFederatedUnlistedVideoUUID, HttpStatusCode.NOT_FOUND_404)
218 })
207 }) 219 })
208 220
209 it('Should update the private and internal videos to public on server 1', async function () { 221 describe('Privacy update', function () {
210 this.timeout(10000)
211 222
212 now = Date.now() 223 it('Should update the private and internal videos to public on server 1', async function () {
224 this.timeout(10000)
213 225
214 { 226 now = Date.now()
215 const attribute = {
216 name: 'private video becomes public',
217 privacy: VideoPrivacy.PUBLIC
218 }
219 227
220 await updateVideo(servers[0].url, servers[0].accessToken, privateVideoId, attribute) 228 {
221 } 229 const attribute = {
230 name: 'private video becomes public',
231 privacy: VideoPrivacy.PUBLIC
232 }
222 233
223 { 234 await updateVideo(servers[0].url, servers[0].accessToken, privateVideoId, attribute)
224 const attribute = {
225 name: 'internal video becomes public',
226 privacy: VideoPrivacy.PUBLIC
227 } 235 }
228 await updateVideo(servers[0].url, servers[0].accessToken, internalVideoId, attribute)
229 }
230 236
231 await waitJobs(servers) 237 {
232 }) 238 const attribute = {
239 name: 'internal video becomes public',
240 privacy: VideoPrivacy.PUBLIC
241 }
242 await updateVideo(servers[0].url, servers[0].accessToken, internalVideoId, attribute)
243 }
233 244
234 it('Should have this new public video listed on server 1 and 2', async function () { 245 await waitJobs(servers)
235 for (const server of servers) { 246 })
236 const res = await getVideosList(server.url)
237 expect(res.body.total).to.equal(2)
238 expect(res.body.data).to.have.lengthOf(2)
239 247
240 const videos: Video[] = res.body.data 248 it('Should have this new public video listed on server 1 and 2', async function () {
241 const privateVideo = videos.find(v => v.name === 'private video becomes public') 249 for (const server of servers) {
242 const internalVideo = videos.find(v => v.name === 'internal video becomes public') 250 const res = await getVideosList(server.url)
251 expect(res.body.total).to.equal(2)
252 expect(res.body.data).to.have.lengthOf(2)
243 253
244 expect(privateVideo).to.not.be.undefined 254 const videos: Video[] = res.body.data
245 expect(internalVideo).to.not.be.undefined 255 const privateVideo = videos.find(v => v.name === 'private video becomes public')
256 const internalVideo = videos.find(v => v.name === 'internal video becomes public')
246 257
247 expect(new Date(privateVideo.publishedAt).getTime()).to.be.at.least(now) 258 expect(privateVideo).to.not.be.undefined
248 // We don't change the publish date of internal videos 259 expect(internalVideo).to.not.be.undefined
249 expect(new Date(internalVideo.publishedAt).getTime()).to.be.below(now)
250 260
251 expect(privateVideo.privacy.id).to.equal(VideoPrivacy.PUBLIC) 261 expect(new Date(privateVideo.publishedAt).getTime()).to.be.at.least(now)
252 expect(internalVideo.privacy.id).to.equal(VideoPrivacy.PUBLIC) 262 // We don't change the publish date of internal videos
253 } 263 expect(new Date(internalVideo.publishedAt).getTime()).to.be.below(now)
254 })
255 264
256 it('Should set these videos as private and internal', async function () { 265 expect(privateVideo.privacy.id).to.equal(VideoPrivacy.PUBLIC)
257 this.timeout(10000) 266 expect(internalVideo.privacy.id).to.equal(VideoPrivacy.PUBLIC)
267 }
268 })
258 269
259 await updateVideo(servers[0].url, servers[0].accessToken, internalVideoId, { privacy: VideoPrivacy.PRIVATE }) 270 it('Should set these videos as private and internal', async function () {
260 await updateVideo(servers[0].url, servers[0].accessToken, privateVideoId, { privacy: VideoPrivacy.INTERNAL }) 271 this.timeout(10000)
261 272
262 await waitJobs(servers) 273 await updateVideo(servers[0].url, servers[0].accessToken, internalVideoId, { privacy: VideoPrivacy.PRIVATE })
274 await updateVideo(servers[0].url, servers[0].accessToken, privateVideoId, { privacy: VideoPrivacy.INTERNAL })
263 275
264 for (const server of servers) { 276 await waitJobs(servers)
265 const res = await getVideosList(server.url)
266 277
267 expect(res.body.total).to.equal(0) 278 for (const server of servers) {
268 expect(res.body.data).to.have.lengthOf(0) 279 const res = await getVideosList(server.url)
269 } 280
281 expect(res.body.total).to.equal(0)
282 expect(res.body.data).to.have.lengthOf(0)
283 }
270 284
271 { 285 {
272 const res = await getMyVideos(servers[0].url, servers[0].accessToken, 0, 5) 286 const res = await getMyVideos(servers[0].url, servers[0].accessToken, 0, 5)
273 const videos = res.body.data 287 const videos = res.body.data
274 288
275 expect(res.body.total).to.equal(3) 289 expect(res.body.total).to.equal(3)
276 expect(videos).to.have.lengthOf(3) 290 expect(videos).to.have.lengthOf(3)
277 291
278 const privateVideo = videos.find(v => v.name === 'private video becomes public') 292 const privateVideo = videos.find(v => v.name === 'private video becomes public')
279 const internalVideo = videos.find(v => v.name === 'internal video becomes public') 293 const internalVideo = videos.find(v => v.name === 'internal video becomes public')
280 294
281 expect(privateVideo).to.not.be.undefined 295 expect(privateVideo).to.not.be.undefined
282 expect(internalVideo).to.not.be.undefined 296 expect(internalVideo).to.not.be.undefined
283 297
284 expect(privateVideo.privacy.id).to.equal(VideoPrivacy.INTERNAL) 298 expect(privateVideo.privacy.id).to.equal(VideoPrivacy.INTERNAL)
285 expect(internalVideo.privacy.id).to.equal(VideoPrivacy.PRIVATE) 299 expect(internalVideo.privacy.id).to.equal(VideoPrivacy.PRIVATE)
286 } 300 }
301 })
287 }) 302 })
288 303
289 after(async function () { 304 after(async function () {
diff --git a/server/tests/cli/prune-storage.ts b/server/tests/cli/prune-storage.ts
index 5c9e023e1..a0af09de8 100644
--- a/server/tests/cli/prune-storage.ts
+++ b/server/tests/cli/prune-storage.ts
@@ -2,7 +2,10 @@
2 2
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
5import { waitJobs } from '../../../shared/extra-utils/server/jobs' 5import { createFile, readdir } from 'fs-extra'
6import { join } from 'path'
7import { buildUUID } from '@server/helpers/uuid'
8import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes'
6import { 9import {
7 buildServerDirectory, 10 buildServerDirectory,
8 cleanupTests, 11 cleanupTests,
@@ -21,11 +24,8 @@ import {
21 uploadVideo, 24 uploadVideo,
22 wait 25 wait
23} from '../../../shared/extra-utils' 26} from '../../../shared/extra-utils'
27import { waitJobs } from '../../../shared/extra-utils/server/jobs'
24import { Account, VideoPlaylistPrivacy } from '../../../shared/models' 28import { Account, VideoPlaylistPrivacy } from '../../../shared/models'
25import { createFile, readdir } from 'fs-extra'
26import { v4 as uuidv4 } from 'uuid'
27import { join } from 'path'
28import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes'
29 29
30const expect = chai.expect 30const expect = chai.expect
31 31
@@ -131,8 +131,8 @@ describe('Test prune storage scripts', function () {
131 { 131 {
132 const base = buildServerDirectory(servers[0], 'videos') 132 const base = buildServerDirectory(servers[0], 'videos')
133 133
134 const n1 = uuidv4() + '.mp4' 134 const n1 = buildUUID() + '.mp4'
135 const n2 = uuidv4() + '.webm' 135 const n2 = buildUUID() + '.webm'
136 136
137 await createFile(join(base, n1)) 137 await createFile(join(base, n1))
138 await createFile(join(base, n2)) 138 await createFile(join(base, n2))
@@ -143,8 +143,8 @@ describe('Test prune storage scripts', function () {
143 { 143 {
144 const base = buildServerDirectory(servers[0], 'torrents') 144 const base = buildServerDirectory(servers[0], 'torrents')
145 145
146 const n1 = uuidv4() + '-240.torrent' 146 const n1 = buildUUID() + '-240.torrent'
147 const n2 = uuidv4() + '-480.torrent' 147 const n2 = buildUUID() + '-480.torrent'
148 148
149 await createFile(join(base, n1)) 149 await createFile(join(base, n1))
150 await createFile(join(base, n2)) 150 await createFile(join(base, n2))
@@ -155,8 +155,8 @@ describe('Test prune storage scripts', function () {
155 { 155 {
156 const base = buildServerDirectory(servers[0], 'thumbnails') 156 const base = buildServerDirectory(servers[0], 'thumbnails')
157 157
158 const n1 = uuidv4() + '.jpg' 158 const n1 = buildUUID() + '.jpg'
159 const n2 = uuidv4() + '.jpg' 159 const n2 = buildUUID() + '.jpg'
160 160
161 await createFile(join(base, n1)) 161 await createFile(join(base, n1))
162 await createFile(join(base, n2)) 162 await createFile(join(base, n2))
@@ -167,8 +167,8 @@ describe('Test prune storage scripts', function () {
167 { 167 {
168 const base = buildServerDirectory(servers[0], 'previews') 168 const base = buildServerDirectory(servers[0], 'previews')
169 169
170 const n1 = uuidv4() + '.jpg' 170 const n1 = buildUUID() + '.jpg'
171 const n2 = uuidv4() + '.jpg' 171 const n2 = buildUUID() + '.jpg'
172 172
173 await createFile(join(base, n1)) 173 await createFile(join(base, n1))
174 await createFile(join(base, n2)) 174 await createFile(join(base, n2))
@@ -179,8 +179,8 @@ describe('Test prune storage scripts', function () {
179 { 179 {
180 const base = buildServerDirectory(servers[0], 'avatars') 180 const base = buildServerDirectory(servers[0], 'avatars')
181 181
182 const n1 = uuidv4() + '.png' 182 const n1 = buildUUID() + '.png'
183 const n2 = uuidv4() + '.jpg' 183 const n2 = buildUUID() + '.jpg'
184 184
185 await createFile(join(base, n1)) 185 await createFile(join(base, n1))
186 await createFile(join(base, n2)) 186 await createFile(join(base, n2))
diff --git a/server/tests/client.ts b/server/tests/client.ts
index 253a95624..7c4fb4e46 100644
--- a/server/tests/client.ts
+++ b/server/tests/client.ts
@@ -3,9 +3,8 @@
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
5import { omit } from 'lodash' 5import { omit } from 'lodash'
6import * as request from 'supertest'
7import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes' 6import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes'
8import { Account, CustomConfig, HTMLServerConfig, ServerConfig, VideoPlaylistPrivacy } from '@shared/models' 7import { Account, CustomConfig, HTMLServerConfig, ServerConfig, VideoPlaylistCreateResult, VideoPlaylistPrivacy } from '@shared/models'
9import { 8import {
10 addVideoInPlaylist, 9 addVideoInPlaylist,
11 cleanupTests, 10 cleanupTests,
@@ -50,13 +49,16 @@ describe('Test a client controllers', function () {
50 49
51 const playlistName = 'super playlist name' 50 const playlistName = 'super playlist name'
52 const playlistDescription = 'super playlist description' 51 const playlistDescription = 'super playlist description'
53 let playlistUUID: string 52 let playlist: VideoPlaylistCreateResult
54 53
55 const channelDescription = 'my super channel description' 54 const channelDescription = 'my super channel description'
56 55
57 const watchVideoBasePaths = [ '/videos/watch/', '/w/' ] 56 const watchVideoBasePaths = [ '/videos/watch/', '/w/' ]
58 const watchPlaylistBasePaths = [ '/videos/watch/playlist/', '/w/p/' ] 57 const watchPlaylistBasePaths = [ '/videos/watch/playlist/', '/w/p/' ]
59 58
59 let videoIds: (string | number)[] = []
60 let playlistIds: (string | number)[] = []
61
60 before(async function () { 62 before(async function () {
61 this.timeout(120000) 63 this.timeout(120000)
62 64
@@ -79,7 +81,9 @@ describe('Test a client controllers', function () {
79 const videos = resVideosRequest.body.data 81 const videos = resVideosRequest.body.data
80 expect(videos.length).to.equal(1) 82 expect(videos.length).to.equal(1)
81 83
82 servers[0].video = videos[0] 84 const video = videos[0]
85 servers[0].video = video
86 videoIds = [ video.id, video.uuid, video.shortUUID ]
83 87
84 // Playlist 88 // Playlist
85 89
@@ -91,16 +95,14 @@ describe('Test a client controllers', function () {
91 } 95 }
92 96
93 const resVideoPlaylistRequest = await createVideoPlaylist({ url: servers[0].url, token: servers[0].accessToken, playlistAttrs }) 97 const resVideoPlaylistRequest = await createVideoPlaylist({ url: servers[0].url, token: servers[0].accessToken, playlistAttrs })
94 98 playlist = resVideoPlaylistRequest.body.videoPlaylist
95 const playlist = resVideoPlaylistRequest.body.videoPlaylist 99 playlistIds = [ playlist.id, playlist.shortUUID, playlist.uuid ]
96 const playlistId = playlist.id
97 playlistUUID = playlist.uuid
98 100
99 await addVideoInPlaylist({ 101 await addVideoInPlaylist({
100 url: servers[0].url, 102 url: servers[0].url,
101 token: servers[0].accessToken, 103 token: servers[0].accessToken,
102 playlistId, 104 playlistId: playlist.shortUUID,
103 elementAttrs: { videoId: servers[0].video.id } 105 elementAttrs: { videoId: video.id }
104 }) 106 })
105 107
106 // Account 108 // Account
@@ -117,36 +119,43 @@ describe('Test a client controllers', function () {
117 119
118 it('Should have valid oEmbed discovery tags for videos', async function () { 120 it('Should have valid oEmbed discovery tags for videos', async function () {
119 for (const basePath of watchVideoBasePaths) { 121 for (const basePath of watchVideoBasePaths) {
120 const path = basePath + servers[0].video.uuid 122 for (const id of videoIds) {
121 const res = await request(servers[0].url) 123 const res = await makeGetRequest({
122 .get(path) 124 url: servers[0].url,
123 .set('Accept', 'text/html') 125 path: basePath + id,
124 .expect(HttpStatusCode.OK_200) 126 accept: 'text/html',
127 statusCodeExpected: HttpStatusCode.OK_200
128 })
125 129
126 const port = servers[0].port 130 const port = servers[0].port
127 131
128 const expectedLink = '<link rel="alternate" type="application/json+oembed" href="http://localhost:' + port + '/services/oembed?' + 132 const expectedLink = '<link rel="alternate" type="application/json+oembed" href="http://localhost:' + port + '/services/oembed?' +
129 `url=http%3A%2F%2Flocalhost%3A${port}%2Fw%2F${servers[0].video.uuid}" ` + 133 `url=http%3A%2F%2Flocalhost%3A${port}%2Fw%2F${servers[0].video.uuid}" ` +
130 `title="${servers[0].video.name}" />` 134 `title="${servers[0].video.name}" />`
131 135
132 expect(res.text).to.contain(expectedLink) 136 expect(res.text).to.contain(expectedLink)
137 }
133 } 138 }
134 }) 139 })
135 140
136 it('Should have valid oEmbed discovery tags for a playlist', async function () { 141 it('Should have valid oEmbed discovery tags for a playlist', async function () {
137 for (const basePath of watchPlaylistBasePaths) { 142 for (const basePath of watchPlaylistBasePaths) {
138 const res = await request(servers[0].url) 143 for (const id of playlistIds) {
139 .get(basePath + playlistUUID) 144 const res = await makeGetRequest({
140 .set('Accept', 'text/html') 145 url: servers[0].url,
141 .expect(HttpStatusCode.OK_200) 146 path: basePath + id,
147 accept: 'text/html',
148 statusCodeExpected: HttpStatusCode.OK_200
149 })
142 150
143 const port = servers[0].port 151 const port = servers[0].port
144 152
145 const expectedLink = '<link rel="alternate" type="application/json+oembed" href="http://localhost:' + port + '/services/oembed?' + 153 const expectedLink = '<link rel="alternate" type="application/json+oembed" href="http://localhost:' + port + '/services/oembed?' +
146 `url=http%3A%2F%2Flocalhost%3A${port}%2Fw%2Fp%2F${playlistUUID}" ` + 154 `url=http%3A%2F%2Flocalhost%3A${port}%2Fw%2Fp%2F${playlist.uuid}" ` +
147 `title="${playlistName}" />` 155 `title="${playlistName}" />`
148 156
149 expect(res.text).to.contain(expectedLink) 157 expect(res.text).to.contain(expectedLink)
158 }
150 } 159 }
151 }) 160 })
152 }) 161 })
@@ -190,7 +199,7 @@ describe('Test a client controllers', function () {
190 expect(text).to.contain(`<meta property="og:title" content="${playlistName}" />`) 199 expect(text).to.contain(`<meta property="og:title" content="${playlistName}" />`)
191 expect(text).to.contain(`<meta property="og:description" content="${playlistDescription}" />`) 200 expect(text).to.contain(`<meta property="og:description" content="${playlistDescription}" />`)
192 expect(text).to.contain('<meta property="og:type" content="video" />') 201 expect(text).to.contain('<meta property="og:type" content="video" />')
193 expect(text).to.contain(`<meta property="og:url" content="${servers[0].url}/w/p/${playlistUUID}" />`) 202 expect(text).to.contain(`<meta property="og:url" content="${servers[0].url}/w/p/${playlist.uuid}" />`)
194 } 203 }
195 204
196 it('Should have valid Open Graph tags on the account page', async function () { 205 it('Should have valid Open Graph tags on the account page', async function () {
@@ -206,15 +215,19 @@ describe('Test a client controllers', function () {
206 }) 215 })
207 216
208 it('Should have valid Open Graph tags on the watch page', async function () { 217 it('Should have valid Open Graph tags on the watch page', async function () {
209 await watchVideoPageTest('/videos/watch/' + servers[0].video.id) 218 for (const path of watchVideoBasePaths) {
210 await watchVideoPageTest('/videos/watch/' + servers[0].video.uuid) 219 for (const id of videoIds) {
211 await watchVideoPageTest('/w/' + servers[0].video.uuid) 220 await watchVideoPageTest(path + id)
212 await watchVideoPageTest('/w/' + servers[0].video.id) 221 }
222 }
213 }) 223 })
214 224
215 it('Should have valid Open Graph tags on the watch playlist page', async function () { 225 it('Should have valid Open Graph tags on the watch playlist page', async function () {
216 await watchPlaylistPageTest('/videos/watch/playlist/' + playlistUUID) 226 for (const path of watchPlaylistBasePaths) {
217 await watchPlaylistPageTest('/w/p/' + playlistUUID) 227 for (const id of playlistIds) {
228 await watchPlaylistPageTest(path + id)
229 }
230 }
218 }) 231 })
219 }) 232 })
220 233
@@ -263,15 +276,19 @@ describe('Test a client controllers', function () {
263 } 276 }
264 277
265 it('Should have valid twitter card on the watch video page', async function () { 278 it('Should have valid twitter card on the watch video page', async function () {
266 await watchVideoPageTest('/videos/watch/' + servers[0].video.id) 279 for (const path of watchVideoBasePaths) {
267 await watchVideoPageTest('/videos/watch/' + servers[0].video.uuid) 280 for (const id of videoIds) {
268 await watchVideoPageTest('/w/' + servers[0].video.uuid) 281 await watchVideoPageTest(path + id)
269 await watchVideoPageTest('/w/' + servers[0].video.id) 282 }
283 }
270 }) 284 })
271 285
272 it('Should have valid twitter card on the watch playlist page', async function () { 286 it('Should have valid twitter card on the watch playlist page', async function () {
273 await watchPlaylistPageTest('/videos/watch/playlist/' + playlistUUID) 287 for (const path of watchPlaylistBasePaths) {
274 await watchPlaylistPageTest('/w/p/' + playlistUUID) 288 for (const id of playlistIds) {
289 await watchPlaylistPageTest(path + id)
290 }
291 }
275 }) 292 })
276 293
277 it('Should have valid twitter card on the account page', async function () { 294 it('Should have valid twitter card on the account page', async function () {
@@ -333,15 +350,19 @@ describe('Test a client controllers', function () {
333 } 350 }
334 351
335 it('Should have valid twitter card on the watch video page', async function () { 352 it('Should have valid twitter card on the watch video page', async function () {
336 await watchVideoPageTest('/videos/watch/' + servers[0].video.id) 353 for (const path of watchVideoBasePaths) {
337 await watchVideoPageTest('/videos/watch/' + servers[0].video.uuid) 354 for (const id of videoIds) {
338 await watchVideoPageTest('/w/' + servers[0].video.uuid) 355 await watchVideoPageTest(path + id)
339 await watchVideoPageTest('/w/' + servers[0].video.id) 356 }
357 }
340 }) 358 })
341 359
342 it('Should have valid twitter card on the watch playlist page', async function () { 360 it('Should have valid twitter card on the watch playlist page', async function () {
343 await watchPlaylistPageTest('/videos/watch/playlist/' + playlistUUID) 361 for (const path of watchPlaylistBasePaths) {
344 await watchPlaylistPageTest('/w/p/' + playlistUUID) 362 for (const id of playlistIds) {
363 await watchPlaylistPageTest(path + id)
364 }
365 }
345 }) 366 })
346 367
347 it('Should have valid twitter card on the account page', async function () { 368 it('Should have valid twitter card on the account page', async function () {
@@ -399,8 +420,10 @@ describe('Test a client controllers', function () {
399 420
400 it('Should use the original video URL for the canonical tag', async function () { 421 it('Should use the original video URL for the canonical tag', async function () {
401 for (const basePath of watchVideoBasePaths) { 422 for (const basePath of watchVideoBasePaths) {
402 const res = await makeHTMLRequest(servers[1].url, basePath + servers[0].video.uuid) 423 for (const id of videoIds) {
403 expect(res.text).to.contain(`<link rel="canonical" href="${servers[0].url}/videos/watch/${servers[0].video.uuid}" />`) 424 const res = await makeHTMLRequest(servers[1].url, basePath + id)
425 expect(res.text).to.contain(`<link rel="canonical" href="${servers[0].url}/videos/watch/${servers[0].video.uuid}" />`)
426 }
404 } 427 }
405 }) 428 })
406 429
@@ -426,8 +449,10 @@ describe('Test a client controllers', function () {
426 449
427 it('Should use the original playlist URL for the canonical tag', async function () { 450 it('Should use the original playlist URL for the canonical tag', async function () {
428 for (const basePath of watchPlaylistBasePaths) { 451 for (const basePath of watchPlaylistBasePaths) {
429 const res = await makeHTMLRequest(servers[1].url, basePath + playlistUUID) 452 for (const id of playlistIds) {
430 expect(res.text).to.contain(`<link rel="canonical" href="${servers[0].url}/video-playlists/${playlistUUID}" />`) 453 const res = await makeHTMLRequest(servers[1].url, basePath + id)
454 expect(res.text).to.contain(`<link rel="canonical" href="${servers[0].url}/video-playlists/${playlist.uuid}" />`)
455 }
431 } 456 }
432 }) 457 })
433 }) 458 })
diff --git a/server/tools/peertube-repl.ts b/server/tools/peertube-repl.ts
index 63f7667a1..eb0a776b8 100644
--- a/server/tools/peertube-repl.ts
+++ b/server/tools/peertube-repl.ts
@@ -4,7 +4,6 @@ registerTSPaths()
4import * as repl from 'repl' 4import * as repl from 'repl'
5import * as path from 'path' 5import * as path from 'path'
6import * as _ from 'lodash' 6import * as _ from 'lodash'
7import { uuidv1, uuidv3, uuidv4, uuidv5 } from 'uuid'
8import * as Sequelize from 'sequelize' 7import * as Sequelize from 'sequelize'
9import * as YoutubeDL from 'youtube-dl' 8import * as YoutubeDL from 'youtube-dl'
10import { initDatabaseModels, sequelizeTypescript } from '../initializers/database' 9import { initDatabaseModels, sequelizeTypescript } from '../initializers/database'
@@ -31,10 +30,6 @@ const start = async () => {
31 env: process.env, 30 env: process.env,
32 lodash: _, 31 lodash: _,
33 path, 32 path,
34 uuidv1,
35 uuidv3,
36 uuidv4,
37 uuidv5,
38 cli, 33 cli,
39 logger, 34 logger,
40 constants, 35 constants,
diff --git a/shared/extra-utils/server/servers.ts b/shared/extra-utils/server/servers.ts
index d04757470..28e431e94 100644
--- a/shared/extra-utils/server/servers.ts
+++ b/shared/extra-utils/server/servers.ts
@@ -43,6 +43,7 @@ interface ServerInfo {
43 video?: { 43 video?: {
44 id: number 44 id: number
45 uuid: string 45 uuid: string
46 shortUUID: string
46 name?: string 47 name?: string
47 url?: string 48 url?: string
48 49
diff --git a/shared/extra-utils/videos/videos.ts b/shared/extra-utils/videos/videos.ts
index a3a276188..469ea4d63 100644
--- a/shared/extra-utils/videos/videos.ts
+++ b/shared/extra-utils/videos/videos.ts
@@ -6,9 +6,9 @@ import got, { Response as GotResponse } from 'got/dist/source'
6import * as parseTorrent from 'parse-torrent' 6import * as parseTorrent from 'parse-torrent'
7import { join } from 'path' 7import { join } from 'path'
8import * as request from 'supertest' 8import * as request from 'supertest'
9import { v4 as uuidv4 } from 'uuid'
10import validator from 'validator' 9import validator from 'validator'
11import { getLowercaseExtension } from '@server/helpers/core-utils' 10import { getLowercaseExtension } from '@server/helpers/core-utils'
11import { buildUUID } from '@server/helpers/uuid'
12import { HttpStatusCode } from '@shared/core-utils' 12import { HttpStatusCode } from '@shared/core-utils'
13import { VideosCommonQuery } from '@shared/models' 13import { VideosCommonQuery } from '@shared/models'
14import { loadLanguages, VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES, VIDEO_PRIVACIES } from '../../../server/initializers/constants' 14import { loadLanguages, VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES, VIDEO_PRIVACIES } from '../../../server/initializers/constants'
@@ -806,7 +806,7 @@ async function uploadVideoAndGetId (options: {
806 806
807 const res = await uploadVideo(options.server.url, options.token || options.server.accessToken, videoAttrs) 807 const res = await uploadVideo(options.server.url, options.token || options.server.accessToken, videoAttrs)
808 808
809 return { id: res.body.video.id, uuid: res.body.video.uuid } 809 return res.body.video as { id: number, uuid: string, shortUUID: string }
810} 810}
811 811
812async function getLocalIdByUUID (url: string, uuid: string) { 812async function getLocalIdByUUID (url: string, uuid: string) {
@@ -827,7 +827,7 @@ async function uploadRandomVideoOnServers (servers: ServerInfo[], serverNumber:
827 827
828async function uploadRandomVideo (server: ServerInfo, wait = true, additionalParams: any = {}) { 828async function uploadRandomVideo (server: ServerInfo, wait = true, additionalParams: any = {}) {
829 const prefixName = additionalParams.prefixName || '' 829 const prefixName = additionalParams.prefixName || ''
830 const name = prefixName + uuidv4() 830 const name = prefixName + buildUUID()
831 831
832 const data = Object.assign({ name }, additionalParams) 832 const data = Object.assign({ name }, additionalParams)
833 const res = await uploadVideo(server.url, server.accessToken, data) 833 const res = await uploadVideo(server.url, server.accessToken, data)
diff --git a/shared/models/common/index.ts b/shared/models/common/index.ts
new file mode 100644
index 000000000..4db85eff2
--- /dev/null
+++ b/shared/models/common/index.ts
@@ -0,0 +1 @@
export * from './result-list.model'
diff --git a/shared/models/result-list.model.ts b/shared/models/common/result-list.model.ts
index fcafcfb2f..fcafcfb2f 100644
--- a/shared/models/result-list.model.ts
+++ b/shared/models/common/result-list.model.ts
diff --git a/shared/models/index.ts b/shared/models/index.ts
index 4db1f234e..5c2bc480e 100644
--- a/shared/models/index.ts
+++ b/shared/models/index.ts
@@ -1,16 +1,16 @@
1export * from './activitypub' 1export * from './activitypub'
2export * from './actors' 2export * from './actors'
3export * from './moderation'
4export * from './custom-markup'
5export * from './bulk' 3export * from './bulk'
6export * from './redundancy' 4export * from './common'
7export * from './users' 5export * from './custom-markup'
8export * from './videos'
9export * from './feeds' 6export * from './feeds'
10export * from './joinpeertube' 7export * from './joinpeertube'
8export * from './moderation'
11export * from './overviews' 9export * from './overviews'
12export * from './plugins' 10export * from './plugins'
11export * from './redundancy'
13export * from './search' 12export * from './search'
14export * from './server' 13export * from './server'
15export * from './oauth-client-local.model' 14export * from './tokens'
16export * from './result-list.model' 15export * from './users'
16export * from './videos'
diff --git a/shared/models/moderation/abuse/abuse-create.model.ts b/shared/models/moderation/abuse/abuse-create.model.ts
index 0e7e9587f..7d35555c3 100644
--- a/shared/models/moderation/abuse/abuse-create.model.ts
+++ b/shared/models/moderation/abuse/abuse-create.model.ts
@@ -10,7 +10,7 @@ export interface AbuseCreate {
10 } 10 }
11 11
12 video?: { 12 video?: {
13 id: number 13 id: number | string
14 startAt?: number 14 startAt?: number
15 endAt?: number 15 endAt?: number
16 } 16 }
diff --git a/shared/models/tokens/index.ts b/shared/models/tokens/index.ts
new file mode 100644
index 000000000..fe130f153
--- /dev/null
+++ b/shared/models/tokens/index.ts
@@ -0,0 +1 @@
export * from './oauth-client-local.model'
diff --git a/shared/models/oauth-client-local.model.ts b/shared/models/tokens/oauth-client-local.model.ts
index 0c6ce6c5d..0c6ce6c5d 100644
--- a/shared/models/oauth-client-local.model.ts
+++ b/shared/models/tokens/oauth-client-local.model.ts
diff --git a/shared/models/videos/index.ts b/shared/models/videos/index.ts
index 64f2c9df6..faa9b9868 100644
--- a/shared/models/videos/index.ts
+++ b/shared/models/videos/index.ts
@@ -35,3 +35,4 @@ export * from './video-transcoding-fps.model'
35 35
36export * from './video-update.model' 36export * from './video-update.model'
37export * from './video.model' 37export * from './video.model'
38export * from './video-create-result.model'
diff --git a/shared/models/videos/playlist/index.ts b/shared/models/videos/playlist/index.ts
index 99f7e9bab..f11a4bd28 100644
--- a/shared/models/videos/playlist/index.ts
+++ b/shared/models/videos/playlist/index.ts
@@ -1,4 +1,5 @@
1export * from './video-exist-in-playlist.model' 1export * from './video-exist-in-playlist.model'
2export * from './video-playlist-create-result.model'
2export * from './video-playlist-create.model' 3export * from './video-playlist-create.model'
3export * from './video-playlist-element-create.model' 4export * from './video-playlist-element-create.model'
4export * from './video-playlist-element-update.model' 5export * from './video-playlist-element-update.model'
diff --git a/shared/models/videos/playlist/video-playlist-create-result.model.ts b/shared/models/videos/playlist/video-playlist-create-result.model.ts
new file mode 100644
index 000000000..cd9b170ae
--- /dev/null
+++ b/shared/models/videos/playlist/video-playlist-create-result.model.ts
@@ -0,0 +1,5 @@
1export interface VideoPlaylistCreateResult {
2 id: number
3 uuid: string
4 shortUUID: string
5}
diff --git a/shared/models/videos/playlist/video-playlist.model.ts b/shared/models/videos/playlist/video-playlist.model.ts
index ab4171ad1..b8a9955d9 100644
--- a/shared/models/videos/playlist/video-playlist.model.ts
+++ b/shared/models/videos/playlist/video-playlist.model.ts
@@ -6,6 +6,8 @@ import { VideoPlaylistType } from './video-playlist-type.model'
6export interface VideoPlaylist { 6export interface VideoPlaylist {
7 id: number 7 id: number
8 uuid: string 8 uuid: string
9 shortUUID: string
10
9 isLocal: boolean 11 isLocal: boolean
10 12
11 url: string 13 url: string
diff --git a/shared/models/videos/video-create-result.model.ts b/shared/models/videos/video-create-result.model.ts
new file mode 100644
index 000000000..a9f8e25a0
--- /dev/null
+++ b/shared/models/videos/video-create-result.model.ts
@@ -0,0 +1,5 @@
1export interface VideoCreateResult {
2 id: number
3 uuid: string
4 shortUUID: string
5}
diff --git a/shared/models/videos/video.model.ts b/shared/models/videos/video.model.ts
index caefeff82..0e3e89f43 100644
--- a/shared/models/videos/video.model.ts
+++ b/shared/models/videos/video.model.ts
@@ -10,6 +10,8 @@ import { VideoStreamingPlaylist } from './video-streaming-playlist.model'
10export interface Video { 10export interface Video {
11 id: number 11 id: number
12 uuid: string 12 uuid: string
13 shortUUID: string
14
13 createdAt: Date | string 15 createdAt: Date | string
14 updatedAt: Date | string 16 updatedAt: Date | string
15 publishedAt: Date | string 17 publishedAt: Date | string
diff --git a/support/doc/api/openapi.yaml b/support/doc/api/openapi.yaml
index 919905788..1f9f3d5c4 100644
--- a/support/doc/api/openapi.yaml
+++ b/support/doc/api/openapi.yaml
@@ -3003,6 +3003,8 @@ paths:
3003 $ref: '#/components/schemas/VideoPlaylist/properties/id' 3003 $ref: '#/components/schemas/VideoPlaylist/properties/id'
3004 uuid: 3004 uuid:
3005 $ref: '#/components/schemas/VideoPlaylist/properties/uuid' 3005 $ref: '#/components/schemas/VideoPlaylist/properties/uuid'
3006 shortUUID:
3007 $ref: '#/components/schemas/VideoPlaylist/properties/shortUUID'
3006 requestBody: 3008 requestBody:
3007 content: 3009 content:
3008 multipart/form-data: 3010 multipart/form-data:
@@ -4543,11 +4545,12 @@ components:
4543 name: id 4545 name: id
4544 in: path 4546 in: path
4545 required: true 4547 required: true
4546 description: The object id or uuid 4548 description: The object id, uuid or short uuid
4547 schema: 4549 schema:
4548 oneOf: 4550 oneOf:
4549 - $ref: '#/components/schemas/id' 4551 - $ref: '#/components/schemas/id'
4550 - $ref: '#/components/schemas/UUIDv4' 4552 - $ref: '#/components/schemas/UUIDv4'
4553 - $ref: '#/components/schemas/shortUUID'
4551 playlistId: 4554 playlistId:
4552 name: playlistId 4555 name: playlistId
4553 in: path 4556 in: path
@@ -4812,6 +4815,10 @@ components:
4812 pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$' 4815 pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$'
4813 minLength: 36 4816 minLength: 36
4814 maxLength: 36 4817 maxLength: 36
4818 shortUUID:
4819 type: string
4820 description: translation of a uuid v4 with a bigger alphabet to have a shorter uuid
4821 example: 2y84q2MQUMWPbiEcxNXMgC
4815 username: 4822 username:
4816 type: string 4823 type: string
4817 description: immutable name of the user, used to find or mention its actor 4824 description: immutable name of the user, used to find or mention its actor
@@ -5141,6 +5148,9 @@ components:
5141 description: universal identifier for the video, that can be used across instances 5148 description: universal identifier for the video, that can be used across instances
5142 allOf: 5149 allOf:
5143 - $ref: '#/components/schemas/UUIDv4' 5150 - $ref: '#/components/schemas/UUIDv4'
5151 shortUUID:
5152 allOf:
5153 - $ref: '#/components/schemas/shortUUID'
5144 isLive: 5154 isLive:
5145 type: boolean 5155 type: boolean
5146 createdAt: 5156 createdAt:
@@ -5520,6 +5530,9 @@ components:
5520 $ref: '#/components/schemas/id' 5530 $ref: '#/components/schemas/id'
5521 uuid: 5531 uuid:
5522 $ref: '#/components/schemas/UUIDv4' 5532 $ref: '#/components/schemas/UUIDv4'
5533 shortUUID:
5534 allOf:
5535 - $ref: '#/components/schemas/shortUUID'
5523 createdAt: 5536 createdAt:
5524 type: string 5537 type: string
5525 format: date-time 5538 format: date-time
@@ -6295,6 +6308,8 @@ components:
6295 $ref: '#/components/schemas/Video/properties/id' 6308 $ref: '#/components/schemas/Video/properties/id'
6296 uuid: 6309 uuid:
6297 $ref: '#/components/schemas/Video/properties/uuid' 6310 $ref: '#/components/schemas/Video/properties/uuid'
6311 shortUUID:
6312 $ref: '#/components/schemas/Video/properties/shortUUID'
6298 CommentThreadResponse: 6313 CommentThreadResponse:
6299 properties: 6314 properties:
6300 total: 6315 total:
diff --git a/yarn.lock b/yarn.lock
index 05295ea42..f68741038 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -7260,6 +7260,14 @@ shebang-regex@^3.0.0:
7260 resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" 7260 resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172"
7261 integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== 7261 integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==
7262 7262
7263short-uuid@^4.2.0:
7264 version "4.2.0"
7265 resolved "https://registry.yarnpkg.com/short-uuid/-/short-uuid-4.2.0.tgz#3706d9e7287ac589dc5ffe324d3e34817a07540b"
7266 integrity sha512-r3cxuPPZSuF0QkKsK9bBR7u+7cwuCRzWzgjPh07F5N2iIUNgblnMHepBY16xgj5t1lG9iOP9k/TEafY1qhRzaw==
7267 dependencies:
7268 any-base "^1.1.0"
7269 uuid "^8.3.2"
7270
7263side-channel@^1.0.4: 7271side-channel@^1.0.4:
7264 version "1.0.4" 7272 version "1.0.4"
7265 resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" 7273 resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf"