diff options
Diffstat (limited to 'server/models/pod')
-rw-r--r-- | server/models/pod/index.ts | 1 | ||||
-rw-r--r-- | server/models/pod/pod-interface.ts | 67 | ||||
-rw-r--r-- | server/models/pod/pod.ts | 274 |
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 @@ | |||
1 | import * as Sequelize from 'sequelize' | ||
2 | |||
3 | // Don't use barrel, import just what we need | ||
4 | import { Pod as FormatedPod } from '../../../shared/models/pod.model' | ||
5 | |||
6 | export 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 | |||
39 | export 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 | |||
52 | export interface PodAttributes { | ||
53 | host?: string | ||
54 | publicKey?: string | ||
55 | score?: number | Sequelize.literal // Sequelize literal for 'score +' + value | ||
56 | email?: string | ||
57 | } | ||
58 | |||
59 | export interface PodInstance extends PodClass, PodAttributes, Sequelize.Instance<PodAttributes> { | ||
60 | id: number | ||
61 | createdAt: Date | ||
62 | updatedAt: Date | ||
63 | |||
64 | toFormatedJSON: PodMethods.ToFormatedJSON, | ||
65 | } | ||
66 | |||
67 | export 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 @@ | |||
1 | import { each, waterfall } from 'async' | ||
2 | import { map } from 'lodash' | ||
3 | import * as Sequelize from 'sequelize' | ||
4 | |||
5 | import { FRIEND_SCORE, PODS_SCORE } from '../../initializers' | ||
6 | import { logger, isHostValid } from '../../helpers' | ||
7 | |||
8 | import { addMethodsToModel } from '../utils' | ||
9 | import { | ||
10 | PodClass, | ||
11 | PodInstance, | ||
12 | PodAttributes, | ||
13 | |||
14 | PodMethods | ||
15 | } from './pod-interface' | ||
16 | |||
17 | let Pod: Sequelize.Model<PodInstance, PodAttributes> | ||
18 | let toFormatedJSON: PodMethods.ToFormatedJSON | ||
19 | let countAll: PodMethods.CountAll | ||
20 | let incrementScores: PodMethods.IncrementScores | ||
21 | let list: PodMethods.List | ||
22 | let listAllIds: PodMethods.ListAllIds | ||
23 | let listRandomPodIdsWithRequest: PodMethods.ListRandomPodIdsWithRequest | ||
24 | let listBadPods: PodMethods.ListBadPods | ||
25 | let load: PodMethods.Load | ||
26 | let loadByHost: PodMethods.LoadByHost | ||
27 | let removeAll: PodMethods.RemoveAll | ||
28 | let updatePodsScore: PodMethods.UpdatePodsScore | ||
29 | |||
30 | export 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 | |||
99 | toFormatedJSON = 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 | |||
113 | function associate (models) { | ||
114 | Pod.belongsToMany(models.Request, { | ||
115 | foreignKey: 'podId', | ||
116 | through: models.RequestToPod, | ||
117 | onDelete: 'cascade' | ||
118 | }) | ||
119 | } | ||
120 | |||
121 | countAll = function (callback: PodMethods.CountAllCallback) { | ||
122 | return Pod.count().asCallback(callback) | ||
123 | } | ||
124 | |||
125 | incrementScores = 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 | |||
145 | list = function (callback: PodMethods.ListCallback) { | ||
146 | return Pod.findAll().asCallback(callback) | ||
147 | } | ||
148 | |||
149 | listAllIds = 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 | |||
163 | listRandomPodIdsWithRequest = 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 | |||
197 | listBadPods = 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 | |||
207 | load = function (id: number, callback: PodMethods.LoadCallback) { | ||
208 | return Pod.findById(id).asCallback(callback) | ||
209 | } | ||
210 | |||
211 | loadByHost = 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 | |||
221 | removeAll = function (callback: PodMethods.RemoveAllCallback) { | ||
222 | return Pod.destroy().asCallback(callback) | ||
223 | } | ||
224 | |||
225 | updatePodsScore = 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) | ||
245 | function 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 | } | ||