enhance(backend): api/usersに+pv -pvを追加 (MisskeyIO#653)

This commit is contained in:
nenohi 2024-07-13 01:50:57 +09:00 committed by GitHub
parent b8a90659f3
commit 26cdf6fc09
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 94 additions and 6 deletions

View File

@ -52,4 +52,12 @@ export default class PerUserPvChart extends Chart<typeof schema> { // eslint-dis
'pv.visitor': 1, 'pv.visitor': 1,
}, user.id); }, user.id);
} }
@bindThis
public async getChartUsers(span: 'hour' | 'day', amount: number, cursor: Date | null, limit = 0, offset = 0): Promise<{
userId: string;
count: number;
}[]> {
return await this.getChartPv(span, amount, cursor, limit, offset);
}
} }

View File

@ -149,7 +149,8 @@ export default abstract class Chart<T extends Schema> {
//private repositoryForDay: Repository<RawRecord<T>>; //private repositoryForDay: Repository<RawRecord<T>>;
private repositoryForHour: Repository<{ id: number; group?: string | null; date: number;}>; private repositoryForHour: Repository<{ id: number; group?: string | null; date: number;}>;
private repositoryForDay: 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;}>;
/** /**
* 1(CASCADE削除などアプリケーション側で感知できない変動によるズレの修正用) * 1(CASCADE削除などアプリケーション側で感知できない変動によるズレの修正用)
*/ */
@ -273,6 +274,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); 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.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 @bindThis
@ -722,4 +725,51 @@ 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): 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;
// ログ取得
const logs = await repository.createQueryBuilder()
.where('date BETWEEN :gt AND :lt', { gt: Chart.dateToTimestamp(gt), lt: Chart.dateToTimestamp(lt) })
.orderBy('___pv_visitor + ___upv_visitor + ___pv_user + ___upv_user', 'DESC')
.skip(offset)
.take(limit)
.getMany() as {
___pv_visitor: number,
___upv_visitor: number,
___pv_user: number,
___upv_user: number,
group: string,
}[];
const result = [] as {
userId: string,
count: number,
}[];
for (const row of logs) {
const userId = row.group;
const count = row.___pv_user + row.___upv_user + row.___pv_visitor + row.___upv_visitor;
result.push({ userId, count });
}
return result;
}
} }

View File

@ -6,6 +6,7 @@
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import type { UsersRepository } from '@/models/_.js'; import type { UsersRepository } from '@/models/_.js';
import { Endpoint } from '@/server/api/endpoint-base.js'; import { Endpoint } from '@/server/api/endpoint-base.js';
import PerUserPvChart from '@/core/chart/charts/per-user-pv.js';
import { QueryService } from '@/core/QueryService.js'; import { QueryService } from '@/core/QueryService.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
@ -31,7 +32,7 @@ export const paramDef = {
properties: { properties: {
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
offset: { type: 'integer', default: 0 }, offset: { type: 'integer', default: 0 },
sort: { type: 'string', enum: ['+follower', '-follower', '+createdAt', '-createdAt', '+updatedAt', '-updatedAt'] }, sort: { type: 'string', enum: ['+follower', '-follower', '+createdAt', '-createdAt', '+updatedAt', '-updatedAt', '+pv', '-pv'] },
state: { type: 'string', enum: ['all', 'alive'], default: 'all' }, state: { type: 'string', enum: ['all', 'alive'], default: 'all' },
origin: { type: 'string', enum: ['combined', 'local', 'remote'], default: 'local' }, origin: { type: 'string', enum: ['combined', 'local', 'remote'], default: 'local' },
hostname: { hostname: {
@ -49,6 +50,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
constructor( constructor(
@Inject(DI.usersRepository) @Inject(DI.usersRepository)
private usersRepository: UsersRepository, private usersRepository: UsersRepository,
private perUserPvChart: PerUserPvChart,
private userEntityService: UserEntityService, private userEntityService: UserEntityService,
private queryService: QueryService, private queryService: QueryService,
@ -70,7 +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; }[] = [];
if (ps.sort?.endsWith('pv')) {
await this.perUserPvChart.getChartUsers('hour', 0, null, ps.limit, ps.offset).then(users => {
chartUsers.push(...users);
});
}
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;
@ -78,6 +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':
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;
} }
@ -88,6 +105,19 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
query.offset(ps.offset); query.offset(ps.offset);
const users = await query.getMany(); 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;
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;
return aPv - bPv;
});
}
return await this.userEntityService.packMany(users, me, { schema: 'UserDetailed' }); return await this.userEntityService.packMany(users, me, { schema: 'UserDetailed' });
}); });

View File

@ -25480,7 +25480,7 @@ export type operations = {
/** @default 0 */ /** @default 0 */
offset?: number; offset?: number;
/** @enum {string} */ /** @enum {string} */
sort?: '+follower' | '-follower' | '+createdAt' | '-createdAt' | '+updatedAt' | '-updatedAt'; sort?: '+follower' | '-follower' | '+createdAt' | '-createdAt' | '+updatedAt' | '-updatedAt' | '+pv' | '-pv';
/** /**
* @default all * @default all
* @enum {string} * @enum {string}