X-Git-Url: https://git.immae.eu/?a=blobdiff_plain;f=server%2Fmodels%2Factivitypub%2Factor-follow.ts;h=ced48154705187f57271454407e817c37edd9367;hb=f05a1c30c15d2ae35c11e241ca039a72eeb7d6ad;hp=5fcc3449dbb301c76ca1ec88c8a8db0ca9ce4935;hpb=759f8a29e95932023564ca98dcbc90d1acb92da0;p=github%2FChocobozzz%2FPeerTube.git diff --git a/server/models/activitypub/actor-follow.ts b/server/models/activitypub/actor-follow.ts index 5fcc3449d..ced481547 100644 --- a/server/models/activitypub/actor-follow.ts +++ b/server/models/activitypub/actor-follow.ts @@ -1,8 +1,14 @@ import * as Bluebird from 'bluebird' import { values } from 'lodash' import * as Sequelize from 'sequelize' -import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript' +import { + AfterCreate, AfterDestroy, AfterUpdate, AllowNull, BelongsTo, Column, CreatedAt, DataType, Default, ForeignKey, IsInt, Max, Model, + Table, UpdatedAt +} from 'sequelize-typescript' import { FollowState } from '../../../shared/models/actors' +import { AccountFollow } from '../../../shared/models/actors/follow.model' +import { logger } from '../../helpers/logger' +import { ACTOR_FOLLOW_SCORE } from '../../initializers' import { FOLLOW_STATES } from '../../initializers/constants' import { ServerModel } from '../server/server' import { getSort } from '../utils' @@ -20,6 +26,9 @@ import { ActorModel } from './actor' { fields: [ 'actorId', 'targetActorId' ], unique: true + }, + { + fields: [ 'score' ] } ] }) @@ -29,6 +38,13 @@ export class ActorFollowModel extends Model { @Column(DataType.ENUM(values(FOLLOW_STATES))) state: FollowState + @AllowNull(false) + @Default(ACTOR_FOLLOW_SCORE.BASE) + @IsInt + @Max(ACTOR_FOLLOW_SCORE.MAX) + @Column + score: number + @CreatedAt createdAt: Date @@ -63,6 +79,53 @@ export class ActorFollowModel extends Model { }) ActorFollowing: ActorModel + @AfterCreate + @AfterUpdate + static incrementFollowerAndFollowingCount (instance: ActorFollowModel) { + if (instance.state !== 'accepted') return undefined + + return Promise.all([ + ActorModel.incrementFollows(instance.actorId, 'followingCount', 1), + ActorModel.incrementFollows(instance.targetActorId, 'followersCount', 1) + ]) + } + + @AfterDestroy + static decrementFollowerAndFollowingCount (instance: ActorFollowModel) { + return Promise.all([ + ActorModel.incrementFollows(instance.actorId, 'followingCount',-1), + ActorModel.incrementFollows(instance.targetActorId, 'followersCount', -1) + ]) + } + + // Remove actor follows with a score of 0 (too many requests where they were unreachable) + static async removeBadActorFollows () { + const actorFollows = await ActorFollowModel.listBadActorFollows() + + const actorFollowsRemovePromises = actorFollows.map(actorFollow => actorFollow.destroy()) + await Promise.all(actorFollowsRemovePromises) + + const numberOfActorFollowsRemoved = actorFollows.length + + if (numberOfActorFollowsRemoved) logger.info('Removed bad %d actor follows.', numberOfActorFollowsRemoved) + } + + static updateActorFollowsScoreAndRemoveBadOnes (goodInboxes: string[], badInboxes: string[], t: Sequelize.Transaction) { + if (goodInboxes.length === 0 && badInboxes.length === 0) return + + logger.info('Updating %d good actor follows and %d bad actor follows scores.', goodInboxes.length, badInboxes.length) + + if (goodInboxes.length !== 0) { + ActorFollowModel.incrementScores(goodInboxes, ACTOR_FOLLOW_SCORE.BONUS, t) + .catch(err => logger.error('Cannot increment scores of good actor follows.', err)) + } + + if (badInboxes.length !== 0) { + ActorFollowModel.incrementScores(badInboxes, ACTOR_FOLLOW_SCORE.PENALTY, t) + .catch(err => logger.error('Cannot decrement scores of bad actor follows.', err)) + } + } + static loadByActorAndTarget (actorId: number, targetActorId: number, t?: Sequelize.Transaction) { const query = { where: { @@ -119,6 +182,34 @@ export class ActorFollowModel extends Model { return ActorFollowModel.findOne(query) } + static loadByFollowerInbox (url: string, t?: Sequelize.Transaction) { + const query = { + where: { + state: 'accepted' + }, + include: [ + { + model: ActorModel, + required: true, + as: 'ActorFollower', + where: { + [Sequelize.Op.or]: [ + { + inboxUrl: url + }, + { + sharedInboxUrl: url + } + ] + } + } + ], + transaction: t + } as any // FIXME: typings does not work + + return ActorFollowModel.findOne(query) + } + static listFollowingForApi (id: number, start: number, count: number, sort: string) { const query = { distinct: true, @@ -260,7 +351,37 @@ export class ActorFollowModel extends Model { } } - toFormattedJSON () { + private static incrementScores (inboxUrls: string[], value: number, t: Sequelize.Transaction) { + const inboxUrlsString = inboxUrls.map(url => `'${url}'`).join(',') + + const query = `UPDATE "actorFollow" SET "score" = LEAST("score" + ${value}, ${ACTOR_FOLLOW_SCORE.MAX}) ` + + 'WHERE id IN (' + + 'SELECT "actorFollow"."id" FROM "actorFollow" ' + + 'INNER JOIN "actor" ON "actor"."id" = "actorFollow"."actorId" ' + + 'WHERE "actor"."inboxUrl" IN (' + inboxUrlsString + ') OR "actor"."sharedInboxUrl" IN (' + inboxUrlsString + ')' + + ')' + + const options = { + type: Sequelize.QueryTypes.BULKUPDATE, + transaction: t + } + + return ActorFollowModel.sequelize.query(query, options) + } + + private static listBadActorFollows () { + const query = { + where: { + score: { + [Sequelize.Op.lte]: 0 + } + } + } + + return ActorFollowModel.findAll(query) + } + + toFormattedJSON (): AccountFollow { const follower = this.ActorFollower.toFormattedJSON() const following = this.ActorFollowing.toFormattedJSON() @@ -268,6 +389,7 @@ export class ActorFollowModel extends Model { id: this.id, follower, following, + score: this.score, state: this.state, createdAt: this.createdAt, updatedAt: this.updatedAt