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