enhance(backend): api/usersに+pv -pvを追加 (MisskeyIO#653)
This commit is contained in:
		
							parent
							
								
									b8a90659f3
								
							
						
					
					
						commit
						26cdf6fc09
					
				|  | @ -52,4 +52,12 @@ export default class PerUserPvChart extends Chart<typeof schema> { // eslint-dis | |||
| 			'pv.visitor': 1, | ||||
| 		}, 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); | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -147,9 +147,10 @@ export default abstract class Chart<T extends Schema> { | |||
| 	// ↓にしたいけどfindOneとかで型エラーになる
 | ||||
| 	//private repositoryForHour: Repository<RawRecord<T>>;
 | ||||
| 	//private repositoryForDay: Repository<RawRecord<T>>;
 | ||||
| 	private repositoryForHour: Repository<{ id: number; group?: string | null; date: number; }>; | ||||
| 	private repositoryForDay: 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 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削除などアプリケーション側で感知できない変動によるズレの修正用) | ||||
| 	 */ | ||||
|  | @ -273,6 +274,8 @@ export default abstract class Chart<T extends Schema> { | |||
| 		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 | ||||
|  | @ -722,4 +725,51 @@ export default abstract class Chart<T extends Schema> { | |||
| 		} | ||||
| 		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; | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -6,6 +6,7 @@ | |||
| import { Inject, Injectable } from '@nestjs/common'; | ||||
| import type { UsersRepository } from '@/models/_.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 { UserEntityService } from '@/core/entities/UserEntityService.js'; | ||||
| import { DI } from '@/di-symbols.js'; | ||||
|  | @ -31,7 +32,7 @@ export const paramDef = { | |||
| 	properties: { | ||||
| 		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, | ||||
| 		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' }, | ||||
| 		origin: { type: 'string', enum: ['combined', 'local', 'remote'], default: 'local' }, | ||||
| 		hostname: { | ||||
|  | @ -49,6 +50,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | |||
| 	constructor( | ||||
| 		@Inject(DI.usersRepository) | ||||
| 		private usersRepository: UsersRepository, | ||||
| 		private perUserPvChart: PerUserPvChart, | ||||
| 
 | ||||
| 		private userEntityService: UserEntityService, | ||||
| 		private queryService: QueryService, | ||||
|  | @ -70,7 +72,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- | |||
| 			if (ps.hostname) { | ||||
| 				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) { | ||||
| 				case '+follower': query.orderBy('user.followersCount', 'DESC'); 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 '+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; | ||||
| 				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); | ||||
| 
 | ||||
| 			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' }); | ||||
| 		}); | ||||
|  |  | |||
|  | @ -25480,7 +25480,7 @@ export type operations = { | |||
|           /** @default 0 */ | ||||
|           offset?: number; | ||||
|           /** @enum {string} */ | ||||
|           sort?: '+follower' | '-follower' | '+createdAt' | '-createdAt' | '+updatedAt' | '-updatedAt'; | ||||
|           sort?: '+follower' | '-follower' | '+createdAt' | '-createdAt' | '+updatedAt' | '-updatedAt' | '+pv' | '-pv'; | ||||
|           /** | ||||
|            * @default all | ||||
|            * @enum {string} | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue