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