Server: add video language attribute
[oweals/peertube.git] / server / models / pod.js
1 'use strict'
2
3 const each = require('async/each')
4 const map = require('lodash/map')
5 const waterfall = require('async/waterfall')
6
7 const constants = require('../initializers/constants')
8 const logger = require('../helpers/logger')
9 const customPodsValidators = require('../helpers/custom-validators').pods
10
11 // ---------------------------------------------------------------------------
12
13 module.exports = function (sequelize, DataTypes) {
14   const Pod = sequelize.define('Pod',
15     {
16       host: {
17         type: DataTypes.STRING,
18         allowNull: false,
19         validate: {
20           isHost: function (value) {
21             const res = customPodsValidators.isHostValid(value)
22             if (res === false) throw new Error('Host not valid.')
23           }
24         }
25       },
26       publicKey: {
27         type: DataTypes.STRING(5000),
28         allowNull: false
29       },
30       score: {
31         type: DataTypes.INTEGER,
32         defaultValue: constants.FRIEND_SCORE.BASE,
33         allowNull: false,
34         validate: {
35           isInt: true,
36           max: constants.FRIEND_SCORE.MAX
37         }
38       },
39       email: {
40         type: DataTypes.STRING(400),
41         allowNull: false,
42         validate: {
43           isEmail: true
44         }
45       }
46     },
47     {
48       indexes: [
49         {
50           fields: [ 'host' ],
51           unique: true
52         },
53         {
54           fields: [ 'score' ]
55         }
56       ],
57       classMethods: {
58         associate,
59
60         countAll,
61         incrementScores,
62         list,
63         listAllIds,
64         listRandomPodIdsWithRequest,
65         listBadPods,
66         load,
67         loadByHost,
68         updatePodsScore,
69         removeAll
70       },
71       instanceMethods: {
72         toFormatedJSON
73       }
74     }
75   )
76
77   return Pod
78 }
79
80 // ------------------------------ METHODS ------------------------------
81
82 function toFormatedJSON () {
83   const json = {
84     id: this.id,
85     host: this.host,
86     email: this.email,
87     score: this.score,
88     createdAt: this.createdAt
89   }
90
91   return json
92 }
93
94 // ------------------------------ Statics ------------------------------
95
96 function associate (models) {
97   this.belongsToMany(models.Request, {
98     foreignKey: 'podId',
99     through: models.RequestToPod,
100     onDelete: 'cascade'
101   })
102 }
103
104 function countAll (callback) {
105   return this.count().asCallback(callback)
106 }
107
108 function incrementScores (ids, value, callback) {
109   if (!callback) callback = function () {}
110
111   const update = {
112     score: this.sequelize.literal('score +' + value)
113   }
114
115   const options = {
116     where: {
117       id: {
118         $in: ids
119       }
120     },
121     // In this case score is a literal and not an integer so we do not validate it
122     validate: false
123   }
124
125   return this.update(update, options).asCallback(callback)
126 }
127
128 function list (callback) {
129   return this.findAll().asCallback(callback)
130 }
131
132 function listAllIds (transaction, callback) {
133   if (!callback) {
134     callback = transaction
135     transaction = null
136   }
137
138   const query = {
139     attributes: [ 'id' ]
140   }
141
142   if (transaction) query.transaction = transaction
143
144   return this.findAll(query).asCallback(function (err, pods) {
145     if (err) return callback(err)
146
147     return callback(null, map(pods, 'id'))
148   })
149 }
150
151 function listRandomPodIdsWithRequest (limit, tableWithPods, tableWithPodsJoins, callback) {
152   if (!callback) {
153     callback = tableWithPodsJoins
154     tableWithPodsJoins = ''
155   }
156
157   const self = this
158
159   self.count().asCallback(function (err, count) {
160     if (err) return callback(err)
161
162     // Optimization...
163     if (count === 0) return callback(null, [])
164
165     let start = Math.floor(Math.random() * count) - limit
166     if (start < 0) start = 0
167
168     const query = {
169       attributes: [ 'id' ],
170       order: [
171         [ 'id', 'ASC' ]
172       ],
173       offset: start,
174       limit: limit,
175       where: {
176         id: {
177           $in: [
178             this.sequelize.literal(`SELECT DISTINCT "${tableWithPods}"."podId" FROM "${tableWithPods}" ${tableWithPodsJoins}`)
179           ]
180         }
181       }
182     }
183
184     return this.findAll(query).asCallback(function (err, pods) {
185       if (err) return callback(err)
186
187       return callback(null, map(pods, 'id'))
188     })
189   })
190 }
191
192 function listBadPods (callback) {
193   const query = {
194     where: {
195       score: { $lte: 0 }
196     }
197   }
198
199   return this.findAll(query).asCallback(callback)
200 }
201
202 function load (id, callback) {
203   return this.findById(id).asCallback(callback)
204 }
205
206 function loadByHost (host, callback) {
207   const query = {
208     where: {
209       host: host
210     }
211   }
212
213   return this.findOne(query).asCallback(callback)
214 }
215
216 function removeAll (callback) {
217   return this.destroy().asCallback(callback)
218 }
219
220 function updatePodsScore (goodPods, badPods) {
221   const self = this
222
223   logger.info('Updating %d good pods and %d bad pods scores.', goodPods.length, badPods.length)
224
225   if (goodPods.length !== 0) {
226     this.incrementScores(goodPods, constants.PODS_SCORE.BONUS, function (err) {
227       if (err) logger.error('Cannot increment scores of good pods.', { error: err })
228     })
229   }
230
231   if (badPods.length !== 0) {
232     this.incrementScores(badPods, constants.PODS_SCORE.MALUS, function (err) {
233       if (err) logger.error('Cannot decrement scores of bad pods.', { error: err })
234       removeBadPods.call(self)
235     })
236   }
237 }
238
239 // ---------------------------------------------------------------------------
240
241 // Remove pods with a score of 0 (too many requests where they were unreachable)
242 function removeBadPods () {
243   const self = this
244
245   waterfall([
246     function findBadPods (callback) {
247       self.sequelize.models.Pod.listBadPods(function (err, pods) {
248         if (err) {
249           logger.error('Cannot find bad pods.', { error: err })
250           return callback(err)
251         }
252
253         return callback(null, pods)
254       })
255     },
256
257     function removeTheseBadPods (pods, callback) {
258       each(pods, function (pod, callbackEach) {
259         pod.destroy().asCallback(callbackEach)
260       }, function (err) {
261         return callback(err, pods.length)
262       })
263     }
264   ], function (err, numberOfPodsRemoved) {
265     if (err) {
266       logger.error('Cannot remove bad pods.', { error: err })
267     } else if (numberOfPodsRemoved) {
268       logger.info('Removed %d pods.', numberOfPodsRemoved)
269     } else {
270       logger.info('No need to remove bad pods.')
271     }
272   })
273 }