aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/controllers/api
diff options
context:
space:
mode:
authorChocobozzz <florian.bigard@gmail.com>2017-03-08 21:35:43 +0100
committerChocobozzz <florian.bigard@gmail.com>2017-03-08 21:35:43 +0100
commitd38b82810638b9f664c9016fac2684454c273a77 (patch)
tree9465c367e5033675309efca4d66790c6fdd5230d /server/controllers/api
parent8f9064432122cba0f518a24ac4378357dadec589 (diff)
downloadPeerTube-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.js27
-rw-r--r--server/controllers/api/users.js27
-rw-r--r--server/controllers/api/videos.js162
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
11const videosValidators = middlewares.validators.remote.videos 11const videosValidators = middlewares.validators.remote.videos
12const signatureValidators = middlewares.validators.remote.signature 12const signatureValidators = middlewares.validators.remote.signature
13const logger = require('../../../helpers/logger') 13const logger = require('../../../helpers/logger')
14const friends = require('../../../lib/friends')
14const databaseUtils = require('../../../helpers/database-utils') 15const databaseUtils = require('../../../helpers/database-utils')
15 16
16const ENDPOINT_ACTIONS = constants.REQUEST_ENDPOINT_ACTIONS[constants.REQUEST_ENDPOINTS.VIDEOS] 17const 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
19const router = express.Router() 19const router = express.Router()
20 20
21router.get('/me', oAuth.authenticate, getUserInformation) 21router.get('/me',
22 oAuth.authenticate,
23 getUserInformation
24)
25
26router.get('/me/videos/:videoId/rating',
27 oAuth.authenticate,
28 validatorsUsers.usersVideoRating,
29 getUserVideoRating
30)
22 31
23router.get('/', 32router.get('/',
24 validatorsPagination.pagination, 33 validatorsPagination.pagination,
@@ -80,6 +89,22 @@ function getUserInformation (req, res, next) {
80 }) 89 })
81} 90}
82 91
92function 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
83function listUsers (req, res, next) { 108function 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
63router.put('/:id/rate',
64 oAuth.authenticate,
65 validatorsVideos.videoRate,
66 rateVideoRetryWrapper
67)
68
63router.get('/', 69router.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
113function 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
126function 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
109function addVideoRetryWrapper (req, res, next) { 256function 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