8456cbaf24ec48685b6d2ed0348f57545fdefcaf
[oweals/peertube.git] / server / controllers / api / videos / rate.ts
1 import * as express from 'express'
2 import * as Promise from 'bluebird'
3
4 import { database as db } from '../../../initializers/database'
5 import {
6   logger,
7   retryTransactionWrapper
8 } from '../../../helpers'
9 import {
10   VIDEO_RATE_TYPES,
11   REQUEST_VIDEO_EVENT_TYPES,
12   REQUEST_VIDEO_QADU_TYPES
13 } from '../../../initializers'
14 import {
15   addEventsToRemoteVideo,
16   quickAndDirtyUpdatesVideoToFriends
17 } from '../../../lib'
18 import {
19   authenticate,
20   videoRateValidator
21 } from '../../../middlewares'
22 import { UserVideoRateUpdate, VideoRateType } from '../../../../shared'
23
24 const rateVideoRouter = express.Router()
25
26 rateVideoRouter.put('/:id/rate',
27   authenticate,
28   videoRateValidator,
29   rateVideoRetryWrapper
30 )
31
32 // ---------------------------------------------------------------------------
33
34 export {
35   rateVideoRouter
36 }
37
38 // ---------------------------------------------------------------------------
39
40 function rateVideoRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) {
41   const options = {
42     arguments: [ req, res ],
43     errorMessage: 'Cannot update the user video rate.'
44   }
45
46   retryTransactionWrapper(rateVideo, options)
47     .then(() => res.type('json').status(204).end())
48     .catch(err => next(err))
49 }
50
51 function rateVideo (req: express.Request, res: express.Response) {
52   const body: UserVideoRateUpdate = req.body
53   const rateType = body.rating
54   const videoInstance = res.locals.video
55   const userInstance = res.locals.oauth.token.User
56
57   return db.sequelize.transaction(t => {
58     return db.UserVideoRate.load(userInstance.id, videoInstance.id, t)
59       .then(previousRate => {
60         const options = { transaction: t }
61
62         let likesToIncrement = 0
63         let dislikesToIncrement = 0
64
65         if (rateType === VIDEO_RATE_TYPES.LIKE) likesToIncrement++
66         else if (rateType === VIDEO_RATE_TYPES.DISLIKE) dislikesToIncrement++
67
68         let promise: Promise<any>
69
70         // There was a previous rate, update it
71         if (previousRate) {
72           // We will remove the previous rate, so we will need to remove it from the video attribute
73           if (previousRate.type === VIDEO_RATE_TYPES.LIKE) likesToIncrement--
74           else if (previousRate.type === VIDEO_RATE_TYPES.DISLIKE) dislikesToIncrement--
75
76           if (rateType === 'none') { // Destroy previous rate
77             promise = previousRate.destroy()
78           } else { // Update previous rate
79             previousRate.type = rateType as VideoRateType
80
81             promise = previousRate.save()
82           }
83         } else if (rateType !== 'none') { // There was not a previous rate, insert a new one if there is a rate
84           const query = {
85             userId: userInstance.id,
86             videoId: videoInstance.id,
87             type: rateType
88           }
89
90           promise = db.UserVideoRate.create(query, options)
91         } else {
92           promise = Promise.resolve()
93         }
94
95         return promise.then(() => ({ likesToIncrement, dislikesToIncrement }))
96       })
97       .then(({ likesToIncrement, dislikesToIncrement }) => {
98         const options = { transaction: t }
99         const incrementQuery = {
100           likes: likesToIncrement,
101           dislikes: dislikesToIncrement
102         }
103
104         // Even if we do not own the video we increment the attributes
105         // It is usefull for the user to have a feedback
106         return videoInstance.increment(incrementQuery, options).then(() => ({ likesToIncrement, dislikesToIncrement }))
107       })
108       .then(({ likesToIncrement, dislikesToIncrement }) => {
109         // No need for an event type, we own the video
110         if (videoInstance.isOwned()) return { likesToIncrement, dislikesToIncrement }
111
112         const eventsParams = []
113
114         if (likesToIncrement !== 0) {
115           eventsParams.push({
116             videoId: videoInstance.id,
117             type: REQUEST_VIDEO_EVENT_TYPES.LIKES,
118             count: likesToIncrement
119           })
120         }
121
122         if (dislikesToIncrement !== 0) {
123           eventsParams.push({
124             videoId: videoInstance.id,
125             type: REQUEST_VIDEO_EVENT_TYPES.DISLIKES,
126             count: dislikesToIncrement
127           })
128         }
129
130         return addEventsToRemoteVideo(eventsParams, t).then(() => ({ likesToIncrement, dislikesToIncrement }))
131       })
132       .then(({ likesToIncrement, dislikesToIncrement }) => {
133         // We do not own the video, there is no need to send a quick and dirty update to friends
134         // Our rate was already sent by the addEvent function
135         if (videoInstance.isOwned() === false) return undefined
136
137         const qadusParams = []
138
139         if (likesToIncrement !== 0) {
140           qadusParams.push({
141             videoId: videoInstance.id,
142             type: REQUEST_VIDEO_QADU_TYPES.LIKES
143           })
144         }
145
146         if (dislikesToIncrement !== 0) {
147           qadusParams.push({
148             videoId: videoInstance.id,
149             type: REQUEST_VIDEO_QADU_TYPES.DISLIKES
150           })
151         }
152
153         return quickAndDirtyUpdatesVideoToFriends(qadusParams, t)
154       })
155   })
156   .then(() => logger.info('User video rate for video %s of user %s updated.', videoInstance.name, userInstance.username))
157   .catch(err => {
158     // This is just a debug because we will retry the insert
159     logger.debug('Cannot add the user video rate.', err)
160     throw err
161   })
162 }