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