wip
This commit is contained in:
parent
aa8296ef11
commit
c7a0929072
|
@ -50,6 +50,7 @@ import { PollService } from './PollService.js';
|
||||||
import { PushNotificationService } from './PushNotificationService.js';
|
import { PushNotificationService } from './PushNotificationService.js';
|
||||||
import { QueryService } from './QueryService.js';
|
import { QueryService } from './QueryService.js';
|
||||||
import { ReactionService } from './ReactionService.js';
|
import { ReactionService } from './ReactionService.js';
|
||||||
|
import { ReactionsBufferingService } from './ReactionsBufferingService.js';
|
||||||
import { RelayService } from './RelayService.js';
|
import { RelayService } from './RelayService.js';
|
||||||
import { RoleService } from './RoleService.js';
|
import { RoleService } from './RoleService.js';
|
||||||
import { S3Service } from './S3Service.js';
|
import { S3Service } from './S3Service.js';
|
||||||
|
@ -193,6 +194,7 @@ const $ProxyAccountService: Provider = { provide: 'ProxyAccountService', useExis
|
||||||
const $PushNotificationService: Provider = { provide: 'PushNotificationService', useExisting: PushNotificationService };
|
const $PushNotificationService: Provider = { provide: 'PushNotificationService', useExisting: PushNotificationService };
|
||||||
const $QueryService: Provider = { provide: 'QueryService', useExisting: QueryService };
|
const $QueryService: Provider = { provide: 'QueryService', useExisting: QueryService };
|
||||||
const $ReactionService: Provider = { provide: 'ReactionService', useExisting: ReactionService };
|
const $ReactionService: Provider = { provide: 'ReactionService', useExisting: ReactionService };
|
||||||
|
const $ReactionsBufferingService: Provider = { provide: 'ReactionsBufferingService', useExisting: ReactionsBufferingService };
|
||||||
const $RelayService: Provider = { provide: 'RelayService', useExisting: RelayService };
|
const $RelayService: Provider = { provide: 'RelayService', useExisting: RelayService };
|
||||||
const $RoleService: Provider = { provide: 'RoleService', useExisting: RoleService };
|
const $RoleService: Provider = { provide: 'RoleService', useExisting: RoleService };
|
||||||
const $S3Service: Provider = { provide: 'S3Service', useExisting: S3Service };
|
const $S3Service: Provider = { provide: 'S3Service', useExisting: S3Service };
|
||||||
|
@ -342,6 +344,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
||||||
PushNotificationService,
|
PushNotificationService,
|
||||||
QueryService,
|
QueryService,
|
||||||
ReactionService,
|
ReactionService,
|
||||||
|
ReactionsBufferingService,
|
||||||
RelayService,
|
RelayService,
|
||||||
RoleService,
|
RoleService,
|
||||||
S3Service,
|
S3Service,
|
||||||
|
@ -487,6 +490,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
||||||
$PushNotificationService,
|
$PushNotificationService,
|
||||||
$QueryService,
|
$QueryService,
|
||||||
$ReactionService,
|
$ReactionService,
|
||||||
|
$ReactionsBufferingService,
|
||||||
$RelayService,
|
$RelayService,
|
||||||
$RoleService,
|
$RoleService,
|
||||||
$S3Service,
|
$S3Service,
|
||||||
|
@ -633,6 +637,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
||||||
PushNotificationService,
|
PushNotificationService,
|
||||||
QueryService,
|
QueryService,
|
||||||
ReactionService,
|
ReactionService,
|
||||||
|
ReactionsBufferingService,
|
||||||
RelayService,
|
RelayService,
|
||||||
RoleService,
|
RoleService,
|
||||||
S3Service,
|
S3Service,
|
||||||
|
@ -777,6 +782,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
||||||
$PushNotificationService,
|
$PushNotificationService,
|
||||||
$QueryService,
|
$QueryService,
|
||||||
$ReactionService,
|
$ReactionService,
|
||||||
|
$ReactionsBufferingService,
|
||||||
$RelayService,
|
$RelayService,
|
||||||
$RoleService,
|
$RoleService,
|
||||||
$S3Service,
|
$S3Service,
|
||||||
|
|
|
@ -30,6 +30,7 @@ import { RoleService } from '@/core/RoleService.js';
|
||||||
import { FeaturedService } from '@/core/FeaturedService.js';
|
import { FeaturedService } from '@/core/FeaturedService.js';
|
||||||
import { trackPromise } from '@/misc/promise-tracker.js';
|
import { trackPromise } from '@/misc/promise-tracker.js';
|
||||||
import { isQuote, isRenote } from '@/misc/is-renote.js';
|
import { isQuote, isRenote } from '@/misc/is-renote.js';
|
||||||
|
import { ReactionsBufferingService } from '@/core/ReactionsBufferingService.js';
|
||||||
|
|
||||||
const FALLBACK = '\u2764';
|
const FALLBACK = '\u2764';
|
||||||
const PER_NOTE_REACTION_USER_PAIR_CACHE_MAX = 16;
|
const PER_NOTE_REACTION_USER_PAIR_CACHE_MAX = 16;
|
||||||
|
@ -93,6 +94,7 @@ export class ReactionService {
|
||||||
private userEntityService: UserEntityService,
|
private userEntityService: UserEntityService,
|
||||||
private noteEntityService: NoteEntityService,
|
private noteEntityService: NoteEntityService,
|
||||||
private userBlockingService: UserBlockingService,
|
private userBlockingService: UserBlockingService,
|
||||||
|
private reactionsBufferingService: ReactionsBufferingService,
|
||||||
private idService: IdService,
|
private idService: IdService,
|
||||||
private featuredService: FeaturedService,
|
private featuredService: FeaturedService,
|
||||||
private globalEventService: GlobalEventService,
|
private globalEventService: GlobalEventService,
|
||||||
|
@ -200,7 +202,7 @@ export class ReactionService {
|
||||||
|
|
||||||
// Increment reactions count
|
// Increment reactions count
|
||||||
if (rbt) {
|
if (rbt) {
|
||||||
this.redisClient.hincrby(`reactionsBuffer:${note.id}`, reaction, 1);
|
this.reactionsBufferingService.create(note, reaction);
|
||||||
} else {
|
} else {
|
||||||
const sql = `jsonb_set("reactions", '{${reaction}}', (COALESCE("reactions"->>'${reaction}', '0')::int + 1)::text::jsonb)`;
|
const sql = `jsonb_set("reactions", '{${reaction}}', (COALESCE("reactions"->>'${reaction}', '0')::int + 1)::text::jsonb)`;
|
||||||
await this.notesRepository.createQueryBuilder().update()
|
await this.notesRepository.createQueryBuilder().update()
|
||||||
|
|
|
@ -0,0 +1,62 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
|
import * as Redis from 'ioredis';
|
||||||
|
import { DI } from '@/di-symbols.js';
|
||||||
|
import type { MiNote } from '@/models/Note.js';
|
||||||
|
import { bindThis } from '@/decorators.js';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class ReactionsBufferingService {
|
||||||
|
constructor(
|
||||||
|
@Inject(DI.redis)
|
||||||
|
private redisClient: Redis.Redis, // TODO: 専用のRedisインスタンスにする
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public async create(note: MiNote, reaction: string) {
|
||||||
|
this.redisClient.hincrby(`reactionsBuffer:${note.id}`, reaction, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public async delete(note: MiNote, reaction: string) {
|
||||||
|
this.redisClient.hincrby(`reactionsBuffer:${note.id}`, reaction, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public async get(note: MiNote) {
|
||||||
|
const result = await this.redisClient.hgetall(`reactionsBuffer:${note.id}`);
|
||||||
|
const delta = {};
|
||||||
|
for (const [name, count] of Object.entries(result)) {
|
||||||
|
delta[name] = parseInt(count);
|
||||||
|
}
|
||||||
|
return delta;
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public async getMany(notes: MiNote[]) {
|
||||||
|
const deltas = new Map<MiNote['id'], Record<string, number>>();
|
||||||
|
|
||||||
|
const pipeline = this.redisClient.pipeline();
|
||||||
|
for (const note of notes) {
|
||||||
|
pipeline.hgetall(`reactionsBuffer:${note.id}`);
|
||||||
|
}
|
||||||
|
const results = await pipeline.exec();
|
||||||
|
|
||||||
|
for (let i = 0; i < notes.length; i++) {
|
||||||
|
const note = notes[i];
|
||||||
|
const result = results![i][1];
|
||||||
|
const delta = {};
|
||||||
|
for (const [name, count] of Object.entries(result)) {
|
||||||
|
delta[name] = parseInt(count);
|
||||||
|
}
|
||||||
|
deltas.set(note.id, delta);
|
||||||
|
}
|
||||||
|
|
||||||
|
return deltas;
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,6 +17,7 @@ import type { UsersRepository, NotesRepository, FollowingsRepository, PollsRepos
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { DebounceLoader } from '@/misc/loader.js';
|
import { DebounceLoader } from '@/misc/loader.js';
|
||||||
import { IdService } from '@/core/IdService.js';
|
import { IdService } from '@/core/IdService.js';
|
||||||
|
import { ReactionsBufferingService } from '@/core/ReactionsBufferingService.js';
|
||||||
import type { OnModuleInit } from '@nestjs/common';
|
import type { OnModuleInit } from '@nestjs/common';
|
||||||
import type { CustomEmojiService } from '../CustomEmojiService.js';
|
import type { CustomEmojiService } from '../CustomEmojiService.js';
|
||||||
import type { ReactionService } from '../ReactionService.js';
|
import type { ReactionService } from '../ReactionService.js';
|
||||||
|
@ -41,6 +42,7 @@ export class NoteEntityService implements OnModuleInit {
|
||||||
private driveFileEntityService: DriveFileEntityService;
|
private driveFileEntityService: DriveFileEntityService;
|
||||||
private customEmojiService: CustomEmojiService;
|
private customEmojiService: CustomEmojiService;
|
||||||
private reactionService: ReactionService;
|
private reactionService: ReactionService;
|
||||||
|
private reactionsBufferingService: ReactionsBufferingService;
|
||||||
private idService: IdService;
|
private idService: IdService;
|
||||||
private noteLoader = new DebounceLoader(this.findNoteOrFail);
|
private noteLoader = new DebounceLoader(this.findNoteOrFail);
|
||||||
|
|
||||||
|
@ -75,6 +77,7 @@ export class NoteEntityService implements OnModuleInit {
|
||||||
//private driveFileEntityService: DriveFileEntityService,
|
//private driveFileEntityService: DriveFileEntityService,
|
||||||
//private customEmojiService: CustomEmojiService,
|
//private customEmojiService: CustomEmojiService,
|
||||||
//private reactionService: ReactionService,
|
//private reactionService: ReactionService,
|
||||||
|
//private reactionsBufferingService: ReactionsBufferingService,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,6 +86,7 @@ export class NoteEntityService implements OnModuleInit {
|
||||||
this.driveFileEntityService = this.moduleRef.get('DriveFileEntityService');
|
this.driveFileEntityService = this.moduleRef.get('DriveFileEntityService');
|
||||||
this.customEmojiService = this.moduleRef.get('CustomEmojiService');
|
this.customEmojiService = this.moduleRef.get('CustomEmojiService');
|
||||||
this.reactionService = this.moduleRef.get('ReactionService');
|
this.reactionService = this.moduleRef.get('ReactionService');
|
||||||
|
this.reactionsBufferingService = this.moduleRef.get('ReactionsBufferingService');
|
||||||
this.idService = this.moduleRef.get('IdService');
|
this.idService = this.moduleRef.get('IdService');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -319,6 +323,7 @@ export class NoteEntityService implements OnModuleInit {
|
||||||
const meId = me ? me.id : null;
|
const meId = me ? me.id : null;
|
||||||
const note = typeof src === 'object' ? src : await this.noteLoader.load(src);
|
const note = typeof src === 'object' ? src : await this.noteLoader.load(src);
|
||||||
const host = note.userHost;
|
const host = note.userHost;
|
||||||
|
const reactions = mergeReactions(note.reactions, opts._hint_?.reactionsDeltas.get(note.id) ?? {});
|
||||||
|
|
||||||
let text = note.text;
|
let text = note.text;
|
||||||
|
|
||||||
|
@ -332,7 +337,7 @@ export class NoteEntityService implements OnModuleInit {
|
||||||
: await this.channelsRepository.findOneBy({ id: note.channelId })
|
: await this.channelsRepository.findOneBy({ id: note.channelId })
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
const reactionEmojiNames = Object.keys(mergeReactions(note.reactions, opts._hint_?.reactionsDeltas.get(note.id) ?? {}))
|
const reactionEmojiNames = Object.keys(reactions)
|
||||||
.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;
|
||||||
|
@ -351,8 +356,8 @@ export class NoteEntityService implements OnModuleInit {
|
||||||
visibleUserIds: note.visibility === 'specified' ? note.visibleUserIds : undefined,
|
visibleUserIds: note.visibility === 'specified' ? note.visibleUserIds : undefined,
|
||||||
renoteCount: note.renoteCount,
|
renoteCount: note.renoteCount,
|
||||||
repliesCount: note.repliesCount,
|
repliesCount: note.repliesCount,
|
||||||
reactionCount: Object.values(mergeReactions(note.reactions, opts._hint_?.reactionsDeltas.get(note.id) ?? {})).reduce((a, b) => a + b, 0),
|
reactionCount: Object.values(reactions).reduce((a, b) => a + b, 0),
|
||||||
reactions: mergeReactions(this.reactionService.convertLegacyReactions(note.reactions), opts._hint_?.reactionsDeltas.get(note.id) ?? {}),
|
reactions: reactions,
|
||||||
reactionEmojis: this.customEmojiService.populateEmojis(reactionEmojiNames, host),
|
reactionEmojis: this.customEmojiService.populateEmojis(reactionEmojiNames, host),
|
||||||
reactionAndUserPairCache: opts.withReactionAndUserPairCache ? note.reactionAndUserPairCache : undefined,
|
reactionAndUserPairCache: opts.withReactionAndUserPairCache ? note.reactionAndUserPairCache : undefined,
|
||||||
emojis: host != null ? this.customEmojiService.populateEmojis(note.emojis, host) : undefined,
|
emojis: host != null ? this.customEmojiService.populateEmojis(note.emojis, host) : undefined,
|
||||||
|
@ -393,7 +398,7 @@ export class NoteEntityService implements OnModuleInit {
|
||||||
|
|
||||||
poll: note.hasPoll ? this.populatePoll(note, meId) : undefined,
|
poll: note.hasPoll ? this.populatePoll(note, meId) : undefined,
|
||||||
|
|
||||||
...(meId && Object.keys(mergeReactions(note.reactions, opts._hint_?.reactionsDeltas.get(note.id) ?? {})).length > 0 ? {
|
...(meId && Object.keys(reactions).length > 0 ? {
|
||||||
myReaction: this.populateMyReaction(note, meId, options?._hint_),
|
myReaction: this.populateMyReaction(note, meId, options?._hint_),
|
||||||
} : {}),
|
} : {}),
|
||||||
} : {}),
|
} : {}),
|
||||||
|
@ -417,27 +422,8 @@ export class NoteEntityService implements OnModuleInit {
|
||||||
) {
|
) {
|
||||||
if (notes.length === 0) return [];
|
if (notes.length === 0) return [];
|
||||||
|
|
||||||
const reactionsDeltas = new Map<MiNote['id'], Record<string, number>>();
|
|
||||||
|
|
||||||
const rbt = true;
|
const rbt = true;
|
||||||
|
const reactionsDeltas = rbt ? await this.reactionsBufferingService.getMany(notes) : new Map();
|
||||||
if (rbt) {
|
|
||||||
const pipeline = this.redisClient.pipeline();
|
|
||||||
for (const note of notes) {
|
|
||||||
pipeline.hgetall(`reactionsBuffer:${note.id}`);
|
|
||||||
}
|
|
||||||
const results = await pipeline.exec();
|
|
||||||
|
|
||||||
for (let i = 0; i < notes.length; i++) {
|
|
||||||
const note = notes[i];
|
|
||||||
const result = results![i][1];
|
|
||||||
const delta = {};
|
|
||||||
for (const [name, count] of Object.entries(result)) {
|
|
||||||
delta[name] = parseInt(count);
|
|
||||||
}
|
|
||||||
reactionsDeltas.set(note.id, delta);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const meId = me ? me.id : null;
|
const meId = me ? me.id : null;
|
||||||
const myReactionsMap = new Map<MiNote['id'], string | null>();
|
const myReactionsMap = new Map<MiNote['id'], string | null>();
|
||||||
|
|
Loading…
Reference in New Issue