perf(backend): avoid N+1 selects from user
table when packing many entities (#13911)
* perf(backend): avoid N+1 selects from `user` table when packing many entities * perf(backend): use `packMany` instead of mapping to `pack`
This commit is contained in:
parent
97be1a53ad
commit
514a65e453
24 changed files with 268 additions and 87 deletions
|
@ -10,6 +10,8 @@ import { awaitAll } from '@/misc/prelude/await-all.js';
|
||||||
import type { MiAbuseUserReport } from '@/models/AbuseUserReport.js';
|
import type { MiAbuseUserReport } from '@/models/AbuseUserReport.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { IdService } from '@/core/IdService.js';
|
import { IdService } from '@/core/IdService.js';
|
||||||
|
import { isNotNull } from '@/misc/is-not-null.js';
|
||||||
|
import type { Packed } from '@/misc/json-schema.js';
|
||||||
import { UserEntityService } from './UserEntityService.js';
|
import { UserEntityService } from './UserEntityService.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
|
@ -26,6 +28,11 @@ export class AbuseUserReportEntityService {
|
||||||
@bindThis
|
@bindThis
|
||||||
public async pack(
|
public async pack(
|
||||||
src: MiAbuseUserReport['id'] | MiAbuseUserReport,
|
src: MiAbuseUserReport['id'] | MiAbuseUserReport,
|
||||||
|
hint?: {
|
||||||
|
packedReporter?: Packed<'UserDetailedNotMe'>,
|
||||||
|
packedTargetUser?: Packed<'UserDetailedNotMe'>,
|
||||||
|
packedAssignee?: Packed<'UserDetailedNotMe'>,
|
||||||
|
},
|
||||||
) {
|
) {
|
||||||
const report = typeof src === 'object' ? src : await this.abuseUserReportsRepository.findOneByOrFail({ id: src });
|
const report = typeof src === 'object' ? src : await this.abuseUserReportsRepository.findOneByOrFail({ id: src });
|
||||||
|
|
||||||
|
@ -37,13 +44,13 @@ export class AbuseUserReportEntityService {
|
||||||
reporterId: report.reporterId,
|
reporterId: report.reporterId,
|
||||||
targetUserId: report.targetUserId,
|
targetUserId: report.targetUserId,
|
||||||
assigneeId: report.assigneeId,
|
assigneeId: report.assigneeId,
|
||||||
reporter: this.userEntityService.pack(report.reporter ?? report.reporterId, null, {
|
reporter: hint?.packedReporter ?? this.userEntityService.pack(report.reporter ?? report.reporterId, null, {
|
||||||
schema: 'UserDetailedNotMe',
|
schema: 'UserDetailedNotMe',
|
||||||
}),
|
}),
|
||||||
targetUser: this.userEntityService.pack(report.targetUser ?? report.targetUserId, null, {
|
targetUser: hint?.packedTargetUser ?? this.userEntityService.pack(report.targetUser ?? report.targetUserId, null, {
|
||||||
schema: 'UserDetailedNotMe',
|
schema: 'UserDetailedNotMe',
|
||||||
}),
|
}),
|
||||||
assignee: report.assigneeId ? this.userEntityService.pack(report.assignee ?? report.assigneeId, null, {
|
assignee: report.assigneeId ? hint?.packedAssignee ?? this.userEntityService.pack(report.assignee ?? report.assigneeId, null, {
|
||||||
schema: 'UserDetailedNotMe',
|
schema: 'UserDetailedNotMe',
|
||||||
}) : null,
|
}) : null,
|
||||||
forwarded: report.forwarded,
|
forwarded: report.forwarded,
|
||||||
|
@ -51,9 +58,24 @@ export class AbuseUserReportEntityService {
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public packMany(
|
public async packMany(
|
||||||
reports: any[],
|
reports: MiAbuseUserReport[],
|
||||||
) {
|
) {
|
||||||
return Promise.all(reports.map(x => this.pack(x)));
|
const _reporters = reports.map(({ reporter, reporterId }) => reporter ?? reporterId);
|
||||||
|
const _targetUsers = reports.map(({ targetUser, targetUserId }) => targetUser ?? targetUserId);
|
||||||
|
const _assignees = reports.map(({ assignee, assigneeId }) => assignee ?? assigneeId).filter(isNotNull);
|
||||||
|
const _userMap = await this.userEntityService.packMany(
|
||||||
|
[..._reporters, ..._targetUsers, ..._assignees],
|
||||||
|
null,
|
||||||
|
{ schema: 'UserDetailedNotMe' },
|
||||||
|
).then(users => new Map(users.map(u => [u.id, u])));
|
||||||
|
return Promise.all(
|
||||||
|
reports.map(report => {
|
||||||
|
const packedReporter = _userMap.get(report.reporterId);
|
||||||
|
const packedTargetUser = _userMap.get(report.targetUserId);
|
||||||
|
const packedAssignee = report.assigneeId != null ? _userMap.get(report.assigneeId) : undefined;
|
||||||
|
return this.pack(report, { packedReporter, packedTargetUser, packedAssignee });
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,9 @@ export class BlockingEntityService {
|
||||||
public async pack(
|
public async pack(
|
||||||
src: MiBlocking['id'] | MiBlocking,
|
src: MiBlocking['id'] | MiBlocking,
|
||||||
me?: { id: MiUser['id'] } | null | undefined,
|
me?: { id: MiUser['id'] } | null | undefined,
|
||||||
|
hint?: {
|
||||||
|
blockee?: Packed<'UserDetailedNotMe'>,
|
||||||
|
},
|
||||||
): Promise<Packed<'Blocking'>> {
|
): Promise<Packed<'Blocking'>> {
|
||||||
const blocking = typeof src === 'object' ? src : await this.blockingsRepository.findOneByOrFail({ id: src });
|
const blocking = typeof src === 'object' ? src : await this.blockingsRepository.findOneByOrFail({ id: src });
|
||||||
|
|
||||||
|
@ -36,17 +39,20 @@ export class BlockingEntityService {
|
||||||
id: blocking.id,
|
id: blocking.id,
|
||||||
createdAt: this.idService.parse(blocking.id).date.toISOString(),
|
createdAt: this.idService.parse(blocking.id).date.toISOString(),
|
||||||
blockeeId: blocking.blockeeId,
|
blockeeId: blocking.blockeeId,
|
||||||
blockee: this.userEntityService.pack(blocking.blockeeId, me, {
|
blockee: hint?.blockee ?? this.userEntityService.pack(blocking.blockeeId, me, {
|
||||||
schema: 'UserDetailedNotMe',
|
schema: 'UserDetailedNotMe',
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public packMany(
|
public async packMany(
|
||||||
blockings: any[],
|
blockings: MiBlocking[],
|
||||||
me: { id: MiUser['id'] },
|
me: { id: MiUser['id'] },
|
||||||
) {
|
) {
|
||||||
return Promise.all(blockings.map(x => this.pack(x, me)));
|
const _blockees = blockings.map(({ blockee, blockeeId }) => blockee ?? blockeeId);
|
||||||
|
const _userMap = await this.userEntityService.packMany(_blockees, me, { schema: 'UserDetailedNotMe' })
|
||||||
|
.then(users => new Map(users.map(u => [u.id, u])));
|
||||||
|
return Promise.all(blockings.map(blocking => this.pack(blocking, me, { blockee: _userMap.get(blocking.blockeeId) })));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,6 +35,9 @@ export class ClipEntityService {
|
||||||
public async pack(
|
public async pack(
|
||||||
src: MiClip['id'] | MiClip,
|
src: MiClip['id'] | MiClip,
|
||||||
me?: { id: MiUser['id'] } | null | undefined,
|
me?: { id: MiUser['id'] } | null | undefined,
|
||||||
|
hint?: {
|
||||||
|
packedUser?: Packed<'UserLite'>
|
||||||
|
},
|
||||||
): Promise<Packed<'Clip'>> {
|
): Promise<Packed<'Clip'>> {
|
||||||
const meId = me ? me.id : null;
|
const meId = me ? me.id : null;
|
||||||
const clip = typeof src === 'object' ? src : await this.clipsRepository.findOneByOrFail({ id: src });
|
const clip = typeof src === 'object' ? src : await this.clipsRepository.findOneByOrFail({ id: src });
|
||||||
|
@ -44,7 +47,7 @@ export class ClipEntityService {
|
||||||
createdAt: this.idService.parse(clip.id).date.toISOString(),
|
createdAt: this.idService.parse(clip.id).date.toISOString(),
|
||||||
lastClippedAt: clip.lastClippedAt ? clip.lastClippedAt.toISOString() : null,
|
lastClippedAt: clip.lastClippedAt ? clip.lastClippedAt.toISOString() : null,
|
||||||
userId: clip.userId,
|
userId: clip.userId,
|
||||||
user: this.userEntityService.pack(clip.user ?? clip.userId),
|
user: hint?.packedUser ?? this.userEntityService.pack(clip.user ?? clip.userId),
|
||||||
name: clip.name,
|
name: clip.name,
|
||||||
description: clip.description,
|
description: clip.description,
|
||||||
isPublic: clip.isPublic,
|
isPublic: clip.isPublic,
|
||||||
|
@ -55,11 +58,14 @@ export class ClipEntityService {
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public packMany(
|
public async packMany(
|
||||||
clips: MiClip[],
|
clips: MiClip[],
|
||||||
me?: { id: MiUser['id'] } | null | undefined,
|
me?: { id: MiUser['id'] } | null | undefined,
|
||||||
) {
|
) {
|
||||||
return Promise.all(clips.map(x => this.pack(x, me)));
|
const _users = clips.map(({ user, userId }) => user ?? userId);
|
||||||
|
const _userMap = await this.userEntityService.packMany(_users, me)
|
||||||
|
.then(users => new Map(users.map(u => [u.id, u])));
|
||||||
|
return Promise.all(clips.map(clip => this.pack(clip, me, { packedUser: _userMap.get(clip.userId) })));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -222,6 +222,9 @@ export class DriveFileEntityService {
|
||||||
public async packNullable(
|
public async packNullable(
|
||||||
src: MiDriveFile['id'] | MiDriveFile,
|
src: MiDriveFile['id'] | MiDriveFile,
|
||||||
options?: PackOptions,
|
options?: PackOptions,
|
||||||
|
hint?: {
|
||||||
|
packedUser?: Packed<'UserLite'>
|
||||||
|
},
|
||||||
): Promise<Packed<'DriveFile'> | null> {
|
): Promise<Packed<'DriveFile'> | null> {
|
||||||
const opts = Object.assign({
|
const opts = Object.assign({
|
||||||
detail: false,
|
detail: false,
|
||||||
|
@ -249,7 +252,7 @@ export class DriveFileEntityService {
|
||||||
detail: true,
|
detail: true,
|
||||||
}) : null,
|
}) : null,
|
||||||
userId: file.userId,
|
userId: file.userId,
|
||||||
user: (opts.withUser && file.userId) ? this.userEntityService.pack(file.userId) : null,
|
user: (opts.withUser && file.userId) ? hint?.packedUser ?? this.userEntityService.pack(file.userId) : null,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -258,7 +261,10 @@ export class DriveFileEntityService {
|
||||||
files: MiDriveFile[],
|
files: MiDriveFile[],
|
||||||
options?: PackOptions,
|
options?: PackOptions,
|
||||||
): Promise<Packed<'DriveFile'>[]> {
|
): Promise<Packed<'DriveFile'>[]> {
|
||||||
const items = await Promise.all(files.map(f => this.packNullable(f, options)));
|
const _user = files.map(({ user, userId }) => user ?? userId).filter(isNotNull);
|
||||||
|
const _userMap = await this.userEntityService.packMany(_user)
|
||||||
|
.then(users => new Map(users.map(user => [user.id, user])));
|
||||||
|
const items = await Promise.all(files.map(f => this.packNullable(f, options, f.userId ? { packedUser: _userMap.get(f.userId) } : {})));
|
||||||
return items.filter(isNotNull);
|
return items.filter(isNotNull);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -33,6 +33,9 @@ export class FlashEntityService {
|
||||||
public async pack(
|
public async pack(
|
||||||
src: MiFlash['id'] | MiFlash,
|
src: MiFlash['id'] | MiFlash,
|
||||||
me?: { id: MiUser['id'] } | null | undefined,
|
me?: { id: MiUser['id'] } | null | undefined,
|
||||||
|
hint?: {
|
||||||
|
packedUser?: Packed<'UserLite'>
|
||||||
|
},
|
||||||
): Promise<Packed<'Flash'>> {
|
): Promise<Packed<'Flash'>> {
|
||||||
const meId = me ? me.id : null;
|
const meId = me ? me.id : null;
|
||||||
const flash = typeof src === 'object' ? src : await this.flashsRepository.findOneByOrFail({ id: src });
|
const flash = typeof src === 'object' ? src : await this.flashsRepository.findOneByOrFail({ id: src });
|
||||||
|
@ -42,7 +45,7 @@ export class FlashEntityService {
|
||||||
createdAt: this.idService.parse(flash.id).date.toISOString(),
|
createdAt: this.idService.parse(flash.id).date.toISOString(),
|
||||||
updatedAt: flash.updatedAt.toISOString(),
|
updatedAt: flash.updatedAt.toISOString(),
|
||||||
userId: flash.userId,
|
userId: flash.userId,
|
||||||
user: this.userEntityService.pack(flash.user ?? flash.userId, me), // { schema: 'UserDetailed' } すると無限ループするので注意
|
user: hint?.packedUser ?? this.userEntityService.pack(flash.user ?? flash.userId, me), // { schema: 'UserDetailed' } すると無限ループするので注意
|
||||||
title: flash.title,
|
title: flash.title,
|
||||||
summary: flash.summary,
|
summary: flash.summary,
|
||||||
script: flash.script,
|
script: flash.script,
|
||||||
|
@ -52,11 +55,14 @@ export class FlashEntityService {
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public packMany(
|
public async packMany(
|
||||||
flashs: MiFlash[],
|
flashes: MiFlash[],
|
||||||
me?: { id: MiUser['id'] } | null | undefined,
|
me?: { id: MiUser['id'] } | null | undefined,
|
||||||
) {
|
) {
|
||||||
return Promise.all(flashs.map(x => this.pack(x, me)));
|
const _users = flashes.map(({ user, userId }) => user ?? userId);
|
||||||
|
const _userMap = await this.userEntityService.packMany(_users, me)
|
||||||
|
.then(users => new Map(users.map(u => [u.id, u])));
|
||||||
|
return Promise.all(flashes.map(flash => this.pack(flash, me, { packedUser: _userMap.get(flash.userId) })));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ import type { } from '@/models/Blocking.js';
|
||||||
import type { MiUser } from '@/models/User.js';
|
import type { MiUser } from '@/models/User.js';
|
||||||
import type { MiFollowRequest } from '@/models/FollowRequest.js';
|
import type { MiFollowRequest } from '@/models/FollowRequest.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
|
import type { Packed } from '@/misc/json-schema.js';
|
||||||
import { UserEntityService } from './UserEntityService.js';
|
import { UserEntityService } from './UserEntityService.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
|
@ -26,14 +27,36 @@ export class FollowRequestEntityService {
|
||||||
public async pack(
|
public async pack(
|
||||||
src: MiFollowRequest['id'] | MiFollowRequest,
|
src: MiFollowRequest['id'] | MiFollowRequest,
|
||||||
me?: { id: MiUser['id'] } | null | undefined,
|
me?: { id: MiUser['id'] } | null | undefined,
|
||||||
|
hint?: {
|
||||||
|
packedFollower?: Packed<'UserLite'>,
|
||||||
|
packedFollowee?: Packed<'UserLite'>,
|
||||||
|
},
|
||||||
) {
|
) {
|
||||||
const request = typeof src === 'object' ? src : await this.followRequestsRepository.findOneByOrFail({ id: src });
|
const request = typeof src === 'object' ? src : await this.followRequestsRepository.findOneByOrFail({ id: src });
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: request.id,
|
id: request.id,
|
||||||
follower: await this.userEntityService.pack(request.followerId, me),
|
follower: hint?.packedFollower ?? await this.userEntityService.pack(request.followerId, me),
|
||||||
followee: await this.userEntityService.pack(request.followeeId, me),
|
followee: hint?.packedFollowee ?? await this.userEntityService.pack(request.followeeId, me),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public async packMany(
|
||||||
|
requests: MiFollowRequest[],
|
||||||
|
me?: { id: MiUser['id'] } | null | undefined,
|
||||||
|
) {
|
||||||
|
const _followers = requests.map(({ follower, followerId }) => follower ?? followerId);
|
||||||
|
const _followees = requests.map(({ followee, followeeId }) => followee ?? followeeId);
|
||||||
|
const _userMap = await this.userEntityService.packMany([..._followers, ..._followees], me)
|
||||||
|
.then(users => new Map(users.map(u => [u.id, u])));
|
||||||
|
return Promise.all(
|
||||||
|
requests.map(req => {
|
||||||
|
const packedFollower = _userMap.get(req.followerId);
|
||||||
|
const packedFollowee = _userMap.get(req.followeeId);
|
||||||
|
return this.pack(req, me, { packedFollower, packedFollowee });
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -78,6 +78,10 @@ export class FollowingEntityService {
|
||||||
populateFollowee?: boolean;
|
populateFollowee?: boolean;
|
||||||
populateFollower?: boolean;
|
populateFollower?: boolean;
|
||||||
},
|
},
|
||||||
|
hint?: {
|
||||||
|
packedFollowee?: Packed<'UserDetailedNotMe'>,
|
||||||
|
packedFollower?: Packed<'UserDetailedNotMe'>,
|
||||||
|
},
|
||||||
): Promise<Packed<'Following'>> {
|
): Promise<Packed<'Following'>> {
|
||||||
const following = typeof src === 'object' ? src : await this.followingsRepository.findOneByOrFail({ id: src });
|
const following = typeof src === 'object' ? src : await this.followingsRepository.findOneByOrFail({ id: src });
|
||||||
|
|
||||||
|
@ -88,25 +92,35 @@ export class FollowingEntityService {
|
||||||
createdAt: this.idService.parse(following.id).date.toISOString(),
|
createdAt: this.idService.parse(following.id).date.toISOString(),
|
||||||
followeeId: following.followeeId,
|
followeeId: following.followeeId,
|
||||||
followerId: following.followerId,
|
followerId: following.followerId,
|
||||||
followee: opts.populateFollowee ? this.userEntityService.pack(following.followee ?? following.followeeId, me, {
|
followee: opts.populateFollowee ? hint?.packedFollowee ?? this.userEntityService.pack(following.followee ?? following.followeeId, me, {
|
||||||
schema: 'UserDetailedNotMe',
|
schema: 'UserDetailedNotMe',
|
||||||
}) : undefined,
|
}) : undefined,
|
||||||
follower: opts.populateFollower ? this.userEntityService.pack(following.follower ?? following.followerId, me, {
|
follower: opts.populateFollower ? hint?.packedFollower ?? this.userEntityService.pack(following.follower ?? following.followerId, me, {
|
||||||
schema: 'UserDetailedNotMe',
|
schema: 'UserDetailedNotMe',
|
||||||
}) : undefined,
|
}) : undefined,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public packMany(
|
public async packMany(
|
||||||
followings: any[],
|
followings: MiFollowing[],
|
||||||
me?: { id: MiUser['id'] } | null | undefined,
|
me?: { id: MiUser['id'] } | null | undefined,
|
||||||
opts?: {
|
opts?: {
|
||||||
populateFollowee?: boolean;
|
populateFollowee?: boolean;
|
||||||
populateFollower?: boolean;
|
populateFollower?: boolean;
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
return Promise.all(followings.map(x => this.pack(x, me, opts)));
|
const _followees = opts?.populateFollowee ? followings.map(({ followee, followeeId }) => followee ?? followeeId) : [];
|
||||||
|
const _followers = opts?.populateFollower ? followings.map(({ follower, followerId }) => follower ?? followerId) : [];
|
||||||
|
const _userMap = await this.userEntityService.packMany([..._followees, ..._followers], me, { schema: 'UserDetailedNotMe' })
|
||||||
|
.then(users => new Map(users.map(u => [u.id, u])));
|
||||||
|
return Promise.all(
|
||||||
|
followings.map(following => {
|
||||||
|
const packedFollowee = opts?.populateFollowee ? _userMap.get(following.followeeId) : undefined;
|
||||||
|
const packedFollower = opts?.populateFollower ? _userMap.get(following.followerId) : undefined;
|
||||||
|
return this.pack(following, me, opts, { packedFollowee, packedFollower });
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -35,6 +35,9 @@ export class GalleryPostEntityService {
|
||||||
public async pack(
|
public async pack(
|
||||||
src: MiGalleryPost['id'] | MiGalleryPost,
|
src: MiGalleryPost['id'] | MiGalleryPost,
|
||||||
me?: { id: MiUser['id'] } | null | undefined,
|
me?: { id: MiUser['id'] } | null | undefined,
|
||||||
|
hint?: {
|
||||||
|
packedUser?: Packed<'UserLite'>
|
||||||
|
},
|
||||||
): Promise<Packed<'GalleryPost'>> {
|
): Promise<Packed<'GalleryPost'>> {
|
||||||
const meId = me ? me.id : null;
|
const meId = me ? me.id : null;
|
||||||
const post = typeof src === 'object' ? src : await this.galleryPostsRepository.findOneByOrFail({ id: src });
|
const post = typeof src === 'object' ? src : await this.galleryPostsRepository.findOneByOrFail({ id: src });
|
||||||
|
@ -44,7 +47,7 @@ export class GalleryPostEntityService {
|
||||||
createdAt: this.idService.parse(post.id).date.toISOString(),
|
createdAt: this.idService.parse(post.id).date.toISOString(),
|
||||||
updatedAt: post.updatedAt.toISOString(),
|
updatedAt: post.updatedAt.toISOString(),
|
||||||
userId: post.userId,
|
userId: post.userId,
|
||||||
user: this.userEntityService.pack(post.user ?? post.userId, me),
|
user: hint?.packedUser ?? this.userEntityService.pack(post.user ?? post.userId, me),
|
||||||
title: post.title,
|
title: post.title,
|
||||||
description: post.description,
|
description: post.description,
|
||||||
fileIds: post.fileIds,
|
fileIds: post.fileIds,
|
||||||
|
@ -58,11 +61,14 @@ export class GalleryPostEntityService {
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public packMany(
|
public async packMany(
|
||||||
posts: MiGalleryPost[],
|
posts: MiGalleryPost[],
|
||||||
me?: { id: MiUser['id'] } | null | undefined,
|
me?: { id: MiUser['id'] } | null | undefined,
|
||||||
) {
|
) {
|
||||||
return Promise.all(posts.map(x => this.pack(x, me)));
|
const _users = posts.map(({ user, userId }) => user ?? userId);
|
||||||
|
const _userMap = await this.userEntityService.packMany(_users, me)
|
||||||
|
.then(users => new Map(users.map(u => [u.id, u])));
|
||||||
|
return Promise.all(posts.map(post => this.pack(post, me, { packedUser: _userMap.get(post.userId) })));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ import type { MiUser } from '@/models/User.js';
|
||||||
import type { MiRegistrationTicket } from '@/models/RegistrationTicket.js';
|
import type { MiRegistrationTicket } from '@/models/RegistrationTicket.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { IdService } from '@/core/IdService.js';
|
import { IdService } from '@/core/IdService.js';
|
||||||
|
import { isNotNull } from '@/misc/is-not-null.js';
|
||||||
import { UserEntityService } from './UserEntityService.js';
|
import { UserEntityService } from './UserEntityService.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
|
@ -29,6 +30,10 @@ export class InviteCodeEntityService {
|
||||||
public async pack(
|
public async pack(
|
||||||
src: MiRegistrationTicket['id'] | MiRegistrationTicket,
|
src: MiRegistrationTicket['id'] | MiRegistrationTicket,
|
||||||
me?: { id: MiUser['id'] } | null | undefined,
|
me?: { id: MiUser['id'] } | null | undefined,
|
||||||
|
hints?: {
|
||||||
|
packedCreatedBy?: Packed<'UserLite'>,
|
||||||
|
packedUsedBy?: Packed<'UserLite'>,
|
||||||
|
},
|
||||||
): Promise<Packed<'InviteCode'>> {
|
): Promise<Packed<'InviteCode'>> {
|
||||||
const target = typeof src === 'object' ? src : await this.registrationTicketsRepository.findOneOrFail({
|
const target = typeof src === 'object' ? src : await this.registrationTicketsRepository.findOneOrFail({
|
||||||
where: {
|
where: {
|
||||||
|
@ -42,18 +47,28 @@ export class InviteCodeEntityService {
|
||||||
code: target.code,
|
code: target.code,
|
||||||
expiresAt: target.expiresAt ? target.expiresAt.toISOString() : null,
|
expiresAt: target.expiresAt ? target.expiresAt.toISOString() : null,
|
||||||
createdAt: this.idService.parse(target.id).date.toISOString(),
|
createdAt: this.idService.parse(target.id).date.toISOString(),
|
||||||
createdBy: target.createdBy ? await this.userEntityService.pack(target.createdBy, me) : null,
|
createdBy: target.createdBy ? hints?.packedCreatedBy ?? await this.userEntityService.pack(target.createdBy, me) : null,
|
||||||
usedBy: target.usedBy ? await this.userEntityService.pack(target.usedBy, me) : null,
|
usedBy: target.usedBy ? hints?.packedUsedBy ?? await this.userEntityService.pack(target.usedBy, me) : null,
|
||||||
usedAt: target.usedAt ? target.usedAt.toISOString() : null,
|
usedAt: target.usedAt ? target.usedAt.toISOString() : null,
|
||||||
used: !!target.usedAt,
|
used: !!target.usedAt,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public packMany(
|
public async packMany(
|
||||||
targets: any[],
|
tickets: MiRegistrationTicket[],
|
||||||
me: { id: MiUser['id'] },
|
me: { id: MiUser['id'] },
|
||||||
) {
|
) {
|
||||||
return Promise.all(targets.map(x => this.pack(x, me)));
|
const _createdBys = tickets.map(({ createdBy, createdById }) => createdBy ?? createdById).filter(isNotNull);
|
||||||
|
const _usedBys = tickets.map(({ usedBy, usedById }) => usedBy ?? usedById).filter(isNotNull);
|
||||||
|
const _userMap = await this.userEntityService.packMany([..._createdBys, ..._usedBys], me)
|
||||||
|
.then(users => new Map(users.map(u => [u.id, u])));
|
||||||
|
return Promise.all(
|
||||||
|
tickets.map(ticket => {
|
||||||
|
const packedCreatedBy = ticket.createdById != null ? _userMap.get(ticket.createdById) : undefined;
|
||||||
|
const packedUsedBy = ticket.usedById != null ? _userMap.get(ticket.usedById) : undefined;
|
||||||
|
return this.pack(ticket, me, { packedCreatedBy, packedUsedBy });
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,9 +8,10 @@ import { DI } from '@/di-symbols.js';
|
||||||
import type { ModerationLogsRepository } from '@/models/_.js';
|
import type { ModerationLogsRepository } from '@/models/_.js';
|
||||||
import { awaitAll } from '@/misc/prelude/await-all.js';
|
import { awaitAll } from '@/misc/prelude/await-all.js';
|
||||||
import type { } from '@/models/Blocking.js';
|
import type { } from '@/models/Blocking.js';
|
||||||
import type { MiModerationLog } from '@/models/ModerationLog.js';
|
import { MiModerationLog } from '@/models/ModerationLog.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { IdService } from '@/core/IdService.js';
|
import { IdService } from '@/core/IdService.js';
|
||||||
|
import type { Packed } from '@/misc/json-schema.js';
|
||||||
import { UserEntityService } from './UserEntityService.js';
|
import { UserEntityService } from './UserEntityService.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
|
@ -27,6 +28,9 @@ export class ModerationLogEntityService {
|
||||||
@bindThis
|
@bindThis
|
||||||
public async pack(
|
public async pack(
|
||||||
src: MiModerationLog['id'] | MiModerationLog,
|
src: MiModerationLog['id'] | MiModerationLog,
|
||||||
|
hint?: {
|
||||||
|
packedUser?: Packed<'UserDetailedNotMe'>,
|
||||||
|
},
|
||||||
) {
|
) {
|
||||||
const log = typeof src === 'object' ? src : await this.moderationLogsRepository.findOneByOrFail({ id: src });
|
const log = typeof src === 'object' ? src : await this.moderationLogsRepository.findOneByOrFail({ id: src });
|
||||||
|
|
||||||
|
@ -36,17 +40,20 @@ export class ModerationLogEntityService {
|
||||||
type: log.type,
|
type: log.type,
|
||||||
info: log.info,
|
info: log.info,
|
||||||
userId: log.userId,
|
userId: log.userId,
|
||||||
user: this.userEntityService.pack(log.user ?? log.userId, null, {
|
user: hint?.packedUser ?? this.userEntityService.pack(log.user ?? log.userId, null, {
|
||||||
schema: 'UserDetailedNotMe',
|
schema: 'UserDetailedNotMe',
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public packMany(
|
public async packMany(
|
||||||
reports: any[],
|
reports: MiModerationLog[],
|
||||||
) {
|
) {
|
||||||
return Promise.all(reports.map(x => this.pack(x)));
|
const _users = reports.map(({ user, userId }) => user ?? userId);
|
||||||
|
const _userMap = await this.userEntityService.packMany(_users, null, { schema: 'UserDetailedNotMe' })
|
||||||
|
.then(users => new Map(users.map(u => [u.id, u])));
|
||||||
|
return Promise.all(reports.map(report => this.pack(report, { packedUser: _userMap.get(report.userId) })));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,6 +30,9 @@ export class MutingEntityService {
|
||||||
public async pack(
|
public async pack(
|
||||||
src: MiMuting['id'] | MiMuting,
|
src: MiMuting['id'] | MiMuting,
|
||||||
me?: { id: MiUser['id'] } | null | undefined,
|
me?: { id: MiUser['id'] } | null | undefined,
|
||||||
|
hints?: {
|
||||||
|
packedMutee?: Packed<'UserDetailedNotMe'>,
|
||||||
|
},
|
||||||
): Promise<Packed<'Muting'>> {
|
): Promise<Packed<'Muting'>> {
|
||||||
const muting = typeof src === 'object' ? src : await this.mutingsRepository.findOneByOrFail({ id: src });
|
const muting = typeof src === 'object' ? src : await this.mutingsRepository.findOneByOrFail({ id: src });
|
||||||
|
|
||||||
|
@ -38,18 +41,21 @@ export class MutingEntityService {
|
||||||
createdAt: this.idService.parse(muting.id).date.toISOString(),
|
createdAt: this.idService.parse(muting.id).date.toISOString(),
|
||||||
expiresAt: muting.expiresAt ? muting.expiresAt.toISOString() : null,
|
expiresAt: muting.expiresAt ? muting.expiresAt.toISOString() : null,
|
||||||
muteeId: muting.muteeId,
|
muteeId: muting.muteeId,
|
||||||
mutee: this.userEntityService.pack(muting.muteeId, me, {
|
mutee: hints?.packedMutee ?? this.userEntityService.pack(muting.muteeId, me, {
|
||||||
schema: 'UserDetailedNotMe',
|
schema: 'UserDetailedNotMe',
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public packMany(
|
public async packMany(
|
||||||
mutings: any[],
|
mutings: MiMuting[],
|
||||||
me: { id: MiUser['id'] },
|
me: { id: MiUser['id'] },
|
||||||
) {
|
) {
|
||||||
return Promise.all(mutings.map(x => this.pack(x, me)));
|
const _mutees = mutings.map(({ mutee, muteeId }) => mutee ?? muteeId);
|
||||||
|
const _userMap = await this.userEntityService.packMany(_mutees, me, { schema: 'UserDetailedNotMe' })
|
||||||
|
.then(users => new Map(users.map(u => [u.id, u])));
|
||||||
|
return Promise.all(mutings.map(muting => this.pack(muting, me, { packedMutee: _userMap.get(muting.muteeId) })));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -290,6 +290,7 @@ export class NoteEntityService implements OnModuleInit {
|
||||||
_hint_?: {
|
_hint_?: {
|
||||||
myReactions: Map<MiNote['id'], string | null>;
|
myReactions: Map<MiNote['id'], string | null>;
|
||||||
packedFiles: Map<MiNote['fileIds'][number], Packed<'DriveFile'> | null>;
|
packedFiles: Map<MiNote['fileIds'][number], Packed<'DriveFile'> | null>;
|
||||||
|
packedUsers: Map<MiUser['id'], Packed<'UserLite'>>
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
): Promise<Packed<'Note'>> {
|
): Promise<Packed<'Note'>> {
|
||||||
|
@ -319,12 +320,13 @@ export class NoteEntityService implements OnModuleInit {
|
||||||
.filter(x => x.startsWith(':') && x.includes('@') && !x.includes('@.')) // リモートカスタム絵文字のみ
|
.filter(x => x.startsWith(':') && x.includes('@') && !x.includes('@.')) // リモートカスタム絵文字のみ
|
||||||
.map(x => this.reactionService.decodeReaction(x).reaction.replaceAll(':', ''));
|
.map(x => this.reactionService.decodeReaction(x).reaction.replaceAll(':', ''));
|
||||||
const packedFiles = options?._hint_?.packedFiles;
|
const packedFiles = options?._hint_?.packedFiles;
|
||||||
|
const packedUsers = options?._hint_?.packedUsers;
|
||||||
|
|
||||||
const packed: Packed<'Note'> = await awaitAll({
|
const packed: Packed<'Note'> = await awaitAll({
|
||||||
id: note.id,
|
id: note.id,
|
||||||
createdAt: this.idService.parse(note.id).date.toISOString(),
|
createdAt: this.idService.parse(note.id).date.toISOString(),
|
||||||
userId: note.userId,
|
userId: note.userId,
|
||||||
user: this.userEntityService.pack(note.user ?? note.userId, me),
|
user: packedUsers?.get(note.userId) ?? this.userEntityService.pack(note.user ?? note.userId, me),
|
||||||
text: text,
|
text: text,
|
||||||
cw: note.cw,
|
cw: note.cw,
|
||||||
visibility: note.visibility,
|
visibility: note.visibility,
|
||||||
|
@ -449,12 +451,20 @@ export class NoteEntityService implements OnModuleInit {
|
||||||
// TODO: 本当は renote とか reply がないのに renoteId とか replyId があったらここで解決しておく
|
// TODO: 本当は renote とか reply がないのに renoteId とか replyId があったらここで解決しておく
|
||||||
const fileIds = notes.map(n => [n.fileIds, n.renote?.fileIds, n.reply?.fileIds]).flat(2).filter(isNotNull);
|
const fileIds = notes.map(n => [n.fileIds, n.renote?.fileIds, n.reply?.fileIds]).flat(2).filter(isNotNull);
|
||||||
const packedFiles = fileIds.length > 0 ? await this.driveFileEntityService.packManyByIdsMap(fileIds) : new Map();
|
const packedFiles = fileIds.length > 0 ? await this.driveFileEntityService.packManyByIdsMap(fileIds) : new Map();
|
||||||
|
const users = [
|
||||||
|
...notes.map(({ user, userId }) => user ?? userId),
|
||||||
|
...notes.map(({ replyUserId }) => replyUserId).filter(isNotNull),
|
||||||
|
...notes.map(({ renoteUserId }) => renoteUserId).filter(isNotNull),
|
||||||
|
];
|
||||||
|
const packedUsers = await this.userEntityService.packMany(users, me)
|
||||||
|
.then(users => new Map(users.map(u => [u.id, u])));
|
||||||
|
|
||||||
return await Promise.all(notes.map(n => this.pack(n, me, {
|
return await Promise.all(notes.map(n => this.pack(n, me, {
|
||||||
...options,
|
...options,
|
||||||
_hint_: {
|
_hint_: {
|
||||||
myReactions: myReactionsMap,
|
myReactions: myReactionsMap,
|
||||||
packedFiles,
|
packedFiles,
|
||||||
|
packedUsers,
|
||||||
},
|
},
|
||||||
})));
|
})));
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,6 +52,9 @@ export class NoteReactionEntityService implements OnModuleInit {
|
||||||
options?: {
|
options?: {
|
||||||
withNote: boolean;
|
withNote: boolean;
|
||||||
},
|
},
|
||||||
|
hints?: {
|
||||||
|
packedUser?: Packed<'UserLite'>
|
||||||
|
},
|
||||||
): Promise<Packed<'NoteReaction'>> {
|
): Promise<Packed<'NoteReaction'>> {
|
||||||
const opts = Object.assign({
|
const opts = Object.assign({
|
||||||
withNote: false,
|
withNote: false,
|
||||||
|
@ -62,7 +65,7 @@ export class NoteReactionEntityService implements OnModuleInit {
|
||||||
return {
|
return {
|
||||||
id: reaction.id,
|
id: reaction.id,
|
||||||
createdAt: this.idService.parse(reaction.id).date.toISOString(),
|
createdAt: this.idService.parse(reaction.id).date.toISOString(),
|
||||||
user: await this.userEntityService.pack(reaction.user ?? reaction.userId, me),
|
user: hints?.packedUser ?? await this.userEntityService.pack(reaction.user ?? reaction.userId, me),
|
||||||
type: this.reactionService.convertLegacyReaction(reaction.reaction),
|
type: this.reactionService.convertLegacyReaction(reaction.reaction),
|
||||||
...(opts.withNote ? {
|
...(opts.withNote ? {
|
||||||
note: await this.noteEntityService.pack(reaction.note ?? reaction.noteId, me),
|
note: await this.noteEntityService.pack(reaction.note ?? reaction.noteId, me),
|
||||||
|
@ -81,7 +84,9 @@ export class NoteReactionEntityService implements OnModuleInit {
|
||||||
const opts = Object.assign({
|
const opts = Object.assign({
|
||||||
withNote: false,
|
withNote: false,
|
||||||
}, options);
|
}, options);
|
||||||
|
const _users = reactions.map(({ user, userId }) => user ?? userId);
|
||||||
return Promise.all(reactions.map(reaction => this.pack(reaction, me, opts)));
|
const _userMap = await this.userEntityService.packMany(_users, me)
|
||||||
|
.then(users => new Map(users.map(u => [u.id, u])));
|
||||||
|
return Promise.all(reactions.map(reaction => this.pack(reaction, me, opts, { packedUser: _userMap.get(reaction.userId) })));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,6 +40,9 @@ export class PageEntityService {
|
||||||
public async pack(
|
public async pack(
|
||||||
src: MiPage['id'] | MiPage,
|
src: MiPage['id'] | MiPage,
|
||||||
me?: { id: MiUser['id'] } | null | undefined,
|
me?: { id: MiUser['id'] } | null | undefined,
|
||||||
|
hint?: {
|
||||||
|
packedUser?: Packed<'UserLite'>
|
||||||
|
},
|
||||||
): Promise<Packed<'Page'>> {
|
): Promise<Packed<'Page'>> {
|
||||||
const meId = me ? me.id : null;
|
const meId = me ? me.id : null;
|
||||||
const page = typeof src === 'object' ? src : await this.pagesRepository.findOneByOrFail({ id: src });
|
const page = typeof src === 'object' ? src : await this.pagesRepository.findOneByOrFail({ id: src });
|
||||||
|
@ -91,7 +94,7 @@ export class PageEntityService {
|
||||||
createdAt: this.idService.parse(page.id).date.toISOString(),
|
createdAt: this.idService.parse(page.id).date.toISOString(),
|
||||||
updatedAt: page.updatedAt.toISOString(),
|
updatedAt: page.updatedAt.toISOString(),
|
||||||
userId: page.userId,
|
userId: page.userId,
|
||||||
user: this.userEntityService.pack(page.user ?? page.userId, me), // { schema: 'UserDetailed' } すると無限ループするので注意
|
user: hint?.packedUser ?? this.userEntityService.pack(page.user ?? page.userId, me), // { schema: 'UserDetailed' } すると無限ループするので注意
|
||||||
content: page.content,
|
content: page.content,
|
||||||
variables: page.variables,
|
variables: page.variables,
|
||||||
title: page.title,
|
title: page.title,
|
||||||
|
@ -110,11 +113,14 @@ export class PageEntityService {
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public packMany(
|
public async packMany(
|
||||||
pages: MiPage[],
|
pages: MiPage[],
|
||||||
me?: { id: MiUser['id'] } | null | undefined,
|
me?: { id: MiUser['id'] } | null | undefined,
|
||||||
) {
|
) {
|
||||||
return Promise.all(pages.map(x => this.pack(x, me)));
|
const _users = pages.map(({ user, userId }) => user ?? userId);
|
||||||
|
const _userMap = await this.userEntityService.packMany(_users, me)
|
||||||
|
.then(users => new Map(users.map(u => [u.id, u])));
|
||||||
|
return Promise.all(pages.map(page => this.pack(page, me, { packedUser: _userMap.get(page.userId) })));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,6 +30,9 @@ export class RenoteMutingEntityService {
|
||||||
public async pack(
|
public async pack(
|
||||||
src: MiRenoteMuting['id'] | MiRenoteMuting,
|
src: MiRenoteMuting['id'] | MiRenoteMuting,
|
||||||
me?: { id: MiUser['id'] } | null | undefined,
|
me?: { id: MiUser['id'] } | null | undefined,
|
||||||
|
hints?: {
|
||||||
|
packedMutee?: Packed<'UserDetailedNotMe'>
|
||||||
|
},
|
||||||
): Promise<Packed<'RenoteMuting'>> {
|
): Promise<Packed<'RenoteMuting'>> {
|
||||||
const muting = typeof src === 'object' ? src : await this.renoteMutingsRepository.findOneByOrFail({ id: src });
|
const muting = typeof src === 'object' ? src : await this.renoteMutingsRepository.findOneByOrFail({ id: src });
|
||||||
|
|
||||||
|
@ -37,18 +40,21 @@ export class RenoteMutingEntityService {
|
||||||
id: muting.id,
|
id: muting.id,
|
||||||
createdAt: this.idService.parse(muting.id).date.toISOString(),
|
createdAt: this.idService.parse(muting.id).date.toISOString(),
|
||||||
muteeId: muting.muteeId,
|
muteeId: muting.muteeId,
|
||||||
mutee: this.userEntityService.pack(muting.muteeId, me, {
|
mutee: hints?.packedMutee ?? this.userEntityService.pack(muting.muteeId, me, {
|
||||||
schema: 'UserDetailedNotMe',
|
schema: 'UserDetailedNotMe',
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public packMany(
|
public async packMany(
|
||||||
mutings: any[],
|
mutings: MiRenoteMuting[],
|
||||||
me: { id: MiUser['id'] },
|
me: { id: MiUser['id'] },
|
||||||
) {
|
) {
|
||||||
return Promise.all(mutings.map(x => this.pack(x, me)));
|
const _users = mutings.map(({ mutee, muteeId }) => mutee ?? muteeId);
|
||||||
|
const _userMap = await this.userEntityService.packMany(_users, me, { schema: 'UserDetailedNotMe' })
|
||||||
|
.then(users => new Map(users.map(u => [u.id, u])));
|
||||||
|
return Promise.all(mutings.map(muting => this.pack(muting, me, { packedMutee: _userMap.get(muting.muteeId) })));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -28,13 +28,15 @@ export class ReversiGameEntityService {
|
||||||
@bindThis
|
@bindThis
|
||||||
public async packDetail(
|
public async packDetail(
|
||||||
src: MiReversiGame['id'] | MiReversiGame,
|
src: MiReversiGame['id'] | MiReversiGame,
|
||||||
|
hint?: {
|
||||||
|
packedUser1?: Packed<'UserLite'>,
|
||||||
|
packedUser2?: Packed<'UserLite'>,
|
||||||
|
},
|
||||||
): Promise<Packed<'ReversiGameDetailed'>> {
|
): Promise<Packed<'ReversiGameDetailed'>> {
|
||||||
const game = typeof src === 'object' ? src : await this.reversiGamesRepository.findOneByOrFail({ id: src });
|
const game = typeof src === 'object' ? src : await this.reversiGamesRepository.findOneByOrFail({ id: src });
|
||||||
|
|
||||||
const users = await Promise.all([
|
const user1 = hint?.packedUser1 ?? await this.userEntityService.pack(game.user1 ?? game.user1Id);
|
||||||
this.userEntityService.pack(game.user1 ?? game.user1Id),
|
const user2 = hint?.packedUser2 ?? await this.userEntityService.pack(game.user2 ?? game.user2Id);
|
||||||
this.userEntityService.pack(game.user2 ?? game.user2Id),
|
|
||||||
]);
|
|
||||||
|
|
||||||
return await awaitAll({
|
return await awaitAll({
|
||||||
id: game.id,
|
id: game.id,
|
||||||
|
@ -49,10 +51,10 @@ export class ReversiGameEntityService {
|
||||||
user2Ready: game.user2Ready,
|
user2Ready: game.user2Ready,
|
||||||
user1Id: game.user1Id,
|
user1Id: game.user1Id,
|
||||||
user2Id: game.user2Id,
|
user2Id: game.user2Id,
|
||||||
user1: users[0],
|
user1,
|
||||||
user2: users[1],
|
user2,
|
||||||
winnerId: game.winnerId,
|
winnerId: game.winnerId,
|
||||||
winner: game.winnerId ? users.find(u => u.id === game.winnerId)! : null,
|
winner: game.winnerId ? [user1, user2].find(u => u.id === game.winnerId)! : null,
|
||||||
surrenderedUserId: game.surrenderedUserId,
|
surrenderedUserId: game.surrenderedUserId,
|
||||||
timeoutUserId: game.timeoutUserId,
|
timeoutUserId: game.timeoutUserId,
|
||||||
black: game.black,
|
black: game.black,
|
||||||
|
@ -68,22 +70,35 @@ export class ReversiGameEntityService {
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public packDetailMany(
|
public async packDetailMany(
|
||||||
xs: MiReversiGame[],
|
games: MiReversiGame[],
|
||||||
) {
|
) {
|
||||||
return Promise.all(xs.map(x => this.packDetail(x)));
|
const _user1s = games.map(({ user1, user1Id }) => user1 ?? user1Id);
|
||||||
|
const _user2s = games.map(({ user2, user2Id }) => user2 ?? user2Id);
|
||||||
|
const _userMap = await this.userEntityService.packMany([..._user1s, ..._user2s])
|
||||||
|
.then(users => new Map(users.map(u => [u.id, u])));
|
||||||
|
return Promise.all(
|
||||||
|
games.map(game => {
|
||||||
|
return this.packDetail(game, {
|
||||||
|
packedUser1: _userMap.get(game.user1Id),
|
||||||
|
packedUser2: _userMap.get(game.user2Id),
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async packLite(
|
public async packLite(
|
||||||
src: MiReversiGame['id'] | MiReversiGame,
|
src: MiReversiGame['id'] | MiReversiGame,
|
||||||
|
hint?: {
|
||||||
|
packedUser1?: Packed<'UserLite'>,
|
||||||
|
packedUser2?: Packed<'UserLite'>,
|
||||||
|
},
|
||||||
): Promise<Packed<'ReversiGameLite'>> {
|
): Promise<Packed<'ReversiGameLite'>> {
|
||||||
const game = typeof src === 'object' ? src : await this.reversiGamesRepository.findOneByOrFail({ id: src });
|
const game = typeof src === 'object' ? src : await this.reversiGamesRepository.findOneByOrFail({ id: src });
|
||||||
|
|
||||||
const users = await Promise.all([
|
const user1 = hint?.packedUser1 ?? await this.userEntityService.pack(game.user1 ?? game.user1Id);
|
||||||
this.userEntityService.pack(game.user1 ?? game.user1Id),
|
const user2 = hint?.packedUser2 ?? await this.userEntityService.pack(game.user2 ?? game.user2Id);
|
||||||
this.userEntityService.pack(game.user2 ?? game.user2Id),
|
|
||||||
]);
|
|
||||||
|
|
||||||
return await awaitAll({
|
return await awaitAll({
|
||||||
id: game.id,
|
id: game.id,
|
||||||
|
@ -94,10 +109,10 @@ export class ReversiGameEntityService {
|
||||||
isEnded: game.isEnded,
|
isEnded: game.isEnded,
|
||||||
user1Id: game.user1Id,
|
user1Id: game.user1Id,
|
||||||
user2Id: game.user2Id,
|
user2Id: game.user2Id,
|
||||||
user1: users[0],
|
user1,
|
||||||
user2: users[1],
|
user2,
|
||||||
winnerId: game.winnerId,
|
winnerId: game.winnerId,
|
||||||
winner: game.winnerId ? users.find(u => u.id === game.winnerId)! : null,
|
winner: game.winnerId ? [user1, user2].find(u => u.id === game.winnerId)! : null,
|
||||||
surrenderedUserId: game.surrenderedUserId,
|
surrenderedUserId: game.surrenderedUserId,
|
||||||
timeoutUserId: game.timeoutUserId,
|
timeoutUserId: game.timeoutUserId,
|
||||||
black: game.black,
|
black: game.black,
|
||||||
|
@ -111,10 +126,21 @@ export class ReversiGameEntityService {
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public packLiteMany(
|
public async packLiteMany(
|
||||||
xs: MiReversiGame[],
|
games: MiReversiGame[],
|
||||||
) {
|
) {
|
||||||
return Promise.all(xs.map(x => this.packLite(x)));
|
const _user1s = games.map(({ user1, user1Id }) => user1 ?? user1Id);
|
||||||
|
const _user2s = games.map(({ user2, user2Id }) => user2 ?? user2Id);
|
||||||
|
const _userMap = await this.userEntityService.packMany([..._user1s, ..._user2s])
|
||||||
|
.then(users => new Map(users.map(u => [u.id, u])));
|
||||||
|
return Promise.all(
|
||||||
|
games.map(game => {
|
||||||
|
return this.packLite(game, {
|
||||||
|
packedUser1: _userMap.get(game.user1Id),
|
||||||
|
packedUser2: _userMap.get(game.user2Id),
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -50,11 +50,14 @@ export class UserListEntityService {
|
||||||
public async packMembershipsMany(
|
public async packMembershipsMany(
|
||||||
memberships: MiUserListMembership[],
|
memberships: MiUserListMembership[],
|
||||||
) {
|
) {
|
||||||
|
const _users = memberships.map(({ user, userId }) => user ?? userId);
|
||||||
|
const _userMap = await this.userEntityService.packMany(_users)
|
||||||
|
.then(users => new Map(users.map(u => [u.id, u])));
|
||||||
return Promise.all(memberships.map(async x => ({
|
return Promise.all(memberships.map(async x => ({
|
||||||
id: x.id,
|
id: x.id,
|
||||||
createdAt: this.idService.parse(x.id).date.toISOString(),
|
createdAt: this.idService.parse(x.id).date.toISOString(),
|
||||||
userId: x.userId,
|
userId: x.userId,
|
||||||
user: await this.userEntityService.pack(x.userId),
|
user: _userMap.get(x.userId) ?? await this.userEntityService.pack(x.userId),
|
||||||
withReplies: x.withReplies,
|
withReplies: x.withReplies,
|
||||||
})));
|
})));
|
||||||
}
|
}
|
||||||
|
|
|
@ -89,10 +89,13 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
.limit(ps.limit)
|
.limit(ps.limit)
|
||||||
.getMany();
|
.getMany();
|
||||||
|
|
||||||
|
const _users = assigns.map(({ user, userId }) => user ?? userId);
|
||||||
|
const _userMap = await this.userEntityService.packMany(_users, me, { schema: 'UserDetailed' })
|
||||||
|
.then(users => new Map(users.map(u => [u.id, u])));
|
||||||
return await Promise.all(assigns.map(async assign => ({
|
return await Promise.all(assigns.map(async assign => ({
|
||||||
id: assign.id,
|
id: assign.id,
|
||||||
createdAt: this.idService.parse(assign.id).date.toISOString(),
|
createdAt: this.idService.parse(assign.id).date.toISOString(),
|
||||||
user: await this.userEntityService.pack(assign.user!, me, { schema: 'UserDetailed' }),
|
user: _userMap.get(assign.userId) ?? await this.userEntityService.pack(assign.user!, me, { schema: 'UserDetailed' }),
|
||||||
expiresAt: assign.expiresAt?.toISOString() ?? null,
|
expiresAt: assign.expiresAt?.toISOString() ?? null,
|
||||||
})));
|
})));
|
||||||
});
|
});
|
||||||
|
|
|
@ -54,7 +54,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
folderId: ps.folderId ?? IsNull(),
|
folderId: ps.folderId ?? IsNull(),
|
||||||
});
|
});
|
||||||
|
|
||||||
return await Promise.all(files.map(file => this.driveFileEntityService.pack(file, { self: true })));
|
return await this.driveFileEntityService.packMany(files, { self: true });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,7 +71,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
.limit(ps.limit)
|
.limit(ps.limit)
|
||||||
.getMany();
|
.getMany();
|
||||||
|
|
||||||
return await Promise.all(requests.map(req => this.followRequestEntityService.pack(req)));
|
return await this.followRequestEntityService.packMany(requests, me);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -76,7 +76,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
|
|
||||||
const reactions = await query.limit(ps.limit).getMany();
|
const reactions = await query.limit(ps.limit).getMany();
|
||||||
|
|
||||||
return await Promise.all(reactions.map(reaction => this.noteReactionEntityService.pack(reaction, me)));
|
return await this.noteReactionEntityService.packMany(reactions, me);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -92,9 +92,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
.limit(ps.limit)
|
.limit(ps.limit)
|
||||||
.getMany();
|
.getMany();
|
||||||
|
|
||||||
|
const _users = assigns.map(({ user, userId }) => user ?? userId);
|
||||||
|
const _userMap = await this.userEntityService.packMany(_users, me, { schema: 'UserDetailed' })
|
||||||
|
.then(users => new Map(users.map(u => [u.id, u])));
|
||||||
return await Promise.all(assigns.map(async assign => ({
|
return await Promise.all(assigns.map(async assign => ({
|
||||||
id: assign.id,
|
id: assign.id,
|
||||||
user: await this.userEntityService.pack(assign.user!, me, { schema: 'UserDetailed' }),
|
user: _userMap.get(assign.userId) ?? await this.userEntityService.pack(assign.user!, me, { schema: 'UserDetailed' }),
|
||||||
})));
|
})));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -118,12 +118,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
const repliedUsersSorted = Object.keys(repliedUsers).sort((a, b) => repliedUsers[b] - repliedUsers[a]);
|
const repliedUsersSorted = Object.keys(repliedUsers).sort((a, b) => repliedUsers[b] - repliedUsers[a]);
|
||||||
|
|
||||||
// Extract top replied users
|
// Extract top replied users
|
||||||
const topRepliedUsers = repliedUsersSorted.slice(0, ps.limit);
|
const topRepliedUserIds = repliedUsersSorted.slice(0, ps.limit);
|
||||||
|
|
||||||
// Make replies object (includes weights)
|
// Make replies object (includes weights)
|
||||||
const repliesObj = await Promise.all(topRepliedUsers.map(async (user) => ({
|
const _userMap = await this.userEntityService.packMany(topRepliedUserIds, me, { schema: 'UserDetailed' })
|
||||||
user: await this.userEntityService.pack(user, me, { schema: 'UserDetailed' }),
|
.then(users => new Map(users.map(u => [u.id, u])));
|
||||||
weight: repliedUsers[user] / peak,
|
const repliesObj = await Promise.all(topRepliedUserIds.map(async (userId) => ({
|
||||||
|
user: _userMap.get(userId) ?? await this.userEntityService.pack(userId, me, { schema: 'UserDetailed' }),
|
||||||
|
weight: repliedUsers[userId] / peak,
|
||||||
})));
|
})));
|
||||||
|
|
||||||
return repliesObj;
|
return repliesObj;
|
||||||
|
|
|
@ -117,9 +117,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
if (user != null) _users.push(user);
|
if (user != null) _users.push(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
return await Promise.all(_users.map(u => this.userEntityService.pack(u, me, {
|
const _userMap = await this.userEntityService.packMany(_users, me, { schema: 'UserDetailed' })
|
||||||
schema: 'UserDetailed',
|
.then(users => new Map(users.map(u => [u.id, u])));
|
||||||
})));
|
return _users.map(u => _userMap.get(u.id)!);
|
||||||
} else {
|
} else {
|
||||||
// Lookup user
|
// Lookup user
|
||||||
if (typeof ps.host === 'string' && typeof ps.username === 'string') {
|
if (typeof ps.host === 'string' && typeof ps.username === 'string') {
|
||||||
|
|
Loading…
Reference in a new issue