implement note mutings and move favorited/renoted status into note entity directly
This commit is contained in:
parent
9bebf7718f
commit
7200c3d6c8
24 changed files with 342 additions and 181 deletions
|
|
@ -47,6 +47,7 @@ export class CacheService implements OnApplicationShutdown {
|
|||
public userBlockedCache: QuantumKVCache<Set<string>>; // NOTE: 「被」Blockキャッシュ
|
||||
public renoteMutingsCache: QuantumKVCache<Set<string>>;
|
||||
public threadMutingsCache: QuantumKVCache<Set<string>>;
|
||||
public noteMutingsCache: QuantumKVCache<Set<string>>;
|
||||
public userFollowingsCache: QuantumKVCache<Map<string, Omit<MiFollowing, 'isFollowerHibernated'>>>;
|
||||
public userFollowersCache: QuantumKVCache<Map<string, Omit<MiFollowing, 'isFollowerHibernated'>>>;
|
||||
public hibernatedUserCache: QuantumKVCache<boolean>;
|
||||
|
|
@ -152,13 +153,27 @@ export class CacheService implements OnApplicationShutdown {
|
|||
this.threadMutingsCache = new QuantumKVCache<Set<string>>(this.internalEventService, 'threadMutings', {
|
||||
lifetime: 1000 * 60 * 30, // 30m
|
||||
fetcher: muterId => this.noteThreadMutingsRepository
|
||||
.find({ where: { userId: muterId }, select: { threadId: true } })
|
||||
.find({ where: { userId: muterId, isPostMute: false }, select: { threadId: true } })
|
||||
.then(ms => new Set(ms.map(m => m.threadId))),
|
||||
bulkFetcher: muterIds => this.noteThreadMutingsRepository
|
||||
.createQueryBuilder('muting')
|
||||
.select('"muting"."userId"', 'userId')
|
||||
.addSelect('array_agg("muting"."threadId")', 'threadIds')
|
||||
.where({ userId: In(muterIds) })
|
||||
.where({ userId: In(muterIds), isPostMute: false })
|
||||
.getRawMany<{ userId: string, threadIds: string[] }>()
|
||||
.then(ms => ms.map(m => [m.userId, new Set(m.threadIds)])),
|
||||
});
|
||||
|
||||
this.noteMutingsCache = new QuantumKVCache<Set<string>>(this.internalEventService, 'noteMutings', {
|
||||
lifetime: 1000 * 60 * 30, // 30m
|
||||
fetcher: muterId => this.noteThreadMutingsRepository
|
||||
.find({ where: { userId: muterId, isPostMute: true }, select: { threadId: true } })
|
||||
.then(ms => new Set(ms.map(m => m.threadId))),
|
||||
bulkFetcher: muterIds => this.noteThreadMutingsRepository
|
||||
.createQueryBuilder('muting')
|
||||
.select('"muting"."userId"', 'userId')
|
||||
.addSelect('array_agg("muting"."threadId")', 'threadIds')
|
||||
.where({ userId: In(muterIds), isPostMute: true })
|
||||
.getRawMany<{ userId: string, threadIds: string[] }>()
|
||||
.then(ms => ms.map(m => [m.userId, new Set(m.threadIds)])),
|
||||
});
|
||||
|
|
@ -290,6 +305,8 @@ export class CacheService implements OnApplicationShutdown {
|
|||
this.userFollowingsCache.delete(body.id),
|
||||
this.userFollowersCache.delete(body.id),
|
||||
this.hibernatedUserCache.delete(body.id),
|
||||
this.threadMutingsCache.delete(body.id),
|
||||
this.noteMutingsCache.delete(body.id),
|
||||
]);
|
||||
}
|
||||
} else {
|
||||
|
|
@ -560,7 +577,11 @@ export class CacheService implements OnApplicationShutdown {
|
|||
this.userBlockingCache.dispose();
|
||||
this.userBlockedCache.dispose();
|
||||
this.renoteMutingsCache.dispose();
|
||||
this.threadMutingsCache.dispose();
|
||||
this.noteMutingsCache.dispose();
|
||||
this.userFollowingsCache.dispose();
|
||||
this.userFollowersCache.dispose();
|
||||
this.hibernatedUserCache.dispose();
|
||||
}
|
||||
|
||||
@bindThis
|
||||
|
|
|
|||
|
|
@ -394,6 +394,7 @@ export class WebhookTestService {
|
|||
private async toPackedNote(note: MiNote, detail = true, override?: Packed<'Note'>): Promise<Packed<'Note'>> {
|
||||
return {
|
||||
id: note.id,
|
||||
threadId: note.threadId ?? note.id,
|
||||
createdAt: new Date().toISOString(),
|
||||
deletedAt: null,
|
||||
text: note.text,
|
||||
|
|
@ -403,6 +404,10 @@ export class WebhookTestService {
|
|||
replyId: note.replyId,
|
||||
renoteId: note.renoteId,
|
||||
isHidden: false,
|
||||
isMutingThread: false,
|
||||
isMutingNote: false,
|
||||
isFavorited: false,
|
||||
isRenoted: false,
|
||||
visibility: note.visibility,
|
||||
mentions: note.mentions,
|
||||
visibleUserIds: note.visibleUserIds,
|
||||
|
|
|
|||
|
|
@ -11,11 +11,12 @@ import type { Packed } from '@/misc/json-schema.js';
|
|||
import { awaitAll } from '@/misc/prelude/await-all.js';
|
||||
import type { MiUser } from '@/models/User.js';
|
||||
import type { MiNote } from '@/models/Note.js';
|
||||
import type { UsersRepository, NotesRepository, FollowingsRepository, PollsRepository, PollVotesRepository, NoteReactionsRepository, ChannelsRepository, MiMeta, MiPollVote, MiPoll, MiChannel, MiFollowing } from '@/models/_.js';
|
||||
import type { UsersRepository, NotesRepository, FollowingsRepository, PollsRepository, PollVotesRepository, NoteReactionsRepository, ChannelsRepository, MiMeta, MiPollVote, MiPoll, MiChannel, MiFollowing, NoteFavoritesRepository } from '@/models/_.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { DebounceLoader } from '@/misc/loader.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import { ReactionsBufferingService } from '@/core/ReactionsBufferingService.js';
|
||||
import { QueryService } from '@/core/QueryService.js';
|
||||
import { isPackedPureRenote } from '@/misc/is-renote.js';
|
||||
import type { Config } from '@/config.js';
|
||||
import type { OnModuleInit } from '@nestjs/common';
|
||||
|
|
@ -55,6 +56,7 @@ function getAppearNoteIds(notes: MiNote[]): Set<string> {
|
|||
return appearNoteIds;
|
||||
}
|
||||
|
||||
// noinspection ES6MissingAwait
|
||||
@Injectable()
|
||||
export class NoteEntityService implements OnModuleInit {
|
||||
private userEntityService: UserEntityService;
|
||||
|
|
@ -96,6 +98,10 @@ export class NoteEntityService implements OnModuleInit {
|
|||
@Inject(DI.config)
|
||||
private readonly config: Config,
|
||||
|
||||
@Inject(DI.noteFavoritesRepository)
|
||||
private noteFavoritesRepository: NoteFavoritesRepository,
|
||||
|
||||
private readonly queryService: QueryService,
|
||||
//private userEntityService: UserEntityService,
|
||||
//private driveFileEntityService: DriveFileEntityService,
|
||||
//private customEmojiService: CustomEmojiService,
|
||||
|
|
@ -423,6 +429,9 @@ export class NoteEntityService implements OnModuleInit {
|
|||
channels: Map<string, MiChannel>;
|
||||
notes: Map<string, MiNote>;
|
||||
mutedThreads: Set<string>;
|
||||
mutedNotes: Set<string>;
|
||||
favoriteNotes: Set<string>;
|
||||
renotedNotes: Set<string>;
|
||||
};
|
||||
},
|
||||
): Promise<Packed<'Note'>> {
|
||||
|
|
@ -462,6 +471,23 @@ export class NoteEntityService implements OnModuleInit {
|
|||
const packedUsers = options?._hint_?.packedUsers;
|
||||
|
||||
const threadId = note.threadId ?? note.id;
|
||||
const [mutedThreads, mutedNotes, isFavorited, isRenoted] = await Promise.all([
|
||||
// mutedThreads
|
||||
opts._hint_?.mutedThreads
|
||||
?? (meId ? this.cacheService.threadMutingsCache.fetch(meId) : new Set<string>()),
|
||||
// mutedNotes
|
||||
opts._hint_?.mutedNotes
|
||||
?? (meId ? this.cacheService.noteMutingsCache.fetch(meId) : new Set<string>),
|
||||
// isFavorited
|
||||
opts._hint_?.favoriteNotes.has(note.id)
|
||||
?? (meId ? this.noteFavoritesRepository.existsBy({ userId: meId, noteId: note.id }) : false),
|
||||
// isRenoted
|
||||
opts._hint_?.renotedNotes.has(note.id)
|
||||
?? (meId ? this.queryService
|
||||
.andIsRenote(this.notesRepository.createQueryBuilder('note'), 'note')
|
||||
.andWhere({ renoteId: note.id, userId: meId })
|
||||
.getExists() : false),
|
||||
]);
|
||||
|
||||
const packed: Packed<'Note'> = await awaitAll({
|
||||
id: note.id,
|
||||
|
|
@ -505,8 +531,10 @@ export class NoteEntityService implements OnModuleInit {
|
|||
poll: opts._hint_?.polls.get(note.id),
|
||||
myVotes: opts._hint_?.pollVotes.get(note.id)?.get(note.userId),
|
||||
}) : undefined,
|
||||
isMuting: opts._hint_?.mutedThreads.has(threadId)
|
||||
?? (meId != null && this.cacheService.threadMutingsCache.fetch(meId).then(ms => ms.has(threadId))),
|
||||
isMutingThread: mutedThreads.has(threadId),
|
||||
isMutingNote: mutedNotes.has(note.id),
|
||||
isFavorited,
|
||||
isRenoted,
|
||||
|
||||
...(meId && Object.keys(reactions).length > 0 ? {
|
||||
myReaction: this.populateMyReaction({
|
||||
|
|
@ -654,7 +682,7 @@ export class NoteEntityService implements OnModuleInit {
|
|||
const fileIds = new Set(targetNotes.flatMap(n => n.fileIds));
|
||||
const mentionedUsers = new Set(targetNotes.flatMap(note => note.mentions));
|
||||
|
||||
const [{ bufferedReactions, myReactionsMap }, packedFiles, packedUsers, mentionHandles, userFollowings, userBlockers, polls, pollVotes, channels, mutedThreads] = await Promise.all([
|
||||
const [{ bufferedReactions, myReactionsMap }, packedFiles, packedUsers, mentionHandles, userFollowings, userBlockers, polls, pollVotes, channels, mutedThreads, mutedNotes, favoriteNotes, renotedNotes] = await Promise.all([
|
||||
// bufferedReactions & myReactionsMap
|
||||
this.getReactions(targetNotes, me),
|
||||
// packedFiles
|
||||
|
|
@ -692,6 +720,22 @@ export class NoteEntityService implements OnModuleInit {
|
|||
this.getChannels(targetNotes),
|
||||
// mutedThreads
|
||||
me ? this.cacheService.threadMutingsCache.fetch(me.id) : new Set<string>(),
|
||||
// mutedNotes
|
||||
me ? this.cacheService.noteMutingsCache.fetch(me.id) : new Set<string>(),
|
||||
// favoriteNotes
|
||||
me ? this.noteFavoritesRepository
|
||||
.createQueryBuilder('favorite')
|
||||
.select('favorite.noteId', 'noteId')
|
||||
.where({ userId: me.id, noteId: In(noteIds) })
|
||||
.getRawMany<{ noteId: string }>()
|
||||
.then(fs => new Set(fs.map(f => f.noteId))) : new Set<string>(),
|
||||
// renotedNotes
|
||||
me ? this.queryService
|
||||
.andIsRenote(this.notesRepository.createQueryBuilder('note'), 'note')
|
||||
.andWhere({ renoteId: In(noteIds), userId: me.id })
|
||||
.select('note.id', 'id')
|
||||
.getRawMany<{ id: string }>()
|
||||
.then(ns => new Set(ns.map(n => n.id))) : new Set<string>(),
|
||||
// (not returned)
|
||||
this.customEmojiService.prefetchEmojis(this.aggregateNoteEmojis(notes)),
|
||||
]);
|
||||
|
|
@ -711,6 +755,9 @@ export class NoteEntityService implements OnModuleInit {
|
|||
channels,
|
||||
notes: new Map(targetNotes.map(n => [n.id, n])),
|
||||
mutedThreads,
|
||||
mutedNotes,
|
||||
favoriteNotes,
|
||||
renotedNotes,
|
||||
},
|
||||
})));
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue