なんかもうめっちゃ変えた
This commit is contained in:
parent
d9ab03f086
commit
b75184ec8e
946 changed files with 41219 additions and 28839 deletions
54
packages/backend/src/core/chart/charts/active-users.ts
Normal file
54
packages/backend/src/core/chart/charts/active-users.ts
Normal 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],
|
||||
});
|
||||
}
|
||||
}
|
||||
49
packages/backend/src/core/chart/charts/ap-request.ts
Normal file
49
packages/backend/src/core/chart/charts/ap-request.ts
Normal 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,
|
||||
});
|
||||
}
|
||||
}
|
||||
47
packages/backend/src/core/chart/charts/drive.ts
Normal file
47
packages/backend/src/core/chart/charts/drive.ts
Normal 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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
@ -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);
|
||||
16
packages/backend/src/core/chart/charts/entities/drive.ts
Normal file
16
packages/backend/src/core/chart/charts/entities/drive.ts
Normal 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);
|
||||
|
|
@ -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);
|
||||
10
packages/backend/src/core/chart/charts/entities/hashtag.ts
Normal file
10
packages/backend/src/core/chart/charts/entities/hashtag.ts
Normal 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);
|
||||
32
packages/backend/src/core/chart/charts/entities/instance.ts
Normal file
32
packages/backend/src/core/chart/charts/entities/instance.ts
Normal 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);
|
||||
22
packages/backend/src/core/chart/charts/entities/notes.ts
Normal file
22
packages/backend/src/core/chart/charts/entities/notes.ts
Normal 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);
|
||||
|
|
@ -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);
|
||||
|
|
@ -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);
|
||||
|
|
@ -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);
|
||||
|
|
@ -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);
|
||||
|
|
@ -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);
|
||||
|
|
@ -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);
|
||||
|
|
@ -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);
|
||||
11
packages/backend/src/core/chart/charts/entities/test.ts
Normal file
11
packages/backend/src/core/chart/charts/entities/test.ts
Normal 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);
|
||||
14
packages/backend/src/core/chart/charts/entities/users.ts
Normal file
14
packages/backend/src/core/chart/charts/entities/users.ts
Normal 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);
|
||||
121
packages/backend/src/core/chart/charts/federation.ts
Normal file
121
packages/backend/src/core/chart/charts/federation.ts
Normal 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],
|
||||
});
|
||||
}
|
||||
}
|
||||
41
packages/backend/src/core/chart/charts/hashtag.ts
Normal file
41
packages/backend/src/core/chart/charts/hashtag.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
127
packages/backend/src/core/chart/charts/instance.ts
Normal file
127
packages/backend/src/core/chart/charts/instance.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
58
packages/backend/src/core/chart/charts/notes.ts
Normal file
58
packages/backend/src/core/chart/charts/notes.ts
Normal 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,
|
||||
});
|
||||
}
|
||||
}
|
||||
58
packages/backend/src/core/chart/charts/per-user-drive.ts
Normal file
58
packages/backend/src/core/chart/charts/per-user-drive.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
71
packages/backend/src/core/chart/charts/per-user-following.ts
Normal file
71
packages/backend/src/core/chart/charts/per-user-following.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
55
packages/backend/src/core/chart/charts/per-user-notes.ts
Normal file
55
packages/backend/src/core/chart/charts/per-user-notes.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
42
packages/backend/src/core/chart/charts/per-user-reactions.ts
Normal file
42
packages/backend/src/core/chart/charts/per-user-reactions.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
46
packages/backend/src/core/chart/charts/test-grouped.ts
Normal file
46
packages/backend/src/core/chart/charts/test-grouped.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
43
packages/backend/src/core/chart/charts/test-intersection.ts
Normal file
43
packages/backend/src/core/chart/charts/test-intersection.ts
Normal 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],
|
||||
});
|
||||
}
|
||||
}
|
||||
37
packages/backend/src/core/chart/charts/test-unique.ts
Normal file
37
packages/backend/src/core/chart/charts/test-unique.ts
Normal 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],
|
||||
});
|
||||
}
|
||||
}
|
||||
53
packages/backend/src/core/chart/charts/test.ts
Normal file
53
packages/backend/src/core/chart/charts/test.ts
Normal 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,
|
||||
});
|
||||
}
|
||||
}
|
||||
56
packages/backend/src/core/chart/charts/users.ts
Normal file
56
packages/backend/src/core/chart/charts/users.ts
Normal 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,
|
||||
});
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue