]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - server/models/pod/pod.ts
df64127213da6ccb095690f842ba19c20fb1f197
[github/Chocobozzz/PeerTube.git] / server / models / pod / pod.ts
1 import { map } from 'lodash'
2 import * as Sequelize from 'sequelize'
3
4 import { FRIEND_SCORE, PODS_SCORE } from '../../initializers'
5 import { logger, isHostValid } from '../../helpers'
6
7 import { addMethodsToModel } from '../utils'
8 import {
9 PodInstance,
10 PodAttributes,
11
12 PodMethods
13 } from './pod-interface'
14
15 let Pod: Sequelize.Model<PodInstance, PodAttributes>
16 let toFormattedJSON: PodMethods.ToFormattedJSON
17 let countAll: PodMethods.CountAll
18 let incrementScores: PodMethods.IncrementScores
19 let list: PodMethods.List
20 let listAllIds: PodMethods.ListAllIds
21 let listRandomPodIdsWithRequest: PodMethods.ListRandomPodIdsWithRequest
22 let listBadPods: PodMethods.ListBadPods
23 let load: PodMethods.Load
24 let loadByHost: PodMethods.LoadByHost
25 let removeAll: PodMethods.RemoveAll
26 let updatePodsScore: PodMethods.UpdatePodsScore
27
28 export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) {
29 Pod = sequelize.define<PodInstance, PodAttributes>('Pod',
30 {
31 host: {
32 type: DataTypes.STRING,
33 allowNull: false,
34 validate: {
35 isHost: value => {
36 const res = isHostValid(value)
37 if (res === false) throw new Error('Host not valid.')
38 }
39 }
40 },
41 publicKey: {
42 type: DataTypes.STRING(5000),
43 allowNull: false
44 },
45 score: {
46 type: DataTypes.INTEGER,
47 defaultValue: FRIEND_SCORE.BASE,
48 allowNull: false,
49 validate: {
50 isInt: true,
51 max: FRIEND_SCORE.MAX
52 }
53 },
54 email: {
55 type: DataTypes.STRING(400),
56 allowNull: false,
57 validate: {
58 isEmail: true
59 }
60 }
61 },
62 {
63 indexes: [
64 {
65 fields: [ 'host' ],
66 unique: true
67 },
68 {
69 fields: [ 'score' ]
70 }
71 ]
72 }
73 )
74
75 const classMethods = [
76 associate,
77
78 countAll,
79 incrementScores,
80 list,
81 listAllIds,
82 listRandomPodIdsWithRequest,
83 listBadPods,
84 load,
85 loadByHost,
86 updatePodsScore,
87 removeAll
88 ]
89 const instanceMethods = [ toFormattedJSON ]
90 addMethodsToModel(Pod, classMethods, instanceMethods)
91
92 return Pod
93 }
94
95 // ------------------------------ METHODS ------------------------------
96
97 toFormattedJSON = function (this: PodInstance) {
98 const json = {
99 id: this.id,
100 host: this.host,
101 email: this.email,
102 score: this.score as number,
103 createdAt: this.createdAt
104 }
105
106 return json
107 }
108
109 // ------------------------------ Statics ------------------------------
110
111 function associate (models) {
112 Pod.belongsToMany(models.Request, {
113 foreignKey: 'podId',
114 through: models.RequestToPod,
115 onDelete: 'cascade'
116 })
117 }
118
119 countAll = function () {
120 return Pod.count()
121 }
122
123 incrementScores = function (ids: number[], value: number) {
124 const update = {
125 score: Sequelize.literal('score +' + value)
126 }
127
128 const options = {
129 where: {
130 id: {
131 $in: ids
132 }
133 },
134 // In this case score is a literal and not an integer so we do not validate it
135 validate: false
136 }
137
138 return Pod.update(update, options)
139 }
140
141 list = function () {
142 return Pod.findAll()
143 }
144
145 listAllIds = function (transaction: Sequelize.Transaction) {
146 const query = {
147 attributes: [ 'id' ],
148 transaction
149 }
150
151 return Pod.findAll(query).then(pods => {
152 return map(pods, 'id')
153 })
154 }
155
156 listRandomPodIdsWithRequest = function (limit: number, tableWithPods: string, tableWithPodsJoins: string) {
157 return Pod.count().then(count => {
158 // Optimization...
159 if (count === 0) return []
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: Sequelize.literal(`(SELECT DISTINCT "${tableWithPods}"."podId" FROM "${tableWithPods}" ${tableWithPodsJoins})`)
174 }
175 }
176 }
177
178 return Pod.findAll(query).then(pods => {
179 return map(pods, 'id')
180 })
181 })
182 }
183
184 listBadPods = function () {
185 const query = {
186 where: {
187 score: { $lte: 0 }
188 }
189 }
190
191 return Pod.findAll(query)
192 }
193
194 load = function (id: number) {
195 return Pod.findById(id)
196 }
197
198 loadByHost = function (host: string) {
199 const query = {
200 where: {
201 host: host
202 }
203 }
204
205 return Pod.findOne(query)
206 }
207
208 removeAll = function () {
209 return Pod.destroy()
210 }
211
212 updatePodsScore = function (goodPods: number[], badPods: number[]) {
213 logger.info('Updating %d good pods and %d bad pods scores.', goodPods.length, badPods.length)
214
215 if (goodPods.length !== 0) {
216 incrementScores(goodPods, PODS_SCORE.BONUS).catch(err => {
217 logger.error('Cannot increment scores of good pods.', err)
218 })
219 }
220
221 if (badPods.length !== 0) {
222 incrementScores(badPods, PODS_SCORE.MALUS)
223 .then(() => removeBadPods())
224 .catch(err => {
225 if (err) logger.error('Cannot decrement scores of bad pods.', err)
226 })
227 }
228 }
229
230 // ---------------------------------------------------------------------------
231
232 // Remove pods with a score of 0 (too many requests where they were unreachable)
233 function removeBadPods () {
234 return listBadPods()
235 .then(pods => {
236 const podsRemovePromises = pods.map(pod => pod.destroy())
237 return Promise.all(podsRemovePromises).then(() => pods.length)
238 })
239 .then(numberOfPodsRemoved => {
240 if (numberOfPodsRemoved) {
241 logger.info('Removed %d pods.', numberOfPodsRemoved)
242 } else {
243 logger.info('No need to remove bad pods.')
244 }
245 })
246 .catch(err => {
247 logger.error('Cannot remove bad pods.', err)
248 })
249 }