make sure that fanout timeline notes are fully populated
This commit is contained in:
parent
4b37bb23a2
commit
1e2f34c813
1 changed files with 78 additions and 5 deletions
|
|
@ -20,6 +20,7 @@ import { CacheService } from '@/core/CacheService.js';
|
||||||
import { isReply } from '@/misc/is-reply.js';
|
import { isReply } from '@/misc/is-reply.js';
|
||||||
import { isInstanceMuted } from '@/misc/is-instance-muted.js';
|
import { isInstanceMuted } from '@/misc/is-instance-muted.js';
|
||||||
import { NoteVisibilityService, PopulatedNote } from '@/core/NoteVisibilityService.js';
|
import { NoteVisibilityService, PopulatedNote } from '@/core/NoteVisibilityService.js';
|
||||||
|
import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
|
||||||
|
|
||||||
type TimelineOptions = {
|
type TimelineOptions = {
|
||||||
untilId: string | null,
|
untilId: string | null,
|
||||||
|
|
@ -58,6 +59,7 @@ export class FanoutTimelineEndpointService {
|
||||||
private fanoutTimelineService: FanoutTimelineService,
|
private fanoutTimelineService: FanoutTimelineService,
|
||||||
private utilityService: UtilityService,
|
private utilityService: UtilityService,
|
||||||
private readonly noteVisibilityService: NoteVisibilityService,
|
private readonly noteVisibilityService: NoteVisibilityService,
|
||||||
|
private readonly federatedInstanceService: FederatedInstanceService,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -199,17 +201,88 @@ export class FanoutTimelineEndpointService {
|
||||||
private async getAndFilterFromDb(noteIds: string[], noteFilter: (note: MiNote) => boolean, idCompare: (a: string, b: string) => number): Promise<MiNote[]> {
|
private async getAndFilterFromDb(noteIds: string[], noteFilter: (note: MiNote) => boolean, idCompare: (a: string, b: string) => number): Promise<MiNote[]> {
|
||||||
const query = this.notesRepository.createQueryBuilder('note')
|
const query = this.notesRepository.createQueryBuilder('note')
|
||||||
.where('note.id IN (:...noteIds)', { noteIds: noteIds })
|
.where('note.id IN (:...noteIds)', { noteIds: noteIds })
|
||||||
.innerJoinAndSelect('note.user', 'user')
|
|
||||||
.leftJoinAndSelect('note.reply', 'reply')
|
.leftJoinAndSelect('note.reply', 'reply')
|
||||||
.leftJoinAndSelect('note.renote', 'renote')
|
.leftJoinAndSelect('note.renote', 'renote')
|
||||||
.leftJoinAndSelect('note.channel', 'channel')
|
.leftJoinAndSelect('note.channel', 'channel')
|
||||||
.leftJoinAndSelect('reply.user', 'replyUser')
|
|
||||||
.leftJoinAndSelect('renote.user', 'renoteUser');
|
|
||||||
|
|
||||||
const notes = (await query.getMany()).filter(noteFilter);
|
// Needed for populated note
|
||||||
|
.leftJoinAndSelect('renote.renote', 'renoteRenote')
|
||||||
|
.leftJoinAndSelect('renote.reply', 'renoteReply')
|
||||||
|
;
|
||||||
|
|
||||||
notes.sort((a, b) => idCompare(a.id, b.id));
|
const notes = await query.getMany();
|
||||||
|
|
||||||
|
// Manually populate user/instance since it's cacheable and avoids many joins.
|
||||||
|
// These fields *must* be populated or NoteVisibilityService won't work right!
|
||||||
|
await this.populateUsers(notes);
|
||||||
|
|
||||||
|
notes.filter(noteFilter).sort((a, b) => idCompare(a.id, b.id));
|
||||||
|
|
||||||
return notes;
|
return notes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async populateUsers(notes: MiNote[]): Promise<void> {
|
||||||
|
// Enumerate users and instances
|
||||||
|
const usersToFetch = new Set<string>();
|
||||||
|
const instancesToFetch = new Set<string>();
|
||||||
|
for (const note of notes) {
|
||||||
|
usersToFetch.add(note.userId);
|
||||||
|
if (note.userHost) {
|
||||||
|
instancesToFetch.add(note.userHost);
|
||||||
|
}
|
||||||
|
if (note.reply) {
|
||||||
|
usersToFetch.add(note.reply.userId);
|
||||||
|
if (note.replyUserHost) {
|
||||||
|
instancesToFetch.add(note.replyUserHost);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (note.renote) {
|
||||||
|
usersToFetch.add(note.renote.userId);
|
||||||
|
if (note.renoteUserHost) {
|
||||||
|
instancesToFetch.add(note.renoteUserHost);
|
||||||
|
}
|
||||||
|
if (note.renote.reply) {
|
||||||
|
usersToFetch.add(note.renote.reply.userId);
|
||||||
|
if (note.renote.replyUserHost) {
|
||||||
|
instancesToFetch.add(note.renote.replyUserHost);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (note.renote.renote) {
|
||||||
|
usersToFetch.add(note.renote.renote.userId);
|
||||||
|
if (note.renote.renoteUserHost) {
|
||||||
|
instancesToFetch.add(note.renote.renoteUserHost);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch everything and populate users
|
||||||
|
const [users, instances] = await Promise.all([
|
||||||
|
this.cacheService.getUsers(usersToFetch),
|
||||||
|
this.federatedInstanceService.federatedInstanceCache.fetchMany(instancesToFetch).then(i => new Map(i)),
|
||||||
|
]);
|
||||||
|
for (const [id, user] of Array.from(users)) {
|
||||||
|
users.set(id, {
|
||||||
|
...user,
|
||||||
|
instance: (user.host && instances.get(user.host)) || null,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assign users back to notes
|
||||||
|
for (const note of notes) {
|
||||||
|
note.user = users.get(note.userId) ?? null;
|
||||||
|
if (note.reply) {
|
||||||
|
note.reply.user = users.get(note.reply.userId) ?? null;
|
||||||
|
}
|
||||||
|
if (note.renote) {
|
||||||
|
note.renote.user = users.get(note.renote.userId) ?? null;
|
||||||
|
if (note.renote.reply) {
|
||||||
|
note.renote.reply.user = users.get(note.renote.reply.userId) ?? null;
|
||||||
|
}
|
||||||
|
if (note.renote.renote) {
|
||||||
|
note.renote.renote.user = users.get(note.renote.renote.userId) ?? null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue