diff --git a/packages/backend/src/core/chart/charts/per-user-pv.ts b/packages/backend/src/core/chart/charts/per-user-pv.ts index 02baa5ec1..3ad90b090 100644 --- a/packages/backend/src/core/chart/charts/per-user-pv.ts +++ b/packages/backend/src/core/chart/charts/per-user-pv.ts @@ -7,6 +7,7 @@ import { Injectable, Inject } from '@nestjs/common'; import { DataSource } from 'typeorm'; import type { MiUser } from '@/models/User.js'; import { AppLockService } from '@/core/AppLockService.js'; +import { addTime, dateUTC, subtractTime } from '@/misc/prelude/time.js'; import { DI } from '@/di-symbols.js'; import { bindThis } from '@/decorators.js'; import Chart from '../core.js'; @@ -54,10 +55,30 @@ export default class PerUserPvChart extends Chart { // eslint-dis } @bindThis - public async getChartUsers(span: 'hour' | 'day', order: 'ASC' | 'DESC', amount: number, cursor: Date | null, limit = 0, offset = 0): Promise<{ - userId: string; - count: number; -}[]> { - return await this.getChartPv(span, amount, cursor, limit, offset, order); + public async getUsersRanking(span: 'hour' | 'day', order: 'ASC' | 'DESC', amount: number, cursor: Date | null, limit = 0, offset = 0): 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.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 }>(); } } diff --git a/packages/backend/src/core/chart/core.ts b/packages/backend/src/core/chart/core.ts index 3874c1e14..09757c887 100644 --- a/packages/backend/src/core/chart/core.ts +++ b/packages/backend/src/core/chart/core.ts @@ -147,10 +147,8 @@ export default abstract class Chart { // ↓にしたいけどfindOneとかで型エラーになる //private repositoryForHour: Repository>; //private repositoryForDay: Repository>; - private repositoryForHour: Repository<{ id: number; group?: string | null; date: number;}>; - private repositoryForDay: Repository<{ id: number; group?: string | null; date: number;}>; - private repositoryUserPvForHour: Repository<{ id: number; group?: string | null; date: number; ___pv_user:number; ___upv_user:number; ___pv_visitor:number; ___upv_visitor:number;}>; - private repositoryUserPvForDay: Repository<{ id: number; group?: string | null; date: number; ___pv_user:number; ___upv_user:number; ___pv_visitor:number; ___upv_visitor:number;}>; + protected repositoryForHour: Repository<{ id: number; group?: string | null; date: number; }>; + protected repositoryForDay: Repository<{ id: number; group?: string | null; date: number; }>; /** * 1日に一回程度実行されれば良いような計算処理を入れる(主にCASCADE削除などアプリケーション側で感知できない変動によるズレの修正用) */ @@ -186,11 +184,11 @@ export default abstract class Chart { return columns; } - private static dateToTimestamp(x: Date): number { + protected static dateToTimestamp(x: Date): number { return Math.floor(x.getTime() / 1000); } - private static parseDate(date: Date): [number, number, number, number, number, number, number] { + protected static parseDate(date: Date): [number, number, number, number, number, number, number] { const y = date.getUTCFullYear(); const m = date.getUTCMonth(); const d = date.getUTCDate(); @@ -202,7 +200,7 @@ export default abstract class Chart { return [y, m, d, h, _m, _s, _ms]; } - private static getCurrentDate() { + protected static getCurrentDate() { return Chart.parseDate(new Date()); } @@ -274,8 +272,6 @@ export default abstract class Chart { const { hour, day } = Chart.schemaToEntity(name, schema, grouped); this.repositoryForHour = db.getRepository<{ id: number; group?: string | null; date: number; }>(hour); this.repositoryForDay = db.getRepository<{ id: number; group?: string | null; date: number; }>(day); - this.repositoryUserPvForHour = db.getRepository<{ id: number; group?: string | null; date: number; ___pv_user:number; ___upv_user:number; ___pv_visitor:number; ___upv_visitor:number;}>(hour); - this.repositoryUserPvForDay = db.getRepository<{ id: number; group?: string | null; date: number; ___pv_user:number; ___upv_user:number; ___pv_visitor:number; ___upv_visitor:number;}>(day); } @bindThis @@ -725,37 +721,4 @@ export default abstract class Chart { } return object as Unflatten>; } - - @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 }>(); - } } diff --git a/packages/backend/src/server/api/endpoints/users.ts b/packages/backend/src/server/api/endpoints/users.ts index 8adbc7b28..8c02b9ed5 100644 --- a/packages/backend/src/server/api/endpoints/users.ts +++ b/packages/backend/src/server/api/endpoints/users.ts @@ -72,12 +72,16 @@ export default class extends Endpoint { // eslint- if (ps.hostname) { query.andWhere('user.host = :hostname', { hostname: ps.hostname.toLowerCase() }); } - const chartUsers: { userId: string; count: number; }[] = []; + + let pvUsers: { userId: string; count: number; }[] | undefined = undefined; if (ps.sort?.endsWith('pv')) { - await this.perUserPvChart.getChartUsers('hour', ps.sort === '+pv' ? 'DESC' : 'ASC', 0, null, ps.limit, ps.offset).then(users => { - chartUsers.push(...users); - }); + // 直近12時間のPVランキングを取得 + pvUsers = await this.perUserPvChart.getUsersRanking( + 'hour', ps.sort.startsWith('+') ? 'DESC' : 'ASC', + 12, null, ps.limit, ps.offset, + ); } + switch (ps.sort) { case '+follower': query.orderBy('user.followersCount', 'DESC'); break; case '-follower': query.orderBy('user.followersCount', 'ASC'); break; @@ -85,16 +89,8 @@ export default class extends Endpoint { // eslint- 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', 'ASC'); break; - case '+pv': - 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; + case '+pv': query.andWhere('user.id IN (:...userIds)', { userIds: pvUsers?.map(user => user.userId) ?? [] }); break; + case '-pv': query.andWhere('user.id IN (:...userIds)', { userIds: pvUsers?.map(user => user.userId) ?? [] }); break; default: query.orderBy('user.id', 'ASC'); break; } @@ -107,14 +103,14 @@ export default class extends Endpoint { // eslint- const users = await query.getMany(); if (ps.sort === '+pv') { users.sort((a, b) => { - const aPv = chartUsers.find(user => user.userId === a.id)?.count ?? 0; - const bPv = chartUsers.find(user => user.userId === b.id)?.count ?? 0; + const aPv = pvUsers?.find(u => u.userId === a.id)?.count ?? 0; + const bPv = pvUsers?.find(u => u.userId === b.id)?.count ?? 0; return bPv - aPv; }); } else if (ps.sort === '-pv') { users.sort((a, b) => { - const aPv = chartUsers.find(user => user.userId === a.id)?.count ?? 0; - const bPv = chartUsers.find(user => user.userId === b.id)?.count ?? 0; + const aPv = pvUsers?.find(u => u.userId === a.id)?.count ?? 0; + const bPv = pvUsers?.find(u => u.userId === b.id)?.count ?? 0; return aPv - bPv; }); }