aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/controllers
diff options
context:
space:
mode:
Diffstat (limited to 'server/controllers')
-rw-r--r--server/controllers/api/oauth-clients.ts21
-rw-r--r--server/controllers/api/pods.ts71
-rw-r--r--server/controllers/api/remote/pods.ts20
-rw-r--r--server/controllers/api/remote/videos.ts528
-rw-r--r--server/controllers/api/request-schedulers.ts38
-rw-r--r--server/controllers/api/users.ts86
-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
-rw-r--r--server/controllers/client.ts26
11 files changed, 586 insertions, 880 deletions
diff --git a/server/controllers/api/oauth-clients.ts b/server/controllers/api/oauth-clients.ts
index b9bc0534f..f7dac598c 100644
--- a/server/controllers/api/oauth-clients.ts
+++ b/server/controllers/api/oauth-clients.ts
@@ -24,16 +24,17 @@ function getLocalClient (req: express.Request, res: express.Response, next: expr
24 return res.type('json').status(403).end() 24 return res.type('json').status(403).end()
25 } 25 }
26 26
27 db.OAuthClient.loadFirstClient(function (err, client) { 27 db.OAuthClient.loadFirstClient()
28 if (err) return next(err) 28 .then(client => {
29 if (!client) return next(new Error('No client available.')) 29 if (!client) throw new Error('No client available.')
30 30
31 const json: OAuthClientLocal = { 31 const json: OAuthClientLocal = {
32 client_id: client.clientId, 32 client_id: client.clientId,
33 client_secret: client.clientSecret 33 client_secret: client.clientSecret
34 } 34 }
35 res.json(json) 35 res.json(json)
36 }) 36 })
37 .catch(err => next(err))
37} 38}
38 39
39// --------------------------------------------------------------------------- 40// ---------------------------------------------------------------------------
diff --git a/server/controllers/api/pods.ts b/server/controllers/api/pods.ts
index 283105f6c..0f85ab51d 100644
--- a/server/controllers/api/pods.ts
+++ b/server/controllers/api/pods.ts
@@ -1,5 +1,4 @@
1import * as express from 'express' 1import * as express from 'express'
2import { waterfall } from 'async'
3 2
4import { database as db } from '../../initializers/database' 3import { database as db } from '../../initializers/database'
5import { CONFIG } from '../../initializers' 4import { CONFIG } from '../../initializers'
@@ -57,65 +56,39 @@ export {
57function addPods (req: express.Request, res: express.Response, next: express.NextFunction) { 56function addPods (req: express.Request, res: express.Response, next: express.NextFunction) {
58 const informations = req.body 57 const informations = req.body
59 58
60 waterfall<string, Error>([ 59 const pod = db.Pod.build(informations)
61 function addPod (callback) { 60 pod.save()
62 const pod = db.Pod.build(informations) 61 .then(podCreated => {
63 pod.save().asCallback(function (err, podCreated) { 62 return sendOwnedVideosToPod(podCreated.id)
64 // Be sure about the number of parameters for the callback 63 })
65 return callback(err, podCreated) 64 .then(() => {
66 }) 65 return getMyPublicCert()
67 }, 66 })
68 67 .then(cert => {
69 function sendMyVideos (podCreated: PodInstance, callback) { 68 return res.json({ cert: cert, email: CONFIG.ADMIN.EMAIL })
70 sendOwnedVideosToPod(podCreated.id) 69 })
71 70 .catch(err => next(err))
72 callback(null)
73 },
74
75 function fetchMyCertificate (callback) {
76 getMyPublicCert(function (err, cert) {
77 if (err) {
78 logger.error('Cannot read cert file.')
79 return callback(err)
80 }
81
82 return callback(null, cert)
83 })
84 }
85 ], function (err, cert) {
86 if (err) return next(err)
87
88 return res.json({ cert: cert, email: CONFIG.ADMIN.EMAIL })
89 })
90} 71}
91 72
92function listPods (req: express.Request, res: express.Response, next: express.NextFunction) { 73function listPods (req: express.Request, res: express.Response, next: express.NextFunction) {
93 db.Pod.list(function (err, podsList) { 74 db.Pod.list()
94 if (err) return next(err) 75 .then(podsList => res.json(getFormatedObjects(podsList, podsList.length)))
95 76 .catch(err => next(err))
96 res.json(getFormatedObjects(podsList, podsList.length))
97 })
98} 77}
99 78
100function makeFriendsController (req: express.Request, res: express.Response, next: express.NextFunction) { 79function makeFriendsController (req: express.Request, res: express.Response, next: express.NextFunction) {
101 const hosts = req.body.hosts as string[] 80 const hosts = req.body.hosts as string[]
102 81
103 makeFriends(hosts, function (err) { 82 makeFriends(hosts)
104 if (err) { 83 .then(() => logger.info('Made friends!'))
105 logger.error('Could not make friends.', { error: err }) 84 .catch(err => logger.error('Could not make friends.', { error: err }))
106 return
107 }
108
109 logger.info('Made friends!')
110 })
111 85
86 // Don't wait the process that could be long
112 res.type('json').status(204).end() 87 res.type('json').status(204).end()
113} 88}
114 89
115function quitFriendsController (req: express.Request, res: express.Response, next: express.NextFunction) { 90function quitFriendsController (req: express.Request, res: express.Response, next: express.NextFunction) {
116 quitFriends(function (err) { 91 quitFriends()
117 if (err) return next(err) 92 .then(() => res.type('json').status(204).end())
118 93 .catch(err => next(err))
119 res.type('json').status(204).end()
120 })
121} 94}
diff --git a/server/controllers/api/remote/pods.ts b/server/controllers/api/remote/pods.ts
index b0d6642c1..6319957d3 100644
--- a/server/controllers/api/remote/pods.ts
+++ b/server/controllers/api/remote/pods.ts
@@ -1,5 +1,4 @@
1import * as express from 'express' 1import * as express from 'express'
2import * as waterfall from 'async/waterfall'
3 2
4import { database as db } from '../../../initializers/database' 3import { database as db } from '../../../initializers/database'
5import { checkSignature, signatureValidator } from '../../../middlewares' 4import { checkSignature, signatureValidator } from '../../../middlewares'
@@ -24,17 +23,10 @@ export {
24function removePods (req: express.Request, res: express.Response, next: express.NextFunction) { 23function removePods (req: express.Request, res: express.Response, next: express.NextFunction) {
25 const host = req.body.signature.host 24 const host = req.body.signature.host
26 25
27 waterfall([ 26 db.Pod.loadByHost(host)
28 function loadPod (callback) { 27 .then(pod => {
29 db.Pod.loadByHost(host, callback) 28 return pod.destroy()
30 }, 29 })
31 30 .then(() => res.type('json').status(204).end())
32 function deletePod (pod, callback) { 31 .catch(err => next(err))
33 pod.destroy().asCallback(callback)
34 }
35 ], function (err) {
36 if (err) return next(err)
37
38 return res.type('json').status(204).end()
39 })
40} 32}
diff --git a/server/controllers/api/remote/videos.ts b/server/controllers/api/remote/videos.ts
index d9cc08fb4..ebe4eca36 100644
--- a/server/controllers/api/remote/videos.ts
+++ b/server/controllers/api/remote/videos.ts
@@ -1,6 +1,5 @@
1import * as express from 'express' 1import * as express from 'express'
2import * as Sequelize from 'sequelize' 2import * as Promise from 'bluebird'
3import { eachSeries, waterfall } from 'async'
4 3
5import { database as db } from '../../../initializers/database' 4import { database as db } from '../../../initializers/database'
6import { 5import {
@@ -16,20 +15,14 @@ import {
16 remoteQaduVideosValidator, 15 remoteQaduVideosValidator,
17 remoteEventsVideosValidator 16 remoteEventsVideosValidator
18} from '../../../middlewares' 17} from '../../../middlewares'
19import { 18import { logger, retryTransactionWrapper } from '../../../helpers'
20 logger,
21 commitTransaction,
22 retryTransactionWrapper,
23 rollbackTransaction,
24 startSerializableTransaction
25} from '../../../helpers'
26import { quickAndDirtyUpdatesVideoToFriends } from '../../../lib' 19import { quickAndDirtyUpdatesVideoToFriends } from '../../../lib'
27import { PodInstance, VideoInstance } from '../../../models' 20import { PodInstance, VideoInstance } from '../../../models'
28 21
29const ENDPOINT_ACTIONS = REQUEST_ENDPOINT_ACTIONS[REQUEST_ENDPOINTS.VIDEOS] 22const ENDPOINT_ACTIONS = REQUEST_ENDPOINT_ACTIONS[REQUEST_ENDPOINTS.VIDEOS]
30 23
31// Functions to call when processing a remote request 24// Functions to call when processing a remote request
32const functionsHash = {} 25const functionsHash: { [ id: string ]: (...args) => Promise<any> } = {}
33functionsHash[ENDPOINT_ACTIONS.ADD] = addRemoteVideoRetryWrapper 26functionsHash[ENDPOINT_ACTIONS.ADD] = addRemoteVideoRetryWrapper
34functionsHash[ENDPOINT_ACTIONS.UPDATE] = updateRemoteVideoRetryWrapper 27functionsHash[ENDPOINT_ACTIONS.UPDATE] = updateRemoteVideoRetryWrapper
35functionsHash[ENDPOINT_ACTIONS.REMOVE] = removeRemoteVideo 28functionsHash[ENDPOINT_ACTIONS.REMOVE] = removeRemoteVideo
@@ -72,20 +65,19 @@ function remoteVideos (req: express.Request, res: express.Response, next: expres
72 65
73 // We need to process in the same order to keep consistency 66 // We need to process in the same order to keep consistency
74 // TODO: optimization 67 // TODO: optimization
75 eachSeries(requests, function (request: any, callbackEach) { 68 Promise.mapSeries(requests, (request: any) => {
76 const data = request.data 69 const data = request.data
77 70
78 // Get the function we need to call in order to process the request 71 // Get the function we need to call in order to process the request
79 const fun = functionsHash[request.type] 72 const fun = functionsHash[request.type]
80 if (fun === undefined) { 73 if (fun === undefined) {
81 logger.error('Unkown remote request type %s.', request.type) 74 logger.error('Unkown remote request type %s.', request.type)
82 return callbackEach(null) 75 return
83 } 76 }
84 77
85 fun.call(this, data, fromPod, callbackEach) 78 return fun.call(this, data, fromPod)
86 }, function (err) {
87 if (err) logger.error('Error managing remote videos.', { error: err })
88 }) 79 })
80 .catch(err => logger.error('Error managing remote videos.', { error: err }))
89 81
90 // We don't need to keep the other pod waiting 82 // We don't need to keep the other pod waiting
91 return res.type('json').status(204).end() 83 return res.type('json').status(204).end()
@@ -95,13 +87,12 @@ function remoteVideosQadu (req: express.Request, res: express.Response, next: ex
95 const requests = req.body.data 87 const requests = req.body.data
96 const fromPod = res.locals.secure.pod 88 const fromPod = res.locals.secure.pod
97 89
98 eachSeries(requests, function (request: any, callbackEach) { 90 Promise.mapSeries(requests, (request: any) => {
99 const videoData = request.data 91 const videoData = request.data
100 92
101 quickAndDirtyUpdateVideoRetryWrapper(videoData, fromPod, callbackEach) 93 return quickAndDirtyUpdateVideoRetryWrapper(videoData, fromPod)
102 }, function (err) {
103 if (err) logger.error('Error managing remote videos.', { error: err })
104 }) 94 })
95 .catch(err => logger.error('Error managing remote videos.', { error: err }))
105 96
106 return res.type('json').status(204).end() 97 return res.type('json').status(204).end()
107} 98}
@@ -110,414 +101,303 @@ function remoteVideosEvents (req: express.Request, res: express.Response, next:
110 const requests = req.body.data 101 const requests = req.body.data
111 const fromPod = res.locals.secure.pod 102 const fromPod = res.locals.secure.pod
112 103
113 eachSeries(requests, function (request: any, callbackEach) { 104 Promise.mapSeries(requests, (request: any) => {
114 const eventData = request.data 105 const eventData = request.data
115 106
116 processVideosEventsRetryWrapper(eventData, fromPod, callbackEach) 107 return processVideosEventsRetryWrapper(eventData, fromPod)
117 }, function (err) {
118 if (err) logger.error('Error managing remote videos.', { error: err })
119 }) 108 })
109 .catch(err => logger.error('Error managing remote videos.', { error: err }))
120 110
121 return res.type('json').status(204).end() 111 return res.type('json').status(204).end()
122} 112}
123 113
124function processVideosEventsRetryWrapper (eventData: any, fromPod: PodInstance, finalCallback: (err: Error) => void) { 114function processVideosEventsRetryWrapper (eventData: any, fromPod: PodInstance) {
125 const options = { 115 const options = {
126 arguments: [ eventData, fromPod ], 116 arguments: [ eventData, fromPod ],
127 errorMessage: 'Cannot process videos events with many retries.' 117 errorMessage: 'Cannot process videos events with many retries.'
128 } 118 }
129 119
130 retryTransactionWrapper(processVideosEvents, options, finalCallback) 120 return retryTransactionWrapper(processVideosEvents, options)
131} 121}
132 122
133function processVideosEvents (eventData: any, fromPod: PodInstance, finalCallback: (err: Error) => void) { 123function processVideosEvents (eventData: any, fromPod: PodInstance) {
134 waterfall([
135 startSerializableTransaction,
136
137 function findVideo (t, callback) {
138 fetchOwnedVideo(eventData.remoteId, function (err, videoInstance) {
139 return callback(err, t, videoInstance)
140 })
141 },
142 124
143 function updateVideoIntoDB (t, videoInstance, callback) { 125 return db.sequelize.transaction(t => {
144 const options = { transaction: t } 126 return fetchOwnedVideo(eventData.remoteId)
127 .then(videoInstance => {
128 const options = { transaction: t }
145 129
146 let columnToUpdate 130 let columnToUpdate
147 let qaduType 131 let qaduType
148 132
149 switch (eventData.eventType) { 133 switch (eventData.eventType) {
150 case REQUEST_VIDEO_EVENT_TYPES.VIEWS: 134 case REQUEST_VIDEO_EVENT_TYPES.VIEWS:
151 columnToUpdate = 'views' 135 columnToUpdate = 'views'
152 qaduType = REQUEST_VIDEO_QADU_TYPES.VIEWS 136 qaduType = REQUEST_VIDEO_QADU_TYPES.VIEWS
153 break 137 break
154 138
155 case REQUEST_VIDEO_EVENT_TYPES.LIKES: 139 case REQUEST_VIDEO_EVENT_TYPES.LIKES:
156 columnToUpdate = 'likes' 140 columnToUpdate = 'likes'
157 qaduType = REQUEST_VIDEO_QADU_TYPES.LIKES 141 qaduType = REQUEST_VIDEO_QADU_TYPES.LIKES
158 break 142 break
159 143
160 case REQUEST_VIDEO_EVENT_TYPES.DISLIKES: 144 case REQUEST_VIDEO_EVENT_TYPES.DISLIKES:
161 columnToUpdate = 'dislikes' 145 columnToUpdate = 'dislikes'
162 qaduType = REQUEST_VIDEO_QADU_TYPES.DISLIKES 146 qaduType = REQUEST_VIDEO_QADU_TYPES.DISLIKES
163 break 147 break
164 148
165 default: 149 default:
166 return callback(new Error('Unknown video event type.')) 150 throw new Error('Unknown video event type.')
167 } 151 }
168 152
169 const query = {} 153 const query = {}
170 query[columnToUpdate] = eventData.count 154 query[columnToUpdate] = eventData.count
171 155
172 videoInstance.increment(query, options).asCallback(function (err) { 156 return videoInstance.increment(query, options).then(() => ({ videoInstance, qaduType }))
173 return callback(err, t, videoInstance, qaduType)
174 }) 157 })
175 }, 158 .then(({ videoInstance, qaduType }) => {
176 159 const qadusParams = [
177 function sendQaduToFriends (t, videoInstance, qaduType, callback) { 160 {
178 const qadusParams = [ 161 videoId: videoInstance.id,
179 { 162 type: qaduType
180 videoId: videoInstance.id, 163 }
181 type: qaduType 164 ]
182 } 165
183 ] 166 return quickAndDirtyUpdatesVideoToFriends(qadusParams, t)
184
185 quickAndDirtyUpdatesVideoToFriends(qadusParams, t, function (err) {
186 return callback(err, t)
187 }) 167 })
188 }, 168 })
189 169 .then(() => logger.info('Remote video event processed for video %s.', eventData.remoteId))
190 commitTransaction 170 .catch(err => {
191 171 logger.debug('Cannot process a video event.', { error: err })
192 ], function (err: Error, t: Sequelize.Transaction) { 172 throw err
193 if (err) {
194 logger.debug('Cannot process a video event.', { error: err })
195 return rollbackTransaction(err, t, finalCallback)
196 }
197
198 logger.info('Remote video event processed for video %s.', eventData.remoteId)
199 return finalCallback(null)
200 }) 173 })
201} 174}
202 175
203function quickAndDirtyUpdateVideoRetryWrapper (videoData: any, fromPod: PodInstance, finalCallback: (err: Error) => void) { 176function quickAndDirtyUpdateVideoRetryWrapper (videoData: any, fromPod: PodInstance) {
204 const options = { 177 const options = {
205 arguments: [ videoData, fromPod ], 178 arguments: [ videoData, fromPod ],
206 errorMessage: 'Cannot update quick and dirty the remote video with many retries.' 179 errorMessage: 'Cannot update quick and dirty the remote video with many retries.'
207 } 180 }
208 181
209 retryTransactionWrapper(quickAndDirtyUpdateVideo, options, finalCallback) 182 return retryTransactionWrapper(quickAndDirtyUpdateVideo, options)
210} 183}
211 184
212function quickAndDirtyUpdateVideo (videoData: any, fromPod: PodInstance, finalCallback: (err: Error) => void) { 185function quickAndDirtyUpdateVideo (videoData: any, fromPod: PodInstance) {
213 let videoName 186 let videoName
214 187
215 waterfall([ 188 return db.sequelize.transaction(t => {
216 startSerializableTransaction, 189 return fetchRemoteVideo(fromPod.host, videoData.remoteId)
217 190 .then(videoInstance => {
218 function findVideo (t, callback) { 191 const options = { transaction: t }
219 fetchRemoteVideo(fromPod.host, videoData.remoteId, function (err, videoInstance) {
220 return callback(err, t, videoInstance)
221 })
222 },
223
224 function updateVideoIntoDB (t, videoInstance, callback) {
225 const options = { transaction: t }
226 192
227 videoName = videoInstance.name 193 videoName = videoInstance.name
228 194
229 if (videoData.views) { 195 if (videoData.views) {
230 videoInstance.set('views', videoData.views) 196 videoInstance.set('views', videoData.views)
231 } 197 }
232 198
233 if (videoData.likes) { 199 if (videoData.likes) {
234 videoInstance.set('likes', videoData.likes) 200 videoInstance.set('likes', videoData.likes)
235 } 201 }
236 202
237 if (videoData.dislikes) { 203 if (videoData.dislikes) {
238 videoInstance.set('dislikes', videoData.dislikes) 204 videoInstance.set('dislikes', videoData.dislikes)
239 } 205 }
240 206
241 videoInstance.save(options).asCallback(function (err) { 207 return videoInstance.save(options)
242 return callback(err, t)
243 }) 208 })
244 },
245
246 commitTransaction
247
248 ], function (err: Error, t: Sequelize.Transaction) {
249 if (err) {
250 logger.debug('Cannot quick and dirty update the remote video.', { error: err })
251 return rollbackTransaction(err, t, finalCallback)
252 }
253
254 logger.info('Remote video %s quick and dirty updated', videoName)
255 return finalCallback(null)
256 }) 209 })
210 .then(() => logger.info('Remote video %s quick and dirty updated', videoName))
211 .catch(err => logger.debug('Cannot quick and dirty update the remote video.', { error: err }))
257} 212}
258 213
259// Handle retries on fail 214// Handle retries on fail
260function addRemoteVideoRetryWrapper (videoToCreateData: any, fromPod: PodInstance, finalCallback: (err: Error) => void) { 215function addRemoteVideoRetryWrapper (videoToCreateData: any, fromPod: PodInstance) {
261 const options = { 216 const options = {
262 arguments: [ videoToCreateData, fromPod ], 217 arguments: [ videoToCreateData, fromPod ],
263 errorMessage: 'Cannot insert the remote video with many retries.' 218 errorMessage: 'Cannot insert the remote video with many retries.'
264 } 219 }
265 220
266 retryTransactionWrapper(addRemoteVideo, options, finalCallback) 221 return retryTransactionWrapper(addRemoteVideo, options)
267} 222}
268 223
269function addRemoteVideo (videoToCreateData: any, fromPod: PodInstance, finalCallback: (err: Error) => void) { 224function addRemoteVideo (videoToCreateData: any, fromPod: PodInstance) {
270 logger.debug('Adding remote video "%s".', videoToCreateData.remoteId) 225 logger.debug('Adding remote video "%s".', videoToCreateData.remoteId)
271 226
272 waterfall([ 227 return db.sequelize.transaction(t => {
273 228 return db.Video.loadByHostAndRemoteId(fromPod.host, videoToCreateData.remoteId)
274 startSerializableTransaction, 229 .then(video => {
275 230 if (video) throw new Error('RemoteId and host pair is not unique.')
276 function assertRemoteIdAndHostUnique (t, callback) {
277 db.Video.loadByHostAndRemoteId(fromPod.host, videoToCreateData.remoteId, function (err, video) {
278 if (err) return callback(err)
279 231
280 if (video) return callback(new Error('RemoteId and host pair is not unique.')) 232 return undefined
281
282 return callback(null, t)
283 }) 233 })
284 }, 234 .then(() => {
285 235 const name = videoToCreateData.author
286 function findOrCreateAuthor (t, callback) { 236 const podId = fromPod.id
287 const name = videoToCreateData.author 237 // This author is from another pod so we do not associate a user
288 const podId = fromPod.id 238 const userId = null
289 // This author is from another pod so we do not associate a user
290 const userId = null
291 239
292 db.Author.findOrCreateAuthor(name, podId, userId, t, function (err, authorInstance) { 240 return db.Author.findOrCreateAuthor(name, podId, userId, t)
293 return callback(err, t, authorInstance)
294 }) 241 })
295 }, 242 .then(author => {
243 const tags = videoToCreateData.tags
296 244
297 function findOrCreateTags (t, author, callback) { 245 return db.Tag.findOrCreateTags(tags, t).then(tagInstances => ({ author, tagInstances }))
298 const tags = videoToCreateData.tags
299
300 db.Tag.findOrCreateTags(tags, t, function (err, tagInstances) {
301 return callback(err, t, author, tagInstances)
302 }) 246 })
303 }, 247 .then(({ author, tagInstances }) => {
304 248 const videoData = {
305 function createVideoObject (t, author, tagInstances, callback) { 249 name: videoToCreateData.name,
306 const videoData = { 250 remoteId: videoToCreateData.remoteId,
307 name: videoToCreateData.name, 251 extname: videoToCreateData.extname,
308 remoteId: videoToCreateData.remoteId, 252 infoHash: videoToCreateData.infoHash,
309 extname: videoToCreateData.extname, 253 category: videoToCreateData.category,
310 infoHash: videoToCreateData.infoHash, 254 licence: videoToCreateData.licence,
311 category: videoToCreateData.category, 255 language: videoToCreateData.language,
312 licence: videoToCreateData.licence, 256 nsfw: videoToCreateData.nsfw,
313 language: videoToCreateData.language, 257 description: videoToCreateData.description,
314 nsfw: videoToCreateData.nsfw, 258 authorId: author.id,
315 description: videoToCreateData.description, 259 duration: videoToCreateData.duration,
316 authorId: author.id, 260 createdAt: videoToCreateData.createdAt,
317 duration: videoToCreateData.duration, 261 // FIXME: updatedAt does not seems to be considered by Sequelize
318 createdAt: videoToCreateData.createdAt, 262 updatedAt: videoToCreateData.updatedAt,
319 // FIXME: updatedAt does not seems to be considered by Sequelize 263 views: videoToCreateData.views,
320 updatedAt: videoToCreateData.updatedAt, 264 likes: videoToCreateData.likes,
321 views: videoToCreateData.views, 265 dislikes: videoToCreateData.dislikes
322 likes: videoToCreateData.likes,
323 dislikes: videoToCreateData.dislikes
324 }
325
326 const video = db.Video.build(videoData)
327
328 return callback(null, t, tagInstances, video)
329 },
330
331 function generateThumbnail (t, tagInstances, video, callback) {
332 db.Video.generateThumbnailFromData(video, videoToCreateData.thumbnailData, function (err) {
333 if (err) {
334 logger.error('Cannot generate thumbnail from data.', { error: err })
335 return callback(err)
336 } 266 }
337 267
338 return callback(err, t, tagInstances, video) 268 const video = db.Video.build(videoData)
269 return { tagInstances, video }
339 }) 270 })
340 }, 271 .then(({ tagInstances, video }) => {
341 272 return db.Video.generateThumbnailFromData(video, videoToCreateData.thumbnailData).then(() => ({ tagInstances, video }))
342 function insertVideoIntoDB (t, tagInstances, video, callback) {
343 const options = {
344 transaction: t
345 }
346
347 video.save(options).asCallback(function (err, videoCreated) {
348 return callback(err, t, tagInstances, videoCreated)
349 }) 273 })
350 }, 274 .then(({ tagInstances, video }) => {
351 275 const options = {
352 function associateTagsToVideo (t, tagInstances, video, callback) { 276 transaction: t
353 const options = { 277 }
354 transaction: t
355 }
356 278
357 video.setTags(tagInstances, options).asCallback(function (err) { 279 return video.save(options).then(videoCreated => ({ tagInstances, videoCreated }))
358 return callback(err, t)
359 }) 280 })
360 }, 281 .then(({ tagInstances, videoCreated }) => {
361 282 const options = {
362 commitTransaction 283 transaction: t
363 284 }
364 ], function (err: Error, t: Sequelize.Transaction) {
365 if (err) {
366 // This is just a debug because we will retry the insert
367 logger.debug('Cannot insert the remote video.', { error: err })
368 return rollbackTransaction(err, t, finalCallback)
369 }
370 285
371 logger.info('Remote video %s inserted.', videoToCreateData.name) 286 return videoCreated.setTags(tagInstances, options)
372 return finalCallback(null) 287 })
288 })
289 .then(() => logger.info('Remote video %s inserted.', videoToCreateData.name))
290 .catch(err => {
291 logger.debug('Cannot insert the remote video.', { error: err })
292 throw err
373 }) 293 })
374} 294}
375 295
376// Handle retries on fail 296// Handle retries on fail
377function updateRemoteVideoRetryWrapper (videoAttributesToUpdate: any, fromPod: PodInstance, finalCallback: (err: Error) => void) { 297function updateRemoteVideoRetryWrapper (videoAttributesToUpdate: any, fromPod: PodInstance) {
378 const options = { 298 const options = {
379 arguments: [ videoAttributesToUpdate, fromPod ], 299 arguments: [ videoAttributesToUpdate, fromPod ],
380 errorMessage: 'Cannot update the remote video with many retries' 300 errorMessage: 'Cannot update the remote video with many retries'
381 } 301 }
382 302
383 retryTransactionWrapper(updateRemoteVideo, options, finalCallback) 303 return retryTransactionWrapper(updateRemoteVideo, options)
384} 304}
385 305
386function updateRemoteVideo (videoAttributesToUpdate: any, fromPod: PodInstance, finalCallback: (err: Error) => void) { 306function updateRemoteVideo (videoAttributesToUpdate: any, fromPod: PodInstance) {
387 logger.debug('Updating remote video "%s".', videoAttributesToUpdate.remoteId) 307 logger.debug('Updating remote video "%s".', videoAttributesToUpdate.remoteId)
388 308
389 waterfall([ 309 return db.sequelize.transaction(t => {
310 return fetchRemoteVideo(fromPod.host, videoAttributesToUpdate.remoteId)
311 .then(videoInstance => {
312 const tags = videoAttributesToUpdate.tags
390 313
391 startSerializableTransaction, 314 return db.Tag.findOrCreateTags(tags, t).then(tagInstances => ({ videoInstance, tagInstances }))
392
393 function findVideo (t, callback) {
394 fetchRemoteVideo(fromPod.host, videoAttributesToUpdate.remoteId, function (err, videoInstance) {
395 return callback(err, t, videoInstance)
396 }) 315 })
397 }, 316 .then(({ videoInstance, tagInstances }) => {
398 317 const options = { transaction: t }
399 function findOrCreateTags (t, videoInstance, callback) { 318
400 const tags = videoAttributesToUpdate.tags 319 videoInstance.set('name', videoAttributesToUpdate.name)
401 320 videoInstance.set('category', videoAttributesToUpdate.category)
402 db.Tag.findOrCreateTags(tags, t, function (err, tagInstances) { 321 videoInstance.set('licence', videoAttributesToUpdate.licence)
403 return callback(err, t, videoInstance, tagInstances) 322 videoInstance.set('language', videoAttributesToUpdate.language)
404 }) 323 videoInstance.set('nsfw', videoAttributesToUpdate.nsfw)
405 }, 324 videoInstance.set('description', videoAttributesToUpdate.description)
406 325 videoInstance.set('infoHash', videoAttributesToUpdate.infoHash)
407 function updateVideoIntoDB (t, videoInstance, tagInstances, callback) { 326 videoInstance.set('duration', videoAttributesToUpdate.duration)
408 const options = { transaction: t } 327 videoInstance.set('createdAt', videoAttributesToUpdate.createdAt)
409 328 videoInstance.set('updatedAt', videoAttributesToUpdate.updatedAt)
410 videoInstance.set('name', videoAttributesToUpdate.name) 329 videoInstance.set('extname', videoAttributesToUpdate.extname)
411 videoInstance.set('category', videoAttributesToUpdate.category) 330 videoInstance.set('views', videoAttributesToUpdate.views)
412 videoInstance.set('licence', videoAttributesToUpdate.licence) 331 videoInstance.set('likes', videoAttributesToUpdate.likes)
413 videoInstance.set('language', videoAttributesToUpdate.language) 332 videoInstance.set('dislikes', videoAttributesToUpdate.dislikes)
414 videoInstance.set('nsfw', videoAttributesToUpdate.nsfw) 333
415 videoInstance.set('description', videoAttributesToUpdate.description) 334 return videoInstance.save(options).then(() => ({ videoInstance, tagInstances }))
416 videoInstance.set('infoHash', videoAttributesToUpdate.infoHash)
417 videoInstance.set('duration', videoAttributesToUpdate.duration)
418 videoInstance.set('createdAt', videoAttributesToUpdate.createdAt)
419 videoInstance.set('updatedAt', videoAttributesToUpdate.updatedAt)
420 videoInstance.set('extname', videoAttributesToUpdate.extname)
421 videoInstance.set('views', videoAttributesToUpdate.views)
422 videoInstance.set('likes', videoAttributesToUpdate.likes)
423 videoInstance.set('dislikes', videoAttributesToUpdate.dislikes)
424
425 videoInstance.save(options).asCallback(function (err) {
426 return callback(err, t, videoInstance, tagInstances)
427 }) 335 })
428 }, 336 .then(({ videoInstance, tagInstances }) => {
429 337 const options = { transaction: t }
430 function associateTagsToVideo (t, videoInstance, tagInstances, callback) {
431 const options = { transaction: t }
432 338
433 videoInstance.setTags(tagInstances, options).asCallback(function (err) { 339 return videoInstance.setTags(tagInstances, options)
434 return callback(err, t)
435 }) 340 })
436 }, 341 })
437 342 .then(() => logger.info('Remote video %s updated', videoAttributesToUpdate.name))
438 commitTransaction 343 .catch(err => {
439 344 // This is just a debug because we will retry the insert
440 ], function (err: Error, t: Sequelize.Transaction) { 345 logger.debug('Cannot update the remote video.', { error: err })
441 if (err) { 346 throw err
442 // This is just a debug because we will retry the insert
443 logger.debug('Cannot update the remote video.', { error: err })
444 return rollbackTransaction(err, t, finalCallback)
445 }
446
447 logger.info('Remote video %s updated', videoAttributesToUpdate.name)
448 return finalCallback(null)
449 }) 347 })
450} 348}
451 349
452function removeRemoteVideo (videoToRemoveData: any, fromPod: PodInstance, callback: (err: Error) => void) { 350function removeRemoteVideo (videoToRemoveData: any, fromPod: PodInstance) {
453 // We need the instance because we have to remove some other stuffs (thumbnail etc) 351 // We need the instance because we have to remove some other stuffs (thumbnail etc)
454 fetchRemoteVideo(fromPod.host, videoToRemoveData.remoteId, function (err, video) { 352 return fetchRemoteVideo(fromPod.host, videoToRemoveData.remoteId)
455 // Do not return the error, continue the process 353 .then(video => {
456 if (err) return callback(null) 354 logger.debug('Removing remote video %s.', video.remoteId)
457 355 return video.destroy()
458 logger.debug('Removing remote video %s.', video.remoteId) 356 })
459 video.destroy().asCallback(function (err) { 357 .catch(err => {
460 // Do not return the error, continue the process 358 logger.debug('Could not fetch remote video.', { host: fromPod.host, remoteId: videoToRemoveData.remoteId, error: err })
461 if (err) {
462 logger.error('Cannot remove remote video with id %s.', videoToRemoveData.remoteId, { error: err })
463 }
464
465 return callback(null)
466 }) 359 })
467 })
468} 360}
469 361
470function reportAbuseRemoteVideo (reportData: any, fromPod: PodInstance, callback: (err: Error) => void) { 362function reportAbuseRemoteVideo (reportData: any, fromPod: PodInstance) {
471 fetchOwnedVideo(reportData.videoRemoteId, function (err, video) { 363 return fetchOwnedVideo(reportData.videoRemoteId)
472 if (err || !video) { 364 .then(video => {
473 if (!err) err = new Error('video not found') 365 logger.debug('Reporting remote abuse for video %s.', video.id)
474
475 logger.error('Cannot load video from id.', { error: err, id: reportData.videoRemoteId })
476 // Do not return the error, continue the process
477 return callback(null)
478 }
479
480 logger.debug('Reporting remote abuse for video %s.', video.id)
481
482 const videoAbuseData = {
483 reporterUsername: reportData.reporterUsername,
484 reason: reportData.reportReason,
485 reporterPodId: fromPod.id,
486 videoId: video.id
487 }
488 366
489 db.VideoAbuse.create(videoAbuseData).asCallback(function (err) { 367 const videoAbuseData = {
490 if (err) { 368 reporterUsername: reportData.reporterUsername,
491 logger.error('Cannot create remote abuse video.', { error: err }) 369 reason: reportData.reportReason,
370 reporterPodId: fromPod.id,
371 videoId: video.id
492 } 372 }
493 373
494 return callback(null) 374 return db.VideoAbuse.create(videoAbuseData)
495 }) 375 })
496 }) 376 .catch(err => logger.error('Cannot create remote abuse video.', { error: err }))
497} 377}
498 378
499function fetchOwnedVideo (id: string, callback: (err: Error, video?: VideoInstance) => void) { 379function fetchOwnedVideo (id: string) {
500 db.Video.load(id, function (err, video) { 380 return db.Video.load(id)
501 if (err || !video) { 381 .then(video => {
502 if (!err) err = new Error('video not found') 382 if (!video) throw new Error('Video not found')
503 383
384 return video
385 })
386 .catch(err => {
504 logger.error('Cannot load owned video from id.', { error: err, id }) 387 logger.error('Cannot load owned video from id.', { error: err, id })
505 return callback(err) 388 throw err
506 } 389 })
507
508 return callback(null, video)
509 })
510} 390}
511 391
512function fetchRemoteVideo (podHost: string, remoteId: string, callback: (err: Error, video?: VideoInstance) => void) { 392function fetchRemoteVideo (podHost: string, remoteId: string) {
513 db.Video.loadByHostAndRemoteId(podHost, remoteId, function (err, video) { 393 return db.Video.loadByHostAndRemoteId(podHost, remoteId)
514 if (err || !video) { 394 .then(video => {
515 if (!err) err = new Error('video not found') 395 if (!video) throw new Error('Video not found')
516 396
397 return video
398 })
399 .catch(err => {
517 logger.error('Cannot load video from host and remote id.', { error: err, podHost, remoteId }) 400 logger.error('Cannot load video from host and remote id.', { error: err, podHost, remoteId })
518 return callback(err) 401 throw err
519 } 402 })
520
521 return callback(null, video)
522 })
523} 403}
diff --git a/server/controllers/api/request-schedulers.ts b/server/controllers/api/request-schedulers.ts
index 8dd849007..2a934a512 100644
--- a/server/controllers/api/request-schedulers.ts
+++ b/server/controllers/api/request-schedulers.ts
@@ -1,5 +1,5 @@
1import * as express from 'express' 1import * as express from 'express'
2import { parallel } from 'async' 2import * as Promise from 'bluebird'
3 3
4import { 4import {
5 AbstractRequestScheduler, 5 AbstractRequestScheduler,
@@ -27,33 +27,27 @@ export {
27// --------------------------------------------------------------------------- 27// ---------------------------------------------------------------------------
28 28
29function getRequestSchedulersStats (req: express.Request, res: express.Response, next: express.NextFunction) { 29function getRequestSchedulersStats (req: express.Request, res: express.Response, next: express.NextFunction) {
30 parallel({ 30 Promise.props({
31 requestScheduler: buildRequestSchedulerStats(getRequestScheduler()), 31 requestScheduler: buildRequestSchedulerStats(getRequestScheduler()),
32 requestVideoQaduScheduler: buildRequestSchedulerStats(getRequestVideoQaduScheduler()), 32 requestVideoQaduScheduler: buildRequestSchedulerStats(getRequestVideoQaduScheduler()),
33 requestVideoEventScheduler: buildRequestSchedulerStats(getRequestVideoEventScheduler()) 33 requestVideoEventScheduler: buildRequestSchedulerStats(getRequestVideoEventScheduler())
34 }, function (err, result) {
35 if (err) return next(err)
36
37 return res.json(result)
38 }) 34 })
35 .then(result => res.json(result))
36 .catch(err => next(err))
39} 37}
40 38
41// --------------------------------------------------------------------------- 39// ---------------------------------------------------------------------------
42 40
43function buildRequestSchedulerStats (requestScheduler: AbstractRequestScheduler) { 41function buildRequestSchedulerStats (requestScheduler: AbstractRequestScheduler<any>) {
44 return function (callback) { 42 return requestScheduler.remainingRequestsCount().then(count => {
45 requestScheduler.remainingRequestsCount(function (err, count) { 43 const result: RequestSchedulerStatsAttributes = {
46 if (err) return callback(err) 44 totalRequests: count,
47 45 requestsLimitPods: requestScheduler.limitPods,
48 const result: RequestSchedulerStatsAttributes = { 46 requestsLimitPerPod: requestScheduler.limitPerPod,
49 totalRequests: count, 47 remainingMilliSeconds: requestScheduler.remainingMilliSeconds(),
50 requestsLimitPods: requestScheduler.limitPods, 48 milliSecondsInterval: requestScheduler.requestInterval
51 requestsLimitPerPod: requestScheduler.limitPerPod, 49 }
52 remainingMilliSeconds: requestScheduler.remainingMilliSeconds(), 50
53 milliSecondsInterval: requestScheduler.requestInterval 51 return result
54 } 52 })
55
56 return callback(null, result)
57 })
58 }
59} 53}
diff --git a/server/controllers/api/users.ts b/server/controllers/api/users.ts
index ce15353ef..6e0bb474a 100644
--- a/server/controllers/api/users.ts
+++ b/server/controllers/api/users.ts
@@ -1,8 +1,7 @@
1import * as express from 'express' 1import * as express from 'express'
2import { waterfall } from 'async'
3 2
4import { database as db } from '../../initializers/database' 3import { database as db } from '../../initializers/database'
5import { CONFIG, USER_ROLES } from '../../initializers' 4import { USER_ROLES } from '../../initializers'
6import { logger, getFormatedObjects } from '../../helpers' 5import { logger, getFormatedObjects } from '../../helpers'
7import { 6import {
8 authenticate, 7 authenticate,
@@ -87,78 +86,61 @@ function createUser (req: express.Request, res: express.Response, next: express.
87 role: USER_ROLES.USER 86 role: USER_ROLES.USER
88 }) 87 })
89 88
90 user.save().asCallback(function (err) { 89 user.save()
91 if (err) return next(err) 90 .then(() => res.type('json').status(204).end())
92 91 .catch(err => next(err))
93 return res.type('json').status(204).end()
94 })
95} 92}
96 93
97function getUserInformation (req: express.Request, res: express.Response, next: express.NextFunction) { 94function getUserInformation (req: express.Request, res: express.Response, next: express.NextFunction) {
98 db.User.loadByUsername(res.locals.oauth.token.user.username, function (err, user) { 95 db.User.loadByUsername(res.locals.oauth.token.user.username)
99 if (err) return next(err) 96 .then(user => res.json(user.toFormatedJSON()))
100 97 .catch(err => next(err))
101 return res.json(user.toFormatedJSON())
102 })
103} 98}
104 99
105function getUserVideoRating (req: express.Request, res: express.Response, next: express.NextFunction) { 100function getUserVideoRating (req: express.Request, res: express.Response, next: express.NextFunction) {
106 const videoId = '' + req.params.videoId 101 const videoId = '' + req.params.videoId
107 const userId = +res.locals.oauth.token.User.id 102 const userId = +res.locals.oauth.token.User.id
108 103
109 db.UserVideoRate.load(userId, videoId, null, function (err, ratingObj) { 104 db.UserVideoRate.load(userId, videoId, null)
110 if (err) return next(err) 105 .then(ratingObj => {
111 106 const rating = ratingObj ? ratingObj.type : 'none'
112 const rating = ratingObj ? ratingObj.type : 'none' 107 const json: FormatedUserVideoRate = {
113 108 videoId,
114 const json: FormatedUserVideoRate = { 109 rating
115 videoId, 110 }
116 rating 111 res.json(json)
117 } 112 })
118 res.json(json) 113 .catch(err => next(err))
119 })
120} 114}
121 115
122function listUsers (req: express.Request, res: express.Response, next: express.NextFunction) { 116function listUsers (req: express.Request, res: express.Response, next: express.NextFunction) {
123 db.User.listForApi(req.query.start, req.query.count, req.query.sort, function (err, usersList, usersTotal) { 117 db.User.listForApi(req.query.start, req.query.count, req.query.sort)
124 if (err) return next(err) 118 .then(resultList => {
125 119 res.json(getFormatedObjects(resultList.data, resultList.total))
126 res.json(getFormatedObjects(usersList, usersTotal)) 120 })
127 }) 121 .catch(err => next(err))
128} 122}
129 123
130function removeUser (req: express.Request, res: express.Response, next: express.NextFunction) { 124function removeUser (req: express.Request, res: express.Response, next: express.NextFunction) {
131 waterfall([ 125 db.User.loadById(req.params.id)
132 function loadUser (callback) { 126 .then(user => user.destroy())
133 db.User.loadById(req.params.id, callback) 127 .then(() => res.sendStatus(204))
134 }, 128 .catch(err => {
135
136 function deleteUser (user, callback) {
137 user.destroy().asCallback(callback)
138 }
139 ], function andFinally (err) {
140 if (err) {
141 logger.error('Errors when removed the user.', { error: err }) 129 logger.error('Errors when removed the user.', { error: err })
142 return next(err) 130 return next(err)
143 } 131 })
144
145 return res.sendStatus(204)
146 })
147} 132}
148 133
149function updateUser (req: express.Request, res: express.Response, next: express.NextFunction) { 134function updateUser (req: express.Request, res: express.Response, next: express.NextFunction) {
150 db.User.loadByUsername(res.locals.oauth.token.user.username, function (err, user) { 135 db.User.loadByUsername(res.locals.oauth.token.user.username)
151 if (err) return next(err) 136 .then(user => {
152 137 if (req.body.password) user.password = req.body.password
153 if (req.body.password) user.password = req.body.password 138 if (req.body.displayNSFW !== undefined) user.displayNSFW = req.body.displayNSFW
154 if (req.body.displayNSFW !== undefined) user.displayNSFW = req.body.displayNSFW
155 139
156 user.save().asCallback(function (err) { 140 return user.save()
157 if (err) return next(err)
158
159 return res.sendStatus(204)
160 }) 141 })
161 }) 142 .then(() => res.sendStatus(204))
143 .catch(err => next(err))
162} 144}
163 145
164function success (req: express.Request, res: express.Response, next: express.NextFunction) { 146function success (req: express.Request, res: express.Response, next: express.NextFunction) {
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}
diff --git a/server/controllers/client.ts b/server/controllers/client.ts
index 503eff750..e4d69eae7 100644
--- a/server/controllers/client.ts
+++ b/server/controllers/client.ts
@@ -1,8 +1,7 @@
1import { parallel } from 'async'
2import * as express from 'express' 1import * as express from 'express'
3import * as fs from 'fs'
4import { join } from 'path' 2import { join } from 'path'
5import * as validator from 'validator' 3import * as validator from 'validator'
4import * as Promise from 'bluebird'
6 5
7import { database as db } from '../initializers/database' 6import { database as db } from '../initializers/database'
8import { 7import {
@@ -11,7 +10,7 @@ import {
11 STATIC_PATHS, 10 STATIC_PATHS,
12 STATIC_MAX_AGE 11 STATIC_MAX_AGE
13} from '../initializers' 12} from '../initializers'
14import { root } from '../helpers' 13import { root, readFileBufferPromise } from '../helpers'
15import { VideoInstance } from '../models' 14import { VideoInstance } from '../models'
16 15
17const clientsRouter = express.Router() 16const clientsRouter = express.Router()
@@ -95,19 +94,15 @@ function generateWatchHtmlPage (req: express.Request, res: express.Response, nex
95 // Let Angular application handle errors 94 // Let Angular application handle errors
96 if (!validator.isUUID(videoId, 4)) return res.sendFile(indexPath) 95 if (!validator.isUUID(videoId, 4)) return res.sendFile(indexPath)
97 96
98 parallel({ 97 Promise.all([
99 file: function (callback) { 98 readFileBufferPromise(indexPath),
100 fs.readFile(indexPath, callback) 99 db.Video.loadAndPopulateAuthorAndPodAndTags(videoId)
101 }, 100 ])
101 .then(([ file, video ]) => {
102 file = file as Buffer
103 video = video as VideoInstance
102 104
103 video: function (callback) { 105 const html = file.toString()
104 db.Video.loadAndPopulateAuthorAndPodAndTags(videoId, callback)
105 }
106 }, function (err: Error, result: { file: Buffer, video: VideoInstance }) {
107 if (err) return next(err)
108
109 const html = result.file.toString()
110 const video = result.video
111 106
112 // Let Angular application handle errors 107 // Let Angular application handle errors
113 if (!video) return res.sendFile(indexPath) 108 if (!video) return res.sendFile(indexPath)
@@ -115,4 +110,5 @@ function generateWatchHtmlPage (req: express.Request, res: express.Response, nex
115 const htmlStringPageWithTags = addOpenGraphTags(html, video) 110 const htmlStringPageWithTags = addOpenGraphTags(html, video)
116 res.set('Content-Type', 'text/html; charset=UTF-8').send(htmlStringPageWithTags) 111 res.set('Content-Type', 'text/html; charset=UTF-8').send(htmlStringPageWithTags)
117 }) 112 })
113 .catch(err => next(err))
118} 114}