diff options
Diffstat (limited to 'server/controllers')
-rw-r--r-- | server/controllers/api/clients.js | 8 | ||||
-rw-r--r-- | server/controllers/api/index.js | 8 | ||||
-rw-r--r-- | server/controllers/api/pods.js | 34 | ||||
-rw-r--r-- | server/controllers/api/remote.js | 86 | ||||
-rw-r--r-- | server/controllers/api/remote/index.js | 16 | ||||
-rw-r--r-- | server/controllers/api/remote/videos.js | 328 | ||||
-rw-r--r-- | server/controllers/api/requests.js | 10 | ||||
-rw-r--r-- | server/controllers/api/users.js | 72 | ||||
-rw-r--r-- | server/controllers/api/videos.js | 382 | ||||
-rw-r--r-- | server/controllers/client.js | 11 |
10 files changed, 694 insertions, 261 deletions
diff --git a/server/controllers/api/clients.js b/server/controllers/api/clients.js index 7755f6c2b..cf83cb835 100644 --- a/server/controllers/api/clients.js +++ b/server/controllers/api/clients.js | |||
@@ -1,13 +1,11 @@ | |||
1 | 'use strict' | 1 | 'use strict' |
2 | 2 | ||
3 | const express = require('express') | 3 | const express = require('express') |
4 | const mongoose = require('mongoose') | ||
5 | 4 | ||
6 | const constants = require('../../initializers/constants') | 5 | const constants = require('../../initializers/constants') |
6 | const db = require('../../initializers/database') | ||
7 | const logger = require('../../helpers/logger') | 7 | const logger = require('../../helpers/logger') |
8 | 8 | ||
9 | const Client = mongoose.model('OAuthClient') | ||
10 | |||
11 | const router = express.Router() | 9 | const router = express.Router() |
12 | 10 | ||
13 | router.get('/local', getLocalClient) | 11 | router.get('/local', getLocalClient) |
@@ -27,12 +25,12 @@ function getLocalClient (req, res, next) { | |||
27 | return res.type('json').status(403).end() | 25 | return res.type('json').status(403).end() |
28 | } | 26 | } |
29 | 27 | ||
30 | Client.loadFirstClient(function (err, client) { | 28 | db.OAuthClient.loadFirstClient(function (err, client) { |
31 | if (err) return next(err) | 29 | if (err) return next(err) |
32 | if (!client) return next(new Error('No client available.')) | 30 | if (!client) return next(new Error('No client available.')) |
33 | 31 | ||
34 | res.json({ | 32 | res.json({ |
35 | client_id: client._id, | 33 | client_id: client.clientId, |
36 | client_secret: client.clientSecret | 34 | client_secret: client.clientSecret |
37 | }) | 35 | }) |
38 | }) | 36 | }) |
diff --git a/server/controllers/api/index.js b/server/controllers/api/index.js index 4cb65ed55..f13ff922c 100644 --- a/server/controllers/api/index.js +++ b/server/controllers/api/index.js | |||
@@ -2,6 +2,8 @@ | |||
2 | 2 | ||
3 | const express = require('express') | 3 | const express = require('express') |
4 | 4 | ||
5 | const utils = require('../../helpers/utils') | ||
6 | |||
5 | const router = express.Router() | 7 | const router = express.Router() |
6 | 8 | ||
7 | const clientsController = require('./clients') | 9 | const clientsController = require('./clients') |
@@ -18,7 +20,7 @@ router.use('/requests', requestsController) | |||
18 | router.use('/users', usersController) | 20 | router.use('/users', usersController) |
19 | router.use('/videos', videosController) | 21 | router.use('/videos', videosController) |
20 | router.use('/ping', pong) | 22 | router.use('/ping', pong) |
21 | router.use('/*', badRequest) | 23 | router.use('/*', utils.badRequest) |
22 | 24 | ||
23 | // --------------------------------------------------------------------------- | 25 | // --------------------------------------------------------------------------- |
24 | 26 | ||
@@ -29,7 +31,3 @@ module.exports = router | |||
29 | function pong (req, res, next) { | 31 | function pong (req, res, next) { |
30 | return res.send('pong').status(200).end() | 32 | return res.send('pong').status(200).end() |
31 | } | 33 | } |
32 | |||
33 | function badRequest (req, res, next) { | ||
34 | res.type('json').status(400).end() | ||
35 | } | ||
diff --git a/server/controllers/api/pods.js b/server/controllers/api/pods.js index 7857fcee0..38702face 100644 --- a/server/controllers/api/pods.js +++ b/server/controllers/api/pods.js | |||
@@ -1,10 +1,11 @@ | |||
1 | 'use strict' | 1 | 'use strict' |
2 | 2 | ||
3 | const express = require('express') | 3 | const express = require('express') |
4 | const mongoose = require('mongoose') | ||
5 | const waterfall = require('async/waterfall') | 4 | const waterfall = require('async/waterfall') |
6 | 5 | ||
6 | const db = require('../../initializers/database') | ||
7 | const logger = require('../../helpers/logger') | 7 | const logger = require('../../helpers/logger') |
8 | const utils = require('../../helpers/utils') | ||
8 | const friends = require('../../lib/friends') | 9 | const friends = require('../../lib/friends') |
9 | const middlewares = require('../../middlewares') | 10 | const middlewares = require('../../middlewares') |
10 | const admin = middlewares.admin | 11 | const admin = middlewares.admin |
@@ -15,7 +16,6 @@ const validators = middlewares.validators.pods | |||
15 | const signatureValidator = middlewares.validators.remote.signature | 16 | const signatureValidator = middlewares.validators.remote.signature |
16 | 17 | ||
17 | const router = express.Router() | 18 | const router = express.Router() |
18 | const Pod = mongoose.model('Pod') | ||
19 | 19 | ||
20 | router.get('/', listPods) | 20 | router.get('/', listPods) |
21 | router.post('/', | 21 | router.post('/', |
@@ -37,7 +37,7 @@ router.get('/quitfriends', | |||
37 | ) | 37 | ) |
38 | // Post because this is a secured request | 38 | // Post because this is a secured request |
39 | router.post('/remove', | 39 | router.post('/remove', |
40 | signatureValidator, | 40 | signatureValidator.signature, |
41 | checkSignature, | 41 | checkSignature, |
42 | removePods | 42 | removePods |
43 | ) | 43 | ) |
@@ -53,15 +53,15 @@ function addPods (req, res, next) { | |||
53 | 53 | ||
54 | waterfall([ | 54 | waterfall([ |
55 | function addPod (callback) { | 55 | function addPod (callback) { |
56 | const pod = new Pod(informations) | 56 | const pod = db.Pod.build(informations) |
57 | pod.save(function (err, podCreated) { | 57 | pod.save().asCallback(function (err, podCreated) { |
58 | // Be sure about the number of parameters for the callback | 58 | // Be sure about the number of parameters for the callback |
59 | return callback(err, podCreated) | 59 | return callback(err, podCreated) |
60 | }) | 60 | }) |
61 | }, | 61 | }, |
62 | 62 | ||
63 | function sendMyVideos (podCreated, callback) { | 63 | function sendMyVideos (podCreated, callback) { |
64 | friends.sendOwnedVideosToPod(podCreated._id) | 64 | friends.sendOwnedVideosToPod(podCreated.id) |
65 | 65 | ||
66 | callback(null) | 66 | callback(null) |
67 | }, | 67 | }, |
@@ -84,10 +84,10 @@ function addPods (req, res, next) { | |||
84 | } | 84 | } |
85 | 85 | ||
86 | function listPods (req, res, next) { | 86 | function listPods (req, res, next) { |
87 | Pod.list(function (err, podsList) { | 87 | db.Pod.list(function (err, podsList) { |
88 | if (err) return next(err) | 88 | if (err) return next(err) |
89 | 89 | ||
90 | res.json(getFormatedPods(podsList)) | 90 | res.json(utils.getFormatedObjects(podsList, podsList.length)) |
91 | }) | 91 | }) |
92 | } | 92 | } |
93 | 93 | ||
@@ -111,11 +111,11 @@ function removePods (req, res, next) { | |||
111 | 111 | ||
112 | waterfall([ | 112 | waterfall([ |
113 | function loadPod (callback) { | 113 | function loadPod (callback) { |
114 | Pod.loadByHost(host, callback) | 114 | db.Pod.loadByHost(host, callback) |
115 | }, | 115 | }, |
116 | 116 | ||
117 | function removePod (pod, callback) { | 117 | function deletePod (pod, callback) { |
118 | pod.remove(callback) | 118 | pod.destroy().asCallback(callback) |
119 | } | 119 | } |
120 | ], function (err) { | 120 | ], function (err) { |
121 | if (err) return next(err) | 121 | if (err) return next(err) |
@@ -131,15 +131,3 @@ function quitFriends (req, res, next) { | |||
131 | res.type('json').status(204).end() | 131 | res.type('json').status(204).end() |
132 | }) | 132 | }) |
133 | } | 133 | } |
134 | |||
135 | // --------------------------------------------------------------------------- | ||
136 | |||
137 | function getFormatedPods (pods) { | ||
138 | const formatedPods = [] | ||
139 | |||
140 | pods.forEach(function (pod) { | ||
141 | formatedPods.push(pod.toFormatedJSON()) | ||
142 | }) | ||
143 | |||
144 | return formatedPods | ||
145 | } | ||
diff --git a/server/controllers/api/remote.js b/server/controllers/api/remote.js deleted file mode 100644 index f1046c534..000000000 --- a/server/controllers/api/remote.js +++ /dev/null | |||
@@ -1,86 +0,0 @@ | |||
1 | 'use strict' | ||
2 | |||
3 | const each = require('async/each') | ||
4 | const eachSeries = require('async/eachSeries') | ||
5 | const express = require('express') | ||
6 | const mongoose = require('mongoose') | ||
7 | |||
8 | const middlewares = require('../../middlewares') | ||
9 | const secureMiddleware = middlewares.secure | ||
10 | const validators = middlewares.validators.remote | ||
11 | const logger = require('../../helpers/logger') | ||
12 | |||
13 | const router = express.Router() | ||
14 | const Video = mongoose.model('Video') | ||
15 | |||
16 | router.post('/videos', | ||
17 | validators.signature, | ||
18 | secureMiddleware.checkSignature, | ||
19 | validators.remoteVideos, | ||
20 | remoteVideos | ||
21 | ) | ||
22 | |||
23 | // --------------------------------------------------------------------------- | ||
24 | |||
25 | module.exports = router | ||
26 | |||
27 | // --------------------------------------------------------------------------- | ||
28 | |||
29 | function remoteVideos (req, res, next) { | ||
30 | const requests = req.body.data | ||
31 | const fromHost = req.body.signature.host | ||
32 | |||
33 | // We need to process in the same order to keep consistency | ||
34 | // TODO: optimization | ||
35 | eachSeries(requests, function (request, callbackEach) { | ||
36 | const videoData = request.data | ||
37 | |||
38 | if (request.type === 'add') { | ||
39 | addRemoteVideo(videoData, fromHost, callbackEach) | ||
40 | } else if (request.type === 'remove') { | ||
41 | removeRemoteVideo(videoData, fromHost, callbackEach) | ||
42 | } else { | ||
43 | logger.error('Unkown remote request type %s.', request.type) | ||
44 | } | ||
45 | }, function (err) { | ||
46 | if (err) logger.error('Error managing remote videos.', { error: err }) | ||
47 | }) | ||
48 | |||
49 | // We don't need to keep the other pod waiting | ||
50 | return res.type('json').status(204).end() | ||
51 | } | ||
52 | |||
53 | function addRemoteVideo (videoToCreateData, fromHost, callback) { | ||
54 | logger.debug('Adding remote video "%s".', videoToCreateData.name) | ||
55 | |||
56 | const video = new Video(videoToCreateData) | ||
57 | video.podHost = fromHost | ||
58 | Video.generateThumbnailFromBase64(video, videoToCreateData.thumbnailBase64, function (err) { | ||
59 | if (err) { | ||
60 | logger.error('Cannot generate thumbnail from base 64 data.', { error: err }) | ||
61 | return callback(err) | ||
62 | } | ||
63 | |||
64 | video.save(callback) | ||
65 | }) | ||
66 | } | ||
67 | |||
68 | function removeRemoteVideo (videoToRemoveData, fromHost, callback) { | ||
69 | // We need the list because we have to remove some other stuffs (thumbnail etc) | ||
70 | Video.listByHostAndRemoteId(fromHost, videoToRemoveData.remoteId, function (err, videosList) { | ||
71 | if (err) { | ||
72 | logger.error('Cannot list videos from host and magnets.', { error: err }) | ||
73 | return callback(err) | ||
74 | } | ||
75 | |||
76 | if (videosList.length === 0) { | ||
77 | logger.error('No remote video was found for this pod.', { magnetUri: videoToRemoveData.magnetUri, podHost: fromHost }) | ||
78 | } | ||
79 | |||
80 | each(videosList, function (video, callbackEach) { | ||
81 | logger.debug('Removing remote video %s.', video.magnetUri) | ||
82 | |||
83 | video.remove(callbackEach) | ||
84 | }, callback) | ||
85 | }) | ||
86 | } | ||
diff --git a/server/controllers/api/remote/index.js b/server/controllers/api/remote/index.js new file mode 100644 index 000000000..2947632d5 --- /dev/null +++ b/server/controllers/api/remote/index.js | |||
@@ -0,0 +1,16 @@ | |||
1 | 'use strict' | ||
2 | |||
3 | const express = require('express') | ||
4 | |||
5 | const utils = require('../../../helpers/utils') | ||
6 | |||
7 | const router = express.Router() | ||
8 | |||
9 | const videosRemoteController = require('./videos') | ||
10 | |||
11 | router.use('/videos', videosRemoteController) | ||
12 | router.use('/*', utils.badRequest) | ||
13 | |||
14 | // --------------------------------------------------------------------------- | ||
15 | |||
16 | module.exports = router | ||
diff --git a/server/controllers/api/remote/videos.js b/server/controllers/api/remote/videos.js new file mode 100644 index 000000000..c45a86dbb --- /dev/null +++ b/server/controllers/api/remote/videos.js | |||
@@ -0,0 +1,328 @@ | |||
1 | 'use strict' | ||
2 | |||
3 | const eachSeries = require('async/eachSeries') | ||
4 | const express = require('express') | ||
5 | const waterfall = require('async/waterfall') | ||
6 | |||
7 | const db = require('../../../initializers/database') | ||
8 | const middlewares = require('../../../middlewares') | ||
9 | const secureMiddleware = middlewares.secure | ||
10 | const videosValidators = middlewares.validators.remote.videos | ||
11 | const signatureValidators = middlewares.validators.remote.signature | ||
12 | const logger = require('../../../helpers/logger') | ||
13 | const utils = require('../../../helpers/utils') | ||
14 | |||
15 | const router = express.Router() | ||
16 | |||
17 | router.post('/', | ||
18 | signatureValidators.signature, | ||
19 | secureMiddleware.checkSignature, | ||
20 | videosValidators.remoteVideos, | ||
21 | remoteVideos | ||
22 | ) | ||
23 | |||
24 | // --------------------------------------------------------------------------- | ||
25 | |||
26 | module.exports = router | ||
27 | |||
28 | // --------------------------------------------------------------------------- | ||
29 | |||
30 | function remoteVideos (req, res, next) { | ||
31 | const requests = req.body.data | ||
32 | const fromPod = res.locals.secure.pod | ||
33 | |||
34 | // We need to process in the same order to keep consistency | ||
35 | // TODO: optimization | ||
36 | eachSeries(requests, function (request, callbackEach) { | ||
37 | const data = request.data | ||
38 | |||
39 | switch (request.type) { | ||
40 | case 'add': | ||
41 | addRemoteVideoRetryWrapper(data, fromPod, callbackEach) | ||
42 | break | ||
43 | |||
44 | case 'update': | ||
45 | updateRemoteVideoRetryWrapper(data, fromPod, callbackEach) | ||
46 | break | ||
47 | |||
48 | case 'remove': | ||
49 | removeRemoteVideo(data, fromPod, callbackEach) | ||
50 | break | ||
51 | |||
52 | case 'report-abuse': | ||
53 | reportAbuseRemoteVideo(data, fromPod, callbackEach) | ||
54 | break | ||
55 | |||
56 | default: | ||
57 | logger.error('Unkown remote request type %s.', request.type) | ||
58 | } | ||
59 | }, function (err) { | ||
60 | if (err) logger.error('Error managing remote videos.', { error: err }) | ||
61 | }) | ||
62 | |||
63 | // We don't need to keep the other pod waiting | ||
64 | return res.type('json').status(204).end() | ||
65 | } | ||
66 | |||
67 | // Handle retries on fail | ||
68 | function addRemoteVideoRetryWrapper (videoToCreateData, fromPod, finalCallback) { | ||
69 | utils.transactionRetryer( | ||
70 | function (callback) { | ||
71 | return addRemoteVideo(videoToCreateData, fromPod, callback) | ||
72 | }, | ||
73 | function (err) { | ||
74 | if (err) { | ||
75 | logger.error('Cannot insert the remote video with many retries.', { error: err }) | ||
76 | } | ||
77 | |||
78 | // Do not return the error, continue the process | ||
79 | return finalCallback(null) | ||
80 | } | ||
81 | ) | ||
82 | } | ||
83 | |||
84 | function addRemoteVideo (videoToCreateData, fromPod, finalCallback) { | ||
85 | logger.debug('Adding remote video "%s".', videoToCreateData.remoteId) | ||
86 | |||
87 | waterfall([ | ||
88 | |||
89 | function startTransaction (callback) { | ||
90 | db.sequelize.transaction({ isolationLevel: 'SERIALIZABLE' }).asCallback(function (err, t) { | ||
91 | return callback(err, t) | ||
92 | }) | ||
93 | }, | ||
94 | |||
95 | function findOrCreateAuthor (t, callback) { | ||
96 | const name = videoToCreateData.author | ||
97 | const podId = fromPod.id | ||
98 | // This author is from another pod so we do not associate a user | ||
99 | const userId = null | ||
100 | |||
101 | db.Author.findOrCreateAuthor(name, podId, userId, t, function (err, authorInstance) { | ||
102 | return callback(err, t, authorInstance) | ||
103 | }) | ||
104 | }, | ||
105 | |||
106 | function findOrCreateTags (t, author, callback) { | ||
107 | const tags = videoToCreateData.tags | ||
108 | |||
109 | db.Tag.findOrCreateTags(tags, t, function (err, tagInstances) { | ||
110 | return callback(err, t, author, tagInstances) | ||
111 | }) | ||
112 | }, | ||
113 | |||
114 | function createVideoObject (t, author, tagInstances, callback) { | ||
115 | const videoData = { | ||
116 | name: videoToCreateData.name, | ||
117 | remoteId: videoToCreateData.remoteId, | ||
118 | extname: videoToCreateData.extname, | ||
119 | infoHash: videoToCreateData.infoHash, | ||
120 | description: videoToCreateData.description, | ||
121 | authorId: author.id, | ||
122 | duration: videoToCreateData.duration, | ||
123 | createdAt: videoToCreateData.createdAt, | ||
124 | // FIXME: updatedAt does not seems to be considered by Sequelize | ||
125 | updatedAt: videoToCreateData.updatedAt | ||
126 | } | ||
127 | |||
128 | const video = db.Video.build(videoData) | ||
129 | |||
130 | return callback(null, t, tagInstances, video) | ||
131 | }, | ||
132 | |||
133 | function generateThumbnail (t, tagInstances, video, callback) { | ||
134 | db.Video.generateThumbnailFromData(video, videoToCreateData.thumbnailData, function (err) { | ||
135 | if (err) { | ||
136 | logger.error('Cannot generate thumbnail from data.', { error: err }) | ||
137 | return callback(err) | ||
138 | } | ||
139 | |||
140 | return callback(err, t, tagInstances, video) | ||
141 | }) | ||
142 | }, | ||
143 | |||
144 | function insertVideoIntoDB (t, tagInstances, video, callback) { | ||
145 | const options = { | ||
146 | transaction: t | ||
147 | } | ||
148 | |||
149 | video.save(options).asCallback(function (err, videoCreated) { | ||
150 | return callback(err, t, tagInstances, videoCreated) | ||
151 | }) | ||
152 | }, | ||
153 | |||
154 | function associateTagsToVideo (t, tagInstances, video, callback) { | ||
155 | const options = { transaction: t } | ||
156 | |||
157 | video.setTags(tagInstances, options).asCallback(function (err) { | ||
158 | return callback(err, t) | ||
159 | }) | ||
160 | } | ||
161 | |||
162 | ], function (err, t) { | ||
163 | if (err) { | ||
164 | // This is just a debug because we will retry the insert | ||
165 | logger.debug('Cannot insert the remote video.', { error: err }) | ||
166 | |||
167 | // Abort transaction? | ||
168 | if (t) t.rollback() | ||
169 | |||
170 | return finalCallback(err) | ||
171 | } | ||
172 | |||
173 | // Commit transaction | ||
174 | t.commit().asCallback(function (err) { | ||
175 | if (err) return finalCallback(err) | ||
176 | |||
177 | logger.info('Remote video %s inserted.', videoToCreateData.name) | ||
178 | return finalCallback(null) | ||
179 | }) | ||
180 | }) | ||
181 | } | ||
182 | |||
183 | // Handle retries on fail | ||
184 | function updateRemoteVideoRetryWrapper (videoAttributesToUpdate, fromPod, finalCallback) { | ||
185 | utils.transactionRetryer( | ||
186 | function (callback) { | ||
187 | return updateRemoteVideo(videoAttributesToUpdate, fromPod, callback) | ||
188 | }, | ||
189 | function (err) { | ||
190 | if (err) { | ||
191 | logger.error('Cannot update the remote video with many retries.', { error: err }) | ||
192 | } | ||
193 | |||
194 | // Do not return the error, continue the process | ||
195 | return finalCallback(null) | ||
196 | } | ||
197 | ) | ||
198 | } | ||
199 | |||
200 | function updateRemoteVideo (videoAttributesToUpdate, fromPod, finalCallback) { | ||
201 | logger.debug('Updating remote video "%s".', videoAttributesToUpdate.remoteId) | ||
202 | |||
203 | waterfall([ | ||
204 | |||
205 | function startTransaction (callback) { | ||
206 | db.sequelize.transaction({ isolationLevel: 'SERIALIZABLE' }).asCallback(function (err, t) { | ||
207 | return callback(err, t) | ||
208 | }) | ||
209 | }, | ||
210 | |||
211 | function findVideo (t, callback) { | ||
212 | fetchVideo(fromPod.host, videoAttributesToUpdate.remoteId, function (err, videoInstance) { | ||
213 | return callback(err, t, videoInstance) | ||
214 | }) | ||
215 | }, | ||
216 | |||
217 | function findOrCreateTags (t, videoInstance, callback) { | ||
218 | const tags = videoAttributesToUpdate.tags | ||
219 | |||
220 | db.Tag.findOrCreateTags(tags, t, function (err, tagInstances) { | ||
221 | return callback(err, t, videoInstance, tagInstances) | ||
222 | }) | ||
223 | }, | ||
224 | |||
225 | function updateVideoIntoDB (t, videoInstance, tagInstances, callback) { | ||
226 | const options = { transaction: t } | ||
227 | |||
228 | videoInstance.set('name', videoAttributesToUpdate.name) | ||
229 | videoInstance.set('description', videoAttributesToUpdate.description) | ||
230 | videoInstance.set('infoHash', videoAttributesToUpdate.infoHash) | ||
231 | videoInstance.set('duration', videoAttributesToUpdate.duration) | ||
232 | videoInstance.set('createdAt', videoAttributesToUpdate.createdAt) | ||
233 | videoInstance.set('updatedAt', videoAttributesToUpdate.updatedAt) | ||
234 | videoInstance.set('extname', videoAttributesToUpdate.extname) | ||
235 | |||
236 | videoInstance.save(options).asCallback(function (err) { | ||
237 | return callback(err, t, videoInstance, tagInstances) | ||
238 | }) | ||
239 | }, | ||
240 | |||
241 | function associateTagsToVideo (t, videoInstance, tagInstances, callback) { | ||
242 | const options = { transaction: t } | ||
243 | |||
244 | videoInstance.setTags(tagInstances, options).asCallback(function (err) { | ||
245 | return callback(err, t) | ||
246 | }) | ||
247 | } | ||
248 | |||
249 | ], function (err, t) { | ||
250 | if (err) { | ||
251 | // This is just a debug because we will retry the insert | ||
252 | logger.debug('Cannot update the remote video.', { error: err }) | ||
253 | |||
254 | // Abort transaction? | ||
255 | if (t) t.rollback() | ||
256 | |||
257 | return finalCallback(err) | ||
258 | } | ||
259 | |||
260 | // Commit transaction | ||
261 | t.commit().asCallback(function (err) { | ||
262 | if (err) return finalCallback(err) | ||
263 | |||
264 | logger.info('Remote video %s updated', videoAttributesToUpdate.name) | ||
265 | return finalCallback(null) | ||
266 | }) | ||
267 | }) | ||
268 | } | ||
269 | |||
270 | function removeRemoteVideo (videoToRemoveData, fromPod, callback) { | ||
271 | // We need the instance because we have to remove some other stuffs (thumbnail etc) | ||
272 | fetchVideo(fromPod.host, videoToRemoveData.remoteId, function (err, video) { | ||
273 | // Do not return the error, continue the process | ||
274 | if (err) return callback(null) | ||
275 | |||
276 | logger.debug('Removing remote video %s.', video.remoteId) | ||
277 | video.destroy().asCallback(function (err) { | ||
278 | // Do not return the error, continue the process | ||
279 | if (err) { | ||
280 | logger.error('Cannot remove remote video with id %s.', videoToRemoveData.remoteId, { error: err }) | ||
281 | } | ||
282 | |||
283 | return callback(null) | ||
284 | }) | ||
285 | }) | ||
286 | } | ||
287 | |||
288 | function reportAbuseRemoteVideo (reportData, fromPod, callback) { | ||
289 | db.Video.load(reportData.videoRemoteId, function (err, video) { | ||
290 | if (err || !video) { | ||
291 | if (!err) err = new Error('video not found') | ||
292 | |||
293 | logger.error('Cannot load video from id.', { error: err, id: reportData.videoRemoteId }) | ||
294 | // Do not return the error, continue the process | ||
295 | return callback(null) | ||
296 | } | ||
297 | |||
298 | logger.debug('Reporting remote abuse for video %s.', video.id) | ||
299 | |||
300 | const videoAbuseData = { | ||
301 | reporterUsername: reportData.reporterUsername, | ||
302 | reason: reportData.reportReason, | ||
303 | reporterPodId: fromPod.id, | ||
304 | videoId: video.id | ||
305 | } | ||
306 | |||
307 | db.VideoAbuse.create(videoAbuseData).asCallback(function (err) { | ||
308 | if (err) { | ||
309 | logger.error('Cannot create remote abuse video.', { error: err }) | ||
310 | } | ||
311 | |||
312 | return callback(null) | ||
313 | }) | ||
314 | }) | ||
315 | } | ||
316 | |||
317 | function fetchVideo (podHost, remoteId, callback) { | ||
318 | db.Video.loadByHostAndRemoteId(podHost, remoteId, function (err, video) { | ||
319 | if (err || !video) { | ||
320 | if (!err) err = new Error('video not found') | ||
321 | |||
322 | logger.error('Cannot load video from host and remote id.', { error: err, podHost, remoteId }) | ||
323 | return callback(err) | ||
324 | } | ||
325 | |||
326 | return callback(null, video) | ||
327 | }) | ||
328 | } | ||
diff --git a/server/controllers/api/requests.js b/server/controllers/api/requests.js index 52aad6997..1f9193fc8 100644 --- a/server/controllers/api/requests.js +++ b/server/controllers/api/requests.js | |||
@@ -1,15 +1,13 @@ | |||
1 | 'use strict' | 1 | 'use strict' |
2 | 2 | ||
3 | const express = require('express') | 3 | const express = require('express') |
4 | const mongoose = require('mongoose') | ||
5 | 4 | ||
6 | const constants = require('../../initializers/constants') | 5 | const constants = require('../../initializers/constants') |
6 | const db = require('../../initializers/database') | ||
7 | const middlewares = require('../../middlewares') | 7 | const middlewares = require('../../middlewares') |
8 | const admin = middlewares.admin | 8 | const admin = middlewares.admin |
9 | const oAuth = middlewares.oauth | 9 | const oAuth = middlewares.oauth |
10 | 10 | ||
11 | const Request = mongoose.model('Request') | ||
12 | |||
13 | const router = express.Router() | 11 | const router = express.Router() |
14 | 12 | ||
15 | router.get('/stats', | 13 | router.get('/stats', |
@@ -25,13 +23,13 @@ module.exports = router | |||
25 | // --------------------------------------------------------------------------- | 23 | // --------------------------------------------------------------------------- |
26 | 24 | ||
27 | function getStatsRequests (req, res, next) { | 25 | function getStatsRequests (req, res, next) { |
28 | Request.list(function (err, requests) { | 26 | db.Request.countTotalRequests(function (err, totalRequests) { |
29 | if (err) return next(err) | 27 | if (err) return next(err) |
30 | 28 | ||
31 | return res.json({ | 29 | return res.json({ |
32 | requests: requests, | 30 | totalRequests: totalRequests, |
33 | maxRequestsInParallel: constants.REQUESTS_IN_PARALLEL, | 31 | maxRequestsInParallel: constants.REQUESTS_IN_PARALLEL, |
34 | remainingMilliSeconds: Request.remainingMilliSeconds(), | 32 | remainingMilliSeconds: db.Request.remainingMilliSeconds(), |
35 | milliSecondsInterval: constants.REQUESTS_INTERVAL | 33 | milliSecondsInterval: constants.REQUESTS_INTERVAL |
36 | }) | 34 | }) |
37 | }) | 35 | }) |
diff --git a/server/controllers/api/users.js b/server/controllers/api/users.js index b4d687312..6cd0e84f7 100644 --- a/server/controllers/api/users.js +++ b/server/controllers/api/users.js | |||
@@ -1,13 +1,12 @@ | |||
1 | 'use strict' | 1 | 'use strict' |
2 | 2 | ||
3 | const each = require('async/each') | ||
4 | const express = require('express') | 3 | const express = require('express') |
5 | const mongoose = require('mongoose') | ||
6 | const waterfall = require('async/waterfall') | 4 | const waterfall = require('async/waterfall') |
7 | 5 | ||
8 | const constants = require('../../initializers/constants') | 6 | const constants = require('../../initializers/constants') |
9 | const friends = require('../../lib/friends') | 7 | const db = require('../../initializers/database') |
10 | const logger = require('../../helpers/logger') | 8 | const logger = require('../../helpers/logger') |
9 | const utils = require('../../helpers/utils') | ||
11 | const middlewares = require('../../middlewares') | 10 | const middlewares = require('../../middlewares') |
12 | const admin = middlewares.admin | 11 | const admin = middlewares.admin |
13 | const oAuth = middlewares.oauth | 12 | const oAuth = middlewares.oauth |
@@ -17,9 +16,6 @@ const validatorsPagination = middlewares.validators.pagination | |||
17 | const validatorsSort = middlewares.validators.sort | 16 | const validatorsSort = middlewares.validators.sort |
18 | const validatorsUsers = middlewares.validators.users | 17 | const validatorsUsers = middlewares.validators.users |
19 | 18 | ||
20 | const User = mongoose.model('User') | ||
21 | const Video = mongoose.model('Video') | ||
22 | |||
23 | const router = express.Router() | 19 | const router = express.Router() |
24 | 20 | ||
25 | router.get('/me', oAuth.authenticate, getUserInformation) | 21 | router.get('/me', oAuth.authenticate, getUserInformation) |
@@ -62,13 +58,13 @@ module.exports = router | |||
62 | // --------------------------------------------------------------------------- | 58 | // --------------------------------------------------------------------------- |
63 | 59 | ||
64 | function createUser (req, res, next) { | 60 | function createUser (req, res, next) { |
65 | const user = new User({ | 61 | const user = db.User.build({ |
66 | username: req.body.username, | 62 | username: req.body.username, |
67 | password: req.body.password, | 63 | password: req.body.password, |
68 | role: constants.USER_ROLES.USER | 64 | role: constants.USER_ROLES.USER |
69 | }) | 65 | }) |
70 | 66 | ||
71 | user.save(function (err, createdUser) { | 67 | user.save().asCallback(function (err, createdUser) { |
72 | if (err) return next(err) | 68 | if (err) return next(err) |
73 | 69 | ||
74 | return res.type('json').status(204).end() | 70 | return res.type('json').status(204).end() |
@@ -76,7 +72,7 @@ function createUser (req, res, next) { | |||
76 | } | 72 | } |
77 | 73 | ||
78 | function getUserInformation (req, res, next) { | 74 | function getUserInformation (req, res, next) { |
79 | User.loadByUsername(res.locals.oauth.token.user.username, function (err, user) { | 75 | db.User.loadByUsername(res.locals.oauth.token.user.username, function (err, user) { |
80 | if (err) return next(err) | 76 | if (err) return next(err) |
81 | 77 | ||
82 | return res.json(user.toFormatedJSON()) | 78 | return res.json(user.toFormatedJSON()) |
@@ -84,48 +80,21 @@ function getUserInformation (req, res, next) { | |||
84 | } | 80 | } |
85 | 81 | ||
86 | function listUsers (req, res, next) { | 82 | function listUsers (req, res, next) { |
87 | User.listForApi(req.query.start, req.query.count, req.query.sort, function (err, usersList, usersTotal) { | 83 | db.User.listForApi(req.query.start, req.query.count, req.query.sort, function (err, usersList, usersTotal) { |
88 | if (err) return next(err) | 84 | if (err) return next(err) |
89 | 85 | ||
90 | res.json(getFormatedUsers(usersList, usersTotal)) | 86 | res.json(utils.getFormatedObjects(usersList, usersTotal)) |
91 | }) | 87 | }) |
92 | } | 88 | } |
93 | 89 | ||
94 | function removeUser (req, res, next) { | 90 | function removeUser (req, res, next) { |
95 | waterfall([ | 91 | waterfall([ |
96 | function getUser (callback) { | 92 | function loadUser (callback) { |
97 | User.loadById(req.params.id, callback) | 93 | db.User.loadById(req.params.id, callback) |
98 | }, | ||
99 | |||
100 | function getVideos (user, callback) { | ||
101 | Video.listOwnedByAuthor(user.username, function (err, videos) { | ||
102 | return callback(err, user, videos) | ||
103 | }) | ||
104 | }, | ||
105 | |||
106 | function removeVideosFromDB (user, videos, callback) { | ||
107 | each(videos, function (video, callbackEach) { | ||
108 | video.remove(callbackEach) | ||
109 | }, function (err) { | ||
110 | return callback(err, user, videos) | ||
111 | }) | ||
112 | }, | ||
113 | |||
114 | function sendInformationToFriends (user, videos, callback) { | ||
115 | videos.forEach(function (video) { | ||
116 | const params = { | ||
117 | name: video.name, | ||
118 | magnetUri: video.magnetUri | ||
119 | } | ||
120 | |||
121 | friends.removeVideoToFriends(params) | ||
122 | }) | ||
123 | |||
124 | return callback(null, user) | ||
125 | }, | 94 | }, |
126 | 95 | ||
127 | function removeUserFromDB (user, callback) { | 96 | function deleteUser (user, callback) { |
128 | user.remove(callback) | 97 | user.destroy().asCallback(callback) |
129 | } | 98 | } |
130 | ], function andFinally (err) { | 99 | ], function andFinally (err) { |
131 | if (err) { | 100 | if (err) { |
@@ -138,11 +107,11 @@ function removeUser (req, res, next) { | |||
138 | } | 107 | } |
139 | 108 | ||
140 | function updateUser (req, res, next) { | 109 | function updateUser (req, res, next) { |
141 | User.loadByUsername(res.locals.oauth.token.user.username, function (err, user) { | 110 | db.User.loadByUsername(res.locals.oauth.token.user.username, function (err, user) { |
142 | if (err) return next(err) | 111 | if (err) return next(err) |
143 | 112 | ||
144 | user.password = req.body.password | 113 | user.password = req.body.password |
145 | user.save(function (err) { | 114 | user.save().asCallback(function (err) { |
146 | if (err) return next(err) | 115 | if (err) return next(err) |
147 | 116 | ||
148 | return res.sendStatus(204) | 117 | return res.sendStatus(204) |
@@ -153,18 +122,3 @@ function updateUser (req, res, next) { | |||
153 | function success (req, res, next) { | 122 | function success (req, res, next) { |
154 | res.end() | 123 | res.end() |
155 | } | 124 | } |
156 | |||
157 | // --------------------------------------------------------------------------- | ||
158 | |||
159 | function getFormatedUsers (users, usersTotal) { | ||
160 | const formatedUsers = [] | ||
161 | |||
162 | users.forEach(function (user) { | ||
163 | formatedUsers.push(user.toFormatedJSON()) | ||
164 | }) | ||
165 | |||
166 | return { | ||
167 | total: usersTotal, | ||
168 | data: formatedUsers | ||
169 | } | ||
170 | } | ||
diff --git a/server/controllers/api/videos.js b/server/controllers/api/videos.js index daf452573..2c4af520e 100644 --- a/server/controllers/api/videos.js +++ b/server/controllers/api/videos.js | |||
@@ -2,15 +2,16 @@ | |||
2 | 2 | ||
3 | const express = require('express') | 3 | const express = require('express') |
4 | const fs = require('fs') | 4 | const fs = require('fs') |
5 | const mongoose = require('mongoose') | ||
6 | const multer = require('multer') | 5 | const multer = require('multer') |
7 | const path = require('path') | 6 | const path = require('path') |
8 | const waterfall = require('async/waterfall') | 7 | const waterfall = require('async/waterfall') |
9 | 8 | ||
10 | const constants = require('../../initializers/constants') | 9 | const constants = require('../../initializers/constants') |
10 | const db = require('../../initializers/database') | ||
11 | const logger = require('../../helpers/logger') | 11 | const logger = require('../../helpers/logger') |
12 | const friends = require('../../lib/friends') | 12 | const friends = require('../../lib/friends') |
13 | const middlewares = require('../../middlewares') | 13 | const middlewares = require('../../middlewares') |
14 | const admin = middlewares.admin | ||
14 | const oAuth = middlewares.oauth | 15 | const oAuth = middlewares.oauth |
15 | const pagination = middlewares.pagination | 16 | const pagination = middlewares.pagination |
16 | const validators = middlewares.validators | 17 | const validators = middlewares.validators |
@@ -22,7 +23,6 @@ const sort = middlewares.sort | |||
22 | const utils = require('../../helpers/utils') | 23 | const utils = require('../../helpers/utils') |
23 | 24 | ||
24 | const router = express.Router() | 25 | const router = express.Router() |
25 | const Video = mongoose.model('Video') | ||
26 | 26 | ||
27 | // multer configuration | 27 | // multer configuration |
28 | const storage = multer.diskStorage({ | 28 | const storage = multer.diskStorage({ |
@@ -44,6 +44,21 @@ const storage = multer.diskStorage({ | |||
44 | 44 | ||
45 | const reqFiles = multer({ storage: storage }).fields([{ name: 'videofile', maxCount: 1 }]) | 45 | const reqFiles = multer({ storage: storage }).fields([{ name: 'videofile', maxCount: 1 }]) |
46 | 46 | ||
47 | router.get('/abuse', | ||
48 | oAuth.authenticate, | ||
49 | admin.ensureIsAdmin, | ||
50 | validatorsPagination.pagination, | ||
51 | validatorsSort.videoAbusesSort, | ||
52 | sort.setVideoAbusesSort, | ||
53 | pagination.setPagination, | ||
54 | listVideoAbuses | ||
55 | ) | ||
56 | router.post('/:id/abuse', | ||
57 | oAuth.authenticate, | ||
58 | validatorsVideos.videoAbuseReport, | ||
59 | reportVideoAbuseRetryWrapper | ||
60 | ) | ||
61 | |||
47 | router.get('/', | 62 | router.get('/', |
48 | validatorsPagination.pagination, | 63 | validatorsPagination.pagination, |
49 | validatorsSort.videosSort, | 64 | validatorsSort.videosSort, |
@@ -51,11 +66,17 @@ router.get('/', | |||
51 | pagination.setPagination, | 66 | pagination.setPagination, |
52 | listVideos | 67 | listVideos |
53 | ) | 68 | ) |
69 | router.put('/:id', | ||
70 | oAuth.authenticate, | ||
71 | reqFiles, | ||
72 | validatorsVideos.videosUpdate, | ||
73 | updateVideoRetryWrapper | ||
74 | ) | ||
54 | router.post('/', | 75 | router.post('/', |
55 | oAuth.authenticate, | 76 | oAuth.authenticate, |
56 | reqFiles, | 77 | reqFiles, |
57 | validatorsVideos.videosAdd, | 78 | validatorsVideos.videosAdd, |
58 | addVideo | 79 | addVideoRetryWrapper |
59 | ) | 80 | ) |
60 | router.get('/:id', | 81 | router.get('/:id', |
61 | validatorsVideos.videosGet, | 82 | validatorsVideos.videosGet, |
@@ -82,117 +103,264 @@ module.exports = router | |||
82 | 103 | ||
83 | // --------------------------------------------------------------------------- | 104 | // --------------------------------------------------------------------------- |
84 | 105 | ||
85 | function addVideo (req, res, next) { | 106 | // Wrapper to video add that retry the function if there is a database error |
86 | const videoFile = req.files.videofile[0] | 107 | // We need this because we run the transaction in SERIALIZABLE isolation that can fail |
108 | function addVideoRetryWrapper (req, res, next) { | ||
109 | utils.transactionRetryer( | ||
110 | function (callback) { | ||
111 | return addVideo(req, res, req.files.videofile[0], callback) | ||
112 | }, | ||
113 | function (err) { | ||
114 | if (err) { | ||
115 | logger.error('Cannot insert the video with many retries.', { error: err }) | ||
116 | return next(err) | ||
117 | } | ||
118 | |||
119 | // TODO : include Location of the new video -> 201 | ||
120 | return res.type('json').status(204).end() | ||
121 | } | ||
122 | ) | ||
123 | } | ||
124 | |||
125 | function addVideo (req, res, videoFile, callback) { | ||
87 | const videoInfos = req.body | 126 | const videoInfos = req.body |
88 | 127 | ||
89 | waterfall([ | 128 | waterfall([ |
90 | function createVideoObject (callback) { | ||
91 | const id = mongoose.Types.ObjectId() | ||
92 | 129 | ||
130 | function startTransaction (callbackWaterfall) { | ||
131 | db.sequelize.transaction({ isolationLevel: 'SERIALIZABLE' }).asCallback(function (err, t) { | ||
132 | return callbackWaterfall(err, t) | ||
133 | }) | ||
134 | }, | ||
135 | |||
136 | function findOrCreateAuthor (t, callbackWaterfall) { | ||
137 | const user = res.locals.oauth.token.User | ||
138 | |||
139 | const name = user.username | ||
140 | // null because it is OUR pod | ||
141 | const podId = null | ||
142 | const userId = user.id | ||
143 | |||
144 | db.Author.findOrCreateAuthor(name, podId, userId, t, function (err, authorInstance) { | ||
145 | return callbackWaterfall(err, t, authorInstance) | ||
146 | }) | ||
147 | }, | ||
148 | |||
149 | function findOrCreateTags (t, author, callbackWaterfall) { | ||
150 | const tags = videoInfos.tags | ||
151 | |||
152 | db.Tag.findOrCreateTags(tags, t, function (err, tagInstances) { | ||
153 | return callbackWaterfall(err, t, author, tagInstances) | ||
154 | }) | ||
155 | }, | ||
156 | |||
157 | function createVideoObject (t, author, tagInstances, callbackWaterfall) { | ||
93 | const videoData = { | 158 | const videoData = { |
94 | _id: id, | ||
95 | name: videoInfos.name, | 159 | name: videoInfos.name, |
96 | remoteId: null, | 160 | remoteId: null, |
97 | extname: path.extname(videoFile.filename), | 161 | extname: path.extname(videoFile.filename), |
98 | description: videoInfos.description, | 162 | description: videoInfos.description, |
99 | author: res.locals.oauth.token.user.username, | ||
100 | duration: videoFile.duration, | 163 | duration: videoFile.duration, |
101 | tags: videoInfos.tags | 164 | authorId: author.id |
102 | } | 165 | } |
103 | 166 | ||
104 | const video = new Video(videoData) | 167 | const video = db.Video.build(videoData) |
105 | 168 | ||
106 | return callback(null, video) | 169 | return callbackWaterfall(null, t, author, tagInstances, video) |
107 | }, | 170 | }, |
108 | 171 | ||
109 | // Set the videoname the same as the MongoDB id | 172 | // Set the videoname the same as the id |
110 | function renameVideoFile (video, callback) { | 173 | function renameVideoFile (t, author, tagInstances, video, callbackWaterfall) { |
111 | const videoDir = constants.CONFIG.STORAGE.VIDEOS_DIR | 174 | const videoDir = constants.CONFIG.STORAGE.VIDEOS_DIR |
112 | const source = path.join(videoDir, videoFile.filename) | 175 | const source = path.join(videoDir, videoFile.filename) |
113 | const destination = path.join(videoDir, video.getVideoFilename()) | 176 | const destination = path.join(videoDir, video.getVideoFilename()) |
114 | 177 | ||
115 | fs.rename(source, destination, function (err) { | 178 | fs.rename(source, destination, function (err) { |
116 | return callback(err, video) | 179 | if (err) return callbackWaterfall(err) |
180 | |||
181 | // This is important in case if there is another attempt | ||
182 | videoFile.filename = video.getVideoFilename() | ||
183 | return callbackWaterfall(null, t, author, tagInstances, video) | ||
184 | }) | ||
185 | }, | ||
186 | |||
187 | function insertVideoIntoDB (t, author, tagInstances, video, callbackWaterfall) { | ||
188 | const options = { transaction: t } | ||
189 | |||
190 | // Add tags association | ||
191 | video.save(options).asCallback(function (err, videoCreated) { | ||
192 | if (err) return callbackWaterfall(err) | ||
193 | |||
194 | // Do not forget to add Author informations to the created video | ||
195 | videoCreated.Author = author | ||
196 | |||
197 | return callbackWaterfall(err, t, tagInstances, videoCreated) | ||
117 | }) | 198 | }) |
118 | }, | 199 | }, |
119 | 200 | ||
120 | function insertIntoDB (video, callback) { | 201 | function associateTagsToVideo (t, tagInstances, video, callbackWaterfall) { |
121 | video.save(function (err, video) { | 202 | const options = { transaction: t } |
122 | // Assert there are only one argument sent to the next function (video) | 203 | |
123 | return callback(err, video) | 204 | video.setTags(tagInstances, options).asCallback(function (err) { |
205 | video.Tags = tagInstances | ||
206 | |||
207 | return callbackWaterfall(err, t, video) | ||
124 | }) | 208 | }) |
125 | }, | 209 | }, |
126 | 210 | ||
127 | function sendToFriends (video, callback) { | 211 | function sendToFriends (t, video, callbackWaterfall) { |
128 | video.toRemoteJSON(function (err, remoteVideo) { | 212 | video.toAddRemoteJSON(function (err, remoteVideo) { |
129 | if (err) return callback(err) | 213 | if (err) return callbackWaterfall(err) |
130 | 214 | ||
131 | // Now we'll add the video's meta data to our friends | 215 | // Now we'll add the video's meta data to our friends |
132 | friends.addVideoToFriends(remoteVideo) | 216 | friends.addVideoToFriends(remoteVideo, t, function (err) { |
133 | 217 | return callbackWaterfall(err, t) | |
134 | return callback(null) | 218 | }) |
135 | }) | 219 | }) |
136 | } | 220 | } |
137 | 221 | ||
138 | ], function andFinally (err) { | 222 | ], function andFinally (err, t) { |
139 | if (err) { | 223 | if (err) { |
140 | logger.error('Cannot insert the video.') | 224 | // This is just a debug because we will retry the insert |
141 | return next(err) | 225 | logger.debug('Cannot insert the video.', { error: err }) |
226 | |||
227 | // Abort transaction? | ||
228 | if (t) t.rollback() | ||
229 | |||
230 | return callback(err) | ||
142 | } | 231 | } |
143 | 232 | ||
144 | // TODO : include Location of the new video -> 201 | 233 | // Commit transaction |
145 | return res.type('json').status(204).end() | 234 | t.commit().asCallback(function (err) { |
235 | if (err) return callback(err) | ||
236 | |||
237 | logger.info('Video with name %s created.', videoInfos.name) | ||
238 | return callback(null) | ||
239 | }) | ||
146 | }) | 240 | }) |
147 | } | 241 | } |
148 | 242 | ||
149 | function getVideo (req, res, next) { | 243 | function updateVideoRetryWrapper (req, res, next) { |
150 | Video.load(req.params.id, function (err, video) { | 244 | utils.transactionRetryer( |
151 | if (err) return next(err) | 245 | function (callback) { |
246 | return updateVideo(req, res, callback) | ||
247 | }, | ||
248 | function (err) { | ||
249 | if (err) { | ||
250 | logger.error('Cannot update the video with many retries.', { error: err }) | ||
251 | return next(err) | ||
252 | } | ||
152 | 253 | ||
153 | if (!video) { | 254 | // TODO : include Location of the new video -> 201 |
154 | return res.type('json').status(204).end() | 255 | return res.type('json').status(204).end() |
155 | } | 256 | } |
156 | 257 | ) | |
157 | res.json(video.toFormatedJSON()) | ||
158 | }) | ||
159 | } | 258 | } |
160 | 259 | ||
161 | function listVideos (req, res, next) { | 260 | function updateVideo (req, res, finalCallback) { |
162 | Video.listForApi(req.query.start, req.query.count, req.query.sort, function (err, videosList, videosTotal) { | 261 | const videoInstance = res.locals.video |
163 | if (err) return next(err) | 262 | const videoFieldsSave = videoInstance.toJSON() |
263 | const videoInfosToUpdate = req.body | ||
164 | 264 | ||
165 | res.json(getFormatedVideos(videosList, videosTotal)) | 265 | waterfall([ |
166 | }) | ||
167 | } | ||
168 | 266 | ||
169 | function removeVideo (req, res, next) { | 267 | function startTransaction (callback) { |
170 | const videoId = req.params.id | 268 | db.sequelize.transaction({ isolationLevel: 'SERIALIZABLE' }).asCallback(function (err, t) { |
269 | return callback(err, t) | ||
270 | }) | ||
271 | }, | ||
171 | 272 | ||
172 | waterfall([ | 273 | function findOrCreateTags (t, callback) { |
173 | function getVideo (callback) { | 274 | if (videoInfosToUpdate.tags) { |
174 | Video.load(videoId, callback) | 275 | db.Tag.findOrCreateTags(videoInfosToUpdate.tags, t, function (err, tagInstances) { |
276 | return callback(err, t, tagInstances) | ||
277 | }) | ||
278 | } else { | ||
279 | return callback(null, t, null) | ||
280 | } | ||
175 | }, | 281 | }, |
176 | 282 | ||
177 | function removeFromDB (video, callback) { | 283 | function updateVideoIntoDB (t, tagInstances, callback) { |
178 | video.remove(function (err) { | 284 | const options = { |
179 | if (err) return callback(err) | 285 | transaction: t |
286 | } | ||
287 | |||
288 | if (videoInfosToUpdate.name) videoInstance.set('name', videoInfosToUpdate.name) | ||
289 | if (videoInfosToUpdate.description) videoInstance.set('description', videoInfosToUpdate.description) | ||
180 | 290 | ||
181 | return callback(null, video) | 291 | videoInstance.save(options).asCallback(function (err) { |
292 | return callback(err, t, tagInstances) | ||
182 | }) | 293 | }) |
183 | }, | 294 | }, |
184 | 295 | ||
185 | function sendInformationToFriends (video, callback) { | 296 | function associateTagsToVideo (t, tagInstances, callback) { |
186 | const params = { | 297 | if (tagInstances) { |
187 | name: video.name, | 298 | const options = { transaction: t } |
188 | remoteId: video._id | 299 | |
300 | videoInstance.setTags(tagInstances, options).asCallback(function (err) { | ||
301 | videoInstance.Tags = tagInstances | ||
302 | |||
303 | return callback(err, t) | ||
304 | }) | ||
305 | } else { | ||
306 | return callback(null, t) | ||
189 | } | 307 | } |
308 | }, | ||
190 | 309 | ||
191 | friends.removeVideoToFriends(params) | 310 | function sendToFriends (t, callback) { |
311 | const json = videoInstance.toUpdateRemoteJSON() | ||
192 | 312 | ||
193 | return callback(null) | 313 | // Now we'll update the video's meta data to our friends |
314 | friends.updateVideoToFriends(json, t, function (err) { | ||
315 | return callback(err, t) | ||
316 | }) | ||
317 | } | ||
318 | |||
319 | ], function andFinally (err, t) { | ||
320 | if (err) { | ||
321 | logger.debug('Cannot update the video.', { error: err }) | ||
322 | |||
323 | // Abort transaction? | ||
324 | if (t) t.rollback() | ||
325 | |||
326 | // Force fields we want to update | ||
327 | // If the transaction is retried, sequelize will think the object has not changed | ||
328 | // So it will skip the SQL request, even if the last one was ROLLBACKed! | ||
329 | Object.keys(videoFieldsSave).forEach(function (key) { | ||
330 | const value = videoFieldsSave[key] | ||
331 | videoInstance.set(key, value) | ||
332 | }) | ||
333 | |||
334 | return finalCallback(err) | ||
194 | } | 335 | } |
195 | ], function andFinally (err) { | 336 | |
337 | // Commit transaction | ||
338 | t.commit().asCallback(function (err) { | ||
339 | if (err) return finalCallback(err) | ||
340 | |||
341 | logger.info('Video with name %s updated.', videoInfosToUpdate.name) | ||
342 | return finalCallback(null) | ||
343 | }) | ||
344 | }) | ||
345 | } | ||
346 | |||
347 | function getVideo (req, res, next) { | ||
348 | const videoInstance = res.locals.video | ||
349 | res.json(videoInstance.toFormatedJSON()) | ||
350 | } | ||
351 | |||
352 | function listVideos (req, res, next) { | ||
353 | db.Video.listForApi(req.query.start, req.query.count, req.query.sort, function (err, videosList, videosTotal) { | ||
354 | if (err) return next(err) | ||
355 | |||
356 | res.json(utils.getFormatedObjects(videosList, videosTotal)) | ||
357 | }) | ||
358 | } | ||
359 | |||
360 | function removeVideo (req, res, next) { | ||
361 | const videoInstance = res.locals.video | ||
362 | |||
363 | videoInstance.destroy().asCallback(function (err) { | ||
196 | if (err) { | 364 | if (err) { |
197 | logger.error('Errors when removed the video.', { error: err }) | 365 | logger.error('Errors when removed the video.', { error: err }) |
198 | return next(err) | 366 | return next(err) |
@@ -203,25 +371,97 @@ function removeVideo (req, res, next) { | |||
203 | } | 371 | } |
204 | 372 | ||
205 | function searchVideos (req, res, next) { | 373 | function searchVideos (req, res, next) { |
206 | Video.search(req.params.value, req.query.field, req.query.start, req.query.count, req.query.sort, | 374 | db.Video.searchAndPopulateAuthorAndPodAndTags( |
207 | function (err, videosList, videosTotal) { | 375 | req.params.value, req.query.field, req.query.start, req.query.count, req.query.sort, |
376 | function (err, videosList, videosTotal) { | ||
377 | if (err) return next(err) | ||
378 | |||
379 | res.json(utils.getFormatedObjects(videosList, videosTotal)) | ||
380 | } | ||
381 | ) | ||
382 | } | ||
383 | |||
384 | function listVideoAbuses (req, res, next) { | ||
385 | db.VideoAbuse.listForApi(req.query.start, req.query.count, req.query.sort, function (err, abusesList, abusesTotal) { | ||
208 | if (err) return next(err) | 386 | if (err) return next(err) |
209 | 387 | ||
210 | res.json(getFormatedVideos(videosList, videosTotal)) | 388 | res.json(utils.getFormatedObjects(abusesList, abusesTotal)) |
211 | }) | 389 | }) |
212 | } | 390 | } |
213 | 391 | ||
214 | // --------------------------------------------------------------------------- | 392 | function reportVideoAbuseRetryWrapper (req, res, next) { |
393 | utils.transactionRetryer( | ||
394 | function (callback) { | ||
395 | return reportVideoAbuse(req, res, callback) | ||
396 | }, | ||
397 | function (err) { | ||
398 | if (err) { | ||
399 | logger.error('Cannot report abuse to the video with many retries.', { error: err }) | ||
400 | return next(err) | ||
401 | } | ||
215 | 402 | ||
216 | function getFormatedVideos (videos, videosTotal) { | 403 | return res.type('json').status(204).end() |
217 | const formatedVideos = [] | 404 | } |
405 | ) | ||
406 | } | ||
218 | 407 | ||
219 | videos.forEach(function (video) { | 408 | function reportVideoAbuse (req, res, finalCallback) { |
220 | formatedVideos.push(video.toFormatedJSON()) | 409 | const videoInstance = res.locals.video |
221 | }) | 410 | const reporterUsername = res.locals.oauth.token.User.username |
222 | 411 | ||
223 | return { | 412 | const abuse = { |
224 | total: videosTotal, | 413 | reporterUsername, |
225 | data: formatedVideos | 414 | reason: req.body.reason, |
415 | videoId: videoInstance.id, | ||
416 | reporterPodId: null // This is our pod that reported this abuse | ||
226 | } | 417 | } |
418 | |||
419 | waterfall([ | ||
420 | |||
421 | function startTransaction (callback) { | ||
422 | db.sequelize.transaction().asCallback(function (err, t) { | ||
423 | return callback(err, t) | ||
424 | }) | ||
425 | }, | ||
426 | |||
427 | function createAbuse (t, callback) { | ||
428 | db.VideoAbuse.create(abuse).asCallback(function (err, abuse) { | ||
429 | return callback(err, t, abuse) | ||
430 | }) | ||
431 | }, | ||
432 | |||
433 | function sendToFriendsIfNeeded (t, abuse, callback) { | ||
434 | // We send the information to the destination pod | ||
435 | if (videoInstance.isOwned() === false) { | ||
436 | const reportData = { | ||
437 | reporterUsername, | ||
438 | reportReason: abuse.reason, | ||
439 | videoRemoteId: videoInstance.remoteId | ||
440 | } | ||
441 | |||
442 | friends.reportAbuseVideoToFriend(reportData, videoInstance) | ||
443 | } | ||
444 | |||
445 | return callback(null, t) | ||
446 | } | ||
447 | |||
448 | ], function andFinally (err, t) { | ||
449 | if (err) { | ||
450 | logger.debug('Cannot update the video.', { error: err }) | ||
451 | |||
452 | // Abort transaction? | ||
453 | if (t) t.rollback() | ||
454 | |||
455 | return finalCallback(err) | ||
456 | } | ||
457 | |||
458 | // Commit transaction | ||
459 | t.commit().asCallback(function (err) { | ||
460 | if (err) return finalCallback(err) | ||
461 | |||
462 | logger.info('Abuse report for video %s created.', videoInstance.name) | ||
463 | return finalCallback(null) | ||
464 | }) | ||
465 | }) | ||
227 | } | 466 | } |
467 | |||
diff --git a/server/controllers/client.js b/server/controllers/client.js index 572db6133..8c242af07 100644 --- a/server/controllers/client.js +++ b/server/controllers/client.js | |||
@@ -3,13 +3,12 @@ | |||
3 | const parallel = require('async/parallel') | 3 | const parallel = require('async/parallel') |
4 | const express = require('express') | 4 | const express = require('express') |
5 | const fs = require('fs') | 5 | const fs = require('fs') |
6 | const mongoose = require('mongoose') | ||
7 | const path = require('path') | 6 | const path = require('path') |
8 | const validator = require('express-validator').validator | 7 | const validator = require('express-validator').validator |
9 | 8 | ||
10 | const constants = require('../initializers/constants') | 9 | const constants = require('../initializers/constants') |
10 | const db = require('../initializers/database') | ||
11 | 11 | ||
12 | const Video = mongoose.model('Video') | ||
13 | const router = express.Router() | 12 | const router = express.Router() |
14 | 13 | ||
15 | const opengraphComment = '<!-- opengraph tags -->' | 14 | const opengraphComment = '<!-- opengraph tags -->' |
@@ -45,14 +44,14 @@ function addOpenGraphTags (htmlStringPage, video) { | |||
45 | if (video.isOwned()) { | 44 | if (video.isOwned()) { |
46 | basePreviewUrlHttp = constants.CONFIG.WEBSERVER.URL | 45 | basePreviewUrlHttp = constants.CONFIG.WEBSERVER.URL |
47 | } else { | 46 | } else { |
48 | basePreviewUrlHttp = constants.REMOTE_SCHEME.HTTP + '://' + video.podHost | 47 | basePreviewUrlHttp = constants.REMOTE_SCHEME.HTTP + '://' + video.Author.Pod.host |
49 | } | 48 | } |
50 | 49 | ||
51 | // We fetch the remote preview (bigger than the thumbnail) | 50 | // We fetch the remote preview (bigger than the thumbnail) |
52 | // This should not overhead the remote server since social websites put in a cache the OpenGraph tags | 51 | // This should not overhead the remote server since social websites put in a cache the OpenGraph tags |
53 | // We can't use the thumbnail because these social websites want bigger images (> 200x200 for Facebook for example) | 52 | // We can't use the thumbnail because these social websites want bigger images (> 200x200 for Facebook for example) |
54 | const previewUrl = basePreviewUrlHttp + constants.STATIC_PATHS.PREVIEWS + video.getPreviewName() | 53 | const previewUrl = basePreviewUrlHttp + constants.STATIC_PATHS.PREVIEWS + video.getPreviewName() |
55 | const videoUrl = constants.CONFIG.WEBSERVER.URL + '/videos/watch/' + video._id | 54 | const videoUrl = constants.CONFIG.WEBSERVER.URL + '/videos/watch/' + video.id |
56 | 55 | ||
57 | const metaTags = { | 56 | const metaTags = { |
58 | 'og:type': 'video', | 57 | 'og:type': 'video', |
@@ -86,7 +85,7 @@ function generateWatchHtmlPage (req, res, next) { | |||
86 | const videoId = req.params.id | 85 | const videoId = req.params.id |
87 | 86 | ||
88 | // Let Angular application handle errors | 87 | // Let Angular application handle errors |
89 | if (!validator.isMongoId(videoId)) return res.sendFile(indexPath) | 88 | if (!validator.isUUID(videoId, 4)) return res.sendFile(indexPath) |
90 | 89 | ||
91 | parallel({ | 90 | parallel({ |
92 | file: function (callback) { | 91 | file: function (callback) { |
@@ -94,7 +93,7 @@ function generateWatchHtmlPage (req, res, next) { | |||
94 | }, | 93 | }, |
95 | 94 | ||
96 | video: function (callback) { | 95 | video: function (callback) { |
97 | Video.load(videoId, callback) | 96 | db.Video.loadAndPopulateAuthorAndPodAndTags(videoId, callback) |
98 | } | 97 | } |
99 | }, function (err, results) { | 98 | }, function (err, results) { |
100 | if (err) return next(err) | 99 | if (err) return next(err) |