aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/controllers
diff options
context:
space:
mode:
authorChocobozzz <florian.bigard@gmail.com>2017-10-24 19:41:09 +0200
committerChocobozzz <florian.bigard@gmail.com>2017-10-26 09:11:38 +0200
commit72c7248b6fdcdb2175e726ff51b42e7555f2bd84 (patch)
tree1bfdee99dbe2392cc997edba8e314e2a8a401c72 /server/controllers
parent8113a93a0d9f31aa9e23702bbc31b8a76275ae22 (diff)
downloadPeerTube-72c7248b6fdcdb2175e726ff51b42e7555f2bd84.tar.gz
PeerTube-72c7248b6fdcdb2175e726ff51b42e7555f2bd84.tar.zst
PeerTube-72c7248b6fdcdb2175e726ff51b42e7555f2bd84.zip
Add video channels
Diffstat (limited to 'server/controllers')
-rw-r--r--server/controllers/api/remote/pods.ts4
-rw-r--r--server/controllers/api/remote/videos.ts301
-rw-r--r--server/controllers/api/users.ts33
-rw-r--r--server/controllers/api/videos/channel.ts196
-rw-r--r--server/controllers/api/videos/index.ts48
-rw-r--r--server/controllers/services.ts2
6 files changed, 502 insertions, 82 deletions
diff --git a/server/controllers/api/remote/pods.ts b/server/controllers/api/remote/pods.ts
index 6f7b5f651..a62b9c684 100644
--- a/server/controllers/api/remote/pods.ts
+++ b/server/controllers/api/remote/pods.ts
@@ -7,7 +7,7 @@ import {
7 setBodyHostPort, 7 setBodyHostPort,
8 remotePodsAddValidator 8 remotePodsAddValidator
9} from '../../../middlewares' 9} from '../../../middlewares'
10import { sendOwnedVideosToPod } from '../../../lib' 10import { sendOwnedDataToPod } from '../../../lib'
11import { getMyPublicCert, getFormattedObjects } from '../../../helpers' 11import { getMyPublicCert, getFormattedObjects } from '../../../helpers'
12import { CONFIG } from '../../../initializers' 12import { CONFIG } from '../../../initializers'
13import { PodInstance } from '../../../models' 13import { PodInstance } from '../../../models'
@@ -43,7 +43,7 @@ function addPods (req: express.Request, res: express.Response, next: express.Nex
43 const pod = db.Pod.build(information) 43 const pod = db.Pod.build(information)
44 pod.save() 44 pod.save()
45 .then(podCreated => { 45 .then(podCreated => {
46 return sendOwnedVideosToPod(podCreated.id) 46 return sendOwnedDataToPod(podCreated.id)
47 }) 47 })
48 .then(() => { 48 .then(() => {
49 return getMyPublicCert() 49 return getMyPublicCert()
diff --git a/server/controllers/api/remote/videos.ts b/server/controllers/api/remote/videos.ts
index 23023211f..c8f531490 100644
--- a/server/controllers/api/remote/videos.ts
+++ b/server/controllers/api/remote/videos.ts
@@ -1,5 +1,6 @@
1import * as express from 'express' 1import * as express from 'express'
2import * as Promise from 'bluebird' 2import * as Promise from 'bluebird'
3import * as Sequelize from 'sequelize'
3 4
4import { database as db } from '../../../initializers/database' 5import { database as db } from '../../../initializers/database'
5import { 6import {
@@ -27,17 +28,28 @@ import {
27 RemoteQaduVideoRequest, 28 RemoteQaduVideoRequest,
28 RemoteQaduVideoData, 29 RemoteQaduVideoData,
29 RemoteVideoEventRequest, 30 RemoteVideoEventRequest,
30 RemoteVideoEventData 31 RemoteVideoEventData,
32 RemoteVideoChannelCreateData,
33 RemoteVideoChannelUpdateData,
34 RemoteVideoChannelRemoveData,
35 RemoteVideoAuthorRemoveData,
36 RemoteVideoAuthorCreateData
31} from '../../../../shared' 37} from '../../../../shared'
32 38
33const ENDPOINT_ACTIONS = REQUEST_ENDPOINT_ACTIONS[REQUEST_ENDPOINTS.VIDEOS] 39const ENDPOINT_ACTIONS = REQUEST_ENDPOINT_ACTIONS[REQUEST_ENDPOINTS.VIDEOS]
34 40
35// Functions to call when processing a remote request 41// Functions to call when processing a remote request
42// FIXME: use RemoteVideoRequestType as id type
36const functionsHash: { [ id: string ]: (...args) => Promise<any> } = {} 43const functionsHash: { [ id: string ]: (...args) => Promise<any> } = {}
37functionsHash[ENDPOINT_ACTIONS.ADD] = addRemoteVideoRetryWrapper 44functionsHash[ENDPOINT_ACTIONS.ADD_VIDEO] = addRemoteVideoRetryWrapper
38functionsHash[ENDPOINT_ACTIONS.UPDATE] = updateRemoteVideoRetryWrapper 45functionsHash[ENDPOINT_ACTIONS.UPDATE_VIDEO] = updateRemoteVideoRetryWrapper
39functionsHash[ENDPOINT_ACTIONS.REMOVE] = removeRemoteVideo 46functionsHash[ENDPOINT_ACTIONS.REMOVE_VIDEO] = removeRemoteVideoRetryWrapper
40functionsHash[ENDPOINT_ACTIONS.REPORT_ABUSE] = reportAbuseRemoteVideo 47functionsHash[ENDPOINT_ACTIONS.ADD_CHANNEL] = addRemoteVideoChannelRetryWrapper
48functionsHash[ENDPOINT_ACTIONS.UPDATE_CHANNEL] = updateRemoteVideoChannelRetryWrapper
49functionsHash[ENDPOINT_ACTIONS.REMOVE_CHANNEL] = removeRemoteVideoChannelRetryWrapper
50functionsHash[ENDPOINT_ACTIONS.REPORT_ABUSE] = reportAbuseRemoteVideoRetryWrapper
51functionsHash[ENDPOINT_ACTIONS.ADD_AUTHOR] = addRemoteVideoAuthorRetryWrapper
52functionsHash[ENDPOINT_ACTIONS.REMOVE_AUTHOR] = removeRemoteVideoAuthorRetryWrapper
41 53
42const remoteVideosRouter = express.Router() 54const remoteVideosRouter = express.Router()
43 55
@@ -133,7 +145,7 @@ function processVideosEventsRetryWrapper (eventData: RemoteVideoEventData, fromP
133function processVideosEvents (eventData: RemoteVideoEventData, fromPod: PodInstance) { 145function processVideosEvents (eventData: RemoteVideoEventData, fromPod: PodInstance) {
134 146
135 return db.sequelize.transaction(t => { 147 return db.sequelize.transaction(t => {
136 return fetchVideoByUUID(eventData.uuid) 148 return fetchVideoByUUID(eventData.uuid, t)
137 .then(videoInstance => { 149 .then(videoInstance => {
138 const options = { transaction: t } 150 const options = { transaction: t }
139 151
@@ -196,7 +208,7 @@ function quickAndDirtyUpdateVideo (videoData: RemoteQaduVideoData, fromPod: PodI
196 let videoUUID = '' 208 let videoUUID = ''
197 209
198 return db.sequelize.transaction(t => { 210 return db.sequelize.transaction(t => {
199 return fetchVideoByHostAndUUID(fromPod.host, videoData.uuid) 211 return fetchVideoByHostAndUUID(fromPod.host, videoData.uuid, t)
200 .then(videoInstance => { 212 .then(videoInstance => {
201 const options = { transaction: t } 213 const options = { transaction: t }
202 214
@@ -239,22 +251,16 @@ function addRemoteVideo (videoToCreateData: RemoteVideoCreateData, fromPod: PodI
239 .then(video => { 251 .then(video => {
240 if (video) throw new Error('UUID already exists.') 252 if (video) throw new Error('UUID already exists.')
241 253
242 return undefined 254 return db.VideoChannel.loadByHostAndUUID(fromPod.host, videoToCreateData.channelUUID, t)
243 }) 255 })
244 .then(() => { 256 .then(videoChannel => {
245 const name = videoToCreateData.author 257 if (!videoChannel) throw new Error('Video channel ' + videoToCreateData.channelUUID + ' not found.')
246 const podId = fromPod.id
247 // This author is from another pod so we do not associate a user
248 const userId = null
249 258
250 return db.Author.findOrCreateAuthor(name, podId, userId, t)
251 })
252 .then(author => {
253 const tags = videoToCreateData.tags 259 const tags = videoToCreateData.tags
254 260
255 return db.Tag.findOrCreateTags(tags, t).then(tagInstances => ({ author, tagInstances })) 261 return db.Tag.findOrCreateTags(tags, t).then(tagInstances => ({ videoChannel, tagInstances }))
256 }) 262 })
257 .then(({ author, tagInstances }) => { 263 .then(({ videoChannel, tagInstances }) => {
258 const videoData = { 264 const videoData = {
259 name: videoToCreateData.name, 265 name: videoToCreateData.name,
260 uuid: videoToCreateData.uuid, 266 uuid: videoToCreateData.uuid,
@@ -263,7 +269,7 @@ function addRemoteVideo (videoToCreateData: RemoteVideoCreateData, fromPod: PodI
263 language: videoToCreateData.language, 269 language: videoToCreateData.language,
264 nsfw: videoToCreateData.nsfw, 270 nsfw: videoToCreateData.nsfw,
265 description: videoToCreateData.description, 271 description: videoToCreateData.description,
266 authorId: author.id, 272 channelId: videoChannel.id,
267 duration: videoToCreateData.duration, 273 duration: videoToCreateData.duration,
268 createdAt: videoToCreateData.createdAt, 274 createdAt: videoToCreateData.createdAt,
269 // FIXME: updatedAt does not seems to be considered by Sequelize 275 // FIXME: updatedAt does not seems to be considered by Sequelize
@@ -336,7 +342,7 @@ function updateRemoteVideo (videoAttributesToUpdate: RemoteVideoUpdateData, from
336 logger.debug('Updating remote video "%s".', videoAttributesToUpdate.uuid) 342 logger.debug('Updating remote video "%s".', videoAttributesToUpdate.uuid)
337 343
338 return db.sequelize.transaction(t => { 344 return db.sequelize.transaction(t => {
339 return fetchVideoByHostAndUUID(fromPod.host, videoAttributesToUpdate.uuid) 345 return fetchVideoByHostAndUUID(fromPod.host, videoAttributesToUpdate.uuid, t)
340 .then(videoInstance => { 346 .then(videoInstance => {
341 const tags = videoAttributesToUpdate.tags 347 const tags = videoAttributesToUpdate.tags
342 348
@@ -365,7 +371,7 @@ function updateRemoteVideo (videoAttributesToUpdate: RemoteVideoUpdateData, from
365 371
366 // Remove old video files 372 // Remove old video files
367 videoInstance.VideoFiles.forEach(videoFile => { 373 videoInstance.VideoFiles.forEach(videoFile => {
368 tasks.push(videoFile.destroy()) 374 tasks.push(videoFile.destroy({ transaction: t }))
369 }) 375 })
370 376
371 return Promise.all(tasks).then(() => ({ tagInstances, videoInstance })) 377 return Promise.all(tasks).then(() => ({ tagInstances, videoInstance }))
@@ -404,37 +410,231 @@ function updateRemoteVideo (videoAttributesToUpdate: RemoteVideoUpdateData, from
404 }) 410 })
405} 411}
406 412
413function removeRemoteVideoRetryWrapper (videoToRemoveData: RemoteVideoRemoveData, fromPod: PodInstance) {
414 const options = {
415 arguments: [ videoToRemoveData, fromPod ],
416 errorMessage: 'Cannot remove the remote video channel with many retries.'
417 }
418
419 return retryTransactionWrapper(removeRemoteVideo, options)
420}
421
407function removeRemoteVideo (videoToRemoveData: RemoteVideoRemoveData, fromPod: PodInstance) { 422function removeRemoteVideo (videoToRemoveData: RemoteVideoRemoveData, fromPod: PodInstance) {
408 // We need the instance because we have to remove some other stuffs (thumbnail etc) 423 logger.debug('Removing remote video "%s".', videoToRemoveData.uuid)
409 return fetchVideoByHostAndUUID(fromPod.host, videoToRemoveData.uuid) 424
410 .then(video => { 425 return db.sequelize.transaction(t => {
411 logger.debug('Removing remote video with uuid %s.', video.uuid) 426 // We need the instance because we have to remove some other stuffs (thumbnail etc)
412 return video.destroy() 427 return fetchVideoByHostAndUUID(fromPod.host, videoToRemoveData.uuid, t)
413 }) 428 .then(video => video.destroy({ transaction: t }))
429 })
430 .then(() => logger.info('Remote video with uuid %s removed.', videoToRemoveData.uuid))
431 .catch(err => {
432 logger.debug('Cannot remove the remote video.', err)
433 throw err
434 })
435}
436
437function addRemoteVideoAuthorRetryWrapper (authorToCreateData: RemoteVideoAuthorCreateData, fromPod: PodInstance) {
438 const options = {
439 arguments: [ authorToCreateData, fromPod ],
440 errorMessage: 'Cannot insert the remote video author with many retries.'
441 }
442
443 return retryTransactionWrapper(addRemoteVideoAuthor, options)
444}
445
446function addRemoteVideoAuthor (authorToCreateData: RemoteVideoAuthorCreateData, fromPod: PodInstance) {
447 logger.debug('Adding remote video author "%s".', authorToCreateData.uuid)
448
449 return db.sequelize.transaction(t => {
450 return db.Author.loadAuthorByPodAndUUID(authorToCreateData.uuid, fromPod.id, t)
451 .then(author => {
452 if (author) throw new Error('UUID already exists.')
453
454 return undefined
455 })
456 .then(() => {
457 const videoAuthorData = {
458 name: authorToCreateData.name,
459 uuid: authorToCreateData.uuid,
460 userId: null, // Not on our pod
461 podId: fromPod.id
462 }
463
464 const author = db.Author.build(videoAuthorData)
465 return author.save({ transaction: t })
466 })
467 })
468 .then(() => logger.info('Remote video author with uuid %s inserted.', authorToCreateData.uuid))
414 .catch(err => { 469 .catch(err => {
415 logger.debug('Could not fetch remote video.', { host: fromPod.host, uuid: videoToRemoveData.uuid, error: err.stack }) 470 logger.debug('Cannot insert the remote video author.', err)
471 throw err
416 }) 472 })
417} 473}
418 474
475function removeRemoteVideoAuthorRetryWrapper (authorAttributesToRemove: RemoteVideoAuthorRemoveData, fromPod: PodInstance) {
476 const options = {
477 arguments: [ authorAttributesToRemove, fromPod ],
478 errorMessage: 'Cannot remove the remote video author with many retries.'
479 }
480
481 return retryTransactionWrapper(removeRemoteVideoAuthor, options)
482}
483
484function removeRemoteVideoAuthor (authorAttributesToRemove: RemoteVideoAuthorRemoveData, fromPod: PodInstance) {
485 logger.debug('Removing remote video author "%s".', authorAttributesToRemove.uuid)
486
487 return db.sequelize.transaction(t => {
488 return db.Author.loadAuthorByPodAndUUID(authorAttributesToRemove.uuid, fromPod.id, t)
489 .then(videoAuthor => videoAuthor.destroy({ transaction: t }))
490 })
491 .then(() => logger.info('Remote video author with uuid %s removed.', authorAttributesToRemove.uuid))
492 .catch(err => {
493 logger.debug('Cannot remove the remote video author.', err)
494 throw err
495 })
496}
497
498function addRemoteVideoChannelRetryWrapper (videoChannelToCreateData: RemoteVideoChannelCreateData, fromPod: PodInstance) {
499 const options = {
500 arguments: [ videoChannelToCreateData, fromPod ],
501 errorMessage: 'Cannot insert the remote video channel with many retries.'
502 }
503
504 return retryTransactionWrapper(addRemoteVideoChannel, options)
505}
506
507function addRemoteVideoChannel (videoChannelToCreateData: RemoteVideoChannelCreateData, fromPod: PodInstance) {
508 logger.debug('Adding remote video channel "%s".', videoChannelToCreateData.uuid)
509
510 return db.sequelize.transaction(t => {
511 return db.VideoChannel.loadByUUID(videoChannelToCreateData.uuid)
512 .then(videoChannel => {
513 if (videoChannel) throw new Error('UUID already exists.')
514
515 return undefined
516 })
517 .then(() => {
518 const authorUUID = videoChannelToCreateData.ownerUUID
519 const podId = fromPod.id
520
521 return db.Author.loadAuthorByPodAndUUID(authorUUID, podId, t)
522 })
523 .then(author => {
524 if (!author) throw new Error('Unknown author UUID.')
525
526 const videoChannelData = {
527 name: videoChannelToCreateData.name,
528 description: videoChannelToCreateData.description,
529 uuid: videoChannelToCreateData.uuid,
530 createdAt: videoChannelToCreateData.createdAt,
531 updatedAt: videoChannelToCreateData.updatedAt,
532 remote: true,
533 authorId: author.id
534 }
535
536 const videoChannel = db.VideoChannel.build(videoChannelData)
537 return videoChannel.save({ transaction: t })
538 })
539 })
540 .then(() => logger.info('Remote video channel with uuid %s inserted.', videoChannelToCreateData.uuid))
541 .catch(err => {
542 logger.debug('Cannot insert the remote video channel.', err)
543 throw err
544 })
545}
546
547function updateRemoteVideoChannelRetryWrapper (videoChannelAttributesToUpdate: RemoteVideoChannelUpdateData, fromPod: PodInstance) {
548 const options = {
549 arguments: [ videoChannelAttributesToUpdate, fromPod ],
550 errorMessage: 'Cannot update the remote video channel with many retries.'
551 }
552
553 return retryTransactionWrapper(updateRemoteVideoChannel, options)
554}
555
556function updateRemoteVideoChannel (videoChannelAttributesToUpdate: RemoteVideoChannelUpdateData, fromPod: PodInstance) {
557 logger.debug('Updating remote video channel "%s".', videoChannelAttributesToUpdate.uuid)
558
559 return db.sequelize.transaction(t => {
560 return fetchVideoChannelByHostAndUUID(fromPod.host, videoChannelAttributesToUpdate.uuid, t)
561 .then(videoChannelInstance => {
562 const options = { transaction: t }
563
564 videoChannelInstance.set('name', videoChannelAttributesToUpdate.name)
565 videoChannelInstance.set('description', videoChannelAttributesToUpdate.description)
566 videoChannelInstance.set('createdAt', videoChannelAttributesToUpdate.createdAt)
567 videoChannelInstance.set('updatedAt', videoChannelAttributesToUpdate.updatedAt)
568
569 return videoChannelInstance.save(options)
570 })
571 })
572 .then(() => logger.info('Remote video channel with uuid %s updated', videoChannelAttributesToUpdate.uuid))
573 .catch(err => {
574 // This is just a debug because we will retry the insert
575 logger.debug('Cannot update the remote video channel.', err)
576 throw err
577 })
578}
579
580function removeRemoteVideoChannelRetryWrapper (videoChannelAttributesToRemove: RemoteVideoChannelRemoveData, fromPod: PodInstance) {
581 const options = {
582 arguments: [ videoChannelAttributesToRemove, fromPod ],
583 errorMessage: 'Cannot remove the remote video channel with many retries.'
584 }
585
586 return retryTransactionWrapper(removeRemoteVideoChannel, options)
587}
588
589function removeRemoteVideoChannel (videoChannelAttributesToRemove: RemoteVideoChannelRemoveData, fromPod: PodInstance) {
590 logger.debug('Removing remote video channel "%s".', videoChannelAttributesToRemove.uuid)
591
592 return db.sequelize.transaction(t => {
593 return fetchVideoChannelByHostAndUUID(fromPod.host, videoChannelAttributesToRemove.uuid, t)
594 .then(videoChannel => videoChannel.destroy({ transaction: t }))
595 })
596 .then(() => logger.info('Remote video channel with uuid %s removed.', videoChannelAttributesToRemove.uuid))
597 .catch(err => {
598 logger.debug('Cannot remove the remote video channel.', err)
599 throw err
600 })
601}
602
603function reportAbuseRemoteVideoRetryWrapper (reportData: RemoteVideoReportAbuseData, fromPod: PodInstance) {
604 const options = {
605 arguments: [ reportData, fromPod ],
606 errorMessage: 'Cannot create remote abuse video with many retries.'
607 }
608
609 return retryTransactionWrapper(reportAbuseRemoteVideo, options)
610}
611
419function reportAbuseRemoteVideo (reportData: RemoteVideoReportAbuseData, fromPod: PodInstance) { 612function reportAbuseRemoteVideo (reportData: RemoteVideoReportAbuseData, fromPod: PodInstance) {
420 return fetchVideoByUUID(reportData.videoUUID) 613 logger.debug('Reporting remote abuse for video %s.', reportData.videoUUID)
421 .then(video => {
422 logger.debug('Reporting remote abuse for video %s.', video.id)
423 614
424 const videoAbuseData = { 615 return db.sequelize.transaction(t => {
425 reporterUsername: reportData.reporterUsername, 616 return fetchVideoByUUID(reportData.videoUUID, t)
426 reason: reportData.reportReason, 617 .then(video => {
427 reporterPodId: fromPod.id, 618 const videoAbuseData = {
428 videoId: video.id 619 reporterUsername: reportData.reporterUsername,
429 } 620 reason: reportData.reportReason,
621 reporterPodId: fromPod.id,
622 videoId: video.id
623 }
430 624
431 return db.VideoAbuse.create(videoAbuseData) 625 return db.VideoAbuse.create(videoAbuseData)
432 }) 626 })
433 .catch(err => logger.error('Cannot create remote abuse video.', err)) 627 })
628 .then(() => logger.info('Remote abuse for video uuid %s created', reportData.videoUUID))
629 .catch(err => {
630 // This is just a debug because we will retry the insert
631 logger.debug('Cannot create remote abuse video', err)
632 throw err
633 })
434} 634}
435 635
436function fetchVideoByUUID (id: string) { 636function fetchVideoByUUID (id: string, t: Sequelize.Transaction) {
437 return db.Video.loadByUUID(id) 637 return db.Video.loadByUUID(id, t)
438 .then(video => { 638 .then(video => {
439 if (!video) throw new Error('Video not found') 639 if (!video) throw new Error('Video not found')
440 640
@@ -446,8 +646,8 @@ function fetchVideoByUUID (id: string) {
446 }) 646 })
447} 647}
448 648
449function fetchVideoByHostAndUUID (podHost: string, uuid: string) { 649function fetchVideoByHostAndUUID (podHost: string, uuid: string, t: Sequelize.Transaction) {
450 return db.Video.loadByHostAndUUID(podHost, uuid) 650 return db.Video.loadByHostAndUUID(podHost, uuid, t)
451 .then(video => { 651 .then(video => {
452 if (!video) throw new Error('Video not found') 652 if (!video) throw new Error('Video not found')
453 653
@@ -458,3 +658,16 @@ function fetchVideoByHostAndUUID (podHost: string, uuid: string) {
458 throw err 658 throw err
459 }) 659 })
460} 660}
661
662function fetchVideoChannelByHostAndUUID (podHost: string, uuid: string, t: Sequelize.Transaction) {
663 return db.VideoChannel.loadByHostAndUUID(podHost, uuid, t)
664 .then(videoChannel => {
665 if (!videoChannel) throw new Error('Video channel not found')
666
667 return videoChannel
668 })
669 .catch(err => {
670 logger.error('Cannot load video channel from host and uuid.', { error: err.stack, podHost, uuid })
671 throw err
672 })
673}
diff --git a/server/controllers/api/users.ts b/server/controllers/api/users.ts
index 1ecaaf93f..6576e4333 100644
--- a/server/controllers/api/users.ts
+++ b/server/controllers/api/users.ts
@@ -2,7 +2,7 @@ import * as express from 'express'
2 2
3import { database as db } from '../../initializers/database' 3import { database as db } from '../../initializers/database'
4import { USER_ROLES, CONFIG } from '../../initializers' 4import { USER_ROLES, CONFIG } from '../../initializers'
5import { logger, getFormattedObjects } from '../../helpers' 5import { logger, getFormattedObjects, retryTransactionWrapper } from '../../helpers'
6import { 6import {
7 authenticate, 7 authenticate,
8 ensureIsAdmin, 8 ensureIsAdmin,
@@ -26,6 +26,7 @@ import {
26 UserUpdate, 26 UserUpdate,
27 UserUpdateMe 27 UserUpdateMe
28} from '../../../shared' 28} from '../../../shared'
29import { createUserAuthorAndChannel } from '../../lib'
29import { UserInstance } from '../../models' 30import { UserInstance } from '../../models'
30 31
31const usersRouter = express.Router() 32const usersRouter = express.Router()
@@ -58,7 +59,7 @@ usersRouter.post('/',
58 authenticate, 59 authenticate,
59 ensureIsAdmin, 60 ensureIsAdmin,
60 usersAddValidator, 61 usersAddValidator,
61 createUser 62 createUserRetryWrapper
62) 63)
63 64
64usersRouter.post('/register', 65usersRouter.post('/register',
@@ -98,9 +99,22 @@ export {
98 99
99// --------------------------------------------------------------------------- 100// ---------------------------------------------------------------------------
100 101
102function createUserRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) {
103 const options = {
104 arguments: [ req, res ],
105 errorMessage: 'Cannot insert the user with many retries.'
106 }
107
108 retryTransactionWrapper(createUser, options)
109 .then(() => {
110 // TODO : include Location of the new user -> 201
111 res.type('json').status(204).end()
112 })
113 .catch(err => next(err))
114}
115
101function createUser (req: express.Request, res: express.Response, next: express.NextFunction) { 116function createUser (req: express.Request, res: express.Response, next: express.NextFunction) {
102 const body: UserCreate = req.body 117 const body: UserCreate = req.body
103
104 const user = db.User.build({ 118 const user = db.User.build({
105 username: body.username, 119 username: body.username,
106 password: body.password, 120 password: body.password,
@@ -110,9 +124,12 @@ function createUser (req: express.Request, res: express.Response, next: express.
110 videoQuota: body.videoQuota 124 videoQuota: body.videoQuota
111 }) 125 })
112 126
113 user.save() 127 return createUserAuthorAndChannel(user)
114 .then(() => res.type('json').status(204).end()) 128 .then(() => logger.info('User %s with its channel and author created.', body.username))
115 .catch(err => next(err)) 129 .catch((err: Error) => {
130 logger.debug('Cannot insert the user.', err)
131 throw err
132 })
116} 133}
117 134
118function registerUser (req: express.Request, res: express.Response, next: express.NextFunction) { 135function registerUser (req: express.Request, res: express.Response, next: express.NextFunction) {
@@ -127,13 +144,13 @@ function registerUser (req: express.Request, res: express.Response, next: expres
127 videoQuota: CONFIG.USER.VIDEO_QUOTA 144 videoQuota: CONFIG.USER.VIDEO_QUOTA
128 }) 145 })
129 146
130 user.save() 147 return createUserAuthorAndChannel(user)
131 .then(() => res.type('json').status(204).end()) 148 .then(() => res.type('json').status(204).end())
132 .catch(err => next(err)) 149 .catch(err => next(err))
133} 150}
134 151
135function getUserInformation (req: express.Request, res: express.Response, next: express.NextFunction) { 152function getUserInformation (req: express.Request, res: express.Response, next: express.NextFunction) {
136 db.User.loadByUsername(res.locals.oauth.token.user.username) 153 db.User.loadByUsernameAndPopulateChannels(res.locals.oauth.token.user.username)
137 .then(user => res.json(user.toFormattedJSON())) 154 .then(user => res.json(user.toFormattedJSON()))
138 .catch(err => next(err)) 155 .catch(err => next(err))
139} 156}
diff --git a/server/controllers/api/videos/channel.ts b/server/controllers/api/videos/channel.ts
new file mode 100644
index 000000000..630fc4f53
--- /dev/null
+++ b/server/controllers/api/videos/channel.ts
@@ -0,0 +1,196 @@
1import * as express from 'express'
2
3import { database as db } from '../../../initializers'
4import {
5 logger,
6 getFormattedObjects,
7 retryTransactionWrapper
8} from '../../../helpers'
9import {
10 authenticate,
11 paginationValidator,
12 videoChannelsSortValidator,
13 videoChannelsAddValidator,
14 setVideoChannelsSort,
15 setPagination,
16 videoChannelsRemoveValidator,
17 videoChannelGetValidator,
18 videoChannelsUpdateValidator,
19 listVideoAuthorChannelsValidator
20} from '../../../middlewares'
21import {
22 createVideoChannel,
23 updateVideoChannelToFriends
24} from '../../../lib'
25import { VideoChannelInstance, AuthorInstance } from '../../../models'
26import { VideoChannelCreate, VideoChannelUpdate } from '../../../../shared'
27
28const videoChannelRouter = express.Router()
29
30videoChannelRouter.get('/channels',
31 paginationValidator,
32 videoChannelsSortValidator,
33 setVideoChannelsSort,
34 setPagination,
35 listVideoChannels
36)
37
38videoChannelRouter.get('/authors/:authorId/channels',
39 listVideoAuthorChannelsValidator,
40 listVideoAuthorChannels
41)
42
43videoChannelRouter.post('/channels',
44 authenticate,
45 videoChannelsAddValidator,
46 addVideoChannelRetryWrapper
47)
48
49videoChannelRouter.put('/channels/:id',
50 authenticate,
51 videoChannelsUpdateValidator,
52 updateVideoChannelRetryWrapper
53)
54
55videoChannelRouter.delete('/channels/:id',
56 authenticate,
57 videoChannelsRemoveValidator,
58 removeVideoChannelRetryWrapper
59)
60
61videoChannelRouter.get('/channels/:id',
62 videoChannelGetValidator,
63 getVideoChannel
64)
65
66// ---------------------------------------------------------------------------
67
68export {
69 videoChannelRouter
70}
71
72// ---------------------------------------------------------------------------
73
74function listVideoChannels (req: express.Request, res: express.Response, next: express.NextFunction) {
75 db.VideoChannel.listForApi(req.query.start, req.query.count, req.query.sort)
76 .then(result => res.json(getFormattedObjects(result.data, result.total)))
77 .catch(err => next(err))
78}
79
80function listVideoAuthorChannels (req: express.Request, res: express.Response, next: express.NextFunction) {
81 db.VideoChannel.listByAuthor(res.locals.author.id)
82 .then(result => res.json(getFormattedObjects(result.data, result.total)))
83 .catch(err => next(err))
84}
85
86// Wrapper to video channel add that retry the function if there is a database error
87// We need this because we run the transaction in SERIALIZABLE isolation that can fail
88function addVideoChannelRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) {
89 const options = {
90 arguments: [ req, res ],
91 errorMessage: 'Cannot insert the video video channel with many retries.'
92 }
93
94 retryTransactionWrapper(addVideoChannel, options)
95 .then(() => {
96 // TODO : include Location of the new video channel -> 201
97 res.type('json').status(204).end()
98 })
99 .catch(err => next(err))
100}
101
102function addVideoChannel (req: express.Request, res: express.Response) {
103 const videoChannelInfo: VideoChannelCreate = req.body
104 const author: AuthorInstance = res.locals.oauth.token.User.Author
105
106 return db.sequelize.transaction(t => {
107 return createVideoChannel(videoChannelInfo, author, t)
108 })
109 .then(videoChannelUUID => logger.info('Video channel with uuid %s created.', videoChannelUUID))
110 .catch((err: Error) => {
111 logger.debug('Cannot insert the video channel.', err)
112 throw err
113 })
114}
115
116function updateVideoChannelRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) {
117 const options = {
118 arguments: [ req, res ],
119 errorMessage: 'Cannot update the video with many retries.'
120 }
121
122 retryTransactionWrapper(updateVideoChannel, options)
123 .then(() => res.type('json').status(204).end())
124 .catch(err => next(err))
125}
126
127function updateVideoChannel (req: express.Request, res: express.Response) {
128 const videoChannelInstance: VideoChannelInstance = res.locals.videoChannel
129 const videoChannelFieldsSave = videoChannelInstance.toJSON()
130 const videoChannelInfoToUpdate: VideoChannelUpdate = req.body
131
132 return db.sequelize.transaction(t => {
133 const options = {
134 transaction: t
135 }
136
137 if (videoChannelInfoToUpdate.name !== undefined) videoChannelInstance.set('name', videoChannelInfoToUpdate.name)
138 if (videoChannelInfoToUpdate.description !== undefined) videoChannelInstance.set('description', videoChannelInfoToUpdate.description)
139
140 return videoChannelInstance.save(options)
141 .then(() => {
142 const json = videoChannelInstance.toUpdateRemoteJSON()
143
144 // Now we'll update the video channel's meta data to our friends
145 return updateVideoChannelToFriends(json, t)
146 })
147 })
148 .then(() => {
149 logger.info('Video channel with name %s and uuid %s updated.', videoChannelInstance.name, videoChannelInstance.uuid)
150 })
151 .catch(err => {
152 logger.debug('Cannot update the video channel.', err)
153
154 // Force fields we want to update
155 // If the transaction is retried, sequelize will think the object has not changed
156 // So it will skip the SQL request, even if the last one was ROLLBACKed!
157 Object.keys(videoChannelFieldsSave).forEach(key => {
158 const value = videoChannelFieldsSave[key]
159 videoChannelInstance.set(key, value)
160 })
161
162 throw err
163 })
164}
165
166function removeVideoChannelRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) {
167 const options = {
168 arguments: [ req, res ],
169 errorMessage: 'Cannot remove the video channel with many retries.'
170 }
171
172 retryTransactionWrapper(removeVideoChannel, options)
173 .then(() => res.type('json').status(204).end())
174 .catch(err => next(err))
175}
176
177function removeVideoChannel (req: express.Request, res: express.Response) {
178 const videoChannelInstance: VideoChannelInstance = res.locals.videoChannel
179
180 return db.sequelize.transaction(t => {
181 return videoChannelInstance.destroy({ transaction: t })
182 })
183 .then(() => {
184 logger.info('Video channel with name %s and uuid %s deleted.', videoChannelInstance.name, videoChannelInstance.uuid)
185 })
186 .catch(err => {
187 logger.error('Errors when removed the video channel.', err)
188 throw err
189 })
190}
191
192function getVideoChannel (req: express.Request, res: express.Response, next: express.NextFunction) {
193 db.VideoChannel.loadAndPopulateAuthorAndVideos(res.locals.videoChannel.id)
194 .then(videoChannelWithVideos => res.json(videoChannelWithVideos.toFormattedJSON()))
195 .catch(err => next(err))
196}
diff --git a/server/controllers/api/videos/index.ts b/server/controllers/api/videos/index.ts
index 2b7ead954..ec855ee8e 100644
--- a/server/controllers/api/videos/index.ts
+++ b/server/controllers/api/videos/index.ts
@@ -46,6 +46,7 @@ import { VideoCreate, VideoUpdate } from '../../../../shared'
46import { abuseVideoRouter } from './abuse' 46import { abuseVideoRouter } from './abuse'
47import { blacklistRouter } from './blacklist' 47import { blacklistRouter } from './blacklist'
48import { rateVideoRouter } from './rate' 48import { rateVideoRouter } from './rate'
49import { videoChannelRouter } from './channel'
49 50
50const videosRouter = express.Router() 51const videosRouter = express.Router()
51 52
@@ -76,6 +77,7 @@ const reqFiles = multer({ storage: storage }).fields([{ name: 'videofile', maxCo
76videosRouter.use('/', abuseVideoRouter) 77videosRouter.use('/', abuseVideoRouter)
77videosRouter.use('/', blacklistRouter) 78videosRouter.use('/', blacklistRouter)
78videosRouter.use('/', rateVideoRouter) 79videosRouter.use('/', rateVideoRouter)
80videosRouter.use('/', videoChannelRouter)
79 81
80videosRouter.get('/categories', listVideoCategories) 82videosRouter.get('/categories', listVideoCategories)
81videosRouter.get('/licences', listVideoLicences) 83videosRouter.get('/licences', listVideoLicences)
@@ -161,21 +163,13 @@ function addVideo (req: express.Request, res: express.Response, videoPhysicalFil
161 let videoUUID = '' 163 let videoUUID = ''
162 164
163 return db.sequelize.transaction(t => { 165 return db.sequelize.transaction(t => {
164 const user = res.locals.oauth.token.User 166 let p: Promise<TagInstance[]>
165 167
166 const name = user.username 168 if (!videoInfo.tags) p = Promise.resolve(undefined)
167 // null because it is OUR pod 169 else p = db.Tag.findOrCreateTags(videoInfo.tags, t)
168 const podId = null
169 const userId = user.id
170 170
171 return db.Author.findOrCreateAuthor(name, podId, userId, t) 171 return p
172 .then(author => { 172 .then(tagInstances => {
173 const tags = videoInfo.tags
174 if (!tags) return { author, tagInstances: undefined }
175
176 return db.Tag.findOrCreateTags(tags, t).then(tagInstances => ({ author, tagInstances }))
177 })
178 .then(({ author, tagInstances }) => {
179 const videoData = { 173 const videoData = {
180 name: videoInfo.name, 174 name: videoInfo.name,
181 remote: false, 175 remote: false,
@@ -186,18 +180,18 @@ function addVideo (req: express.Request, res: express.Response, videoPhysicalFil
186 nsfw: videoInfo.nsfw, 180 nsfw: videoInfo.nsfw,
187 description: videoInfo.description, 181 description: videoInfo.description,
188 duration: videoPhysicalFile['duration'], // duration was added by a previous middleware 182 duration: videoPhysicalFile['duration'], // duration was added by a previous middleware
189 authorId: author.id 183 channelId: res.locals.videoChannel.id
190 } 184 }
191 185
192 const video = db.Video.build(videoData) 186 const video = db.Video.build(videoData)
193 return { author, tagInstances, video } 187 return { tagInstances, video }
194 }) 188 })
195 .then(({ author, tagInstances, video }) => { 189 .then(({ tagInstances, video }) => {
196 const videoFilePath = join(CONFIG.STORAGE.VIDEOS_DIR, videoPhysicalFile.filename) 190 const videoFilePath = join(CONFIG.STORAGE.VIDEOS_DIR, videoPhysicalFile.filename)
197 return getVideoFileHeight(videoFilePath) 191 return getVideoFileHeight(videoFilePath)
198 .then(height => ({ author, tagInstances, video, videoFileHeight: height })) 192 .then(height => ({ tagInstances, video, videoFileHeight: height }))
199 }) 193 })
200 .then(({ author, tagInstances, video, videoFileHeight }) => { 194 .then(({ tagInstances, video, videoFileHeight }) => {
201 const videoFileData = { 195 const videoFileData = {
202 extname: extname(videoPhysicalFile.filename), 196 extname: extname(videoPhysicalFile.filename),
203 resolution: videoFileHeight, 197 resolution: videoFileHeight,
@@ -205,9 +199,9 @@ function addVideo (req: express.Request, res: express.Response, videoPhysicalFil
205 } 199 }
206 200
207 const videoFile = db.VideoFile.build(videoFileData) 201 const videoFile = db.VideoFile.build(videoFileData)
208 return { author, tagInstances, video, videoFile } 202 return { tagInstances, video, videoFile }
209 }) 203 })
210 .then(({ author, tagInstances, video, videoFile }) => { 204 .then(({ tagInstances, video, videoFile }) => {
211 const videoDir = CONFIG.STORAGE.VIDEOS_DIR 205 const videoDir = CONFIG.STORAGE.VIDEOS_DIR
212 const source = join(videoDir, videoPhysicalFile.filename) 206 const source = join(videoDir, videoPhysicalFile.filename)
213 const destination = join(videoDir, video.getVideoFilename(videoFile)) 207 const destination = join(videoDir, video.getVideoFilename(videoFile))
@@ -216,10 +210,10 @@ function addVideo (req: express.Request, res: express.Response, videoPhysicalFil
216 .then(() => { 210 .then(() => {
217 // This is important in case if there is another attempt in the retry process 211 // This is important in case if there is another attempt in the retry process
218 videoPhysicalFile.filename = video.getVideoFilename(videoFile) 212 videoPhysicalFile.filename = video.getVideoFilename(videoFile)
219 return { author, tagInstances, video, videoFile } 213 return { tagInstances, video, videoFile }
220 }) 214 })
221 }) 215 })
222 .then(({ author, tagInstances, video, videoFile }) => { 216 .then(({ tagInstances, video, videoFile }) => {
223 const tasks = [] 217 const tasks = []
224 218
225 tasks.push( 219 tasks.push(
@@ -239,15 +233,15 @@ function addVideo (req: express.Request, res: express.Response, videoPhysicalFil
239 ) 233 )
240 } 234 }
241 235
242 return Promise.all(tasks).then(() => ({ author, tagInstances, video, videoFile })) 236 return Promise.all(tasks).then(() => ({ tagInstances, video, videoFile }))
243 }) 237 })
244 .then(({ author, tagInstances, video, videoFile }) => { 238 .then(({ tagInstances, video, videoFile }) => {
245 const options = { transaction: t } 239 const options = { transaction: t }
246 240
247 return video.save(options) 241 return video.save(options)
248 .then(videoCreated => { 242 .then(videoCreated => {
249 // Do not forget to add Author information to the created video 243 // Do not forget to add video channel information to the created video
250 videoCreated.Author = author 244 videoCreated.VideoChannel = res.locals.videoChannel
251 videoUUID = videoCreated.uuid 245 videoUUID = videoCreated.uuid
252 246
253 return { tagInstances, video: videoCreated, videoFile } 247 return { tagInstances, video: videoCreated, videoFile }
@@ -392,7 +386,7 @@ function getVideo (req: express.Request, res: express.Response) {
392 } 386 }
393 387
394 // Do not wait the view system 388 // Do not wait the view system
395 res.json(videoInstance.toFormattedJSON()) 389 res.json(videoInstance.toFormattedDetailsJSON())
396} 390}
397 391
398function listVideos (req: express.Request, res: express.Response, next: express.NextFunction) { 392function listVideos (req: express.Request, res: express.Response, next: express.NextFunction) {
diff --git a/server/controllers/services.ts b/server/controllers/services.ts
index 4bbe56a8a..99a33a716 100644
--- a/server/controllers/services.ts
+++ b/server/controllers/services.ts
@@ -47,7 +47,7 @@ function generateOEmbed (req: express.Request, res: express.Response, next: expr
47 width: embedWidth, 47 width: embedWidth,
48 height: embedHeight, 48 height: embedHeight,
49 title: video.name, 49 title: video.name,
50 author_name: video.Author.name, 50 author_name: video.VideoChannel.Author.name,
51 provider_name: 'PeerTube', 51 provider_name: 'PeerTube',
52 provider_url: webserverUrl 52 provider_url: webserverUrl
53 } 53 }