aboutsummaryrefslogtreecommitdiffhomepage
path: root/server
diff options
context:
space:
mode:
Diffstat (limited to 'server')
-rw-r--r--server/controllers/api/config.ts32
-rw-r--r--server/controllers/api/oauth-clients.ts26
-rw-r--r--server/controllers/api/pods.ts41
-rw-r--r--server/controllers/api/remote/pods.ts48
-rw-r--r--server/controllers/api/remote/videos.ts654
-rw-r--r--server/controllers/api/request-schedulers.ts38
-rw-r--r--server/controllers/api/users.ts108
-rw-r--r--server/controllers/api/videos/abuse.ts62
-rw-r--r--server/controllers/api/videos/blacklist.ts48
-rw-r--r--server/controllers/api/videos/channel.ts153
-rw-r--r--server/controllers/api/videos/index.ts354
-rw-r--r--server/controllers/api/videos/rate.ts205
-rw-r--r--server/controllers/client.ts31
-rw-r--r--server/controllers/static.ts13
-rw-r--r--server/helpers/database-utils.ts1
-rw-r--r--server/helpers/utils.ts9
-rw-r--r--server/initializers/database.ts1
-rw-r--r--server/lib/video-channel.ts46
-rw-r--r--server/middlewares/async.ts16
-rw-r--r--server/middlewares/index.ts1
20 files changed, 859 insertions, 1028 deletions
diff --git a/server/controllers/api/config.ts b/server/controllers/api/config.ts
index c9a051bdc..5f704f0ee 100644
--- a/server/controllers/api/config.ts
+++ b/server/controllers/api/config.ts
@@ -2,30 +2,32 @@ import * as express from 'express'
2 2
3import { isSignupAllowed } from '../../helpers' 3import { isSignupAllowed } from '../../helpers'
4import { CONFIG } from '../../initializers' 4import { CONFIG } from '../../initializers'
5import { asyncMiddleware } from '../../middlewares'
5import { ServerConfig } from '../../../shared' 6import { ServerConfig } from '../../../shared'
6 7
7const configRouter = express.Router() 8const configRouter = express.Router()
8 9
9configRouter.get('/', getConfig) 10configRouter.get('/',
11 asyncMiddleware(getConfig)
12)
10 13
11function getConfig (req: express.Request, res: express.Response, next: express.NextFunction) { 14async function getConfig (req: express.Request, res: express.Response, next: express.NextFunction) {
15 const allowed = await isSignupAllowed()
12 16
13 isSignupAllowed().then(allowed => { 17 const enabledResolutions = Object.keys(CONFIG.TRANSCODING.RESOLUTIONS)
14 const enabledResolutions = Object.keys(CONFIG.TRANSCODING.RESOLUTIONS) 18 .filter(key => CONFIG.TRANSCODING.RESOLUTIONS[key] === true)
15 .filter(key => CONFIG.TRANSCODING.RESOLUTIONS[key] === true) 19 .map(r => parseInt(r, 10))
16 .map(r => parseInt(r, 10))
17 20
18 const json: ServerConfig = { 21 const json: ServerConfig = {
19 signup: { 22 signup: {
20 allowed 23 allowed
21 }, 24 },
22 transcoding: { 25 transcoding: {
23 enabledResolutions 26 enabledResolutions
24 }
25 } 27 }
28 }
26 29
27 res.json(json) 30 return res.json(json)
28 })
29} 31}
30 32
31// --------------------------------------------------------------------------- 33// ---------------------------------------------------------------------------
diff --git a/server/controllers/api/oauth-clients.ts b/server/controllers/api/oauth-clients.ts
index f7dac598c..ac1ee9e36 100644
--- a/server/controllers/api/oauth-clients.ts
+++ b/server/controllers/api/oauth-clients.ts
@@ -2,15 +2,18 @@ import * as express from 'express'
2 2
3import { CONFIG } from '../../initializers' 3import { CONFIG } from '../../initializers'
4import { logger } from '../../helpers' 4import { logger } from '../../helpers'
5import { asyncMiddleware } from '../../middlewares'
5import { database as db } from '../../initializers/database' 6import { database as db } from '../../initializers/database'
6import { OAuthClientLocal } from '../../../shared' 7import { OAuthClientLocal } from '../../../shared'
7 8
8const oauthClientsRouter = express.Router() 9const oauthClientsRouter = express.Router()
9 10
10oauthClientsRouter.get('/local', getLocalClient) 11oauthClientsRouter.get('/local',
12 asyncMiddleware(getLocalClient)
13)
11 14
12// Get the client credentials for the PeerTube front end 15// Get the client credentials for the PeerTube front end
13function getLocalClient (req: express.Request, res: express.Response, next: express.NextFunction) { 16async function getLocalClient (req: express.Request, res: express.Response, next: express.NextFunction) {
14 const serverHostname = CONFIG.WEBSERVER.HOSTNAME 17 const serverHostname = CONFIG.WEBSERVER.HOSTNAME
15 const serverPort = CONFIG.WEBSERVER.PORT 18 const serverPort = CONFIG.WEBSERVER.PORT
16 let headerHostShouldBe = serverHostname 19 let headerHostShouldBe = serverHostname
@@ -24,17 +27,14 @@ function getLocalClient (req: express.Request, res: express.Response, next: expr
24 return res.type('json').status(403).end() 27 return res.type('json').status(403).end()
25 } 28 }
26 29
27 db.OAuthClient.loadFirstClient() 30 const client = await db.OAuthClient.loadFirstClient()
28 .then(client => { 31 if (!client) throw new Error('No client available.')
29 if (!client) throw new Error('No client available.') 32
30 33 const json: OAuthClientLocal = {
31 const json: OAuthClientLocal = { 34 client_id: client.clientId,
32 client_id: client.clientId, 35 client_secret: client.clientSecret
33 client_secret: client.clientSecret 36 }
34 } 37 return res.json(json)
35 res.json(json)
36 })
37 .catch(err => next(err))
38} 38}
39 39
40// --------------------------------------------------------------------------- 40// ---------------------------------------------------------------------------
diff --git a/server/controllers/api/pods.ts b/server/controllers/api/pods.ts
index 804aa0659..bf1b744e5 100644
--- a/server/controllers/api/pods.ts
+++ b/server/controllers/api/pods.ts
@@ -16,7 +16,8 @@ import {
16 paginationValidator, 16 paginationValidator,
17 setPagination, 17 setPagination,
18 setPodsSort, 18 setPodsSort,
19 podsSortValidator 19 podsSortValidator,
20 asyncMiddleware
20} from '../../middlewares' 21} from '../../middlewares'
21import { PodInstance } from '../../models' 22import { PodInstance } from '../../models'
22 23
@@ -27,25 +28,25 @@ podsRouter.get('/',
27 podsSortValidator, 28 podsSortValidator,
28 setPodsSort, 29 setPodsSort,
29 setPagination, 30 setPagination,
30 listPods 31 asyncMiddleware(listPods)
31) 32)
32podsRouter.post('/make-friends', 33podsRouter.post('/make-friends',
33 authenticate, 34 authenticate,
34 ensureIsAdmin, 35 ensureIsAdmin,
35 makeFriendsValidator, 36 makeFriendsValidator,
36 setBodyHostsPort, 37 setBodyHostsPort,
37 makeFriendsController 38 asyncMiddleware(makeFriendsController)
38) 39)
39podsRouter.get('/quit-friends', 40podsRouter.get('/quit-friends',
40 authenticate, 41 authenticate,
41 ensureIsAdmin, 42 ensureIsAdmin,
42 quitFriendsController 43 asyncMiddleware(quitFriendsController)
43) 44)
44podsRouter.delete('/:id', 45podsRouter.delete('/:id',
45 authenticate, 46 authenticate,
46 ensureIsAdmin, 47 ensureIsAdmin,
47 podRemoveValidator, 48 podRemoveValidator,
48 removeFriendController 49 asyncMiddleware(removeFriendController)
49) 50)
50 51
51// --------------------------------------------------------------------------- 52// ---------------------------------------------------------------------------
@@ -56,33 +57,33 @@ export {
56 57
57// --------------------------------------------------------------------------- 58// ---------------------------------------------------------------------------
58 59
59function listPods (req: express.Request, res: express.Response, next: express.NextFunction) { 60async function listPods (req: express.Request, res: express.Response, next: express.NextFunction) {
60 db.Pod.listForApi(req.query.start, req.query.count, req.query.sort) 61 const resultList = await db.Pod.listForApi(req.query.start, req.query.count, req.query.sort)
61 .then(resultList => res.json(getFormattedObjects(resultList.data, resultList.total))) 62
62 .catch(err => next(err)) 63 return res.json(getFormattedObjects(resultList.data, resultList.total))
63} 64}
64 65
65function makeFriendsController (req: express.Request, res: express.Response, next: express.NextFunction) { 66async function makeFriendsController (req: express.Request, res: express.Response, next: express.NextFunction) {
66 const hosts = req.body.hosts as string[] 67 const hosts = req.body.hosts as string[]
67 68
69 // Don't wait the process that could be long
68 makeFriends(hosts) 70 makeFriends(hosts)
69 .then(() => logger.info('Made friends!')) 71 .then(() => logger.info('Made friends!'))
70 .catch(err => logger.error('Could not make friends.', err)) 72 .catch(err => logger.error('Could not make friends.', err))
71 73
72 // Don't wait the process that could be long 74 return res.type('json').status(204).end()
73 res.type('json').status(204).end()
74} 75}
75 76
76function quitFriendsController (req: express.Request, res: express.Response, next: express.NextFunction) { 77async function quitFriendsController (req: express.Request, res: express.Response, next: express.NextFunction) {
77 quitFriends() 78 await quitFriends()
78 .then(() => res.type('json').status(204).end()) 79
79 .catch(err => next(err)) 80 return res.type('json').status(204).end()
80} 81}
81 82
82function removeFriendController (req: express.Request, res: express.Response, next: express.NextFunction) { 83async function removeFriendController (req: express.Request, res: express.Response, next: express.NextFunction) {
83 const pod = res.locals.pod as PodInstance 84 const pod = res.locals.pod as PodInstance
84 85
85 removeFriend(pod) 86 await removeFriend(pod)
86 .then(() => res.type('json').status(204).end()) 87
87 .catch(err => next(err)) 88 return res.type('json').status(204).end()
88} 89}
diff --git a/server/controllers/api/remote/pods.ts b/server/controllers/api/remote/pods.ts
index a62b9c684..326eb61ac 100644
--- a/server/controllers/api/remote/pods.ts
+++ b/server/controllers/api/remote/pods.ts
@@ -5,7 +5,8 @@ import {
5 checkSignature, 5 checkSignature,
6 signatureValidator, 6 signatureValidator,
7 setBodyHostPort, 7 setBodyHostPort,
8 remotePodsAddValidator 8 remotePodsAddValidator,
9 asyncMiddleware
9} from '../../../middlewares' 10} from '../../../middlewares'
10import { sendOwnedDataToPod } from '../../../lib' 11import { sendOwnedDataToPod } from '../../../lib'
11import { getMyPublicCert, getFormattedObjects } from '../../../helpers' 12import { getMyPublicCert, getFormattedObjects } from '../../../helpers'
@@ -18,15 +19,17 @@ const remotePodsRouter = express.Router()
18remotePodsRouter.post('/remove', 19remotePodsRouter.post('/remove',
19 signatureValidator, 20 signatureValidator,
20 checkSignature, 21 checkSignature,
21 removePods 22 asyncMiddleware(removePods)
22) 23)
23 24
24remotePodsRouter.post('/list', remotePodsList) 25remotePodsRouter.post('/list',
26 asyncMiddleware(remotePodsList)
27)
25 28
26remotePodsRouter.post('/add', 29remotePodsRouter.post('/add',
27 setBodyHostPort, // We need to modify the host before running the validator! 30 setBodyHostPort, // We need to modify the host before running the validator!
28 remotePodsAddValidator, 31 remotePodsAddValidator,
29 addPods 32 asyncMiddleware(addPods)
30) 33)
31 34
32// --------------------------------------------------------------------------- 35// ---------------------------------------------------------------------------
@@ -37,35 +40,30 @@ export {
37 40
38// --------------------------------------------------------------------------- 41// ---------------------------------------------------------------------------
39 42
40function addPods (req: express.Request, res: express.Response, next: express.NextFunction) { 43async function addPods (req: express.Request, res: express.Response, next: express.NextFunction) {
41 const information = req.body 44 const information = req.body
42 45
43 const pod = db.Pod.build(information) 46 const pod = db.Pod.build(information)
44 pod.save() 47 const podCreated = await pod.save()
45 .then(podCreated => { 48
46 return sendOwnedDataToPod(podCreated.id) 49 await sendOwnedDataToPod(podCreated.id)
47 }) 50
48 .then(() => { 51 const cert = await getMyPublicCert()
49 return getMyPublicCert() 52 return res.json({ cert, email: CONFIG.ADMIN.EMAIL })
50 })
51 .then(cert => {
52 return res.json({ cert: cert, email: CONFIG.ADMIN.EMAIL })
53 })
54 .catch(err => next(err))
55} 53}
56 54
57function remotePodsList (req: express.Request, res: express.Response, next: express.NextFunction) { 55async function remotePodsList (req: express.Request, res: express.Response, next: express.NextFunction) {
58 db.Pod.list() 56 const pods = await db.Pod.list()
59 .then(podsList => res.json(getFormattedObjects<FormattedPod, PodInstance>(podsList, podsList.length))) 57
60 .catch(err => next(err)) 58 return res.json(getFormattedObjects<FormattedPod, PodInstance>(pods, pods.length))
61} 59}
62 60
63function removePods (req: express.Request, res: express.Response, next: express.NextFunction) { 61async function removePods (req: express.Request, res: express.Response, next: express.NextFunction) {
64 const signature: PodSignature = req.body.signature 62 const signature: PodSignature = req.body.signature
65 const host = signature.host 63 const host = signature.host
66 64
67 db.Pod.loadByHost(host) 65 const pod = await db.Pod.loadByHost(host)
68 .then(pod => pod.destroy()) 66 await pod.destroy()
69 .then(() => res.type('json').status(204).end()) 67
70 .catch(err => next(err)) 68 return res.type('json').status(204).end()
71} 69}
diff --git a/server/controllers/api/remote/videos.ts b/server/controllers/api/remote/videos.ts
index c8f531490..bf442c6e5 100644
--- a/server/controllers/api/remote/videos.ts
+++ b/server/controllers/api/remote/videos.ts
@@ -1,5 +1,5 @@
1import * as express from 'express' 1import * as express from 'express'
2import * as Promise from 'bluebird' 2import * as Bluebird from 'bluebird'
3import * as Sequelize from 'sequelize' 3import * as Sequelize from 'sequelize'
4 4
5import { database as db } from '../../../initializers/database' 5import { database as db } from '../../../initializers/database'
@@ -17,7 +17,7 @@ import {
17 remoteEventsVideosValidator 17 remoteEventsVideosValidator
18} from '../../../middlewares' 18} from '../../../middlewares'
19import { logger, retryTransactionWrapper } from '../../../helpers' 19import { logger, retryTransactionWrapper } from '../../../helpers'
20import { quickAndDirtyUpdatesVideoToFriends } from '../../../lib' 20import { quickAndDirtyUpdatesVideoToFriends, fetchVideoChannelByHostAndUUID } from '../../../lib'
21import { PodInstance, VideoFileInstance } from '../../../models' 21import { PodInstance, VideoFileInstance } from '../../../models'
22import { 22import {
23 RemoteVideoRequest, 23 RemoteVideoRequest,
@@ -87,7 +87,7 @@ function remoteVideos (req: express.Request, res: express.Response, next: expres
87 const fromPod = res.locals.secure.pod 87 const fromPod = res.locals.secure.pod
88 88
89 // We need to process in the same order to keep consistency 89 // We need to process in the same order to keep consistency
90 Promise.each(requests, request => { 90 Bluebird.each(requests, request => {
91 const data = request.data 91 const data = request.data
92 92
93 // Get the function we need to call in order to process the request 93 // Get the function we need to call in order to process the request
@@ -109,7 +109,7 @@ function remoteVideosQadu (req: express.Request, res: express.Response, next: ex
109 const requests: RemoteQaduVideoRequest[] = req.body.data 109 const requests: RemoteQaduVideoRequest[] = req.body.data
110 const fromPod = res.locals.secure.pod 110 const fromPod = res.locals.secure.pod
111 111
112 Promise.each(requests, request => { 112 Bluebird.each(requests, request => {
113 const videoData = request.data 113 const videoData = request.data
114 114
115 return quickAndDirtyUpdateVideoRetryWrapper(videoData, fromPod) 115 return quickAndDirtyUpdateVideoRetryWrapper(videoData, fromPod)
@@ -123,7 +123,7 @@ function remoteVideosEvents (req: express.Request, res: express.Response, next:
123 const requests: RemoteVideoEventRequest[] = req.body.data 123 const requests: RemoteVideoEventRequest[] = req.body.data
124 const fromPod = res.locals.secure.pod 124 const fromPod = res.locals.secure.pod
125 125
126 Promise.each(requests, request => { 126 Bluebird.each(requests, request => {
127 const eventData = request.data 127 const eventData = request.data
128 128
129 return processVideosEventsRetryWrapper(eventData, fromPod) 129 return processVideosEventsRetryWrapper(eventData, fromPod)
@@ -133,541 +133,447 @@ function remoteVideosEvents (req: express.Request, res: express.Response, next:
133 return res.type('json').status(204).end() 133 return res.type('json').status(204).end()
134} 134}
135 135
136function processVideosEventsRetryWrapper (eventData: RemoteVideoEventData, fromPod: PodInstance) { 136async function processVideosEventsRetryWrapper (eventData: RemoteVideoEventData, fromPod: PodInstance) {
137 const options = { 137 const options = {
138 arguments: [ eventData, fromPod ], 138 arguments: [ eventData, fromPod ],
139 errorMessage: 'Cannot process videos events with many retries.' 139 errorMessage: 'Cannot process videos events with many retries.'
140 } 140 }
141 141
142 return retryTransactionWrapper(processVideosEvents, options) 142 await retryTransactionWrapper(processVideosEvents, options)
143} 143}
144 144
145function processVideosEvents (eventData: RemoteVideoEventData, fromPod: PodInstance) { 145async function processVideosEvents (eventData: RemoteVideoEventData, fromPod: PodInstance) {
146 await db.sequelize.transaction(async t => {
147 const sequelizeOptions = { transaction: t }
148 const videoInstance = await fetchVideoByUUID(eventData.uuid, t)
146 149
147 return db.sequelize.transaction(t => { 150 let columnToUpdate
148 return fetchVideoByUUID(eventData.uuid, t) 151 let qaduType
149 .then(videoInstance => {
150 const options = { transaction: t }
151 152
152 let columnToUpdate 153 switch (eventData.eventType) {
153 let qaduType 154 case REQUEST_VIDEO_EVENT_TYPES.VIEWS:
155 columnToUpdate = 'views'
156 qaduType = REQUEST_VIDEO_QADU_TYPES.VIEWS
157 break
154 158
155 switch (eventData.eventType) { 159 case REQUEST_VIDEO_EVENT_TYPES.LIKES:
156 case REQUEST_VIDEO_EVENT_TYPES.VIEWS: 160 columnToUpdate = 'likes'
157 columnToUpdate = 'views' 161 qaduType = REQUEST_VIDEO_QADU_TYPES.LIKES
158 qaduType = REQUEST_VIDEO_QADU_TYPES.VIEWS 162 break
159 break
160 163
161 case REQUEST_VIDEO_EVENT_TYPES.LIKES: 164 case REQUEST_VIDEO_EVENT_TYPES.DISLIKES:
162 columnToUpdate = 'likes' 165 columnToUpdate = 'dislikes'
163 qaduType = REQUEST_VIDEO_QADU_TYPES.LIKES 166 qaduType = REQUEST_VIDEO_QADU_TYPES.DISLIKES
164 break 167 break
165 168
166 case REQUEST_VIDEO_EVENT_TYPES.DISLIKES: 169 default:
167 columnToUpdate = 'dislikes' 170 throw new Error('Unknown video event type.')
168 qaduType = REQUEST_VIDEO_QADU_TYPES.DISLIKES 171 }
169 break
170 172
171 default: 173 const query = {}
172 throw new Error('Unknown video event type.') 174 query[columnToUpdate] = eventData.count
173 }
174 175
175 const query = {} 176 await videoInstance.increment(query, sequelizeOptions)
176 query[columnToUpdate] = eventData.count
177 177
178 return videoInstance.increment(query, options).then(() => ({ videoInstance, qaduType })) 178 const qadusParams = [
179 }) 179 {
180 .then(({ videoInstance, qaduType }) => { 180 videoId: videoInstance.id,
181 const qadusParams = [ 181 type: qaduType
182 { 182 }
183 videoId: videoInstance.id, 183 ]
184 type: qaduType 184 await quickAndDirtyUpdatesVideoToFriends(qadusParams, t)
185 }
186 ]
187
188 return quickAndDirtyUpdatesVideoToFriends(qadusParams, t)
189 })
190 })
191 .then(() => logger.info('Remote video event processed for video with uuid %s.', eventData.uuid))
192 .catch(err => {
193 logger.debug('Cannot process a video event.', err)
194 throw err
195 }) 185 })
186
187 logger.info('Remote video event processed for video with uuid %s.', eventData.uuid)
196} 188}
197 189
198function quickAndDirtyUpdateVideoRetryWrapper (videoData: RemoteQaduVideoData, fromPod: PodInstance) { 190async function quickAndDirtyUpdateVideoRetryWrapper (videoData: RemoteQaduVideoData, fromPod: PodInstance) {
199 const options = { 191 const options = {
200 arguments: [ videoData, fromPod ], 192 arguments: [ videoData, fromPod ],
201 errorMessage: 'Cannot update quick and dirty the remote video with many retries.' 193 errorMessage: 'Cannot update quick and dirty the remote video with many retries.'
202 } 194 }
203 195
204 return retryTransactionWrapper(quickAndDirtyUpdateVideo, options) 196 await retryTransactionWrapper(quickAndDirtyUpdateVideo, options)
205} 197}
206 198
207function quickAndDirtyUpdateVideo (videoData: RemoteQaduVideoData, fromPod: PodInstance) { 199async function quickAndDirtyUpdateVideo (videoData: RemoteQaduVideoData, fromPod: PodInstance) {
208 let videoUUID = '' 200 let videoUUID = ''
209 201
210 return db.sequelize.transaction(t => { 202 await db.sequelize.transaction(async t => {
211 return fetchVideoByHostAndUUID(fromPod.host, videoData.uuid, t) 203 const videoInstance = await fetchVideoByHostAndUUID(fromPod.host, videoData.uuid, t)
212 .then(videoInstance => { 204 const sequelizeOptions = { transaction: t }
213 const options = { transaction: t }
214 205
215 videoUUID = videoInstance.uuid 206 videoUUID = videoInstance.uuid
216 207
217 if (videoData.views) { 208 if (videoData.views) {
218 videoInstance.set('views', videoData.views) 209 videoInstance.set('views', videoData.views)
219 } 210 }
220 211
221 if (videoData.likes) { 212 if (videoData.likes) {
222 videoInstance.set('likes', videoData.likes) 213 videoInstance.set('likes', videoData.likes)
223 } 214 }
224 215
225 if (videoData.dislikes) { 216 if (videoData.dislikes) {
226 videoInstance.set('dislikes', videoData.dislikes) 217 videoInstance.set('dislikes', videoData.dislikes)
227 } 218 }
228 219
229 return videoInstance.save(options) 220 await videoInstance.save(sequelizeOptions)
230 })
231 }) 221 })
232 .then(() => logger.info('Remote video with uuid %s quick and dirty updated', videoUUID)) 222
233 .catch(err => logger.debug('Cannot quick and dirty update the remote video.', err)) 223 logger.info('Remote video with uuid %s quick and dirty updated', videoUUID)
234} 224}
235 225
236// Handle retries on fail 226// Handle retries on fail
237function addRemoteVideoRetryWrapper (videoToCreateData: RemoteVideoCreateData, fromPod: PodInstance) { 227async function addRemoteVideoRetryWrapper (videoToCreateData: RemoteVideoCreateData, fromPod: PodInstance) {
238 const options = { 228 const options = {
239 arguments: [ videoToCreateData, fromPod ], 229 arguments: [ videoToCreateData, fromPod ],
240 errorMessage: 'Cannot insert the remote video with many retries.' 230 errorMessage: 'Cannot insert the remote video with many retries.'
241 } 231 }
242 232
243 return retryTransactionWrapper(addRemoteVideo, options) 233 await retryTransactionWrapper(addRemoteVideo, options)
244} 234}
245 235
246function addRemoteVideo (videoToCreateData: RemoteVideoCreateData, fromPod: PodInstance) { 236async function addRemoteVideo (videoToCreateData: RemoteVideoCreateData, fromPod: PodInstance) {
247 logger.debug('Adding remote video "%s".', videoToCreateData.uuid) 237 logger.debug('Adding remote video "%s".', videoToCreateData.uuid)
248 238
249 return db.sequelize.transaction(t => { 239 await db.sequelize.transaction(async t => {
250 return db.Video.loadByUUID(videoToCreateData.uuid) 240 const sequelizeOptions = {
251 .then(video => { 241 transaction: t
252 if (video) throw new Error('UUID already exists.') 242 }
253
254 return db.VideoChannel.loadByHostAndUUID(fromPod.host, videoToCreateData.channelUUID, t)
255 })
256 .then(videoChannel => {
257 if (!videoChannel) throw new Error('Video channel ' + videoToCreateData.channelUUID + ' not found.')
258 243
259 const tags = videoToCreateData.tags 244 const videoFromDatabase = await db.Video.loadByUUID(videoToCreateData.uuid)
245 if (videoFromDatabase) throw new Error('UUID already exists.')
246
247 const videoChannel = await db.VideoChannel.loadByHostAndUUID(fromPod.host, videoToCreateData.channelUUID, t)
248 if (!videoChannel) throw new Error('Video channel ' + videoToCreateData.channelUUID + ' not found.')
249
250 const tags = videoToCreateData.tags
251 const tagInstances = await db.Tag.findOrCreateTags(tags, t)
252
253 const videoData = {
254 name: videoToCreateData.name,
255 uuid: videoToCreateData.uuid,
256 category: videoToCreateData.category,
257 licence: videoToCreateData.licence,
258 language: videoToCreateData.language,
259 nsfw: videoToCreateData.nsfw,
260 description: videoToCreateData.description,
261 channelId: videoChannel.id,
262 duration: videoToCreateData.duration,
263 createdAt: videoToCreateData.createdAt,
264 // FIXME: updatedAt does not seems to be considered by Sequelize
265 updatedAt: videoToCreateData.updatedAt,
266 views: videoToCreateData.views,
267 likes: videoToCreateData.likes,
268 dislikes: videoToCreateData.dislikes,
269 remote: true
270 }
260 271
261 return db.Tag.findOrCreateTags(tags, t).then(tagInstances => ({ videoChannel, tagInstances })) 272 const video = db.Video.build(videoData)
262 }) 273 await db.Video.generateThumbnailFromData(video, videoToCreateData.thumbnailData)
263 .then(({ videoChannel, tagInstances }) => { 274 const videoCreated = await video.save(sequelizeOptions)
264 const videoData = { 275
265 name: videoToCreateData.name, 276 const tasks = []
266 uuid: videoToCreateData.uuid, 277 for (const fileData of videoToCreateData.files) {
267 category: videoToCreateData.category, 278 const videoFileInstance = db.VideoFile.build({
268 licence: videoToCreateData.licence, 279 extname: fileData.extname,
269 language: videoToCreateData.language, 280 infoHash: fileData.infoHash,
270 nsfw: videoToCreateData.nsfw, 281 resolution: fileData.resolution,
271 description: videoToCreateData.description, 282 size: fileData.size,
272 channelId: videoChannel.id, 283 videoId: videoCreated.id
273 duration: videoToCreateData.duration,
274 createdAt: videoToCreateData.createdAt,
275 // FIXME: updatedAt does not seems to be considered by Sequelize
276 updatedAt: videoToCreateData.updatedAt,
277 views: videoToCreateData.views,
278 likes: videoToCreateData.likes,
279 dislikes: videoToCreateData.dislikes,
280 remote: true
281 }
282
283 const video = db.Video.build(videoData)
284 return { tagInstances, video }
285 })
286 .then(({ tagInstances, video }) => {
287 return db.Video.generateThumbnailFromData(video, videoToCreateData.thumbnailData).then(() => ({ tagInstances, video }))
288 }) 284 })
289 .then(({ tagInstances, video }) => {
290 const options = {
291 transaction: t
292 }
293 285
294 return video.save(options).then(videoCreated => ({ tagInstances, videoCreated })) 286 tasks.push(videoFileInstance.save(sequelizeOptions))
295 }) 287 }
296 .then(({ tagInstances, videoCreated }) => {
297 const tasks = []
298 const options = {
299 transaction: t
300 }
301
302 videoToCreateData.files.forEach(fileData => {
303 const videoFileInstance = db.VideoFile.build({
304 extname: fileData.extname,
305 infoHash: fileData.infoHash,
306 resolution: fileData.resolution,
307 size: fileData.size,
308 videoId: videoCreated.id
309 })
310
311 tasks.push(videoFileInstance.save(options))
312 })
313 288
314 return Promise.all(tasks).then(() => ({ tagInstances, videoCreated })) 289 await Promise.all(tasks)
315 })
316 .then(({ tagInstances, videoCreated }) => {
317 const options = {
318 transaction: t
319 }
320 290
321 return videoCreated.setTags(tagInstances, options) 291 await videoCreated.setTags(tagInstances, sequelizeOptions)
322 })
323 })
324 .then(() => logger.info('Remote video with uuid %s inserted.', videoToCreateData.uuid))
325 .catch(err => {
326 logger.debug('Cannot insert the remote video.', err)
327 throw err
328 }) 292 })
293
294 logger.info('Remote video with uuid %s inserted.', videoToCreateData.uuid)
329} 295}
330 296
331// Handle retries on fail 297// Handle retries on fail
332function updateRemoteVideoRetryWrapper (videoAttributesToUpdate: RemoteVideoUpdateData, fromPod: PodInstance) { 298async function updateRemoteVideoRetryWrapper (videoAttributesToUpdate: RemoteVideoUpdateData, fromPod: PodInstance) {
333 const options = { 299 const options = {
334 arguments: [ videoAttributesToUpdate, fromPod ], 300 arguments: [ videoAttributesToUpdate, fromPod ],
335 errorMessage: 'Cannot update the remote video with many retries' 301 errorMessage: 'Cannot update the remote video with many retries'
336 } 302 }
337 303
338 return retryTransactionWrapper(updateRemoteVideo, options) 304 await retryTransactionWrapper(updateRemoteVideo, options)
339} 305}
340 306
341function updateRemoteVideo (videoAttributesToUpdate: RemoteVideoUpdateData, fromPod: PodInstance) { 307async function updateRemoteVideo (videoAttributesToUpdate: RemoteVideoUpdateData, fromPod: PodInstance) {
342 logger.debug('Updating remote video "%s".', videoAttributesToUpdate.uuid) 308 logger.debug('Updating remote video "%s".', videoAttributesToUpdate.uuid)
343 309
344 return db.sequelize.transaction(t => { 310 try {
345 return fetchVideoByHostAndUUID(fromPod.host, videoAttributesToUpdate.uuid, t) 311 await db.sequelize.transaction(async t => {
346 .then(videoInstance => { 312 const sequelizeOptions = {
347 const tags = videoAttributesToUpdate.tags 313 transaction: t
348 314 }
349 return db.Tag.findOrCreateTags(tags, t).then(tagInstances => ({ videoInstance, tagInstances })) 315
350 }) 316 const videoInstance = await fetchVideoByHostAndUUID(fromPod.host, videoAttributesToUpdate.uuid, t)
351 .then(({ videoInstance, tagInstances }) => { 317 const tags = videoAttributesToUpdate.tags
352 const options = { transaction: t } 318
353 319 const tagInstances = await db.Tag.findOrCreateTags(tags, t)
354 videoInstance.set('name', videoAttributesToUpdate.name) 320
355 videoInstance.set('category', videoAttributesToUpdate.category) 321 videoInstance.set('name', videoAttributesToUpdate.name)
356 videoInstance.set('licence', videoAttributesToUpdate.licence) 322 videoInstance.set('category', videoAttributesToUpdate.category)
357 videoInstance.set('language', videoAttributesToUpdate.language) 323 videoInstance.set('licence', videoAttributesToUpdate.licence)
358 videoInstance.set('nsfw', videoAttributesToUpdate.nsfw) 324 videoInstance.set('language', videoAttributesToUpdate.language)
359 videoInstance.set('description', videoAttributesToUpdate.description) 325 videoInstance.set('nsfw', videoAttributesToUpdate.nsfw)
360 videoInstance.set('duration', videoAttributesToUpdate.duration) 326 videoInstance.set('description', videoAttributesToUpdate.description)
361 videoInstance.set('createdAt', videoAttributesToUpdate.createdAt) 327 videoInstance.set('duration', videoAttributesToUpdate.duration)
362 videoInstance.set('updatedAt', videoAttributesToUpdate.updatedAt) 328 videoInstance.set('createdAt', videoAttributesToUpdate.createdAt)
363 videoInstance.set('views', videoAttributesToUpdate.views) 329 videoInstance.set('updatedAt', videoAttributesToUpdate.updatedAt)
364 videoInstance.set('likes', videoAttributesToUpdate.likes) 330 videoInstance.set('views', videoAttributesToUpdate.views)
365 videoInstance.set('dislikes', videoAttributesToUpdate.dislikes) 331 videoInstance.set('likes', videoAttributesToUpdate.likes)
366 332 videoInstance.set('dislikes', videoAttributesToUpdate.dislikes)
367 return videoInstance.save(options).then(() => ({ videoInstance, tagInstances })) 333
368 }) 334 await videoInstance.save(sequelizeOptions)
369 .then(({ tagInstances, videoInstance }) => { 335
370 const tasks: Promise<void>[] = [] 336 // Remove old video files
371 337 const videoFileDestroyTasks: Bluebird<void>[] = []
372 // Remove old video files 338 for (const videoFile of videoInstance.VideoFiles) {
373 videoInstance.VideoFiles.forEach(videoFile => { 339 videoFileDestroyTasks.push(videoFile.destroy(sequelizeOptions))
374 tasks.push(videoFile.destroy({ transaction: t })) 340 }
341 await Promise.all(videoFileDestroyTasks)
342
343 const videoFileCreateTasks: Bluebird<VideoFileInstance>[] = []
344 for (const fileData of videoAttributesToUpdate.files) {
345 const videoFileInstance = db.VideoFile.build({
346 extname: fileData.extname,
347 infoHash: fileData.infoHash,
348 resolution: fileData.resolution,
349 size: fileData.size,
350 videoId: videoInstance.id
375 }) 351 })
376 352
377 return Promise.all(tasks).then(() => ({ tagInstances, videoInstance })) 353 videoFileCreateTasks.push(videoFileInstance.save(sequelizeOptions))
378 }) 354 }
379 .then(({ tagInstances, videoInstance }) => {
380 const tasks: Promise<VideoFileInstance>[] = []
381 const options = {
382 transaction: t
383 }
384
385 videoAttributesToUpdate.files.forEach(fileData => {
386 const videoFileInstance = db.VideoFile.build({
387 extname: fileData.extname,
388 infoHash: fileData.infoHash,
389 resolution: fileData.resolution,
390 size: fileData.size,
391 videoId: videoInstance.id
392 })
393
394 tasks.push(videoFileInstance.save(options))
395 })
396 355
397 return Promise.all(tasks).then(() => ({ tagInstances, videoInstance })) 356 await Promise.all(videoFileCreateTasks)
398 })
399 .then(({ videoInstance, tagInstances }) => {
400 const options = { transaction: t }
401 357
402 return videoInstance.setTags(tagInstances, options) 358 await videoInstance.setTags(tagInstances, sequelizeOptions)
403 }) 359 })
404 }) 360
405 .then(() => logger.info('Remote video with uuid %s updated', videoAttributesToUpdate.uuid)) 361 logger.info('Remote video with uuid %s updated', videoAttributesToUpdate.uuid)
406 .catch(err => { 362 } catch (err) {
407 // This is just a debug because we will retry the insert 363 // This is just a debug because we will retry the insert
408 logger.debug('Cannot update the remote video.', err) 364 logger.debug('Cannot update the remote video.', err)
409 throw err 365 throw err
410 }) 366 }
411} 367}
412 368
413function removeRemoteVideoRetryWrapper (videoToRemoveData: RemoteVideoRemoveData, fromPod: PodInstance) { 369async function removeRemoteVideoRetryWrapper (videoToRemoveData: RemoteVideoRemoveData, fromPod: PodInstance) {
414 const options = { 370 const options = {
415 arguments: [ videoToRemoveData, fromPod ], 371 arguments: [ videoToRemoveData, fromPod ],
416 errorMessage: 'Cannot remove the remote video channel with many retries.' 372 errorMessage: 'Cannot remove the remote video channel with many retries.'
417 } 373 }
418 374
419 return retryTransactionWrapper(removeRemoteVideo, options) 375 await retryTransactionWrapper(removeRemoteVideo, options)
420} 376}
421 377
422function removeRemoteVideo (videoToRemoveData: RemoteVideoRemoveData, fromPod: PodInstance) { 378async function removeRemoteVideo (videoToRemoveData: RemoteVideoRemoveData, fromPod: PodInstance) {
423 logger.debug('Removing remote video "%s".', videoToRemoveData.uuid) 379 logger.debug('Removing remote video "%s".', videoToRemoveData.uuid)
424 380
425 return db.sequelize.transaction(t => { 381 await db.sequelize.transaction(async t => {
426 // We need the instance because we have to remove some other stuffs (thumbnail etc) 382 // We need the instance because we have to remove some other stuffs (thumbnail etc)
427 return fetchVideoByHostAndUUID(fromPod.host, videoToRemoveData.uuid, t) 383 const videoInstance = await fetchVideoByHostAndUUID(fromPod.host, videoToRemoveData.uuid, t)
428 .then(video => video.destroy({ transaction: t })) 384 await videoInstance.destroy({ transaction: t })
429 })
430 .then(() => logger.info('Remote video with uuid %s removed.', videoToRemoveData.uuid))
431 .catch(err => {
432 logger.debug('Cannot remove the remote video.', err)
433 throw err
434 }) 385 })
386
387 logger.info('Remote video with uuid %s removed.', videoToRemoveData.uuid)
435} 388}
436 389
437function addRemoteVideoAuthorRetryWrapper (authorToCreateData: RemoteVideoAuthorCreateData, fromPod: PodInstance) { 390async function addRemoteVideoAuthorRetryWrapper (authorToCreateData: RemoteVideoAuthorCreateData, fromPod: PodInstance) {
438 const options = { 391 const options = {
439 arguments: [ authorToCreateData, fromPod ], 392 arguments: [ authorToCreateData, fromPod ],
440 errorMessage: 'Cannot insert the remote video author with many retries.' 393 errorMessage: 'Cannot insert the remote video author with many retries.'
441 } 394 }
442 395
443 return retryTransactionWrapper(addRemoteVideoAuthor, options) 396 await retryTransactionWrapper(addRemoteVideoAuthor, options)
444} 397}
445 398
446function addRemoteVideoAuthor (authorToCreateData: RemoteVideoAuthorCreateData, fromPod: PodInstance) { 399async function addRemoteVideoAuthor (authorToCreateData: RemoteVideoAuthorCreateData, fromPod: PodInstance) {
447 logger.debug('Adding remote video author "%s".', authorToCreateData.uuid) 400 logger.debug('Adding remote video author "%s".', authorToCreateData.uuid)
448 401
449 return db.sequelize.transaction(t => { 402 await db.sequelize.transaction(async t => {
450 return db.Author.loadAuthorByPodAndUUID(authorToCreateData.uuid, fromPod.id, t) 403 const authorInDatabase = await db.Author.loadAuthorByPodAndUUID(authorToCreateData.uuid, fromPod.id, t)
451 .then(author => { 404 if (authorInDatabase) throw new Error('Author with UUID ' + authorToCreateData.uuid + ' already exists.')
452 if (author) throw new Error('UUID already exists.')
453 405
454 return undefined 406 const videoAuthorData = {
455 }) 407 name: authorToCreateData.name,
456 .then(() => { 408 uuid: authorToCreateData.uuid,
457 const videoAuthorData = { 409 userId: null, // Not on our pod
458 name: authorToCreateData.name, 410 podId: fromPod.id
459 uuid: authorToCreateData.uuid, 411 }
460 userId: null, // Not on our pod 412
461 podId: fromPod.id 413 const author = db.Author.build(videoAuthorData)
462 } 414 await author.save({ transaction: t })
463
464 const author = db.Author.build(videoAuthorData)
465 return author.save({ transaction: t })
466 })
467 }) 415 })
468 .then(() => logger.info('Remote video author with uuid %s inserted.', authorToCreateData.uuid)) 416
469 .catch(err => { 417 logger.info('Remote video author with uuid %s inserted.', authorToCreateData.uuid)
470 logger.debug('Cannot insert the remote video author.', err)
471 throw err
472 })
473} 418}
474 419
475function removeRemoteVideoAuthorRetryWrapper (authorAttributesToRemove: RemoteVideoAuthorRemoveData, fromPod: PodInstance) { 420async function removeRemoteVideoAuthorRetryWrapper (authorAttributesToRemove: RemoteVideoAuthorRemoveData, fromPod: PodInstance) {
476 const options = { 421 const options = {
477 arguments: [ authorAttributesToRemove, fromPod ], 422 arguments: [ authorAttributesToRemove, fromPod ],
478 errorMessage: 'Cannot remove the remote video author with many retries.' 423 errorMessage: 'Cannot remove the remote video author with many retries.'
479 } 424 }
480 425
481 return retryTransactionWrapper(removeRemoteVideoAuthor, options) 426 await retryTransactionWrapper(removeRemoteVideoAuthor, options)
482} 427}
483 428
484function removeRemoteVideoAuthor (authorAttributesToRemove: RemoteVideoAuthorRemoveData, fromPod: PodInstance) { 429async function removeRemoteVideoAuthor (authorAttributesToRemove: RemoteVideoAuthorRemoveData, fromPod: PodInstance) {
485 logger.debug('Removing remote video author "%s".', authorAttributesToRemove.uuid) 430 logger.debug('Removing remote video author "%s".', authorAttributesToRemove.uuid)
486 431
487 return db.sequelize.transaction(t => { 432 await db.sequelize.transaction(async t => {
488 return db.Author.loadAuthorByPodAndUUID(authorAttributesToRemove.uuid, fromPod.id, t) 433 const videoAuthor = await db.Author.loadAuthorByPodAndUUID(authorAttributesToRemove.uuid, fromPod.id, t)
489 .then(videoAuthor => videoAuthor.destroy({ transaction: t })) 434 await videoAuthor.destroy({ transaction: t })
490 })
491 .then(() => logger.info('Remote video author with uuid %s removed.', authorAttributesToRemove.uuid))
492 .catch(err => {
493 logger.debug('Cannot remove the remote video author.', err)
494 throw err
495 }) 435 })
436
437 logger.info('Remote video author with uuid %s removed.', authorAttributesToRemove.uuid)
496} 438}
497 439
498function addRemoteVideoChannelRetryWrapper (videoChannelToCreateData: RemoteVideoChannelCreateData, fromPod: PodInstance) { 440async function addRemoteVideoChannelRetryWrapper (videoChannelToCreateData: RemoteVideoChannelCreateData, fromPod: PodInstance) {
499 const options = { 441 const options = {
500 arguments: [ videoChannelToCreateData, fromPod ], 442 arguments: [ videoChannelToCreateData, fromPod ],
501 errorMessage: 'Cannot insert the remote video channel with many retries.' 443 errorMessage: 'Cannot insert the remote video channel with many retries.'
502 } 444 }
503 445
504 return retryTransactionWrapper(addRemoteVideoChannel, options) 446 await retryTransactionWrapper(addRemoteVideoChannel, options)
505} 447}
506 448
507function addRemoteVideoChannel (videoChannelToCreateData: RemoteVideoChannelCreateData, fromPod: PodInstance) { 449async function addRemoteVideoChannel (videoChannelToCreateData: RemoteVideoChannelCreateData, fromPod: PodInstance) {
508 logger.debug('Adding remote video channel "%s".', videoChannelToCreateData.uuid) 450 logger.debug('Adding remote video channel "%s".', videoChannelToCreateData.uuid)
509 451
510 return db.sequelize.transaction(t => { 452 await db.sequelize.transaction(async t => {
511 return db.VideoChannel.loadByUUID(videoChannelToCreateData.uuid) 453 const videoChannelInDatabase = await db.VideoChannel.loadByUUID(videoChannelToCreateData.uuid)
512 .then(videoChannel => { 454 if (videoChannelInDatabase) {
513 if (videoChannel) throw new Error('UUID already exists.') 455 throw new Error('Video channel with UUID ' + videoChannelToCreateData.uuid + ' already exists.')
456 }
514 457
515 return undefined 458 const authorUUID = videoChannelToCreateData.ownerUUID
516 }) 459 const podId = fromPod.id
517 .then(() => {
518 const authorUUID = videoChannelToCreateData.ownerUUID
519 const podId = fromPod.id
520 460
521 return db.Author.loadAuthorByPodAndUUID(authorUUID, podId, t) 461 const author = await db.Author.loadAuthorByPodAndUUID(authorUUID, podId, t)
522 }) 462 if (!author) throw new Error('Unknown author UUID' + authorUUID + '.')
523 .then(author => { 463
524 if (!author) throw new Error('Unknown author UUID.') 464 const videoChannelData = {
525 465 name: videoChannelToCreateData.name,
526 const videoChannelData = { 466 description: videoChannelToCreateData.description,
527 name: videoChannelToCreateData.name, 467 uuid: videoChannelToCreateData.uuid,
528 description: videoChannelToCreateData.description, 468 createdAt: videoChannelToCreateData.createdAt,
529 uuid: videoChannelToCreateData.uuid, 469 updatedAt: videoChannelToCreateData.updatedAt,
530 createdAt: videoChannelToCreateData.createdAt, 470 remote: true,
531 updatedAt: videoChannelToCreateData.updatedAt, 471 authorId: author.id
532 remote: true, 472 }
533 authorId: author.id 473
534 } 474 const videoChannel = db.VideoChannel.build(videoChannelData)
535 475 await videoChannel.save({ transaction: t })
536 const videoChannel = db.VideoChannel.build(videoChannelData)
537 return videoChannel.save({ transaction: t })
538 })
539 })
540 .then(() => logger.info('Remote video channel with uuid %s inserted.', videoChannelToCreateData.uuid))
541 .catch(err => {
542 logger.debug('Cannot insert the remote video channel.', err)
543 throw err
544 }) 476 })
477
478 logger.info('Remote video channel with uuid %s inserted.', videoChannelToCreateData.uuid)
545} 479}
546 480
547function updateRemoteVideoChannelRetryWrapper (videoChannelAttributesToUpdate: RemoteVideoChannelUpdateData, fromPod: PodInstance) { 481async function updateRemoteVideoChannelRetryWrapper (videoChannelAttributesToUpdate: RemoteVideoChannelUpdateData, fromPod: PodInstance) {
548 const options = { 482 const options = {
549 arguments: [ videoChannelAttributesToUpdate, fromPod ], 483 arguments: [ videoChannelAttributesToUpdate, fromPod ],
550 errorMessage: 'Cannot update the remote video channel with many retries.' 484 errorMessage: 'Cannot update the remote video channel with many retries.'
551 } 485 }
552 486
553 return retryTransactionWrapper(updateRemoteVideoChannel, options) 487 await retryTransactionWrapper(updateRemoteVideoChannel, options)
554} 488}
555 489
556function updateRemoteVideoChannel (videoChannelAttributesToUpdate: RemoteVideoChannelUpdateData, fromPod: PodInstance) { 490async function updateRemoteVideoChannel (videoChannelAttributesToUpdate: RemoteVideoChannelUpdateData, fromPod: PodInstance) {
557 logger.debug('Updating remote video channel "%s".', videoChannelAttributesToUpdate.uuid) 491 logger.debug('Updating remote video channel "%s".', videoChannelAttributesToUpdate.uuid)
558 492
559 return db.sequelize.transaction(t => { 493 await db.sequelize.transaction(async t => {
560 return fetchVideoChannelByHostAndUUID(fromPod.host, videoChannelAttributesToUpdate.uuid, t) 494 const sequelizeOptions = { transaction: t }
561 .then(videoChannelInstance => {
562 const options = { transaction: t }
563 495
564 videoChannelInstance.set('name', videoChannelAttributesToUpdate.name) 496 const videoChannelInstance = await fetchVideoChannelByHostAndUUID(fromPod.host, videoChannelAttributesToUpdate.uuid, t)
565 videoChannelInstance.set('description', videoChannelAttributesToUpdate.description) 497 videoChannelInstance.set('name', videoChannelAttributesToUpdate.name)
566 videoChannelInstance.set('createdAt', videoChannelAttributesToUpdate.createdAt) 498 videoChannelInstance.set('description', videoChannelAttributesToUpdate.description)
567 videoChannelInstance.set('updatedAt', videoChannelAttributesToUpdate.updatedAt) 499 videoChannelInstance.set('createdAt', videoChannelAttributesToUpdate.createdAt)
500 videoChannelInstance.set('updatedAt', videoChannelAttributesToUpdate.updatedAt)
568 501
569 return videoChannelInstance.save(options) 502 await videoChannelInstance.save(sequelizeOptions)
570 })
571 })
572 .then(() => logger.info('Remote video channel with uuid %s updated', videoChannelAttributesToUpdate.uuid))
573 .catch(err => {
574 // This is just a debug because we will retry the insert
575 logger.debug('Cannot update the remote video channel.', err)
576 throw err
577 }) 503 })
504
505 logger.info('Remote video channel with uuid %s updated', videoChannelAttributesToUpdate.uuid)
578} 506}
579 507
580function removeRemoteVideoChannelRetryWrapper (videoChannelAttributesToRemove: RemoteVideoChannelRemoveData, fromPod: PodInstance) { 508async function removeRemoteVideoChannelRetryWrapper (videoChannelAttributesToRemove: RemoteVideoChannelRemoveData, fromPod: PodInstance) {
581 const options = { 509 const options = {
582 arguments: [ videoChannelAttributesToRemove, fromPod ], 510 arguments: [ videoChannelAttributesToRemove, fromPod ],
583 errorMessage: 'Cannot remove the remote video channel with many retries.' 511 errorMessage: 'Cannot remove the remote video channel with many retries.'
584 } 512 }
585 513
586 return retryTransactionWrapper(removeRemoteVideoChannel, options) 514 await retryTransactionWrapper(removeRemoteVideoChannel, options)
587} 515}
588 516
589function removeRemoteVideoChannel (videoChannelAttributesToRemove: RemoteVideoChannelRemoveData, fromPod: PodInstance) { 517async function removeRemoteVideoChannel (videoChannelAttributesToRemove: RemoteVideoChannelRemoveData, fromPod: PodInstance) {
590 logger.debug('Removing remote video channel "%s".', videoChannelAttributesToRemove.uuid) 518 logger.debug('Removing remote video channel "%s".', videoChannelAttributesToRemove.uuid)
591 519
592 return db.sequelize.transaction(t => { 520 await db.sequelize.transaction(async t => {
593 return fetchVideoChannelByHostAndUUID(fromPod.host, videoChannelAttributesToRemove.uuid, t) 521 const videoChannel = await fetchVideoChannelByHostAndUUID(fromPod.host, videoChannelAttributesToRemove.uuid, t)
594 .then(videoChannel => videoChannel.destroy({ transaction: t })) 522 await videoChannel.destroy({ transaction: t })
595 })
596 .then(() => logger.info('Remote video channel with uuid %s removed.', videoChannelAttributesToRemove.uuid))
597 .catch(err => {
598 logger.debug('Cannot remove the remote video channel.', err)
599 throw err
600 }) 523 })
524
525 logger.info('Remote video channel with uuid %s removed.', videoChannelAttributesToRemove.uuid)
601} 526}
602 527
603function reportAbuseRemoteVideoRetryWrapper (reportData: RemoteVideoReportAbuseData, fromPod: PodInstance) { 528async function reportAbuseRemoteVideoRetryWrapper (reportData: RemoteVideoReportAbuseData, fromPod: PodInstance) {
604 const options = { 529 const options = {
605 arguments: [ reportData, fromPod ], 530 arguments: [ reportData, fromPod ],
606 errorMessage: 'Cannot create remote abuse video with many retries.' 531 errorMessage: 'Cannot create remote abuse video with many retries.'
607 } 532 }
608 533
609 return retryTransactionWrapper(reportAbuseRemoteVideo, options) 534 await retryTransactionWrapper(reportAbuseRemoteVideo, options)
610} 535}
611 536
612function reportAbuseRemoteVideo (reportData: RemoteVideoReportAbuseData, fromPod: PodInstance) { 537async function reportAbuseRemoteVideo (reportData: RemoteVideoReportAbuseData, fromPod: PodInstance) {
613 logger.debug('Reporting remote abuse for video %s.', reportData.videoUUID) 538 logger.debug('Reporting remote abuse for video %s.', reportData.videoUUID)
614 539
615 return db.sequelize.transaction(t => { 540 await db.sequelize.transaction(async t => {
616 return fetchVideoByUUID(reportData.videoUUID, t) 541 const videoInstance = await fetchVideoByUUID(reportData.videoUUID, t)
617 .then(video => { 542 const videoAbuseData = {
618 const videoAbuseData = { 543 reporterUsername: reportData.reporterUsername,
619 reporterUsername: reportData.reporterUsername, 544 reason: reportData.reportReason,
620 reason: reportData.reportReason, 545 reporterPodId: fromPod.id,
621 reporterPodId: fromPod.id, 546 videoId: videoInstance.id
622 videoId: video.id 547 }
623 }
624
625 return db.VideoAbuse.create(videoAbuseData)
626 })
627 })
628 .then(() => logger.info('Remote abuse for video uuid %s created', reportData.videoUUID))
629 .catch(err => {
630 // This is just a debug because we will retry the insert
631 logger.debug('Cannot create remote abuse video', err)
632 throw err
633 })
634}
635 548
636function fetchVideoByUUID (id: string, t: Sequelize.Transaction) { 549 await db.VideoAbuse.create(videoAbuseData)
637 return db.Video.loadByUUID(id, t)
638 .then(video => {
639 if (!video) throw new Error('Video not found')
640 550
641 return video 551 })
642 }) 552
643 .catch(err => { 553 logger.info('Remote abuse for video uuid %s created', reportData.videoUUID)
644 logger.error('Cannot load owned video from id.', { error: err.stack, id })
645 throw err
646 })
647} 554}
648 555
649function fetchVideoByHostAndUUID (podHost: string, uuid: string, t: Sequelize.Transaction) { 556async function fetchVideoByUUID (id: string, t: Sequelize.Transaction) {
650 return db.Video.loadByHostAndUUID(podHost, uuid, t) 557 try {
651 .then(video => { 558 const video = await db.Video.loadByUUID(id, t)
652 if (!video) throw new Error('Video not found')
653 559
654 return video 560 if (!video) throw new Error('Video ' + id + ' not found')
655 }) 561
656 .catch(err => { 562 return video
657 logger.error('Cannot load video from host and uuid.', { error: err.stack, podHost, uuid }) 563 } catch (err) {
658 throw err 564 logger.error('Cannot load owned video from id.', { error: err.stack, id })
659 }) 565 throw err
566 }
660} 567}
661 568
662function fetchVideoChannelByHostAndUUID (podHost: string, uuid: string, t: Sequelize.Transaction) { 569async function fetchVideoByHostAndUUID (podHost: string, uuid: string, t: Sequelize.Transaction) {
663 return db.VideoChannel.loadByHostAndUUID(podHost, uuid, t) 570 try {
664 .then(videoChannel => { 571 const video = await db.Video.loadByHostAndUUID(podHost, uuid, t)
665 if (!videoChannel) throw new Error('Video channel not found') 572 if (!video) throw new Error('Video not found')
666 573
667 return videoChannel 574 return video
668 }) 575 } catch (err) {
669 .catch(err => { 576 logger.error('Cannot load video from host and uuid.', { error: err.stack, podHost, uuid })
670 logger.error('Cannot load video channel from host and uuid.', { error: err.stack, podHost, uuid }) 577 throw err
671 throw err 578 }
672 })
673} 579}
diff --git a/server/controllers/api/request-schedulers.ts b/server/controllers/api/request-schedulers.ts
index 2a934a512..28f46f3ee 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 * as Promise from 'bluebird' 2import * as Bluebird from 'bluebird'
3 3
4import { 4import {
5 AbstractRequestScheduler, 5 AbstractRequestScheduler,
@@ -7,7 +7,7 @@ import {
7 getRequestVideoQaduScheduler, 7 getRequestVideoQaduScheduler,
8 getRequestVideoEventScheduler 8 getRequestVideoEventScheduler
9} from '../../lib' 9} from '../../lib'
10import { authenticate, ensureIsAdmin } from '../../middlewares' 10import { authenticate, ensureIsAdmin, asyncMiddleware } from '../../middlewares'
11import { RequestSchedulerStatsAttributes } from '../../../shared' 11import { RequestSchedulerStatsAttributes } from '../../../shared'
12 12
13const requestSchedulerRouter = express.Router() 13const requestSchedulerRouter = express.Router()
@@ -15,7 +15,7 @@ const requestSchedulerRouter = express.Router()
15requestSchedulerRouter.get('/stats', 15requestSchedulerRouter.get('/stats',
16 authenticate, 16 authenticate,
17 ensureIsAdmin, 17 ensureIsAdmin,
18 getRequestSchedulersStats 18 asyncMiddleware(getRequestSchedulersStats)
19) 19)
20 20
21// --------------------------------------------------------------------------- 21// ---------------------------------------------------------------------------
@@ -26,28 +26,28 @@ export {
26 26
27// --------------------------------------------------------------------------- 27// ---------------------------------------------------------------------------
28 28
29function getRequestSchedulersStats (req: express.Request, res: express.Response, next: express.NextFunction) { 29async function getRequestSchedulersStats (req: express.Request, res: express.Response, next: express.NextFunction) {
30 Promise.props({ 30 const result = await Bluebird.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 }) 34 })
35 .then(result => res.json(result)) 35
36 .catch(err => next(err)) 36 return res.json(result)
37} 37}
38 38
39// --------------------------------------------------------------------------- 39// ---------------------------------------------------------------------------
40 40
41function buildRequestSchedulerStats (requestScheduler: AbstractRequestScheduler<any>) { 41async function buildRequestSchedulerStats (requestScheduler: AbstractRequestScheduler<any>) {
42 return requestScheduler.remainingRequestsCount().then(count => { 42 const count = await requestScheduler.remainingRequestsCount()
43 const result: RequestSchedulerStatsAttributes = { 43
44 totalRequests: count, 44 const result: RequestSchedulerStatsAttributes = {
45 requestsLimitPods: requestScheduler.limitPods, 45 totalRequests: count,
46 requestsLimitPerPod: requestScheduler.limitPerPod, 46 requestsLimitPods: requestScheduler.limitPods,
47 remainingMilliSeconds: requestScheduler.remainingMilliSeconds(), 47 requestsLimitPerPod: requestScheduler.limitPerPod,
48 milliSecondsInterval: requestScheduler.requestInterval 48 remainingMilliSeconds: requestScheduler.remainingMilliSeconds(),
49 } 49 milliSecondsInterval: requestScheduler.requestInterval
50 50 }
51 return result 51
52 }) 52 return result
53} 53}
diff --git a/server/controllers/api/users.ts b/server/controllers/api/users.ts
index 6576e4333..a7528328a 100644
--- a/server/controllers/api/users.ts
+++ b/server/controllers/api/users.ts
@@ -18,7 +18,8 @@ import {
18 setPagination, 18 setPagination,
19 usersSortValidator, 19 usersSortValidator,
20 setUsersSort, 20 setUsersSort,
21 token 21 token,
22 asyncMiddleware
22} from '../../middlewares' 23} from '../../middlewares'
23import { 24import {
24 UserVideoRate as FormattedUserVideoRate, 25 UserVideoRate as FormattedUserVideoRate,
@@ -33,13 +34,13 @@ const usersRouter = express.Router()
33 34
34usersRouter.get('/me', 35usersRouter.get('/me',
35 authenticate, 36 authenticate,
36 getUserInformation 37 asyncMiddleware(getUserInformation)
37) 38)
38 39
39usersRouter.get('/me/videos/:videoId/rating', 40usersRouter.get('/me/videos/:videoId/rating',
40 authenticate, 41 authenticate,
41 usersVideoRatingValidator, 42 usersVideoRatingValidator,
42 getUserVideoRating 43 asyncMiddleware(getUserVideoRating)
43) 44)
44 45
45usersRouter.get('/', 46usersRouter.get('/',
@@ -47,7 +48,7 @@ usersRouter.get('/',
47 usersSortValidator, 48 usersSortValidator,
48 setUsersSort, 49 setUsersSort,
49 setPagination, 50 setPagination,
50 listUsers 51 asyncMiddleware(listUsers)
51) 52)
52 53
53usersRouter.get('/:id', 54usersRouter.get('/:id',
@@ -65,27 +66,27 @@ usersRouter.post('/',
65usersRouter.post('/register', 66usersRouter.post('/register',
66 ensureUserRegistrationAllowed, 67 ensureUserRegistrationAllowed,
67 usersRegisterValidator, 68 usersRegisterValidator,
68 registerUser 69 asyncMiddleware(registerUser)
69) 70)
70 71
71usersRouter.put('/me', 72usersRouter.put('/me',
72 authenticate, 73 authenticate,
73 usersUpdateMeValidator, 74 usersUpdateMeValidator,
74 updateMe 75 asyncMiddleware(updateMe)
75) 76)
76 77
77usersRouter.put('/:id', 78usersRouter.put('/:id',
78 authenticate, 79 authenticate,
79 ensureIsAdmin, 80 ensureIsAdmin,
80 usersUpdateValidator, 81 usersUpdateValidator,
81 updateUser 82 asyncMiddleware(updateUser)
82) 83)
83 84
84usersRouter.delete('/:id', 85usersRouter.delete('/:id',
85 authenticate, 86 authenticate,
86 ensureIsAdmin, 87 ensureIsAdmin,
87 usersRemoveValidator, 88 usersRemoveValidator,
88 removeUser 89 asyncMiddleware(removeUser)
89) 90)
90 91
91usersRouter.post('/token', token, success) 92usersRouter.post('/token', token, success)
@@ -99,21 +100,19 @@ export {
99 100
100// --------------------------------------------------------------------------- 101// ---------------------------------------------------------------------------
101 102
102function createUserRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) { 103async function createUserRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) {
103 const options = { 104 const options = {
104 arguments: [ req, res ], 105 arguments: [ req, res ],
105 errorMessage: 'Cannot insert the user with many retries.' 106 errorMessage: 'Cannot insert the user with many retries.'
106 } 107 }
107 108
108 retryTransactionWrapper(createUser, options) 109 await retryTransactionWrapper(createUser, options)
109 .then(() => { 110
110 // TODO : include Location of the new user -> 201 111 // TODO : include Location of the new user -> 201
111 res.type('json').status(204).end() 112 return res.type('json').status(204).end()
112 })
113 .catch(err => next(err))
114} 113}
115 114
116function createUser (req: express.Request, res: express.Response, next: express.NextFunction) { 115async function createUser (req: express.Request, res: express.Response, next: express.NextFunction) {
117 const body: UserCreate = req.body 116 const body: UserCreate = req.body
118 const user = db.User.build({ 117 const user = db.User.build({
119 username: body.username, 118 username: body.username,
@@ -124,15 +123,12 @@ function createUser (req: express.Request, res: express.Response, next: express.
124 videoQuota: body.videoQuota 123 videoQuota: body.videoQuota
125 }) 124 })
126 125
127 return createUserAuthorAndChannel(user) 126 await createUserAuthorAndChannel(user)
128 .then(() => logger.info('User %s with its channel and author created.', body.username)) 127
129 .catch((err: Error) => { 128 logger.info('User %s with its channel and author created.', body.username)
130 logger.debug('Cannot insert the user.', err)
131 throw err
132 })
133} 129}
134 130
135function registerUser (req: express.Request, res: express.Response, next: express.NextFunction) { 131async function registerUser (req: express.Request, res: express.Response, next: express.NextFunction) {
136 const body: UserCreate = req.body 132 const body: UserCreate = req.body
137 133
138 const user = db.User.build({ 134 const user = db.User.build({
@@ -144,22 +140,21 @@ function registerUser (req: express.Request, res: express.Response, next: expres
144 videoQuota: CONFIG.USER.VIDEO_QUOTA 140 videoQuota: CONFIG.USER.VIDEO_QUOTA
145 }) 141 })
146 142
147 return createUserAuthorAndChannel(user) 143 await createUserAuthorAndChannel(user)
148 .then(() => res.type('json').status(204).end()) 144 return res.type('json').status(204).end()
149 .catch(err => next(err))
150} 145}
151 146
152function getUserInformation (req: express.Request, res: express.Response, next: express.NextFunction) { 147async function getUserInformation (req: express.Request, res: express.Response, next: express.NextFunction) {
153 db.User.loadByUsernameAndPopulateChannels(res.locals.oauth.token.user.username) 148 const user = await db.User.loadByUsernameAndPopulateChannels(res.locals.oauth.token.user.username)
154 .then(user => res.json(user.toFormattedJSON())) 149
155 .catch(err => next(err)) 150 return res.json(user.toFormattedJSON())
156} 151}
157 152
158function getUser (req: express.Request, res: express.Response, next: express.NextFunction) { 153function getUser (req: express.Request, res: express.Response, next: express.NextFunction) {
159 return res.json(res.locals.user.toFormattedJSON()) 154 return res.json(res.locals.user.toFormattedJSON())
160} 155}
161 156
162function getUserVideoRating (req: express.Request, res: express.Response, next: express.NextFunction) { 157async function getUserVideoRating (req: express.Request, res: express.Response, next: express.NextFunction) {
163 const videoId = +req.params.videoId 158 const videoId = +req.params.videoId
164 const userId = +res.locals.oauth.token.User.id 159 const userId = +res.locals.oauth.token.User.id
165 160
@@ -175,50 +170,45 @@ function getUserVideoRating (req: express.Request, res: express.Response, next:
175 .catch(err => next(err)) 170 .catch(err => next(err))
176} 171}
177 172
178function listUsers (req: express.Request, res: express.Response, next: express.NextFunction) { 173async function listUsers (req: express.Request, res: express.Response, next: express.NextFunction) {
179 db.User.listForApi(req.query.start, req.query.count, req.query.sort) 174 const resultList = await db.User.listForApi(req.query.start, req.query.count, req.query.sort)
180 .then(resultList => { 175
181 res.json(getFormattedObjects(resultList.data, resultList.total)) 176 return res.json(getFormattedObjects(resultList.data, resultList.total))
182 })
183 .catch(err => next(err))
184} 177}
185 178
186function removeUser (req: express.Request, res: express.Response, next: express.NextFunction) { 179async function removeUser (req: express.Request, res: express.Response, next: express.NextFunction) {
187 db.User.loadById(req.params.id) 180 const user = await db.User.loadById(req.params.id)
188 .then(user => user.destroy()) 181
189 .then(() => res.sendStatus(204)) 182 await user.destroy()
190 .catch(err => { 183
191 logger.error('Errors when removed the user.', err) 184 return res.sendStatus(204)
192 return next(err)
193 })
194} 185}
195 186
196function updateMe (req: express.Request, res: express.Response, next: express.NextFunction) { 187async function updateMe (req: express.Request, res: express.Response, next: express.NextFunction) {
197 const body: UserUpdateMe = req.body 188 const body: UserUpdateMe = req.body
198 189
199 // FIXME: user is not already a Sequelize instance? 190 // FIXME: user is not already a Sequelize instance?
200 db.User.loadByUsername(res.locals.oauth.token.user.username) 191 const user = res.locals.oauth.token.user
201 .then(user => {
202 if (body.password !== undefined) user.password = body.password
203 if (body.email !== undefined) user.email = body.email
204 if (body.displayNSFW !== undefined) user.displayNSFW = body.displayNSFW
205 192
206 return user.save() 193 if (body.password !== undefined) user.password = body.password
207 }) 194 if (body.email !== undefined) user.email = body.email
208 .then(() => res.sendStatus(204)) 195 if (body.displayNSFW !== undefined) user.displayNSFW = body.displayNSFW
209 .catch(err => next(err)) 196
197 await user.save()
198
199 return await res.sendStatus(204)
210} 200}
211 201
212function updateUser (req: express.Request, res: express.Response, next: express.NextFunction) { 202async function updateUser (req: express.Request, res: express.Response, next: express.NextFunction) {
213 const body: UserUpdate = req.body 203 const body: UserUpdate = req.body
214 const user: UserInstance = res.locals.user 204 const user: UserInstance = res.locals.user
215 205
216 if (body.email !== undefined) user.email = body.email 206 if (body.email !== undefined) user.email = body.email
217 if (body.videoQuota !== undefined) user.videoQuota = body.videoQuota 207 if (body.videoQuota !== undefined) user.videoQuota = body.videoQuota
218 208
219 return user.save() 209 await user.save()
220 .then(() => res.sendStatus(204)) 210
221 .catch(err => next(err)) 211 return res.sendStatus(204)
222} 212}
223 213
224function success (req: express.Request, res: express.Response, next: express.NextFunction) { 214function 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 c9313d5f5..4c7abf395 100644
--- a/server/controllers/api/videos/abuse.ts
+++ b/server/controllers/api/videos/abuse.ts
@@ -14,7 +14,8 @@ import {
14 videoAbuseReportValidator, 14 videoAbuseReportValidator,
15 videoAbusesSortValidator, 15 videoAbusesSortValidator,
16 setVideoAbusesSort, 16 setVideoAbusesSort,
17 setPagination 17 setPagination,
18 asyncMiddleware
18} from '../../../middlewares' 19} from '../../../middlewares'
19import { VideoInstance } from '../../../models' 20import { VideoInstance } from '../../../models'
20import { VideoAbuseCreate } from '../../../../shared' 21import { VideoAbuseCreate } from '../../../../shared'
@@ -28,12 +29,12 @@ abuseVideoRouter.get('/abuse',
28 videoAbusesSortValidator, 29 videoAbusesSortValidator,
29 setVideoAbusesSort, 30 setVideoAbusesSort,
30 setPagination, 31 setPagination,
31 listVideoAbuses 32 asyncMiddleware(listVideoAbuses)
32) 33)
33abuseVideoRouter.post('/:id/abuse', 34abuseVideoRouter.post('/:id/abuse',
34 authenticate, 35 authenticate,
35 videoAbuseReportValidator, 36 videoAbuseReportValidator,
36 reportVideoAbuseRetryWrapper 37 asyncMiddleware(reportVideoAbuseRetryWrapper)
37) 38)
38 39
39// --------------------------------------------------------------------------- 40// ---------------------------------------------------------------------------
@@ -44,55 +45,48 @@ export {
44 45
45// --------------------------------------------------------------------------- 46// ---------------------------------------------------------------------------
46 47
47function listVideoAbuses (req: express.Request, res: express.Response, next: express.NextFunction) { 48async function listVideoAbuses (req: express.Request, res: express.Response, next: express.NextFunction) {
48 db.VideoAbuse.listForApi(req.query.start, req.query.count, req.query.sort) 49 const resultList = await db.VideoAbuse.listForApi(req.query.start, req.query.count, req.query.sort)
49 .then(result => res.json(getFormattedObjects(result.data, result.total))) 50
50 .catch(err => next(err)) 51 return res.json(getFormattedObjects(resultList.data, resultList.total))
51} 52}
52 53
53function reportVideoAbuseRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) { 54async function reportVideoAbuseRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) {
54 const options = { 55 const options = {
55 arguments: [ req, res ], 56 arguments: [ req, res ],
56 errorMessage: 'Cannot report abuse to the video with many retries.' 57 errorMessage: 'Cannot report abuse to the video with many retries.'
57 } 58 }
58 59
59 retryTransactionWrapper(reportVideoAbuse, options) 60 await retryTransactionWrapper(reportVideoAbuse, options)
60 .then(() => res.type('json').status(204).end()) 61
61 .catch(err => next(err)) 62 return res.type('json').status(204).end()
62} 63}
63 64
64function reportVideoAbuse (req: express.Request, res: express.Response) { 65async function reportVideoAbuse (req: express.Request, res: express.Response) {
65 const videoInstance = res.locals.video as VideoInstance 66 const videoInstance = res.locals.video as VideoInstance
66 const reporterUsername = res.locals.oauth.token.User.username 67 const reporterUsername = res.locals.oauth.token.User.username
67 const body: VideoAbuseCreate = req.body 68 const body: VideoAbuseCreate = req.body
68 69
69 const abuse = { 70 const abuseToCreate = {
70 reporterUsername, 71 reporterUsername,
71 reason: body.reason, 72 reason: body.reason,
72 videoId: videoInstance.id, 73 videoId: videoInstance.id,
73 reporterPodId: null // This is our pod that reported this abuse 74 reporterPodId: null // This is our pod that reported this abuse
74 } 75 }
75 76
76 return db.sequelize.transaction(t => { 77 await db.sequelize.transaction(async t => {
77 return db.VideoAbuse.create(abuse, { transaction: t }) 78 const abuse = await db.VideoAbuse.create(abuseToCreate, { transaction: t })
78 .then(abuse => { 79 // We send the information to the destination pod
79 // We send the information to the destination pod 80 if (videoInstance.isOwned() === false) {
80 if (videoInstance.isOwned() === false) { 81 const reportData = {
81 const reportData = { 82 reporterUsername,
82 reporterUsername, 83 reportReason: abuse.reason,
83 reportReason: abuse.reason, 84 videoUUID: videoInstance.uuid
84 videoUUID: videoInstance.uuid 85 }
85 } 86
86 87 await friends.reportAbuseVideoToFriend(reportData, videoInstance, t)
87 return friends.reportAbuseVideoToFriend(reportData, videoInstance, t).then(() => videoInstance) 88 }
88 }
89
90 return videoInstance
91 })
92 })
93 .then((videoInstance: VideoInstance) => logger.info('Abuse report for video %s created.', videoInstance.name))
94 .catch(err => {
95 logger.debug('Cannot update the video.', err)
96 throw err
97 }) 89 })
90
91 logger.info('Abuse report for video %s created.', videoInstance.name)
98} 92}
diff --git a/server/controllers/api/videos/blacklist.ts b/server/controllers/api/videos/blacklist.ts
index 66311598e..5a2c3fd80 100644
--- a/server/controllers/api/videos/blacklist.ts
+++ b/server/controllers/api/videos/blacklist.ts
@@ -10,7 +10,8 @@ import {
10 paginationValidator, 10 paginationValidator,
11 blacklistSortValidator, 11 blacklistSortValidator,
12 setBlacklistSort, 12 setBlacklistSort,
13 setPagination 13 setPagination,
14 asyncMiddleware
14} from '../../../middlewares' 15} from '../../../middlewares'
15import { BlacklistedVideoInstance } from '../../../models' 16import { BlacklistedVideoInstance } from '../../../models'
16import { BlacklistedVideo } from '../../../../shared' 17import { BlacklistedVideo } from '../../../../shared'
@@ -21,7 +22,7 @@ blacklistRouter.post('/:videoId/blacklist',
21 authenticate, 22 authenticate,
22 ensureIsAdmin, 23 ensureIsAdmin,
23 videosBlacklistAddValidator, 24 videosBlacklistAddValidator,
24 addVideoToBlacklist 25 asyncMiddleware(addVideoToBlacklist)
25) 26)
26 27
27blacklistRouter.get('/blacklist', 28blacklistRouter.get('/blacklist',
@@ -31,14 +32,14 @@ blacklistRouter.get('/blacklist',
31 blacklistSortValidator, 32 blacklistSortValidator,
32 setBlacklistSort, 33 setBlacklistSort,
33 setPagination, 34 setPagination,
34 listBlacklist 35 asyncMiddleware(listBlacklist)
35) 36)
36 37
37blacklistRouter.delete('/:videoId/blacklist', 38blacklistRouter.delete('/:videoId/blacklist',
38 authenticate, 39 authenticate,
39 ensureIsAdmin, 40 ensureIsAdmin,
40 videosBlacklistRemoveValidator, 41 videosBlacklistRemoveValidator,
41 removeVideoFromBlacklistController 42 asyncMiddleware(removeVideoFromBlacklistController)
42) 43)
43 44
44// --------------------------------------------------------------------------- 45// ---------------------------------------------------------------------------
@@ -49,37 +50,34 @@ export {
49 50
50// --------------------------------------------------------------------------- 51// ---------------------------------------------------------------------------
51 52
52function addVideoToBlacklist (req: express.Request, res: express.Response, next: express.NextFunction) { 53async function addVideoToBlacklist (req: express.Request, res: express.Response, next: express.NextFunction) {
53 const videoInstance = res.locals.video 54 const videoInstance = res.locals.video
54 55
55 const toCreate = { 56 const toCreate = {
56 videoId: videoInstance.id 57 videoId: videoInstance.id
57 } 58 }
58 59
59 db.BlacklistedVideo.create(toCreate) 60 await db.BlacklistedVideo.create(toCreate)
60 .then(() => res.type('json').status(204).end()) 61 return res.type('json').status(204).end()
61 .catch(err => {
62 logger.error('Errors when blacklisting video ', err)
63 return next(err)
64 })
65} 62}
66 63
67function listBlacklist (req: express.Request, res: express.Response, next: express.NextFunction) { 64async function listBlacklist (req: express.Request, res: express.Response, next: express.NextFunction) {
68 db.BlacklistedVideo.listForApi(req.query.start, req.query.count, req.query.sort) 65 const resultList = await db.BlacklistedVideo.listForApi(req.query.start, req.query.count, req.query.sort)
69 .then(resultList => res.json(getFormattedObjects<BlacklistedVideo, BlacklistedVideoInstance>(resultList.data, resultList.total))) 66
70 .catch(err => next(err)) 67 return res.json(getFormattedObjects<BlacklistedVideo, BlacklistedVideoInstance>(resultList.data, resultList.total))
71} 68}
72 69
73function removeVideoFromBlacklistController (req: express.Request, res: express.Response, next: express.NextFunction) { 70async function removeVideoFromBlacklistController (req: express.Request, res: express.Response, next: express.NextFunction) {
74 const blacklistedVideo = res.locals.blacklistedVideo as BlacklistedVideoInstance 71 const blacklistedVideo = res.locals.blacklistedVideo as BlacklistedVideoInstance
75 72
76 blacklistedVideo.destroy() 73 try {
77 .then(() => { 74 await blacklistedVideo.destroy()
78 logger.info('Video %s removed from blacklist.', res.locals.video.uuid) 75
79 res.sendStatus(204) 76 logger.info('Video %s removed from blacklist.', res.locals.video.uuid)
80 }) 77
81 .catch(err => { 78 return res.sendStatus(204)
82 logger.error('Some error while removing video %s from blacklist.', res.locals.video.uuid, err) 79 } catch (err) {
83 next(err) 80 logger.error('Some error while removing video %s from blacklist.', res.locals.video.uuid, err)
84 }) 81 throw err
82 }
85} 83}
diff --git a/server/controllers/api/videos/channel.ts b/server/controllers/api/videos/channel.ts
index 630fc4f53..ab54eedee 100644
--- a/server/controllers/api/videos/channel.ts
+++ b/server/controllers/api/videos/channel.ts
@@ -4,7 +4,8 @@ import { database as db } from '../../../initializers'
4import { 4import {
5 logger, 5 logger,
6 getFormattedObjects, 6 getFormattedObjects,
7 retryTransactionWrapper 7 retryTransactionWrapper,
8 resetSequelizeInstance
8} from '../../../helpers' 9} from '../../../helpers'
9import { 10import {
10 authenticate, 11 authenticate,
@@ -16,7 +17,8 @@ import {
16 videoChannelsRemoveValidator, 17 videoChannelsRemoveValidator,
17 videoChannelGetValidator, 18 videoChannelGetValidator,
18 videoChannelsUpdateValidator, 19 videoChannelsUpdateValidator,
19 listVideoAuthorChannelsValidator 20 listVideoAuthorChannelsValidator,
21 asyncMiddleware
20} from '../../../middlewares' 22} from '../../../middlewares'
21import { 23import {
22 createVideoChannel, 24 createVideoChannel,
@@ -32,18 +34,18 @@ videoChannelRouter.get('/channels',
32 videoChannelsSortValidator, 34 videoChannelsSortValidator,
33 setVideoChannelsSort, 35 setVideoChannelsSort,
34 setPagination, 36 setPagination,
35 listVideoChannels 37 asyncMiddleware(listVideoChannels)
36) 38)
37 39
38videoChannelRouter.get('/authors/:authorId/channels', 40videoChannelRouter.get('/authors/:authorId/channels',
39 listVideoAuthorChannelsValidator, 41 listVideoAuthorChannelsValidator,
40 listVideoAuthorChannels 42 asyncMiddleware(listVideoAuthorChannels)
41) 43)
42 44
43videoChannelRouter.post('/channels', 45videoChannelRouter.post('/channels',
44 authenticate, 46 authenticate,
45 videoChannelsAddValidator, 47 videoChannelsAddValidator,
46 addVideoChannelRetryWrapper 48 asyncMiddleware(addVideoChannelRetryWrapper)
47) 49)
48 50
49videoChannelRouter.put('/channels/:id', 51videoChannelRouter.put('/channels/:id',
@@ -55,12 +57,12 @@ videoChannelRouter.put('/channels/:id',
55videoChannelRouter.delete('/channels/:id', 57videoChannelRouter.delete('/channels/:id',
56 authenticate, 58 authenticate,
57 videoChannelsRemoveValidator, 59 videoChannelsRemoveValidator,
58 removeVideoChannelRetryWrapper 60 asyncMiddleware(removeVideoChannelRetryWrapper)
59) 61)
60 62
61videoChannelRouter.get('/channels/:id', 63videoChannelRouter.get('/channels/:id',
62 videoChannelGetValidator, 64 videoChannelGetValidator,
63 getVideoChannel 65 asyncMiddleware(getVideoChannel)
64) 66)
65 67
66// --------------------------------------------------------------------------- 68// ---------------------------------------------------------------------------
@@ -71,126 +73,113 @@ export {
71 73
72// --------------------------------------------------------------------------- 74// ---------------------------------------------------------------------------
73 75
74function listVideoChannels (req: express.Request, res: express.Response, next: express.NextFunction) { 76async function listVideoChannels (req: express.Request, res: express.Response, next: express.NextFunction) {
75 db.VideoChannel.listForApi(req.query.start, req.query.count, req.query.sort) 77 const resultList = await db.VideoChannel.listForApi(req.query.start, req.query.count, req.query.sort)
76 .then(result => res.json(getFormattedObjects(result.data, result.total))) 78
77 .catch(err => next(err)) 79 return res.json(getFormattedObjects(resultList.data, resultList.total))
78} 80}
79 81
80function listVideoAuthorChannels (req: express.Request, res: express.Response, next: express.NextFunction) { 82async function listVideoAuthorChannels (req: express.Request, res: express.Response, next: express.NextFunction) {
81 db.VideoChannel.listByAuthor(res.locals.author.id) 83 const resultList = await db.VideoChannel.listByAuthor(res.locals.author.id)
82 .then(result => res.json(getFormattedObjects(result.data, result.total))) 84
83 .catch(err => next(err)) 85 return res.json(getFormattedObjects(resultList.data, resultList.total))
84} 86}
85 87
86// Wrapper to video channel add that retry the function if there is a database error 88// Wrapper to video channel add that retry the async function if there is a database error
87// We need this because we run the transaction in SERIALIZABLE isolation that can fail 89// We need this because we run the transaction in SERIALIZABLE isolation that can fail
88function addVideoChannelRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) { 90async function addVideoChannelRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) {
89 const options = { 91 const options = {
90 arguments: [ req, res ], 92 arguments: [ req, res ],
91 errorMessage: 'Cannot insert the video video channel with many retries.' 93 errorMessage: 'Cannot insert the video video channel with many retries.'
92 } 94 }
93 95
94 retryTransactionWrapper(addVideoChannel, options) 96 await retryTransactionWrapper(addVideoChannel, options)
95 .then(() => { 97
96 // TODO : include Location of the new video channel -> 201 98 // TODO : include Location of the new video channel -> 201
97 res.type('json').status(204).end() 99 return res.type('json').status(204).end()
98 })
99 .catch(err => next(err))
100} 100}
101 101
102function addVideoChannel (req: express.Request, res: express.Response) { 102async function addVideoChannel (req: express.Request, res: express.Response) {
103 const videoChannelInfo: VideoChannelCreate = req.body 103 const videoChannelInfo: VideoChannelCreate = req.body
104 const author: AuthorInstance = res.locals.oauth.token.User.Author 104 const author: AuthorInstance = res.locals.oauth.token.User.Author
105 let videoChannelCreated: VideoChannelInstance
105 106
106 return db.sequelize.transaction(t => { 107 await db.sequelize.transaction(async t => {
107 return createVideoChannel(videoChannelInfo, author, t) 108 videoChannelCreated = await createVideoChannel(videoChannelInfo, author, t)
108 })
109 .then(videoChannelUUID => logger.info('Video channel with uuid %s created.', videoChannelUUID))
110 .catch((err: Error) => {
111 logger.debug('Cannot insert the video channel.', err)
112 throw err
113 }) 109 })
110
111 logger.info('Video channel with uuid %s created.', videoChannelCreated.uuid)
114} 112}
115 113
116function updateVideoChannelRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) { 114async function updateVideoChannelRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) {
117 const options = { 115 const options = {
118 arguments: [ req, res ], 116 arguments: [ req, res ],
119 errorMessage: 'Cannot update the video with many retries.' 117 errorMessage: 'Cannot update the video with many retries.'
120 } 118 }
121 119
122 retryTransactionWrapper(updateVideoChannel, options) 120 await retryTransactionWrapper(updateVideoChannel, options)
123 .then(() => res.type('json').status(204).end()) 121
124 .catch(err => next(err)) 122 return res.type('json').status(204).end()
125} 123}
126 124
127function updateVideoChannel (req: express.Request, res: express.Response) { 125async function updateVideoChannel (req: express.Request, res: express.Response) {
128 const videoChannelInstance: VideoChannelInstance = res.locals.videoChannel 126 const videoChannelInstance: VideoChannelInstance = res.locals.videoChannel
129 const videoChannelFieldsSave = videoChannelInstance.toJSON() 127 const videoChannelFieldsSave = videoChannelInstance.toJSON()
130 const videoChannelInfoToUpdate: VideoChannelUpdate = req.body 128 const videoChannelInfoToUpdate: VideoChannelUpdate = req.body
131 129
132 return db.sequelize.transaction(t => { 130 try {
133 const options = { 131 await db.sequelize.transaction(async t => {
134 transaction: t 132 const sequelizeOptions = {
135 } 133 transaction: t
134 }
136 135
137 if (videoChannelInfoToUpdate.name !== undefined) videoChannelInstance.set('name', videoChannelInfoToUpdate.name) 136 if (videoChannelInfoToUpdate.name !== undefined) videoChannelInstance.set('name', videoChannelInfoToUpdate.name)
138 if (videoChannelInfoToUpdate.description !== undefined) videoChannelInstance.set('description', videoChannelInfoToUpdate.description) 137 if (videoChannelInfoToUpdate.description !== undefined) videoChannelInstance.set('description', videoChannelInfoToUpdate.description)
139 138
140 return videoChannelInstance.save(options) 139 await videoChannelInstance.save(sequelizeOptions)
141 .then(() => { 140 const json = videoChannelInstance.toUpdateRemoteJSON()
142 const json = videoChannelInstance.toUpdateRemoteJSON() 141
142 // Now we'll update the video channel's meta data to our friends
143 return updateVideoChannelToFriends(json, t)
143 144
144 // Now we'll update the video channel's meta data to our friends
145 return updateVideoChannelToFriends(json, t)
146 })
147 })
148 .then(() => {
149 logger.info('Video channel with name %s and uuid %s updated.', videoChannelInstance.name, videoChannelInstance.uuid)
150 })
151 .catch(err => {
152 logger.debug('Cannot update the video channel.', err)
153
154 // Force fields we want to update
155 // If the transaction is retried, sequelize will think the object has not changed
156 // So it will skip the SQL request, even if the last one was ROLLBACKed!
157 Object.keys(videoChannelFieldsSave).forEach(key => {
158 const value = videoChannelFieldsSave[key]
159 videoChannelInstance.set(key, value)
160 })
161
162 throw err
163 }) 145 })
146
147 logger.info('Video channel with name %s and uuid %s updated.', videoChannelInstance.name, videoChannelInstance.uuid)
148 } catch (err) {
149 logger.debug('Cannot update the video channel.', err)
150
151 // Force fields we want to update
152 // If the transaction is retried, sequelize will think the object has not changed
153 // So it will skip the SQL request, even if the last one was ROLLBACKed!
154 resetSequelizeInstance(videoChannelInstance, videoChannelFieldsSave)
155
156 throw err
157 }
164} 158}
165 159
166function removeVideoChannelRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) { 160async function removeVideoChannelRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) {
167 const options = { 161 const options = {
168 arguments: [ req, res ], 162 arguments: [ req, res ],
169 errorMessage: 'Cannot remove the video channel with many retries.' 163 errorMessage: 'Cannot remove the video channel with many retries.'
170 } 164 }
171 165
172 retryTransactionWrapper(removeVideoChannel, options) 166 await retryTransactionWrapper(removeVideoChannel, options)
173 .then(() => res.type('json').status(204).end()) 167
174 .catch(err => next(err)) 168 return res.type('json').status(204).end()
175} 169}
176 170
177function removeVideoChannel (req: express.Request, res: express.Response) { 171async function removeVideoChannel (req: express.Request, res: express.Response) {
178 const videoChannelInstance: VideoChannelInstance = res.locals.videoChannel 172 const videoChannelInstance: VideoChannelInstance = res.locals.videoChannel
179 173
180 return db.sequelize.transaction(t => { 174 await db.sequelize.transaction(async t => {
181 return videoChannelInstance.destroy({ transaction: t }) 175 await videoChannelInstance.destroy({ transaction: t })
182 })
183 .then(() => {
184 logger.info('Video channel with name %s and uuid %s deleted.', videoChannelInstance.name, videoChannelInstance.uuid)
185 })
186 .catch(err => {
187 logger.error('Errors when removed the video channel.', err)
188 throw err
189 }) 176 })
177
178 logger.info('Video channel with name %s and uuid %s deleted.', videoChannelInstance.name, videoChannelInstance.uuid)
190} 179}
191 180
192function getVideoChannel (req: express.Request, res: express.Response, next: express.NextFunction) { 181async function getVideoChannel (req: express.Request, res: express.Response, next: express.NextFunction) {
193 db.VideoChannel.loadAndPopulateAuthorAndVideos(res.locals.videoChannel.id) 182 const videoChannelWithVideos = await db.VideoChannel.loadAndPopulateAuthorAndVideos(res.locals.videoChannel.id)
194 .then(videoChannelWithVideos => res.json(videoChannelWithVideos.toFormattedJSON())) 183
195 .catch(err => next(err)) 184 return res.json(videoChannelWithVideos.toFormattedJSON())
196} 185}
diff --git a/server/controllers/api/videos/index.ts b/server/controllers/api/videos/index.ts
index ec855ee8e..7ebbf4d6e 100644
--- a/server/controllers/api/videos/index.ts
+++ b/server/controllers/api/videos/index.ts
@@ -1,5 +1,4 @@
1import * as express from 'express' 1import * as express from 'express'
2import * as Promise from 'bluebird'
3import * as multer from 'multer' 2import * as multer from 'multer'
4import { extname, join } from 'path' 3import { extname, join } from 'path'
5 4
@@ -30,7 +29,8 @@ import {
30 videosSearchValidator, 29 videosSearchValidator,
31 videosAddValidator, 30 videosAddValidator,
32 videosGetValidator, 31 videosGetValidator,
33 videosRemoveValidator 32 videosRemoveValidator,
33 asyncMiddleware
34} from '../../../middlewares' 34} from '../../../middlewares'
35import { 35import {
36 logger, 36 logger,
@@ -38,7 +38,8 @@ import {
38 generateRandomString, 38 generateRandomString,
39 getFormattedObjects, 39 getFormattedObjects,
40 renamePromise, 40 renamePromise,
41 getVideoFileHeight 41 getVideoFileHeight,
42 resetSequelizeInstance
42} from '../../../helpers' 43} from '../../../helpers'
43import { TagInstance, VideoInstance } from '../../../models' 44import { TagInstance, VideoInstance } from '../../../models'
44import { VideoCreate, VideoUpdate } from '../../../../shared' 45import { VideoCreate, VideoUpdate } from '../../../../shared'
@@ -88,18 +89,18 @@ videosRouter.get('/',
88 videosSortValidator, 89 videosSortValidator,
89 setVideosSort, 90 setVideosSort,
90 setPagination, 91 setPagination,
91 listVideos 92 asyncMiddleware(listVideos)
92) 93)
93videosRouter.put('/:id', 94videosRouter.put('/:id',
94 authenticate, 95 authenticate,
95 videosUpdateValidator, 96 videosUpdateValidator,
96 updateVideoRetryWrapper 97 asyncMiddleware(updateVideoRetryWrapper)
97) 98)
98videosRouter.post('/upload', 99videosRouter.post('/upload',
99 authenticate, 100 authenticate,
100 reqFiles, 101 reqFiles,
101 videosAddValidator, 102 videosAddValidator,
102 addVideoRetryWrapper 103 asyncMiddleware(addVideoRetryWrapper)
103) 104)
104videosRouter.get('/:id', 105videosRouter.get('/:id',
105 videosGetValidator, 106 videosGetValidator,
@@ -109,7 +110,7 @@ videosRouter.get('/:id',
109videosRouter.delete('/:id', 110videosRouter.delete('/:id',
110 authenticate, 111 authenticate,
111 videosRemoveValidator, 112 videosRemoveValidator,
112 removeVideoRetryWrapper 113 asyncMiddleware(removeVideoRetryWrapper)
113) 114)
114 115
115videosRouter.get('/search/:value', 116videosRouter.get('/search/:value',
@@ -119,7 +120,7 @@ videosRouter.get('/search/:value',
119 setVideosSort, 120 setVideosSort,
120 setPagination, 121 setPagination,
121 setVideosSearch, 122 setVideosSearch,
122 searchVideos 123 asyncMiddleware(searchVideos)
123) 124)
124 125
125// --------------------------------------------------------------------------- 126// ---------------------------------------------------------------------------
@@ -144,220 +145,157 @@ function listVideoLanguages (req: express.Request, res: express.Response) {
144 145
145// Wrapper to video add that retry the function if there is a database error 146// Wrapper to video add that retry the function if there is a database error
146// We need this because we run the transaction in SERIALIZABLE isolation that can fail 147// We need this because we run the transaction in SERIALIZABLE isolation that can fail
147function addVideoRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) { 148async function addVideoRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) {
148 const options = { 149 const options = {
149 arguments: [ req, res, req.files['videofile'][0] ], 150 arguments: [ req, res, req.files['videofile'][0] ],
150 errorMessage: 'Cannot insert the video with many retries.' 151 errorMessage: 'Cannot insert the video with many retries.'
151 } 152 }
152 153
153 retryTransactionWrapper(addVideo, options) 154 await retryTransactionWrapper(addVideo, options)
154 .then(() => { 155
155 // TODO : include Location of the new video -> 201 156 // TODO : include Location of the new video -> 201
156 res.type('json').status(204).end() 157 res.type('json').status(204).end()
157 })
158 .catch(err => next(err))
159} 158}
160 159
161function addVideo (req: express.Request, res: express.Response, videoPhysicalFile: Express.Multer.File) { 160async function addVideo (req: express.Request, res: express.Response, videoPhysicalFile: Express.Multer.File) {
162 const videoInfo: VideoCreate = req.body 161 const videoInfo: VideoCreate = req.body
163 let videoUUID = '' 162 let videoUUID = ''
164 163
165 return db.sequelize.transaction(t => { 164 await db.sequelize.transaction(async t => {
166 let p: Promise<TagInstance[]> 165 const sequelizeOptions = { transaction: t }
167 166
168 if (!videoInfo.tags) p = Promise.resolve(undefined) 167 const videoData = {
169 else p = db.Tag.findOrCreateTags(videoInfo.tags, t) 168 name: videoInfo.name,
170 169 remote: false,
171 return p 170 extname: extname(videoPhysicalFile.filename),
172 .then(tagInstances => { 171 category: videoInfo.category,
173 const videoData = { 172 licence: videoInfo.licence,
174 name: videoInfo.name, 173 language: videoInfo.language,
175 remote: false, 174 nsfw: videoInfo.nsfw,
176 extname: extname(videoPhysicalFile.filename), 175 description: videoInfo.description,
177 category: videoInfo.category, 176 duration: videoPhysicalFile['duration'], // duration was added by a previous middleware
178 licence: videoInfo.licence, 177 channelId: res.locals.videoChannel.id
179 language: videoInfo.language, 178 }
180 nsfw: videoInfo.nsfw, 179 const video = db.Video.build(videoData)
181 description: videoInfo.description,
182 duration: videoPhysicalFile['duration'], // duration was added by a previous middleware
183 channelId: res.locals.videoChannel.id
184 }
185 180
186 const video = db.Video.build(videoData) 181 const videoFilePath = join(CONFIG.STORAGE.VIDEOS_DIR, videoPhysicalFile.filename)
187 return { tagInstances, video } 182 const videoFileHeight = await getVideoFileHeight(videoFilePath)
188 })
189 .then(({ tagInstances, video }) => {
190 const videoFilePath = join(CONFIG.STORAGE.VIDEOS_DIR, videoPhysicalFile.filename)
191 return getVideoFileHeight(videoFilePath)
192 .then(height => ({ tagInstances, video, videoFileHeight: height }))
193 })
194 .then(({ tagInstances, video, videoFileHeight }) => {
195 const videoFileData = {
196 extname: extname(videoPhysicalFile.filename),
197 resolution: videoFileHeight,
198 size: videoPhysicalFile.size
199 }
200 183
201 const videoFile = db.VideoFile.build(videoFileData) 184 const videoFileData = {
202 return { tagInstances, video, videoFile } 185 extname: extname(videoPhysicalFile.filename),
203 }) 186 resolution: videoFileHeight,
204 .then(({ tagInstances, video, videoFile }) => { 187 size: videoPhysicalFile.size
205 const videoDir = CONFIG.STORAGE.VIDEOS_DIR 188 }
206 const source = join(videoDir, videoPhysicalFile.filename) 189 const videoFile = db.VideoFile.build(videoFileData)
207 const destination = join(videoDir, video.getVideoFilename(videoFile)) 190 const videoDir = CONFIG.STORAGE.VIDEOS_DIR
208 191 const source = join(videoDir, videoPhysicalFile.filename)
209 return renamePromise(source, destination) 192 const destination = join(videoDir, video.getVideoFilename(videoFile))
210 .then(() => { 193
211 // This is important in case if there is another attempt in the retry process 194 await renamePromise(source, destination)
212 videoPhysicalFile.filename = video.getVideoFilename(videoFile) 195 // This is important in case if there is another attempt in the retry process
213 return { tagInstances, video, videoFile } 196 videoPhysicalFile.filename = video.getVideoFilename(videoFile)
214 }) 197
215 }) 198 const tasks = []
216 .then(({ tagInstances, video, videoFile }) => { 199
217 const tasks = [] 200 tasks.push(
218 201 video.createTorrentAndSetInfoHash(videoFile),
219 tasks.push( 202 video.createThumbnail(videoFile),
220 video.createTorrentAndSetInfoHash(videoFile), 203 video.createPreview(videoFile)
221 video.createThumbnail(videoFile), 204 )
222 video.createPreview(videoFile) 205
223 ) 206 if (CONFIG.TRANSCODING.ENABLED === true) {
224 207 // Put uuid because we don't have id auto incremented for now
225 if (CONFIG.TRANSCODING.ENABLED === true) { 208 const dataInput = {
226 // Put uuid because we don't have id auto incremented for now 209 videoUUID: video.uuid
227 const dataInput = { 210 }
228 videoUUID: video.uuid 211
229 } 212 tasks.push(
230 213 JobScheduler.Instance.createJob(t, 'videoFileOptimizer', dataInput)
231 tasks.push( 214 )
232 JobScheduler.Instance.createJob(t, 'videoFileOptimizer', dataInput) 215 }
233 ) 216 await Promise.all(tasks)
234 }
235 217
236 return Promise.all(tasks).then(() => ({ tagInstances, video, videoFile })) 218 const videoCreated = await video.save(sequelizeOptions)
237 }) 219 // Do not forget to add video channel information to the created video
238 .then(({ tagInstances, video, videoFile }) => { 220 videoCreated.VideoChannel = res.locals.videoChannel
239 const options = { transaction: t } 221 videoUUID = videoCreated.uuid
240 222
241 return video.save(options) 223 videoFile.videoId = video.id
242 .then(videoCreated => {
243 // Do not forget to add video channel information to the created video
244 videoCreated.VideoChannel = res.locals.videoChannel
245 videoUUID = videoCreated.uuid
246 224
247 return { tagInstances, video: videoCreated, videoFile } 225 await videoFile.save(sequelizeOptions)
248 }) 226 video.VideoFiles = [videoFile]
249 })
250 .then(({ tagInstances, video, videoFile }) => {
251 const options = { transaction: t }
252 videoFile.videoId = video.id
253 227
254 return videoFile.save(options) 228 if (videoInfo.tags) {
255 .then(() => video.VideoFiles = [ videoFile ]) 229 const tagInstances = await db.Tag.findOrCreateTags(videoInfo.tags, t)
256 .then(() => ({ tagInstances, video })) 230
257 }) 231 await video.setTags(tagInstances, sequelizeOptions)
258 .then(({ tagInstances, video }) => { 232 video.Tags = tagInstances
259 if (!tagInstances) return video 233 }
260 234
261 const options = { transaction: t } 235 // Let transcoding job send the video to friends because the video file extension might change
262 return video.setTags(tagInstances, options) 236 if (CONFIG.TRANSCODING.ENABLED === true) return undefined
263 .then(() => { 237
264 video.Tags = tagInstances 238 const remoteVideo = await video.toAddRemoteJSON()
265 return video 239 // Now we'll add the video's meta data to our friends
266 }) 240 return addVideoToFriends(remoteVideo, t)
267 })
268 .then(video => {
269 // Let transcoding job send the video to friends because the video file extension might change
270 if (CONFIG.TRANSCODING.ENABLED === true) return undefined
271
272 return video.toAddRemoteJSON()
273 .then(remoteVideo => {
274 // Now we'll add the video's meta data to our friends
275 return addVideoToFriends(remoteVideo, t)
276 })
277 })
278 })
279 .then(() => logger.info('Video with name %s and uuid %s created.', videoInfo.name, videoUUID))
280 .catch((err: Error) => {
281 logger.debug('Cannot insert the video.', err)
282 throw err
283 }) 241 })
242
243 logger.info('Video with name %s and uuid %s created.', videoInfo.name, videoUUID)
284} 244}
285 245
286function updateVideoRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) { 246async function updateVideoRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) {
287 const options = { 247 const options = {
288 arguments: [ req, res ], 248 arguments: [ req, res ],
289 errorMessage: 'Cannot update the video with many retries.' 249 errorMessage: 'Cannot update the video with many retries.'
290 } 250 }
291 251
292 retryTransactionWrapper(updateVideo, options) 252 await retryTransactionWrapper(updateVideo, options)
293 .then(() => { 253
294 return res.type('json').status(204).end() 254 return res.type('json').status(204).end()
295 })
296 .catch(err => next(err))
297} 255}
298 256
299function updateVideo (req: express.Request, res: express.Response) { 257async function updateVideo (req: express.Request, res: express.Response) {
300 const videoInstance = res.locals.video 258 const videoInstance = res.locals.video
301 const videoFieldsSave = videoInstance.toJSON() 259 const videoFieldsSave = videoInstance.toJSON()
302 const videoInfoToUpdate: VideoUpdate = req.body 260 const videoInfoToUpdate: VideoUpdate = req.body
303 261
304 return db.sequelize.transaction(t => { 262 try {
305 let tagsPromise: Promise<TagInstance[]> 263 await db.sequelize.transaction(async t => {
306 if (!videoInfoToUpdate.tags) { 264 const sequelizeOptions = {
307 tagsPromise = Promise.resolve(null) 265 transaction: t
308 } else { 266 }
309 tagsPromise = db.Tag.findOrCreateTags(videoInfoToUpdate.tags, t)
310 }
311 267
312 return tagsPromise 268 if (videoInfoToUpdate.name !== undefined) videoInstance.set('name', videoInfoToUpdate.name)
313 .then(tagInstances => { 269 if (videoInfoToUpdate.category !== undefined) videoInstance.set('category', videoInfoToUpdate.category)
314 const options = { 270 if (videoInfoToUpdate.licence !== undefined) videoInstance.set('licence', videoInfoToUpdate.licence)
315 transaction: t 271 if (videoInfoToUpdate.language !== undefined) videoInstance.set('language', videoInfoToUpdate.language)
316 } 272 if (videoInfoToUpdate.nsfw !== undefined) videoInstance.set('nsfw', videoInfoToUpdate.nsfw)
273 if (videoInfoToUpdate.description !== undefined) videoInstance.set('description', videoInfoToUpdate.description)
317 274
318 if (videoInfoToUpdate.name !== undefined) videoInstance.set('name', videoInfoToUpdate.name) 275 await videoInstance.save(sequelizeOptions)
319 if (videoInfoToUpdate.category !== undefined) videoInstance.set('category', videoInfoToUpdate.category)
320 if (videoInfoToUpdate.licence !== undefined) videoInstance.set('licence', videoInfoToUpdate.licence)
321 if (videoInfoToUpdate.language !== undefined) videoInstance.set('language', videoInfoToUpdate.language)
322 if (videoInfoToUpdate.nsfw !== undefined) videoInstance.set('nsfw', videoInfoToUpdate.nsfw)
323 if (videoInfoToUpdate.description !== undefined) videoInstance.set('description', videoInfoToUpdate.description)
324 276
325 return videoInstance.save(options).then(() => tagInstances) 277 if (videoInfoToUpdate.tags) {
326 }) 278 const tagInstances = await db.Tag.findOrCreateTags(videoInfoToUpdate.tags, t)
327 .then(tagInstances => {
328 if (!tagInstances) return
329 279
330 const options = { transaction: t } 280 await videoInstance.setTags(tagInstances, sequelizeOptions)
331 return videoInstance.setTags(tagInstances, options) 281 videoInstance.Tags = tagInstances
332 .then(() => { 282 }
333 videoInstance.Tags = tagInstances
334 283
335 return 284 const json = videoInstance.toUpdateRemoteJSON()
336 })
337 })
338 .then(() => {
339 const json = videoInstance.toUpdateRemoteJSON()
340 285
341 // Now we'll update the video's meta data to our friends 286 // Now we'll update the video's meta data to our friends
342 return updateVideoToFriends(json, t) 287 return updateVideoToFriends(json, t)
343 }) 288 })
344 })
345 .then(() => {
346 logger.info('Video with name %s and uuid %s updated.', videoInstance.name, videoInstance.uuid)
347 })
348 .catch(err => {
349 logger.debug('Cannot update the video.', err)
350 289
290 logger.info('Video with name %s and uuid %s updated.', videoInstance.name, videoInstance.uuid)
291 } catch (err) {
351 // Force fields we want to update 292 // Force fields we want to update
352 // If the transaction is retried, sequelize will think the object has not changed 293 // If the transaction is retried, sequelize will think the object has not changed
353 // So it will skip the SQL request, even if the last one was ROLLBACKed! 294 // So it will skip the SQL request, even if the last one was ROLLBACKed!
354 Object.keys(videoFieldsSave).forEach(key => { 295 resetSequelizeInstance(videoInstance, videoFieldsSave)
355 const value = videoFieldsSave[key]
356 videoInstance.set(key, value)
357 })
358 296
359 throw err 297 throw err
360 }) 298 }
361} 299}
362 300
363function getVideo (req: express.Request, res: express.Response) { 301function getVideo (req: express.Request, res: express.Response) {
@@ -365,17 +303,17 @@ function getVideo (req: express.Request, res: express.Response) {
365 303
366 if (videoInstance.isOwned()) { 304 if (videoInstance.isOwned()) {
367 // The increment is done directly in the database, not using the instance value 305 // The increment is done directly in the database, not using the instance value
306 // FIXME: make a real view system
307 // For example, only add a view when a user watch a video during 30s etc
368 videoInstance.increment('views') 308 videoInstance.increment('views')
369 .then(() => { 309 .then(() => {
370 // FIXME: make a real view system
371 // For example, only add a view when a user watch a video during 30s etc
372 const qaduParams = { 310 const qaduParams = {
373 videoId: videoInstance.id, 311 videoId: videoInstance.id,
374 type: REQUEST_VIDEO_QADU_TYPES.VIEWS 312 type: REQUEST_VIDEO_QADU_TYPES.VIEWS
375 } 313 }
376 return quickAndDirtyUpdateVideoToFriends(qaduParams) 314 return quickAndDirtyUpdateVideoToFriends(qaduParams)
377 }) 315 })
378 .catch(err => logger.error('Cannot add view to video %d.', videoInstance.id, err)) 316 .catch(err => logger.error('Cannot add view to video %s.', videoInstance.uuid, err))
379 } else { 317 } else {
380 // Just send the event to our friends 318 // Just send the event to our friends
381 const eventParams = { 319 const eventParams = {
@@ -383,48 +321,48 @@ function getVideo (req: express.Request, res: express.Response) {
383 type: REQUEST_VIDEO_EVENT_TYPES.VIEWS 321 type: REQUEST_VIDEO_EVENT_TYPES.VIEWS
384 } 322 }
385 addEventToRemoteVideo(eventParams) 323 addEventToRemoteVideo(eventParams)
324 .catch(err => logger.error('Cannot add event to remote video %s.', videoInstance.uuid, err))
386 } 325 }
387 326
388 // Do not wait the view system 327 // Do not wait the view system
389 res.json(videoInstance.toFormattedDetailsJSON()) 328 return res.json(videoInstance.toFormattedDetailsJSON())
390} 329}
391 330
392function listVideos (req: express.Request, res: express.Response, next: express.NextFunction) { 331async function listVideos (req: express.Request, res: express.Response, next: express.NextFunction) {
393 db.Video.listForApi(req.query.start, req.query.count, req.query.sort) 332 const resultList = await db.Video.listForApi(req.query.start, req.query.count, req.query.sort)
394 .then(result => res.json(getFormattedObjects(result.data, result.total))) 333
395 .catch(err => next(err)) 334 return res.json(getFormattedObjects(resultList.data, resultList.total))
396} 335}
397 336
398function removeVideoRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) { 337async function removeVideoRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) {
399 const options = { 338 const options = {
400 arguments: [ req, res ], 339 arguments: [ req, res ],
401 errorMessage: 'Cannot remove the video with many retries.' 340 errorMessage: 'Cannot remove the video with many retries.'
402 } 341 }
403 342
404 retryTransactionWrapper(removeVideo, options) 343 await retryTransactionWrapper(removeVideo, options)
405 .then(() => { 344
406 return res.type('json').status(204).end() 345 return res.type('json').status(204).end()
407 })
408 .catch(err => next(err))
409} 346}
410 347
411function removeVideo (req: express.Request, res: express.Response) { 348async function removeVideo (req: express.Request, res: express.Response) {
412 const videoInstance: VideoInstance = res.locals.video 349 const videoInstance: VideoInstance = res.locals.video
413 350
414 return db.sequelize.transaction(t => { 351 await db.sequelize.transaction(async t => {
415 return videoInstance.destroy({ transaction: t }) 352 await videoInstance.destroy({ transaction: t })
416 })
417 .then(() => {
418 logger.info('Video with name %s and uuid %s deleted.', videoInstance.name, videoInstance.uuid)
419 })
420 .catch(err => {
421 logger.error('Errors when removed the video.', err)
422 throw err
423 }) 353 })
354
355 logger.info('Video with name %s and uuid %s deleted.', videoInstance.name, videoInstance.uuid)
424} 356}
425 357
426function searchVideos (req: express.Request, res: express.Response, next: express.NextFunction) { 358async function searchVideos (req: express.Request, res: express.Response, next: express.NextFunction) {
427 db.Video.searchAndPopulateAuthorAndPodAndTags(req.params.value, req.query.field, req.query.start, req.query.count, req.query.sort) 359 const resultList = await db.Video.searchAndPopulateAuthorAndPodAndTags(
428 .then(result => res.json(getFormattedObjects(result.data, result.total))) 360 req.params.value,
429 .catch(err => next(err)) 361 req.query.field,
362 req.query.start,
363 req.query.count,
364 req.query.sort
365 )
366
367 return res.json(getFormattedObjects(resultList.data, resultList.total))
430} 368}
diff --git a/server/controllers/api/videos/rate.ts b/server/controllers/api/videos/rate.ts
index 6ddc69817..354c3d8f9 100644
--- a/server/controllers/api/videos/rate.ts
+++ b/server/controllers/api/videos/rate.ts
@@ -1,5 +1,4 @@
1import * as express from 'express' 1import * as express from 'express'
2import * as Promise from 'bluebird'
3 2
4import { database as db } from '../../../initializers/database' 3import { database as db } from '../../../initializers/database'
5import { 4import {
@@ -17,7 +16,8 @@ import {
17} from '../../../lib' 16} from '../../../lib'
18import { 17import {
19 authenticate, 18 authenticate,
20 videoRateValidator 19 videoRateValidator,
20 asyncMiddleware
21} from '../../../middlewares' 21} from '../../../middlewares'
22import { UserVideoRateUpdate, VideoRateType } from '../../../../shared' 22import { UserVideoRateUpdate, VideoRateType } from '../../../../shared'
23 23
@@ -26,7 +26,7 @@ const rateVideoRouter = express.Router()
26rateVideoRouter.put('/:id/rate', 26rateVideoRouter.put('/:id/rate',
27 authenticate, 27 authenticate,
28 videoRateValidator, 28 videoRateValidator,
29 rateVideoRetryWrapper 29 asyncMiddleware(rateVideoRetryWrapper)
30) 30)
31 31
32// --------------------------------------------------------------------------- 32// ---------------------------------------------------------------------------
@@ -37,126 +37,107 @@ export {
37 37
38// --------------------------------------------------------------------------- 38// ---------------------------------------------------------------------------
39 39
40function rateVideoRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) { 40async function rateVideoRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) {
41 const options = { 41 const options = {
42 arguments: [ req, res ], 42 arguments: [ req, res ],
43 errorMessage: 'Cannot update the user video rate.' 43 errorMessage: 'Cannot update the user video rate.'
44 } 44 }
45 45
46 retryTransactionWrapper(rateVideo, options) 46 await retryTransactionWrapper(rateVideo, options)
47 .then(() => res.type('json').status(204).end()) 47
48 .catch(err => next(err)) 48 return res.type('json').status(204).end()
49} 49}
50 50
51function rateVideo (req: express.Request, res: express.Response) { 51async function rateVideo (req: express.Request, res: express.Response) {
52 const body: UserVideoRateUpdate = req.body 52 const body: UserVideoRateUpdate = req.body
53 const rateType = body.rating 53 const rateType = body.rating
54 const videoInstance = res.locals.video 54 const videoInstance = res.locals.video
55 const userInstance = res.locals.oauth.token.User 55 const userInstance = res.locals.oauth.token.User
56 56
57 return db.sequelize.transaction(t => { 57 await db.sequelize.transaction(async t => {
58 return db.UserVideoRate.load(userInstance.id, videoInstance.id, t) 58 const sequelizeOptions = { transaction: t }
59 .then(previousRate => { 59 const previousRate = await db.UserVideoRate.load(userInstance.id, videoInstance.id, t)
60 const options = { transaction: t } 60
61 61 let likesToIncrement = 0
62 let likesToIncrement = 0 62 let dislikesToIncrement = 0
63 let dislikesToIncrement = 0 63
64 64 if (rateType === VIDEO_RATE_TYPES.LIKE) likesToIncrement++
65 if (rateType === VIDEO_RATE_TYPES.LIKE) likesToIncrement++ 65 else if (rateType === VIDEO_RATE_TYPES.DISLIKE) dislikesToIncrement++
66 else if (rateType === VIDEO_RATE_TYPES.DISLIKE) dislikesToIncrement++ 66
67 67 // There was a previous rate, update it
68 let promise: Promise<any> 68 if (previousRate) {
69 69 // We will remove the previous rate, so we will need to update the video count attribute
70 // There was a previous rate, update it 70 if (previousRate.type === VIDEO_RATE_TYPES.LIKE) likesToIncrement--
71 if (previousRate) { 71 else if (previousRate.type === VIDEO_RATE_TYPES.DISLIKE) dislikesToIncrement--
72 // We will remove the previous rate, so we will need to update the video count attribute 72
73 if (previousRate.type === VIDEO_RATE_TYPES.LIKE) likesToIncrement-- 73 if (rateType === 'none') { // Destroy previous rate
74 else if (previousRate.type === VIDEO_RATE_TYPES.DISLIKE) dislikesToIncrement-- 74 await previousRate.destroy()
75 75 } else { // Update previous rate
76 if (rateType === 'none') { // Destroy previous rate 76 previousRate.type = rateType as VideoRateType
77 promise = previousRate.destroy() 77
78 } else { // Update previous rate 78 await previousRate.save()
79 previousRate.type = rateType as VideoRateType 79 }
80 80 } else if (rateType !== 'none') { // There was not a previous rate, insert a new one if there is a rate
81 promise = previousRate.save() 81 const query = {
82 } 82 userId: userInstance.id,
83 } else if (rateType !== 'none') { // There was not a previous rate, insert a new one if there is a rate 83 videoId: videoInstance.id,
84 const query = { 84 type: rateType
85 userId: userInstance.id, 85 }
86 videoId: videoInstance.id, 86
87 type: rateType 87 await db.UserVideoRate.create(query, sequelizeOptions)
88 } 88 }
89 89
90 promise = db.UserVideoRate.create(query, options) 90 const incrementQuery = {
91 } else { 91 likes: likesToIncrement,
92 promise = Promise.resolve() 92 dislikes: dislikesToIncrement
93 } 93 }
94 94
95 return promise.then(() => ({ likesToIncrement, dislikesToIncrement })) 95 // Even if we do not own the video we increment the attributes
96 }) 96 // It is useful for the user to have a feedback
97 .then(({ likesToIncrement, dislikesToIncrement }) => { 97 await videoInstance.increment(incrementQuery, sequelizeOptions)
98 const options = { transaction: t } 98
99 const incrementQuery = { 99 // Send a event to original pod
100 likes: likesToIncrement, 100 if (videoInstance.isOwned() === false) {
101 dislikes: dislikesToIncrement 101
102 } 102 const eventsParams = []
103 103
104 // Even if we do not own the video we increment the attributes 104 if (likesToIncrement !== 0) {
105 // It is usefull for the user to have a feedback 105 eventsParams.push({
106 return videoInstance.increment(incrementQuery, options).then(() => ({ likesToIncrement, dislikesToIncrement })) 106 videoId: videoInstance.id,
107 }) 107 type: REQUEST_VIDEO_EVENT_TYPES.LIKES,
108 .then(({ likesToIncrement, dislikesToIncrement }) => { 108 count: likesToIncrement
109 // No need for an event type, we own the video 109 })
110 if (videoInstance.isOwned()) return { likesToIncrement, dislikesToIncrement } 110 }
111 111
112 const eventsParams = [] 112 if (dislikesToIncrement !== 0) {
113 113 eventsParams.push({
114 if (likesToIncrement !== 0) { 114 videoId: videoInstance.id,
115 eventsParams.push({ 115 type: REQUEST_VIDEO_EVENT_TYPES.DISLIKES,
116 videoId: videoInstance.id, 116 count: dislikesToIncrement
117 type: REQUEST_VIDEO_EVENT_TYPES.LIKES, 117 })
118 count: likesToIncrement 118 }
119 }) 119
120 } 120 await addEventsToRemoteVideo(eventsParams, t)
121 121 } else { // We own the video, we need to send a quick and dirty update to friends to notify the counts changed
122 if (dislikesToIncrement !== 0) { 122 const qadusParams = []
123 eventsParams.push({ 123
124 videoId: videoInstance.id, 124 if (likesToIncrement !== 0) {
125 type: REQUEST_VIDEO_EVENT_TYPES.DISLIKES, 125 qadusParams.push({
126 count: dislikesToIncrement 126 videoId: videoInstance.id,
127 }) 127 type: REQUEST_VIDEO_QADU_TYPES.LIKES
128 } 128 })
129 129 }
130 return addEventsToRemoteVideo(eventsParams, t).then(() => ({ likesToIncrement, dislikesToIncrement })) 130
131 }) 131 if (dislikesToIncrement !== 0) {
132 .then(({ likesToIncrement, dislikesToIncrement }) => { 132 qadusParams.push({
133 // We do not own the video, there is no need to send a quick and dirty update to friends 133 videoId: videoInstance.id,
134 // Our rate was already sent by the addEvent function 134 type: REQUEST_VIDEO_QADU_TYPES.DISLIKES
135 if (videoInstance.isOwned() === false) return undefined 135 })
136 136 }
137 const qadusParams = [] 137
138 138 await quickAndDirtyUpdatesVideoToFriends(qadusParams, t)
139 if (likesToIncrement !== 0) { 139 }
140 qadusParams.push({
141 videoId: videoInstance.id,
142 type: REQUEST_VIDEO_QADU_TYPES.LIKES
143 })
144 }
145
146 if (dislikesToIncrement !== 0) {
147 qadusParams.push({
148 videoId: videoInstance.id,
149 type: REQUEST_VIDEO_QADU_TYPES.DISLIKES
150 })
151 }
152
153 return quickAndDirtyUpdatesVideoToFriends(qadusParams, t)
154 })
155 })
156 .then(() => logger.info('User video rate for video %s of user %s updated.', videoInstance.name, userInstance.username))
157 .catch(err => {
158 // This is just a debug because we will retry the insert
159 logger.debug('Cannot add the user video rate.', err)
160 throw err
161 }) 140 })
141
142 logger.info('User video rate for video %s of user %s updated.', videoInstance.name, userInstance.username)
162} 143}
diff --git a/server/controllers/client.ts b/server/controllers/client.ts
index 6a2ac4aab..1391993a7 100644
--- a/server/controllers/client.ts
+++ b/server/controllers/client.ts
@@ -1,7 +1,7 @@
1import * as express from 'express' 1import * as express from 'express'
2import { join } from 'path' 2import { join } from 'path'
3import * as validator from 'validator' 3import * as validator from 'validator'
4import * as Promise from 'bluebird' 4import * as Bluebird from 'bluebird'
5 5
6import { database as db } from '../initializers/database' 6import { database as db } from '../initializers/database'
7import { 7import {
@@ -11,6 +11,7 @@ import {
11 OPENGRAPH_AND_OEMBED_COMMENT 11 OPENGRAPH_AND_OEMBED_COMMENT
12} from '../initializers' 12} from '../initializers'
13import { root, readFileBufferPromise, escapeHTML } from '../helpers' 13import { root, readFileBufferPromise, escapeHTML } from '../helpers'
14import { asyncMiddleware } from '../middlewares'
14import { VideoInstance } from '../models' 15import { VideoInstance } from '../models'
15 16
16const clientsRouter = express.Router() 17const clientsRouter = express.Router()
@@ -21,7 +22,9 @@ const indexPath = join(distPath, 'index.html')
21 22
22// Special route that add OpenGraph and oEmbed tags 23// Special route that add OpenGraph and oEmbed tags
23// Do not use a template engine for a so little thing 24// Do not use a template engine for a so little thing
24clientsRouter.use('/videos/watch/:id', generateWatchHtmlPage) 25clientsRouter.use('/videos/watch/:id',
26 asyncMiddleware(generateWatchHtmlPage)
27)
25 28
26clientsRouter.use('/videos/embed', (req: express.Request, res: express.Response, next: express.NextFunction) => { 29clientsRouter.use('/videos/embed', (req: express.Request, res: express.Response, next: express.NextFunction) => {
27 res.sendFile(embedPath) 30 res.sendFile(embedPath)
@@ -90,9 +93,9 @@ function addOpenGraphAndOEmbedTags (htmlStringPage: string, video: VideoInstance
90 return htmlStringPage.replace(OPENGRAPH_AND_OEMBED_COMMENT, tagsString) 93 return htmlStringPage.replace(OPENGRAPH_AND_OEMBED_COMMENT, tagsString)
91} 94}
92 95
93function generateWatchHtmlPage (req: express.Request, res: express.Response, next: express.NextFunction) { 96async function generateWatchHtmlPage (req: express.Request, res: express.Response, next: express.NextFunction) {
94 const videoId = '' + req.params.id 97 const videoId = '' + req.params.id
95 let videoPromise: Promise<VideoInstance> 98 let videoPromise: Bluebird<VideoInstance>
96 99
97 // Let Angular application handle errors 100 // Let Angular application handle errors
98 if (validator.isUUID(videoId, 4)) { 101 if (validator.isUUID(videoId, 4)) {
@@ -103,21 +106,19 @@ function generateWatchHtmlPage (req: express.Request, res: express.Response, nex
103 return res.sendFile(indexPath) 106 return res.sendFile(indexPath)
104 } 107 }
105 108
106 Promise.all([ 109 let [ file, video ] = await Promise.all([
107 readFileBufferPromise(indexPath), 110 readFileBufferPromise(indexPath),
108 videoPromise 111 videoPromise
109 ]) 112 ])
110 .then(([ file, video ]) => {
111 file = file as Buffer
112 video = video as VideoInstance
113 113
114 const html = file.toString() 114 file = file as Buffer
115 video = video as VideoInstance
115 116
116 // Let Angular application handle errors 117 const html = file.toString()
117 if (!video) return res.sendFile(indexPath)
118 118
119 const htmlStringPageWithTags = addOpenGraphAndOEmbedTags(html, video) 119 // Let Angular application handle errors
120 res.set('Content-Type', 'text/html; charset=UTF-8').send(htmlStringPageWithTags) 120 if (!video) return res.sendFile(indexPath)
121 }) 121
122 .catch(err => next(err)) 122 const htmlStringPageWithTags = addOpenGraphAndOEmbedTags(html, video)
123 res.set('Content-Type', 'text/html; charset=UTF-8').send(htmlStringPageWithTags)
123} 124}
diff --git a/server/controllers/static.ts b/server/controllers/static.ts
index 8fbf9cc97..c7c952d6f 100644
--- a/server/controllers/static.ts
+++ b/server/controllers/static.ts
@@ -7,6 +7,7 @@ import {
7 STATIC_PATHS 7 STATIC_PATHS
8} from '../initializers' 8} from '../initializers'
9import { VideosPreviewCache } from '../lib' 9import { VideosPreviewCache } from '../lib'
10import { asyncMiddleware } from '../middlewares'
10 11
11const staticRouter = express.Router() 12const staticRouter = express.Router()
12 13
@@ -39,7 +40,7 @@ staticRouter.use(
39// Video previews path for express 40// Video previews path for express
40staticRouter.use( 41staticRouter.use(
41 STATIC_PATHS.PREVIEWS + ':uuid.jpg', 42 STATIC_PATHS.PREVIEWS + ':uuid.jpg',
42 getPreview 43 asyncMiddleware(getPreview)
43) 44)
44 45
45// --------------------------------------------------------------------------- 46// ---------------------------------------------------------------------------
@@ -50,11 +51,9 @@ export {
50 51
51// --------------------------------------------------------------------------- 52// ---------------------------------------------------------------------------
52 53
53function getPreview (req: express.Request, res: express.Response, next: express.NextFunction) { 54async function getPreview (req: express.Request, res: express.Response, next: express.NextFunction) {
54 VideosPreviewCache.Instance.getPreviewPath(req.params.uuid) 55 const path = await VideosPreviewCache.Instance.getPreviewPath(req.params.uuid)
55 .then(path => { 56 if (!path) return res.sendStatus(404)
56 if (!path) return res.sendStatus(404)
57 57
58 return res.sendFile(path, { maxAge: STATIC_MAX_AGE }) 58 return res.sendFile(path, { maxAge: STATIC_MAX_AGE })
59 })
60} 59}
diff --git a/server/helpers/database-utils.ts b/server/helpers/database-utils.ts
index 987e42eb0..dcc9e2577 100644
--- a/server/helpers/database-utils.ts
+++ b/server/helpers/database-utils.ts
@@ -1,6 +1,5 @@
1// TODO: import from ES6 when retry typing file will include errorFilter function 1// TODO: import from ES6 when retry typing file will include errorFilter function
2import * as retry from 'async/retry' 2import * as retry from 'async/retry'
3import * as Promise from 'bluebird'
4 3
5import { logger } from './logger' 4import { logger } from './logger'
6 5
diff --git a/server/helpers/utils.ts b/server/helpers/utils.ts
index 3317dddc3..6cabe117c 100644
--- a/server/helpers/utils.ts
+++ b/server/helpers/utils.ts
@@ -1,4 +1,5 @@
1import * as express from 'express' 1import * as express from 'express'
2import * as Sequelize from 'sequelize'
2import * as Promise from 'bluebird' 3import * as Promise from 'bluebird'
3 4
4import { pseudoRandomBytesPromise } from './core-utils' 5import { pseudoRandomBytesPromise } from './core-utils'
@@ -69,6 +70,13 @@ function computeResolutionsToTranscode (videoFileHeight: number) {
69 return resolutionsEnabled 70 return resolutionsEnabled
70} 71}
71 72
73function resetSequelizeInstance (instance: Sequelize.Instance<any>, savedFields: object) {
74 Object.keys(savedFields).forEach(key => {
75 const value = savedFields[key]
76 instance.set(key, value)
77 })
78}
79
72type SortType = { sortModel: any, sortValue: string } 80type SortType = { sortModel: any, sortValue: string }
73 81
74// --------------------------------------------------------------------------- 82// ---------------------------------------------------------------------------
@@ -79,5 +87,6 @@ export {
79 getFormattedObjects, 87 getFormattedObjects,
80 isSignupAllowed, 88 isSignupAllowed,
81 computeResolutionsToTranscode, 89 computeResolutionsToTranscode,
90 resetSequelizeInstance,
82 SortType 91 SortType
83} 92}
diff --git a/server/initializers/database.ts b/server/initializers/database.ts
index d461cb440..ea2b68f59 100644
--- a/server/initializers/database.ts
+++ b/server/initializers/database.ts
@@ -63,6 +63,7 @@ const sequelize = new Sequelize(dbname, username, password, {
63 host: CONFIG.DATABASE.HOSTNAME, 63 host: CONFIG.DATABASE.HOSTNAME,
64 port: CONFIG.DATABASE.PORT, 64 port: CONFIG.DATABASE.PORT,
65 benchmark: isTestInstance(), 65 benchmark: isTestInstance(),
66 isolationLevel: Sequelize.Transaction.ISOLATION_LEVELS.SERIALIZABLE,
66 67
67 logging: (message: string, benchmark: number) => { 68 logging: (message: string, benchmark: number) => {
68 let newMessage = message 69 let newMessage = message
diff --git a/server/lib/video-channel.ts b/server/lib/video-channel.ts
index 224179973..678ffe643 100644
--- a/server/lib/video-channel.ts
+++ b/server/lib/video-channel.ts
@@ -2,12 +2,11 @@ import * as Sequelize from 'sequelize'
2 2
3import { addVideoChannelToFriends } from './friends' 3import { addVideoChannelToFriends } from './friends'
4import { database as db } from '../initializers' 4import { database as db } from '../initializers'
5import { logger } from '../helpers'
5import { AuthorInstance } from '../models' 6import { AuthorInstance } from '../models'
6import { VideoChannelCreate } from '../../shared/models' 7import { VideoChannelCreate } from '../../shared/models'
7 8
8function createVideoChannel (videoChannelInfo: VideoChannelCreate, author: AuthorInstance, t: Sequelize.Transaction) { 9async function createVideoChannel (videoChannelInfo: VideoChannelCreate, author: AuthorInstance, t: Sequelize.Transaction) {
9 let videoChannelUUID = ''
10
11 const videoChannelData = { 10 const videoChannelData = {
12 name: videoChannelInfo.name, 11 name: videoChannelInfo.name,
13 description: videoChannelInfo.description, 12 description: videoChannelInfo.description,
@@ -18,25 +17,34 @@ function createVideoChannel (videoChannelInfo: VideoChannelCreate, author: Autho
18 const videoChannel = db.VideoChannel.build(videoChannelData) 17 const videoChannel = db.VideoChannel.build(videoChannelData)
19 const options = { transaction: t } 18 const options = { transaction: t }
20 19
21 return videoChannel.save(options) 20 const videoChannelCreated = await videoChannel.save(options)
22 .then(videoChannelCreated => { 21
23 // Do not forget to add Author information to the created video channel 22 // Do not forget to add Author information to the created video channel
24 videoChannelCreated.Author = author 23 videoChannelCreated.Author = author
25 videoChannelUUID = videoChannelCreated.uuid 24
26 25 const remoteVideoChannel = videoChannelCreated.toAddRemoteJSON()
27 return videoChannelCreated 26
28 }) 27 // Now we'll add the video channel's meta data to our friends
29 .then(videoChannel => { 28 await addVideoChannelToFriends(remoteVideoChannel, t)
30 const remoteVideoChannel = videoChannel.toAddRemoteJSON() 29
31 30 return videoChannelCreated
32 // Now we'll add the video channel's meta data to our friends 31}
33 return addVideoChannelToFriends(remoteVideoChannel, t) 32
34 }) 33async function fetchVideoChannelByHostAndUUID (podHost: string, uuid: string, t: Sequelize.Transaction) {
35 .then(() => videoChannelUUID) // Return video channel UUID 34 try {
35 const videoChannel = await db.VideoChannel.loadByHostAndUUID(podHost, uuid, t)
36 if (!videoChannel) throw new Error('Video channel not found')
37
38 return videoChannel
39 } catch (err) {
40 logger.error('Cannot load video channel from host and uuid.', { error: err.stack, podHost, uuid })
41 throw err
42 }
36} 43}
37 44
38// --------------------------------------------------------------------------- 45// ---------------------------------------------------------------------------
39 46
40export { 47export {
41 createVideoChannel 48 createVideoChannel,
49 fetchVideoChannelByHostAndUUID
42} 50}
diff --git a/server/middlewares/async.ts b/server/middlewares/async.ts
new file mode 100644
index 000000000..29ebd169d
--- /dev/null
+++ b/server/middlewares/async.ts
@@ -0,0 +1,16 @@
1import { Request, Response, NextFunction } from 'express'
2
3// Syntactic sugar to avoid try/catch in express controllers
4// Thanks: https://medium.com/@Abazhenov/using-async-await-in-express-with-node-8-b8af872c0016
5function asyncMiddleware (fn: (req: Request, res: Response, next: NextFunction) => Promise<any>) {
6 return (req: Request, res: Response, next: NextFunction) => {
7 return Promise.resolve(fn(req, res, next))
8 .catch(next)
9 }
10}
11
12// ---------------------------------------------------------------------------
13
14export {
15 asyncMiddleware
16}
diff --git a/server/middlewares/index.ts b/server/middlewares/index.ts
index d71dd2452..0e2c850e1 100644
--- a/server/middlewares/index.ts
+++ b/server/middlewares/index.ts
@@ -1,5 +1,6 @@
1export * from './validators' 1export * from './validators'
2export * from './admin' 2export * from './admin'
3export * from './async'
3export * from './oauth' 4export * from './oauth'
4export * from './pagination' 5export * from './pagination'
5export * from './pods' 6export * from './pods'