なんかもうめっちゃ変えた

This commit is contained in:
syuilo 2022-09-18 03:27:08 +09:00 committed by GitHub
parent d9ab03f086
commit b75184ec8e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
946 changed files with 41219 additions and 28839 deletions

View file

@ -0,0 +1,54 @@
import { Injectable, Inject } from '@nestjs/common';
import { DataSource } from 'typeorm';
import { AppLockService } from '@/core/AppLockService.js';
import type { User } from '@/models/entities/User.js';
import { DI } from '@/di-symbols.js';
import Chart from '../core.js';
import { name, schema } from './entities/active-users.js';
import type { KVs } from '../core.js';
const week = 1000 * 60 * 60 * 24 * 7;
const month = 1000 * 60 * 60 * 24 * 30;
const year = 1000 * 60 * 60 * 24 * 365;
/**
*
*/
// eslint-disable-next-line import/no-default-export
@Injectable()
export default class ActiveUsersChart extends Chart<typeof schema> {
constructor(
@Inject(DI.db)
private db: DataSource,
private appLockService: AppLockService,
) {
super(db, (k) => appLockService.getChartInsertLock(k), name, schema);
}
protected async tickMajor(): Promise<Partial<KVs<typeof schema>>> {
return {};
}
protected async tickMinor(): Promise<Partial<KVs<typeof schema>>> {
return {};
}
public async read(user: { id: User['id'], host: null, createdAt: User['createdAt'] }): Promise<void> {
await this.commit({
'read': [user.id],
'registeredWithinWeek': (Date.now() - user.createdAt.getTime() < week) ? [user.id] : [],
'registeredWithinMonth': (Date.now() - user.createdAt.getTime() < month) ? [user.id] : [],
'registeredWithinYear': (Date.now() - user.createdAt.getTime() < year) ? [user.id] : [],
'registeredOutsideWeek': (Date.now() - user.createdAt.getTime() > week) ? [user.id] : [],
'registeredOutsideMonth': (Date.now() - user.createdAt.getTime() > month) ? [user.id] : [],
'registeredOutsideYear': (Date.now() - user.createdAt.getTime() > year) ? [user.id] : [],
});
}
public async write(user: { id: User['id'], host: null, createdAt: User['createdAt'] }): Promise<void> {
await this.commit({
'write': [user.id],
});
}
}

View file

@ -0,0 +1,49 @@
import { Injectable, Inject } from '@nestjs/common';
import { DataSource } from 'typeorm';
import { AppLockService } from '@/core/AppLockService.js';
import { DI } from '@/di-symbols.js';
import Chart from '../core.js';
import { name, schema } from './entities/ap-request.js';
import type { KVs } from '../core.js';
/**
* Chart about ActivityPub requests
*/
// eslint-disable-next-line import/no-default-export
@Injectable()
export default class ApRequestChart extends Chart<typeof schema> {
constructor(
@Inject(DI.db)
private db: DataSource,
private appLockService: AppLockService,
) {
super(db, (k) => appLockService.getChartInsertLock(k), name, schema);
}
protected async tickMajor(): Promise<Partial<KVs<typeof schema>>> {
return {};
}
protected async tickMinor(): Promise<Partial<KVs<typeof schema>>> {
return {};
}
public async deliverSucc(): Promise<void> {
await this.commit({
'deliverSucceeded': 1,
});
}
public async deliverFail(): Promise<void> {
await this.commit({
'deliverFailed': 1,
});
}
public async inbox(): Promise<void> {
await this.commit({
'inboxReceived': 1,
});
}
}

View file

@ -0,0 +1,47 @@
import { Injectable, Inject } from '@nestjs/common';
import { Not, IsNull, DataSource } from 'typeorm';
import type { DriveFile } from '@/models/entities/DriveFile.js';
import { AppLockService } from '@/core/AppLockService.js';
import { DI } from '@/di-symbols.js';
import Chart from '../core.js';
import { name, schema } from './entities/drive.js';
import type { KVs } from '../core.js';
/**
*
*/
// eslint-disable-next-line import/no-default-export
@Injectable()
export default class DriveChart extends Chart<typeof schema> {
constructor(
@Inject(DI.db)
private db: DataSource,
private appLockService: AppLockService,
) {
super(db, (k) => appLockService.getChartInsertLock(k), name, schema);
}
protected async tickMajor(): Promise<Partial<KVs<typeof schema>>> {
return {};
}
protected async tickMinor(): Promise<Partial<KVs<typeof schema>>> {
return {};
}
public async update(file: DriveFile, isAdditional: boolean): Promise<void> {
const fileSizeKb = file.size / 1000;
await this.commit(file.userHost === null ? {
'local.incCount': isAdditional ? 1 : 0,
'local.incSize': isAdditional ? fileSizeKb : 0,
'local.decCount': isAdditional ? 0 : 1,
'local.decSize': isAdditional ? 0 : fileSizeKb,
} : {
'remote.incCount': isAdditional ? 1 : 0,
'remote.incSize': isAdditional ? fileSizeKb : 0,
'remote.decCount': isAdditional ? 0 : 1,
'remote.decSize': isAdditional ? 0 : fileSizeKb,
});
}
}

View file

@ -0,0 +1,17 @@
import Chart from '../../core.js';
export const name = 'activeUsers';
export const schema = {
'readWrite': { intersection: ['read', 'write'], range: 'small' },
'read': { uniqueIncrement: true, range: 'small' },
'write': { uniqueIncrement: true, range: 'small' },
'registeredWithinWeek': { uniqueIncrement: true, range: 'small' },
'registeredWithinMonth': { uniqueIncrement: true, range: 'small' },
'registeredWithinYear': { uniqueIncrement: true, range: 'small' },
'registeredOutsideWeek': { uniqueIncrement: true, range: 'small' },
'registeredOutsideMonth': { uniqueIncrement: true, range: 'small' },
'registeredOutsideYear': { uniqueIncrement: true, range: 'small' },
} as const;
export const entity = Chart.schemaToEntity(name, schema);

View file

@ -0,0 +1,11 @@
import Chart from '../../core.js';
export const name = 'apRequest';
export const schema = {
'deliverFailed': { },
'deliverSucceeded': { },
'inboxReceived': { },
} as const;
export const entity = Chart.schemaToEntity(name, schema);

View file

@ -0,0 +1,16 @@
import Chart from '../../core.js';
export const name = 'drive';
export const schema = {
'local.incCount': {},
'local.incSize': {}, // in kilobyte
'local.decCount': {},
'local.decSize': {}, // in kilobyte
'remote.incCount': {},
'remote.incSize': {}, // in kilobyte
'remote.decCount': {},
'remote.decSize': {}, // in kilobyte
} as const;
export const entity = Chart.schemaToEntity(name, schema);

View file

@ -0,0 +1,16 @@
import Chart from '../../core.js';
export const name = 'federation';
export const schema = {
'deliveredInstances': { uniqueIncrement: true, range: 'small' },
'inboxInstances': { uniqueIncrement: true, range: 'small' },
'stalled': { uniqueIncrement: true, range: 'small' },
'sub': { accumulate: true, range: 'small' },
'pub': { accumulate: true, range: 'small' },
'pubsub': { accumulate: true, range: 'small' },
'subActive': { accumulate: true, range: 'small' },
'pubActive': { accumulate: true, range: 'small' },
} as const;
export const entity = Chart.schemaToEntity(name, schema);

View file

@ -0,0 +1,10 @@
import Chart from '../../core.js';
export const name = 'hashtag';
export const schema = {
'local.users': { uniqueIncrement: true },
'remote.users': { uniqueIncrement: true },
} as const;
export const entity = Chart.schemaToEntity(name, schema, true);

View file

@ -0,0 +1,32 @@
import Chart from '../../core.js';
export const name = 'instance';
export const schema = {
'requests.failed': { range: 'small' },
'requests.succeeded': { range: 'small' },
'requests.received': { range: 'small' },
'notes.total': { accumulate: true },
'notes.inc': {},
'notes.dec': {},
'notes.diffs.normal': {},
'notes.diffs.reply': {},
'notes.diffs.renote': {},
'notes.diffs.withFile': {},
'users.total': { accumulate: true },
'users.inc': { range: 'small' },
'users.dec': { range: 'small' },
'following.total': { accumulate: true },
'following.inc': { range: 'small' },
'following.dec': { range: 'small' },
'followers.total': { accumulate: true },
'followers.inc': { range: 'small' },
'followers.dec': { range: 'small' },
'drive.totalFiles': { accumulate: true },
'drive.incFiles': {},
'drive.decFiles': {},
'drive.incUsage': {}, // in kilobyte
'drive.decUsage': {}, // in kilobyte
} as const;
export const entity = Chart.schemaToEntity(name, schema, true);

View file

@ -0,0 +1,22 @@
import Chart from '../../core.js';
export const name = 'notes';
export const schema = {
'local.total': { accumulate: true },
'local.inc': {},
'local.dec': {},
'local.diffs.normal': {},
'local.diffs.reply': {},
'local.diffs.renote': {},
'local.diffs.withFile': {},
'remote.total': { accumulate: true },
'remote.inc': {},
'remote.dec': {},
'remote.diffs.normal': {},
'remote.diffs.reply': {},
'remote.diffs.renote': {},
'remote.diffs.withFile': {},
} as const;
export const entity = Chart.schemaToEntity(name, schema);

View file

@ -0,0 +1,14 @@
import Chart from '../../core.js';
export const name = 'perUserDrive';
export const schema = {
'totalCount': { accumulate: true },
'totalSize': { accumulate: true }, // in kilobyte
'incCount': { range: 'small' },
'incSize': {}, // in kilobyte
'decCount': { range: 'small' },
'decSize': {}, // in kilobyte
} as const;
export const entity = Chart.schemaToEntity(name, schema, true);

View file

@ -0,0 +1,20 @@
import Chart from '../../core.js';
export const name = 'perUserFollowing';
export const schema = {
'local.followings.total': { accumulate: true },
'local.followings.inc': { range: 'small' },
'local.followings.dec': { range: 'small' },
'local.followers.total': { accumulate: true },
'local.followers.inc': { range: 'small' },
'local.followers.dec': { range: 'small' },
'remote.followings.total': { accumulate: true },
'remote.followings.inc': { range: 'small' },
'remote.followings.dec': { range: 'small' },
'remote.followers.total': { accumulate: true },
'remote.followers.inc': { range: 'small' },
'remote.followers.dec': { range: 'small' },
} as const;
export const entity = Chart.schemaToEntity(name, schema, true);

View file

@ -0,0 +1,15 @@
import Chart from '../../core.js';
export const name = 'perUserNotes';
export const schema = {
'total': { accumulate: true },
'inc': { range: 'small' },
'dec': { range: 'small' },
'diffs.normal': { range: 'small' },
'diffs.reply': { range: 'small' },
'diffs.renote': { range: 'small' },
'diffs.withFile': { range: 'small' },
} as const;
export const entity = Chart.schemaToEntity(name, schema, true);

View file

@ -0,0 +1,10 @@
import Chart from '../../core.js';
export const name = 'perUserReaction';
export const schema = {
'local.count': { range: 'small' },
'remote.count': { range: 'small' },
} as const;
export const entity = Chart.schemaToEntity(name, schema, true);

View file

@ -0,0 +1,11 @@
import Chart from '../../core.js';
export const name = 'testGrouped';
export const schema = {
'foo.total': { accumulate: true },
'foo.inc': {},
'foo.dec': {},
} as const;
export const entity = Chart.schemaToEntity(name, schema, true);

View file

@ -0,0 +1,11 @@
import Chart from '../../core.js';
export const name = 'testIntersection';
export const schema = {
'a': { uniqueIncrement: true },
'b': { uniqueIncrement: true },
'aAndB': { intersection: ['a', 'b'] },
} as const;
export const entity = Chart.schemaToEntity(name, schema);

View file

@ -0,0 +1,9 @@
import Chart from '../../core.js';
export const name = 'testUnique';
export const schema = {
'foo': { uniqueIncrement: true },
} as const;
export const entity = Chart.schemaToEntity(name, schema);

View file

@ -0,0 +1,11 @@
import Chart from '../../core.js';
export const name = 'test';
export const schema = {
'foo.total': { accumulate: true },
'foo.inc': {},
'foo.dec': {},
} as const;
export const entity = Chart.schemaToEntity(name, schema);

View file

@ -0,0 +1,14 @@
import Chart from '../../core.js';
export const name = 'users';
export const schema = {
'local.total': { accumulate: true },
'local.inc': { range: 'small' },
'local.dec': { range: 'small' },
'remote.total': { accumulate: true },
'remote.inc': { range: 'small' },
'remote.dec': { range: 'small' },
} as const;
export const entity = Chart.schemaToEntity(name, schema);

View file

@ -0,0 +1,121 @@
import { Injectable, Inject } from '@nestjs/common';
import { DataSource } from 'typeorm';
import { FollowingsRepository, InstancesRepository } from '@/models/index.js';
import { AppLockService } from '@/core/AppLockService.js';
import { DI } from '@/di-symbols.js';
import { MetaService } from '@/core/MetaService.js';
import Chart from '../core.js';
import { name, schema } from './entities/federation.js';
import type { KVs } from '../core.js';
/**
*
*/
// eslint-disable-next-line import/no-default-export
@Injectable()
export default class FederationChart extends Chart<typeof schema> {
constructor(
@Inject(DI.db)
private db: DataSource,
@Inject(DI.followingsRepository)
private followingsRepository: FollowingsRepository,
@Inject(DI.instancesRepository)
private instancesRepository: InstancesRepository,
private metaService: MetaService,
private appLockService: AppLockService,
) {
super(db, (k) => appLockService.getChartInsertLock(k), name, schema);
}
protected async tickMajor(): Promise<Partial<KVs<typeof schema>>> {
return {
};
}
protected async tickMinor(): Promise<Partial<KVs<typeof schema>>> {
const meta = await this.metaService.fetch();
const suspendedInstancesQuery = this.instancesRepository.createQueryBuilder('instance')
.select('instance.host')
.where('instance.isSuspended = true');
const pubsubSubQuery = this.followingsRepository.createQueryBuilder('f')
.select('f.followerHost')
.where('f.followerHost IS NOT NULL');
const subInstancesQuery = this.followingsRepository.createQueryBuilder('f')
.select('f.followeeHost')
.where('f.followeeHost IS NOT NULL');
const pubInstancesQuery = this.followingsRepository.createQueryBuilder('f')
.select('f.followerHost')
.where('f.followerHost IS NOT NULL');
const [sub, pub, pubsub, subActive, pubActive] = await Promise.all([
this.followingsRepository.createQueryBuilder('following')
.select('COUNT(DISTINCT following.followeeHost)')
.where('following.followeeHost IS NOT NULL')
.andWhere(meta.blockedHosts.length === 0 ? '1=1' : 'following.followeeHost NOT IN (:...blocked)', { blocked: meta.blockedHosts })
.andWhere(`following.followeeHost NOT IN (${ suspendedInstancesQuery.getQuery() })`)
.getRawOne()
.then(x => parseInt(x.count, 10)),
this.followingsRepository.createQueryBuilder('following')
.select('COUNT(DISTINCT following.followerHost)')
.where('following.followerHost IS NOT NULL')
.andWhere(meta.blockedHosts.length === 0 ? '1=1' : 'following.followerHost NOT IN (:...blocked)', { blocked: meta.blockedHosts })
.andWhere(`following.followerHost NOT IN (${ suspendedInstancesQuery.getQuery() })`)
.getRawOne()
.then(x => parseInt(x.count, 10)),
this.followingsRepository.createQueryBuilder('following')
.select('COUNT(DISTINCT following.followeeHost)')
.where('following.followeeHost IS NOT NULL')
.andWhere(meta.blockedHosts.length === 0 ? '1=1' : 'following.followeeHost NOT IN (:...blocked)', { blocked: meta.blockedHosts })
.andWhere(`following.followeeHost NOT IN (${ suspendedInstancesQuery.getQuery() })`)
.andWhere(`following.followeeHost IN (${ pubsubSubQuery.getQuery() })`)
.setParameters(pubsubSubQuery.getParameters())
.getRawOne()
.then(x => parseInt(x.count, 10)),
this.instancesRepository.createQueryBuilder('instance')
.select('COUNT(instance.id)')
.where(`instance.host IN (${ subInstancesQuery.getQuery() })`)
.andWhere(meta.blockedHosts.length === 0 ? '1=1' : 'instance.host NOT IN (:...blocked)', { blocked: meta.blockedHosts })
.andWhere('instance.isSuspended = false')
.andWhere('instance.lastCommunicatedAt > :gt', { gt: new Date(Date.now() - (1000 * 60 * 60 * 24 * 30)) })
.getRawOne()
.then(x => parseInt(x.count, 10)),
this.instancesRepository.createQueryBuilder('instance')
.select('COUNT(instance.id)')
.where(`instance.host IN (${ pubInstancesQuery.getQuery() })`)
.andWhere(meta.blockedHosts.length === 0 ? '1=1' : 'instance.host NOT IN (:...blocked)', { blocked: meta.blockedHosts })
.andWhere('instance.isSuspended = false')
.andWhere('instance.lastCommunicatedAt > :gt', { gt: new Date(Date.now() - (1000 * 60 * 60 * 24 * 30)) })
.getRawOne()
.then(x => parseInt(x.count, 10)),
]);
return {
'sub': sub,
'pub': pub,
'pubsub': pubsub,
'subActive': subActive,
'pubActive': pubActive,
};
}
public async deliverd(host: string, succeeded: boolean): Promise<void> {
await this.commit(succeeded ? {
'deliveredInstances': [host],
} : {
'stalled': [host],
});
}
public async inbox(host: string): Promise<void> {
await this.commit({
'inboxInstances': [host],
});
}
}

View file

@ -0,0 +1,41 @@
import { Injectable, Inject } from '@nestjs/common';
import { DataSource } from 'typeorm';
import type { User } from '@/models/entities/User.js';
import { AppLockService } from '@/core/AppLockService.js';
import { DI } from '@/di-symbols.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
import Chart from '../core.js';
import { name, schema } from './entities/hashtag.js';
import type { KVs } from '../core.js';
/**
*
*/
// eslint-disable-next-line import/no-default-export
@Injectable()
export default class HashtagChart extends Chart<typeof schema> {
constructor(
@Inject(DI.db)
private db: DataSource,
private appLockService: AppLockService,
private userEntityService: UserEntityService,
) {
super(db, (k) => appLockService.getChartInsertLock(k), name, schema, true);
}
protected async tickMajor(): Promise<Partial<KVs<typeof schema>>> {
return {};
}
protected async tickMinor(): Promise<Partial<KVs<typeof schema>>> {
return {};
}
public async update(hashtag: string, user: { id: User['id'], host: User['host'] }): Promise<void> {
await this.commit({
'local.users': this.userEntityService.isLocalUser(user) ? [user.id] : [],
'remote.users': this.userEntityService.isLocalUser(user) ? [] : [user.id],
}, hashtag);
}
}

View file

@ -0,0 +1,127 @@
import { Injectable, Inject } from '@nestjs/common';
import { DataSource } from 'typeorm';
import { DriveFilesRepository, FollowingsRepository, UsersRepository, NotesRepository } from '@/models/index.js';
import type { DriveFile } from '@/models/entities/DriveFile.js';
import type { Note } from '@/models/entities/Note.js';
import { AppLockService } from '@/core/AppLockService.js';
import { DI } from '@/di-symbols.js';
import { UtilityService } from '@/core/UtilityService.js';
import Chart from '../core.js';
import { name, schema } from './entities/instance.js';
import type { KVs } from '../core.js';
/**
*
*/
// eslint-disable-next-line import/no-default-export
@Injectable()
export default class InstanceChart extends Chart<typeof schema> {
constructor(
@Inject(DI.db)
private db: DataSource,
@Inject(DI.usersRepository)
private usersRepository: UsersRepository,
@Inject(DI.notesRepository)
private notesRepository: NotesRepository,
@Inject(DI.driveFilesRepository)
private driveFilesRepository: DriveFilesRepository,
@Inject(DI.followingsRepository)
private followingsRepository: FollowingsRepository,
private utilityService: UtilityService,
private appLockService: AppLockService,
) {
super(db, (k) => appLockService.getChartInsertLock(k), name, schema, true);
}
protected async tickMajor(group: string): Promise<Partial<KVs<typeof schema>>> {
const [
notesCount,
usersCount,
followingCount,
followersCount,
driveFiles,
] = await Promise.all([
this.notesRepository.countBy({ userHost: group }),
this.usersRepository.countBy({ host: group }),
this.followingsRepository.countBy({ followerHost: group }),
this.followingsRepository.countBy({ followeeHost: group }),
this.driveFilesRepository.countBy({ userHost: group }),
]);
return {
'notes.total': notesCount,
'users.total': usersCount,
'following.total': followingCount,
'followers.total': followersCount,
'drive.totalFiles': driveFiles,
};
}
protected async tickMinor(): Promise<Partial<KVs<typeof schema>>> {
return {};
}
public async requestReceived(host: string): Promise<void> {
await this.commit({
'requests.received': 1,
}, this.utilityService.toPuny(host));
}
public async requestSent(host: string, isSucceeded: boolean): Promise<void> {
await this.commit({
'requests.succeeded': isSucceeded ? 1 : 0,
'requests.failed': isSucceeded ? 0 : 1,
}, this.utilityService.toPuny(host));
}
public async newUser(host: string): Promise<void> {
await this.commit({
'users.total': 1,
'users.inc': 1,
}, this.utilityService.toPuny(host));
}
public async updateNote(host: string, note: Note, isAdditional: boolean): Promise<void> {
await this.commit({
'notes.total': isAdditional ? 1 : -1,
'notes.inc': isAdditional ? 1 : 0,
'notes.dec': isAdditional ? 0 : 1,
'notes.diffs.normal': note.replyId == null && note.renoteId == null ? (isAdditional ? 1 : -1) : 0,
'notes.diffs.renote': note.renoteId != null ? (isAdditional ? 1 : -1) : 0,
'notes.diffs.reply': note.replyId != null ? (isAdditional ? 1 : -1) : 0,
'notes.diffs.withFile': note.fileIds.length > 0 ? (isAdditional ? 1 : -1) : 0,
}, this.utilityService.toPuny(host));
}
public async updateFollowing(host: string, isAdditional: boolean): Promise<void> {
await this.commit({
'following.total': isAdditional ? 1 : -1,
'following.inc': isAdditional ? 1 : 0,
'following.dec': isAdditional ? 0 : 1,
}, this.utilityService.toPuny(host));
}
public async updateFollowers(host: string, isAdditional: boolean): Promise<void> {
await this.commit({
'followers.total': isAdditional ? 1 : -1,
'followers.inc': isAdditional ? 1 : 0,
'followers.dec': isAdditional ? 0 : 1,
}, this.utilityService.toPuny(host));
}
public async updateDrive(file: DriveFile, isAdditional: boolean): Promise<void> {
const fileSizeKb = file.size / 1000;
await this.commit({
'drive.totalFiles': isAdditional ? 1 : -1,
'drive.incFiles': isAdditional ? 1 : 0,
'drive.incUsage': isAdditional ? fileSizeKb : 0,
'drive.decFiles': isAdditional ? 1 : 0,
'drive.decUsage': isAdditional ? fileSizeKb : 0,
}, file.userHost);
}
}

View file

@ -0,0 +1,58 @@
import { Injectable, Inject } from '@nestjs/common';
import { Not, IsNull, DataSource } from 'typeorm';
import { NotesRepository } from '@/models/index.js';
import type { Note } from '@/models/entities/Note.js';
import { AppLockService } from '@/core/AppLockService.js';
import { DI } from '@/di-symbols.js';
import Chart from '../core.js';
import { name, schema } from './entities/notes.js';
import type { KVs } from '../core.js';
/**
*
*/
// eslint-disable-next-line import/no-default-export
@Injectable()
export default class NotesChart extends Chart<typeof schema> {
constructor(
@Inject(DI.db)
private db: DataSource,
@Inject(DI.notesRepository)
private notesRepository: NotesRepository,
private appLockService: AppLockService,
) {
super(db, (k) => appLockService.getChartInsertLock(k), name, schema);
}
protected async tickMajor(): Promise<Partial<KVs<typeof schema>>> {
const [localCount, remoteCount] = await Promise.all([
this.notesRepository.countBy({ userHost: IsNull() }),
this.notesRepository.countBy({ userHost: Not(IsNull()) }),
]);
return {
'local.total': localCount,
'remote.total': remoteCount,
};
}
protected async tickMinor(): Promise<Partial<KVs<typeof schema>>> {
return {};
}
public async update(note: Note, isAdditional: boolean): Promise<void> {
const prefix = note.userHost === null ? 'local' : 'remote';
await this.commit({
[`${prefix}.total`]: isAdditional ? 1 : -1,
[`${prefix}.inc`]: isAdditional ? 1 : 0,
[`${prefix}.dec`]: isAdditional ? 0 : 1,
[`${prefix}.diffs.normal`]: note.replyId == null && note.renoteId == null ? (isAdditional ? 1 : -1) : 0,
[`${prefix}.diffs.renote`]: note.renoteId != null ? (isAdditional ? 1 : -1) : 0,
[`${prefix}.diffs.reply`]: note.replyId != null ? (isAdditional ? 1 : -1) : 0,
[`${prefix}.diffs.withFile`]: note.fileIds.length > 0 ? (isAdditional ? 1 : -1) : 0,
});
}
}

View file

@ -0,0 +1,58 @@
import { Injectable, Inject } from '@nestjs/common';
import { DataSource } from 'typeorm';
import { DriveFilesRepository } from '@/models/index.js';
import type { DriveFile } from '@/models/entities/DriveFile.js';
import { AppLockService } from '@/core/AppLockService.js';
import { DI } from '@/di-symbols.js';
import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
import Chart from '../core.js';
import { name, schema } from './entities/per-user-drive.js';
import type { KVs } from '../core.js';
/**
*
*/
// eslint-disable-next-line import/no-default-export
@Injectable()
export default class PerUserDriveChart extends Chart<typeof schema> {
constructor(
@Inject(DI.db)
private db: DataSource,
@Inject(DI.driveFilesRepository)
private driveFilesRepository: DriveFilesRepository,
private appLockService: AppLockService,
private driveFileEntityService: DriveFileEntityService,
) {
super(db, (k) => appLockService.getChartInsertLock(k), name, schema, true);
}
protected async tickMajor(group: string): Promise<Partial<KVs<typeof schema>>> {
const [count, size] = await Promise.all([
this.driveFilesRepository.countBy({ userId: group }),
this.driveFileEntityService.calcDriveUsageOf(group),
]);
return {
'totalCount': count,
'totalSize': size,
};
}
protected async tickMinor(): Promise<Partial<KVs<typeof schema>>> {
return {};
}
public async update(file: DriveFile, isAdditional: boolean): Promise<void> {
const fileSizeKb = file.size / 1000;
await this.commit({
'totalCount': isAdditional ? 1 : -1,
'totalSize': isAdditional ? fileSizeKb : -fileSizeKb,
'incCount': isAdditional ? 1 : 0,
'incSize': isAdditional ? fileSizeKb : 0,
'decCount': isAdditional ? 0 : 1,
'decSize': isAdditional ? 0 : fileSizeKb,
}, file.userId);
}
}

View file

@ -0,0 +1,71 @@
import { Injectable, Inject } from '@nestjs/common';
import { Not, IsNull, DataSource } from 'typeorm';
import type { User } from '@/models/entities/User.js';
import { AppLockService } from '@/core/AppLockService.js';
import { DI } from '@/di-symbols.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { FollowingsRepository } from '@/models/index.js';
import Chart from '../core.js';
import { name, schema } from './entities/per-user-following.js';
import type { KVs } from '../core.js';
/**
*
*/
// eslint-disable-next-line import/no-default-export
@Injectable()
export default class PerUserFollowingChart extends Chart<typeof schema> {
constructor(
@Inject(DI.db)
private db: DataSource,
@Inject(DI.followingsRepository)
private followingsRepository: FollowingsRepository,
private appLockService: AppLockService,
private userEntityService: UserEntityService,
) {
super(db, (k) => appLockService.getChartInsertLock(k), name, schema, true);
}
protected async tickMajor(group: string): Promise<Partial<KVs<typeof schema>>> {
const [
localFollowingsCount,
localFollowersCount,
remoteFollowingsCount,
remoteFollowersCount,
] = await Promise.all([
this.followingsRepository.countBy({ followerId: group, followeeHost: IsNull() }),
this.followingsRepository.countBy({ followeeId: group, followerHost: IsNull() }),
this.followingsRepository.countBy({ followerId: group, followeeHost: Not(IsNull()) }),
this.followingsRepository.countBy({ followeeId: group, followerHost: Not(IsNull()) }),
]);
return {
'local.followings.total': localFollowingsCount,
'local.followers.total': localFollowersCount,
'remote.followings.total': remoteFollowingsCount,
'remote.followers.total': remoteFollowersCount,
};
}
protected async tickMinor(): Promise<Partial<KVs<typeof schema>>> {
return {};
}
public async update(follower: { id: User['id']; host: User['host']; }, followee: { id: User['id']; host: User['host']; }, isFollow: boolean): Promise<void> {
const prefixFollower = this.userEntityService.isLocalUser(follower) ? 'local' : 'remote';
const prefixFollowee = this.userEntityService.isLocalUser(followee) ? 'local' : 'remote';
this.commit({
[`${prefixFollower}.followings.total`]: isFollow ? 1 : -1,
[`${prefixFollower}.followings.inc`]: isFollow ? 1 : 0,
[`${prefixFollower}.followings.dec`]: isFollow ? 0 : 1,
}, follower.id);
this.commit({
[`${prefixFollowee}.followers.total`]: isFollow ? 1 : -1,
[`${prefixFollowee}.followers.inc`]: isFollow ? 1 : 0,
[`${prefixFollowee}.followers.dec`]: isFollow ? 0 : 1,
}, followee.id);
}
}

View file

@ -0,0 +1,55 @@
import { Injectable, Inject } from '@nestjs/common';
import { DataSource } from 'typeorm';
import type { User } from '@/models/entities/User.js';
import type { Note } from '@/models/entities/Note.js';
import { AppLockService } from '@/core/AppLockService.js';
import { DI } from '@/di-symbols.js';
import { NotesRepository } from '@/models/index.js';
import Chart from '../core.js';
import { name, schema } from './entities/per-user-notes.js';
import type { KVs } from '../core.js';
/**
*
*/
// eslint-disable-next-line import/no-default-export
@Injectable()
export default class PerUserNotesChart extends Chart<typeof schema> {
constructor(
@Inject(DI.db)
private db: DataSource,
@Inject(DI.notesRepository)
private notesRepository: NotesRepository,
private appLockService: AppLockService,
) {
super(db, (k) => appLockService.getChartInsertLock(k), name, schema, true);
}
protected async tickMajor(group: string): Promise<Partial<KVs<typeof schema>>> {
const [count] = await Promise.all([
this.notesRepository.countBy({ userId: group }),
]);
return {
total: count,
};
}
protected async tickMinor(): Promise<Partial<KVs<typeof schema>>> {
return {};
}
public async update(user: { id: User['id'] }, note: Note, isAdditional: boolean): Promise<void> {
await this.commit({
'total': isAdditional ? 1 : -1,
'inc': isAdditional ? 1 : 0,
'dec': isAdditional ? 0 : 1,
'diffs.normal': note.replyId == null && note.renoteId == null ? (isAdditional ? 1 : -1) : 0,
'diffs.renote': note.renoteId != null ? (isAdditional ? 1 : -1) : 0,
'diffs.reply': note.replyId != null ? (isAdditional ? 1 : -1) : 0,
'diffs.withFile': note.fileIds.length > 0 ? (isAdditional ? 1 : -1) : 0,
}, user.id);
}
}

View file

@ -0,0 +1,42 @@
import { Injectable, Inject } from '@nestjs/common';
import { DataSource } from 'typeorm';
import type { User } from '@/models/entities/User.js';
import type { Note } from '@/models/entities/Note.js';
import { AppLockService } from '@/core/AppLockService.js';
import { DI } from '@/di-symbols.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
import Chart from '../core.js';
import { name, schema } from './entities/per-user-reactions.js';
import type { KVs } from '../core.js';
/**
*
*/
// eslint-disable-next-line import/no-default-export
@Injectable()
export default class PerUserReactionsChart extends Chart<typeof schema> {
constructor(
@Inject(DI.db)
private db: DataSource,
private appLockService: AppLockService,
private userEntityService: UserEntityService,
) {
super(db, (k) => appLockService.getChartInsertLock(k), name, schema, true);
}
protected async tickMajor(group: string): Promise<Partial<KVs<typeof schema>>> {
return {};
}
protected async tickMinor(): Promise<Partial<KVs<typeof schema>>> {
return {};
}
public async update(user: { id: User['id'], host: User['host'] }, note: Note): Promise<void> {
const prefix = this.userEntityService.isLocalUser(user) ? 'local' : 'remote';
this.commit({
[`${prefix}.count`]: 1,
}, note.userId);
}
}

View file

@ -0,0 +1,46 @@
import { Injectable, Inject } from '@nestjs/common';
import { DataSource } from 'typeorm';
import { AppLockService } from '@/core/AppLockService.js';
import { DI } from '@/di-symbols.js';
import Chart from '../core.js';
import { name, schema } from './entities/test-grouped.js';
import type { KVs } from '../core.js';
/**
* For testing
*/
// eslint-disable-next-line import/no-default-export
@Injectable()
export default class TestGroupedChart extends Chart<typeof schema> {
private total = {} as Record<string, number>;
constructor(
@Inject(DI.db)
private db: DataSource,
private appLockService: AppLockService,
) {
super(db, (k) => appLockService.getChartInsertLock(k), name, schema, true);
}
protected async tickMajor(group: string): Promise<Partial<KVs<typeof schema>>> {
return {
'foo.total': this.total[group],
};
}
protected async tickMinor(): Promise<Partial<KVs<typeof schema>>> {
return {};
}
public async increment(group: string): Promise<void> {
if (this.total[group] == null) this.total[group] = 0;
this.total[group]++;
await this.commit({
'foo.total': 1,
'foo.inc': 1,
}, group);
}
}

View file

@ -0,0 +1,43 @@
import { Injectable, Inject } from '@nestjs/common';
import { DataSource } from 'typeorm';
import { AppLockService } from '@/core/AppLockService.js';
import { DI } from '@/di-symbols.js';
import Chart from '../core.js';
import { name, schema } from './entities/test-intersection.js';
import type { KVs } from '../core.js';
/**
* For testing
*/
// eslint-disable-next-line import/no-default-export
@Injectable()
export default class TestIntersectionChart extends Chart<typeof schema> {
constructor(
@Inject(DI.db)
private db: DataSource,
private appLockService: AppLockService,
) {
super(db, (k) => appLockService.getChartInsertLock(k), name, schema);
}
protected async tickMajor(): Promise<Partial<KVs<typeof schema>>> {
return {};
}
protected async tickMinor(): Promise<Partial<KVs<typeof schema>>> {
return {};
}
public async addA(key: string): Promise<void> {
await this.commit({
a: [key],
});
}
public async addB(key: string): Promise<void> {
await this.commit({
b: [key],
});
}
}

View file

@ -0,0 +1,37 @@
import { Injectable, Inject } from '@nestjs/common';
import { DataSource } from 'typeorm';
import { AppLockService } from '@/core/AppLockService.js';
import { DI } from '@/di-symbols.js';
import Chart from '../core.js';
import { name, schema } from './entities/test-unique.js';
import type { KVs } from '../core.js';
/**
* For testing
*/
// eslint-disable-next-line import/no-default-export
@Injectable()
export default class TestUniqueChart extends Chart<typeof schema> {
constructor(
@Inject(DI.db)
private db: DataSource,
private appLockService: AppLockService,
) {
super(db, (k) => appLockService.getChartInsertLock(k), name, schema);
}
protected async tickMajor(): Promise<Partial<KVs<typeof schema>>> {
return {};
}
protected async tickMinor(): Promise<Partial<KVs<typeof schema>>> {
return {};
}
public async uniqueIncrement(key: string): Promise<void> {
await this.commit({
foo: [key],
});
}
}

View file

@ -0,0 +1,53 @@
import { Injectable, Inject } from '@nestjs/common';
import { DataSource } from 'typeorm';
import { AppLockService } from '@/core/AppLockService.js';
import { DI } from '@/di-symbols.js';
import Chart from '../core.js';
import { name, schema } from './entities/test.js';
import type { KVs } from '../core.js';
/**
* For testing
*/
// eslint-disable-next-line import/no-default-export
@Injectable()
export default class TestChart extends Chart<typeof schema> {
public total = 0; // publicにするのはテストのため
constructor(
@Inject(DI.db)
private db: DataSource,
private appLockService: AppLockService,
) {
super(db, (k) => appLockService.getChartInsertLock(k), name, schema);
}
protected async tickMajor(): Promise<Partial<KVs<typeof schema>>> {
return {
'foo.total': this.total,
};
}
protected async tickMinor(): Promise<Partial<KVs<typeof schema>>> {
return {};
}
public async increment(): Promise<void> {
this.total++;
await this.commit({
'foo.total': 1,
'foo.inc': 1,
});
}
public async decrement(): Promise<void> {
this.total--;
await this.commit({
'foo.total': -1,
'foo.dec': 1,
});
}
}

View file

@ -0,0 +1,56 @@
import { Injectable, Inject } from '@nestjs/common';
import { Not, IsNull, DataSource } from 'typeorm';
import type { User } from '@/models/entities/User.js';
import { AppLockService } from '@/core/AppLockService.js';
import { DI } from '@/di-symbols.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { UsersRepository } from '@/models/index.js';
import Chart from '../core.js';
import { name, schema } from './entities/users.js';
import type { KVs } from '../core.js';
/**
*
*/
// eslint-disable-next-line import/no-default-export
@Injectable()
export default class UsersChart extends Chart<typeof schema> {
constructor(
@Inject(DI.db)
private db: DataSource,
@Inject(DI.usersRepository)
private usersRepository: UsersRepository,
private appLockService: AppLockService,
private userEntityService: UserEntityService,
) {
super(db, (k) => appLockService.getChartInsertLock(k), name, schema);
}
protected async tickMajor(): Promise<Partial<KVs<typeof schema>>> {
const [localCount, remoteCount] = await Promise.all([
this.usersRepository.countBy({ host: IsNull() }),
this.usersRepository.countBy({ host: Not(IsNull()) }),
]);
return {
'local.total': localCount,
'remote.total': remoteCount,
};
}
protected async tickMinor(): Promise<Partial<KVs<typeof schema>>> {
return {};
}
public async update(user: { id: User['id'], host: User['host'] }, isAdditional: boolean): Promise<void> {
const prefix = this.userEntityService.isLocalUser(user) ? 'local' : 'remote';
await this.commit({
[`${prefix}.total`]: isAdditional ? 1 : -1,
[`${prefix}.inc`]: isAdditional ? 1 : 0,
[`${prefix}.dec`]: isAdditional ? 0 : 1,
});
}
}