adjust following and follower counts
This commit is contained in:
parent
75d02a51a6
commit
fc327f0567
|
@ -1,14 +1,13 @@
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { IsNull } from 'typeorm';
|
import { IsNull, In } from 'typeorm';
|
||||||
|
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import type { Config } from '@/config.js';
|
import type { Config } from '@/config.js';
|
||||||
import type { LocalUser } from '@/models/entities/User.js';
|
import type { LocalUser } from '@/models/entities/User.js';
|
||||||
import type { BlockingsRepository, FollowingsRepository, Muting, MutingsRepository, UserListJoiningsRepository, UsersRepository } from '@/models/index.js';
|
import type { BlockingsRepository, FollowingsRepository, InstancesRepository, Muting, MutingsRepository, UserListJoiningsRepository, UsersRepository } from '@/models/index.js';
|
||||||
import type { RelationshipJobData, ThinUser } from '@/queue/types.js';
|
import type { RelationshipJobData, ThinUser } from '@/queue/types.js';
|
||||||
|
import type { User } from '@/models/entities/User.js';
|
||||||
import { User } from '@/models/entities/User.js';
|
|
||||||
|
|
||||||
import { AccountUpdateService } from '@/core/AccountUpdateService.js';
|
import { AccountUpdateService } from '@/core/AccountUpdateService.js';
|
||||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||||
|
@ -19,8 +18,12 @@ import { ApDeliverManagerService } from '@/core/activitypub/ApDeliverManagerServ
|
||||||
import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
|
import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
|
||||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||||
import { IdService } from '@/core/IdService.js';
|
import { IdService } from '@/core/IdService.js';
|
||||||
import { CacheService } from '@/core/CacheService';
|
import { CacheService } from '@/core/CacheService.js';
|
||||||
import { ProxyAccountService } from '@/core/ProxyAccountService.js';
|
import { ProxyAccountService } from '@/core/ProxyAccountService.js';
|
||||||
|
import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
|
||||||
|
import { MetaService } from '@/core/MetaService.js';
|
||||||
|
import InstanceChart from '@/core/chart/charts/instance.js';
|
||||||
|
import PerUserFollowingChart from '@/core/chart/charts/per-user-following.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AccountMoveService {
|
export class AccountMoveService {
|
||||||
|
@ -43,6 +46,9 @@ export class AccountMoveService {
|
||||||
@Inject(DI.userListJoiningsRepository)
|
@Inject(DI.userListJoiningsRepository)
|
||||||
private userListJoiningsRepository: UserListJoiningsRepository,
|
private userListJoiningsRepository: UserListJoiningsRepository,
|
||||||
|
|
||||||
|
@Inject(DI.instancesRepository)
|
||||||
|
private instancesRepository: InstancesRepository,
|
||||||
|
|
||||||
private idService: IdService,
|
private idService: IdService,
|
||||||
private userEntityService: UserEntityService,
|
private userEntityService: UserEntityService,
|
||||||
private apRendererService: ApRendererService,
|
private apRendererService: ApRendererService,
|
||||||
|
@ -51,6 +57,10 @@ export class AccountMoveService {
|
||||||
private userFollowingService: UserFollowingService,
|
private userFollowingService: UserFollowingService,
|
||||||
private accountUpdateService: AccountUpdateService,
|
private accountUpdateService: AccountUpdateService,
|
||||||
private proxyAccountService: ProxyAccountService,
|
private proxyAccountService: ProxyAccountService,
|
||||||
|
private perUserFollowingChart: PerUserFollowingChart,
|
||||||
|
private federatedInstanceService: FederatedInstanceService,
|
||||||
|
private instanceChart: InstanceChart,
|
||||||
|
private metaService: MetaService,
|
||||||
private relayService: RelayService,
|
private relayService: RelayService,
|
||||||
private cacheService: CacheService,
|
private cacheService: CacheService,
|
||||||
private queueService: QueueService,
|
private queueService: QueueService,
|
||||||
|
@ -140,6 +150,10 @@ export class AccountMoveService {
|
||||||
if (!following.follower) continue;
|
if (!following.follower) continue;
|
||||||
followJobs.push({ from: { id: following.follower.id }, to: { id: dst.id } });
|
followJobs.push({ from: { id: following.follower.id }, to: { id: dst.id } });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Decrease following count instead of unfollowing.
|
||||||
|
await this.adjustFollowingCounts(followJobs.map(job => job.from.id), src);
|
||||||
|
|
||||||
// Should be queued because this can cause a number of follow per one move.
|
// Should be queued because this can cause a number of follow per one move.
|
||||||
this.queueService.createFollowJob(followJobs);
|
this.queueService.createFollowJob(followJobs);
|
||||||
}
|
}
|
||||||
|
@ -216,4 +230,28 @@ export class AccountMoveService {
|
||||||
return this.userEntityService.isRemoteUser(user)
|
return this.userEntityService.isRemoteUser(user)
|
||||||
? user.uri : `${this.config.url}/users/${user.id}`;
|
? user.uri : `${this.config.url}/users/${user.id}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
private async adjustFollowingCounts(localFollowerIds: string[], oldAccount: User) {
|
||||||
|
// Set the old account's following and followers counts to 0.
|
||||||
|
await this.usersRepository.update(oldAccount.id, { followersCount: 0, followingCount: 0 });
|
||||||
|
|
||||||
|
// Decrease following counts of local followers by 1.
|
||||||
|
await this.usersRepository.decrement({ id: In(localFollowerIds) }, 'followingCount', 1);
|
||||||
|
|
||||||
|
// Update instance stats by decreasing remote followers count by the number of local followers who were following the old account.
|
||||||
|
if (this.userEntityService.isRemoteUser(oldAccount)) {
|
||||||
|
this.federatedInstanceService.fetch(oldAccount.host).then(async i => {
|
||||||
|
this.instancesRepository.decrement({ id: i.id }, 'followersCount', localFollowerIds.length);
|
||||||
|
if ((await this.metaService.fetch()).enableChartsForFederatedInstances) {
|
||||||
|
this.instanceChart.updateFollowers(i.host, false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: expensive?
|
||||||
|
for (const followerId of localFollowerIds) {
|
||||||
|
this.perUserFollowingChart.update({ id: followerId, host: null }, oldAccount, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -230,32 +230,40 @@ export class UserFollowingService implements OnModuleInit {
|
||||||
|
|
||||||
this.globalEventService.publishInternalEvent('follow', { followerId: follower.id, followeeId: followee.id });
|
this.globalEventService.publishInternalEvent('follow', { followerId: follower.id, followeeId: followee.id });
|
||||||
|
|
||||||
//#region Increment counts
|
const [followeeUser, followerUser] = await Promise.all([
|
||||||
await Promise.all([
|
this.usersRepository.findOneByOrFail({ id: followee.id }),
|
||||||
this.usersRepository.increment({ id: follower.id }, 'followingCount', 1),
|
this.usersRepository.findOneByOrFail({ id: follower.id }),
|
||||||
this.usersRepository.increment({ id: followee.id }, 'followersCount', 1),
|
|
||||||
]);
|
]);
|
||||||
//#endregion
|
|
||||||
|
|
||||||
//#region Update instance stats
|
// Neither followee nor follower has moved.
|
||||||
if (this.userEntityService.isRemoteUser(follower) && this.userEntityService.isLocalUser(followee)) {
|
if (!followeeUser.movedToUri && !followerUser.movedToUri) {
|
||||||
this.federatedInstanceService.fetch(follower.host).then(async i => {
|
//#region Increment counts
|
||||||
this.instancesRepository.increment({ id: i.id }, 'followingCount', 1);
|
await Promise.all([
|
||||||
if ((await this.metaService.fetch()).enableChartsForFederatedInstances) {
|
this.usersRepository.increment({ id: follower.id }, 'followingCount', 1),
|
||||||
this.instanceChart.updateFollowing(i.host, true);
|
this.usersRepository.increment({ id: followee.id }, 'followersCount', 1),
|
||||||
}
|
]);
|
||||||
});
|
//#endregion
|
||||||
} else if (this.userEntityService.isLocalUser(follower) && this.userEntityService.isRemoteUser(followee)) {
|
|
||||||
this.federatedInstanceService.fetch(followee.host).then(async i => {
|
//#region Update instance stats
|
||||||
this.instancesRepository.increment({ id: i.id }, 'followersCount', 1);
|
if (this.userEntityService.isRemoteUser(follower) && this.userEntityService.isLocalUser(followee)) {
|
||||||
if ((await this.metaService.fetch()).enableChartsForFederatedInstances) {
|
this.federatedInstanceService.fetch(follower.host).then(async i => {
|
||||||
this.instanceChart.updateFollowers(i.host, true);
|
this.instancesRepository.increment({ id: i.id }, 'followingCount', 1);
|
||||||
}
|
if ((await this.metaService.fetch()).enableChartsForFederatedInstances) {
|
||||||
});
|
this.instanceChart.updateFollowing(i.host, true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (this.userEntityService.isLocalUser(follower) && this.userEntityService.isRemoteUser(followee)) {
|
||||||
|
this.federatedInstanceService.fetch(followee.host).then(async i => {
|
||||||
|
this.instancesRepository.increment({ id: i.id }, 'followersCount', 1);
|
||||||
|
if ((await this.metaService.fetch()).enableChartsForFederatedInstances) {
|
||||||
|
this.instanceChart.updateFollowers(i.host, true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
//#endregion
|
||||||
|
|
||||||
|
this.perUserFollowingChart.update(follower, followee, true);
|
||||||
}
|
}
|
||||||
//#endregion
|
|
||||||
|
|
||||||
this.perUserFollowingChart.update(follower, followee, true);
|
|
||||||
|
|
||||||
// Publish follow event
|
// Publish follow event
|
||||||
if (this.userEntityService.isLocalUser(follower) && !silent) {
|
if (this.userEntityService.isLocalUser(follower) && !silent) {
|
||||||
|
@ -303,12 +311,18 @@ export class UserFollowingService implements OnModuleInit {
|
||||||
},
|
},
|
||||||
silent = false,
|
silent = false,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const following = await this.followingsRepository.findOneBy({
|
const following = await this.followingsRepository.findOne({
|
||||||
followerId: follower.id,
|
relations: {
|
||||||
followeeId: followee.id,
|
follower: true,
|
||||||
|
followee: true,
|
||||||
|
},
|
||||||
|
where: {
|
||||||
|
followerId: follower.id,
|
||||||
|
followeeId: followee.id,
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (following == null) {
|
if (following === null) {
|
||||||
logger.warn('フォロー解除がリクエストされましたがフォローしていませんでした');
|
logger.warn('フォロー解除がリクエストされましたがフォローしていませんでした');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -317,7 +331,10 @@ export class UserFollowingService implements OnModuleInit {
|
||||||
|
|
||||||
this.cacheService.userFollowingsCache.refresh(follower.id);
|
this.cacheService.userFollowingsCache.refresh(follower.id);
|
||||||
|
|
||||||
this.decrementFollowing(follower, followee);
|
// Neither followee nor follower has moved.
|
||||||
|
if (!following.followee?.movedToUri && !following.follower?.movedToUri) {
|
||||||
|
this.decrementFollowing(follower, followee);
|
||||||
|
}
|
||||||
|
|
||||||
// Publish unfollow event
|
// Publish unfollow event
|
||||||
if (!silent && this.userEntityService.isLocalUser(follower)) {
|
if (!silent && this.userEntityService.isLocalUser(follower)) {
|
||||||
|
@ -582,15 +599,25 @@ export class UserFollowingService implements OnModuleInit {
|
||||||
*/
|
*/
|
||||||
@bindThis
|
@bindThis
|
||||||
private async removeFollow(followee: Both, follower: Both): Promise<void> {
|
private async removeFollow(followee: Both, follower: Both): Promise<void> {
|
||||||
const following = await this.followingsRepository.findOneBy({
|
const following = await this.followingsRepository.findOne({
|
||||||
followeeId: followee.id,
|
relations: {
|
||||||
followerId: follower.id,
|
followee: true,
|
||||||
|
follower: true,
|
||||||
|
},
|
||||||
|
where: {
|
||||||
|
followeeId: followee.id,
|
||||||
|
followerId: follower.id,
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!following) return;
|
if (!following) return;
|
||||||
|
|
||||||
await this.followingsRepository.delete(following.id);
|
await this.followingsRepository.delete(following.id);
|
||||||
this.decrementFollowing(follower, followee);
|
|
||||||
|
// Neither followee nor follower has moved.
|
||||||
|
if (!following.followee?.movedToUri && !following.follower?.movedToUri) {
|
||||||
|
this.decrementFollowing(follower, followee);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Reference in a new issue