From fa0d5e665f13a964a276109d90391b57ea4837ea Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 21 Oct 2018 17:51:35 +0900 Subject: [PATCH] Implement following stats --- src/services/following/create.ts | 3 + src/services/following/delete.ts | 3 + src/services/following/requests/accept.ts | 3 + src/services/stats.ts | 139 ++++++++++++++++++++-- 4 files changed, 139 insertions(+), 9 deletions(-) diff --git a/src/services/following/create.ts b/src/services/following/create.ts index c0d0b9215..209c663a7 100644 --- a/src/services/following/create.ts +++ b/src/services/following/create.ts @@ -7,6 +7,7 @@ import renderFollow from '../../remote/activitypub/renderer/follow'; import renderAccept from '../../remote/activitypub/renderer/accept'; import { deliver } from '../../queue'; import createFollowRequest from './requests/create'; +import { followingStats } from '../stats'; export default async function(follower: IUser, followee: IUser, requestId?: string) { // フォロー対象が鍵アカウントである or @@ -52,6 +53,8 @@ export default async function(follower: IUser, followee: IUser, requestId?: stri }); //#endregion + followingStats.update(follower, followee, true); + // Publish follow event if (isLocalUser(follower)) { packUser(followee, follower).then(packed => publishMainStream(follower._id, 'follow', packed)); diff --git a/src/services/following/delete.ts b/src/services/following/delete.ts index 3fb0e50b1..035e00ba3 100644 --- a/src/services/following/delete.ts +++ b/src/services/following/delete.ts @@ -5,6 +5,7 @@ import pack from '../../remote/activitypub/renderer'; import renderFollow from '../../remote/activitypub/renderer/follow'; import renderUndo from '../../remote/activitypub/renderer/undo'; import { deliver } from '../../queue'; +import { followingStats } from '../stats'; export default async function(follower: IUser, followee: IUser) { const following = await Following.findOne({ @@ -37,6 +38,8 @@ export default async function(follower: IUser, followee: IUser) { }); //#endregion + followingStats.update(follower, followee, false); + // Publish unfollow event if (isLocalUser(follower)) { packUser(followee, follower).then(packed => publishMainStream(follower._id, 'unfollow', packed)); diff --git a/src/services/following/requests/accept.ts b/src/services/following/requests/accept.ts index 1f07df29d..9f02376a1 100644 --- a/src/services/following/requests/accept.ts +++ b/src/services/following/requests/accept.ts @@ -6,6 +6,7 @@ import renderAccept from '../../../remote/activitypub/renderer/accept'; import { deliver } from '../../../queue'; import Following from '../../../models/following'; import { publishMainStream } from '../../../stream'; +import { followingStats } from '../../stats'; export default async function(followee: IUser, follower: IUser) { await Following.insert({ @@ -57,6 +58,8 @@ export default async function(followee: IUser, follower: IUser) { }); //#endregion + followingStats.update(follower, followee, true); + await User.update({ _id: followee._id }, { $inc: { pendingReceivedFollowRequestsCount: -1 diff --git a/src/services/stats.ts b/src/services/stats.ts index 1df5148a9..a0b3a2874 100644 --- a/src/services/stats.ts +++ b/src/services/stats.ts @@ -10,6 +10,7 @@ import Note, { INote } from '../models/note'; import User, { isLocalUser, IUser } from '../models/user'; import DriveFile, { IDriveFile } from '../models/drive-file'; import { ICollection } from 'monk'; +import Following from '../models/following'; type Obj = { [key: string]: any }; @@ -58,7 +59,7 @@ type Log = { */ abstract class Stats { protected collection: ICollection>; - protected abstract async generateTemplate(init: boolean, latestLog?: T): Promise; + protected abstract async getTemplate(init: boolean, latestLog?: T, group?: any): Promise; constructor(name: string) { this.collection = db.get>(`stats.${name}`); @@ -127,7 +128,7 @@ abstract class Stats { if (latestLog) { // 現在の統計を初期挿入 - const data = await this.generateTemplate(false, latestLog.data); + const data = await this.getTemplate(false, latestLog.data); const log = await this.collection.insert({ group: group, @@ -142,7 +143,7 @@ abstract class Stats { // * Misskeyインスタンスを建てて初めてのチャート更新時など // 空の統計を作成 - const data = await this.generateTemplate(true); + const data = await this.getTemplate(true, null, group); const log = await this.collection.insert({ group: group, @@ -237,7 +238,7 @@ abstract class Stats { promisedChart.unshift(Promise.resolve(log.data)); } else { // 隙間埋め const latest = logs.find(l => l.date.getTime() < current.getTime()); - promisedChart.unshift(this.generateTemplate(false, latest ? latest.data : null)); + promisedChart.unshift(this.getTemplate(false, latest ? latest.data : null)); } } @@ -315,7 +316,7 @@ class UsersStats extends Stats { } @autobind - protected async generateTemplate(init: boolean, latestLog?: UsersLog): Promise { + protected async getTemplate(init: boolean, latestLog?: UsersLog): Promise { const [localCount, remoteCount] = init ? await Promise.all([ User.count({ host: null }), User.count({ host: { $ne: null } }) @@ -406,7 +407,7 @@ class NotesStats extends Stats { } @autobind - protected async generateTemplate(init: boolean, latestLog?: NotesLog): Promise { + protected async getTemplate(init: boolean, latestLog?: NotesLog): Promise { const [localCount, remoteCount] = init ? await Promise.all([ Note.count({ '_user.host': null }), Note.count({ '_user.host': { $ne: null } }) @@ -516,7 +517,7 @@ class DriveStats extends Stats { } @autobind - protected async generateTemplate(init: boolean, latestLog?: DriveLog): Promise { + protected async getTemplate(init: boolean, latestLog?: DriveLog): Promise { const calcSize = (local: boolean) => DriveFile .aggregate([{ $match: { @@ -628,7 +629,7 @@ class NetworkStats extends Stats { } @autobind - protected async generateTemplate(init: boolean, latestLog?: NetworkLog): Promise { + protected async getTemplate(init: boolean, latestLog?: NetworkLog): Promise { return { incomingRequests: 0, outgoingRequests: 0, @@ -671,7 +672,7 @@ class HashtagStats extends Stats { } @autobind - protected async generateTemplate(init: boolean, latestLog?: HashtagLog): Promise { + protected async getTemplate(init: boolean, latestLog?: HashtagLog): Promise { return { count: 0 }; @@ -689,4 +690,124 @@ class HashtagStats extends Stats { export const hashtagStats = new HashtagStats(); //#endregion + +//#region Following stats +/** + * ユーザーごとのフォローに関する統計 + */ +type FollowingLog = { + local: { + /** + * フォローしている + */ + followings: { + /** + * 合計 + */ + total: number; + + /** + * フォローした数 + */ + inc: number; + + /** + * フォロー解除した数 + */ + dec: number; + }; + + /** + * フォローされている + */ + followers: { + /** + * 合計 + */ + total: number; + + /** + * フォローされた数 + */ + inc: number; + + /** + * フォロー解除された数 + */ + dec: number; + }; + }; + + remote: FollowingLog['local']; +}; + +class FollowingStats extends Stats { + constructor() { + super('following'); + } + + @autobind + protected async getTemplate(init: boolean, latestLog?: FollowingLog, group?: any): Promise { + const [localFollowings, localFollowers, remoteFollowings, remoteFollowers] = init ? await Promise.all([ + Following.count({ followerId: group, '_followee.host': null }), + Following.count({ followeeId: group, '_user.host': null }), + Following.count({ followerId: group, '_followee.host': { $ne: null } }), + Following.count({ followeeId: group, '_user.host': { $ne: null } }) + ]) : [ + latestLog ? latestLog.local.followings.total : 0, + latestLog ? latestLog.local.followers.total : 0, + latestLog ? latestLog.remote.followings.total : 0, + latestLog ? latestLog.remote.followers.total : 0 + ]; + + return { + local: { + followings: { + total: localFollowings, + inc: 0, + dec: 0 + }, + followers: { + total: localFollowers, + inc: 0, + dec: 0 + } + }, + remote: { + followings: { + total: remoteFollowings, + inc: 0, + dec: 0 + }, + followers: { + total: remoteFollowers, + inc: 0, + dec: 0 + } + } + }; + } + + @autobind + public async update(follower: IUser, followee: IUser, isFollow: boolean) { + const update: Obj = {}; + + update.total = isFollow ? 1 : -1; + + if (isFollow) { + update.inc = 1; + } else { + update.dec = 1; + } + + this.inc({ + [isLocalUser(follower) ? 'local' : 'remote']: { followings: update } + }); + this.inc({ + [isLocalUser(followee) ? 'local' : 'remote']: { followers: update } + }); + } +} + +export const followingStats = new FollowingStats(); //#endregion