From 72c7248b6fdcdb2175e726ff51b42e7555f2bd84 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Tue, 24 Oct 2017 19:41:09 +0200 Subject: Add video channels --- server/controllers/api/videos/channel.ts | 196 +++++++++++++++++++++++++++++++ server/controllers/api/videos/index.ts | 48 ++++---- 2 files changed, 217 insertions(+), 27 deletions(-) create mode 100644 server/controllers/api/videos/channel.ts (limited to 'server/controllers/api/videos') 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 @@ +import * as express from 'express' + +import { database as db } from '../../../initializers' +import { + logger, + getFormattedObjects, + retryTransactionWrapper +} from '../../../helpers' +import { + authenticate, + paginationValidator, + videoChannelsSortValidator, + videoChannelsAddValidator, + setVideoChannelsSort, + setPagination, + videoChannelsRemoveValidator, + videoChannelGetValidator, + videoChannelsUpdateValidator, + listVideoAuthorChannelsValidator +} from '../../../middlewares' +import { + createVideoChannel, + updateVideoChannelToFriends +} from '../../../lib' +import { VideoChannelInstance, AuthorInstance } from '../../../models' +import { VideoChannelCreate, VideoChannelUpdate } from '../../../../shared' + +const videoChannelRouter = express.Router() + +videoChannelRouter.get('/channels', + paginationValidator, + videoChannelsSortValidator, + setVideoChannelsSort, + setPagination, + listVideoChannels +) + +videoChannelRouter.get('/authors/:authorId/channels', + listVideoAuthorChannelsValidator, + listVideoAuthorChannels +) + +videoChannelRouter.post('/channels', + authenticate, + videoChannelsAddValidator, + addVideoChannelRetryWrapper +) + +videoChannelRouter.put('/channels/:id', + authenticate, + videoChannelsUpdateValidator, + updateVideoChannelRetryWrapper +) + +videoChannelRouter.delete('/channels/:id', + authenticate, + videoChannelsRemoveValidator, + removeVideoChannelRetryWrapper +) + +videoChannelRouter.get('/channels/:id', + videoChannelGetValidator, + getVideoChannel +) + +// --------------------------------------------------------------------------- + +export { + videoChannelRouter +} + +// --------------------------------------------------------------------------- + +function listVideoChannels (req: express.Request, res: express.Response, next: express.NextFunction) { + db.VideoChannel.listForApi(req.query.start, req.query.count, req.query.sort) + .then(result => res.json(getFormattedObjects(result.data, result.total))) + .catch(err => next(err)) +} + +function listVideoAuthorChannels (req: express.Request, res: express.Response, next: express.NextFunction) { + db.VideoChannel.listByAuthor(res.locals.author.id) + .then(result => res.json(getFormattedObjects(result.data, result.total))) + .catch(err => next(err)) +} + +// Wrapper to video channel add that retry the function if there is a database error +// We need this because we run the transaction in SERIALIZABLE isolation that can fail +function addVideoChannelRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) { + const options = { + arguments: [ req, res ], + errorMessage: 'Cannot insert the video video channel with many retries.' + } + + retryTransactionWrapper(addVideoChannel, options) + .then(() => { + // TODO : include Location of the new video channel -> 201 + res.type('json').status(204).end() + }) + .catch(err => next(err)) +} + +function addVideoChannel (req: express.Request, res: express.Response) { + const videoChannelInfo: VideoChannelCreate = req.body + const author: AuthorInstance = res.locals.oauth.token.User.Author + + return db.sequelize.transaction(t => { + return createVideoChannel(videoChannelInfo, author, t) + }) + .then(videoChannelUUID => logger.info('Video channel with uuid %s created.', videoChannelUUID)) + .catch((err: Error) => { + logger.debug('Cannot insert the video channel.', err) + throw err + }) +} + +function updateVideoChannelRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) { + const options = { + arguments: [ req, res ], + errorMessage: 'Cannot update the video with many retries.' + } + + retryTransactionWrapper(updateVideoChannel, options) + .then(() => res.type('json').status(204).end()) + .catch(err => next(err)) +} + +function updateVideoChannel (req: express.Request, res: express.Response) { + const videoChannelInstance: VideoChannelInstance = res.locals.videoChannel + const videoChannelFieldsSave = videoChannelInstance.toJSON() + const videoChannelInfoToUpdate: VideoChannelUpdate = req.body + + return db.sequelize.transaction(t => { + const options = { + transaction: t + } + + if (videoChannelInfoToUpdate.name !== undefined) videoChannelInstance.set('name', videoChannelInfoToUpdate.name) + if (videoChannelInfoToUpdate.description !== undefined) videoChannelInstance.set('description', videoChannelInfoToUpdate.description) + + return videoChannelInstance.save(options) + .then(() => { + const json = videoChannelInstance.toUpdateRemoteJSON() + + // Now we'll update the video channel's meta data to our friends + return updateVideoChannelToFriends(json, t) + }) + }) + .then(() => { + logger.info('Video channel with name %s and uuid %s updated.', videoChannelInstance.name, videoChannelInstance.uuid) + }) + .catch(err => { + logger.debug('Cannot update the video channel.', err) + + // Force fields we want to update + // If the transaction is retried, sequelize will think the object has not changed + // So it will skip the SQL request, even if the last one was ROLLBACKed! + Object.keys(videoChannelFieldsSave).forEach(key => { + const value = videoChannelFieldsSave[key] + videoChannelInstance.set(key, value) + }) + + throw err + }) +} + +function removeVideoChannelRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) { + const options = { + arguments: [ req, res ], + errorMessage: 'Cannot remove the video channel with many retries.' + } + + retryTransactionWrapper(removeVideoChannel, options) + .then(() => res.type('json').status(204).end()) + .catch(err => next(err)) +} + +function removeVideoChannel (req: express.Request, res: express.Response) { + const videoChannelInstance: VideoChannelInstance = res.locals.videoChannel + + return db.sequelize.transaction(t => { + return videoChannelInstance.destroy({ transaction: t }) + }) + .then(() => { + logger.info('Video channel with name %s and uuid %s deleted.', videoChannelInstance.name, videoChannelInstance.uuid) + }) + .catch(err => { + logger.error('Errors when removed the video channel.', err) + throw err + }) +} + +function getVideoChannel (req: express.Request, res: express.Response, next: express.NextFunction) { + db.VideoChannel.loadAndPopulateAuthorAndVideos(res.locals.videoChannel.id) + .then(videoChannelWithVideos => res.json(videoChannelWithVideos.toFormattedJSON())) + .catch(err => next(err)) +} 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' import { abuseVideoRouter } from './abuse' import { blacklistRouter } from './blacklist' import { rateVideoRouter } from './rate' +import { videoChannelRouter } from './channel' const videosRouter = express.Router() @@ -76,6 +77,7 @@ const reqFiles = multer({ storage: storage }).fields([{ name: 'videofile', maxCo videosRouter.use('/', abuseVideoRouter) videosRouter.use('/', blacklistRouter) videosRouter.use('/', rateVideoRouter) +videosRouter.use('/', videoChannelRouter) videosRouter.get('/categories', listVideoCategories) videosRouter.get('/licences', listVideoLicences) @@ -161,21 +163,13 @@ function addVideo (req: express.Request, res: express.Response, videoPhysicalFil let videoUUID = '' return db.sequelize.transaction(t => { - const user = res.locals.oauth.token.User + let p: Promise - const name = user.username - // null because it is OUR pod - const podId = null - const userId = user.id + if (!videoInfo.tags) p = Promise.resolve(undefined) + else p = db.Tag.findOrCreateTags(videoInfo.tags, t) - return db.Author.findOrCreateAuthor(name, podId, userId, t) - .then(author => { - const tags = videoInfo.tags - if (!tags) return { author, tagInstances: undefined } - - return db.Tag.findOrCreateTags(tags, t).then(tagInstances => ({ author, tagInstances })) - }) - .then(({ author, tagInstances }) => { + return p + .then(tagInstances => { const videoData = { name: videoInfo.name, remote: false, @@ -186,18 +180,18 @@ function addVideo (req: express.Request, res: express.Response, videoPhysicalFil nsfw: videoInfo.nsfw, description: videoInfo.description, duration: videoPhysicalFile['duration'], // duration was added by a previous middleware - authorId: author.id + channelId: res.locals.videoChannel.id } const video = db.Video.build(videoData) - return { author, tagInstances, video } + return { tagInstances, video } }) - .then(({ author, tagInstances, video }) => { + .then(({ tagInstances, video }) => { const videoFilePath = join(CONFIG.STORAGE.VIDEOS_DIR, videoPhysicalFile.filename) return getVideoFileHeight(videoFilePath) - .then(height => ({ author, tagInstances, video, videoFileHeight: height })) + .then(height => ({ tagInstances, video, videoFileHeight: height })) }) - .then(({ author, tagInstances, video, videoFileHeight }) => { + .then(({ tagInstances, video, videoFileHeight }) => { const videoFileData = { extname: extname(videoPhysicalFile.filename), resolution: videoFileHeight, @@ -205,9 +199,9 @@ function addVideo (req: express.Request, res: express.Response, videoPhysicalFil } const videoFile = db.VideoFile.build(videoFileData) - return { author, tagInstances, video, videoFile } + return { tagInstances, video, videoFile } }) - .then(({ author, tagInstances, video, videoFile }) => { + .then(({ tagInstances, video, videoFile }) => { const videoDir = CONFIG.STORAGE.VIDEOS_DIR const source = join(videoDir, videoPhysicalFile.filename) const destination = join(videoDir, video.getVideoFilename(videoFile)) @@ -216,10 +210,10 @@ function addVideo (req: express.Request, res: express.Response, videoPhysicalFil .then(() => { // This is important in case if there is another attempt in the retry process videoPhysicalFile.filename = video.getVideoFilename(videoFile) - return { author, tagInstances, video, videoFile } + return { tagInstances, video, videoFile } }) }) - .then(({ author, tagInstances, video, videoFile }) => { + .then(({ tagInstances, video, videoFile }) => { const tasks = [] tasks.push( @@ -239,15 +233,15 @@ function addVideo (req: express.Request, res: express.Response, videoPhysicalFil ) } - return Promise.all(tasks).then(() => ({ author, tagInstances, video, videoFile })) + return Promise.all(tasks).then(() => ({ tagInstances, video, videoFile })) }) - .then(({ author, tagInstances, video, videoFile }) => { + .then(({ tagInstances, video, videoFile }) => { const options = { transaction: t } return video.save(options) .then(videoCreated => { - // Do not forget to add Author information to the created video - videoCreated.Author = author + // Do not forget to add video channel information to the created video + videoCreated.VideoChannel = res.locals.videoChannel videoUUID = videoCreated.uuid return { tagInstances, video: videoCreated, videoFile } @@ -392,7 +386,7 @@ function getVideo (req: express.Request, res: express.Response) { } // Do not wait the view system - res.json(videoInstance.toFormattedJSON()) + res.json(videoInstance.toFormattedDetailsJSON()) } function listVideos (req: express.Request, res: express.Response, next: express.NextFunction) { -- cgit v1.2.3