summaryrefslogtreecommitdiff
path: root/packages/backend/src/core
diff options
context:
space:
mode:
authordakkar <dakkar@thenautilus.net>2024-05-31 12:26:07 +0100
committerdakkar <dakkar@thenautilus.net>2024-05-31 12:26:07 +0100
commit3372e0ffe153d996bdf04ae3d834633d0ba18fca (patch)
tree7a46452030b2a2ca24d1ea9158fa5f0f75000245 /packages/backend/src/core
parentMerge branch 'develop' into future (diff)
parentBump version to 2024.5.0-rc.9 (diff)
downloadsharkey-3372e0ffe153d996bdf04ae3d834633d0ba18fca.tar.gz
sharkey-3372e0ffe153d996bdf04ae3d834633d0ba18fca.tar.bz2
sharkey-3372e0ffe153d996bdf04ae3d834633d0ba18fca.zip
Merge remote-tracking branch 'misskey/release/2024.5.0' into future
Diffstat (limited to 'packages/backend/src/core')
-rw-r--r--packages/backend/src/core/AnnouncementService.ts49
-rw-r--r--packages/backend/src/core/CoreModule.ts6
-rw-r--r--packages/backend/src/core/CustomEmojiService.ts7
-rw-r--r--packages/backend/src/core/DriveService.ts14
-rw-r--r--packages/backend/src/core/FanoutTimelineEndpointService.ts13
-rw-r--r--packages/backend/src/core/FetchInstanceMetadataService.ts2
-rw-r--r--packages/backend/src/core/NoteCreateService.ts1
-rw-r--r--packages/backend/src/core/activitypub/ApInboxService.ts117
-rw-r--r--packages/backend/src/core/activitypub/models/ApNoteService.ts8
-rw-r--r--packages/backend/src/core/activitypub/type.ts1
-rw-r--r--packages/backend/src/core/entities/AbuseUserReportEntityService.ts34
-rw-r--r--packages/backend/src/core/entities/AnnouncementEntityService.ts71
-rw-r--r--packages/backend/src/core/entities/AntennaEntityService.ts2
-rw-r--r--packages/backend/src/core/entities/BlockingEntityService.ts14
-rw-r--r--packages/backend/src/core/entities/ClipEntityService.ts12
-rw-r--r--packages/backend/src/core/entities/DriveFileEntityService.ts10
-rw-r--r--packages/backend/src/core/entities/FlashEntityService.ts14
-rw-r--r--packages/backend/src/core/entities/FollowRequestEntityService.ts27
-rw-r--r--packages/backend/src/core/entities/FollowingEntityService.ts24
-rw-r--r--packages/backend/src/core/entities/GalleryPostEntityService.ts12
-rw-r--r--packages/backend/src/core/entities/InstanceEntityService.ts3
-rw-r--r--packages/backend/src/core/entities/InviteCodeEntityService.ts25
-rw-r--r--packages/backend/src/core/entities/MetaEntityService.ts1
-rw-r--r--packages/backend/src/core/entities/ModerationLogEntityService.ts17
-rw-r--r--packages/backend/src/core/entities/MutingEntityService.ts14
-rw-r--r--packages/backend/src/core/entities/NoteEntityService.ts12
-rw-r--r--packages/backend/src/core/entities/NoteReactionEntityService.ts11
-rw-r--r--packages/backend/src/core/entities/PageEntityService.ts12
-rw-r--r--packages/backend/src/core/entities/RenoteMutingEntityService.ts14
-rw-r--r--packages/backend/src/core/entities/ReversiGameEntityService.ts66
-rw-r--r--packages/backend/src/core/entities/UserListEntityService.ts5
31 files changed, 445 insertions, 173 deletions
diff --git a/packages/backend/src/core/AnnouncementService.ts b/packages/backend/src/core/AnnouncementService.ts
index b298a70929..9b60df2cae 100644
--- a/packages/backend/src/core/AnnouncementService.ts
+++ b/packages/backend/src/core/AnnouncementService.ts
@@ -4,13 +4,14 @@
*/
import { Inject, Injectable } from '@nestjs/common';
-import { Brackets } from 'typeorm';
+import { Brackets, EntityNotFoundError } from 'typeorm';
import { DI } from '@/di-symbols.js';
import type { MiUser } from '@/models/User.js';
import type { AnnouncementReadsRepository, AnnouncementsRepository, MiAnnouncement, MiAnnouncementRead, UsersRepository } from '@/models/_.js';
import { bindThis } from '@/decorators.js';
import { Packed } from '@/misc/json-schema.js';
import { IdService } from '@/core/IdService.js';
+import { AnnouncementEntityService } from '@/core/entities/AnnouncementEntityService.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
import { ModerationLogService } from '@/core/ModerationLogService.js';
@@ -29,6 +30,7 @@ export class AnnouncementService {
private idService: IdService,
private globalEventService: GlobalEventService,
private moderationLogService: ModerationLogService,
+ private announcementEntityService: AnnouncementEntityService,
) {
}
@@ -79,7 +81,7 @@ export class AnnouncementService {
userId: values.userId,
}).then(x => this.announcementsRepository.findOneByOrFail(x.identifiers[0]));
- const packed = (await this.packMany([announcement]))[0];
+ const packed = await this.announcementEntityService.pack(announcement);
if (values.userId) {
this.globalEventService.publishMainStream(values.userId, 'announcementCreated', {
@@ -178,6 +180,24 @@ export class AnnouncementService {
}
@bindThis
+ public async getAnnouncement(announcementId: MiAnnouncement['id'], me: MiUser | null): Promise<Packed<'Announcement'>> {
+ const announcement = await this.announcementsRepository.findOneByOrFail({ id: announcementId });
+ if (me) {
+ if (announcement.userId && announcement.userId !== me.id) {
+ throw new EntityNotFoundError(this.announcementsRepository.metadata.target, { id: announcementId });
+ }
+
+ const read = await this.announcementReadsRepository.findOneBy({
+ announcementId: announcement.id,
+ userId: me.id,
+ });
+ return this.announcementEntityService.pack({ ...announcement, isRead: read !== null }, me);
+ } else {
+ return this.announcementEntityService.pack(announcement, null);
+ }
+ }
+
+ @bindThis
public async read(user: MiUser, announcementId: MiAnnouncement['id']): Promise<void> {
try {
await this.announcementReadsRepository.insert({
@@ -193,29 +213,4 @@ export class AnnouncementService {
this.globalEventService.publishMainStream(user.id, 'readAllAnnouncements');
}
}
-
- @bindThis
- public async packMany(
- announcements: MiAnnouncement[],
- me?: { id: MiUser['id'] } | null | undefined,
- options?: {
- reads?: MiAnnouncementRead[];
- },
- ): Promise<Packed<'Announcement'>[]> {
- const reads = me ? (options?.reads ?? await this.getReads(me.id)) : [];
- return announcements.map(announcement => ({
- id: announcement.id,
- createdAt: this.idService.parse(announcement.id).date.toISOString(),
- updatedAt: announcement.updatedAt?.toISOString() ?? null,
- text: announcement.text,
- title: announcement.title,
- imageUrl: announcement.imageUrl,
- icon: announcement.icon,
- display: announcement.display,
- needConfirmationToRead: announcement.needConfirmationToRead,
- silence: announcement.silence,
- forYou: announcement.userId === me?.id,
- isRead: reads.some(read => read.announcementId === announcement.id),
- }));
- }
}
diff --git a/packages/backend/src/core/CoreModule.ts b/packages/backend/src/core/CoreModule.ts
index e68cb86af0..9baec9a59f 100644
--- a/packages/backend/src/core/CoreModule.ts
+++ b/packages/backend/src/core/CoreModule.ts
@@ -84,6 +84,7 @@ import ApRequestChart from './chart/charts/ap-request.js';
import { ChartManagementService } from './chart/ChartManagementService.js';
import { AbuseUserReportEntityService } from './entities/AbuseUserReportEntityService.js';
+import { AnnouncementEntityService } from './entities/AnnouncementEntityService.js';
import { AntennaEntityService } from './entities/AntennaEntityService.js';
import { AppEntityService } from './entities/AppEntityService.js';
import { AuthSessionEntityService } from './entities/AuthSessionEntityService.js';
@@ -223,6 +224,7 @@ const $ApRequestChart: Provider = { provide: 'ApRequestChart', useExisting: ApRe
const $ChartManagementService: Provider = { provide: 'ChartManagementService', useExisting: ChartManagementService };
const $AbuseUserReportEntityService: Provider = { provide: 'AbuseUserReportEntityService', useExisting: AbuseUserReportEntityService };
+const $AnnouncementEntityService: Provider = { provide: 'AnnouncementEntityService', useExisting: AnnouncementEntityService };
const $AntennaEntityService: Provider = { provide: 'AntennaEntityService', useExisting: AntennaEntityService };
const $AppEntityService: Provider = { provide: 'AppEntityService', useExisting: AppEntityService };
const $AuthSessionEntityService: Provider = { provide: 'AuthSessionEntityService', useExisting: AuthSessionEntityService };
@@ -363,6 +365,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
ChartManagementService,
AbuseUserReportEntityService,
+ AnnouncementEntityService,
AntennaEntityService,
AppEntityService,
AuthSessionEntityService,
@@ -499,6 +502,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
$ChartManagementService,
$AbuseUserReportEntityService,
+ $AnnouncementEntityService,
$AntennaEntityService,
$AppEntityService,
$AuthSessionEntityService,
@@ -635,6 +639,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
ChartManagementService,
AbuseUserReportEntityService,
+ AnnouncementEntityService,
AntennaEntityService,
AppEntityService,
AuthSessionEntityService,
@@ -770,6 +775,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
$ChartManagementService,
$AbuseUserReportEntityService,
+ $AnnouncementEntityService,
$AntennaEntityService,
$AppEntityService,
$AuthSessionEntityService,
diff --git a/packages/backend/src/core/CustomEmojiService.ts b/packages/backend/src/core/CustomEmojiService.ts
index 2fbba4d6c5..bfbc2b172d 100644
--- a/packages/backend/src/core/CustomEmojiService.ts
+++ b/packages/backend/src/core/CustomEmojiService.ts
@@ -369,10 +369,11 @@ export class CustomEmojiService implements OnApplicationShutdown {
@bindThis
public async populateEmojis(emojiNames: string[], noteUserHost: string | null): Promise<Record<string, string>> {
const emojis = await Promise.all(emojiNames.map(x => this.populateEmoji(x, noteUserHost)));
- const res = {} as any;
+ const res = {} as Record<string, string>;
for (let i = 0; i < emojiNames.length; i++) {
- if (emojis[i] != null) {
- res[emojiNames[i]] = emojis[i];
+ const resolvedEmoji = emojis[i];
+ if (resolvedEmoji != null) {
+ res[emojiNames[i]] = resolvedEmoji;
}
}
return res;
diff --git a/packages/backend/src/core/DriveService.ts b/packages/backend/src/core/DriveService.ts
index dc84ea1999..af5451bfc8 100644
--- a/packages/backend/src/core/DriveService.ts
+++ b/packages/backend/src/core/DriveService.ts
@@ -477,14 +477,20 @@ export class DriveService {
if (user && !force) {
// Check if there is a file with the same hash
- const much = await this.driveFilesRepository.findOneBy({
+ const matched = await this.driveFilesRepository.findOneBy({
md5: info.md5,
userId: user.id,
});
- if (much) {
- this.registerLogger.info(`file with same hash is found: ${much.id}`);
- return much;
+ if (matched) {
+ this.registerLogger.info(`file with same hash is found: ${matched.id}`);
+ if (sensitive && !matched.isSensitive) {
+ // The file is federated as sensitive for this time, but was federated as non-sensitive before.
+ // Therefore, update the file to sensitive.
+ await this.driveFilesRepository.update({ id: matched.id }, { isSensitive: true });
+ matched.isSensitive = true;
+ }
+ return matched;
}
}
diff --git a/packages/backend/src/core/FanoutTimelineEndpointService.ts b/packages/backend/src/core/FanoutTimelineEndpointService.ts
index 2f4d98fab4..5725c795ed 100644
--- a/packages/backend/src/core/FanoutTimelineEndpointService.ts
+++ b/packages/backend/src/core/FanoutTimelineEndpointService.ts
@@ -62,8 +62,8 @@ export class FanoutTimelineEndpointService {
// 呼び出し元と以下の処理をシンプルにするためにdbFallbackを置き換える
if (!ps.useDbFallback) ps.dbFallback = () => Promise.resolve([]);
- const shouldPrepend = ps.sinceId && !ps.untilId;
- const idCompare: (a: string, b: string) => number = shouldPrepend ? (a, b) => a < b ? -1 : 1 : (a, b) => a > b ? -1 : 1;
+ const ascending = ps.sinceId && !ps.untilId;
+ const idCompare: (a: string, b: string) => number = ascending ? (a, b) => a < b ? -1 : 1 : (a, b) => a > b ? -1 : 1;
const redisResult = await this.fanoutTimelineService.getMulti(ps.redisTimelines, ps.untilId, ps.sinceId);
@@ -148,9 +148,7 @@ export class FanoutTimelineEndpointService {
if (ps.allowPartial ? redisTimeline.length !== 0 : redisTimeline.length >= ps.limit) {
// 十分Redisからとれた
- const result = redisTimeline.slice(0, ps.limit);
- if (shouldPrepend) result.reverse();
- return result;
+ return redisTimeline.slice(0, ps.limit);
}
}
@@ -158,8 +156,7 @@ export class FanoutTimelineEndpointService {
const remainingToRead = ps.limit - redisTimeline.length;
let dbUntil: string | null;
let dbSince: string | null;
- if (shouldPrepend) {
- redisTimeline.reverse();
+ if (ascending) {
dbUntil = ps.untilId;
dbSince = noteIds[noteIds.length - 1];
} else {
@@ -167,7 +164,7 @@ export class FanoutTimelineEndpointService {
dbSince = ps.sinceId;
}
const gotFromDb = await ps.dbFallback(dbUntil, dbSince, remainingToRead);
- return shouldPrepend ? [...gotFromDb, ...redisTimeline] : [...redisTimeline, ...gotFromDb];
+ return [...redisTimeline, ...gotFromDb];
}
return await ps.dbFallback(ps.untilId, ps.sinceId, ps.limit);
diff --git a/packages/backend/src/core/FetchInstanceMetadataService.ts b/packages/backend/src/core/FetchInstanceMetadataService.ts
index 8d173855f3..aa16468ecb 100644
--- a/packages/backend/src/core/FetchInstanceMetadataService.ts
+++ b/packages/backend/src/core/FetchInstanceMetadataService.ts
@@ -154,7 +154,7 @@ export class FetchInstanceMetadataService {
throw new Error('No wellknown links');
}
- const links = wellknown.links as any[];
+ const links = wellknown.links as ({ rel: string, href: string; })[];
const link1_0 = links.find(link => link.rel === 'http://nodeinfo.diaspora.software/ns/schema/1.0');
const link2_0 = links.find(link => link.rel === 'http://nodeinfo.diaspora.software/ns/schema/2.0');
diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts
index 1f575c083a..4ff0d0fbef 100644
--- a/packages/backend/src/core/NoteCreateService.ts
+++ b/packages/backend/src/core/NoteCreateService.ts
@@ -662,6 +662,7 @@ export class NoteCreateService implements OnApplicationShutdown {
noteVisibility: insert.visibility,
userId: user.id,
userHost: user.host,
+ channelId: insert.channelId,
});
await transactionalEntityManager.insert(MiPoll, poll);
diff --git a/packages/backend/src/core/activitypub/ApInboxService.ts b/packages/backend/src/core/activitypub/ApInboxService.ts
index 6ff03b22e1..cf66816566 100644
--- a/packages/backend/src/core/activitypub/ApInboxService.ts
+++ b/packages/backend/src/core/activitypub/ApInboxService.ts
@@ -28,6 +28,7 @@ import type { UsersRepository, NotesRepository, FollowingsRepository, AbuseUserR
import { bindThis } from '@/decorators.js';
import type { MiRemoteUser } from '@/models/User.js';
import { isNotNull } from '@/misc/is-not-null.js';
+import { GlobalEventService } from '@/core/GlobalEventService.js';
import { getApHrefNullable, getApId, getApIds, getApType, isAccept, isActor, isAdd, isAnnounce, isBlock, isCollection, isCollectionOrOrderedCollection, isCreate, isDelete, isFlag, isFollow, isLike, isMove, isPost, isReject, isRemove, isTombstone, isUndo, isUpdate, validActor, validPost } from './type.js';
import { ApNoteService } from './models/ApNoteService.js';
import { ApLoggerService } from './ApLoggerService.js';
@@ -36,9 +37,8 @@ import { ApResolverService } from './ApResolverService.js';
import { ApAudienceService } from './ApAudienceService.js';
import { ApPersonService } from './models/ApPersonService.js';
import { ApQuestionService } from './models/ApQuestionService.js';
-import { GlobalEventService } from '@/core/GlobalEventService.js';
import type { Resolver } from './ApResolverService.js';
-import type { IAccept, IAdd, IAnnounce, IBlock, ICreate, IDelete, IFlag, IFollow, ILike, IObject, IReject, IRemove, IUndo, IUpdate, IMove } from './type.js';
+import type { IAccept, IAdd, IAnnounce, IBlock, ICreate, IDelete, IFlag, IFollow, ILike, IObject, IReject, IRemove, IUndo, IUpdate, IMove, IPost } from './type.js';
@Injectable()
export class ApInboxService {
@@ -90,13 +90,15 @@ export class ApInboxService {
}
@bindThis
- public async performActivity(actor: MiRemoteUser, activity: IObject): Promise<void> {
+ public async performActivity(actor: MiRemoteUser, activity: IObject): Promise<string | void> {
+ let result = undefined as string | void;
if (isCollectionOrOrderedCollection(activity)) {
+ const results = [] as [string, string | void][];
const resolver = this.apResolverService.createResolver();
for (const item of toArray(isCollection(activity) ? activity.items : activity.orderedItems)) {
const act = await resolver.resolve(item);
try {
- await this.performOneActivity(actor, act);
+ results.push([getApId(item), await this.performOneActivity(actor, act)]);
} catch (err) {
if (err instanceof Error || typeof err === 'string') {
this.logger.error(err);
@@ -105,8 +107,13 @@ export class ApInboxService {
}
}
}
+
+ const hasReason = results.some(([, reason]) => (reason != null && !reason.startsWith('ok')));
+ if (hasReason) {
+ result = results.map(([id, reason]) => `${id}: ${reason}`).join('\n');
+ }
} else {
- await this.performOneActivity(actor, activity);
+ result = await this.performOneActivity(actor, activity);
}
// ついでにリモートユーザーの情報が古かったら更新しておく
@@ -117,42 +124,43 @@ export class ApInboxService {
});
}
}
+ return result;
}
@bindThis
- public async performOneActivity(actor: MiRemoteUser, activity: IObject): Promise<void> {
+ public async performOneActivity(actor: MiRemoteUser, activity: IObject): Promise<string | void> {
if (actor.isSuspended) return;
if (isCreate(activity)) {
- await this.create(actor, activity);
+ return await this.create(actor, activity);
} else if (isDelete(activity)) {
- await this.delete(actor, activity);
+ return await this.delete(actor, activity);
} else if (isUpdate(activity)) {
- await this.update(actor, activity);
+ return await this.update(actor, activity);
} else if (isFollow(activity)) {
- await this.follow(actor, activity);
+ return await this.follow(actor, activity);
} else if (isAccept(activity)) {
- await this.accept(actor, activity);
+ return await this.accept(actor, activity);
} else if (isReject(activity)) {
- await this.reject(actor, activity);
+ return await this.reject(actor, activity);
} else if (isAdd(activity)) {
- await this.add(actor, activity).catch(err => this.logger.error(err));
+ return await this.add(actor, activity);
} else if (isRemove(activity)) {
- await this.remove(actor, activity).catch(err => this.logger.error(err));
+ return await this.remove(actor, activity);
} else if (isAnnounce(activity)) {
- await this.announce(actor, activity);
+ return await this.announce(actor, activity);
} else if (isLike(activity)) {
- await this.like(actor, activity);
+ return await this.like(actor, activity);
} else if (isUndo(activity)) {
- await this.undo(actor, activity);
+ return await this.undo(actor, activity);
} else if (isBlock(activity)) {
- await this.block(actor, activity);
+ return await this.block(actor, activity);
} else if (isFlag(activity)) {
- await this.flag(actor, activity);
+ return await this.flag(actor, activity);
} else if (isMove(activity)) {
- await this.move(actor, activity);
+ return await this.move(actor, activity);
} else {
- this.logger.warn(`unrecognized activity type: ${activity.type}`);
+ return `unrecognized activity type: ${activity.type}`;
}
}
@@ -234,38 +242,49 @@ export class ApInboxService {
}
@bindThis
- private async add(actor: MiRemoteUser, activity: IAdd): Promise<void> {
+ private async add(actor: MiRemoteUser, activity: IAdd): Promise<string | void> {
if (actor.uri !== activity.actor) {
- throw new Error('invalid actor');
+ return 'invalid actor';
}
if (activity.target == null) {
- throw new Error('target is null');
+ return 'target is null';
}
if (activity.target === actor.featured) {
const note = await this.apNoteService.resolveNote(activity.object);
- if (note == null) throw new Error('note not found');
+ if (note == null) return 'note not found';
await this.notePiningService.addPinned(actor, note.id);
return;
}
- throw new Error(`unknown target: ${activity.target}`);
+ return `unknown target: ${activity.target}`;
}
@bindThis
- private async announce(actor: MiRemoteUser, activity: IAnnounce): Promise<void> {
+ private async announce(actor: MiRemoteUser, activity: IAnnounce): Promise<string | void> {
const uri = getApId(activity);
this.logger.info(`Announce: ${uri}`);
+ const resolver = this.apResolverService.createResolver();
+
+ if (!activity.object) return 'skip: activity has no object property';
const targetUri = getApId(activity.object);
+ if (targetUri.startsWith('bear:')) return 'skip: bearcaps url not supported.';
+
+ const target = await resolver.resolve(activity.object).catch(e => {
+ this.logger.error(`Resolution failed: ${e}`);
+ return e;
+ });
+
+ if (isPost(target)) return await this.announceNote(actor, activity, target);
- await this.announceNote(actor, activity, targetUri);
+ return `skip: unknown object type ${getApType(target)}`;
}
@bindThis
- private async announceNote(actor: MiRemoteUser, activity: IAnnounce, targetUri: string): Promise<void> {
+ private async announceNote(actor: MiRemoteUser, activity: IAnnounce, target: IPost): Promise<string | void> {
const uri = getApId(activity);
if (actor.isSuspended) {
@@ -288,24 +307,21 @@ export class ApInboxService {
// Announce対象をresolve
let renote;
try {
- renote = await this.apNoteService.resolveNote(targetUri);
- if (renote == null) throw new Error('announce target is null');
+ renote = await this.apNoteService.resolveNote(target);
+ if (renote == null) return 'announce target is null';
} catch (err) {
// 対象が4xxならスキップ
if (err instanceof StatusError) {
if (!err.isRetryable) {
- this.logger.warn(`Ignored announce target ${targetUri} - ${err.statusCode}`);
- return;
+ return `Ignored announce target ${target.id} - ${err.statusCode}`;
}
-
- this.logger.warn(`Error in announce target ${targetUri} - ${err.statusCode}`);
+ return `Error in announce target ${target.id} - ${err.statusCode}`;
}
throw err;
}
if (!await this.noteEntityService.isVisibleForMe(renote, actor.id)) {
- this.logger.warn('skip: invalid actor for this activity');
- return;
+ return 'skip: invalid actor for this activity';
}
this.logger.info(`Creating the (Re)Note: ${uri}`);
@@ -314,8 +330,7 @@ export class ApInboxService {
const createdAt = activity.published ? new Date(activity.published) : null;
if (createdAt && createdAt < this.idService.parse(renote.id).date) {
- this.logger.warn('skip: malformed createdAt');
- return;
+ return 'skip: malformed createdAt';
}
await this.noteCreateService.create(actor, {
@@ -349,11 +364,15 @@ export class ApInboxService {
}
@bindThis
- private async create(actor: MiRemoteUser, activity: ICreate): Promise<void> {
+ private async create(actor: MiRemoteUser, activity: ICreate): Promise<string | void> {
const uri = getApId(activity);
this.logger.info(`Create: ${uri}`);
+ if (!activity.object) return 'skip: activity has no object property';
+ const targetUri = getApId(activity.object);
+ if (targetUri.startsWith('bear:')) return 'skip: bearcaps url not supported.';
+
// copy audiences between activity <=> object.
if (typeof activity.object === 'object') {
const to = unique(concat([toArray(activity.to), toArray(activity.object.to)]));
@@ -380,7 +399,7 @@ export class ApInboxService {
if (isPost(object)) {
await this.createNote(resolver, actor, object, false, activity);
} else {
- this.logger.warn(`Unknown type: ${getApType(object)}`);
+ return `Unknown type: ${getApType(object)}`;
}
}
@@ -422,7 +441,7 @@ export class ApInboxService {
@bindThis
private async delete(actor: MiRemoteUser, activity: IDelete): Promise<string> {
if (actor.uri !== activity.actor) {
- throw new Error('invalid actor');
+ return 'invalid actor';
}
// 削除対象objectのtype
@@ -581,29 +600,29 @@ export class ApInboxService {
}
@bindThis
- private async remove(actor: MiRemoteUser, activity: IRemove): Promise<void> {
+ private async remove(actor: MiRemoteUser, activity: IRemove): Promise<string | void> {
if (actor.uri !== activity.actor) {
- throw new Error('invalid actor');
+ return 'invalid actor';
}
if (activity.target == null) {
- throw new Error('target is null');
+ return 'target is null';
}
if (activity.target === actor.featured) {
const note = await this.apNoteService.resolveNote(activity.object);
- if (note == null) throw new Error('note not found');
+ if (note == null) return 'note not found';
await this.notePiningService.removePinned(actor, note.id);
return;
}
- throw new Error(`unknown target: ${activity.target}`);
+ return `unknown target: ${activity.target}`;
}
@bindThis
private async undo(actor: MiRemoteUser, activity: IUndo): Promise<string> {
if (actor.uri !== activity.actor) {
- throw new Error('invalid actor');
+ return 'invalid actor';
}
const uri = activity.id ?? activity;
@@ -614,7 +633,7 @@ export class ApInboxService {
const object = await resolver.resolve(activity.object).catch(e => {
this.logger.error(`Resolution failed: ${e}`);
- throw e;
+ return e;
});
// don't queue because the sender may attempt again when timeout
diff --git a/packages/backend/src/core/activitypub/models/ApNoteService.ts b/packages/backend/src/core/activitypub/models/ApNoteService.ts
index 542bb9e2e5..cad1af02e5 100644
--- a/packages/backend/src/core/activitypub/models/ApNoteService.ts
+++ b/packages/backend/src/core/activitypub/models/ApNoteService.ts
@@ -86,20 +86,20 @@ export class ApNoteService {
const expectHost = this.utilityService.extractDbHost(uri);
if (!validPost.includes(getApType(object))) {
- return new Error(`invalid Note: invalid object type ${getApType(object)}`);
+ return new IdentifiableError('d450b8a9-48e4-4dab-ae36-f4db763fda7c', `invalid Note: invalid object type ${getApType(object)}`);
}
if (object.id && this.utilityService.extractDbHost(object.id) !== expectHost) {
- return new Error(`invalid Note: id has different host. expected: ${expectHost}, actual: ${this.utilityService.extractDbHost(object.id)}`);
+ return new IdentifiableError('d450b8a9-48e4-4dab-ae36-f4db763fda7c', `invalid Note: id has different host. expected: ${expectHost}, actual: ${this.utilityService.extractDbHost(object.id)}`);
}
const actualHost = object.attributedTo && this.utilityService.extractDbHost(getOneApId(object.attributedTo));
if (object.attributedTo && actualHost !== expectHost) {
- return new Error(`invalid Note: attributedTo has different host. expected: ${expectHost}, actual: ${actualHost}`);
+ return new IdentifiableError('d450b8a9-48e4-4dab-ae36-f4db763fda7c', `invalid Note: attributedTo has different host. expected: ${expectHost}, actual: ${actualHost}`);
}
if (object.published && !this.idService.isSafeT(new Date(object.published).valueOf())) {
- return new Error('invalid Note: published timestamp is malformed');
+ return new IdentifiableError('d450b8a9-48e4-4dab-ae36-f4db763fda7c', 'invalid Note: published timestamp is malformed');
}
return null;
diff --git a/packages/backend/src/core/activitypub/type.ts b/packages/backend/src/core/activitypub/type.ts
index cf2c3f185e..8edd8a1aba 100644
--- a/packages/backend/src/core/activitypub/type.ts
+++ b/packages/backend/src/core/activitypub/type.ts
@@ -334,3 +334,4 @@ export const isAnnounce = (object: IObject): object is IAnnounce => getApType(ob
export const isBlock = (object: IObject): object is IBlock => getApType(object) === 'Block';
export const isFlag = (object: IObject): object is IFlag => getApType(object) === 'Flag';
export const isMove = (object: IObject): object is IMove => getApType(object) === 'Move';
+export const isNote = (object: IObject): object is IPost => getApType(object) === 'Note';
diff --git a/packages/backend/src/core/entities/AbuseUserReportEntityService.ts b/packages/backend/src/core/entities/AbuseUserReportEntityService.ts
index 49f256d870..b0e1d1ab36 100644
--- a/packages/backend/src/core/entities/AbuseUserReportEntityService.ts
+++ b/packages/backend/src/core/entities/AbuseUserReportEntityService.ts
@@ -10,6 +10,8 @@ import { awaitAll } from '@/misc/prelude/await-all.js';
import type { MiAbuseUserReport } from '@/models/AbuseUserReport.js';
import { bindThis } from '@/decorators.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';
@Injectable()
@@ -26,6 +28,11 @@ export class AbuseUserReportEntityService {
@bindThis
public async pack(
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 });
@@ -37,13 +44,13 @@ export class AbuseUserReportEntityService {
reporterId: report.reporterId,
targetUserId: report.targetUserId,
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',
}),
- targetUser: this.userEntityService.pack(report.targetUser ?? report.targetUserId, null, {
+ targetUser: hint?.packedTargetUser ?? this.userEntityService.pack(report.targetUser ?? report.targetUserId, null, {
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',
}) : null,
forwarded: report.forwarded,
@@ -51,9 +58,24 @@ export class AbuseUserReportEntityService {
}
@bindThis
- public packMany(
- reports: any[],
+ public async packMany(
+ 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 });
+ }),
+ );
}
}
diff --git a/packages/backend/src/core/entities/AnnouncementEntityService.ts b/packages/backend/src/core/entities/AnnouncementEntityService.ts
new file mode 100644
index 0000000000..90b04d0229
--- /dev/null
+++ b/packages/backend/src/core/entities/AnnouncementEntityService.ts
@@ -0,0 +1,71 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { Inject, Injectable } from '@nestjs/common';
+import { DI } from '@/di-symbols.js';
+import type { AnnouncementsRepository, AnnouncementReadsRepository, MiAnnouncement, MiUser } from '@/models/_.js';
+import type { Packed } from '@/misc/json-schema.js';
+import { bindThis } from '@/decorators.js';
+import { IdService } from '@/core/IdService.js';
+
+@Injectable()
+export class AnnouncementEntityService {
+ constructor(
+ @Inject(DI.announcementsRepository)
+ private announcementsRepository: AnnouncementsRepository,
+
+ @Inject(DI.announcementReadsRepository)
+ private announcementReadsRepository: AnnouncementReadsRepository,
+
+ private idService: IdService,
+ ) {
+ }
+
+ @bindThis
+ public async pack(
+ src: MiAnnouncement['id'] | MiAnnouncement & { isRead?: boolean | null },
+ me?: { id: MiUser['id'] } | null | undefined,
+ ): Promise<Packed<'Announcement'>> {
+ const announcement = typeof src === 'object'
+ ? src
+ : await this.announcementsRepository.findOneByOrFail({
+ id: src,
+ }) as MiAnnouncement & { isRead?: boolean | null };
+
+ if (me && announcement.isRead === undefined) {
+ announcement.isRead = await this.announcementReadsRepository
+ .countBy({
+ announcementId: announcement.id,
+ userId: me.id,
+ })
+ .then((count: number) => count > 0);
+ }
+
+ return {
+ id: announcement.id,
+ createdAt: this.idService.parse(announcement.id).date.toISOString(),
+ updatedAt: announcement.updatedAt?.toISOString() ?? null,
+ title: announcement.title,
+ text: announcement.text,
+ imageUrl: announcement.imageUrl,
+ icon: announcement.icon,
+ display: announcement.display,
+ forYou: announcement.userId === me?.id,
+ needConfirmationToRead: announcement.needConfirmationToRead,
+ silence: announcement.silence,
+ isRead: announcement.isRead !== null ? announcement.isRead : undefined,
+ };
+ }
+
+ @bindThis
+ public async packMany(
+ announcements: (MiAnnouncement['id'] | MiAnnouncement & { isRead?: boolean | null } | MiAnnouncement)[],
+ me?: { id: MiUser['id'] } | null | undefined,
+ ) : Promise<Packed<'Announcement'>[]> {
+ return (await Promise.allSettled(announcements.map(x => this.pack(x, me))))
+ .filter(result => result.status === 'fulfilled')
+ .map(result => (result as PromiseFulfilledResult<Packed<'Announcement'>>).value);
+ }
+}
diff --git a/packages/backend/src/core/entities/AntennaEntityService.ts b/packages/backend/src/core/entities/AntennaEntityService.ts
index 3ec8efa6bf..e770028af3 100644
--- a/packages/backend/src/core/entities/AntennaEntityService.ts
+++ b/packages/backend/src/core/entities/AntennaEntityService.ts
@@ -38,12 +38,12 @@ export class AntennaEntityService {
users: antenna.users,
caseSensitive: antenna.caseSensitive,
localOnly: antenna.localOnly,
- notify: antenna.notify,
excludeBots: antenna.excludeBots,
withReplies: antenna.withReplies,
withFile: antenna.withFile,
isActive: antenna.isActive,
hasUnreadNote: false, // TODO
+ notify: false, // 後方互換性のため
};
}
}
diff --git a/packages/backend/src/core/entities/BlockingEntityService.ts b/packages/backend/src/core/entities/BlockingEntityService.ts
index c8c1520ceb..1e699032e2 100644
--- a/packages/backend/src/core/entities/BlockingEntityService.ts
+++ b/packages/backend/src/core/entities/BlockingEntityService.ts
@@ -29,6 +29,9 @@ export class BlockingEntityService {
public async pack(
src: MiBlocking['id'] | MiBlocking,
me?: { id: MiUser['id'] } | null | undefined,
+ hint?: {
+ blockee?: Packed<'UserDetailedNotMe'>,
+ },
): Promise<Packed<'Blocking'>> {
const blocking = typeof src === 'object' ? src : await this.blockingsRepository.findOneByOrFail({ id: src });
@@ -36,17 +39,20 @@ export class BlockingEntityService {
id: blocking.id,
createdAt: this.idService.parse(blocking.id).date.toISOString(),
blockeeId: blocking.blockeeId,
- blockee: this.userEntityService.pack(blocking.blockeeId, me, {
+ blockee: hint?.blockee ?? this.userEntityService.pack(blocking.blockeeId, me, {
schema: 'UserDetailedNotMe',
}),
});
}
@bindThis
- public packMany(
- blockings: any[],
+ public async packMany(
+ blockings: MiBlocking[],
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) })));
}
}
diff --git a/packages/backend/src/core/entities/ClipEntityService.ts b/packages/backend/src/core/entities/ClipEntityService.ts
index ce49c3458c..3855a28436 100644
--- a/packages/backend/src/core/entities/ClipEntityService.ts
+++ b/packages/backend/src/core/entities/ClipEntityService.ts
@@ -35,6 +35,9 @@ export class ClipEntityService {
public async pack(
src: MiClip['id'] | MiClip,
me?: { id: MiUser['id'] } | null | undefined,
+ hint?: {
+ packedUser?: Packed<'UserLite'>
+ },
): Promise<Packed<'Clip'>> {
const meId = me ? me.id : null;
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(),
lastClippedAt: clip.lastClippedAt ? clip.lastClippedAt.toISOString() : null,
userId: clip.userId,
- user: this.userEntityService.pack(clip.user ?? clip.userId),
+ user: hint?.packedUser ?? this.userEntityService.pack(clip.user ?? clip.userId),
name: clip.name,
description: clip.description,
isPublic: clip.isPublic,
@@ -55,11 +58,14 @@ export class ClipEntityService {
}
@bindThis
- public packMany(
+ public async packMany(
clips: MiClip[],
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) })));
}
}
diff --git a/packages/backend/src/core/entities/DriveFileEntityService.ts b/packages/backend/src/core/entities/DriveFileEntityService.ts
index 26bf386cbc..02ff2e7754 100644
--- a/packages/backend/src/core/entities/DriveFileEntityService.ts
+++ b/packages/backend/src/core/entities/DriveFileEntityService.ts
@@ -222,6 +222,9 @@ export class DriveFileEntityService {
public async packNullable(
src: MiDriveFile['id'] | MiDriveFile,
options?: PackOptions,
+ hint?: {
+ packedUser?: Packed<'UserLite'>
+ },
): Promise<Packed<'DriveFile'> | null> {
const opts = Object.assign({
detail: false,
@@ -249,7 +252,7 @@ export class DriveFileEntityService {
detail: true,
}) : null,
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[],
options?: PackOptions,
): 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);
}
diff --git a/packages/backend/src/core/entities/FlashEntityService.ts b/packages/backend/src/core/entities/FlashEntityService.ts
index db4cf6d360..d110f7afc6 100644
--- a/packages/backend/src/core/entities/FlashEntityService.ts
+++ b/packages/backend/src/core/entities/FlashEntityService.ts
@@ -33,6 +33,9 @@ export class FlashEntityService {
public async pack(
src: MiFlash['id'] | MiFlash,
me?: { id: MiUser['id'] } | null | undefined,
+ hint?: {
+ packedUser?: Packed<'UserLite'>
+ },
): Promise<Packed<'Flash'>> {
const meId = me ? me.id : null;
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(),
updatedAt: flash.updatedAt.toISOString(),
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,
summary: flash.summary,
script: flash.script,
@@ -52,11 +55,14 @@ export class FlashEntityService {
}
@bindThis
- public packMany(
- flashs: MiFlash[],
+ public async packMany(
+ flashes: MiFlash[],
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) })));
}
}
diff --git a/packages/backend/src/core/entities/FollowRequestEntityService.ts b/packages/backend/src/core/entities/FollowRequestEntityService.ts
index 763b75101f..0101ec8aa7 100644
--- a/packages/backend/src/core/entities/FollowRequestEntityService.ts
+++ b/packages/backend/src/core/entities/FollowRequestEntityService.ts
@@ -10,6 +10,7 @@ import type { } from '@/models/Blocking.js';
import type { MiUser } from '@/models/User.js';
import type { MiFollowRequest } from '@/models/FollowRequest.js';
import { bindThis } from '@/decorators.js';
+import type { Packed } from '@/misc/json-schema.js';
import { UserEntityService } from './UserEntityService.js';
@Injectable()
@@ -26,14 +27,36 @@ export class FollowRequestEntityService {
public async pack(
src: MiFollowRequest['id'] | MiFollowRequest,
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 });
return {
id: request.id,
- follower: await this.userEntityService.pack(request.followerId, me),
- followee: await this.userEntityService.pack(request.followeeId, me),
+ follower: hint?.packedFollower ?? await this.userEntityService.pack(request.followerId, 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 });
+ }),
+ );
+ }
}
diff --git a/packages/backend/src/core/entities/FollowingEntityService.ts b/packages/backend/src/core/entities/FollowingEntityService.ts
index 24cd33e3f7..d2dbaf2270 100644
--- a/packages/backend/src/core/entities/FollowingEntityService.ts
+++ b/packages/backend/src/core/entities/FollowingEntityService.ts
@@ -78,6 +78,10 @@ export class FollowingEntityService {
populateFollowee?: boolean;
populateFollower?: boolean;
},
+ hint?: {
+ packedFollowee?: Packed<'UserDetailedNotMe'>,
+ packedFollower?: Packed<'UserDetailedNotMe'>,
+ },
): Promise<Packed<'Following'>> {
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(),
followeeId: following.followeeId,
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',
}) : 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',
}) : undefined,
});
}
@bindThis
- public packMany(
- followings: any[],
+ public async packMany(
+ followings: MiFollowing[],
me?: { id: MiUser['id'] } | null | undefined,
opts?: {
populateFollowee?: 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 });
+ }),
+ );
}
}
diff --git a/packages/backend/src/core/entities/GalleryPostEntityService.ts b/packages/backend/src/core/entities/GalleryPostEntityService.ts
index 101182a9e5..9746a4c1af 100644
--- a/packages/backend/src/core/entities/GalleryPostEntityService.ts
+++ b/packages/backend/src/core/entities/GalleryPostEntityService.ts
@@ -35,6 +35,9 @@ export class GalleryPostEntityService {
public async pack(
src: MiGalleryPost['id'] | MiGalleryPost,
me?: { id: MiUser['id'] } | null | undefined,
+ hint?: {
+ packedUser?: Packed<'UserLite'>
+ },
): Promise<Packed<'GalleryPost'>> {
const meId = me ? me.id : null;
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(),
updatedAt: post.updatedAt.toISOString(),
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,
description: post.description,
fileIds: post.fileIds,
@@ -58,11 +61,14 @@ export class GalleryPostEntityService {
}
@bindThis
- public packMany(
+ public async packMany(
posts: MiGalleryPost[],
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) })));
}
}
diff --git a/packages/backend/src/core/entities/InstanceEntityService.ts b/packages/backend/src/core/entities/InstanceEntityService.ts
index b4a518a1c6..002a93397d 100644
--- a/packages/backend/src/core/entities/InstanceEntityService.ts
+++ b/packages/backend/src/core/entities/InstanceEntityService.ts
@@ -39,7 +39,8 @@ export class InstanceEntityService {
followingCount: instance.followingCount,
followersCount: instance.followersCount,
isNotResponding: instance.isNotResponding,
- isSuspended: instance.isSuspended,
+ isSuspended: instance.suspensionState !== 'none',
+ suspensionState: instance.suspensionState,
isBlocked: this.utilityService.isBlockedHost(meta.blockedHosts, instance.host),
softwareName: instance.softwareName,
softwareVersion: instance.softwareVersion,
diff --git a/packages/backend/src/core/entities/InviteCodeEntityService.ts b/packages/backend/src/core/entities/InviteCodeEntityService.ts
index 891543bc0f..26f57e1299 100644
--- a/packages/backend/src/core/entities/InviteCodeEntityService.ts
+++ b/packages/backend/src/core/entities/InviteCodeEntityService.ts
@@ -12,6 +12,7 @@ import type { MiUser } from '@/models/User.js';
import type { MiRegistrationTicket } from '@/models/RegistrationTicket.js';
import { bindThis } from '@/decorators.js';
import { IdService } from '@/core/IdService.js';
+import { isNotNull } from '@/misc/is-not-null.js';
import { UserEntityService } from './UserEntityService.js';
@Injectable()
@@ -29,6 +30,10 @@ export class InviteCodeEntityService {
public async pack(
src: MiRegistrationTicket['id'] | MiRegistrationTicket,
me?: { id: MiUser['id'] } | null | undefined,
+ hints?: {
+ packedCreatedBy?: Packed<'UserLite'>,
+ packedUsedBy?: Packed<'UserLite'>,
+ },
): Promise<Packed<'InviteCode'>> {
const target = typeof src === 'object' ? src : await this.registrationTicketsRepository.findOneOrFail({
where: {
@@ -42,18 +47,28 @@ export class InviteCodeEntityService {
code: target.code,
expiresAt: target.expiresAt ? target.expiresAt.toISOString() : null,
createdAt: this.idService.parse(target.id).date.toISOString(),
- createdBy: target.createdBy ? await this.userEntityService.pack(target.createdBy, me) : null,
- usedBy: target.usedBy ? await this.userEntityService.pack(target.usedBy, me) : null,
+ createdBy: target.createdBy ? hints?.packedCreatedBy ?? await this.userEntityService.pack(target.createdBy, me) : null,
+ usedBy: target.usedBy ? hints?.packedUsedBy ?? await this.userEntityService.pack(target.usedBy, me) : null,
usedAt: target.usedAt ? target.usedAt.toISOString() : null,
used: !!target.usedAt,
});
}
@bindThis
- public packMany(
- targets: any[],
+ public async packMany(
+ tickets: MiRegistrationTicket[],
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 });
+ }),
+ );
}
}
diff --git a/packages/backend/src/core/entities/MetaEntityService.ts b/packages/backend/src/core/entities/MetaEntityService.ts
index fa643e45a7..34d46e50e5 100644
--- a/packages/backend/src/core/entities/MetaEntityService.ts
+++ b/packages/backend/src/core/entities/MetaEntityService.ts
@@ -67,6 +67,7 @@ export class MetaEntityService {
impressumUrl: instance.impressumUrl,
donationUrl: instance.donationUrl,
privacyPolicyUrl: instance.privacyPolicyUrl,
+ inquiryUrl: instance.inquiryUrl,
disableRegistration: instance.disableRegistration,
emailRequiredForSignup: instance.emailRequiredForSignup,
approvalRequiredForSignup: instance.approvalRequiredForSignup,
diff --git a/packages/backend/src/core/entities/ModerationLogEntityService.ts b/packages/backend/src/core/entities/ModerationLogEntityService.ts
index 205e147bd1..bf1b2a002c 100644
--- a/packages/backend/src/core/entities/ModerationLogEntityService.ts
+++ b/packages/backend/src/core/entities/ModerationLogEntityService.ts
@@ -8,9 +8,10 @@ import { DI } from '@/di-symbols.js';
import type { ModerationLogsRepository } from '@/models/_.js';
import { awaitAll } from '@/misc/prelude/await-all.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 { IdService } from '@/core/IdService.js';
+import type { Packed } from '@/misc/json-schema.js';
import { UserEntityService } from './UserEntityService.js';
@Injectable()
@@ -27,6 +28,9 @@ export class ModerationLogEntityService {
@bindThis
public async pack(
src: MiModerationLog['id'] | MiModerationLog,
+ hint?: {
+ packedUser?: Packed<'UserDetailedNotMe'>,
+ },
) {
const log = typeof src === 'object' ? src : await this.moderationLogsRepository.findOneByOrFail({ id: src });
@@ -36,17 +40,20 @@ export class ModerationLogEntityService {
type: log.type,
info: log.info,
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',
}),
});
}
@bindThis
- public packMany(
- reports: any[],
+ public async packMany(
+ 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) })));
}
}
diff --git a/packages/backend/src/core/entities/MutingEntityService.ts b/packages/backend/src/core/entities/MutingEntityService.ts
index 0a52f429a2..d361a20271 100644
--- a/packages/backend/src/core/entities/MutingEntityService.ts
+++ b/packages/backend/src/core/entities/MutingEntityService.ts
@@ -30,6 +30,9 @@ export class MutingEntityService {
public async pack(
src: MiMuting['id'] | MiMuting,
me?: { id: MiUser['id'] } | null | undefined,
+ hints?: {
+ packedMutee?: Packed<'UserDetailedNotMe'>,
+ },
): Promise<Packed<'Muting'>> {
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(),
expiresAt: muting.expiresAt ? muting.expiresAt.toISOString() : null,
muteeId: muting.muteeId,
- mutee: this.userEntityService.pack(muting.muteeId, me, {
+ mutee: hints?.packedMutee ?? this.userEntityService.pack(muting.muteeId, me, {
schema: 'UserDetailedNotMe',
}),
});
}
@bindThis
- public packMany(
- mutings: any[],
+ public async packMany(
+ mutings: MiMuting[],
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) })));
}
}
diff --git a/packages/backend/src/core/entities/NoteEntityService.ts b/packages/backend/src/core/entities/NoteEntityService.ts
index 9f59f89d17..ca755ea286 100644
--- a/packages/backend/src/core/entities/NoteEntityService.ts
+++ b/packages/backend/src/core/entities/NoteEntityService.ts
@@ -307,6 +307,7 @@ export class NoteEntityService implements OnModuleInit {
_hint_?: {
myReactions: Map<MiNote['id'], string | null>;
packedFiles: Map<MiNote['fileIds'][number], Packed<'DriveFile'> | null>;
+ packedUsers: Map<MiUser['id'], Packed<'UserLite'>>
};
},
): Promise<Packed<'Note'>> {
@@ -336,13 +337,14 @@ export class NoteEntityService implements OnModuleInit {
.filter(x => x.startsWith(':') && x.includes('@') && !x.includes('@.')) // リモートカスタム絵文字のみ
.map(x => this.reactionService.decodeReaction(x).reaction.replaceAll(':', ''));
const packedFiles = options?._hint_?.packedFiles;
+ const packedUsers = options?._hint_?.packedUsers;
const packed: Packed<'Note'> = await awaitAll({
id: note.id,
createdAt: this.idService.parse(note.id).date.toISOString(),
updatedAt: note.updatedAt ? note.updatedAt.toISOString() : undefined,
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,
cw: note.cw,
visibility: note.visibility,
@@ -465,12 +467,20 @@ export class NoteEntityService implements OnModuleInit {
// TODO: 本当は renote とか reply がないのに renoteId とか replyId があったらここで解決しておく
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 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, {
...options,
_hint_: {
myReactions: myReactionsMap,
packedFiles,
+ packedUsers,
},
})));
}
diff --git a/packages/backend/src/core/entities/NoteReactionEntityService.ts b/packages/backend/src/core/entities/NoteReactionEntityService.ts
index 3f4fa3cf96..46ec13704c 100644
--- a/packages/backend/src/core/entities/NoteReactionEntityService.ts
+++ b/packages/backend/src/core/entities/NoteReactionEntityService.ts
@@ -52,6 +52,9 @@ export class NoteReactionEntityService implements OnModuleInit {
options?: {
withNote: boolean;
},
+ hints?: {
+ packedUser?: Packed<'UserLite'>
+ },
): Promise<Packed<'NoteReaction'>> {
const opts = Object.assign({
withNote: false,
@@ -62,7 +65,7 @@ export class NoteReactionEntityService implements OnModuleInit {
return {
id: reaction.id,
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),
...(opts.withNote ? {
note: await this.noteEntityService.pack(reaction.note ?? reaction.noteId, me),
@@ -81,7 +84,9 @@ export class NoteReactionEntityService implements OnModuleInit {
const opts = Object.assign({
withNote: false,
}, options);
-
- return Promise.all(reactions.map(reaction => this.pack(reaction, me, opts)));
+ const _users = reactions.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(reactions.map(reaction => this.pack(reaction, me, opts, { packedUser: _userMap.get(reaction.userId) })));
}
}
diff --git a/packages/backend/src/core/entities/PageEntityService.ts b/packages/backend/src/core/entities/PageEntityService.ts
index 65c69a49a7..142d9e81db 100644
--- a/packages/backend/src/core/entities/PageEntityService.ts
+++ b/packages/backend/src/core/entities/PageEntityService.ts
@@ -40,6 +40,9 @@ export class PageEntityService {
public async pack(
src: MiPage['id'] | MiPage,
me?: { id: MiUser['id'] } | null | undefined,
+ hint?: {
+ packedUser?: Packed<'UserLite'>
+ },
): Promise<Packed<'Page'>> {
const meId = me ? me.id : null;
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(),
updatedAt: page.updatedAt.toISOString(),
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,
variables: page.variables,
title: page.title,
@@ -110,11 +113,14 @@ export class PageEntityService {
}
@bindThis
- public packMany(
+ public async packMany(
pages: MiPage[],
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) })));
}
}
diff --git a/packages/backend/src/core/entities/RenoteMutingEntityService.ts b/packages/backend/src/core/entities/RenoteMutingEntityService.ts
index 0b05a5db80..e4e154109a 100644
--- a/packages/backend/src/core/entities/RenoteMutingEntityService.ts
+++ b/packages/backend/src/core/entities/RenoteMutingEntityService.ts
@@ -30,6 +30,9 @@ export class RenoteMutingEntityService {
public async pack(
src: MiRenoteMuting['id'] | MiRenoteMuting,
me?: { id: MiUser['id'] } | null | undefined,
+ hints?: {
+ packedMutee?: Packed<'UserDetailedNotMe'>
+ },
): Promise<Packed<'RenoteMuting'>> {
const muting = typeof src === 'object' ? src : await this.renoteMutingsRepository.findOneByOrFail({ id: src });
@@ -37,18 +40,21 @@ export class RenoteMutingEntityService {
id: muting.id,
createdAt: this.idService.parse(muting.id).date.toISOString(),
muteeId: muting.muteeId,
- mutee: this.userEntityService.pack(muting.muteeId, me, {
+ mutee: hints?.packedMutee ?? this.userEntityService.pack(muting.muteeId, me, {
schema: 'UserDetailedNotMe',
}),
});
}
@bindThis
- public packMany(
- mutings: any[],
+ public async packMany(
+ mutings: MiRenoteMuting[],
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) })));
}
}
diff --git a/packages/backend/src/core/entities/ReversiGameEntityService.ts b/packages/backend/src/core/entities/ReversiGameEntityService.ts
index 32cbe631e4..df042e75c1 100644
--- a/packages/backend/src/core/entities/ReversiGameEntityService.ts
+++ b/packages/backend/src/core/entities/ReversiGameEntityService.ts
@@ -28,13 +28,15 @@ export class ReversiGameEntityService {
@bindThis
public async packDetail(
src: MiReversiGame['id'] | MiReversiGame,
+ hint?: {
+ packedUser1?: Packed<'UserLite'>,
+ packedUser2?: Packed<'UserLite'>,
+ },
): Promise<Packed<'ReversiGameDetailed'>> {
const game = typeof src === 'object' ? src : await this.reversiGamesRepository.findOneByOrFail({ id: src });
- const users = await Promise.all([
- this.userEntityService.pack(game.user1 ?? game.user1Id),
- this.userEntityService.pack(game.user2 ?? game.user2Id),
- ]);
+ const user1 = hint?.packedUser1 ?? await this.userEntityService.pack(game.user1 ?? game.user1Id);
+ const user2 = hint?.packedUser2 ?? await this.userEntityService.pack(game.user2 ?? game.user2Id);
return await awaitAll({
id: game.id,
@@ -49,10 +51,10 @@ export class ReversiGameEntityService {
user2Ready: game.user2Ready,
user1Id: game.user1Id,
user2Id: game.user2Id,
- user1: users[0],
- user2: users[1],
+ user1,
+ user2,
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,
timeoutUserId: game.timeoutUserId,
black: game.black,
@@ -68,22 +70,35 @@ export class ReversiGameEntityService {
}
@bindThis
- public packDetailMany(
- xs: MiReversiGame[],
+ public async packDetailMany(
+ 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
public async packLite(
src: MiReversiGame['id'] | MiReversiGame,
+ hint?: {
+ packedUser1?: Packed<'UserLite'>,
+ packedUser2?: Packed<'UserLite'>,
+ },
): Promise<Packed<'ReversiGameLite'>> {
const game = typeof src === 'object' ? src : await this.reversiGamesRepository.findOneByOrFail({ id: src });
- const users = await Promise.all([
- this.userEntityService.pack(game.user1 ?? game.user1Id),
- this.userEntityService.pack(game.user2 ?? game.user2Id),
- ]);
+ const user1 = hint?.packedUser1 ?? await this.userEntityService.pack(game.user1 ?? game.user1Id);
+ const user2 = hint?.packedUser2 ?? await this.userEntityService.pack(game.user2 ?? game.user2Id);
return await awaitAll({
id: game.id,
@@ -94,10 +109,10 @@ export class ReversiGameEntityService {
isEnded: game.isEnded,
user1Id: game.user1Id,
user2Id: game.user2Id,
- user1: users[0],
- user2: users[1],
+ user1,
+ user2,
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,
timeoutUserId: game.timeoutUserId,
black: game.black,
@@ -111,10 +126,21 @@ export class ReversiGameEntityService {
}
@bindThis
- public packLiteMany(
- xs: MiReversiGame[],
+ public async packLiteMany(
+ 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),
+ });
+ }),
+ );
}
}
diff --git a/packages/backend/src/core/entities/UserListEntityService.ts b/packages/backend/src/core/entities/UserListEntityService.ts
index 09cab24521..b77249c5cb 100644
--- a/packages/backend/src/core/entities/UserListEntityService.ts
+++ b/packages/backend/src/core/entities/UserListEntityService.ts
@@ -50,11 +50,14 @@ export class UserListEntityService {
public async packMembershipsMany(
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 => ({
id: x.id,
createdAt: this.idService.parse(x.id).date.toISOString(),
userId: x.userId,
- user: await this.userEntityService.pack(x.userId),
+ user: _userMap.get(x.userId) ?? await this.userEntityService.pack(x.userId),
withReplies: x.withReplies,
})));
}