diff options
Diffstat (limited to 'server')
-rw-r--r-- | server/controllers/api/users/index.ts | 2 | ||||
-rw-r--r-- | server/controllers/api/users/my-video-playlists.ts | 47 | ||||
-rw-r--r-- | server/controllers/api/video-playlist.ts | 34 | ||||
-rw-r--r-- | server/helpers/custom-validators/misc.ts | 10 | ||||
-rw-r--r-- | server/initializers/constants.ts | 2 | ||||
-rw-r--r-- | server/middlewares/validators/videos/video-playlists.ts | 23 | ||||
-rw-r--r-- | server/models/video/video-playlist.ts | 23 |
7 files changed, 125 insertions, 16 deletions
diff --git a/server/controllers/api/users/index.ts b/server/controllers/api/users/index.ts index 5758c8227..f7edbddf3 100644 --- a/server/controllers/api/users/index.ts +++ b/server/controllers/api/users/index.ts | |||
@@ -38,6 +38,7 @@ import { auditLoggerFactory, getAuditIdFromRes, UserAuditView } from '../../../h | |||
38 | import { meRouter } from './me' | 38 | import { meRouter } from './me' |
39 | import { deleteUserToken } from '../../../lib/oauth-model' | 39 | import { deleteUserToken } from '../../../lib/oauth-model' |
40 | import { myBlocklistRouter } from './my-blocklist' | 40 | import { myBlocklistRouter } from './my-blocklist' |
41 | import { myVideoPlaylistsRouter } from './my-video-playlists' | ||
41 | import { myVideosHistoryRouter } from './my-history' | 42 | import { myVideosHistoryRouter } from './my-history' |
42 | import { myNotificationsRouter } from './my-notifications' | 43 | import { myNotificationsRouter } from './my-notifications' |
43 | import { Notifier } from '../../../lib/notifier' | 44 | import { Notifier } from '../../../lib/notifier' |
@@ -60,6 +61,7 @@ usersRouter.use('/', myNotificationsRouter) | |||
60 | usersRouter.use('/', mySubscriptionsRouter) | 61 | usersRouter.use('/', mySubscriptionsRouter) |
61 | usersRouter.use('/', myBlocklistRouter) | 62 | usersRouter.use('/', myBlocklistRouter) |
62 | usersRouter.use('/', myVideosHistoryRouter) | 63 | usersRouter.use('/', myVideosHistoryRouter) |
64 | usersRouter.use('/', myVideoPlaylistsRouter) | ||
63 | usersRouter.use('/', meRouter) | 65 | usersRouter.use('/', meRouter) |
64 | 66 | ||
65 | usersRouter.get('/autocomplete', | 67 | usersRouter.get('/autocomplete', |
diff --git a/server/controllers/api/users/my-video-playlists.ts b/server/controllers/api/users/my-video-playlists.ts new file mode 100644 index 000000000..1ec175f64 --- /dev/null +++ b/server/controllers/api/users/my-video-playlists.ts | |||
@@ -0,0 +1,47 @@ | |||
1 | import * as express from 'express' | ||
2 | import { asyncMiddleware, authenticate } from '../../../middlewares' | ||
3 | import { UserModel } from '../../../models/account/user' | ||
4 | import { doVideosInPlaylistExistValidator } from '../../../middlewares/validators/videos/video-playlists' | ||
5 | import { VideoPlaylistModel } from '../../../models/video/video-playlist' | ||
6 | import { VideoExistInPlaylist } from '../../../../shared/models/videos/playlist/video-exist-in-playlist.model' | ||
7 | |||
8 | const myVideoPlaylistsRouter = express.Router() | ||
9 | |||
10 | myVideoPlaylistsRouter.get('/me/video-playlists/videos-exist', | ||
11 | authenticate, | ||
12 | doVideosInPlaylistExistValidator, | ||
13 | asyncMiddleware(doVideosInPlaylistExist) | ||
14 | ) | ||
15 | |||
16 | // --------------------------------------------------------------------------- | ||
17 | |||
18 | export { | ||
19 | myVideoPlaylistsRouter | ||
20 | } | ||
21 | |||
22 | // --------------------------------------------------------------------------- | ||
23 | |||
24 | async function doVideosInPlaylistExist (req: express.Request, res: express.Response) { | ||
25 | const videoIds = req.query.videoIds as number[] | ||
26 | const user = res.locals.oauth.token.User as UserModel | ||
27 | |||
28 | const results = await VideoPlaylistModel.listPlaylistIdsOf(user.Account.id, videoIds) | ||
29 | |||
30 | const existObject: VideoExistInPlaylist = {} | ||
31 | |||
32 | for (const videoId of videoIds) { | ||
33 | existObject[videoId] = [] | ||
34 | } | ||
35 | |||
36 | for (const result of results) { | ||
37 | for (const element of result.VideoPlaylistElements) { | ||
38 | existObject[element.videoId].push({ | ||
39 | playlistId: result.id, | ||
40 | startTimestamp: element.startTimestamp, | ||
41 | stopTimestamp: element.stopTimestamp | ||
42 | }) | ||
43 | } | ||
44 | } | ||
45 | |||
46 | return res.json(existObject) | ||
47 | } | ||
diff --git a/server/controllers/api/video-playlist.ts b/server/controllers/api/video-playlist.ts index 145764d35..49432d3aa 100644 --- a/server/controllers/api/video-playlist.ts +++ b/server/controllers/api/video-playlist.ts | |||
@@ -291,23 +291,26 @@ async function addVideoInPlaylist (req: express.Request, res: express.Response) | |||
291 | videoId: video.id | 291 | videoId: video.id |
292 | }, { transaction: t }) | 292 | }, { transaction: t }) |
293 | 293 | ||
294 | // If the user did not set a thumbnail, automatically take the video thumbnail | 294 | videoPlaylist.updatedAt = new Date() |
295 | if (playlistElement.position === 1) { | 295 | await videoPlaylist.save({ transaction: t }) |
296 | const playlistThumbnailPath = join(CONFIG.STORAGE.THUMBNAILS_DIR, videoPlaylist.getThumbnailName()) | ||
297 | |||
298 | if (await pathExists(playlistThumbnailPath) === false) { | ||
299 | logger.info('Generating default thumbnail to playlist %s.', videoPlaylist.url) | ||
300 | |||
301 | const videoThumbnailPath = join(CONFIG.STORAGE.THUMBNAILS_DIR, video.getThumbnailName()) | ||
302 | await copy(videoThumbnailPath, playlistThumbnailPath) | ||
303 | } | ||
304 | } | ||
305 | 296 | ||
306 | await sendUpdateVideoPlaylist(videoPlaylist, t) | 297 | await sendUpdateVideoPlaylist(videoPlaylist, t) |
307 | 298 | ||
308 | return playlistElement | 299 | return playlistElement |
309 | }) | 300 | }) |
310 | 301 | ||
302 | // If the user did not set a thumbnail, automatically take the video thumbnail | ||
303 | if (playlistElement.position === 1) { | ||
304 | const playlistThumbnailPath = join(CONFIG.STORAGE.THUMBNAILS_DIR, videoPlaylist.getThumbnailName()) | ||
305 | |||
306 | if (await pathExists(playlistThumbnailPath) === false) { | ||
307 | logger.info('Generating default thumbnail to playlist %s.', videoPlaylist.url) | ||
308 | |||
309 | const videoThumbnailPath = join(CONFIG.STORAGE.THUMBNAILS_DIR, video.getThumbnailName()) | ||
310 | await copy(videoThumbnailPath, playlistThumbnailPath) | ||
311 | } | ||
312 | } | ||
313 | |||
311 | logger.info('Video added in playlist %s at position %d.', videoPlaylist.uuid, playlistElement.position) | 314 | logger.info('Video added in playlist %s at position %d.', videoPlaylist.uuid, playlistElement.position) |
312 | 315 | ||
313 | return res.json({ | 316 | return res.json({ |
@@ -328,6 +331,9 @@ async function updateVideoPlaylistElement (req: express.Request, res: express.Re | |||
328 | 331 | ||
329 | const element = await videoPlaylistElement.save({ transaction: t }) | 332 | const element = await videoPlaylistElement.save({ transaction: t }) |
330 | 333 | ||
334 | videoPlaylist.updatedAt = new Date() | ||
335 | await videoPlaylist.save({ transaction: t }) | ||
336 | |||
331 | await sendUpdateVideoPlaylist(videoPlaylist, t) | 337 | await sendUpdateVideoPlaylist(videoPlaylist, t) |
332 | 338 | ||
333 | return element | 339 | return element |
@@ -349,6 +355,9 @@ async function removeVideoFromPlaylist (req: express.Request, res: express.Respo | |||
349 | // Decrease position of the next elements | 355 | // Decrease position of the next elements |
350 | await VideoPlaylistElementModel.increasePositionOf(videoPlaylist.id, positionToDelete, null, -1, t) | 356 | await VideoPlaylistElementModel.increasePositionOf(videoPlaylist.id, positionToDelete, null, -1, t) |
351 | 357 | ||
358 | videoPlaylist.updatedAt = new Date() | ||
359 | await videoPlaylist.save({ transaction: t }) | ||
360 | |||
352 | await sendUpdateVideoPlaylist(videoPlaylist, t) | 361 | await sendUpdateVideoPlaylist(videoPlaylist, t) |
353 | 362 | ||
354 | logger.info('Video playlist element %d of playlist %s deleted.', videoPlaylistElement.position, videoPlaylist.uuid) | 363 | logger.info('Video playlist element %d of playlist %s deleted.', videoPlaylistElement.position, videoPlaylist.uuid) |
@@ -390,6 +399,9 @@ async function reorderVideosPlaylist (req: express.Request, res: express.Respons | |||
390 | // Decrease positions of elements after the old position of our ordered elements (decrease) | 399 | // Decrease positions of elements after the old position of our ordered elements (decrease) |
391 | await VideoPlaylistElementModel.increasePositionOf(videoPlaylist.id, oldPosition, null, -reorderLength, t) | 400 | await VideoPlaylistElementModel.increasePositionOf(videoPlaylist.id, oldPosition, null, -reorderLength, t) |
392 | 401 | ||
402 | videoPlaylist.updatedAt = new Date() | ||
403 | await videoPlaylist.save({ transaction: t }) | ||
404 | |||
393 | await sendUpdateVideoPlaylist(videoPlaylist, t) | 405 | await sendUpdateVideoPlaylist(videoPlaylist, t) |
394 | }) | 406 | }) |
395 | 407 | ||
diff --git a/server/helpers/custom-validators/misc.ts b/server/helpers/custom-validators/misc.ts index 76647fea2..3a3deab0c 100644 --- a/server/helpers/custom-validators/misc.ts +++ b/server/helpers/custom-validators/misc.ts | |||
@@ -49,12 +49,19 @@ function toValueOrNull (value: string) { | |||
49 | return value | 49 | return value |
50 | } | 50 | } |
51 | 51 | ||
52 | function toArray (value: string) { | 52 | function toArray (value: any) { |
53 | if (value && isArray(value) === false) return [ value ] | 53 | if (value && isArray(value) === false) return [ value ] |
54 | 54 | ||
55 | return value | 55 | return value |
56 | } | 56 | } |
57 | 57 | ||
58 | function toIntArray (value: any) { | ||
59 | if (!value) return [] | ||
60 | if (isArray(value) === false) return [ validator.toInt(value) ] | ||
61 | |||
62 | return value.map(v => validator.toInt(v)) | ||
63 | } | ||
64 | |||
58 | function isFileValid ( | 65 | function isFileValid ( |
59 | files: { [ fieldname: string ]: Express.Multer.File[] } | Express.Multer.File[], | 66 | files: { [ fieldname: string ]: Express.Multer.File[] } | Express.Multer.File[], |
60 | mimeTypeRegex: string, | 67 | mimeTypeRegex: string, |
@@ -97,5 +104,6 @@ export { | |||
97 | isBooleanValid, | 104 | isBooleanValid, |
98 | toIntOrNull, | 105 | toIntOrNull, |
99 | toArray, | 106 | toArray, |
107 | toIntArray, | ||
100 | isFileValid | 108 | isFileValid |
101 | } | 109 | } |
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts index 54c390540..169a98ceb 100644 --- a/server/initializers/constants.ts +++ b/server/initializers/constants.ts | |||
@@ -56,7 +56,7 @@ const SORTABLE_COLUMNS = { | |||
56 | 56 | ||
57 | USER_NOTIFICATIONS: [ 'createdAt' ], | 57 | USER_NOTIFICATIONS: [ 'createdAt' ], |
58 | 58 | ||
59 | VIDEO_PLAYLISTS: [ 'createdAt' ] | 59 | VIDEO_PLAYLISTS: [ 'displayName', 'createdAt', 'updatedAt' ] |
60 | } | 60 | } |
61 | 61 | ||
62 | const OAUTH_LIFETIME = { | 62 | const OAUTH_LIFETIME = { |
diff --git a/server/middlewares/validators/videos/video-playlists.ts b/server/middlewares/validators/videos/video-playlists.ts index 22b8b8ff1..87d2c7b51 100644 --- a/server/middlewares/validators/videos/video-playlists.ts +++ b/server/middlewares/validators/videos/video-playlists.ts | |||
@@ -4,9 +4,9 @@ import { UserRight } from '../../../../shared' | |||
4 | import { logger } from '../../../helpers/logger' | 4 | import { logger } from '../../../helpers/logger' |
5 | import { UserModel } from '../../../models/account/user' | 5 | import { UserModel } from '../../../models/account/user' |
6 | import { areValidationErrors } from '../utils' | 6 | import { areValidationErrors } from '../utils' |
7 | import { isVideoExist, isVideoImage } from '../../../helpers/custom-validators/videos' | 7 | import { isVideoExist, isVideoFileInfoHashValid, isVideoImage } from '../../../helpers/custom-validators/videos' |
8 | import { CONSTRAINTS_FIELDS } from '../../../initializers' | 8 | import { CONSTRAINTS_FIELDS } from '../../../initializers' |
9 | import { isIdOrUUIDValid, isUUIDValid, toValueOrNull } from '../../../helpers/custom-validators/misc' | 9 | import { isArrayOf, isIdOrUUIDValid, isIdValid, isUUIDValid, toArray, toValueOrNull, toIntArray } from '../../../helpers/custom-validators/misc' |
10 | import { | 10 | import { |
11 | isVideoPlaylistDescriptionValid, | 11 | isVideoPlaylistDescriptionValid, |
12 | isVideoPlaylistExist, | 12 | isVideoPlaylistExist, |
@@ -23,6 +23,7 @@ import { VideoModel } from '../../../models/video/video' | |||
23 | import { authenticatePromiseIfNeeded } from '../../oauth' | 23 | import { authenticatePromiseIfNeeded } from '../../oauth' |
24 | import { VideoPlaylistPrivacy } from '../../../../shared/models/videos/playlist/video-playlist-privacy.model' | 24 | import { VideoPlaylistPrivacy } from '../../../../shared/models/videos/playlist/video-playlist-privacy.model' |
25 | import { VideoPlaylistType } from '../../../../shared/models/videos/playlist/video-playlist-type.model' | 25 | import { VideoPlaylistType } from '../../../../shared/models/videos/playlist/video-playlist-type.model' |
26 | import { areValidActorHandles } from '../../../helpers/custom-validators/activitypub/actor' | ||
26 | 27 | ||
27 | const videoPlaylistsAddValidator = getCommonPlaylistEditAttributes().concat([ | 28 | const videoPlaylistsAddValidator = getCommonPlaylistEditAttributes().concat([ |
28 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | 29 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { |
@@ -305,6 +306,20 @@ const commonVideoPlaylistFiltersValidator = [ | |||
305 | } | 306 | } |
306 | ] | 307 | ] |
307 | 308 | ||
309 | const doVideosInPlaylistExistValidator = [ | ||
310 | query('videoIds') | ||
311 | .customSanitizer(toIntArray) | ||
312 | .custom(v => isArrayOf(v, isIdValid)).withMessage('Should have a valid video ids array'), | ||
313 | |||
314 | (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
315 | logger.debug('Checking areVideosInPlaylistExistValidator parameters', { parameters: req.query }) | ||
316 | |||
317 | if (areValidationErrors(req, res)) return | ||
318 | |||
319 | return next() | ||
320 | } | ||
321 | ] | ||
322 | |||
308 | // --------------------------------------------------------------------------- | 323 | // --------------------------------------------------------------------------- |
309 | 324 | ||
310 | export { | 325 | export { |
@@ -319,7 +334,9 @@ export { | |||
319 | 334 | ||
320 | videoPlaylistElementAPGetValidator, | 335 | videoPlaylistElementAPGetValidator, |
321 | 336 | ||
322 | commonVideoPlaylistFiltersValidator | 337 | commonVideoPlaylistFiltersValidator, |
338 | |||
339 | doVideosInPlaylistExistValidator | ||
323 | } | 340 | } |
324 | 341 | ||
325 | // --------------------------------------------------------------------------- | 342 | // --------------------------------------------------------------------------- |
diff --git a/server/models/video/video-playlist.ts b/server/models/video/video-playlist.ts index 4d2ea0a66..aa42687cd 100644 --- a/server/models/video/video-playlist.ts +++ b/server/models/video/video-playlist.ts | |||
@@ -317,6 +317,29 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> { | |||
317 | }) | 317 | }) |
318 | } | 318 | } |
319 | 319 | ||
320 | static listPlaylistIdsOf (accountId: number, videoIds: number[]) { | ||
321 | const query = { | ||
322 | attributes: [ 'id' ], | ||
323 | where: { | ||
324 | ownerAccountId: accountId | ||
325 | }, | ||
326 | include: [ | ||
327 | { | ||
328 | attributes: [ 'videoId', 'startTimestamp', 'stopTimestamp' ], | ||
329 | model: VideoPlaylistElementModel.unscoped(), | ||
330 | where: { | ||
331 | videoId: { | ||
332 | [Sequelize.Op.any]: videoIds | ||
333 | } | ||
334 | }, | ||
335 | required: true | ||
336 | } | ||
337 | ] | ||
338 | } | ||
339 | |||
340 | return VideoPlaylistModel.findAll(query) | ||
341 | } | ||
342 | |||
320 | static doesPlaylistExist (url: string) { | 343 | static doesPlaylistExist (url: string) { |
321 | const query = { | 344 | const query = { |
322 | attributes: [], | 345 | attributes: [], |