Compare commits

..

No commits in common. "4ad11a238d39a4503b9c9ca01291c1f2ea09d319" and "c7be019fdd4b9a6549bda3de426265e96a167f65" have entirely different histories.

4 changed files with 68 additions and 47 deletions

View File

@ -1,8 +1,7 @@
## Unreleased ## Unreleased
### General ### General
- Enhance: 人気ユーザーの算出基準を変更できるように -
(Cherry-picked from https://github.com/MisskeyIO/misskey/pull/653, https://github.com/MisskeyIO/misskey/pull/664, https://github.com/MisskeyIO/misskey/pull/728, https://github.com/MisskeyIO/misskey/pull/734, https://github.com/MisskeyIO/misskey/pull/737)
### Client ### Client
- Feat: ノート単体・ユーザーのノート・クリップのノートの埋め込み機能 - Feat: ノート単体・ユーザーのノート・クリップのノートの埋め込み機能
@ -72,6 +71,8 @@
- Feat: メディアサイレンスを実装 #13842 - Feat: メディアサイレンスを実装 #13842
- メディアサイレンスされたサーバーに所属するアカウントによるファイルはすべてセンシティブとして扱われ、カスタム絵文字が使用できないようになります。 - メディアサイレンスされたサーバーに所属するアカウントによるファイルはすべてセンシティブとして扱われ、カスタム絵文字が使用できないようになります。
- Enhance: 管理画面でアーカイブにしたお知らせを表示・編集できるように - Enhance: 管理画面でアーカイブにしたお知らせを表示・編集できるように
- Enhance: 人気ユーザーの算出基準を変更できるように
(Cherry-picked from https://github.com/MisskeyIO/misskey/pull/653 , https://github.com/MisskeyIO/misskey/pull/664)
- Fix: 配信停止したインスタンス一覧が見れなくなる問題を修正 - Fix: 配信停止したインスタンス一覧が見れなくなる問題を修正
- Fix: Dockerコンテナの立ち上げ時に`pnpm`のインストールで固まることがある問題 - Fix: Dockerコンテナの立ち上げ時に`pnpm`のインストールで固まることがある問題
- Fix: デフォルトテーマに無効なテーマコードを入力するとUIが使用できなくなる問題を修正 - Fix: デフォルトテーマに無効なテーマコードを入力するとUIが使用できなくなる問題を修正

View File

@ -7,7 +7,6 @@ import { Injectable, Inject } from '@nestjs/common';
import { DataSource } from 'typeorm'; import { DataSource } from 'typeorm';
import type { MiUser } from '@/models/User.js'; import type { MiUser } from '@/models/User.js';
import { AppLockService } from '@/core/AppLockService.js'; import { AppLockService } from '@/core/AppLockService.js';
import { addTime, dateUTC, subtractTime } from '@/misc/prelude/time.js';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
import Chart from '../core.js'; import Chart from '../core.js';
@ -55,30 +54,10 @@ export default class PerUserPvChart extends Chart<typeof schema> { // eslint-dis
} }
@bindThis @bindThis
public async getUsersRanking(span: 'hour' | 'day', order: 'ASC' | 'DESC', amount: number, cursor: Date | null, limit = 0, offset = 0): Promise<{ userId: string; count: number; }[]> { public async getChartUsers(span: 'hour' | 'day', order: 'ASC' | 'DESC', amount: number, cursor: Date | null, limit = 0, offset = 0): Promise<{
const [y, m, d, h, _m, _s, _ms] = cursor ? Chart.parseDate(subtractTime(addTime(cursor, 1, span), 1)) : Chart.getCurrentDate(); userId: string;
const [y2, m2, d2, h2] = cursor ? Chart.parseDate(addTime(cursor, 1, span)) : [] as never; count: number;
}[]> {
const lt = dateUTC([y, m, d, h, _m, _s, _ms]); return await this.getChartPv(span, amount, cursor, limit, offset, order);
const gt =
span === 'day' ? subtractTime(cursor ? dateUTC([y2, m2, d2, 0]) : dateUTC([y, m, d, 0]), amount - 1, 'day') :
span === 'hour' ? subtractTime(cursor ? dateUTC([y2, m2, d2, h2]) : dateUTC([y, m, d, h]), amount - 1, 'hour') :
new Error('not happen') as never;
const repository =
span === 'hour' ? this.repositoryForHour :
span === 'day' ? this.repositoryForDay :
new Error('not happen') as never;
// ログ取得
return await repository.createQueryBuilder()
.select('"group" as "userId", sum("___upv_user" + "___upv_visitor") as "count"')
.where('date BETWEEN :gt AND :lt', { gt: Chart.dateToTimestamp(gt), lt: Chart.dateToTimestamp(lt) })
.groupBy('"userId"')
.orderBy('"count"', order)
.offset(offset)
.limit(limit)
.getRawMany<{ userId: string, count: number }>();
} }
} }

View File

@ -150,8 +150,10 @@ export default abstract class Chart<T extends Schema> {
// ↓にしたいけどfindOneとかで型エラーになる // ↓にしたいけどfindOneとかで型エラーになる
//private repositoryForHour: MiAndOrmRepository<RawRecord<T>>; //private repositoryForHour: MiAndOrmRepository<RawRecord<T>>;
//private repositoryForDay: MiAndOrmRepository<RawRecord<T>>; //private repositoryForDay: MiAndOrmRepository<RawRecord<T>>;
protected repositoryForHour: MiAndOrmRepository<{ id: number; group?: string | null; date: number; }>; private repositoryForHour: MiAndOrmRepository<{ id: number; group?: string | null; date: number;}>;
protected repositoryForDay: MiAndOrmRepository<{ id: number; group?: string | null; date: number; }>; private repositoryForDay: MiAndOrmRepository<{ id: number; group?: string | null; date: number;}>;
private repositoryUserPvForHour: MiAndOrmRepository<{ id: number; group?: string | null; date: number; ___pv_user:number; ___upv_user:number; ___pv_visitor:number; ___upv_visitor:number;}>;
private repositoryUserPvForDay: MiAndOrmRepository<{ id: number; group?: string | null; date: number; ___pv_user:number; ___upv_user:number; ___pv_visitor:number; ___upv_visitor:number;}>;
/** /**
* 1(CASCADE削除などアプリケーション側で感知できない変動によるズレの修正用) * 1(CASCADE削除などアプリケーション側で感知できない変動によるズレの修正用)
*/ */
@ -187,11 +189,11 @@ export default abstract class Chart<T extends Schema> {
return columns; return columns;
} }
protected static dateToTimestamp(x: Date): number { private static dateToTimestamp(x: Date): number {
return Math.floor(x.getTime() / 1000); return Math.floor(x.getTime() / 1000);
} }
protected static parseDate(date: Date): [number, number, number, number, number, number, number] { private static parseDate(date: Date): [number, number, number, number, number, number, number] {
const y = date.getUTCFullYear(); const y = date.getUTCFullYear();
const m = date.getUTCMonth(); const m = date.getUTCMonth();
const d = date.getUTCDate(); const d = date.getUTCDate();
@ -203,7 +205,7 @@ export default abstract class Chart<T extends Schema> {
return [y, m, d, h, _m, _s, _ms]; return [y, m, d, h, _m, _s, _ms];
} }
protected static getCurrentDate() { private static getCurrentDate() {
return Chart.parseDate(new Date()); return Chart.parseDate(new Date());
} }
@ -279,6 +281,8 @@ export default abstract class Chart<T extends Schema> {
const { hour, day } = Chart.schemaToEntity(name, schema, grouped); const { hour, day } = Chart.schemaToEntity(name, schema, grouped);
this.repositoryForHour = db.getRepository<{ id: number; group?: string | null; date: number; }>(hour).extend(miRepository); this.repositoryForHour = db.getRepository<{ id: number; group?: string | null; date: number; }>(hour).extend(miRepository);
this.repositoryForDay = db.getRepository<{ id: number; group?: string | null; date: number; }>(day).extend(miRepository); this.repositoryForDay = db.getRepository<{ id: number; group?: string | null; date: number; }>(day).extend(miRepository);
this.repositoryUserPvForHour = db.getRepository<{ id: number; group?: string | null; date: number; ___pv_user:number; ___upv_user:number; ___pv_visitor:number; ___upv_visitor:number;}>(hour).extend(miRepository);
this.repositoryUserPvForDay = db.getRepository<{ id: number; group?: string | null; date: number; ___pv_user:number; ___upv_user:number; ___pv_visitor:number; ___upv_visitor:number;}>(day).extend(miRepository);
} }
@bindThis @bindThis
@ -728,4 +732,37 @@ export default abstract class Chart<T extends Schema> {
} }
return object as Unflatten<ChartResult<T>>; return object as Unflatten<ChartResult<T>>;
} }
@bindThis
public async getChartPv(span: 'hour' | 'day', amount: number, cursor: Date | null, limit: number, offset: number, order: 'ASC' | 'DESC'): Promise<
{
userId: string,
count: number,
}[]
> {
const [y, m, d, h, _m, _s, _ms] = cursor ? Chart.parseDate(subtractTime(addTime(cursor, 1, span), 1)) : Chart.getCurrentDate();
const [y2, m2, d2, h2] = cursor ? Chart.parseDate(addTime(cursor, 1, span)) : [] as never;
const lt = dateUTC([y, m, d, h, _m, _s, _ms]);
const gt =
span === 'day' ? subtractTime(cursor ? dateUTC([y2, m2, d2, 0]) : dateUTC([y, m, d, 0]), amount - 1, 'day') :
span === 'hour' ? subtractTime(cursor ? dateUTC([y2, m2, d2, h2]) : dateUTC([y, m, d, h]), amount - 1, 'hour') :
new Error('not happen') as never;
const repository =
span === 'hour' ? this.repositoryUserPvForHour :
span === 'day' ? this.repositoryUserPvForDay :
new Error('not happen') as never;
// ログ取得
return await repository.createQueryBuilder()
.select('"group" as "userId", sum("___upv_user" + "___upv_visitor") as "count"')
.where('date BETWEEN :gt AND :lt', { gt: Chart.dateToTimestamp(gt), lt: Chart.dateToTimestamp(lt) })
.groupBy('"userId"')
.orderBy('"count"', order)
.offset(offset)
.limit(limit)
.getRawMany<{ userId: string, count: number }>();
}
} }

View File

@ -72,16 +72,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
if (ps.hostname) { if (ps.hostname) {
query.andWhere('user.host = :hostname', { hostname: ps.hostname.toLowerCase() }); query.andWhere('user.host = :hostname', { hostname: ps.hostname.toLowerCase() });
} }
const chartUsers: { userId: string; count: number; }[] = [];
let pvRankedUsers: { userId: string; count: number; }[] | undefined = undefined;
if (ps.sort?.endsWith('pv')) { if (ps.sort?.endsWith('pv')) {
// 直近12時間のPVランキングを取得 await this.perUserPvChart.getChartUsers('day', ps.sort === '+pv' ? 'DESC' : 'ASC', 0, null, ps.limit, ps.offset).then(users => {
pvRankedUsers = await this.perUserPvChart.getUsersRanking( chartUsers.push(...users);
'hour', ps.sort.startsWith('+') ? 'DESC' : 'ASC', });
12, null, ps.limit, ps.offset,
);
} }
switch (ps.sort) { switch (ps.sort) {
case '+follower': query.orderBy('user.followersCount', 'DESC'); break; case '+follower': query.orderBy('user.followersCount', 'DESC'); break;
case '-follower': query.orderBy('user.followersCount', 'ASC'); break; case '-follower': query.orderBy('user.followersCount', 'ASC'); break;
@ -89,8 +85,16 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
case '-createdAt': query.orderBy('user.id', 'ASC'); break; case '-createdAt': query.orderBy('user.id', 'ASC'); break;
case '+updatedAt': query.andWhere('user.updatedAt IS NOT NULL').orderBy('user.updatedAt', 'DESC'); break; case '+updatedAt': query.andWhere('user.updatedAt IS NOT NULL').orderBy('user.updatedAt', 'DESC'); break;
case '-updatedAt': query.andWhere('user.updatedAt IS NOT NULL').orderBy('user.updatedAt', 'ASC'); break; case '-updatedAt': query.andWhere('user.updatedAt IS NOT NULL').orderBy('user.updatedAt', 'ASC'); break;
case '+pv': query.andWhere((pvRankedUsers?.length ?? 0) > 0 ? 'user.id IN (:...userIds)' : '1 = 0', { userIds: pvRankedUsers?.map(user => user.userId) ?? [] }); break; case '+pv':
case '-pv': query.andWhere((pvRankedUsers?.length ?? 0) > 0 ? 'user.id IN (:...userIds)' : '1 = 0', { userIds: pvRankedUsers?.map(user => user.userId) ?? [] }); break; if (chartUsers.length > 0) {
query.andWhere('user.id IN (:...userIds)', { userIds: chartUsers.map(user => user.userId) });
}
break;
case '-pv':
if (chartUsers.length > 0) {
query.andWhere('user.id IN (:...userIds)', { userIds: chartUsers.map(user => user.userId) });
}
break;
default: query.orderBy('user.id', 'ASC'); break; default: query.orderBy('user.id', 'ASC'); break;
} }
@ -103,14 +107,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
const users = await query.getMany(); const users = await query.getMany();
if (ps.sort === '+pv') { if (ps.sort === '+pv') {
users.sort((a, b) => { users.sort((a, b) => {
const aPv = pvRankedUsers?.find(u => u.userId === a.id)?.count ?? 0; const aPv = chartUsers.find(user => user.userId === a.id)?.count ?? 0;
const bPv = pvRankedUsers?.find(u => u.userId === b.id)?.count ?? 0; const bPv = chartUsers.find(user => user.userId === b.id)?.count ?? 0;
return bPv - aPv; return bPv - aPv;
}); });
} else if (ps.sort === '-pv') { } else if (ps.sort === '-pv') {
users.sort((a, b) => { users.sort((a, b) => {
const aPv = pvRankedUsers?.find(u => u.userId === a.id)?.count ?? 0; const aPv = chartUsers.find(user => user.userId === a.id)?.count ?? 0;
const bPv = pvRankedUsers?.find(u => u.userId === b.id)?.count ?? 0; const bPv = chartUsers.find(user => user.userId === b.id)?.count ?? 0;
return aPv - bPv; return aPv - bPv;
}); });
} }