diff options
author | Chocobozzz <me@florianbigard.com> | 2020-08-17 16:39:32 +0200 |
---|---|---|
committer | Chocobozzz <chocobozzz@cpy.re> | 2020-08-19 11:30:21 +0200 |
commit | 371906639ee9b6ea4daae504bc7c2b15856c3f38 (patch) | |
tree | a1807d7e5648a8b99565cc22daa22a1afbb34db4 | |
parent | b75410b87d889ae5b1704cc7f9d32a3792db9075 (diff) | |
download | PeerTube-371906639ee9b6ea4daae504bc7c2b15856c3f38.tar.gz PeerTube-371906639ee9b6ea4daae504bc7c2b15856c3f38.tar.zst PeerTube-371906639ee9b6ea4daae504bc7c2b15856c3f38.zip |
Add ability to a video multiple times in a playlist
-rw-r--r-- | server/controllers/activitypub/client.ts | 2 | ||||
-rw-r--r-- | server/controllers/api/video-playlist.ts | 4 | ||||
-rw-r--r-- | server/lib/activitypub/url.ts | 7 | ||||
-rw-r--r-- | server/middlewares/validators/videos/video-playlists.ts | 19 | ||||
-rw-r--r-- | server/models/video/video-playlist-element.ts | 21 | ||||
-rw-r--r-- | server/tests/api/check-params/video-playlists.ts | 5 | ||||
-rw-r--r-- | server/tests/api/videos/video-playlists.ts | 35 |
7 files changed, 53 insertions, 40 deletions
diff --git a/server/controllers/activitypub/client.ts b/server/controllers/activitypub/client.ts index acce53713..1da44d096 100644 --- a/server/controllers/activitypub/client.ts +++ b/server/controllers/activitypub/client.ts | |||
@@ -159,7 +159,7 @@ activityPubClientRouter.get('/video-playlists/:playlistId', | |||
159 | asyncMiddleware(videoPlaylistsGetValidator('all')), | 159 | asyncMiddleware(videoPlaylistsGetValidator('all')), |
160 | asyncMiddleware(videoPlaylistController) | 160 | asyncMiddleware(videoPlaylistController) |
161 | ) | 161 | ) |
162 | activityPubClientRouter.get('/video-playlists/:playlistId/:videoId', | 162 | activityPubClientRouter.get('/video-playlists/:playlistId/videos/:playlistElementId', |
163 | executeIfActivityPub, | 163 | executeIfActivityPub, |
164 | asyncMiddleware(videoPlaylistElementAPGetValidator), | 164 | asyncMiddleware(videoPlaylistElementAPGetValidator), |
165 | videoPlaylistElementController | 165 | videoPlaylistElementController |
diff --git a/server/controllers/api/video-playlist.ts b/server/controllers/api/video-playlist.ts index 88a2314fb..41a0e07ff 100644 --- a/server/controllers/api/video-playlist.ts +++ b/server/controllers/api/video-playlist.ts | |||
@@ -297,7 +297,6 @@ async function addVideoInPlaylist (req: express.Request, res: express.Response) | |||
297 | const position = await VideoPlaylistElementModel.getNextPositionOf(videoPlaylist.id, t) | 297 | const position = await VideoPlaylistElementModel.getNextPositionOf(videoPlaylist.id, t) |
298 | 298 | ||
299 | const playlistElement = await VideoPlaylistElementModel.create({ | 299 | const playlistElement = await VideoPlaylistElementModel.create({ |
300 | url: getVideoPlaylistElementActivityPubUrl(videoPlaylist, video), | ||
301 | position, | 300 | position, |
302 | startTimestamp: body.startTimestamp || null, | 301 | startTimestamp: body.startTimestamp || null, |
303 | stopTimestamp: body.stopTimestamp || null, | 302 | stopTimestamp: body.stopTimestamp || null, |
@@ -305,6 +304,9 @@ async function addVideoInPlaylist (req: express.Request, res: express.Response) | |||
305 | videoId: video.id | 304 | videoId: video.id |
306 | }, { transaction: t }) | 305 | }, { transaction: t }) |
307 | 306 | ||
307 | playlistElement.url = getVideoPlaylistElementActivityPubUrl(videoPlaylist, playlistElement) | ||
308 | await playlistElement.save({ transaction: t }) | ||
309 | |||
308 | videoPlaylist.changed('updatedAt', true) | 310 | videoPlaylist.changed('updatedAt', true) |
309 | await videoPlaylist.save({ transaction: t }) | 311 | await videoPlaylist.save({ transaction: t }) |
310 | 312 | ||
diff --git a/server/lib/activitypub/url.ts b/server/lib/activitypub/url.ts index b54e038a4..58030be2c 100644 --- a/server/lib/activitypub/url.ts +++ b/server/lib/activitypub/url.ts | |||
@@ -8,7 +8,8 @@ import { | |||
8 | MVideoId, | 8 | MVideoId, |
9 | MVideoUrl, | 9 | MVideoUrl, |
10 | MVideoUUID, | 10 | MVideoUUID, |
11 | MAbuseId | 11 | MAbuseId, |
12 | MVideoPlaylistElement | ||
12 | } from '../../types/models' | 13 | } from '../../types/models' |
13 | import { MVideoPlaylist, MVideoPlaylistUUID } from '../../types/models/video/video-playlist' | 14 | import { MVideoPlaylist, MVideoPlaylistUUID } from '../../types/models/video/video-playlist' |
14 | import { MVideoFileVideoUUID } from '../../types/models/video/video-file' | 15 | import { MVideoFileVideoUUID } from '../../types/models/video/video-file' |
@@ -22,8 +23,8 @@ function getVideoPlaylistActivityPubUrl (videoPlaylist: MVideoPlaylist) { | |||
22 | return WEBSERVER.URL + '/video-playlists/' + videoPlaylist.uuid | 23 | return WEBSERVER.URL + '/video-playlists/' + videoPlaylist.uuid |
23 | } | 24 | } |
24 | 25 | ||
25 | function getVideoPlaylistElementActivityPubUrl (videoPlaylist: MVideoPlaylistUUID, video: MVideoUUID) { | 26 | function getVideoPlaylistElementActivityPubUrl (videoPlaylist: MVideoPlaylistUUID, videoPlaylistElement: MVideoPlaylistElement) { |
26 | return WEBSERVER.URL + '/video-playlists/' + videoPlaylist.uuid + '/' + video.uuid | 27 | return WEBSERVER.URL + '/video-playlists/' + videoPlaylist.uuid + '/videos/' + videoPlaylistElement.id |
27 | } | 28 | } |
28 | 29 | ||
29 | function getVideoCacheFileActivityPubUrl (videoFile: MVideoFileVideoUUID) { | 30 | function getVideoCacheFileActivityPubUrl (videoFile: MVideoFileVideoUUID) { |
diff --git a/server/middlewares/validators/videos/video-playlists.ts b/server/middlewares/validators/videos/video-playlists.ts index 07fd8533c..4647eae44 100644 --- a/server/middlewares/validators/videos/video-playlists.ts +++ b/server/middlewares/validators/videos/video-playlists.ts | |||
@@ -199,16 +199,6 @@ const videoPlaylistsAddVideoValidator = [ | |||
199 | if (!await doesVideoExist(req.body.videoId, res, 'only-video')) return | 199 | if (!await doesVideoExist(req.body.videoId, res, 'only-video')) return |
200 | 200 | ||
201 | const videoPlaylist = getPlaylist(res) | 201 | const videoPlaylist = getPlaylist(res) |
202 | const video = res.locals.onlyVideo | ||
203 | |||
204 | const videoPlaylistElement = await VideoPlaylistElementModel.loadByPlaylistAndVideo(videoPlaylist.id, video.id) | ||
205 | if (videoPlaylistElement) { | ||
206 | res.status(409) | ||
207 | .json({ error: 'This video in this playlist already exists' }) | ||
208 | .end() | ||
209 | |||
210 | return | ||
211 | } | ||
212 | 202 | ||
213 | if (!checkUserCanManageVideoPlaylist(res.locals.oauth.token.User, videoPlaylist, UserRight.UPDATE_ANY_VIDEO_PLAYLIST, res)) { | 203 | if (!checkUserCanManageVideoPlaylist(res.locals.oauth.token.User, videoPlaylist, UserRight.UPDATE_ANY_VIDEO_PLAYLIST, res)) { |
214 | return | 204 | return |
@@ -258,15 +248,18 @@ const videoPlaylistsUpdateOrRemoveVideoValidator = [ | |||
258 | const videoPlaylistElementAPGetValidator = [ | 248 | const videoPlaylistElementAPGetValidator = [ |
259 | param('playlistId') | 249 | param('playlistId') |
260 | .custom(isIdOrUUIDValid).withMessage('Should have a valid playlist id/uuid'), | 250 | .custom(isIdOrUUIDValid).withMessage('Should have a valid playlist id/uuid'), |
261 | param('videoId') | 251 | param('playlistElementId') |
262 | .custom(isIdOrUUIDValid).withMessage('Should have an video id/uuid'), | 252 | .custom(isIdValid).withMessage('Should have an playlist element id'), |
263 | 253 | ||
264 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | 254 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { |
265 | logger.debug('Checking videoPlaylistElementAPGetValidator parameters', { parameters: req.params }) | 255 | logger.debug('Checking videoPlaylistElementAPGetValidator parameters', { parameters: req.params }) |
266 | 256 | ||
267 | if (areValidationErrors(req, res)) return | 257 | if (areValidationErrors(req, res)) return |
268 | 258 | ||
269 | const videoPlaylistElement = await VideoPlaylistElementModel.loadByPlaylistAndVideoForAP(req.params.playlistId, req.params.videoId) | 259 | const playlistElementId = parseInt(req.params.playlistElementId + '', 10) |
260 | const playlistId = req.params.playlistId | ||
261 | |||
262 | const videoPlaylistElement = await VideoPlaylistElementModel.loadByPlaylistAndElementIdForAP(playlistId, playlistElementId) | ||
270 | if (!videoPlaylistElement) { | 263 | if (!videoPlaylistElement) { |
271 | res.status(404) | 264 | res.status(404) |
272 | .json({ error: 'Video playlist element not found' }) | 265 | .json({ error: 'Video playlist element not found' }) |
diff --git a/server/models/video/video-playlist-element.ts b/server/models/video/video-playlist-element.ts index ba92e129a..d357766e9 100644 --- a/server/models/video/video-playlist-element.ts +++ b/server/models/video/video-playlist-element.ts | |||
@@ -44,10 +44,6 @@ import { MUserAccountId } from '@server/types/models' | |||
44 | fields: [ 'videoId' ] | 44 | fields: [ 'videoId' ] |
45 | }, | 45 | }, |
46 | { | 46 | { |
47 | fields: [ 'videoPlaylistId', 'videoId' ], | ||
48 | unique: true | ||
49 | }, | ||
50 | { | ||
51 | fields: [ 'url' ], | 47 | fields: [ 'url' ], |
52 | unique: true | 48 | unique: true |
53 | } | 49 | } |
@@ -60,8 +56,8 @@ export class VideoPlaylistElementModel extends Model<VideoPlaylistElementModel> | |||
60 | @UpdatedAt | 56 | @UpdatedAt |
61 | updatedAt: Date | 57 | updatedAt: Date |
62 | 58 | ||
63 | @AllowNull(false) | 59 | @AllowNull(true) |
64 | @Is('VideoPlaylistUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'url')) | 60 | @Is('VideoPlaylistUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'url', true)) |
65 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEO_PLAYLISTS.URL.max)) | 61 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEO_PLAYLISTS.URL.max)) |
66 | url: string | 62 | url: string |
67 | 63 | ||
@@ -185,12 +181,11 @@ export class VideoPlaylistElementModel extends Model<VideoPlaylistElementModel> | |||
185 | return VideoPlaylistElementModel.findByPk(playlistElementId) | 181 | return VideoPlaylistElementModel.findByPk(playlistElementId) |
186 | } | 182 | } |
187 | 183 | ||
188 | static loadByPlaylistAndVideoForAP ( | 184 | static loadByPlaylistAndElementIdForAP ( |
189 | playlistId: number | string, | 185 | playlistId: number | string, |
190 | videoId: number | string | 186 | playlistElementId: number |
191 | ): Bluebird<MVideoPlaylistElementVideoUrlPlaylistPrivacy> { | 187 | ): Bluebird<MVideoPlaylistElementVideoUrlPlaylistPrivacy> { |
192 | const playlistWhere = validator.isUUID('' + playlistId) ? { uuid: playlistId } : { id: playlistId } | 188 | const playlistWhere = validator.isUUID('' + playlistId) ? { uuid: playlistId } : { id: playlistId } |
193 | const videoWhere = validator.isUUID('' + videoId) ? { uuid: videoId } : { id: videoId } | ||
194 | 189 | ||
195 | const query = { | 190 | const query = { |
196 | include: [ | 191 | include: [ |
@@ -201,10 +196,12 @@ export class VideoPlaylistElementModel extends Model<VideoPlaylistElementModel> | |||
201 | }, | 196 | }, |
202 | { | 197 | { |
203 | attributes: [ 'url' ], | 198 | attributes: [ 'url' ], |
204 | model: VideoModel.unscoped(), | 199 | model: VideoModel.unscoped() |
205 | where: videoWhere | ||
206 | } | 200 | } |
207 | ] | 201 | ], |
202 | where: { | ||
203 | id: playlistElementId | ||
204 | } | ||
208 | } | 205 | } |
209 | 206 | ||
210 | return VideoPlaylistElementModel.findOne(query) | 207 | return VideoPlaylistElementModel.findOne(query) |
diff --git a/server/tests/api/check-params/video-playlists.ts b/server/tests/api/check-params/video-playlists.ts index 46ec00d46..179ae9201 100644 --- a/server/tests/api/check-params/video-playlists.ts +++ b/server/tests/api/check-params/video-playlists.ts | |||
@@ -346,11 +346,6 @@ describe('Test video playlists API validator', function () { | |||
346 | const res = await addVideoInPlaylist(params) | 346 | const res = await addVideoInPlaylist(params) |
347 | playlistElementId = res.body.videoPlaylistElement.id | 347 | playlistElementId = res.body.videoPlaylistElement.id |
348 | }) | 348 | }) |
349 | |||
350 | it('Should fail if the video was already added in the playlist', async function () { | ||
351 | const params = getBase({}, { expectedStatus: 409 }) | ||
352 | await addVideoInPlaylist(params) | ||
353 | }) | ||
354 | }) | 349 | }) |
355 | 350 | ||
356 | describe('When updating an element in a playlist', function () { | 351 | describe('When updating an element in a playlist', function () { |
diff --git a/server/tests/api/videos/video-playlists.ts b/server/tests/api/videos/video-playlists.ts index 52b32998d..0bfb5bcd4 100644 --- a/server/tests/api/videos/video-playlists.ts +++ b/server/tests/api/videos/video-playlists.ts | |||
@@ -552,6 +552,9 @@ describe('Test video playlists', function () { | |||
552 | { | 552 | { |
553 | const res = await addVideo({ videoId: nsfwVideoServer1, startTimestamp: 5 }) | 553 | const res = await addVideo({ videoId: nsfwVideoServer1, startTimestamp: 5 }) |
554 | playlistElementNSFW = res.body.videoPlaylistElement.id | 554 | playlistElementNSFW = res.body.videoPlaylistElement.id |
555 | |||
556 | await addVideo({ videoId: nsfwVideoServer1, startTimestamp: 4 }) | ||
557 | await addVideo({ videoId: nsfwVideoServer1 }) | ||
555 | } | 558 | } |
556 | 559 | ||
557 | await waitJobs(servers) | 560 | await waitJobs(servers) |
@@ -563,10 +566,10 @@ describe('Test video playlists', function () { | |||
563 | for (const server of servers) { | 566 | for (const server of servers) { |
564 | const res = await getPlaylistVideos(server.url, server.accessToken, playlistServer1UUID, 0, 10) | 567 | const res = await getPlaylistVideos(server.url, server.accessToken, playlistServer1UUID, 0, 10) |
565 | 568 | ||
566 | expect(res.body.total).to.equal(6) | 569 | expect(res.body.total).to.equal(8) |
567 | 570 | ||
568 | const videoElements: VideoPlaylistElement[] = res.body.data | 571 | const videoElements: VideoPlaylistElement[] = res.body.data |
569 | expect(videoElements).to.have.lengthOf(6) | 572 | expect(videoElements).to.have.lengthOf(8) |
570 | 573 | ||
571 | expect(videoElements[0].video.name).to.equal('video 0 server 1') | 574 | expect(videoElements[0].video.name).to.equal('video 0 server 1') |
572 | expect(videoElements[0].position).to.equal(1) | 575 | expect(videoElements[0].position).to.equal(1) |
@@ -598,6 +601,16 @@ describe('Test video playlists', function () { | |||
598 | expect(videoElements[5].startTimestamp).to.equal(5) | 601 | expect(videoElements[5].startTimestamp).to.equal(5) |
599 | expect(videoElements[5].stopTimestamp).to.be.null | 602 | expect(videoElements[5].stopTimestamp).to.be.null |
600 | 603 | ||
604 | expect(videoElements[6].video.name).to.equal('NSFW video') | ||
605 | expect(videoElements[6].position).to.equal(7) | ||
606 | expect(videoElements[6].startTimestamp).to.equal(4) | ||
607 | expect(videoElements[6].stopTimestamp).to.be.null | ||
608 | |||
609 | expect(videoElements[7].video.name).to.equal('NSFW video') | ||
610 | expect(videoElements[7].position).to.equal(8) | ||
611 | expect(videoElements[7].startTimestamp).to.be.null | ||
612 | expect(videoElements[7].stopTimestamp).to.be.null | ||
613 | |||
601 | const res3 = await getPlaylistVideos(server.url, server.accessToken, playlistServer1UUID, 0, 2) | 614 | const res3 = await getPlaylistVideos(server.url, server.accessToken, playlistServer1UUID, 0, 2) |
602 | expect(res3.body.data).to.have.lengthOf(2) | 615 | expect(res3.body.data).to.have.lengthOf(2) |
603 | } | 616 | } |
@@ -807,6 +820,8 @@ describe('Test video playlists', function () { | |||
807 | 'video 1 server 3', | 820 | 'video 1 server 3', |
808 | 'video 3 server 1', | 821 | 'video 3 server 1', |
809 | 'video 4 server 1', | 822 | 'video 4 server 1', |
823 | 'NSFW video', | ||
824 | 'NSFW video', | ||
810 | 'NSFW video' | 825 | 'NSFW video' |
811 | ]) | 826 | ]) |
812 | } | 827 | } |
@@ -836,6 +851,8 @@ describe('Test video playlists', function () { | |||
836 | 'video 2 server 3', | 851 | 'video 2 server 3', |
837 | 'video 1 server 3', | 852 | 'video 1 server 3', |
838 | 'video 4 server 1', | 853 | 'video 4 server 1', |
854 | 'NSFW video', | ||
855 | 'NSFW video', | ||
839 | 'NSFW video' | 856 | 'NSFW video' |
840 | ]) | 857 | ]) |
841 | } | 858 | } |
@@ -865,7 +882,9 @@ describe('Test video playlists', function () { | |||
865 | 'video 2 server 3', | 882 | 'video 2 server 3', |
866 | 'NSFW video', | 883 | 'NSFW video', |
867 | 'video 1 server 3', | 884 | 'video 1 server 3', |
868 | 'video 4 server 1' | 885 | 'video 4 server 1', |
886 | 'NSFW video', | ||
887 | 'NSFW video' | ||
869 | ]) | 888 | ]) |
870 | 889 | ||
871 | for (let i = 1; i <= elements.length; i++) { | 890 | for (let i = 1; i <= elements.length; i++) { |
@@ -1023,10 +1042,10 @@ describe('Test video playlists', function () { | |||
1023 | for (const server of servers) { | 1042 | for (const server of servers) { |
1024 | const res = await getPlaylistVideos(server.url, server.accessToken, playlistServer1UUID, 0, 10) | 1043 | const res = await getPlaylistVideos(server.url, server.accessToken, playlistServer1UUID, 0, 10) |
1025 | 1044 | ||
1026 | expect(res.body.total).to.equal(4) | 1045 | expect(res.body.total).to.equal(6) |
1027 | 1046 | ||
1028 | const elements: VideoPlaylistElement[] = res.body.data | 1047 | const elements: VideoPlaylistElement[] = res.body.data |
1029 | expect(elements).to.have.lengthOf(4) | 1048 | expect(elements).to.have.lengthOf(6) |
1030 | 1049 | ||
1031 | expect(elements[0].video.name).to.equal('video 0 server 1') | 1050 | expect(elements[0].video.name).to.equal('video 0 server 1') |
1032 | expect(elements[0].position).to.equal(1) | 1051 | expect(elements[0].position).to.equal(1) |
@@ -1039,6 +1058,12 @@ describe('Test video playlists', function () { | |||
1039 | 1058 | ||
1040 | expect(elements[3].video.name).to.equal('video 4 server 1') | 1059 | expect(elements[3].video.name).to.equal('video 4 server 1') |
1041 | expect(elements[3].position).to.equal(4) | 1060 | expect(elements[3].position).to.equal(4) |
1061 | |||
1062 | expect(elements[4].video.name).to.equal('NSFW video') | ||
1063 | expect(elements[4].position).to.equal(5) | ||
1064 | |||
1065 | expect(elements[5].video.name).to.equal('NSFW video') | ||
1066 | expect(elements[5].position).to.equal(6) | ||
1042 | } | 1067 | } |
1043 | }) | 1068 | }) |
1044 | 1069 | ||