From 74889a71fe687dda74f2a687653122327807af36 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Fri, 16 Jun 2017 09:45:46 +0200 Subject: Reorganize model files --- server/models/pod/index.ts | 1 + server/models/pod/pod-interface.ts | 67 +++++++++ server/models/pod/pod.ts | 274 +++++++++++++++++++++++++++++++++++++ 3 files changed, 342 insertions(+) create mode 100644 server/models/pod/index.ts create mode 100644 server/models/pod/pod-interface.ts create mode 100644 server/models/pod/pod.ts (limited to 'server/models/pod') 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 @@ +import * as Sequelize from 'sequelize' + +// Don't use barrel, import just what we need +import { Pod as FormatedPod } from '../../../shared/models/pod.model' + +export namespace PodMethods { + export type ToFormatedJSON = () => FormatedPod + + export type CountAllCallback = (err: Error, total: number) => void + export type CountAll = (callback) => void + + export type IncrementScoresCallback = (err: Error) => void + export type IncrementScores = (ids: number[], value: number, callback?: IncrementScoresCallback) => void + + export type ListCallback = (err: Error, podInstances?: PodInstance[]) => void + export type List = (callback: ListCallback) => void + + export type ListAllIdsCallback = (err: Error, ids?: number[]) => void + export type ListAllIds = (transaction: Sequelize.Transaction, callback: ListAllIdsCallback) => void + + export type ListRandomPodIdsWithRequestCallback = (err: Error, podInstanceIds?: number[]) => void + export type ListRandomPodIdsWithRequest = (limit: number, tableWithPods: string, tableWithPodsJoins: string, callback: ListRandomPodIdsWithRequestCallback) => void + + export type ListBadPodsCallback = (err: Error, podInstances?: PodInstance[]) => void + export type ListBadPods = (callback: ListBadPodsCallback) => void + + export type LoadCallback = (err: Error, podInstance: PodInstance) => void + export type Load = (id: number, callback: LoadCallback) => void + + export type LoadByHostCallback = (err: Error, podInstance: PodInstance) => void + export type LoadByHost = (host: string, callback: LoadByHostCallback) => void + + export type RemoveAllCallback = (err: Error) => void + export type RemoveAll = (callback: RemoveAllCallback) => void + + export type UpdatePodsScore = (goodPods: number[], badPods: number[]) => void +} + +export interface PodClass { + countAll: PodMethods.CountAll + incrementScores: PodMethods.IncrementScores + list: PodMethods.List + listAllIds: PodMethods.ListAllIds + listRandomPodIdsWithRequest: PodMethods.ListRandomPodIdsWithRequest + listBadPods: PodMethods.ListBadPods + load: PodMethods.Load + loadByHost: PodMethods.LoadByHost + removeAll: PodMethods.RemoveAll + updatePodsScore: PodMethods.UpdatePodsScore +} + +export interface PodAttributes { + host?: string + publicKey?: string + score?: number | Sequelize.literal // Sequelize literal for 'score +' + value + email?: string +} + +export interface PodInstance extends PodClass, PodAttributes, Sequelize.Instance { + id: number + createdAt: Date + updatedAt: Date + + toFormatedJSON: PodMethods.ToFormatedJSON, +} + +export interface PodModel extends PodClass, Sequelize.Model {} 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 @@ +import { each, waterfall } from 'async' +import { map } from 'lodash' +import * as Sequelize from 'sequelize' + +import { FRIEND_SCORE, PODS_SCORE } from '../../initializers' +import { logger, isHostValid } from '../../helpers' + +import { addMethodsToModel } from '../utils' +import { + PodClass, + PodInstance, + PodAttributes, + + PodMethods +} from './pod-interface' + +let Pod: Sequelize.Model +let toFormatedJSON: PodMethods.ToFormatedJSON +let countAll: PodMethods.CountAll +let incrementScores: PodMethods.IncrementScores +let list: PodMethods.List +let listAllIds: PodMethods.ListAllIds +let listRandomPodIdsWithRequest: PodMethods.ListRandomPodIdsWithRequest +let listBadPods: PodMethods.ListBadPods +let load: PodMethods.Load +let loadByHost: PodMethods.LoadByHost +let removeAll: PodMethods.RemoveAll +let updatePodsScore: PodMethods.UpdatePodsScore + +export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) { + Pod = sequelize.define('Pod', + { + host: { + type: DataTypes.STRING, + allowNull: false, + validate: { + isHost: function (value) { + const res = isHostValid(value) + if (res === false) throw new Error('Host not valid.') + } + } + }, + publicKey: { + type: DataTypes.STRING(5000), + allowNull: false + }, + score: { + type: DataTypes.INTEGER, + defaultValue: FRIEND_SCORE.BASE, + allowNull: false, + validate: { + isInt: true, + max: FRIEND_SCORE.MAX + } + }, + email: { + type: DataTypes.STRING(400), + allowNull: false, + validate: { + isEmail: true + } + } + }, + { + indexes: [ + { + fields: [ 'host' ], + unique: true + }, + { + fields: [ 'score' ] + } + ] + } + ) + + const classMethods = [ + associate, + + countAll, + incrementScores, + list, + listAllIds, + listRandomPodIdsWithRequest, + listBadPods, + load, + loadByHost, + updatePodsScore, + removeAll + ] + const instanceMethods = [ toFormatedJSON ] + addMethodsToModel(Pod, classMethods, instanceMethods) + + return Pod +} + +// ------------------------------ METHODS ------------------------------ + +toFormatedJSON = function () { + const json = { + id: this.id, + host: this.host, + email: this.email, + score: this.score, + createdAt: this.createdAt + } + + return json +} + +// ------------------------------ Statics ------------------------------ + +function associate (models) { + Pod.belongsToMany(models.Request, { + foreignKey: 'podId', + through: models.RequestToPod, + onDelete: 'cascade' + }) +} + +countAll = function (callback: PodMethods.CountAllCallback) { + return Pod.count().asCallback(callback) +} + +incrementScores = function (ids: number[], value: number, callback?: PodMethods.IncrementScoresCallback) { + if (!callback) callback = function () { /* empty */ } + + const update = { + score: Sequelize.literal('score +' + value) + } + + const options = { + where: { + id: { + $in: ids + } + }, + // In this case score is a literal and not an integer so we do not validate it + validate: false + } + + return Pod.update(update, options).asCallback(callback) +} + +list = function (callback: PodMethods.ListCallback) { + return Pod.findAll().asCallback(callback) +} + +listAllIds = function (transaction: Sequelize.Transaction, callback: PodMethods.ListAllIdsCallback) { + const query: any = { + attributes: [ 'id' ] + } + + if (transaction !== null) query.transaction = transaction + + return Pod.findAll(query).asCallback(function (err: Error, pods) { + if (err) return callback(err) + + return callback(null, map(pods, 'id')) + }) +} + +listRandomPodIdsWithRequest = function (limit: number, tableWithPods: string, tableWithPodsJoins: string, callback: PodMethods.ListRandomPodIdsWithRequestCallback) { + Pod.count().asCallback(function (err, count) { + if (err) return callback(err) + + // Optimization... + if (count === 0) return callback(null, []) + + let start = Math.floor(Math.random() * count) - limit + if (start < 0) start = 0 + + const query = { + attributes: [ 'id' ], + order: [ + [ 'id', 'ASC' ] + ], + offset: start, + limit: limit, + where: { + id: { + $in: [ + Sequelize.literal(`SELECT DISTINCT "${tableWithPods}"."podId" FROM "${tableWithPods}" ${tableWithPodsJoins}`) + ] + } + } + } + + return Pod.findAll(query).asCallback(function (err, pods) { + if (err) return callback(err) + + return callback(null, map(pods, 'id')) + }) + }) +} + +listBadPods = function (callback: PodMethods.ListBadPodsCallback) { + const query = { + where: { + score: { $lte: 0 } + } + } + + return Pod.findAll(query).asCallback(callback) +} + +load = function (id: number, callback: PodMethods.LoadCallback) { + return Pod.findById(id).asCallback(callback) +} + +loadByHost = function (host: string, callback: PodMethods.LoadByHostCallback) { + const query = { + where: { + host: host + } + } + + return Pod.findOne(query).asCallback(callback) +} + +removeAll = function (callback: PodMethods.RemoveAllCallback) { + return Pod.destroy().asCallback(callback) +} + +updatePodsScore = function (goodPods: number[], badPods: number[]) { + logger.info('Updating %d good pods and %d bad pods scores.', goodPods.length, badPods.length) + + if (goodPods.length !== 0) { + incrementScores(goodPods, PODS_SCORE.BONUS, function (err) { + if (err) logger.error('Cannot increment scores of good pods.', { error: err }) + }) + } + + if (badPods.length !== 0) { + incrementScores(badPods, PODS_SCORE.MALUS, function (err) { + if (err) logger.error('Cannot decrement scores of bad pods.', { error: err }) + removeBadPods() + }) + } +} + +// --------------------------------------------------------------------------- + +// Remove pods with a score of 0 (too many requests where they were unreachable) +function removeBadPods () { + waterfall([ + function findBadPods (callback) { + listBadPods(function (err, pods) { + if (err) { + logger.error('Cannot find bad pods.', { error: err }) + return callback(err) + } + + return callback(null, pods) + }) + }, + + function removeTheseBadPods (pods, callback) { + each(pods, function (pod: any, callbackEach) { + pod.destroy().asCallback(callbackEach) + }, function (err) { + return callback(err, pods.length) + }) + } + ], function (err, numberOfPodsRemoved) { + if (err) { + logger.error('Cannot remove bad pods.', { error: err }) + } else if (numberOfPodsRemoved) { + logger.info('Removed %d pods.', numberOfPodsRemoved) + } else { + logger.info('No need to remove bad pods.') + } + }) +} -- cgit v1.2.3