enhance(backend): チャンネルの既読管理を削除
- 現状上手く機能していない - パフォーマンス上の理由 - 実装するにしてももっと効率的な方法がある
This commit is contained in:
parent
ecaf152b4a
commit
625fed8838
|
@ -502,18 +502,6 @@ export class NoteCreateService implements OnApplicationShutdown {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Channel
|
|
||||||
if (note.channelId) {
|
|
||||||
this.channelFollowingsRepository.findBy({ followeeId: note.channelId }).then(followings => {
|
|
||||||
for (const following of followings) {
|
|
||||||
this.noteReadService.insertNoteUnread(following.followerId, note, {
|
|
||||||
isSpecified: false,
|
|
||||||
isMentioned: false,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.reply) {
|
if (data.reply) {
|
||||||
this.saveReply(data.reply, note);
|
this.saveReply(data.reply, note);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,28 +1,20 @@
|
||||||
import { setTimeout } from 'node:timers/promises';
|
import { setTimeout } from 'node:timers/promises';
|
||||||
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
|
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
|
||||||
import { In, IsNull, Not } from 'typeorm';
|
import { In } from 'typeorm';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import type { User } from '@/models/entities/User.js';
|
import type { User } from '@/models/entities/User.js';
|
||||||
import type { Channel } from '@/models/entities/Channel.js';
|
|
||||||
import type { Packed } from '@/misc/json-schema.js';
|
import type { Packed } from '@/misc/json-schema.js';
|
||||||
import type { Note } from '@/models/entities/Note.js';
|
import type { Note } from '@/models/entities/Note.js';
|
||||||
import { IdService } from '@/core/IdService.js';
|
import { IdService } from '@/core/IdService.js';
|
||||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||||
import type { UsersRepository, NoteUnreadsRepository, MutingsRepository, NoteThreadMutingsRepository, FollowingsRepository, ChannelFollowingsRepository } from '@/models/index.js';
|
import type { NoteUnreadsRepository, MutingsRepository, NoteThreadMutingsRepository } from '@/models/index.js';
|
||||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { NotificationService } from './NotificationService.js';
|
|
||||||
import { AntennaService } from './AntennaService.js';
|
|
||||||
import { PushNotificationService } from './PushNotificationService.js';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class NoteReadService implements OnApplicationShutdown {
|
export class NoteReadService implements OnApplicationShutdown {
|
||||||
#shutdownController = new AbortController();
|
#shutdownController = new AbortController();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.usersRepository)
|
|
||||||
private usersRepository: UsersRepository,
|
|
||||||
|
|
||||||
@Inject(DI.noteUnreadsRepository)
|
@Inject(DI.noteUnreadsRepository)
|
||||||
private noteUnreadsRepository: NoteUnreadsRepository,
|
private noteUnreadsRepository: NoteUnreadsRepository,
|
||||||
|
|
||||||
|
@ -32,18 +24,8 @@ export class NoteReadService implements OnApplicationShutdown {
|
||||||
@Inject(DI.noteThreadMutingsRepository)
|
@Inject(DI.noteThreadMutingsRepository)
|
||||||
private noteThreadMutingsRepository: NoteThreadMutingsRepository,
|
private noteThreadMutingsRepository: NoteThreadMutingsRepository,
|
||||||
|
|
||||||
@Inject(DI.followingsRepository)
|
|
||||||
private followingsRepository: FollowingsRepository,
|
|
||||||
|
|
||||||
@Inject(DI.channelFollowingsRepository)
|
|
||||||
private channelFollowingsRepository: ChannelFollowingsRepository,
|
|
||||||
|
|
||||||
private userEntityService: UserEntityService,
|
|
||||||
private idService: IdService,
|
private idService: IdService,
|
||||||
private globalEventService: GlobalEventService,
|
private globalEventService: GlobalEventService,
|
||||||
private notificationService: NotificationService,
|
|
||||||
private antennaService: AntennaService,
|
|
||||||
private pushNotificationService: PushNotificationService,
|
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,7 +36,6 @@ export class NoteReadService implements OnApplicationShutdown {
|
||||||
isMentioned: boolean;
|
isMentioned: boolean;
|
||||||
}): Promise<void> {
|
}): Promise<void> {
|
||||||
//#region ミュートしているなら無視
|
//#region ミュートしているなら無視
|
||||||
// TODO: 現在の仕様ではChannelにミュートは適用されないのでよしなにケアする
|
|
||||||
const mute = await this.mutingsRepository.findBy({
|
const mute = await this.mutingsRepository.findBy({
|
||||||
muterId: userId,
|
muterId: userId,
|
||||||
});
|
});
|
||||||
|
@ -74,7 +55,6 @@ export class NoteReadService implements OnApplicationShutdown {
|
||||||
userId: userId,
|
userId: userId,
|
||||||
isSpecified: params.isSpecified,
|
isSpecified: params.isSpecified,
|
||||||
isMentioned: params.isMentioned,
|
isMentioned: params.isMentioned,
|
||||||
noteChannelId: note.channelId,
|
|
||||||
noteUserId: note.userId,
|
noteUserId: note.userId,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -92,9 +72,6 @@ export class NoteReadService implements OnApplicationShutdown {
|
||||||
if (params.isSpecified) {
|
if (params.isSpecified) {
|
||||||
this.globalEventService.publishMainStream(userId, 'unreadSpecifiedNote', note.id);
|
this.globalEventService.publishMainStream(userId, 'unreadSpecifiedNote', note.id);
|
||||||
}
|
}
|
||||||
if (note.channelId) {
|
|
||||||
this.globalEventService.publishMainStream(userId, 'unreadChannel', note.id);
|
|
||||||
}
|
|
||||||
}, () => { /* aborted, ignore it */ });
|
}, () => { /* aborted, ignore it */ });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,22 +79,9 @@ export class NoteReadService implements OnApplicationShutdown {
|
||||||
public async read(
|
public async read(
|
||||||
userId: User['id'],
|
userId: User['id'],
|
||||||
notes: (Note | Packed<'Note'>)[],
|
notes: (Note | Packed<'Note'>)[],
|
||||||
info?: {
|
|
||||||
following: Set<User['id']>;
|
|
||||||
followingChannels: Set<Channel['id']>;
|
|
||||||
},
|
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const followingChannels = info?.followingChannels ? info.followingChannels : new Set<string>((await this.channelFollowingsRepository.find({
|
|
||||||
where: {
|
|
||||||
followerId: userId,
|
|
||||||
},
|
|
||||||
select: ['followeeId'],
|
|
||||||
})).map(x => x.followeeId));
|
|
||||||
|
|
||||||
const myAntennas = (await this.antennaService.getAntennas()).filter(a => a.userId === userId);
|
|
||||||
const readMentions: (Note | Packed<'Note'>)[] = [];
|
const readMentions: (Note | Packed<'Note'>)[] = [];
|
||||||
const readSpecifiedNotes: (Note | Packed<'Note'>)[] = [];
|
const readSpecifiedNotes: (Note | Packed<'Note'>)[] = [];
|
||||||
const readChannelNotes: (Note | Packed<'Note'>)[] = [];
|
|
||||||
|
|
||||||
for (const note of notes) {
|
for (const note of notes) {
|
||||||
if (note.mentions && note.mentions.includes(userId)) {
|
if (note.mentions && note.mentions.includes(userId)) {
|
||||||
|
@ -125,17 +89,13 @@ export class NoteReadService implements OnApplicationShutdown {
|
||||||
} else if (note.visibleUserIds && note.visibleUserIds.includes(userId)) {
|
} else if (note.visibleUserIds && note.visibleUserIds.includes(userId)) {
|
||||||
readSpecifiedNotes.push(note);
|
readSpecifiedNotes.push(note);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (note.channelId && followingChannels.has(note.channelId)) {
|
|
||||||
readChannelNotes.push(note);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((readMentions.length > 0) || (readSpecifiedNotes.length > 0) || (readChannelNotes.length > 0)) {
|
if ((readMentions.length > 0) || (readSpecifiedNotes.length > 0)) {
|
||||||
// Remove the record
|
// Remove the record
|
||||||
await this.noteUnreadsRepository.delete({
|
await this.noteUnreadsRepository.delete({
|
||||||
userId: userId,
|
userId: userId,
|
||||||
noteId: In([...readMentions.map(n => n.id), ...readSpecifiedNotes.map(n => n.id), ...readChannelNotes.map(n => n.id)]),
|
noteId: In([...readMentions.map(n => n.id), ...readSpecifiedNotes.map(n => n.id)]),
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: ↓まとめてクエリしたい
|
// TODO: ↓まとめてクエリしたい
|
||||||
|
@ -159,16 +119,6 @@ export class NoteReadService implements OnApplicationShutdown {
|
||||||
this.globalEventService.publishMainStream(userId, 'readAllUnreadSpecifiedNotes');
|
this.globalEventService.publishMainStream(userId, 'readAllUnreadSpecifiedNotes');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.noteUnreadsRepository.countBy({
|
|
||||||
userId: userId,
|
|
||||||
noteChannelId: Not(IsNull()),
|
|
||||||
}).then(channelNoteCount => {
|
|
||||||
if (channelNoteCount === 0) {
|
|
||||||
// 全て既読になったイベントを発行
|
|
||||||
this.globalEventService.publishMainStream(userId, 'readAllChannels');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -234,18 +234,6 @@ export class UserEntityService implements OnModuleInit {
|
||||||
return false; // TODO
|
return false; // TODO
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
|
||||||
public async getHasUnreadChannel(userId: User['id']): Promise<boolean> {
|
|
||||||
const channels = await this.channelFollowingsRepository.findBy({ followerId: userId });
|
|
||||||
|
|
||||||
const unread = channels.length > 0 ? await this.noteUnreadsRepository.findOneBy({
|
|
||||||
userId: userId,
|
|
||||||
noteChannelId: In(channels.map(x => x.followeeId)),
|
|
||||||
}) : null;
|
|
||||||
|
|
||||||
return unread != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async getHasUnreadNotification(userId: User['id']): Promise<boolean> {
|
public async getHasUnreadNotification(userId: User['id']): Promise<boolean> {
|
||||||
const latestReadNotificationId = await this.redisClient.get(`latestReadNotification:${userId}`);
|
const latestReadNotificationId = await this.redisClient.get(`latestReadNotification:${userId}`);
|
||||||
|
@ -463,7 +451,7 @@ export class UserEntityService implements OnModuleInit {
|
||||||
}).then(count => count > 0),
|
}).then(count => count > 0),
|
||||||
hasUnreadAnnouncement: this.getHasUnreadAnnouncement(user.id),
|
hasUnreadAnnouncement: this.getHasUnreadAnnouncement(user.id),
|
||||||
hasUnreadAntenna: this.getHasUnreadAntenna(user.id),
|
hasUnreadAntenna: this.getHasUnreadAntenna(user.id),
|
||||||
hasUnreadChannel: this.getHasUnreadChannel(user.id),
|
hasUnreadChannel: false, // 後方互換性のため
|
||||||
hasUnreadNotification: this.getHasUnreadNotification(user.id),
|
hasUnreadNotification: this.getHasUnreadNotification(user.id),
|
||||||
hasPendingReceivedFollowRequest: this.getHasPendingReceivedFollowRequest(user.id),
|
hasPendingReceivedFollowRequest: this.getHasPendingReceivedFollowRequest(user.id),
|
||||||
mutedWords: profile!.mutedWords,
|
mutedWords: profile!.mutedWords,
|
||||||
|
|
|
@ -311,10 +311,6 @@ export const packedMeDetailedOnlySchema = {
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
nullable: false, optional: false,
|
nullable: false, optional: false,
|
||||||
},
|
},
|
||||||
hasUnreadChannel: {
|
|
||||||
type: 'boolean',
|
|
||||||
nullable: false, optional: false,
|
|
||||||
},
|
|
||||||
hasUnreadNotification: {
|
hasUnreadNotification: {
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
nullable: false, optional: false,
|
nullable: false, optional: false,
|
||||||
|
|
|
@ -186,10 +186,7 @@ export default class Connection {
|
||||||
if (note == null) return;
|
if (note == null) return;
|
||||||
|
|
||||||
if (this.user && (note.userId !== this.user.id)) {
|
if (this.user && (note.userId !== this.user.id)) {
|
||||||
this.noteReadService.read(this.user.id, [note], {
|
this.noteReadService.read(this.user.id, [note]);
|
||||||
following: this.following,
|
|
||||||
followingChannels: this.followingChannels,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -97,8 +97,6 @@ export interface MainStreamTypes {
|
||||||
readAllAntennas: undefined;
|
readAllAntennas: undefined;
|
||||||
unreadAntenna: Antenna;
|
unreadAntenna: Antenna;
|
||||||
readAllAnnouncements: undefined;
|
readAllAnnouncements: undefined;
|
||||||
readAllChannels: undefined;
|
|
||||||
unreadChannel: Note['id'];
|
|
||||||
myTokenRegenerated: undefined;
|
myTokenRegenerated: undefined;
|
||||||
signin: Signin;
|
signin: Signin;
|
||||||
registryUpdated: {
|
registryUpdated: {
|
||||||
|
|
|
@ -513,15 +513,6 @@ if ($i) {
|
||||||
updateAccount({ hasUnreadAnnouncement: false });
|
updateAccount({ hasUnreadAnnouncement: false });
|
||||||
});
|
});
|
||||||
|
|
||||||
main.on('readAllChannels', () => {
|
|
||||||
updateAccount({ hasUnreadChannel: false });
|
|
||||||
});
|
|
||||||
|
|
||||||
main.on('unreadChannel', () => {
|
|
||||||
updateAccount({ hasUnreadChannel: true });
|
|
||||||
sound.play('channel');
|
|
||||||
});
|
|
||||||
|
|
||||||
// トークンが再生成されたとき
|
// トークンが再生成されたとき
|
||||||
// このままではMisskeyが利用できないので強制的にサインアウトさせる
|
// このままではMisskeyが利用できないので強制的にサインアウトさせる
|
||||||
main.on('myTokenRegenerated', () => {
|
main.on('myTokenRegenerated', () => {
|
||||||
|
|
|
@ -88,7 +88,6 @@ export type MeDetailed = UserDetailed & {
|
||||||
hasPendingReceivedFollowRequest: boolean;
|
hasPendingReceivedFollowRequest: boolean;
|
||||||
hasUnreadAnnouncement: boolean;
|
hasUnreadAnnouncement: boolean;
|
||||||
hasUnreadAntenna: boolean;
|
hasUnreadAntenna: boolean;
|
||||||
hasUnreadChannel: boolean;
|
|
||||||
hasUnreadMentions: boolean;
|
hasUnreadMentions: boolean;
|
||||||
hasUnreadMessagingMessage: boolean;
|
hasUnreadMessagingMessage: boolean;
|
||||||
hasUnreadNotification: boolean;
|
hasUnreadNotification: boolean;
|
||||||
|
|
|
@ -28,8 +28,6 @@ export type Channels = {
|
||||||
readAllAntennas: () => void;
|
readAllAntennas: () => void;
|
||||||
unreadAntenna: (payload: Antenna) => void;
|
unreadAntenna: (payload: Antenna) => void;
|
||||||
readAllAnnouncements: () => void;
|
readAllAnnouncements: () => void;
|
||||||
readAllChannels: () => void;
|
|
||||||
unreadChannel: (payload: Note['id']) => void;
|
|
||||||
myTokenRegenerated: () => void;
|
myTokenRegenerated: () => void;
|
||||||
reversiNoInvites: () => void;
|
reversiNoInvites: () => void;
|
||||||
reversiInvited: (payload: FIXME) => void;
|
reversiInvited: (payload: FIXME) => void;
|
||||||
|
|
Loading…
Reference in New Issue