Introduce per-instance chart (#4183)
* Introduce per-instance chart * Implement chart view in client * Handle note deleting * More chart srcs * Add drive stats * Improve drive stats * Fix bug * Add icon
This commit is contained in:
		
							parent
							
								
									f35688bab8
								
							
						
					
					
						commit
						56275bcfcb
					
				
					 17 changed files with 746 additions and 13 deletions
				
			
		| 
						 | 
				
			
			@ -40,6 +40,29 @@
 | 
			
		|||
					<span>{{ $t('latest-request-received-at') }}</span>
 | 
			
		||||
				</ui-input>
 | 
			
		||||
				<ui-switch v-model="instance.isBlocked" @change="updateInstance()">{{ $t('block') }}</ui-switch>
 | 
			
		||||
				<details>
 | 
			
		||||
					<summary>{{ $t('charts') }}</summary>
 | 
			
		||||
					<ui-horizon-group inputs>
 | 
			
		||||
						<ui-select v-model="chartSrc">
 | 
			
		||||
							<option value="requests">{{ $t('chart-srcs.requests') }}</option>
 | 
			
		||||
							<option value="users">{{ $t('chart-srcs.users') }}</option>
 | 
			
		||||
							<option value="users-total">{{ $t('chart-srcs.users-total') }}</option>
 | 
			
		||||
							<option value="notes">{{ $t('chart-srcs.notes') }}</option>
 | 
			
		||||
							<option value="notes-total">{{ $t('chart-srcs.notes-total') }}</option>
 | 
			
		||||
							<option value="ff">{{ $t('chart-srcs.ff') }}</option>
 | 
			
		||||
							<option value="ff-total">{{ $t('chart-srcs.ff-total') }}</option>
 | 
			
		||||
							<option value="drive-usage">{{ $t('chart-srcs.drive-usage') }}</option>
 | 
			
		||||
							<option value="drive-usage-total">{{ $t('chart-srcs.drive-usage-total') }}</option>
 | 
			
		||||
							<option value="drive-files">{{ $t('chart-srcs.drive-files') }}</option>
 | 
			
		||||
							<option value="drive-files-total">{{ $t('chart-srcs.drive-files-total') }}</option>
 | 
			
		||||
						</ui-select>
 | 
			
		||||
						<ui-select v-model="chartSpan">
 | 
			
		||||
							<option value="hour">{{ $t('chart-spans.hour') }}</option>
 | 
			
		||||
							<option value="day">{{ $t('chart-spans.day') }}</option>
 | 
			
		||||
						</ui-select>
 | 
			
		||||
					</ui-horizon-group>
 | 
			
		||||
					<div ref="chart"></div>
 | 
			
		||||
				</details>
 | 
			
		||||
				<details>
 | 
			
		||||
					<summary>{{ $t('remove-all-following') }}</summary>
 | 
			
		||||
					<ui-button @click="removeAllFollowing()" style="margin-top: 16px;"><fa :icon="faMinusCircle"/> {{ $t('remove-all-following') }}</ui-button>
 | 
			
		||||
| 
						 | 
				
			
			@ -50,7 +73,7 @@
 | 
			
		|||
	</ui-card>
 | 
			
		||||
 | 
			
		||||
	<ui-card>
 | 
			
		||||
		<div slot="title"><fa :icon="faUsers"/> {{ $t('instances') }}</div>
 | 
			
		||||
		<div slot="title"><fa :icon="faServer"/> {{ $t('instances') }}</div>
 | 
			
		||||
		<section class="fit-top">
 | 
			
		||||
			<ui-horizon-group inputs>
 | 
			
		||||
				<ui-select v-model="sort">
 | 
			
		||||
| 
						 | 
				
			
			@ -65,6 +88,10 @@
 | 
			
		|||
					<option value="+following">{{ $t('sorts.followingDesc') }}</option>
 | 
			
		||||
					<option value="-followers">{{ $t('sorts.followersAsc') }}</option>
 | 
			
		||||
					<option value="+followers">{{ $t('sorts.followersDesc') }}</option>
 | 
			
		||||
					<option value="-driveUsage">{{ $t('sorts.driveUsageAsc') }}</option>
 | 
			
		||||
					<option value="+driveUsage">{{ $t('sorts.driveUsageDesc') }}</option>
 | 
			
		||||
					<option value="-driveFiles">{{ $t('sorts.driveFilesAsc') }}</option>
 | 
			
		||||
					<option value="+driveFiles">{{ $t('sorts.driveFilesDesc') }}</option>
 | 
			
		||||
				</ui-select>
 | 
			
		||||
				<ui-select v-model="state">
 | 
			
		||||
					<span slot="label">{{ $t('state') }}</span>
 | 
			
		||||
| 
						 | 
				
			
			@ -101,7 +128,13 @@
 | 
			
		|||
<script lang="ts">
 | 
			
		||||
import Vue from 'vue';
 | 
			
		||||
import i18n from '../../i18n';
 | 
			
		||||
import { faGlobe, faTerminal, faSearch, faMinusCircle } from '@fortawesome/free-solid-svg-icons';
 | 
			
		||||
import { faGlobe, faTerminal, faSearch, faMinusCircle, faServer } from '@fortawesome/free-solid-svg-icons';
 | 
			
		||||
import ApexCharts from 'apexcharts';
 | 
			
		||||
import * as tinycolor from 'tinycolor2';
 | 
			
		||||
 | 
			
		||||
const chartLimit = 90;
 | 
			
		||||
const sum = (...arr) => arr.reduce((r, a) => r.map((b, i) => a[i] + b));
 | 
			
		||||
const negate = arr => arr.map(x => -x);
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
	i18n: i18n('admin/views/federation.vue'),
 | 
			
		||||
| 
						 | 
				
			
			@ -114,10 +147,42 @@ export default Vue.extend({
 | 
			
		|||
			state: 'all',
 | 
			
		||||
			limit: 50,
 | 
			
		||||
			instances: [],
 | 
			
		||||
			faGlobe, faTerminal, faSearch, faMinusCircle
 | 
			
		||||
			chart: null,
 | 
			
		||||
			chartSrc: 'requests',
 | 
			
		||||
			chartSpan: 'hour',
 | 
			
		||||
			chartInstance: null,
 | 
			
		||||
			faGlobe, faTerminal, faSearch, faMinusCircle, faServer
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	computed: {
 | 
			
		||||
		data(): any {
 | 
			
		||||
			if (this.chart == null) return null;
 | 
			
		||||
			switch (this.chartSrc) {
 | 
			
		||||
				case 'requests': return this.requestsChart();
 | 
			
		||||
				case 'users': return this.usersChart(false);
 | 
			
		||||
				case 'users-total': return this.usersChart(true);
 | 
			
		||||
				case 'notes': return this.notesChart(false);
 | 
			
		||||
				case 'notes-total': return this.notesChart(true);
 | 
			
		||||
				case 'ff': return this.ffChart(false);
 | 
			
		||||
				case 'ff-total': return this.ffChart(true);
 | 
			
		||||
				case 'drive-usage': return this.driveUsageChart(false);
 | 
			
		||||
				case 'drive-usage-total': return this.driveUsageChart(true);
 | 
			
		||||
				case 'drive-files': return this.driveFilesChart(false);
 | 
			
		||||
				case 'drive-files-total': return this.driveFilesChart(true);
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		stats(): any[] {
 | 
			
		||||
			const stats =
 | 
			
		||||
				this.chartSpan == 'day' ? this.chart.perDay :
 | 
			
		||||
				this.chartSpan == 'hour' ? this.chart.perHour :
 | 
			
		||||
				null;
 | 
			
		||||
 | 
			
		||||
			return stats;
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	watch: {
 | 
			
		||||
		sort() {
 | 
			
		||||
			this.fetchInstances();
 | 
			
		||||
| 
						 | 
				
			
			@ -126,12 +191,42 @@ export default Vue.extend({
 | 
			
		|||
		state() {
 | 
			
		||||
			this.fetchInstances();
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		async instance() {
 | 
			
		||||
			this.now = new Date();
 | 
			
		||||
 | 
			
		||||
			const [perHour, perDay] = await Promise.all([
 | 
			
		||||
				this.$root.api('charts/instance', { host: this.instance.host, limit: chartLimit, span: 'hour' }),
 | 
			
		||||
				this.$root.api('charts/instance', { host: this.instance.host, limit: chartLimit, span: 'day' }),
 | 
			
		||||
			]);
 | 
			
		||||
 | 
			
		||||
			const chart = {
 | 
			
		||||
				perHour: perHour,
 | 
			
		||||
				perDay: perDay
 | 
			
		||||
			};
 | 
			
		||||
 | 
			
		||||
			this.chart = chart;
 | 
			
		||||
 | 
			
		||||
			this.renderChart();
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		chartSrc() {
 | 
			
		||||
			this.renderChart();
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		chartSpan() {
 | 
			
		||||
			this.renderChart();
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	mounted() {
 | 
			
		||||
		this.fetchInstances();
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	beforeDestroy() {
 | 
			
		||||
		this.chartInstance.destroy();
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	methods: {
 | 
			
		||||
		showInstance() {
 | 
			
		||||
			this.$root.api('federation/show-instance', {
 | 
			
		||||
| 
						 | 
				
			
			@ -177,6 +272,180 @@ export default Vue.extend({
 | 
			
		|||
				isBlocked: this.instance.isBlocked,
 | 
			
		||||
			});
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		setSrc(src) {
 | 
			
		||||
			this.chartSrc = src;
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		renderChart() {
 | 
			
		||||
			if (this.chartInstance) {
 | 
			
		||||
				this.chartInstance.destroy();
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			this.chartInstance = new ApexCharts(this.$refs.chart, {
 | 
			
		||||
				chart: {
 | 
			
		||||
					type: 'area',
 | 
			
		||||
					height: 300,
 | 
			
		||||
					animations: {
 | 
			
		||||
						dynamicAnimation: {
 | 
			
		||||
							enabled: false
 | 
			
		||||
						}
 | 
			
		||||
					},
 | 
			
		||||
					toolbar: {
 | 
			
		||||
						show: false
 | 
			
		||||
					},
 | 
			
		||||
					zoom: {
 | 
			
		||||
						enabled: false
 | 
			
		||||
					}
 | 
			
		||||
				},
 | 
			
		||||
				dataLabels: {
 | 
			
		||||
					enabled: false
 | 
			
		||||
				},
 | 
			
		||||
				grid: {
 | 
			
		||||
					clipMarkers: false,
 | 
			
		||||
					borderColor: 'rgba(0, 0, 0, 0.1)'
 | 
			
		||||
				},
 | 
			
		||||
				stroke: {
 | 
			
		||||
					curve: 'straight',
 | 
			
		||||
					width: 2
 | 
			
		||||
				},
 | 
			
		||||
				legend: {
 | 
			
		||||
					labels: {
 | 
			
		||||
						colors: tinycolor(getComputedStyle(document.documentElement).getPropertyValue('--text')).toRgbString()
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
				xaxis: {
 | 
			
		||||
					type: 'datetime',
 | 
			
		||||
					labels: {
 | 
			
		||||
						style: {
 | 
			
		||||
							colors: tinycolor(getComputedStyle(document.documentElement).getPropertyValue('--text')).toRgbString()
 | 
			
		||||
						}
 | 
			
		||||
					},
 | 
			
		||||
					axisBorder: {
 | 
			
		||||
						color: 'rgba(0, 0, 0, 0.1)'
 | 
			
		||||
					},
 | 
			
		||||
					axisTicks: {
 | 
			
		||||
						color: 'rgba(0, 0, 0, 0.1)'
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
				yaxis: {
 | 
			
		||||
					labels: {
 | 
			
		||||
						formatter: this.data.bytes ? v => Vue.filter('bytes')(v, 0) : v => Vue.filter('number')(v),
 | 
			
		||||
						style: {
 | 
			
		||||
							color: tinycolor(getComputedStyle(document.documentElement).getPropertyValue('--text')).toRgbString()
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
				},
 | 
			
		||||
				series: this.data.series
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
			this.chartInstance.render();
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		getDate(i: number) {
 | 
			
		||||
			const y = this.now.getFullYear();
 | 
			
		||||
			const m = this.now.getMonth();
 | 
			
		||||
			const d = this.now.getDate();
 | 
			
		||||
			const h = this.now.getHours();
 | 
			
		||||
 | 
			
		||||
			return (
 | 
			
		||||
				this.chartSpan == 'day' ? new Date(y, m, d - i) :
 | 
			
		||||
				this.chartSpan == 'hour' ? new Date(y, m, d, h - i) :
 | 
			
		||||
				null
 | 
			
		||||
			);
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		format(arr) {
 | 
			
		||||
			return arr.map((v, i) => ({ x: this.getDate(i).getTime(), y: v }));
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		requestsChart(): any {
 | 
			
		||||
			return {
 | 
			
		||||
				series: [{
 | 
			
		||||
					name: 'Incoming',
 | 
			
		||||
					data: this.format(this.stats.requests.received)
 | 
			
		||||
				}, {
 | 
			
		||||
					name: 'Outgoing (succeeded)',
 | 
			
		||||
					data: this.format(this.stats.requests.succeeded)
 | 
			
		||||
				}, {
 | 
			
		||||
					name: 'Outgoing (failed)',
 | 
			
		||||
					data: this.format(this.stats.requests.failed)
 | 
			
		||||
				}]
 | 
			
		||||
			};
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		usersChart(total: boolean): any {
 | 
			
		||||
			return {
 | 
			
		||||
				series: [{
 | 
			
		||||
					name: 'Users',
 | 
			
		||||
					type: 'area',
 | 
			
		||||
					data: this.format(total
 | 
			
		||||
						? this.stats.users.total
 | 
			
		||||
						: sum(this.stats.users.inc, negate(this.stats.users.dec))
 | 
			
		||||
					)
 | 
			
		||||
				}]
 | 
			
		||||
			};
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		notesChart(total: boolean): any {
 | 
			
		||||
			return {
 | 
			
		||||
				series: [{
 | 
			
		||||
					name: 'Notes',
 | 
			
		||||
					type: 'area',
 | 
			
		||||
					data: this.format(total
 | 
			
		||||
						? this.stats.notes.total
 | 
			
		||||
						: sum(this.stats.notes.inc, negate(this.stats.notes.dec))
 | 
			
		||||
					)
 | 
			
		||||
				}]
 | 
			
		||||
			};
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		ffChart(total: boolean): any {
 | 
			
		||||
			return {
 | 
			
		||||
				series: [{
 | 
			
		||||
					name: 'Following',
 | 
			
		||||
					type: 'area',
 | 
			
		||||
					data: this.format(total
 | 
			
		||||
						? this.stats.following.total
 | 
			
		||||
						: sum(this.stats.following.inc, negate(this.stats.following.dec))
 | 
			
		||||
					)
 | 
			
		||||
				}, {
 | 
			
		||||
					name: 'Followers',
 | 
			
		||||
					type: 'area',
 | 
			
		||||
					data: this.format(total
 | 
			
		||||
						? this.stats.followers.total
 | 
			
		||||
						: sum(this.stats.followers.inc, negate(this.stats.followers.dec))
 | 
			
		||||
					)
 | 
			
		||||
				}]
 | 
			
		||||
			};
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		driveUsageChart(total: boolean): any {
 | 
			
		||||
			return {
 | 
			
		||||
				bytes: true,
 | 
			
		||||
				series: [{
 | 
			
		||||
					name: 'Drive usage',
 | 
			
		||||
					type: 'area',
 | 
			
		||||
					data: this.format(total
 | 
			
		||||
						? this.stats.drive.totalUsage
 | 
			
		||||
						: sum(this.stats.drive.incUsage, negate(this.stats.drive.decUsage))
 | 
			
		||||
					)
 | 
			
		||||
				}]
 | 
			
		||||
			};
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		driveFilesChart(total: boolean): any {
 | 
			
		||||
			return {
 | 
			
		||||
				series: [{
 | 
			
		||||
					name: 'Drive files',
 | 
			
		||||
					type: 'area',
 | 
			
		||||
					data: this.format(total
 | 
			
		||||
						? this.stats.drive.totalFiles
 | 
			
		||||
						: sum(this.stats.drive.incFiles, negate(this.stats.drive.decFiles))
 | 
			
		||||
					)
 | 
			
		||||
				}]
 | 
			
		||||
			};
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -11,6 +11,7 @@ DriveFile.createIndex('md5');
 | 
			
		|||
DriveFile.createIndex('metadata.uri');
 | 
			
		||||
DriveFile.createIndex('metadata.userId');
 | 
			
		||||
DriveFile.createIndex('metadata.folderId');
 | 
			
		||||
DriveFile.createIndex('metadata._user.host');
 | 
			
		||||
export default DriveFile;
 | 
			
		||||
 | 
			
		||||
export const DriveFileChunk = monkDb.get('driveFiles.chunks');
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -43,6 +43,16 @@ export interface IInstance {
 | 
			
		|||
	 */
 | 
			
		||||
	followersCount: number;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * ドライブ使用量
 | 
			
		||||
	 */
 | 
			
		||||
	driveUsage: number;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * ドライブのファイル数
 | 
			
		||||
	 */
 | 
			
		||||
	driveFiles: number;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * 直近のリクエスト送信日時
 | 
			
		||||
	 */
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -17,6 +17,7 @@ const User = db.get<IUser>('users');
 | 
			
		|||
 | 
			
		||||
User.createIndex('username');
 | 
			
		||||
User.createIndex('usernameLower');
 | 
			
		||||
User.createIndex('host');
 | 
			
		||||
User.createIndex(['username', 'host'], { unique: true });
 | 
			
		||||
User.createIndex(['usernameLower', 'host'], { unique: true });
 | 
			
		||||
User.createIndex('token', { sparse: true, unique: true });
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,6 +4,7 @@ import request from '../../../remote/activitypub/request';
 | 
			
		|||
import { queueLogger } from '../../logger';
 | 
			
		||||
import { registerOrFetchInstanceDoc } from '../../../services/register-or-fetch-instance-doc';
 | 
			
		||||
import Instance from '../../../models/instance';
 | 
			
		||||
import instanceChart from '../../../services/chart/instance';
 | 
			
		||||
 | 
			
		||||
export default async (job: bq.Job, done: any): Promise<void> => {
 | 
			
		||||
	const { host } = new URL(job.data.to);
 | 
			
		||||
| 
						 | 
				
			
			@ -19,6 +20,8 @@ export default async (job: bq.Job, done: any): Promise<void> => {
 | 
			
		|||
					latestStatus: 200
 | 
			
		||||
				}
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
			instanceChart.requestSent(i.host, true);
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		done();
 | 
			
		||||
| 
						 | 
				
			
			@ -31,6 +34,8 @@ export default async (job: bq.Job, done: any): Promise<void> => {
 | 
			
		|||
					latestStatus: res != null && res.hasOwnProperty('statusCode') ? res.statusCode : null
 | 
			
		||||
				}
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
			instanceChart.requestSent(i.host, false);
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		if (res != null && res.hasOwnProperty('statusCode')) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -10,6 +10,7 @@ import { publishApLogStream } from '../../../services/stream';
 | 
			
		|||
import Logger from '../../../misc/logger';
 | 
			
		||||
import { registerOrFetchInstanceDoc } from '../../../services/register-or-fetch-instance-doc';
 | 
			
		||||
import Instance from '../../../models/instance';
 | 
			
		||||
import instanceChart from '../../../services/chart/instance';
 | 
			
		||||
 | 
			
		||||
const logger = new Logger('inbox');
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -128,6 +129,8 @@ export default async (job: bq.Job, done: any): Promise<void> => {
 | 
			
		|||
				latestRequestReceivedAt: new Date()
 | 
			
		||||
			}
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		instanceChart.requestReceived(i.host);
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	// アクティビティを処理
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -10,6 +10,7 @@ import { IDriveFile } from '../../../models/drive-file';
 | 
			
		|||
import Meta from '../../../models/meta';
 | 
			
		||||
import { fromHtml } from '../../../mfm/fromHtml';
 | 
			
		||||
import usersChart from '../../../services/chart/users';
 | 
			
		||||
import instanceChart from '../../../services/chart/instance';
 | 
			
		||||
import { URL } from 'url';
 | 
			
		||||
import { resolveNote, extractEmojis } from './note';
 | 
			
		||||
import { registerOrFetchInstanceDoc } from '../../../services/register-or-fetch-instance-doc';
 | 
			
		||||
| 
						 | 
				
			
			@ -195,8 +196,7 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise<IU
 | 
			
		|||
			}
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		// TODO
 | 
			
		||||
		//perInstanceChart.newUser();
 | 
			
		||||
		instanceChart.newUser(i.host);
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	//#region Increment users count
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										42
									
								
								src/server/api/endpoints/charts/instance.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								src/server/api/endpoints/charts/instance.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,42 @@
 | 
			
		|||
import $ from 'cafy';
 | 
			
		||||
import define from '../../define';
 | 
			
		||||
import instanceChart from '../../../../services/chart/instance';
 | 
			
		||||
 | 
			
		||||
export const meta = {
 | 
			
		||||
	stability: 'stable',
 | 
			
		||||
 | 
			
		||||
	desc: {
 | 
			
		||||
		'ja-JP': 'インスタンスごとのチャートを取得します。'
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	params: {
 | 
			
		||||
		span: {
 | 
			
		||||
			validator: $.str.or(['day', 'hour']),
 | 
			
		||||
			desc: {
 | 
			
		||||
				'ja-JP': '集計のスパン (day または hour)'
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		limit: {
 | 
			
		||||
			validator: $.num.optional.range(1, 500),
 | 
			
		||||
			default: 30,
 | 
			
		||||
			desc: {
 | 
			
		||||
				'ja-JP': '最大数。例えば 30 を指定したとすると、スパンが"day"の場合は30日分のデータが、スパンが"hour"の場合は30時間分のデータが返ります。'
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		host: {
 | 
			
		||||
			validator: $.str,
 | 
			
		||||
			desc: {
 | 
			
		||||
				'ja-JP': '対象のインスタンスのホスト',
 | 
			
		||||
				'en-US': 'Target instance host'
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default define(meta, (ps) => new Promise(async (res, rej) => {
 | 
			
		||||
	const stats = await instanceChart.getChart(ps.span as any, ps.limit, ps.host);
 | 
			
		||||
 | 
			
		||||
	res(stats);
 | 
			
		||||
}));
 | 
			
		||||
| 
						 | 
				
			
			@ -70,6 +70,22 @@ export default define(meta, (ps, me) => new Promise(async (res, rej) => {
 | 
			
		|||
			sort = {
 | 
			
		||||
				caughtAt: 1
 | 
			
		||||
			};
 | 
			
		||||
		} else if (ps.sort == '+driveUsage') {
 | 
			
		||||
			sort = {
 | 
			
		||||
				driveUsage: -1
 | 
			
		||||
			};
 | 
			
		||||
		} else if (ps.sort == '-driveUsage') {
 | 
			
		||||
			sort = {
 | 
			
		||||
				driveUsage: 1
 | 
			
		||||
			};
 | 
			
		||||
		} else if (ps.sort == '+driveFiles') {
 | 
			
		||||
			sort = {
 | 
			
		||||
				driveFiles: -1
 | 
			
		||||
			};
 | 
			
		||||
		} else if (ps.sort == '-driveFiles') {
 | 
			
		||||
			sort = {
 | 
			
		||||
				driveFiles: 1
 | 
			
		||||
			};
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		sort = {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										302
									
								
								src/services/chart/instance.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										302
									
								
								src/services/chart/instance.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,302 @@
 | 
			
		|||
import autobind from 'autobind-decorator';
 | 
			
		||||
import Chart, { Obj } from '.';
 | 
			
		||||
import User from '../../models/user';
 | 
			
		||||
import Note from '../../models/note';
 | 
			
		||||
import Following from '../../models/following';
 | 
			
		||||
import DriveFile, { IDriveFile } from '../../models/drive-file';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * インスタンスごとのチャート
 | 
			
		||||
 */
 | 
			
		||||
type InstanceLog = {
 | 
			
		||||
	requests: {
 | 
			
		||||
		/**
 | 
			
		||||
		 * 失敗したリクエスト数
 | 
			
		||||
		 */
 | 
			
		||||
		failed: number;
 | 
			
		||||
 | 
			
		||||
		/**
 | 
			
		||||
		 * 成功したリクエスト数
 | 
			
		||||
		 */
 | 
			
		||||
		succeeded: number;
 | 
			
		||||
 | 
			
		||||
		/**
 | 
			
		||||
		 * 受信したリクエスト数
 | 
			
		||||
		 */
 | 
			
		||||
		received: number;
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	notes: {
 | 
			
		||||
		/**
 | 
			
		||||
		 * 集計期間時点での、全投稿数
 | 
			
		||||
		 */
 | 
			
		||||
		total: number;
 | 
			
		||||
 | 
			
		||||
		/**
 | 
			
		||||
		 * 増加した投稿数
 | 
			
		||||
		 */
 | 
			
		||||
		inc: number;
 | 
			
		||||
 | 
			
		||||
		/**
 | 
			
		||||
		 * 減少した投稿数
 | 
			
		||||
		 */
 | 
			
		||||
		dec: number;
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	users: {
 | 
			
		||||
		/**
 | 
			
		||||
		 * 集計期間時点での、全ユーザー数
 | 
			
		||||
		 */
 | 
			
		||||
		total: number;
 | 
			
		||||
 | 
			
		||||
		/**
 | 
			
		||||
		 * 増加したユーザー数
 | 
			
		||||
		 */
 | 
			
		||||
		inc: number;
 | 
			
		||||
 | 
			
		||||
		/**
 | 
			
		||||
		 * 減少したユーザー数
 | 
			
		||||
		 */
 | 
			
		||||
		dec: number;
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	following: {
 | 
			
		||||
		/**
 | 
			
		||||
		 * 集計期間時点での、全フォロー数
 | 
			
		||||
		 */
 | 
			
		||||
		total: number;
 | 
			
		||||
 | 
			
		||||
		/**
 | 
			
		||||
		 * 増加したフォロー数
 | 
			
		||||
		 */
 | 
			
		||||
		inc: number;
 | 
			
		||||
 | 
			
		||||
		/**
 | 
			
		||||
		 * 減少したフォロー数
 | 
			
		||||
		 */
 | 
			
		||||
		dec: number;
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	followers: {
 | 
			
		||||
		/**
 | 
			
		||||
		 * 集計期間時点での、全フォロワー数
 | 
			
		||||
		 */
 | 
			
		||||
		total: number;
 | 
			
		||||
 | 
			
		||||
		/**
 | 
			
		||||
		 * 増加したフォロワー数
 | 
			
		||||
		 */
 | 
			
		||||
		inc: number;
 | 
			
		||||
 | 
			
		||||
		/**
 | 
			
		||||
		 * 減少したフォロワー数
 | 
			
		||||
		 */
 | 
			
		||||
		dec: number;
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	drive: {
 | 
			
		||||
		/**
 | 
			
		||||
		 * 集計期間時点での、全ドライブファイル数
 | 
			
		||||
		 */
 | 
			
		||||
		totalFiles: number;
 | 
			
		||||
 | 
			
		||||
		/**
 | 
			
		||||
		 * 集計期間時点での、全ドライブファイルの合計サイズ
 | 
			
		||||
		 */
 | 
			
		||||
		totalUsage: number;
 | 
			
		||||
 | 
			
		||||
		/**
 | 
			
		||||
		 * 増加したドライブファイル数
 | 
			
		||||
		 */
 | 
			
		||||
		incFiles: number;
 | 
			
		||||
 | 
			
		||||
		/**
 | 
			
		||||
		 * 増加したドライブ使用量
 | 
			
		||||
		 */
 | 
			
		||||
		incUsage: number;
 | 
			
		||||
 | 
			
		||||
		/**
 | 
			
		||||
		 * 減少したドライブファイル数
 | 
			
		||||
		 */
 | 
			
		||||
		decFiles: number;
 | 
			
		||||
 | 
			
		||||
		/**
 | 
			
		||||
		 * 減少したドライブ使用量
 | 
			
		||||
		 */
 | 
			
		||||
		decUsage: number;
 | 
			
		||||
	};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class InstanceChart extends Chart<InstanceLog> {
 | 
			
		||||
	constructor() {
 | 
			
		||||
		super('instance', true);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@autobind
 | 
			
		||||
	protected async getTemplate(init: boolean, latest?: InstanceLog, group?: any): Promise<InstanceLog> {
 | 
			
		||||
		const calcUsage = () => DriveFile
 | 
			
		||||
			.aggregate([{
 | 
			
		||||
				$match: {
 | 
			
		||||
					'metadata._user.host': group,
 | 
			
		||||
					'metadata.deletedAt': { $exists: false }
 | 
			
		||||
				}
 | 
			
		||||
			}, {
 | 
			
		||||
				$project: {
 | 
			
		||||
					length: true
 | 
			
		||||
				}
 | 
			
		||||
			}, {
 | 
			
		||||
				$group: {
 | 
			
		||||
					_id: null,
 | 
			
		||||
					usage: { $sum: '$length' }
 | 
			
		||||
				}
 | 
			
		||||
			}])
 | 
			
		||||
			.then(res => res.length > 0 ? res[0].usage : 0);
 | 
			
		||||
 | 
			
		||||
		const [
 | 
			
		||||
			notesCount,
 | 
			
		||||
			usersCount,
 | 
			
		||||
			followingCount,
 | 
			
		||||
			followersCount,
 | 
			
		||||
			driveFiles,
 | 
			
		||||
			driveUsage,
 | 
			
		||||
		] = init ? await Promise.all([
 | 
			
		||||
			Note.count({ '_user.host': group }),
 | 
			
		||||
			User.count({ host: group }),
 | 
			
		||||
			Following.count({ '_follower.host': group }),
 | 
			
		||||
			Following.count({ '_followee.host': group }),
 | 
			
		||||
			DriveFile.count({ 'metadata._user.host': group }),
 | 
			
		||||
			calcUsage(),
 | 
			
		||||
		]) : [
 | 
			
		||||
			latest ? latest.notes.total : 0,
 | 
			
		||||
			latest ? latest.users.total : 0,
 | 
			
		||||
			latest ? latest.following.total : 0,
 | 
			
		||||
			latest ? latest.followers.total : 0,
 | 
			
		||||
			latest ? latest.drive.totalFiles : 0,
 | 
			
		||||
			latest ? latest.drive.totalUsage : 0,
 | 
			
		||||
		];
 | 
			
		||||
 | 
			
		||||
		return {
 | 
			
		||||
			requests: {
 | 
			
		||||
				failed: 0,
 | 
			
		||||
				succeeded: 0,
 | 
			
		||||
				received: 0
 | 
			
		||||
			},
 | 
			
		||||
			notes: {
 | 
			
		||||
				total: notesCount,
 | 
			
		||||
				inc: 0,
 | 
			
		||||
				dec: 0
 | 
			
		||||
			},
 | 
			
		||||
			users: {
 | 
			
		||||
				total: usersCount,
 | 
			
		||||
				inc: 0,
 | 
			
		||||
				dec: 0
 | 
			
		||||
			},
 | 
			
		||||
			following: {
 | 
			
		||||
				total: followingCount,
 | 
			
		||||
				inc: 0,
 | 
			
		||||
				dec: 0
 | 
			
		||||
			},
 | 
			
		||||
			followers: {
 | 
			
		||||
				total: followersCount,
 | 
			
		||||
				inc: 0,
 | 
			
		||||
				dec: 0
 | 
			
		||||
			},
 | 
			
		||||
			drive: {
 | 
			
		||||
				totalFiles: driveFiles,
 | 
			
		||||
				totalUsage: driveUsage,
 | 
			
		||||
				incFiles: 0,
 | 
			
		||||
				incUsage: 0,
 | 
			
		||||
				decFiles: 0,
 | 
			
		||||
				decUsage: 0
 | 
			
		||||
			}
 | 
			
		||||
		};
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@autobind
 | 
			
		||||
	public async requestReceived(host: string) {
 | 
			
		||||
		await this.inc({
 | 
			
		||||
			requests: {
 | 
			
		||||
				received: 1
 | 
			
		||||
			}
 | 
			
		||||
		}, host);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@autobind
 | 
			
		||||
	public async requestSent(host: string, isSucceeded: boolean) {
 | 
			
		||||
		const update: Obj = {};
 | 
			
		||||
 | 
			
		||||
		if (isSucceeded) {
 | 
			
		||||
			update.succeeded = 1;
 | 
			
		||||
		} else {
 | 
			
		||||
			update.failed = 1;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		await this.inc({
 | 
			
		||||
			requests: update
 | 
			
		||||
		}, host);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@autobind
 | 
			
		||||
	public async newUser(host: string) {
 | 
			
		||||
		await this.inc({
 | 
			
		||||
			users: {
 | 
			
		||||
				total: 1,
 | 
			
		||||
				inc: 1
 | 
			
		||||
			}
 | 
			
		||||
		}, host);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@autobind
 | 
			
		||||
	public async updateNote(host: string, isAdditional: boolean) {
 | 
			
		||||
		await this.inc({
 | 
			
		||||
			notes: {
 | 
			
		||||
				total: isAdditional ? 1 : -1,
 | 
			
		||||
				inc: isAdditional ? 1 : 0,
 | 
			
		||||
				dec: isAdditional ? 0 : 1,
 | 
			
		||||
			}
 | 
			
		||||
		}, host);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@autobind
 | 
			
		||||
	public async updateFollowing(host: string, isAdditional: boolean) {
 | 
			
		||||
		await this.inc({
 | 
			
		||||
			following: {
 | 
			
		||||
				total: isAdditional ? 1 : -1,
 | 
			
		||||
				inc: isAdditional ? 1 : 0,
 | 
			
		||||
				dec: isAdditional ? 0 : 1,
 | 
			
		||||
			}
 | 
			
		||||
		}, host);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@autobind
 | 
			
		||||
	public async updateFollowers(host: string, isAdditional: boolean) {
 | 
			
		||||
		await this.inc({
 | 
			
		||||
			followers: {
 | 
			
		||||
				total: isAdditional ? 1 : -1,
 | 
			
		||||
				inc: isAdditional ? 1 : 0,
 | 
			
		||||
				dec: isAdditional ? 0 : 1,
 | 
			
		||||
			}
 | 
			
		||||
		}, host);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@autobind
 | 
			
		||||
	public async updateDrive(file: IDriveFile, isAdditional: boolean) {
 | 
			
		||||
		const update: Obj = {};
 | 
			
		||||
 | 
			
		||||
		update.totalFiles = isAdditional ? 1 : -1;
 | 
			
		||||
		update.totalUsage = isAdditional ? file.length : -file.length;
 | 
			
		||||
		if (isAdditional) {
 | 
			
		||||
			update.incFiles = 1;
 | 
			
		||||
			update.incUsage = file.length;
 | 
			
		||||
		} else {
 | 
			
		||||
			update.decFiles = 1;
 | 
			
		||||
			update.decUsage = file.length;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		await this.inc({
 | 
			
		||||
			drive: update
 | 
			
		||||
		}, file.metadata._user.host);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default new InstanceChart();
 | 
			
		||||
| 
						 | 
				
			
			@ -13,17 +13,19 @@ import DriveFile, { IMetadata, getDriveFileBucket, IDriveFile } from '../../mode
 | 
			
		|||
import DriveFolder from '../../models/drive-folder';
 | 
			
		||||
import { pack } from '../../models/drive-file';
 | 
			
		||||
import { publishMainStream, publishDriveStream } from '../stream';
 | 
			
		||||
import { isLocalUser, IUser, IRemoteUser } from '../../models/user';
 | 
			
		||||
import { isLocalUser, IUser, IRemoteUser, isRemoteUser } from '../../models/user';
 | 
			
		||||
import delFile from './delete-file';
 | 
			
		||||
import config from '../../config';
 | 
			
		||||
import { getDriveFileWebpublicBucket } from '../../models/drive-file-webpublic';
 | 
			
		||||
import { getDriveFileThumbnailBucket } from '../../models/drive-file-thumbnail';
 | 
			
		||||
import driveChart from '../../services/chart/drive';
 | 
			
		||||
import perUserDriveChart from '../../services/chart/per-user-drive';
 | 
			
		||||
import instanceChart from '../../services/chart/instance';
 | 
			
		||||
import fetchMeta from '../../misc/fetch-meta';
 | 
			
		||||
import { GenerateVideoThumbnail } from './generate-video-thumbnail';
 | 
			
		||||
import { driveLogger } from './logger';
 | 
			
		||||
import { IImage, ConvertToJpeg, ConvertToWebp, ConvertToPng } from './image-processor';
 | 
			
		||||
import Instance from '../../models/instance';
 | 
			
		||||
 | 
			
		||||
const logger = driveLogger.createSubLogger('register', 'yellow');
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -523,6 +525,15 @@ export default async function(
 | 
			
		|||
	// 統計を更新
 | 
			
		||||
	driveChart.update(driveFile, true);
 | 
			
		||||
	perUserDriveChart.update(driveFile, true);
 | 
			
		||||
	if (isRemoteUser(driveFile.metadata._user)) {
 | 
			
		||||
		instanceChart.updateDrive(driveFile, true);
 | 
			
		||||
		Instance.update({ host: driveFile.metadata._user.host }, {
 | 
			
		||||
			$inc: {
 | 
			
		||||
				driveUsage: driveFile.length,
 | 
			
		||||
				driveFiles: 1
 | 
			
		||||
			}
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return driveFile;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,7 +4,10 @@ import DriveFileThumbnail, { DriveFileThumbnailChunk } from '../../models/drive-
 | 
			
		|||
import config from '../../config';
 | 
			
		||||
import driveChart from '../../services/chart/drive';
 | 
			
		||||
import perUserDriveChart from '../../services/chart/per-user-drive';
 | 
			
		||||
import instanceChart from '../../services/chart/instance';
 | 
			
		||||
import DriveFileWebpublic, { DriveFileWebpublicChunk } from '../../models/drive-file-webpublic';
 | 
			
		||||
import Instance from '../../models/instance';
 | 
			
		||||
import { isRemoteUser } from '../../models/user';
 | 
			
		||||
 | 
			
		||||
export default async function(file: IDriveFile, isExpired = false) {
 | 
			
		||||
	if (file.metadata.storage == 'minio') {
 | 
			
		||||
| 
						 | 
				
			
			@ -84,4 +87,13 @@ export default async function(file: IDriveFile, isExpired = false) {
 | 
			
		|||
	// 統計を更新
 | 
			
		||||
	driveChart.update(file, false);
 | 
			
		||||
	perUserDriveChart.update(file, false);
 | 
			
		||||
	if (isRemoteUser(file.metadata._user)) {
 | 
			
		||||
		instanceChart.updateDrive(file, false);
 | 
			
		||||
		Instance.update({ host: file.metadata._user.host }, {
 | 
			
		||||
			$inc: {
 | 
			
		||||
				driveUsage: -file.length,
 | 
			
		||||
				driveFiles: -1
 | 
			
		||||
			}
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -12,6 +12,7 @@ import createFollowRequest from './requests/create';
 | 
			
		|||
import perUserFollowingChart from '../../services/chart/per-user-following';
 | 
			
		||||
import { registerOrFetchInstanceDoc } from '../register-or-fetch-instance-doc';
 | 
			
		||||
import Instance from '../../models/instance';
 | 
			
		||||
import instanceChart from '../../services/chart/instance';
 | 
			
		||||
 | 
			
		||||
export default async function(follower: IUser, followee: IUser, requestId?: string) {
 | 
			
		||||
	// check blocking
 | 
			
		||||
| 
						 | 
				
			
			@ -108,8 +109,7 @@ export default async function(follower: IUser, followee: IUser, requestId?: stri
 | 
			
		|||
				}
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
			// TODO
 | 
			
		||||
			//perInstanceChart.newFollowing();
 | 
			
		||||
			instanceChart.updateFollowing(i.host, true);
 | 
			
		||||
		});
 | 
			
		||||
	} else if (isLocalUser(follower) && isRemoteUser(followee)) {
 | 
			
		||||
		registerOrFetchInstanceDoc(followee.host).then(i => {
 | 
			
		||||
| 
						 | 
				
			
			@ -119,8 +119,7 @@ export default async function(follower: IUser, followee: IUser, requestId?: stri
 | 
			
		|||
				}
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
			// TODO
 | 
			
		||||
			//perInstanceChart.newFollower();
 | 
			
		||||
			instanceChart.updateFollowers(i.host, true);
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
	//#endregion
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,6 +7,9 @@ import renderUndo from '../../remote/activitypub/renderer/undo';
 | 
			
		|||
import { deliver } from '../../queue';
 | 
			
		||||
import perUserFollowingChart from '../../services/chart/per-user-following';
 | 
			
		||||
import Logger from '../../misc/logger';
 | 
			
		||||
import { registerOrFetchInstanceDoc } from '../register-or-fetch-instance-doc';
 | 
			
		||||
import Instance from '../../models/instance';
 | 
			
		||||
import instanceChart from '../../services/chart/instance';
 | 
			
		||||
 | 
			
		||||
const logger = new Logger('following/delete');
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -41,6 +44,30 @@ export default async function(follower: IUser, followee: IUser) {
 | 
			
		|||
	});
 | 
			
		||||
	//#endregion
 | 
			
		||||
 | 
			
		||||
	//#region Update instance stats
 | 
			
		||||
	if (isRemoteUser(follower) && isLocalUser(followee)) {
 | 
			
		||||
		registerOrFetchInstanceDoc(follower.host).then(i => {
 | 
			
		||||
			Instance.update({ _id: i._id }, {
 | 
			
		||||
				$inc: {
 | 
			
		||||
					followingCount: -1
 | 
			
		||||
				}
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
			instanceChart.updateFollowing(i.host, false);
 | 
			
		||||
		});
 | 
			
		||||
	} else if (isLocalUser(follower) && isRemoteUser(followee)) {
 | 
			
		||||
		registerOrFetchInstanceDoc(followee.host).then(i => {
 | 
			
		||||
			Instance.update({ _id: i._id }, {
 | 
			
		||||
				$inc: {
 | 
			
		||||
					followersCount: -1
 | 
			
		||||
				}
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
			instanceChart.updateFollowers(i.host, false);
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
	//#endregion
 | 
			
		||||
 | 
			
		||||
	perUserFollowingChart.update(follower, followee, false);
 | 
			
		||||
 | 
			
		||||
	// Publish unfollow event
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -24,6 +24,7 @@ import isQuote from '../../misc/is-quote';
 | 
			
		|||
import notesChart from '../../services/chart/notes';
 | 
			
		||||
import perUserNotesChart from '../../services/chart/per-user-notes';
 | 
			
		||||
import activeUsersChart from '../../services/chart/active-users';
 | 
			
		||||
import instanceChart from '../../services/chart/instance';
 | 
			
		||||
 | 
			
		||||
import { erase, concat } from '../../prelude/array';
 | 
			
		||||
import insertNoteUnread from './unread';
 | 
			
		||||
| 
						 | 
				
			
			@ -229,8 +230,7 @@ export default async (user: IUser, data: Option, silent = false) => new Promise<
 | 
			
		|||
				}
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
			// TODO
 | 
			
		||||
			//perInstanceChart.newNote();
 | 
			
		||||
			instanceChart.updateNote(i.host, true);
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,5 @@
 | 
			
		|||
import Note, { INote } from '../../models/note';
 | 
			
		||||
import { IUser, isLocalUser } from '../../models/user';
 | 
			
		||||
import { IUser, isLocalUser, isRemoteUser } from '../../models/user';
 | 
			
		||||
import { publishNoteStream } from '../stream';
 | 
			
		||||
import renderDelete from '../../remote/activitypub/renderer/delete';
 | 
			
		||||
import { renderActivity } from '../../remote/activitypub/renderer';
 | 
			
		||||
| 
						 | 
				
			
			@ -12,6 +12,9 @@ import config from '../../config';
 | 
			
		|||
import NoteUnread from '../../models/note-unread';
 | 
			
		||||
import read from './read';
 | 
			
		||||
import DriveFile from '../../models/drive-file';
 | 
			
		||||
import { registerOrFetchInstanceDoc } from '../register-or-fetch-instance-doc';
 | 
			
		||||
import Instance from '../../models/instance';
 | 
			
		||||
import instanceChart from '../../services/chart/instance';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 投稿を削除します。
 | 
			
		||||
| 
						 | 
				
			
			@ -91,4 +94,16 @@ export default async function(user: IUser, note: INote) {
 | 
			
		|||
	// 統計を更新
 | 
			
		||||
	notesChart.update(note, false);
 | 
			
		||||
	perUserNotesChart.update(user, note, false);
 | 
			
		||||
 | 
			
		||||
	if (isRemoteUser(user)) {
 | 
			
		||||
		registerOrFetchInstanceDoc(user.host).then(i => {
 | 
			
		||||
			Instance.update({ _id: i._id }, {
 | 
			
		||||
				$inc: {
 | 
			
		||||
					notesCount: -1
 | 
			
		||||
				}
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
			instanceChart.updateNote(i.host, false);
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue