diff options
author | Chocobozzz <florian.bigard@gmail.com> | 2017-03-08 21:35:43 +0100 |
---|---|---|
committer | Chocobozzz <florian.bigard@gmail.com> | 2017-03-08 21:35:43 +0100 |
commit | d38b82810638b9f664c9016fac2684454c273a77 (patch) | |
tree | 9465c367e5033675309efca4d66790c6fdd5230d /server/controllers/api | |
parent | 8f9064432122cba0f518a24ac4378357dadec589 (diff) | |
download | PeerTube-d38b82810638b9f664c9016fac2684454c273a77.tar.gz PeerTube-d38b82810638b9f664c9016fac2684454c273a77.tar.zst PeerTube-d38b82810638b9f664c9016fac2684454c273a77.zip |
Add like/dislike system for videos
Diffstat (limited to 'server/controllers/api')
-rw-r--r-- | server/controllers/api/remote/videos.js | 27 | ||||
-rw-r--r-- | server/controllers/api/users.js | 27 | ||||
-rw-r--r-- | server/controllers/api/videos.js | 162 |
3 files changed, 209 insertions, 7 deletions
diff --git a/server/controllers/api/remote/videos.js b/server/controllers/api/remote/videos.js index 39c9579c1..98891c99e 100644 --- a/server/controllers/api/remote/videos.js +++ b/server/controllers/api/remote/videos.js | |||
@@ -11,6 +11,7 @@ const secureMiddleware = middlewares.secure | |||
11 | const videosValidators = middlewares.validators.remote.videos | 11 | const videosValidators = middlewares.validators.remote.videos |
12 | const signatureValidators = middlewares.validators.remote.signature | 12 | const signatureValidators = middlewares.validators.remote.signature |
13 | const logger = require('../../../helpers/logger') | 13 | const logger = require('../../../helpers/logger') |
14 | const friends = require('../../../lib/friends') | ||
14 | const databaseUtils = require('../../../helpers/database-utils') | 15 | const databaseUtils = require('../../../helpers/database-utils') |
15 | 16 | ||
16 | const ENDPOINT_ACTIONS = constants.REQUEST_ENDPOINT_ACTIONS[constants.REQUEST_ENDPOINTS.VIDEOS] | 17 | const ENDPOINT_ACTIONS = constants.REQUEST_ENDPOINT_ACTIONS[constants.REQUEST_ENDPOINTS.VIDEOS] |
@@ -129,18 +130,22 @@ function processVideosEvents (eventData, fromPod, finalCallback) { | |||
129 | const options = { transaction: t } | 130 | const options = { transaction: t } |
130 | 131 | ||
131 | let columnToUpdate | 132 | let columnToUpdate |
133 | let qaduType | ||
132 | 134 | ||
133 | switch (eventData.eventType) { | 135 | switch (eventData.eventType) { |
134 | case constants.REQUEST_VIDEO_EVENT_TYPES.VIEWS: | 136 | case constants.REQUEST_VIDEO_EVENT_TYPES.VIEWS: |
135 | columnToUpdate = 'views' | 137 | columnToUpdate = 'views' |
138 | qaduType = constants.REQUEST_VIDEO_QADU_TYPES.VIEWS | ||
136 | break | 139 | break |
137 | 140 | ||
138 | case constants.REQUEST_VIDEO_EVENT_TYPES.LIKES: | 141 | case constants.REQUEST_VIDEO_EVENT_TYPES.LIKES: |
139 | columnToUpdate = 'likes' | 142 | columnToUpdate = 'likes' |
143 | qaduType = constants.REQUEST_VIDEO_QADU_TYPES.LIKES | ||
140 | break | 144 | break |
141 | 145 | ||
142 | case constants.REQUEST_VIDEO_EVENT_TYPES.DISLIKES: | 146 | case constants.REQUEST_VIDEO_EVENT_TYPES.DISLIKES: |
143 | columnToUpdate = 'dislikes' | 147 | columnToUpdate = 'dislikes' |
148 | qaduType = constants.REQUEST_VIDEO_QADU_TYPES.DISLIKES | ||
144 | break | 149 | break |
145 | 150 | ||
146 | default: | 151 | default: |
@@ -151,6 +156,19 @@ function processVideosEvents (eventData, fromPod, finalCallback) { | |||
151 | query[columnToUpdate] = eventData.count | 156 | query[columnToUpdate] = eventData.count |
152 | 157 | ||
153 | videoInstance.increment(query, options).asCallback(function (err) { | 158 | videoInstance.increment(query, options).asCallback(function (err) { |
159 | return callback(err, t, videoInstance, qaduType) | ||
160 | }) | ||
161 | }, | ||
162 | |||
163 | function sendQaduToFriends (t, videoInstance, qaduType, callback) { | ||
164 | const qadusParams = [ | ||
165 | { | ||
166 | videoId: videoInstance.id, | ||
167 | type: qaduType | ||
168 | } | ||
169 | ] | ||
170 | |||
171 | friends.quickAndDirtyUpdatesVideoToFriends(qadusParams, t, function (err) { | ||
154 | return callback(err, t) | 172 | return callback(err, t) |
155 | }) | 173 | }) |
156 | }, | 174 | }, |
@@ -159,7 +177,6 @@ function processVideosEvents (eventData, fromPod, finalCallback) { | |||
159 | 177 | ||
160 | ], function (err, t) { | 178 | ], function (err, t) { |
161 | if (err) { | 179 | if (err) { |
162 | console.log(err) | ||
163 | logger.debug('Cannot process a video event.', { error: err }) | 180 | logger.debug('Cannot process a video event.', { error: err }) |
164 | return databaseUtils.rollbackTransaction(err, t, finalCallback) | 181 | return databaseUtils.rollbackTransaction(err, t, finalCallback) |
165 | } | 182 | } |
@@ -278,7 +295,10 @@ function addRemoteVideo (videoToCreateData, fromPod, finalCallback) { | |||
278 | duration: videoToCreateData.duration, | 295 | duration: videoToCreateData.duration, |
279 | createdAt: videoToCreateData.createdAt, | 296 | createdAt: videoToCreateData.createdAt, |
280 | // FIXME: updatedAt does not seems to be considered by Sequelize | 297 | // FIXME: updatedAt does not seems to be considered by Sequelize |
281 | updatedAt: videoToCreateData.updatedAt | 298 | updatedAt: videoToCreateData.updatedAt, |
299 | views: videoToCreateData.views, | ||
300 | likes: videoToCreateData.likes, | ||
301 | dislikes: videoToCreateData.dislikes | ||
282 | } | 302 | } |
283 | 303 | ||
284 | const video = db.Video.build(videoData) | 304 | const video = db.Video.build(videoData) |
@@ -372,6 +392,9 @@ function updateRemoteVideo (videoAttributesToUpdate, fromPod, finalCallback) { | |||
372 | videoInstance.set('createdAt', videoAttributesToUpdate.createdAt) | 392 | videoInstance.set('createdAt', videoAttributesToUpdate.createdAt) |
373 | videoInstance.set('updatedAt', videoAttributesToUpdate.updatedAt) | 393 | videoInstance.set('updatedAt', videoAttributesToUpdate.updatedAt) |
374 | videoInstance.set('extname', videoAttributesToUpdate.extname) | 394 | videoInstance.set('extname', videoAttributesToUpdate.extname) |
395 | videoInstance.set('views', videoAttributesToUpdate.views) | ||
396 | videoInstance.set('likes', videoAttributesToUpdate.likes) | ||
397 | videoInstance.set('dislikes', videoAttributesToUpdate.dislikes) | ||
375 | 398 | ||
376 | videoInstance.save(options).asCallback(function (err) { | 399 | videoInstance.save(options).asCallback(function (err) { |
377 | return callback(err, t, videoInstance, tagInstances) | 400 | return callback(err, t, videoInstance, tagInstances) |
diff --git a/server/controllers/api/users.js b/server/controllers/api/users.js index 324c99b4c..f854b3082 100644 --- a/server/controllers/api/users.js +++ b/server/controllers/api/users.js | |||
@@ -18,7 +18,16 @@ const validatorsUsers = middlewares.validators.users | |||
18 | 18 | ||
19 | const router = express.Router() | 19 | const router = express.Router() |
20 | 20 | ||
21 | router.get('/me', oAuth.authenticate, getUserInformation) | 21 | router.get('/me', |
22 | oAuth.authenticate, | ||
23 | getUserInformation | ||
24 | ) | ||
25 | |||
26 | router.get('/me/videos/:videoId/rating', | ||
27 | oAuth.authenticate, | ||
28 | validatorsUsers.usersVideoRating, | ||
29 | getUserVideoRating | ||
30 | ) | ||
22 | 31 | ||
23 | router.get('/', | 32 | router.get('/', |
24 | validatorsPagination.pagination, | 33 | validatorsPagination.pagination, |
@@ -80,6 +89,22 @@ function getUserInformation (req, res, next) { | |||
80 | }) | 89 | }) |
81 | } | 90 | } |
82 | 91 | ||
92 | function getUserVideoRating (req, res, next) { | ||
93 | const videoId = req.params.videoId | ||
94 | const userId = res.locals.oauth.token.User.id | ||
95 | |||
96 | db.UserVideoRate.load(userId, videoId, function (err, ratingObj) { | ||
97 | if (err) return next(err) | ||
98 | |||
99 | const rating = ratingObj ? ratingObj.type : 'none' | ||
100 | |||
101 | res.json({ | ||
102 | videoId, | ||
103 | rating | ||
104 | }) | ||
105 | }) | ||
106 | } | ||
107 | |||
83 | function listUsers (req, res, next) { | 108 | function listUsers (req, res, next) { |
84 | db.User.listForApi(req.query.start, req.query.count, req.query.sort, function (err, usersList, usersTotal) { | 109 | db.User.listForApi(req.query.start, req.query.count, req.query.sort, function (err, usersList, usersTotal) { |
85 | if (err) return next(err) | 110 | if (err) return next(err) |
diff --git a/server/controllers/api/videos.js b/server/controllers/api/videos.js index 5a67d1121..9acdb8fd2 100644 --- a/server/controllers/api/videos.js +++ b/server/controllers/api/videos.js | |||
@@ -60,6 +60,12 @@ router.post('/:id/abuse', | |||
60 | reportVideoAbuseRetryWrapper | 60 | reportVideoAbuseRetryWrapper |
61 | ) | 61 | ) |
62 | 62 | ||
63 | router.put('/:id/rate', | ||
64 | oAuth.authenticate, | ||
65 | validatorsVideos.videoRate, | ||
66 | rateVideoRetryWrapper | ||
67 | ) | ||
68 | |||
63 | router.get('/', | 69 | router.get('/', |
64 | validatorsPagination.pagination, | 70 | validatorsPagination.pagination, |
65 | validatorsSort.videosSort, | 71 | validatorsSort.videosSort, |
@@ -104,6 +110,147 @@ module.exports = router | |||
104 | 110 | ||
105 | // --------------------------------------------------------------------------- | 111 | // --------------------------------------------------------------------------- |
106 | 112 | ||
113 | function rateVideoRetryWrapper (req, res, next) { | ||
114 | const options = { | ||
115 | arguments: [ req, res ], | ||
116 | errorMessage: 'Cannot update the user video rate.' | ||
117 | } | ||
118 | |||
119 | databaseUtils.retryTransactionWrapper(rateVideo, options, function (err) { | ||
120 | if (err) return next(err) | ||
121 | |||
122 | return res.type('json').status(204).end() | ||
123 | }) | ||
124 | } | ||
125 | |||
126 | function rateVideo (req, res, finalCallback) { | ||
127 | const rateType = req.body.rating | ||
128 | const videoInstance = res.locals.video | ||
129 | const userInstance = res.locals.oauth.token.User | ||
130 | |||
131 | waterfall([ | ||
132 | databaseUtils.startSerializableTransaction, | ||
133 | |||
134 | function findPreviousRate (t, callback) { | ||
135 | db.UserVideoRate.load(userInstance.id, videoInstance.id, t, function (err, previousRate) { | ||
136 | return callback(err, t, previousRate) | ||
137 | }) | ||
138 | }, | ||
139 | |||
140 | function insertUserRateIntoDB (t, previousRate, callback) { | ||
141 | const options = { transaction: t } | ||
142 | |||
143 | let likesToIncrement = 0 | ||
144 | let dislikesToIncrement = 0 | ||
145 | |||
146 | if (rateType === constants.VIDEO_RATE_TYPES.LIKE) likesToIncrement++ | ||
147 | else if (rateType === constants.VIDEO_RATE_TYPES.DISLIKE) dislikesToIncrement++ | ||
148 | |||
149 | // There was a previous rate, update it | ||
150 | if (previousRate) { | ||
151 | // We will remove the previous rate, so we will need to remove it from the video attribute | ||
152 | if (previousRate.type === constants.VIDEO_RATE_TYPES.LIKE) likesToIncrement-- | ||
153 | else if (previousRate.type === constants.VIDEO_RATE_TYPES.DISLIKE) dislikesToIncrement-- | ||
154 | |||
155 | previousRate.type = rateType | ||
156 | |||
157 | previousRate.save(options).asCallback(function (err) { | ||
158 | return callback(err, t, likesToIncrement, dislikesToIncrement) | ||
159 | }) | ||
160 | } else { // There was not a previous rate, insert a new one | ||
161 | const query = { | ||
162 | userId: userInstance.id, | ||
163 | videoId: videoInstance.id, | ||
164 | type: rateType | ||
165 | } | ||
166 | |||
167 | db.UserVideoRate.create(query, options).asCallback(function (err) { | ||
168 | return callback(err, t, likesToIncrement, dislikesToIncrement) | ||
169 | }) | ||
170 | } | ||
171 | }, | ||
172 | |||
173 | function updateVideoAttributeDB (t, likesToIncrement, dislikesToIncrement, callback) { | ||
174 | const options = { transaction: t } | ||
175 | const incrementQuery = { | ||
176 | likes: likesToIncrement, | ||
177 | dislikes: dislikesToIncrement | ||
178 | } | ||
179 | |||
180 | // Even if we do not own the video we increment the attributes | ||
181 | // It is usefull for the user to have a feedback | ||
182 | videoInstance.increment(incrementQuery, options).asCallback(function (err) { | ||
183 | return callback(err, t, likesToIncrement, dislikesToIncrement) | ||
184 | }) | ||
185 | }, | ||
186 | |||
187 | function sendEventsToFriendsIfNeeded (t, likesToIncrement, dislikesToIncrement, callback) { | ||
188 | // No need for an event type, we own the video | ||
189 | if (videoInstance.isOwned()) return callback(null, t, likesToIncrement, dislikesToIncrement) | ||
190 | |||
191 | const eventsParams = [] | ||
192 | |||
193 | if (likesToIncrement !== 0) { | ||
194 | eventsParams.push({ | ||
195 | videoId: videoInstance.id, | ||
196 | type: constants.REQUEST_VIDEO_EVENT_TYPES.LIKES, | ||
197 | count: likesToIncrement | ||
198 | }) | ||
199 | } | ||
200 | |||
201 | if (dislikesToIncrement !== 0) { | ||
202 | eventsParams.push({ | ||
203 | videoId: videoInstance.id, | ||
204 | type: constants.REQUEST_VIDEO_EVENT_TYPES.DISLIKES, | ||
205 | count: dislikesToIncrement | ||
206 | }) | ||
207 | } | ||
208 | |||
209 | friends.addEventsToRemoteVideo(eventsParams, t, function (err) { | ||
210 | return callback(err, t, likesToIncrement, dislikesToIncrement) | ||
211 | }) | ||
212 | }, | ||
213 | |||
214 | function sendQaduToFriendsIfNeeded (t, likesToIncrement, dislikesToIncrement, callback) { | ||
215 | // We do not own the video, there is no need to send a quick and dirty update to friends | ||
216 | // Our rate was already sent by the addEvent function | ||
217 | if (videoInstance.isOwned() === false) return callback(null, t) | ||
218 | |||
219 | const qadusParams = [] | ||
220 | |||
221 | if (likesToIncrement !== 0) { | ||
222 | qadusParams.push({ | ||
223 | videoId: videoInstance.id, | ||
224 | type: constants.REQUEST_VIDEO_QADU_TYPES.LIKES | ||
225 | }) | ||
226 | } | ||
227 | |||
228 | if (dislikesToIncrement !== 0) { | ||
229 | qadusParams.push({ | ||
230 | videoId: videoInstance.id, | ||
231 | type: constants.REQUEST_VIDEO_QADU_TYPES.DISLIKES | ||
232 | }) | ||
233 | } | ||
234 | |||
235 | friends.quickAndDirtyUpdatesVideoToFriends(qadusParams, t, function (err) { | ||
236 | return callback(err, t) | ||
237 | }) | ||
238 | }, | ||
239 | |||
240 | databaseUtils.commitTransaction | ||
241 | |||
242 | ], function (err, t) { | ||
243 | if (err) { | ||
244 | // This is just a debug because we will retry the insert | ||
245 | logger.debug('Cannot add the user video rate.', { error: err }) | ||
246 | return databaseUtils.rollbackTransaction(err, t, finalCallback) | ||
247 | } | ||
248 | |||
249 | logger.info('User video rate for video %s of user %s updated.', videoInstance.name, userInstance.username) | ||
250 | return finalCallback(null) | ||
251 | }) | ||
252 | } | ||
253 | |||
107 | // Wrapper to video add that retry the function if there is a database error | 254 | // Wrapper to video add that retry the function if there is a database error |
108 | // We need this because we run the transaction in SERIALIZABLE isolation that can fail | 255 | // We need this because we run the transaction in SERIALIZABLE isolation that can fail |
109 | function addVideoRetryWrapper (req, res, next) { | 256 | function addVideoRetryWrapper (req, res, next) { |
@@ -155,8 +302,7 @@ function addVideo (req, res, videoFile, finalCallback) { | |||
155 | extname: path.extname(videoFile.filename), | 302 | extname: path.extname(videoFile.filename), |
156 | description: videoInfos.description, | 303 | description: videoInfos.description, |
157 | duration: videoFile.duration, | 304 | duration: videoFile.duration, |
158 | authorId: author.id, | 305 | authorId: author.id |
159 | views: videoInfos.views | ||
160 | } | 306 | } |
161 | 307 | ||
162 | const video = db.Video.build(videoData) | 308 | const video = db.Video.build(videoData) |
@@ -332,11 +478,19 @@ function getVideo (req, res, next) { | |||
332 | 478 | ||
333 | // FIXME: make a real view system | 479 | // FIXME: make a real view system |
334 | // For example, only add a view when a user watch a video during 30s etc | 480 | // For example, only add a view when a user watch a video during 30s etc |
335 | friends.quickAndDirtyUpdateVideoToFriends(videoInstance.id, constants.REQUEST_VIDEO_QADU_TYPES.VIEWS) | 481 | const qaduParams = { |
482 | videoId: videoInstance.id, | ||
483 | type: constants.REQUEST_VIDEO_QADU_TYPES.VIEWS | ||
484 | } | ||
485 | friends.quickAndDirtyUpdateVideoToFriends(qaduParams) | ||
336 | }) | 486 | }) |
337 | } else { | 487 | } else { |
338 | // Just send the event to our friends | 488 | // Just send the event to our friends |
339 | friends.addEventToRemoteVideo(videoInstance.id, constants.REQUEST_VIDEO_EVENT_TYPES.VIEWS) | 489 | const eventParams = { |
490 | videoId: videoInstance.id, | ||
491 | type: constants.REQUEST_VIDEO_EVENT_TYPES.VIEWS | ||
492 | } | ||
493 | friends.addEventToRemoteVideo(eventParams) | ||
340 | } | 494 | } |
341 | 495 | ||
342 | // Do not wait the view system | 496 | // Do not wait the view system |