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