aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/controllers/api/videos
diff options
context:
space:
mode:
Diffstat (limited to 'server/controllers/api/videos')
-rw-r--r--server/controllers/api/videos/abuse.ts78
-rw-r--r--server/controllers/api/videos/blacklist.ts10
-rw-r--r--server/controllers/api/videos/index.ts381
-rw-r--r--server/controllers/api/videos/rate.ts207
4 files changed, 282 insertions, 394 deletions
diff --git a/server/controllers/api/videos/abuse.ts b/server/controllers/api/videos/abuse.ts
index 78e8e8b3d..fcbd5465f 100644
--- a/server/controllers/api/videos/abuse.ts
+++ b/server/controllers/api/videos/abuse.ts
@@ -1,16 +1,11 @@
1import * as express from 'express' 1import * as express from 'express'
2import * as Sequelize from 'sequelize'
3import { waterfall } from 'async'
4 2
5import { database as db } from '../../../initializers/database' 3import { database as db } from '../../../initializers/database'
6import * as friends from '../../../lib/friends' 4import * as friends from '../../../lib/friends'
7import { 5import {
8 logger, 6 logger,
9 getFormatedObjects, 7 getFormatedObjects,
10 retryTransactionWrapper, 8 retryTransactionWrapper
11 startSerializableTransaction,
12 commitTransaction,
13 rollbackTransaction
14} from '../../../helpers' 9} from '../../../helpers'
15import { 10import {
16 authenticate, 11 authenticate,
@@ -21,6 +16,7 @@ import {
21 setVideoAbusesSort, 16 setVideoAbusesSort,
22 setPagination 17 setPagination
23} from '../../../middlewares' 18} from '../../../middlewares'
19import { VideoInstance } from '../../../models'
24 20
25const abuseVideoRouter = express.Router() 21const abuseVideoRouter = express.Router()
26 22
@@ -48,11 +44,9 @@ export {
48// --------------------------------------------------------------------------- 44// ---------------------------------------------------------------------------
49 45
50function listVideoAbuses (req: express.Request, res: express.Response, next: express.NextFunction) { 46function listVideoAbuses (req: express.Request, res: express.Response, next: express.NextFunction) {
51 db.VideoAbuse.listForApi(req.query.start, req.query.count, req.query.sort, function (err, abusesList, abusesTotal) { 47 db.VideoAbuse.listForApi(req.query.start, req.query.count, req.query.sort)
52 if (err) return next(err) 48 .then(result => res.json(getFormatedObjects(result.data, result.total)))
53 49 .catch(err => next(err))
54 res.json(getFormatedObjects(abusesList, abusesTotal))
55 })
56} 50}
57 51
58function reportVideoAbuseRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) { 52function reportVideoAbuseRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) {
@@ -61,14 +55,12 @@ function reportVideoAbuseRetryWrapper (req: express.Request, res: express.Respon
61 errorMessage: 'Cannot report abuse to the video with many retries.' 55 errorMessage: 'Cannot report abuse to the video with many retries.'
62 } 56 }
63 57
64 retryTransactionWrapper(reportVideoAbuse, options, function (err) { 58 retryTransactionWrapper(reportVideoAbuse, options)
65 if (err) return next(err) 59 .then(() => res.type('json').status(204).end())
66 60 .catch(err => next(err))
67 return res.type('json').status(204).end()
68 })
69} 61}
70 62
71function reportVideoAbuse (req: express.Request, res: express.Response, finalCallback: (err: Error) => void) { 63function reportVideoAbuse (req: express.Request, res: express.Response) {
72 const videoInstance = res.locals.video 64 const videoInstance = res.locals.video
73 const reporterUsername = res.locals.oauth.token.User.username 65 const reporterUsername = res.locals.oauth.token.User.username
74 66
@@ -79,40 +71,26 @@ function reportVideoAbuse (req: express.Request, res: express.Response, finalCal
79 reporterPodId: null // This is our pod that reported this abuse 71 reporterPodId: null // This is our pod that reported this abuse
80 } 72 }
81 73
82 waterfall([ 74 return db.sequelize.transaction(t => {
83 75 return db.VideoAbuse.create(abuse, { transaction: t })
84 startSerializableTransaction, 76 .then(abuse => {
85 77 // We send the information to the destination pod
86 function createAbuse (t, callback) { 78 if (videoInstance.isOwned() === false) {
87 db.VideoAbuse.create(abuse).asCallback(function (err, abuse) { 79 const reportData = {
88 return callback(err, t, abuse) 80 reporterUsername,
89 }) 81 reportReason: abuse.reason,
90 }, 82 videoRemoteId: videoInstance.remoteId
91 83 }
92 function sendToFriendsIfNeeded (t, abuse, callback) { 84
93 // We send the information to the destination pod 85 return friends.reportAbuseVideoToFriend(reportData, videoInstance, t).then(() => videoInstance)
94 if (videoInstance.isOwned() === false) {
95 const reportData = {
96 reporterUsername,
97 reportReason: abuse.reason,
98 videoRemoteId: videoInstance.remoteId
99 } 86 }
100 87
101 friends.reportAbuseVideoToFriend(reportData, videoInstance) 88 return videoInstance
102 } 89 })
103 90 })
104 return callback(null, t) 91 .then((videoInstance: VideoInstance) => logger.info('Abuse report for video %s created.', videoInstance.name))
105 }, 92 .catch(err => {
106 93 logger.debug('Cannot update the video.', { error: err })
107 commitTransaction 94 throw err
108
109 ], function andFinally (err: Error, t: Sequelize.Transaction) {
110 if (err) {
111 logger.debug('Cannot update the video.', { error: err })
112 return rollbackTransaction(err, t, finalCallback)
113 }
114
115 logger.info('Abuse report for video %s created.', videoInstance.name)
116 return finalCallback(null)
117 }) 95 })
118} 96}
diff --git a/server/controllers/api/videos/blacklist.ts b/server/controllers/api/videos/blacklist.ts
index 4b42fc2d7..e4be6f0f9 100644
--- a/server/controllers/api/videos/blacklist.ts
+++ b/server/controllers/api/videos/blacklist.ts
@@ -32,12 +32,10 @@ function addVideoToBlacklist (req: express.Request, res: express.Response, next:
32 videoId: videoInstance.id 32 videoId: videoInstance.id
33 } 33 }
34 34
35 db.BlacklistedVideo.create(toCreate).asCallback(function (err) { 35 db.BlacklistedVideo.create(toCreate)
36 if (err) { 36 .then(() => res.type('json').status(204).end())
37 .catch(err => {
37 logger.error('Errors when blacklisting video ', { error: err }) 38 logger.error('Errors when blacklisting video ', { error: err })
38 return next(err) 39 return next(err)
39 } 40 })
40
41 return res.type('json').status(204).end()
42 })
43} 41}
diff --git a/server/controllers/api/videos/index.ts b/server/controllers/api/videos/index.ts
index 5e8cf2d25..ed1f21d66 100644
--- a/server/controllers/api/videos/index.ts
+++ b/server/controllers/api/videos/index.ts
@@ -1,9 +1,7 @@
1import * as express from 'express' 1import * as express from 'express'
2import * as Sequelize from 'sequelize' 2import * as Promise from 'bluebird'
3import * as fs from 'fs'
4import * as multer from 'multer' 3import * as multer from 'multer'
5import * as path from 'path' 4import * as path from 'path'
6import { waterfall } from 'async'
7 5
8import { database as db } from '../../../initializers/database' 6import { database as db } from '../../../initializers/database'
9import { 7import {
@@ -35,13 +33,12 @@ import {
35} from '../../../middlewares' 33} from '../../../middlewares'
36import { 34import {
37 logger, 35 logger,
38 commitTransaction,
39 retryTransactionWrapper, 36 retryTransactionWrapper,
40 rollbackTransaction,
41 startSerializableTransaction,
42 generateRandomString, 37 generateRandomString,
43 getFormatedObjects 38 getFormatedObjects,
39 renamePromise
44} from '../../../helpers' 40} from '../../../helpers'
41import { TagInstance } from '../../../models'
45 42
46import { abuseVideoRouter } from './abuse' 43import { abuseVideoRouter } from './abuse'
47import { blacklistRouter } from './blacklist' 44import { blacklistRouter } from './blacklist'
@@ -60,10 +57,15 @@ const storage = multer.diskStorage({
60 if (file.mimetype === 'video/webm') extension = 'webm' 57 if (file.mimetype === 'video/webm') extension = 'webm'
61 else if (file.mimetype === 'video/mp4') extension = 'mp4' 58 else if (file.mimetype === 'video/mp4') extension = 'mp4'
62 else if (file.mimetype === 'video/ogg') extension = 'ogv' 59 else if (file.mimetype === 'video/ogg') extension = 'ogv'
63 generateRandomString(16, function (err, randomString) { 60 generateRandomString(16)
64 const fieldname = err ? undefined : randomString 61 .then(randomString => {
65 cb(null, fieldname + '.' + extension) 62 const filename = randomString
66 }) 63 cb(null, filename + '.' + extension)
64 })
65 .catch(err => {
66 logger.error('Cannot generate random string for file name.', { error: err })
67 throw err
68 })
67 } 69 }
68}) 70})
69 71
@@ -144,125 +146,97 @@ function addVideoRetryWrapper (req: express.Request, res: express.Response, next
144 errorMessage: 'Cannot insert the video with many retries.' 146 errorMessage: 'Cannot insert the video with many retries.'
145 } 147 }
146 148
147 retryTransactionWrapper(addVideo, options, function (err) { 149 retryTransactionWrapper(addVideo, options)
148 if (err) return next(err) 150 .then(() => {
149 151 // TODO : include Location of the new video -> 201
150 // TODO : include Location of the new video -> 201 152 res.type('json').status(204).end()
151 return res.type('json').status(204).end() 153 })
152 }) 154 .catch(err => next(err))
153} 155}
154 156
155function addVideo (req: express.Request, res: express.Response, videoFile: Express.Multer.File, finalCallback: (err: Error) => void) { 157function addVideo (req: express.Request, res: express.Response, videoFile: Express.Multer.File) {
156 const videoInfos = req.body 158 const videoInfos = req.body
157 159
158 waterfall([ 160 return db.sequelize.transaction(t => {
159 161 const user = res.locals.oauth.token.User
160 startSerializableTransaction,
161 162
162 function findOrCreateAuthor (t, callback) { 163 const name = user.username
163 const user = res.locals.oauth.token.User 164 // null because it is OUR pod
165 const podId = null
166 const userId = user.id
164 167
165 const name = user.username 168 return db.Author.findOrCreateAuthor(name, podId, userId, t)
166 // null because it is OUR pod 169 .then(author => {
167 const podId = null 170 const tags = videoInfos.tags
168 const userId = user.id 171 if (!tags) return { author, tagInstances: undefined }
169 172
170 db.Author.findOrCreateAuthor(name, podId, userId, t, function (err, authorInstance) { 173 return db.Tag.findOrCreateTags(tags, t).then(tagInstances => ({ author, tagInstances }))
171 return callback(err, t, authorInstance)
172 }) 174 })
173 }, 175 .then(({ author, tagInstances }) => {
174 176 const videoData = {
175 function findOrCreateTags (t, author, callback) { 177 name: videoInfos.name,
176 const tags = videoInfos.tags 178 remoteId: null,
177 179 extname: path.extname(videoFile.filename),
178 db.Tag.findOrCreateTags(tags, t, function (err, tagInstances) { 180 category: videoInfos.category,
179 return callback(err, t, author, tagInstances) 181 licence: videoInfos.licence,
182 language: videoInfos.language,
183 nsfw: videoInfos.nsfw,
184 description: videoInfos.description,
185 duration: videoFile['duration'], // duration was added by a previous middleware
186 authorId: author.id
187 }
188
189 const video = db.Video.build(videoData)
190 return { author, tagInstances, video }
180 }) 191 })
181 }, 192 .then(({ author, tagInstances, video }) => {
182 193 const videoDir = CONFIG.STORAGE.VIDEOS_DIR
183 function createVideoObject (t, author, tagInstances, callback) { 194 const source = path.join(videoDir, videoFile.filename)
184 const videoData = { 195 const destination = path.join(videoDir, video.getVideoFilename())
185 name: videoInfos.name, 196
186 remoteId: null, 197 return renamePromise(source, destination)
187 extname: path.extname(videoFile.filename), 198 .then(() => {
188 category: videoInfos.category, 199 // This is important in case if there is another attempt in the retry process
189 licence: videoInfos.licence, 200 videoFile.filename = video.getVideoFilename()
190 language: videoInfos.language, 201 return { author, tagInstances, video }
191 nsfw: videoInfos.nsfw, 202 })
192 description: videoInfos.description,
193 duration: videoFile['duration'], // duration was added by a previous middleware
194 authorId: author.id
195 }
196
197 const video = db.Video.build(videoData)
198
199 return callback(null, t, author, tagInstances, video)
200 },
201
202 // Set the videoname the same as the id
203 function renameVideoFile (t, author, tagInstances, video, callback) {
204 const videoDir = CONFIG.STORAGE.VIDEOS_DIR
205 const source = path.join(videoDir, videoFile.filename)
206 const destination = path.join(videoDir, video.getVideoFilename())
207
208 fs.rename(source, destination, function (err) {
209 if (err) return callback(err)
210
211 // This is important in case if there is another attempt
212 videoFile.filename = video.getVideoFilename()
213 return callback(null, t, author, tagInstances, video)
214 }) 203 })
215 }, 204 .then(({ author, tagInstances, video }) => {
216 205 const options = { transaction: t }
217 function insertVideoIntoDB (t, author, tagInstances, video, callback) {
218 const options = { transaction: t }
219
220 // Add tags association
221 video.save(options).asCallback(function (err, videoCreated) {
222 if (err) return callback(err)
223 206
224 // Do not forget to add Author informations to the created video 207 return video.save(options)
225 videoCreated.Author = author 208 .then(videoCreated => {
209 // Do not forget to add Author informations to the created video
210 videoCreated.Author = author
226 211
227 return callback(err, t, tagInstances, videoCreated) 212 return { tagInstances, video: videoCreated }
213 })
228 }) 214 })
229 }, 215 .then(({ tagInstances, video }) => {
230 216 if (!tagInstances) return video
231 function associateTagsToVideo (t, tagInstances, video, callback) {
232 const options = { transaction: t }
233 217
234 video.setTags(tagInstances, options).asCallback(function (err) { 218 const options = { transaction: t }
235 video.Tags = tagInstances 219 return video.setTags(tagInstances, options)
236 220 .then(() => {
237 return callback(err, t, video) 221 video.Tags = tagInstances
222 return video
223 })
238 }) 224 })
239 }, 225 .then(video => {
240 226 // Let transcoding job send the video to friends because the videofile extension might change
241 function sendToFriends (t, video, callback) { 227 if (CONFIG.TRANSCODING.ENABLED === true) return undefined
242 // Let transcoding job send the video to friends because the videofile extension might change 228
243 if (CONFIG.TRANSCODING.ENABLED === true) return callback(null, t) 229 return video.toAddRemoteJSON()
244 230 .then(remoteVideo => {
245 video.toAddRemoteJSON(function (err, remoteVideo) { 231 // Now we'll add the video's meta data to our friends
246 if (err) return callback(err) 232 return addVideoToFriends(remoteVideo, t)
247 233 })
248 // Now we'll add the video's meta data to our friends
249 addVideoToFriends(remoteVideo, t, function (err) {
250 return callback(err, t)
251 })
252 }) 234 })
253 }, 235 })
254 236 .then(() => logger.info('Video with name %s created.', videoInfos.name))
255 commitTransaction 237 .catch((err: Error) => {
256 238 logger.debug('Cannot insert the video.', { error: err.stack })
257 ], function andFinally (err: Error, t: Sequelize.Transaction) { 239 throw err
258 if (err) {
259 // This is just a debug because we will retry the insert
260 logger.debug('Cannot insert the video.', { error: err })
261 return rollbackTransaction(err, t, finalCallback)
262 }
263
264 logger.info('Video with name %s created.', videoInfos.name)
265 return finalCallback(null)
266 }) 240 })
267} 241}
268 242
@@ -272,92 +246,75 @@ function updateVideoRetryWrapper (req: express.Request, res: express.Response, n
272 errorMessage: 'Cannot update the video with many retries.' 246 errorMessage: 'Cannot update the video with many retries.'
273 } 247 }
274 248
275 retryTransactionWrapper(updateVideo, options, function (err) { 249 retryTransactionWrapper(updateVideo, options)
276 if (err) return next(err) 250 .then(() => {
277 251 // TODO : include Location of the new video -> 201
278 // TODO : include Location of the new video -> 201 252 return res.type('json').status(204).end()
279 return res.type('json').status(204).end() 253 })
280 }) 254 .catch(err => next(err))
281} 255}
282 256
283function updateVideo (req: express.Request, res: express.Response, finalCallback: (err: Error) => void) { 257function updateVideo (req: express.Request, res: express.Response) {
284 const videoInstance = res.locals.video 258 const videoInstance = res.locals.video
285 const videoFieldsSave = videoInstance.toJSON() 259 const videoFieldsSave = videoInstance.toJSON()
286 const videoInfosToUpdate = req.body 260 const videoInfosToUpdate = req.body
287 261
288 waterfall([ 262 return db.sequelize.transaction(t => {
289 263 let tagsPromise: Promise<TagInstance[]>
290 startSerializableTransaction, 264 if (!videoInfosToUpdate.tags) {
291 265 tagsPromise = Promise.resolve(null)
292 function findOrCreateTags (t, callback) { 266 } else {
293 if (videoInfosToUpdate.tags) { 267 tagsPromise = db.Tag.findOrCreateTags(videoInfosToUpdate.tags, t)
294 db.Tag.findOrCreateTags(videoInfosToUpdate.tags, t, function (err, tagInstances) { 268 }
295 return callback(err, t, tagInstances)
296 })
297 } else {
298 return callback(null, t, null)
299 }
300 },
301
302 function updateVideoIntoDB (t, tagInstances, callback) {
303 const options = {
304 transaction: t
305 }
306
307 if (videoInfosToUpdate.name !== undefined) videoInstance.set('name', videoInfosToUpdate.name)
308 if (videoInfosToUpdate.category !== undefined) videoInstance.set('category', videoInfosToUpdate.category)
309 if (videoInfosToUpdate.licence !== undefined) videoInstance.set('licence', videoInfosToUpdate.licence)
310 if (videoInfosToUpdate.language !== undefined) videoInstance.set('language', videoInfosToUpdate.language)
311 if (videoInfosToUpdate.nsfw !== undefined) videoInstance.set('nsfw', videoInfosToUpdate.nsfw)
312 if (videoInfosToUpdate.description !== undefined) videoInstance.set('description', videoInfosToUpdate.description)
313
314 videoInstance.save(options).asCallback(function (err) {
315 return callback(err, t, tagInstances)
316 })
317 },
318
319 function associateTagsToVideo (t, tagInstances, callback) {
320 if (tagInstances) {
321 const options = { transaction: t }
322
323 videoInstance.setTags(tagInstances, options).asCallback(function (err) {
324 videoInstance.Tags = tagInstances
325 269
326 return callback(err, t) 270 return tagsPromise
327 }) 271 .then(tagInstances => {
328 } else { 272 const options = {
329 return callback(null, t) 273 transaction: t
330 } 274 }
331 },
332 275
333 function sendToFriends (t, callback) { 276 if (videoInfosToUpdate.name !== undefined) videoInstance.set('name', videoInfosToUpdate.name)
334 const json = videoInstance.toUpdateRemoteJSON() 277 if (videoInfosToUpdate.category !== undefined) videoInstance.set('category', videoInfosToUpdate.category)
278 if (videoInfosToUpdate.licence !== undefined) videoInstance.set('licence', videoInfosToUpdate.licence)
279 if (videoInfosToUpdate.language !== undefined) videoInstance.set('language', videoInfosToUpdate.language)
280 if (videoInfosToUpdate.nsfw !== undefined) videoInstance.set('nsfw', videoInfosToUpdate.nsfw)
281 if (videoInfosToUpdate.description !== undefined) videoInstance.set('description', videoInfosToUpdate.description)
335 282
336 // Now we'll update the video's meta data to our friends 283 return videoInstance.save(options).then(() => tagInstances)
337 updateVideoToFriends(json, t, function (err) {
338 return callback(err, t)
339 }) 284 })
340 }, 285 .then(tagInstances => {
341 286 if (!tagInstances) return
342 commitTransaction
343 287
344 ], function andFinally (err: Error, t: Sequelize.Transaction) { 288 const options = { transaction: t }
345 if (err) { 289 return videoInstance.setTags(tagInstances, options)
346 logger.debug('Cannot update the video.', { error: err }) 290 .then(() => {
291 videoInstance.Tags = tagInstances
347 292
348 // Force fields we want to update 293 return
349 // If the transaction is retried, sequelize will think the object has not changed 294 })
350 // So it will skip the SQL request, even if the last one was ROLLBACKed!
351 Object.keys(videoFieldsSave).forEach(function (key) {
352 const value = videoFieldsSave[key]
353 videoInstance.set(key, value)
354 }) 295 })
296 .then(() => {
297 const json = videoInstance.toUpdateRemoteJSON()
355 298
356 return rollbackTransaction(err, t, finalCallback) 299 // Now we'll update the video's meta data to our friends
357 } 300 return updateVideoToFriends(json, t)
358 301 })
302 })
303 .then(() => {
359 logger.info('Video with name %s updated.', videoInstance.name) 304 logger.info('Video with name %s updated.', videoInstance.name)
360 return finalCallback(null) 305 })
306 .catch(err => {
307 logger.debug('Cannot update the video.', { error: err })
308
309 // Force fields we want to update
310 // If the transaction is retried, sequelize will think the object has not changed
311 // So it will skip the SQL request, even if the last one was ROLLBACKed!
312 Object.keys(videoFieldsSave).forEach(function (key) {
313 const value = videoFieldsSave[key]
314 videoInstance.set(key, value)
315 })
316
317 throw err
361 }) 318 })
362} 319}
363 320
@@ -366,20 +323,17 @@ function getVideo (req: express.Request, res: express.Response, next: express.Ne
366 323
367 if (videoInstance.isOwned()) { 324 if (videoInstance.isOwned()) {
368 // The increment is done directly in the database, not using the instance value 325 // The increment is done directly in the database, not using the instance value
369 videoInstance.increment('views').asCallback(function (err) { 326 videoInstance.increment('views')
370 if (err) { 327 .then(() => {
371 logger.error('Cannot add view to video %d.', videoInstance.id) 328 // FIXME: make a real view system
372 return 329 // For example, only add a view when a user watch a video during 30s etc
373 } 330 const qaduParams = {
374 331 videoId: videoInstance.id,
375 // FIXME: make a real view system 332 type: REQUEST_VIDEO_QADU_TYPES.VIEWS
376 // For example, only add a view when a user watch a video during 30s etc 333 }
377 const qaduParams = { 334 return quickAndDirtyUpdateVideoToFriends(qaduParams)
378 videoId: videoInstance.id, 335 })
379 type: REQUEST_VIDEO_QADU_TYPES.VIEWS 336 .catch(err => logger.error('Cannot add view to video %d.', videoInstance.id, { error: err }))
380 }
381 quickAndDirtyUpdateVideoToFriends(qaduParams)
382 })
383 } else { 337 } else {
384 // Just send the event to our friends 338 // Just send the event to our friends
385 const eventParams = { 339 const eventParams = {
@@ -394,33 +348,24 @@ function getVideo (req: express.Request, res: express.Response, next: express.Ne
394} 348}
395 349
396function listVideos (req: express.Request, res: express.Response, next: express.NextFunction) { 350function listVideos (req: express.Request, res: express.Response, next: express.NextFunction) {
397 db.Video.listForApi(req.query.start, req.query.count, req.query.sort, function (err, videosList, videosTotal) { 351 db.Video.listForApi(req.query.start, req.query.count, req.query.sort)
398 if (err) return next(err) 352 .then(result => res.json(getFormatedObjects(result.data, result.total)))
399 353 .catch(err => next(err))
400 res.json(getFormatedObjects(videosList, videosTotal))
401 })
402} 354}
403 355
404function removeVideo (req: express.Request, res: express.Response, next: express.NextFunction) { 356function removeVideo (req: express.Request, res: express.Response, next: express.NextFunction) {
405 const videoInstance = res.locals.video 357 const videoInstance = res.locals.video
406 358
407 videoInstance.destroy().asCallback(function (err) { 359 videoInstance.destroy()
408 if (err) { 360 .then(() => res.type('json').status(204).end())
361 .catch(err => {
409 logger.error('Errors when removed the video.', { error: err }) 362 logger.error('Errors when removed the video.', { error: err })
410 return next(err) 363 return next(err)
411 } 364 })
412
413 return res.type('json').status(204).end()
414 })
415} 365}
416 366
417function searchVideos (req: express.Request, res: express.Response, next: express.NextFunction) { 367function searchVideos (req: express.Request, res: express.Response, next: express.NextFunction) {
418 db.Video.searchAndPopulateAuthorAndPodAndTags( 368 db.Video.searchAndPopulateAuthorAndPodAndTags(req.params.value, req.query.field, req.query.start, req.query.count, req.query.sort)
419 req.params.value, req.query.field, req.query.start, req.query.count, req.query.sort, 369 .then(result => res.json(getFormatedObjects(result.data, result.total)))
420 function (err, videosList, videosTotal) { 370 .catch(err => next(err))
421 if (err) return next(err)
422
423 res.json(getFormatedObjects(videosList, videosTotal))
424 }
425 )
426} 371}
diff --git a/server/controllers/api/videos/rate.ts b/server/controllers/api/videos/rate.ts
index afdd099f8..3d119d98b 100644
--- a/server/controllers/api/videos/rate.ts
+++ b/server/controllers/api/videos/rate.ts
@@ -1,14 +1,9 @@
1import * as express from 'express' 1import * as express from 'express'
2import * as Sequelize from 'sequelize'
3import { waterfall } from 'async'
4 2
5import { database as db } from '../../../initializers/database' 3import { database as db } from '../../../initializers/database'
6import { 4import {
7 logger, 5 logger,
8 retryTransactionWrapper, 6 retryTransactionWrapper
9 startSerializableTransaction,
10 commitTransaction,
11 rollbackTransaction
12} from '../../../helpers' 7} from '../../../helpers'
13import { 8import {
14 VIDEO_RATE_TYPES, 9 VIDEO_RATE_TYPES,
@@ -46,137 +41,109 @@ function rateVideoRetryWrapper (req: express.Request, res: express.Response, nex
46 errorMessage: 'Cannot update the user video rate.' 41 errorMessage: 'Cannot update the user video rate.'
47 } 42 }
48 43
49 retryTransactionWrapper(rateVideo, options, function (err) { 44 retryTransactionWrapper(rateVideo, options)
50 if (err) return next(err) 45 .then(() => res.type('json').status(204).end())
51 46 .catch(err => next(err))
52 return res.type('json').status(204).end()
53 })
54} 47}
55 48
56function rateVideo (req: express.Request, res: express.Response, finalCallback: (err: Error) => void) { 49function rateVideo (req: express.Request, res: express.Response) {
57 const rateType = req.body.rating 50 const rateType = req.body.rating
58 const videoInstance = res.locals.video 51 const videoInstance = res.locals.video
59 const userInstance = res.locals.oauth.token.User 52 const userInstance = res.locals.oauth.token.User
60 53
61 waterfall([ 54 return db.sequelize.transaction(t => {
62 startSerializableTransaction, 55 return db.UserVideoRate.load(userInstance.id, videoInstance.id, t)
63 56 .then(previousRate => {
64 function findPreviousRate (t, callback) { 57 const options = { transaction: t }
65 db.UserVideoRate.load(userInstance.id, videoInstance.id, t, function (err, previousRate) {
66 return callback(err, t, previousRate)
67 })
68 },
69 58
70 function insertUserRateIntoDB (t, previousRate, callback) { 59 let likesToIncrement = 0
71 const options = { transaction: t } 60 let dislikesToIncrement = 0
72 61
73 let likesToIncrement = 0 62 if (rateType === VIDEO_RATE_TYPES.LIKE) likesToIncrement++
74 let dislikesToIncrement = 0 63 else if (rateType === VIDEO_RATE_TYPES.DISLIKE) dislikesToIncrement++
75 64
76 if (rateType === VIDEO_RATE_TYPES.LIKE) likesToIncrement++ 65 // There was a previous rate, update it
77 else if (rateType === VIDEO_RATE_TYPES.DISLIKE) dislikesToIncrement++ 66 if (previousRate) {
67 // We will remove the previous rate, so we will need to remove it from the video attribute
68 if (previousRate.type === VIDEO_RATE_TYPES.LIKE) likesToIncrement--
69 else if (previousRate.type === VIDEO_RATE_TYPES.DISLIKE) dislikesToIncrement--
78 70
79 // There was a previous rate, update it 71 previousRate.type = rateType
80 if (previousRate) {
81 // We will remove the previous rate, so we will need to remove it from the video attribute
82 if (previousRate.type === VIDEO_RATE_TYPES.LIKE) likesToIncrement--
83 else if (previousRate.type === VIDEO_RATE_TYPES.DISLIKE) dislikesToIncrement--
84 72
85 previousRate.type = rateType 73 return previousRate.save(options).then(() => ({ t, likesToIncrement, dislikesToIncrement }))
74 } else { // There was not a previous rate, insert a new one
75 const query = {
76 userId: userInstance.id,
77 videoId: videoInstance.id,
78 type: rateType
79 }
86 80
87 previousRate.save(options).asCallback(function (err) { 81 return db.UserVideoRate.create(query, options).then(() => ({ likesToIncrement, dislikesToIncrement }))
88 return callback(err, t, likesToIncrement, dislikesToIncrement)
89 })
90 } else { // There was not a previous rate, insert a new one
91 const query = {
92 userId: userInstance.id,
93 videoId: videoInstance.id,
94 type: rateType
95 } 82 }
96
97 db.UserVideoRate.create(query, options).asCallback(function (err) {
98 return callback(err, t, likesToIncrement, dislikesToIncrement)
99 })
100 }
101 },
102
103 function updateVideoAttributeDB (t, likesToIncrement, dislikesToIncrement, callback) {
104 const options = { transaction: t }
105 const incrementQuery = {
106 likes: likesToIncrement,
107 dislikes: dislikesToIncrement
108 }
109
110 // Even if we do not own the video we increment the attributes
111 // It is usefull for the user to have a feedback
112 videoInstance.increment(incrementQuery, options).asCallback(function (err) {
113 return callback(err, t, likesToIncrement, dislikesToIncrement)
114 })
115 },
116
117 function sendEventsToFriendsIfNeeded (t, likesToIncrement, dislikesToIncrement, callback) {
118 // No need for an event type, we own the video
119 if (videoInstance.isOwned()) return callback(null, t, likesToIncrement, dislikesToIncrement)
120
121 const eventsParams = []
122
123 if (likesToIncrement !== 0) {
124 eventsParams.push({
125 videoId: videoInstance.id,
126 type: REQUEST_VIDEO_EVENT_TYPES.LIKES,
127 count: likesToIncrement
128 })
129 }
130
131 if (dislikesToIncrement !== 0) {
132 eventsParams.push({
133 videoId: videoInstance.id,
134 type: REQUEST_VIDEO_EVENT_TYPES.DISLIKES,
135 count: dislikesToIncrement
136 })
137 }
138
139 addEventsToRemoteVideo(eventsParams, t, function (err) {
140 return callback(err, t, likesToIncrement, dislikesToIncrement)
141 }) 83 })
142 }, 84 .then(({ likesToIncrement, dislikesToIncrement }) => {
143 85 const options = { transaction: t }
144 function sendQaduToFriendsIfNeeded (t, likesToIncrement, dislikesToIncrement, callback) { 86 const incrementQuery = {
145 // We do not own the video, there is no need to send a quick and dirty update to friends 87 likes: likesToIncrement,
146 // Our rate was already sent by the addEvent function 88 dislikes: dislikesToIncrement
147 if (videoInstance.isOwned() === false) return callback(null, t) 89 }
148 90
149 const qadusParams = [] 91 // Even if we do not own the video we increment the attributes
150 92 // It is usefull for the user to have a feedback
151 if (likesToIncrement !== 0) { 93 return videoInstance.increment(incrementQuery, options).then(() => ({ likesToIncrement, dislikesToIncrement }))
152 qadusParams.push({
153 videoId: videoInstance.id,
154 type: REQUEST_VIDEO_QADU_TYPES.LIKES
155 })
156 }
157
158 if (dislikesToIncrement !== 0) {
159 qadusParams.push({
160 videoId: videoInstance.id,
161 type: REQUEST_VIDEO_QADU_TYPES.DISLIKES
162 })
163 }
164
165 quickAndDirtyUpdatesVideoToFriends(qadusParams, t, function (err) {
166 return callback(err, t)
167 }) 94 })
168 }, 95 .then(({ likesToIncrement, dislikesToIncrement }) => {
96 // No need for an event type, we own the video
97 if (videoInstance.isOwned()) return { likesToIncrement, dislikesToIncrement }
98
99 const eventsParams = []
100
101 if (likesToIncrement !== 0) {
102 eventsParams.push({
103 videoId: videoInstance.id,
104 type: REQUEST_VIDEO_EVENT_TYPES.LIKES,
105 count: likesToIncrement
106 })
107 }
108
109 if (dislikesToIncrement !== 0) {
110 eventsParams.push({
111 videoId: videoInstance.id,
112 type: REQUEST_VIDEO_EVENT_TYPES.DISLIKES,
113 count: dislikesToIncrement
114 })
115 }
169 116
170 commitTransaction 117 return addEventsToRemoteVideo(eventsParams, t).then(() => ({ likesToIncrement, dislikesToIncrement }))
118 })
119 .then(({ likesToIncrement, dislikesToIncrement }) => {
120 // We do not own the video, there is no need to send a quick and dirty update to friends
121 // Our rate was already sent by the addEvent function
122 if (videoInstance.isOwned() === false) return undefined
123
124 const qadusParams = []
125
126 if (likesToIncrement !== 0) {
127 qadusParams.push({
128 videoId: videoInstance.id,
129 type: REQUEST_VIDEO_QADU_TYPES.LIKES
130 })
131 }
171 132
172 ], function (err: Error, t: Sequelize.Transaction) { 133 if (dislikesToIncrement !== 0) {
173 if (err) { 134 qadusParams.push({
174 // This is just a debug because we will retry the insert 135 videoId: videoInstance.id,
175 logger.debug('Cannot add the user video rate.', { error: err }) 136 type: REQUEST_VIDEO_QADU_TYPES.DISLIKES
176 return rollbackTransaction(err, t, finalCallback) 137 })
177 } 138 }
178 139
179 logger.info('User video rate for video %s of user %s updated.', videoInstance.name, userInstance.username) 140 return quickAndDirtyUpdatesVideoToFriends(qadusParams, t)
180 return finalCallback(null) 141 })
142 })
143 .then(() => logger.info('User video rate for video %s of user %s updated.', videoInstance.name, userInstance.username))
144 .catch(err => {
145 // This is just a debug because we will retry the insert
146 logger.debug('Cannot add the user video rate.', { error: err })
147 throw err
181 }) 148 })
182} 149}