From aa2f87b92fc979cebbc4a2465d8c37b983a257d3 Mon Sep 17 00:00:00 2001 From: syuilo Date: Mon, 9 Oct 2023 20:31:39 +0900 Subject: [PATCH 01/22] =?UTF-8?q?enhance(backend):=20Redis=E3=81=B8?= =?UTF-8?q?=E3=81=AETL=E3=81=AE=E6=A7=8B=E7=AF=89=E3=82=92List=E3=81=A7?= =?UTF-8?q?=E8=A1=8C=E3=81=86=E3=82=88=E3=81=86=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/backend/src/core/AntennaService.ts | 9 +- packages/backend/src/core/CoreModule.ts | 6 + .../backend/src/core/NoteCreateService.ts | 215 ++++++++++++++++-- .../backend/src/core/RedisTimelineService.ts | 80 +++++++ packages/backend/src/core/RoleService.ts | 9 +- 5 files changed, 287 insertions(+), 32 deletions(-) create mode 100644 packages/backend/src/core/RedisTimelineService.ts diff --git a/packages/backend/src/core/AntennaService.ts b/packages/backend/src/core/AntennaService.ts index d9f27b8c6306..c7d18e01445c 100644 --- a/packages/backend/src/core/AntennaService.ts +++ b/packages/backend/src/core/AntennaService.ts @@ -16,6 +16,7 @@ import type { AntennasRepository, UserListJoiningsRepository } from '@/models/_. import { UtilityService } from '@/core/UtilityService.js'; import { bindThis } from '@/decorators.js'; import type { GlobalEvents } from '@/core/GlobalEventService.js'; +import { RedisTimelineService } from '@/core/RedisTimelineService.js'; import type { OnApplicationShutdown } from '@nestjs/common'; @Injectable() @@ -38,6 +39,7 @@ export class AntennaService implements OnApplicationShutdown { private utilityService: UtilityService, private globalEventService: GlobalEventService, + private redisTimelineService: RedisTimelineService, ) { this.antennasFetched = false; this.antennas = []; @@ -84,12 +86,7 @@ export class AntennaService implements OnApplicationShutdown { const redisPipeline = this.redisClient.pipeline(); for (const antenna of matchedAntennas) { - redisPipeline.xadd( - `antennaTimeline:${antenna.id}`, - 'MAXLEN', '~', '200', - '*', - 'note', note.id); - + this.redisTimelineService.push(`antennaTimeline:${antenna.id}`, note.id, 200, redisPipeline); this.globalEventService.publishAntennaStream(antenna.id, 'note', note); } diff --git a/packages/backend/src/core/CoreModule.ts b/packages/backend/src/core/CoreModule.ts index e1931f7b22ac..3d8d64786126 100644 --- a/packages/backend/src/core/CoreModule.ts +++ b/packages/backend/src/core/CoreModule.ts @@ -59,6 +59,7 @@ import { UtilityService } from './UtilityService.js'; import { FileInfoService } from './FileInfoService.js'; import { SearchService } from './SearchService.js'; import { ClipService } from './ClipService.js'; +import { RedisTimelineService } from './RedisTimelineService.js'; import { ChartLoggerService } from './chart/ChartLoggerService.js'; import FederationChart from './chart/charts/federation.js'; import NotesChart from './chart/charts/notes.js'; @@ -186,6 +187,7 @@ const $UtilityService: Provider = { provide: 'UtilityService', useExisting: Util const $FileInfoService: Provider = { provide: 'FileInfoService', useExisting: FileInfoService }; const $SearchService: Provider = { provide: 'SearchService', useExisting: SearchService }; const $ClipService: Provider = { provide: 'ClipService', useExisting: ClipService }; +const $RedisTimelineService: Provider = { provide: 'RedisTimelineService', useExisting: RedisTimelineService }; const $ChartLoggerService: Provider = { provide: 'ChartLoggerService', useExisting: ChartLoggerService }; const $FederationChart: Provider = { provide: 'FederationChart', useExisting: FederationChart }; @@ -316,6 +318,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting FileInfoService, SearchService, ClipService, + RedisTimelineService, ChartLoggerService, FederationChart, NotesChart, @@ -440,6 +443,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting $FileInfoService, $SearchService, $ClipService, + $RedisTimelineService, $ChartLoggerService, $FederationChart, $NotesChart, @@ -564,6 +568,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting FileInfoService, SearchService, ClipService, + RedisTimelineService, FederationChart, NotesChart, UsersChart, @@ -687,6 +692,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting $FileInfoService, $SearchService, $ClipService, + $RedisTimelineService, $FederationChart, $NotesChart, $UsersChart, diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts index f784b0584fba..5e9035b88b47 100644 --- a/packages/backend/src/core/NoteCreateService.ts +++ b/packages/backend/src/core/NoteCreateService.ts @@ -5,7 +5,7 @@ import { setImmediate } from 'node:timers/promises'; import * as mfm from 'mfm-js'; -import { In, DataSource } from 'typeorm'; +import { In, DataSource, IsNull, LessThan } from 'typeorm'; import * as Redis from 'ioredis'; import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common'; import RE2 from 're2'; @@ -14,7 +14,7 @@ import { extractCustomEmojisFromMfm } from '@/misc/extract-custom-emojis-from-mf import { extractHashtags } from '@/misc/extract-hashtags.js'; import type { IMentionedRemoteUsers } from '@/models/Note.js'; import { MiNote } from '@/models/Note.js'; -import type { ChannelsRepository, FollowingsRepository, InstancesRepository, MutedNotesRepository, MutingsRepository, NotesRepository, NoteThreadMutingsRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js'; +import type { ChannelFollowingsRepository, ChannelsRepository, FollowingsRepository, InstancesRepository, MutedNotesRepository, MiFollowing, MutingsRepository, NotesRepository, NoteThreadMutingsRepository, UserListMembershipsRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js'; import type { MiDriveFile } from '@/models/DriveFile.js'; import type { MiApp } from '@/models/App.js'; import { concat } from '@/misc/prelude/array.js'; @@ -53,6 +53,7 @@ import { DB_MAX_NOTE_TEXT_LENGTH } from '@/const.js'; import { RoleService } from '@/core/RoleService.js'; import { MetaService } from '@/core/MetaService.js'; import { SearchService } from '@/core/SearchService.js'; +import { RedisTimelineService } from '@/core/RedisTimelineService.js'; const mutedWordsCache = new MemorySingleCache<{ userId: MiUserProfile['userId']; mutedWords: MiUserProfile['mutedWords']; }[]>(1000 * 60 * 5); @@ -157,8 +158,8 @@ export class NoteCreateService implements OnApplicationShutdown { @Inject(DI.db) private db: DataSource, - @Inject(DI.redis) - private redisClient: Redis.Redis, + @Inject(DI.redisForTimelines) + private redisForTimelines: Redis.Redis, @Inject(DI.usersRepository) private usersRepository: UsersRepository, @@ -175,6 +176,9 @@ export class NoteCreateService implements OnApplicationShutdown { @Inject(DI.userProfilesRepository) private userProfilesRepository: UserProfilesRepository, + @Inject(DI.userListMembershipsRepository) + private userListMembershipsRepository: UserListMembershipsRepository, + @Inject(DI.mutedNotesRepository) private mutedNotesRepository: MutedNotesRepository, @@ -187,11 +191,15 @@ export class NoteCreateService implements OnApplicationShutdown { @Inject(DI.followingsRepository) private followingsRepository: FollowingsRepository, + @Inject(DI.channelFollowingsRepository) + private channelFollowingsRepository: ChannelFollowingsRepository, + private userEntityService: UserEntityService, private noteEntityService: NoteEntityService, private idService: IdService, private globalEventService: GlobalEventService, private queueService: QueueService, + private redisTimelineService: RedisTimelineService, private noteReadService: NoteReadService, private notificationService: NotificationService, private relayService: RelayService, @@ -251,19 +259,30 @@ export class NoteCreateService implements OnApplicationShutdown { } } - // Renote対象が「ホームまたは全体」以外の公開範囲ならreject - if (data.renote && data.renote.visibility !== 'public' && data.renote.visibility !== 'home' && data.renote.userId !== user.id) { - throw new Error('Renote target is not public or home'); - } - - // Renote対象がpublicではないならhomeにする - if (data.renote && data.renote.visibility !== 'public' && data.visibility === 'public') { - data.visibility = 'home'; - } + if (data.renote) { + switch (data.renote.visibility) { + case 'public': + // public noteは無条件にrenote可能 + break; + case 'home': + // home noteはhome以下にrenote可能 + if (data.visibility === 'public') { + data.visibility = 'home'; + } + break; + case 'followers': + // 他人のfollowers noteはreject + if (data.renote.userId !== user.id) { + throw new Error('Renote target is not public or home'); + } - // Renote対象がfollowersならfollowersにする - if (data.renote && data.renote.visibility === 'followers') { - data.visibility = 'followers'; + // Renote対象がfollowersならfollowersにする + data.visibility = 'followers'; + break; + case 'specified': + // specified / direct noteはreject + throw new Error('Renote target is not public or home'); + } } // 返信対象がpublicではないならhomeにする @@ -334,7 +353,7 @@ export class NoteCreateService implements OnApplicationShutdown { const note = await this.insertNote(user, data, tags, emojis, mentionedUsers); if (data.channel) { - this.redisClient.xadd( + this.redisForTimelines.xadd( `channelTimeline:${data.channel.id}`, 'MAXLEN', '~', this.config.perChannelMaxNoteCacheCount.toString(), '*', @@ -480,6 +499,8 @@ export class NoteCreateService implements OnApplicationShutdown { // Increment notes count (user) this.incNotesCountOfUser(user); + this.pushToTl(note, user); + // Word mute mutedWordsCache.fetch(() => this.userProfilesRepository.find({ where: { @@ -520,9 +541,8 @@ export class NoteCreateService implements OnApplicationShutdown { }); } - // この投稿を除く指定したユーザーによる指定したノートのリノートが存在しないとき - if (data.renote && (await this.noteEntityService.countSameRenotes(user.id, data.renote.id, note.id) === 0)) { - if (!user.isBot) this.incRenoteCount(data.renote); + if (data.renote && data.renote.userId !== user.id && !user.isBot) { + this.incRenoteCount(data.renote); } if (data.poll && data.poll.expiresAt) { @@ -812,6 +832,161 @@ export class NoteCreateService implements OnApplicationShutdown { return mentionedUsers; } + @bindThis + private async pushToTl(note: MiNote, user: { id: MiUser['id']; host: MiUser['host']; }) { + const meta = await this.metaService.fetch(); + + const r = this.redisForTimelines.pipeline(); + + if (note.channelId) { + this.redisTimelineService.push(`channelTimeline:${note.channelId}`, note.id, this.config.perChannelMaxNoteCacheCount, r); + + this.redisTimelineService.push(`userTimelineWithChannel:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax : meta.perRemoteUserUserTimelineCacheMax, r); + + const channelFollowings = await this.channelFollowingsRepository.find({ + where: { + followeeId: note.channelId, + }, + select: ['followerId'], + }); + + for (const channelFollowing of channelFollowings) { + this.redisTimelineService.push(`homeTimeline:${channelFollowing.followerId}`, note.id, meta.perUserHomeTimelineCacheMax, r); + if (note.fileIds.length > 0) { + this.redisTimelineService.push(`homeTimelineWithFiles:${channelFollowing.followerId}`, note.id, meta.perUserHomeTimelineCacheMax / 2, r); + } + } + } else { + // TODO: キャッシュ? + // eslint-disable-next-line prefer-const + let [followings, userListMemberships] = await Promise.all([ + this.followingsRepository.find({ + where: { + followeeId: user.id, + followerHost: IsNull(), + isFollowerHibernated: false, + }, + select: ['followerId', 'withReplies'], + }), + this.userListMembershipsRepository.find({ + where: { + userId: user.id, + }, + select: ['userListId', 'userListUserId', 'withReplies'], + }), + ]); + + if (note.visibility === 'followers') { + // TODO: 重そうだから何とかしたい Set 使う? + userListMemberships = userListMemberships.filter(x => followings.some(f => f.followerId === x.userListUserId)); + } + + // TODO: あまりにも数が多いと redisPipeline.exec に失敗する(理由は不明)ため、3万件程度を目安に分割して実行するようにする + for (const following of followings) { + // 基本的にvisibleUserIdsには自身のidが含まれている前提であること + if (note.visibility === 'specified' && !note.visibleUserIds.some(v => v === following.followerId)) continue; + + // 自分自身以外への返信 + if (note.replyId && note.replyUserId !== note.userId) { + if (!following.withReplies) continue; + } + + this.redisTimelineService.push(`homeTimeline:${following.followerId}`, note.id, meta.perUserHomeTimelineCacheMax, r); + if (note.fileIds.length > 0) { + this.redisTimelineService.push(`homeTimelineWithFiles:${following.followerId}`, note.id, meta.perUserHomeTimelineCacheMax / 2, r); + } + } + + for (const userListMembership of userListMemberships) { + // ダイレクトのとき、そのリストが対象外のユーザーの場合 + if ( + note.visibility === 'specified' && + !note.visibleUserIds.some(v => v === userListMembership.userListUserId) + ) continue; + + // 自分自身以外への返信 + if (note.replyId && note.replyUserId !== note.userId) { + if (!userListMembership.withReplies) continue; + } + + this.redisTimelineService.push(`userListTimeline:${userListMembership.userListId}`, note.id, meta.perUserListTimelineCacheMax, r); + if (note.fileIds.length > 0) { + this.redisTimelineService.push(`userListTimelineWithFiles:${userListMembership.userListId}`, note.id, meta.perUserListTimelineCacheMax / 2, r); + } + } + + if (note.visibility !== 'specified' || !note.visibleUserIds.some(v => v === user.id)) { // 自分自身のHTL + this.redisTimelineService.push(`homeTimeline:${user.id}`, note.id, meta.perUserHomeTimelineCacheMax, r); + if (note.fileIds.length > 0) { + this.redisTimelineService.push(`homeTimelineWithFiles:${user.id}`, note.id, meta.perUserHomeTimelineCacheMax / 2, r); + } + } + + // 自分自身以外への返信 + if (note.replyId && note.replyUserId !== note.userId) { + this.redisTimelineService.push(`userTimelineWithReplies:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax : meta.perRemoteUserUserTimelineCacheMax, r); + } else { + this.redisTimelineService.push(`userTimeline:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax : meta.perRemoteUserUserTimelineCacheMax, r); + if (note.fileIds.length > 0) { + this.redisTimelineService.push(`userTimelineWithFiles:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax / 2 : meta.perRemoteUserUserTimelineCacheMax / 2, r); + } + + if (note.visibility === 'public' && note.userHost == null) { + this.redisTimelineService.push('localTimeline', note.id, 1000, r); + if (note.fileIds.length > 0) { + this.redisTimelineService.push('localTimelineWithFiles', note.id, 500, r); + } + } + } + + if (Math.random() < 0.1) { + process.nextTick(() => { + this.checkHibernation(followings); + }); + } + } + + r.exec(); + } + + @bindThis + public async checkHibernation(followings: MiFollowing[]) { + if (followings.length === 0) return; + + const shuffle = (array: MiFollowing[]) => { + for (let i = array.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [array[i], array[j]] = [array[j], array[i]]; + } + return array; + }; + + // ランダムに最大1000件サンプリング + const samples = shuffle(followings).slice(0, Math.min(followings.length, 1000)); + + const hibernatedUsers = await this.usersRepository.find({ + where: { + id: In(samples.map(x => x.followerId)), + lastActiveDate: LessThan(new Date(Date.now() - (1000 * 60 * 60 * 24 * 50))), + }, + select: ['id'], + }); + + if (hibernatedUsers.length > 0) { + this.usersRepository.update({ + id: In(hibernatedUsers.map(x => x.id)), + }, { + isHibernated: true, + }); + + this.followingsRepository.update({ + followerId: In(hibernatedUsers.map(x => x.id)), + }, { + isFollowerHibernated: true, + }); + } + } + @bindThis public dispose(): void { this.#shutdownController.abort(); diff --git a/packages/backend/src/core/RedisTimelineService.ts b/packages/backend/src/core/RedisTimelineService.ts new file mode 100644 index 000000000000..f0ca2726d60b --- /dev/null +++ b/packages/backend/src/core/RedisTimelineService.ts @@ -0,0 +1,80 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * 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 { bindThis } from '@/decorators.js'; +import { IdService } from '@/core/IdService.js'; + +@Injectable() +export class RedisTimelineService { + constructor( + @Inject(DI.redisForTimelines) + private redisForTimelines: Redis.Redis, + + private idService: IdService, + ) { + } + + @bindThis + public push(tl: string, id: string, maxlen: number, pipeline: Redis.ChainableCommander) { + // リモートから遅れて届いた(もしくは後から追加された)投稿日時が古い投稿が追加されるとページネーション時に問題を引き起こすため、 + // 3分以内に投稿されたものでない場合、Redisにある最古のIDより新しい場合のみ追加する + if (this.idService.parse(id).date.getTime() > Date.now() - 1000 * 60 * 3) { + pipeline.lpush('list:' + tl, id); + if (Math.random() < 0.1) { // 10%の確率でトリム + pipeline.ltrim('list:' + tl, 0, maxlen - 1); + } + } else { + // 末尾のIDを取得 + this.redisForTimelines.lindex('list:' + tl, -1).then(lastId => { + if (lastId == null || (this.idService.parse(id).date.getTime() > this.idService.parse(lastId).date.getTime())) { + this.redisForTimelines.lpush('list:' + tl, id); + } else { + Promise.resolve(); + } + }); + } + } + + @bindThis + public get(name: string, untilId?: string | null, sinceId?: string | null) { + if (untilId && sinceId) { + return this.redisForTimelines.lrange('list:' + name, 0, -1) + .then(ids => ids.filter(id => id > untilId && id < sinceId).sort((a, b) => a > b ? -1 : 1)); + } else if (untilId) { + return this.redisForTimelines.lrange('list:' + name, 0, -1) + .then(ids => ids.filter(id => id > untilId).sort((a, b) => a > b ? -1 : 1)); + } else if (sinceId) { + return this.redisForTimelines.lrange('list:' + name, 0, -1) + .then(ids => ids.filter(id => id < sinceId).sort((a, b) => a < b ? -1 : 1)); + } else { + return this.redisForTimelines.lrange('list:' + name, 0, -1) + .then(ids => ids.sort((a, b) => a > b ? -1 : 1)); + } + } + + @bindThis + public getMulti(name: string[], untilId?: string | null, sinceId?: string | null): Promise { + const pipeline = this.redisForTimelines.pipeline(); + for (const n of name) { + pipeline.lrange('list:' + n, 0, -1); + } + return pipeline.exec().then(res => { + if (res == null) return []; + const tls = res.map(r => r[1] as string[]); + return tls.map(ids => + (untilId && sinceId) + ? ids.filter(id => id > untilId && id < sinceId).sort((a, b) => a > b ? -1 : 1) + : untilId + ? ids.filter(id => id > untilId).sort((a, b) => a > b ? -1 : 1) + : sinceId + ? ids.filter(id => id < sinceId).sort((a, b) => a < b ? -1 : 1) + : ids.sort((a, b) => a > b ? -1 : 1), + ); + }); + } +} diff --git a/packages/backend/src/core/RoleService.ts b/packages/backend/src/core/RoleService.ts index f2bd9de5ee20..2c3547e4acd9 100644 --- a/packages/backend/src/core/RoleService.ts +++ b/packages/backend/src/core/RoleService.ts @@ -20,6 +20,7 @@ import { IdService } from '@/core/IdService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { ModerationLogService } from '@/core/ModerationLogService.js'; import type { Packed } from '@/misc/json-schema.js'; +import { RedisTimelineService } from '@/core/RedisTimelineService.js'; import type { OnApplicationShutdown } from '@nestjs/common'; export type RolePolicies = { @@ -102,6 +103,7 @@ export class RoleService implements OnApplicationShutdown { private globalEventService: GlobalEventService, private idService: IdService, private moderationLogService: ModerationLogService, + private redisTimelineService: RedisTimelineService, ) { //this.onMessage = this.onMessage.bind(this); @@ -472,12 +474,7 @@ export class RoleService implements OnApplicationShutdown { const redisPipeline = this.redisClient.pipeline(); for (const role of roles) { - redisPipeline.xadd( - `roleTimeline:${role.id}`, - 'MAXLEN', '~', '1000', - '*', - 'note', note.id); - + this.redisTimelineService.push(`roleTimeline:${role.id}`, note.id, 1000, redisPipeline); this.globalEventService.publishRoleTimelineStream(role.id, 'note', note); } From d5a04b02ca796a92d2f2c6334ae2535e0f4fc12b Mon Sep 17 00:00:00 2001 From: syuilo Date: Mon, 9 Oct 2023 21:23:07 +0900 Subject: [PATCH 02/22] fix of 0bb0c32908 --- .../backend/src/core/RedisTimelineService.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/backend/src/core/RedisTimelineService.ts b/packages/backend/src/core/RedisTimelineService.ts index f0ca2726d60b..2f110c014e8d 100644 --- a/packages/backend/src/core/RedisTimelineService.ts +++ b/packages/backend/src/core/RedisTimelineService.ts @@ -44,16 +44,16 @@ export class RedisTimelineService { public get(name: string, untilId?: string | null, sinceId?: string | null) { if (untilId && sinceId) { return this.redisForTimelines.lrange('list:' + name, 0, -1) - .then(ids => ids.filter(id => id > untilId && id < sinceId).sort((a, b) => a > b ? -1 : 1)); + .then(ids => ids.filter(id => id < untilId && id > sinceId).sort((a, b) => a > b ? -1 : 1)); } else if (untilId) { return this.redisForTimelines.lrange('list:' + name, 0, -1) - .then(ids => ids.filter(id => id > untilId).sort((a, b) => a > b ? -1 : 1)); + .then(ids => ids.filter(id => id < untilId).sort((a, b) => a > b ? -1 : 1)); } else if (sinceId) { return this.redisForTimelines.lrange('list:' + name, 0, -1) - .then(ids => ids.filter(id => id < sinceId).sort((a, b) => a < b ? -1 : 1)); + .then(ids => ids.filter(id => id > sinceId).sort((a, b) => a < b ? -1 : 1)); } else { return this.redisForTimelines.lrange('list:' + name, 0, -1) - .then(ids => ids.sort((a, b) => a > b ? -1 : 1)); + .then(ids => ids.sort((a, b) => a < b ? -1 : 1)); } } @@ -68,12 +68,12 @@ export class RedisTimelineService { const tls = res.map(r => r[1] as string[]); return tls.map(ids => (untilId && sinceId) - ? ids.filter(id => id > untilId && id < sinceId).sort((a, b) => a > b ? -1 : 1) + ? ids.filter(id => id < untilId && id > sinceId).sort((a, b) => a > b ? -1 : 1) : untilId - ? ids.filter(id => id > untilId).sort((a, b) => a > b ? -1 : 1) + ? ids.filter(id => id < untilId).sort((a, b) => a > b ? -1 : 1) : sinceId - ? ids.filter(id => id < sinceId).sort((a, b) => a < b ? -1 : 1) - : ids.sort((a, b) => a > b ? -1 : 1), + ? ids.filter(id => id > sinceId).sort((a, b) => a < b ? -1 : 1) + : ids.sort((a, b) => a < b ? -1 : 1), ); }); } From 23ec97264faf7329f265f1d078178987cfd67f30 Mon Sep 17 00:00:00 2001 From: syuilo Date: Mon, 9 Oct 2023 21:52:31 +0900 Subject: [PATCH 03/22] fix of 0bb0c32908 --- packages/backend/src/core/RedisTimelineService.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/backend/src/core/RedisTimelineService.ts b/packages/backend/src/core/RedisTimelineService.ts index 2f110c014e8d..94541759cc08 100644 --- a/packages/backend/src/core/RedisTimelineService.ts +++ b/packages/backend/src/core/RedisTimelineService.ts @@ -53,7 +53,7 @@ export class RedisTimelineService { .then(ids => ids.filter(id => id > sinceId).sort((a, b) => a < b ? -1 : 1)); } else { return this.redisForTimelines.lrange('list:' + name, 0, -1) - .then(ids => ids.sort((a, b) => a < b ? -1 : 1)); + .then(ids => ids.sort((a, b) => a > b ? -1 : 1)); } } @@ -73,7 +73,7 @@ export class RedisTimelineService { ? ids.filter(id => id < untilId).sort((a, b) => a > b ? -1 : 1) : sinceId ? ids.filter(id => id > sinceId).sort((a, b) => a < b ? -1 : 1) - : ids.sort((a, b) => a < b ? -1 : 1), + : ids.sort((a, b) => a > b ? -1 : 1), ); }); } From c941eaebecb80b3b20bfd5a482682a7ea75dad7a Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Tue, 10 Oct 2023 22:10:36 +0900 Subject: [PATCH 04/22] feat: improve tl performance (#11946) (only updating redis TL) --- .config/docker_example.yml | 8 ++ .config/example.yml | 10 +++ .devcontainer/devcontainer.yml | 8 ++ chart/files/default.yml | 8 ++ packages/backend/jest.config.cjs | 2 + .../migration/1696222183852-withReplies.js | 20 +++++ .../1696323464251-user-list-membership.js | 11 +++ .../migration/1696331570827-hibernation.js | 17 ++++ packages/backend/src/GlobalModule.ts | 14 +++- packages/backend/src/config.ts | 3 + .../backend/src/core/AccountMoveService.ts | 30 +++---- packages/backend/src/core/AntennaService.ts | 14 ++-- packages/backend/src/core/CacheService.ts | 2 +- packages/backend/src/core/CoreModule.ts | 6 ++ .../backend/src/core/NotificationService.ts | 8 +- .../backend/src/core/UserBlockingService.ts | 8 +- .../backend/src/core/UserFollowingService.ts | 6 +- packages/backend/src/core/UserListService.ts | 36 ++++++--- packages/backend/src/core/UserService.ts | 53 +++++++++++++ .../src/core/entities/NoteEntityService.ts | 6 +- .../src/core/entities/UserEntityService.ts | 1 + .../core/entities/UserListEntityService.ts | 24 +++++- packages/backend/src/di-symbols.ts | 3 +- packages/backend/src/models/Following.ts | 12 +++ .../backend/src/models/RepositoryModule.ts | 12 +-- packages/backend/src/models/User.ts | 5 ++ ...erListJoining.ts => UserListMembership.ts} | 12 ++- packages/backend/src/models/_.ts | 6 +- .../backend/src/models/json-schema/user.ts | 4 + packages/backend/src/postgres.ts | 4 +- .../ExportAntennasProcessorService.ts | 10 +-- .../ExportUserListsProcessorService.ts | 10 +-- .../ImportUserListsProcessorService.ts | 8 +- .../backend/src/server/api/EndpointsModule.ts | 16 +++- .../server/api/StreamingApiServerService.ts | 10 +-- packages/backend/src/server/api/endpoints.ts | 8 +- .../server/api/endpoints/following/update.ts | 6 +- .../src/server/api/endpoints/roles/notes.ts | 6 +- .../users/lists/create-from-public.ts | 10 +-- .../endpoints/users/lists/get-memberships.ts | 79 +++++++++++++++++++ .../server/api/endpoints/users/lists/push.ts | 8 +- .../users/lists/update-membership.ts | 79 +++++++++++++++++++ .../server/api/stream/channels/user-list.ts | 12 +-- 43 files changed, 504 insertions(+), 111 deletions(-) create mode 100644 packages/backend/migration/1696222183852-withReplies.js create mode 100644 packages/backend/migration/1696323464251-user-list-membership.js create mode 100644 packages/backend/migration/1696331570827-hibernation.js create mode 100644 packages/backend/src/core/UserService.ts rename packages/backend/src/models/{UserListJoining.ts => UserListMembership.ts} (76%) create mode 100644 packages/backend/src/server/api/endpoints/users/lists/get-memberships.ts create mode 100644 packages/backend/src/server/api/endpoints/users/lists/update-membership.ts diff --git a/.config/docker_example.yml b/.config/docker_example.yml index 940b095fe29c..29217462958c 100644 --- a/.config/docker_example.yml +++ b/.config/docker_example.yml @@ -95,6 +95,14 @@ redis: # #prefix: example-prefix # #db: 1 +#redisForTimelines: +# host: redis +# port: 6379 +# #family: 0 # 0=Both, 4=IPv4, 6=IPv6 +# #pass: example-pass +# #prefix: example-prefix +# #db: 1 + # ┌───────────────────────────┐ #───┘ MeiliSearch configuration └───────────────────────────── diff --git a/.config/example.yml b/.config/example.yml index 03864a32994f..0e4f2f5a15cd 100644 --- a/.config/example.yml +++ b/.config/example.yml @@ -105,6 +105,16 @@ redis: # # You can specify more ioredis options... # #username: example-username +#redisForTimelines: +# host: localhost +# port: 6379 +# #family: 0 # 0=Both, 4=IPv4, 6=IPv6 +# #pass: example-pass +# #prefix: example-prefix +# #db: 1 +# # You can specify more ioredis options... +# #username: example-username + # ┌───────────────────────────┐ #───┘ MeiliSearch configuration └───────────────────────────── diff --git a/.devcontainer/devcontainer.yml b/.devcontainer/devcontainer.yml index 5dcd41599acd..3d57d1245daa 100644 --- a/.devcontainer/devcontainer.yml +++ b/.devcontainer/devcontainer.yml @@ -95,6 +95,14 @@ redis: # #prefix: example-prefix # #db: 1 +#redisForTimelines: +# host: redis +# port: 6379 +# #family: 0 # 0=Both, 4=IPv4, 6=IPv6 +# #pass: example-pass +# #prefix: example-prefix +# #db: 1 + # ┌───────────────────────────┐ #───┘ MeiliSearch configuration └───────────────────────────── diff --git a/chart/files/default.yml b/chart/files/default.yml index 90b574b99f40..87b2f677ebe2 100644 --- a/chart/files/default.yml +++ b/chart/files/default.yml @@ -116,6 +116,14 @@ redis: # #prefix: example-prefix # #db: 1 +#redisForTimelines: +# host: redis +# port: 6379 +# #family: 0 # 0=Both, 4=IPv4, 6=IPv6 +# #pass: example-pass +# #prefix: example-prefix +# #db: 1 + # ┌───────────────────────────┐ #───┘ MeiliSearch configuration └───────────────────────────── diff --git a/packages/backend/jest.config.cjs b/packages/backend/jest.config.cjs index 6b1afec73492..97d777c86287 100644 --- a/packages/backend/jest.config.cjs +++ b/packages/backend/jest.config.cjs @@ -216,4 +216,6 @@ module.exports = { maxWorkers: 1, // Make it use worker (that can be killed and restarted) logHeapUsage: true, // To debug when out-of-memory happens on CI workerIdleMemoryLimit: '1GiB', // Limit the worker to 1GB (GitHub Workflows dies at 2GB) + + maxConcurrency: 32, }; diff --git a/packages/backend/migration/1696222183852-withReplies.js b/packages/backend/migration/1696222183852-withReplies.js new file mode 100644 index 000000000000..a2fd6aef053b --- /dev/null +++ b/packages/backend/migration/1696222183852-withReplies.js @@ -0,0 +1,20 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class WithReplies1696222183852 { + name = 'WithReplies1696222183852' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "following" ADD "withReplies" boolean NOT NULL DEFAULT false`); + await queryRunner.query(`ALTER TABLE "user_list_joining" ADD "withReplies" boolean NOT NULL DEFAULT true`); + await queryRunner.query(`CREATE INDEX "IDX_d74d8ab5efa7e3bb82825c0fa2" ON "following" ("followeeId", "followerHost") `); + } + + async down(queryRunner) { + await queryRunner.query(`DROP INDEX "public"."IDX_d74d8ab5efa7e3bb82825c0fa2"`); + await queryRunner.query(`ALTER TABLE "user_list_joining" DROP COLUMN "withReplies"`); + await queryRunner.query(`ALTER TABLE "following" DROP COLUMN "withReplies"`); + } +} diff --git a/packages/backend/migration/1696323464251-user-list-membership.js b/packages/backend/migration/1696323464251-user-list-membership.js new file mode 100644 index 000000000000..7534040c4c9b --- /dev/null +++ b/packages/backend/migration/1696323464251-user-list-membership.js @@ -0,0 +1,11 @@ +export class UserListMembership1696323464251 { + name = 'UserListMembership1696323464251' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "user_list_joining" RENAME TO "user_list_membership"`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "user_list_membership" RENAME TO "user_list_joining"`); + } +} diff --git a/packages/backend/migration/1696331570827-hibernation.js b/packages/backend/migration/1696331570827-hibernation.js new file mode 100644 index 000000000000..119d35913f7b --- /dev/null +++ b/packages/backend/migration/1696331570827-hibernation.js @@ -0,0 +1,17 @@ +export class Hibernation1696331570827 { + name = 'Hibernation1696331570827' + + async up(queryRunner) { + await queryRunner.query(`DROP INDEX "public"."IDX_d74d8ab5efa7e3bb82825c0fa2"`); + await queryRunner.query(`ALTER TABLE "user" ADD "isHibernated" boolean NOT NULL DEFAULT false`); + await queryRunner.query(`ALTER TABLE "following" ADD "isFollowerHibernated" boolean NOT NULL DEFAULT false`); + await queryRunner.query(`CREATE INDEX "IDX_ce62b50d882d4e9dee10ad0d2f" ON "following" ("followeeId", "followerHost", "isFollowerHibernated") `); + } + + async down(queryRunner) { + await queryRunner.query(`DROP INDEX "public"."IDX_ce62b50d882d4e9dee10ad0d2f"`); + await queryRunner.query(`ALTER TABLE "following" DROP COLUMN "isFollowerHibernated"`); + await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "isHibernated"`); + await queryRunner.query(`CREATE INDEX "IDX_d74d8ab5efa7e3bb82825c0fa2" ON "following" ("followeeId", "followerHost") `); + } +} diff --git a/packages/backend/src/GlobalModule.ts b/packages/backend/src/GlobalModule.ts index 9f1ee9fcaa3f..3e9d19f82598 100644 --- a/packages/backend/src/GlobalModule.ts +++ b/packages/backend/src/GlobalModule.ts @@ -70,11 +70,19 @@ const $redisForSub: Provider = { inject: [DI.config], }; +const $redisForTimelines: Provider = { + provide: DI.redisForTimelines, + useFactory: (config: Config) => { + return new Redis.Redis(config.redisForTimelines); + }, + inject: [DI.config], +}; + @Global() @Module({ imports: [RepositoryModule], - providers: [$config, $db, $meilisearch, $redis, $redisForPub, $redisForSub], - exports: [$config, $db, $meilisearch, $redis, $redisForPub, $redisForSub, RepositoryModule], + providers: [$config, $db, $meilisearch, $redis, $redisForPub, $redisForSub, $redisForTimelines], + exports: [$config, $db, $meilisearch, $redis, $redisForPub, $redisForSub, $redisForTimelines, RepositoryModule], }) export class GlobalModule implements OnApplicationShutdown { constructor( @@ -82,6 +90,7 @@ export class GlobalModule implements OnApplicationShutdown { @Inject(DI.redis) private redisClient: Redis.Redis, @Inject(DI.redisForPub) private redisForPub: Redis.Redis, @Inject(DI.redisForSub) private redisForSub: Redis.Redis, + @Inject(DI.redisForTimelines) private redisForTimelines: Redis.Redis, ) {} public async dispose(): Promise { @@ -98,6 +107,7 @@ export class GlobalModule implements OnApplicationShutdown { this.redisClient.disconnect(), this.redisForPub.disconnect(), this.redisForSub.disconnect(), + this.redisForTimelines.disconnect(), ]); } diff --git a/packages/backend/src/config.ts b/packages/backend/src/config.ts index ba3a72d84a25..45367b8e5e61 100644 --- a/packages/backend/src/config.ts +++ b/packages/backend/src/config.ts @@ -47,6 +47,7 @@ type Source = { redis: RedisOptionsSource; redisForPubsub?: RedisOptionsSource; redisForJobQueue?: RedisOptionsSource; + redisForTimelines?: RedisOptionsSource; meilisearch?: { host: string; port: string; @@ -167,6 +168,7 @@ export type Config = { redis: RedisOptions & RedisOptionsSource; redisForPubsub: RedisOptions & RedisOptionsSource; redisForJobQueue: RedisOptions & RedisOptionsSource; + redisForTimelines: RedisOptions & RedisOptionsSource; perChannelMaxNoteCacheCount: number; perUserNotificationsMaxCount: number; deactivateAntennaThreshold: number; @@ -241,6 +243,7 @@ export function loadConfig(): Config { redis, redisForPubsub: config.redisForPubsub ? convertRedisOptions(config.redisForPubsub, host) : redis, redisForJobQueue: config.redisForJobQueue ? convertRedisOptions(config.redisForJobQueue, host) : redis, + redisForTimelines: config.redisForTimelines ? convertRedisOptions(config.redisForTimelines, host) : redis, id: config.id, proxy: config.proxy, proxySmtp: config.proxySmtp, diff --git a/packages/backend/src/core/AccountMoveService.ts b/packages/backend/src/core/AccountMoveService.ts index ec1d013922b7..ba3413007d84 100644 --- a/packages/backend/src/core/AccountMoveService.ts +++ b/packages/backend/src/core/AccountMoveService.ts @@ -9,7 +9,7 @@ import { IsNull, In, MoreThan, Not } from 'typeorm'; import { bindThis } from '@/decorators.js'; import { DI } from '@/di-symbols.js'; import type { MiLocalUser, MiRemoteUser, MiUser } from '@/models/User.js'; -import type { BlockingsRepository, FollowingsRepository, InstancesRepository, MutingsRepository, UserListJoiningsRepository, UsersRepository } from '@/models/_.js'; +import type { BlockingsRepository, FollowingsRepository, InstancesRepository, MutingsRepository, UserListMembershipsRepository, UsersRepository } from '@/models/_.js'; import type { RelationshipJobData, ThinUser } from '@/queue/types.js'; import { IdService } from '@/core/IdService.js'; @@ -42,8 +42,8 @@ export class AccountMoveService { @Inject(DI.mutingsRepository) private mutingsRepository: MutingsRepository, - @Inject(DI.userListJoiningsRepository) - private userListJoiningsRepository: UserListJoiningsRepository, + @Inject(DI.userListMembershipsRepository) + private userListMembershipsRepository: UserListMembershipsRepository, @Inject(DI.instancesRepository) private instancesRepository: InstancesRepository, @@ -215,40 +215,40 @@ export class AccountMoveService { @bindThis public async updateLists(src: ThinUser, dst: MiUser): Promise { // Return if there is no list to be updated. - const oldJoinings = await this.userListJoiningsRepository.find({ + const oldMemberships = await this.userListMembershipsRepository.find({ where: { userId: src.id, }, }); - if (oldJoinings.length === 0) return; + if (oldMemberships.length === 0) return; - const existingUserListIds = await this.userListJoiningsRepository.find({ + const existingUserListIds = await this.userListMembershipsRepository.find({ where: { userId: dst.id, }, - }).then(joinings => joinings.map(joining => joining.userListId)); + }).then(memberships => memberships.map(membership => membership.userListId)); - const newJoinings: Map = new Map(); + const newMemberships: Map = new Map(); // 重複しないようにIDを生成 const genId = (): string => { let id: string; do { id = this.idService.genId(); - } while (newJoinings.has(id)); + } while (newMemberships.has(id)); return id; }; - for (const joining of oldJoinings) { - if (existingUserListIds.includes(joining.userListId)) continue; // skip if dst exists in this user's list - newJoinings.set(genId(), { + for (const membership of oldMemberships) { + if (existingUserListIds.includes(membership.userListId)) continue; // skip if dst exists in this user's list + newMemberships.set(genId(), { createdAt: new Date(), userId: dst.id, - userListId: joining.userListId, + userListId: membership.userListId, }); } - const arrayToInsert = Array.from(newJoinings.entries()).map(entry => ({ ...entry[1], id: entry[0] })); - await this.userListJoiningsRepository.insert(arrayToInsert); + const arrayToInsert = Array.from(newMemberships.entries()).map(entry => ({ ...entry[1], id: entry[0] })); + await this.userListMembershipsRepository.insert(arrayToInsert); // Have the proxy account follow the new account in the same way as UserListService.push if (this.userEntityService.isRemoteUser(dst)) { diff --git a/packages/backend/src/core/AntennaService.ts b/packages/backend/src/core/AntennaService.ts index c7d18e01445c..ca7624b1d4fb 100644 --- a/packages/backend/src/core/AntennaService.ts +++ b/packages/backend/src/core/AntennaService.ts @@ -12,7 +12,7 @@ import { GlobalEventService } from '@/core/GlobalEventService.js'; import * as Acct from '@/misc/acct.js'; import type { Packed } from '@/misc/json-schema.js'; import { DI } from '@/di-symbols.js'; -import type { AntennasRepository, UserListJoiningsRepository } from '@/models/_.js'; +import type { AntennasRepository, UserListMembershipsRepository } from '@/models/_.js'; import { UtilityService } from '@/core/UtilityService.js'; import { bindThis } from '@/decorators.js'; import type { GlobalEvents } from '@/core/GlobalEventService.js'; @@ -25,8 +25,8 @@ export class AntennaService implements OnApplicationShutdown { private antennas: MiAntenna[]; constructor( - @Inject(DI.redis) - private redisClient: Redis.Redis, + @Inject(DI.redisForTimelines) + private redisForTimelines: Redis.Redis, @Inject(DI.redisForSub) private redisForSub: Redis.Redis, @@ -34,8 +34,8 @@ export class AntennaService implements OnApplicationShutdown { @Inject(DI.antennasRepository) private antennasRepository: AntennasRepository, - @Inject(DI.userListJoiningsRepository) - private userListJoiningsRepository: UserListJoiningsRepository, + @Inject(DI.userListMembershipsRepository) + private userListMembershipsRepository: UserListMembershipsRepository, private utilityService: UtilityService, private globalEventService: GlobalEventService, @@ -83,7 +83,7 @@ export class AntennaService implements OnApplicationShutdown { const antennasWithMatchResult = await Promise.all(antennas.map(antenna => this.checkHitAntenna(antenna, note, noteUser).then(hit => [antenna, hit] as const))); const matchedAntennas = antennasWithMatchResult.filter(([, hit]) => hit).map(([antenna]) => antenna); - const redisPipeline = this.redisClient.pipeline(); + const redisPipeline = this.redisForTimelines.pipeline(); for (const antenna of matchedAntennas) { this.redisTimelineService.push(`antennaTimeline:${antenna.id}`, note.id, 200, redisPipeline); @@ -105,7 +105,7 @@ export class AntennaService implements OnApplicationShutdown { if (antenna.src === 'home') { // TODO } else if (antenna.src === 'list') { - const listUsers = (await this.userListJoiningsRepository.findBy({ + const listUsers = (await this.userListMembershipsRepository.findBy({ userListId: antenna.userListId!, })).map(x => x.userId); diff --git a/packages/backend/src/core/CacheService.ts b/packages/backend/src/core/CacheService.ts index 561979c4bf14..bf821326f2a5 100644 --- a/packages/backend/src/core/CacheService.ts +++ b/packages/backend/src/core/CacheService.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import * as Redis from 'ioredis'; -import type { BlockingsRepository, ChannelFollowingsRepository, FollowingsRepository, MutingsRepository, RenoteMutingsRepository, MiUserProfile, UserProfilesRepository, UsersRepository } from '@/models/_.js'; +import type { BlockingsRepository, ChannelFollowingsRepository, FollowingsRepository, MutingsRepository, RenoteMutingsRepository, MiUserProfile, UserProfilesRepository, UsersRepository, MiFollowing } from '@/models/_.js'; import { MemoryKVCache, RedisKVCache } from '@/misc/cache.js'; import type { MiLocalUser, MiUser } from '@/models/User.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/core/CoreModule.ts b/packages/backend/src/core/CoreModule.ts index 3d8d64786126..8298bae9b5d4 100644 --- a/packages/backend/src/core/CoreModule.ts +++ b/packages/backend/src/core/CoreModule.ts @@ -46,6 +46,7 @@ import { SignupService } from './SignupService.js'; import { WebAuthnService } from './WebAuthnService.js'; import { UserBlockingService } from './UserBlockingService.js'; import { CacheService } from './CacheService.js'; +import { UserService } from './UserService.js'; import { UserFollowingService } from './UserFollowingService.js'; import { UserKeypairService } from './UserKeypairService.js'; import { UserListService } from './UserListService.js'; @@ -175,6 +176,7 @@ const $SignupService: Provider = { provide: 'SignupService', useExisting: Signup const $WebAuthnService: Provider = { provide: 'WebAuthnService', useExisting: WebAuthnService }; const $UserBlockingService: Provider = { provide: 'UserBlockingService', useExisting: UserBlockingService }; const $CacheService: Provider = { provide: 'CacheService', useExisting: CacheService }; +const $UserService: Provider = { provide: 'UserService', useExisting: UserService }; const $UserFollowingService: Provider = { provide: 'UserFollowingService', useExisting: UserFollowingService }; const $UserKeypairService: Provider = { provide: 'UserKeypairService', useExisting: UserKeypairService }; const $UserListService: Provider = { provide: 'UserListService', useExisting: UserListService }; @@ -306,6 +308,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting WebAuthnService, UserBlockingService, CacheService, + UserService, UserFollowingService, UserKeypairService, UserListService, @@ -431,6 +434,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting $WebAuthnService, $UserBlockingService, $CacheService, + $UserService, $UserFollowingService, $UserKeypairService, $UserListService, @@ -556,6 +560,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting WebAuthnService, UserBlockingService, CacheService, + UserService, UserFollowingService, UserKeypairService, UserListService, @@ -680,6 +685,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting $WebAuthnService, $UserBlockingService, $CacheService, + $UserService, $UserFollowingService, $UserKeypairService, $UserListService, diff --git a/packages/backend/src/core/NotificationService.ts b/packages/backend/src/core/NotificationService.ts index ca05989a4a67..32d54d257688 100644 --- a/packages/backend/src/core/NotificationService.ts +++ b/packages/backend/src/core/NotificationService.ts @@ -99,19 +99,19 @@ export class NotificationService implements OnApplicationShutdown { } if (recieveConfig?.type === 'following') { - const isFollowing = await this.cacheService.userFollowingsCache.fetch(notifieeId).then(followings => followings.has(notifierId)); + const isFollowing = await this.cacheService.userFollowingsCache.fetch(notifieeId).then(followings => Object.hasOwn(followings, notifierId)); if (!isFollowing) { return null; } } else if (recieveConfig?.type === 'follower') { - const isFollower = await this.cacheService.userFollowingsCache.fetch(notifierId).then(followings => followings.has(notifieeId)); + const isFollower = await this.cacheService.userFollowingsCache.fetch(notifierId).then(followings => Object.hasOwn(followings, notifieeId)); if (!isFollower) { return null; } } else if (recieveConfig?.type === 'mutualFollow') { const [isFollowing, isFollower] = await Promise.all([ - this.cacheService.userFollowingsCache.fetch(notifieeId).then(followings => followings.has(notifierId)), - this.cacheService.userFollowingsCache.fetch(notifierId).then(followings => followings.has(notifieeId)), + this.cacheService.userFollowingsCache.fetch(notifieeId).then(followings => Object.hasOwn(followings, notifierId)), + this.cacheService.userFollowingsCache.fetch(notifierId).then(followings => Object.hasOwn(followings, notifieeId)), ]); if (!isFollowing && !isFollower) { return null; diff --git a/packages/backend/src/core/UserBlockingService.ts b/packages/backend/src/core/UserBlockingService.ts index 37031e341e4f..087dfd92147c 100644 --- a/packages/backend/src/core/UserBlockingService.ts +++ b/packages/backend/src/core/UserBlockingService.ts @@ -11,7 +11,7 @@ import type { MiBlocking } from '@/models/Blocking.js'; import { QueueService } from '@/core/QueueService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; -import type { FollowRequestsRepository, BlockingsRepository, UserListsRepository, UserListJoiningsRepository } from '@/models/_.js'; +import type { FollowRequestsRepository, BlockingsRepository, UserListsRepository, UserListMembershipsRepository } from '@/models/_.js'; import Logger from '@/logger.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { ApRendererService } from '@/core/activitypub/ApRendererService.js'; @@ -38,8 +38,8 @@ export class UserBlockingService implements OnModuleInit { @Inject(DI.userListsRepository) private userListsRepository: UserListsRepository, - @Inject(DI.userListJoiningsRepository) - private userListJoiningsRepository: UserListJoiningsRepository, + @Inject(DI.userListMembershipsRepository) + private userListMembershipsRepository: UserListMembershipsRepository, private cacheService: CacheService, private userEntityService: UserEntityService, @@ -149,7 +149,7 @@ export class UserBlockingService implements OnModuleInit { }); for (const userList of userLists) { - await this.userListJoiningsRepository.delete({ + await this.userListMembershipsRepository.delete({ userListId: userList.id, userId: user.id, }); diff --git a/packages/backend/src/core/UserFollowingService.ts b/packages/backend/src/core/UserFollowingService.ts index 230f6ef261b8..beffcc2e9c97 100644 --- a/packages/backend/src/core/UserFollowingService.ts +++ b/packages/backend/src/core/UserFollowingService.ts @@ -123,7 +123,11 @@ export class UserFollowingService implements OnModuleInit { // フォロワーがBotであり、フォロー対象がBotからのフォローに慎重である or // フォロワーがローカルユーザーであり、フォロー対象がリモートユーザーである // 上記のいずれかに当てはまる場合はすぐフォローせずにフォローリクエストを発行しておく - if (followee.isLocked || (followeeProfile.carefulBot && follower.isBot) || (this.userEntityService.isLocalUser(follower) && this.userEntityService.isRemoteUser(followee))) { + if ( + followee.isLocked || + (followeeProfile.carefulBot && follower.isBot) || + (this.userEntityService.isLocalUser(follower) && this.userEntityService.isRemoteUser(followee) && process.env.FORCE_FOLLOW_REMOTE_USER_FOR_TESTING !== 'true') + ) { let autoAccept = false; // 鍵アカウントであっても、既にフォローされていた場合はスルー diff --git a/packages/backend/src/core/UserListService.ts b/packages/backend/src/core/UserListService.ts index 93dc5edbbafe..bece1e442ef5 100644 --- a/packages/backend/src/core/UserListService.ts +++ b/packages/backend/src/core/UserListService.ts @@ -5,10 +5,10 @@ import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common'; import * as Redis from 'ioredis'; -import type { UserListJoiningsRepository } from '@/models/_.js'; +import type { UserListMembershipsRepository } from '@/models/_.js'; import type { MiUser } from '@/models/User.js'; import type { MiUserList } from '@/models/UserList.js'; -import type { MiUserListJoining } from '@/models/UserListJoining.js'; +import type { MiUserListMembership } from '@/models/UserListMembership.js'; import { IdService } from '@/core/IdService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; @@ -33,8 +33,8 @@ export class UserListService implements OnApplicationShutdown { @Inject(DI.redisForSub) private redisForSub: Redis.Redis, - @Inject(DI.userListJoiningsRepository) - private userListJoiningsRepository: UserListJoiningsRepository, + @Inject(DI.userListMembershipsRepository) + private userListMembershipsRepository: UserListMembershipsRepository, private userEntityService: UserEntityService, private idService: IdService, @@ -46,7 +46,7 @@ export class UserListService implements OnApplicationShutdown { this.membersCache = new RedisKVCache>(this.redisClient, 'userListMembers', { lifetime: 1000 * 60 * 30, // 30m memoryCacheLifetime: 1000 * 60, // 1m - fetcher: (key) => this.userListJoiningsRepository.find({ where: { userListId: key }, select: ['userId'] }).then(xs => new Set(xs.map(x => x.userId))), + fetcher: (key) => this.userListMembershipsRepository.find({ where: { userListId: key }, select: ['userId'] }).then(xs => new Set(xs.map(x => x.userId))), toRedisConverter: (value) => JSON.stringify(Array.from(value)), fromRedisConverter: (value) => new Set(JSON.parse(value)), }); @@ -85,19 +85,19 @@ export class UserListService implements OnApplicationShutdown { @bindThis public async addMember(target: MiUser, list: MiUserList, me: MiUser) { - const currentCount = await this.userListJoiningsRepository.countBy({ + const currentCount = await this.userListMembershipsRepository.countBy({ userListId: list.id, }); if (currentCount > (await this.roleService.getUserPolicies(me.id)).userEachUserListsLimit) { throw new UserListService.TooManyUsersError(); } - await this.userListJoiningsRepository.insert({ + await this.userListMembershipsRepository.insert({ id: this.idService.genId(), createdAt: new Date(), userId: target.id, userListId: list.id, - } as MiUserListJoining); + } as MiUserListMembership); this.globalEventService.publishInternalEvent('userListMemberAdded', { userListId: list.id, memberId: target.id }); this.globalEventService.publishUserListStream(list.id, 'userAdded', await this.userEntityService.pack(target)); @@ -113,7 +113,7 @@ export class UserListService implements OnApplicationShutdown { @bindThis public async removeMember(target: MiUser, list: MiUserList) { - await this.userListJoiningsRepository.delete({ + await this.userListMembershipsRepository.delete({ userId: target.id, userListId: list.id, }); @@ -122,6 +122,24 @@ export class UserListService implements OnApplicationShutdown { this.globalEventService.publishUserListStream(list.id, 'userRemoved', await this.userEntityService.pack(target)); } + @bindThis + public async updateMembership(target: MiUser, list: MiUserList, options: { withReplies?: boolean }) { + const membership = await this.userListMembershipsRepository.findOneBy({ + userId: target.id, + userListId: list.id, + }); + + if (membership == null) { + throw new Error('User is not a member of the list'); + } + + await this.userListMembershipsRepository.update({ + id: membership.id, + }, { + withReplies: options.withReplies, + }); + } + @bindThis public dispose(): void { this.redisForSub.off('message', this.onMessage); diff --git a/packages/backend/src/core/UserService.ts b/packages/backend/src/core/UserService.ts new file mode 100644 index 000000000000..d16e1be61513 --- /dev/null +++ b/packages/backend/src/core/UserService.ts @@ -0,0 +1,53 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { Inject, Injectable } from '@nestjs/common'; +import type { FollowingsRepository, UsersRepository } from '@/models/_.js'; +import type { MiUser } from '@/models/User.js'; +import { DI } from '@/di-symbols.js'; +import { bindThis } from '@/decorators.js'; + +@Injectable() +export class UserService { + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + @Inject(DI.followingsRepository) + private followingsRepository: FollowingsRepository, + ) { + } + + @bindThis + public async updateLastActiveDate(user: MiUser): Promise { + if (user.isHibernated) { + const result = await this.usersRepository.createQueryBuilder().update() + .set({ + lastActiveDate: new Date(), + }) + .where('id = :id', { id: user.id }) + .returning('*') + .execute() + .then((response) => { + return response.raw[0]; + }); + const wokeUp = result.isHibernated; + if (wokeUp) { + this.usersRepository.update(user.id, { + isHibernated: false, + }); + this.followingsRepository.update({ + followerId: user.id, + }, { + isFollowerHibernated: false, + }); + } + } else { + this.usersRepository.update(user.id, { + lastActiveDate: new Date(), + }); + } + } +} diff --git a/packages/backend/src/core/entities/NoteEntityService.ts b/packages/backend/src/core/entities/NoteEntityService.ts index bf42e98ce0a9..824f8fa71d86 100644 --- a/packages/backend/src/core/entities/NoteEntityService.ts +++ b/packages/backend/src/core/entities/NoteEntityService.ts @@ -98,13 +98,13 @@ export class NoteEntityService implements OnModuleInit { } else if (meId === packedNote.userId) { hide = false; } else if (packedNote.reply && (meId === packedNote.reply.userId)) { - // 自分の投稿に対するリプライ + // 自分の投稿に対するリプライ hide = false; } else if (packedNote.mentions && packedNote.mentions.some(id => meId === id)) { - // 自分へのメンション + // 自分へのメンション hide = false; } else { - // フォロワーかどうか + // フォロワーかどうか const isFollowing = await this.followingsRepository.exist({ where: { followeeId: packedNote.userId, diff --git a/packages/backend/src/core/entities/UserEntityService.ts b/packages/backend/src/core/entities/UserEntityService.ts index a47b3d51ac2b..171dda8fdc88 100644 --- a/packages/backend/src/core/entities/UserEntityService.ts +++ b/packages/backend/src/core/entities/UserEntityService.ts @@ -487,6 +487,7 @@ export class UserEntityService implements OnModuleInit { isMuted: relation.isMuted, isRenoteMuted: relation.isRenoteMuted, notify: relation.following?.notify ?? 'none', + withReplies: relation.following?.withReplies ?? false, } : {}), } as Promiseable> as Promiseable>; diff --git a/packages/backend/src/core/entities/UserListEntityService.ts b/packages/backend/src/core/entities/UserListEntityService.ts index a7f28851943f..06b6e852b146 100644 --- a/packages/backend/src/core/entities/UserListEntityService.ts +++ b/packages/backend/src/core/entities/UserListEntityService.ts @@ -5,11 +5,12 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import type { UserListJoiningsRepository, UserListsRepository } from '@/models/_.js'; +import type { MiUserListMembership, UserListMembershipsRepository, UserListsRepository } from '@/models/_.js'; import type { Packed } from '@/misc/json-schema.js'; import type { } from '@/models/Blocking.js'; import type { MiUserList } from '@/models/UserList.js'; import { bindThis } from '@/decorators.js'; +import { UserEntityService } from './UserEntityService.js'; @Injectable() export class UserListEntityService { @@ -17,8 +18,10 @@ export class UserListEntityService { @Inject(DI.userListsRepository) private userListsRepository: UserListsRepository, - @Inject(DI.userListJoiningsRepository) - private userListJoiningsRepository: UserListJoiningsRepository, + @Inject(DI.userListMembershipsRepository) + private userListMembershipsRepository: UserListMembershipsRepository, + + private userEntityService: UserEntityService, ) { } @@ -28,7 +31,7 @@ export class UserListEntityService { ): Promise> { const userList = typeof src === 'object' ? src : await this.userListsRepository.findOneByOrFail({ id: src }); - const users = await this.userListJoiningsRepository.findBy({ + const users = await this.userListMembershipsRepository.findBy({ userListId: userList.id, }); @@ -40,5 +43,18 @@ export class UserListEntityService { isPublic: userList.isPublic, }; } + + @bindThis + public async packMembershipsMany( + memberships: MiUserListMembership[], + ) { + return Promise.all(memberships.map(async x => ({ + id: x.id, + createdAt: x.createdAt.toISOString(), + userId: x.userId, + user: await this.userEntityService.pack(x.userId), + withReplies: x.withReplies, + }))); + } } diff --git a/packages/backend/src/di-symbols.ts b/packages/backend/src/di-symbols.ts index 72ec98cebe89..d992d9e2f50e 100644 --- a/packages/backend/src/di-symbols.ts +++ b/packages/backend/src/di-symbols.ts @@ -10,6 +10,7 @@ export const DI = { redis: Symbol('redis'), redisForPub: Symbol('redisForPub'), redisForSub: Symbol('redisForSub'), + redisForTimelines: Symbol('redisForTimelines'), //#region Repositories usersRepository: Symbol('usersRepository'), @@ -30,7 +31,7 @@ export const DI = { userPublickeysRepository: Symbol('userPublickeysRepository'), userListsRepository: Symbol('userListsRepository'), userListFavoritesRepository: Symbol('userListFavoritesRepository'), - userListJoiningsRepository: Symbol('userListJoiningsRepository'), + userListMembershipsRepository: Symbol('userListMembershipsRepository'), userNotePiningsRepository: Symbol('userNotePiningsRepository'), userIpsRepository: Symbol('userIpsRepository'), usedUsernamesRepository: Symbol('usedUsernamesRepository'), diff --git a/packages/backend/src/models/Following.ts b/packages/backend/src/models/Following.ts index 8c9f965fadd4..607538b1e79c 100644 --- a/packages/backend/src/models/Following.ts +++ b/packages/backend/src/models/Following.ts @@ -9,6 +9,7 @@ import { MiUser } from './User.js'; @Entity('following') @Index(['followerId', 'followeeId'], { unique: true }) +@Index(['followeeId', 'followerHost', 'isFollowerHibernated']) export class MiFollowing { @PrimaryColumn(id()) public id: string; @@ -45,6 +46,17 @@ export class MiFollowing { @JoinColumn() public follower: MiUser | null; + @Column('boolean', { + default: false, + }) + public isFollowerHibernated: boolean; + + // タイムラインにその人のリプライまで含めるかどうか + @Column('boolean', { + default: false, + }) + public withReplies: boolean; + @Index() @Column('varchar', { length: 32, diff --git a/packages/backend/src/models/RepositoryModule.ts b/packages/backend/src/models/RepositoryModule.ts index 766e7ce21c70..fce63d9b46dd 100644 --- a/packages/backend/src/models/RepositoryModule.ts +++ b/packages/backend/src/models/RepositoryModule.ts @@ -5,7 +5,7 @@ import { Module } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import { MiAbuseUserReport, MiAccessToken, MiAd, MiAnnouncement, MiAnnouncementRead, MiAntenna, MiApp, MiAuthSession, MiBlocking, MiChannel, MiChannelFavorite, MiChannelFollowing, MiClip, MiClipFavorite, MiClipNote, MiDriveFile, MiDriveFolder, MiEmoji, MiFlash, MiFlashLike, MiFollowRequest, MiFollowing, MiGalleryLike, MiGalleryPost, MiHashtag, MiInstance, MiMeta, MiModerationLog, MiMutedNote, MiMuting, MiNote, MiNoteFavorite, MiNoteReaction, MiNoteThreadMuting, MiNoteUnread, MiPage, MiPageLike, MiPasswordResetRequest, MiPoll, MiPollVote, MiPromoNote, MiPromoRead, MiRegistrationTicket, MiRegistryItem, MiRelay, MiRenoteMuting, MiRetentionAggregation, MiRole, MiRoleAssignment, MiSignin, MiSwSubscription, MiUsedUsername, MiUser, MiUserIp, MiUserKeypair, MiUserList, MiUserListFavorite, MiUserListJoining, MiUserMemo, MiUserNotePining, MiUserPending, MiUserProfile, MiUserPublickey, MiUserSecurityKey, MiWebhook } from './_.js'; +import { MiAbuseUserReport, MiAccessToken, MiAd, MiAnnouncement, MiAnnouncementRead, MiAntenna, MiApp, MiAuthSession, MiBlocking, MiChannel, MiChannelFavorite, MiChannelFollowing, MiClip, MiClipFavorite, MiClipNote, MiDriveFile, MiDriveFolder, MiEmoji, MiFlash, MiFlashLike, MiFollowRequest, MiFollowing, MiGalleryLike, MiGalleryPost, MiHashtag, MiInstance, MiMeta, MiModerationLog, MiMutedNote, MiMuting, MiNote, MiNoteFavorite, MiNoteReaction, MiNoteThreadMuting, MiNoteUnread, MiPage, MiPageLike, MiPasswordResetRequest, MiPoll, MiPollVote, MiPromoNote, MiPromoRead, MiRegistrationTicket, MiRegistryItem, MiRelay, MiRenoteMuting, MiRetentionAggregation, MiRole, MiRoleAssignment, MiSignin, MiSwSubscription, MiUsedUsername, MiUser, MiUserIp, MiUserKeypair, MiUserList, MiUserListFavorite, MiUserListMembership, MiUserMemo, MiUserNotePining, MiUserPending, MiUserProfile, MiUserPublickey, MiUserSecurityKey, MiWebhook } from './_.js'; import type { DataSource } from 'typeorm'; import type { Provider } from '@nestjs/common'; @@ -117,9 +117,9 @@ const $userListFavoritesRepository: Provider = { inject: [DI.db], }; -const $userListJoiningsRepository: Provider = { - provide: DI.userListJoiningsRepository, - useFactory: (db: DataSource) => db.getRepository(MiUserListJoining), +const $userListMembershipsRepository: Provider = { + provide: DI.userListMembershipsRepository, + useFactory: (db: DataSource) => db.getRepository(MiUserListMembership), inject: [DI.db], }; @@ -421,7 +421,7 @@ const $userMemosRepository: Provider = { $userPublickeysRepository, $userListsRepository, $userListFavoritesRepository, - $userListJoiningsRepository, + $userListMembershipsRepository, $userNotePiningsRepository, $userIpsRepository, $usedUsernamesRepository, @@ -488,7 +488,7 @@ const $userMemosRepository: Provider = { $userPublickeysRepository, $userListsRepository, $userListFavoritesRepository, - $userListJoiningsRepository, + $userListMembershipsRepository, $userNotePiningsRepository, $userIpsRepository, $usedUsernamesRepository, diff --git a/packages/backend/src/models/User.ts b/packages/backend/src/models/User.ts index b040d302ce7c..4d961c4290b9 100644 --- a/packages/backend/src/models/User.ts +++ b/packages/backend/src/models/User.ts @@ -187,6 +187,11 @@ export class MiUser { }) public isExplorable: boolean; + @Column('boolean', { + default: false, + }) + public isHibernated: boolean; + // アカウントが削除されたかどうかのフラグだが、完全に削除される際は物理削除なので実質削除されるまでの「削除が進行しているかどうか」のフラグ @Column('boolean', { default: false, diff --git a/packages/backend/src/models/UserListJoining.ts b/packages/backend/src/models/UserListMembership.ts similarity index 76% rename from packages/backend/src/models/UserListJoining.ts rename to packages/backend/src/models/UserListMembership.ts index 4918f2f70001..3a50e4874ba9 100644 --- a/packages/backend/src/models/UserListJoining.ts +++ b/packages/backend/src/models/UserListMembership.ts @@ -8,14 +8,14 @@ import { id } from './util/id.js'; import { MiUser } from './User.js'; import { MiUserList } from './UserList.js'; -@Entity('user_list_joining') +@Entity('user_list_membership') @Index(['userId', 'userListId'], { unique: true }) -export class MiUserListJoining { +export class MiUserListMembership { @PrimaryColumn(id()) public id: string; @Column('timestamp with time zone', { - comment: 'The created date of the UserListJoining.', + comment: 'The created date of the UserListMembership.', }) public createdAt: Date; @@ -44,4 +44,10 @@ export class MiUserListJoining { }) @JoinColumn() public userList: MiUserList | null; + + // タイムラインにその人のリプライまで含めるかどうか + @Column('boolean', { + default: true, + }) + public withReplies: boolean; } diff --git a/packages/backend/src/models/_.ts b/packages/backend/src/models/_.ts index 6be7bd0df6ba..cc527ad21049 100644 --- a/packages/backend/src/models/_.ts +++ b/packages/backend/src/models/_.ts @@ -53,7 +53,7 @@ import { MiUser } from '@/models/User.js'; import { MiUserIp } from '@/models/UserIp.js'; import { MiUserKeypair } from '@/models/UserKeypair.js'; import { MiUserList } from '@/models/UserList.js'; -import { MiUserListJoining } from '@/models/UserListJoining.js'; +import { MiUserListMembership } from '@/models/UserListMembership.js'; import { MiUserNotePining } from '@/models/UserNotePining.js'; import { MiUserPending } from '@/models/UserPending.js'; import { MiUserProfile } from '@/models/UserProfile.js'; @@ -122,7 +122,7 @@ export { MiUserKeypair, MiUserList, MiUserListFavorite, - MiUserListJoining, + MiUserListMembership, MiUserNotePining, MiUserPending, MiUserProfile, @@ -189,7 +189,7 @@ export type UserIpsRepository = Repository; export type UserKeypairsRepository = Repository; export type UserListsRepository = Repository; export type UserListFavoritesRepository = Repository; -export type UserListJoiningsRepository = Repository; +export type UserListMembershipsRepository = Repository; export type UserNotePiningsRepository = Repository; export type UserPendingsRepository = Repository; export type UserProfilesRepository = Repository; diff --git a/packages/backend/src/models/json-schema/user.ts b/packages/backend/src/models/json-schema/user.ts index 0181ea50e8dd..57d2d976ff7a 100644 --- a/packages/backend/src/models/json-schema/user.ts +++ b/packages/backend/src/models/json-schema/user.ts @@ -277,6 +277,10 @@ export const packedUserDetailedNotMeOnlySchema = { type: 'string', nullable: false, optional: true, }, + withReplies: { + type: 'boolean', + nullable: false, optional: true, + }, //#endregion }, } as const; diff --git a/packages/backend/src/postgres.ts b/packages/backend/src/postgres.ts index 10126eab2bbd..0366203b8537 100644 --- a/packages/backend/src/postgres.ts +++ b/packages/backend/src/postgres.ts @@ -62,7 +62,7 @@ import { MiUserIp } from '@/models/UserIp.js'; import { MiUserKeypair } from '@/models/UserKeypair.js'; import { MiUserList } from '@/models/UserList.js'; import { MiUserListFavorite } from '@/models/UserListFavorite.js'; -import { MiUserListJoining } from '@/models/UserListJoining.js'; +import { MiUserListMembership } from '@/models/UserListMembership.js'; import { MiUserNotePining } from '@/models/UserNotePining.js'; import { MiUserPending } from '@/models/UserPending.js'; import { MiUserProfile } from '@/models/UserProfile.js'; @@ -138,7 +138,7 @@ export const entities = [ MiUserPublickey, MiUserList, MiUserListFavorite, - MiUserListJoining, + MiUserListMembership, MiUserNotePining, MiUserSecurityKey, MiUsedUsername, diff --git a/packages/backend/src/queue/processors/ExportAntennasProcessorService.ts b/packages/backend/src/queue/processors/ExportAntennasProcessorService.ts index f941fb6e858c..a0afbee3baa1 100644 --- a/packages/backend/src/queue/processors/ExportAntennasProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportAntennasProcessorService.ts @@ -8,7 +8,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { format as DateFormat } from 'date-fns'; import { In } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import type { AntennasRepository, UsersRepository, UserListJoiningsRepository, MiUser } from '@/models/_.js'; +import type { AntennasRepository, UsersRepository, UserListMembershipsRepository, MiUser } from '@/models/_.js'; import Logger from '@/logger.js'; import { DriveService } from '@/core/DriveService.js'; import { bindThis } from '@/decorators.js'; @@ -29,8 +29,8 @@ export class ExportAntennasProcessorService { @Inject(DI.antennasRepository) private antennsRepository: AntennasRepository, - @Inject(DI.userListJoiningsRepository) - private userListJoiningsRepository: UserListJoiningsRepository, + @Inject(DI.userListMembershipsRepository) + private userListMembershipsRepository: UserListMembershipsRepository, private driveService: DriveService, private utilityService: UtilityService, @@ -65,9 +65,9 @@ export class ExportAntennasProcessorService { for (const [index, antenna] of antennas.entries()) { let users: MiUser[] | undefined; if (antenna.userListId !== null) { - const joinings = await this.userListJoiningsRepository.findBy({ userListId: antenna.userListId }); + const memberships = await this.userListMembershipsRepository.findBy({ userListId: antenna.userListId }); users = await this.usersRepository.findBy({ - id: In(joinings.map(j => j.userId)), + id: In(memberships.map(j => j.userId)), }); } write(JSON.stringify({ diff --git a/packages/backend/src/queue/processors/ExportUserListsProcessorService.ts b/packages/backend/src/queue/processors/ExportUserListsProcessorService.ts index 7baaa7081a86..a3f9441dc282 100644 --- a/packages/backend/src/queue/processors/ExportUserListsProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportUserListsProcessorService.ts @@ -8,7 +8,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { In } from 'typeorm'; import { format as dateFormat } from 'date-fns'; import { DI } from '@/di-symbols.js'; -import type { UserListJoiningsRepository, UserListsRepository, UsersRepository } from '@/models/_.js'; +import type { UserListMembershipsRepository, UserListsRepository, UsersRepository } from '@/models/_.js'; import type Logger from '@/logger.js'; import { DriveService } from '@/core/DriveService.js'; import { createTemp } from '@/misc/create-temp.js'; @@ -29,8 +29,8 @@ export class ExportUserListsProcessorService { @Inject(DI.userListsRepository) private userListsRepository: UserListsRepository, - @Inject(DI.userListJoiningsRepository) - private userListJoiningsRepository: UserListJoiningsRepository, + @Inject(DI.userListMembershipsRepository) + private userListMembershipsRepository: UserListMembershipsRepository, private utilityService: UtilityService, private driveService: DriveService, @@ -61,9 +61,9 @@ export class ExportUserListsProcessorService { const stream = fs.createWriteStream(path, { flags: 'a' }); for (const list of lists) { - const joinings = await this.userListJoiningsRepository.findBy({ userListId: list.id }); + const memberships = await this.userListMembershipsRepository.findBy({ userListId: list.id }); const users = await this.usersRepository.findBy({ - id: In(joinings.map(j => j.userId)), + id: In(memberships.map(j => j.userId)), }); for (const u of users) { diff --git a/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts b/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts index 60a0d1605f11..9be36a9d0d1e 100644 --- a/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts @@ -6,7 +6,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { IsNull } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import type { UsersRepository, DriveFilesRepository, UserListJoiningsRepository, UserListsRepository } from '@/models/_.js'; +import type { UsersRepository, DriveFilesRepository, UserListMembershipsRepository, UserListsRepository } from '@/models/_.js'; import type Logger from '@/logger.js'; import * as Acct from '@/misc/acct.js'; import { RemoteUserResolveService } from '@/core/RemoteUserResolveService.js'; @@ -33,8 +33,8 @@ export class ImportUserListsProcessorService { @Inject(DI.userListsRepository) private userListsRepository: UserListsRepository, - @Inject(DI.userListJoiningsRepository) - private userListJoiningsRepository: UserListJoiningsRepository, + @Inject(DI.userListMembershipsRepository) + private userListMembershipsRepository: UserListMembershipsRepository, private utilityService: UtilityService, private idService: IdService, @@ -99,7 +99,7 @@ export class ImportUserListsProcessorService { target = await this.remoteUserResolveService.resolveUser(username, host); } - if (await this.userListJoiningsRepository.findOneBy({ userListId: list!.id, userId: target.id }) != null) continue; + if (await this.userListMembershipsRepository.findOneBy({ userListId: list!.id, userId: target.id }) != null) continue; this.userListService.addMember(target, list!, user); } catch (e) { diff --git a/packages/backend/src/server/api/EndpointsModule.ts b/packages/backend/src/server/api/EndpointsModule.ts index 41a11bfb1913..b6df8a7a6760 100644 --- a/packages/backend/src/server/api/EndpointsModule.ts +++ b/packages/backend/src/server/api/EndpointsModule.ts @@ -335,7 +335,9 @@ import * as ep___users_lists_show from './endpoints/users/lists/show.js'; import * as ep___users_lists_update from './endpoints/users/lists/update.js'; import * as ep___users_lists_favorite from './endpoints/users/lists/favorite.js'; import * as ep___users_lists_unfavorite from './endpoints/users/lists/unfavorite.js'; -import * as ep___users_lists_create_from_public from './endpoints/users/lists/create-from-public.js'; +import * as ep___users_lists_createFromPublic from './endpoints/users/lists/create-from-public.js'; +import * as ep___users_lists_updateMembership from './endpoints/users/lists/update-membership.js'; +import * as ep___users_lists_getMemberships from './endpoints/users/lists/get-memberships.js'; import * as ep___users_notes from './endpoints/users/notes.js'; import * as ep___users_pages from './endpoints/users/pages.js'; import * as ep___users_flashs from './endpoints/users/flashs.js'; @@ -683,7 +685,9 @@ const $users_lists_show: Provider = { provide: 'ep:users/lists/show', useClass: const $users_lists_update: Provider = { provide: 'ep:users/lists/update', useClass: ep___users_lists_update.default }; const $users_lists_favorite: Provider = { provide: 'ep:users/lists/favorite', useClass: ep___users_lists_favorite.default }; const $users_lists_unfavorite: Provider = { provide: 'ep:users/lists/unfavorite', useClass: ep___users_lists_unfavorite.default }; -const $users_lists_create_from_public: Provider = { provide: 'ep:users/lists/create-from-public', useClass: ep___users_lists_create_from_public.default }; +const $users_lists_createFromPublic: Provider = { provide: 'ep:users/lists/create-from-public', useClass: ep___users_lists_createFromPublic.default }; +const $users_lists_updateMembership: Provider = { provide: 'ep:users/lists/update-membership', useClass: ep___users_lists_updateMembership.default }; +const $users_lists_getMemberships: Provider = { provide: 'ep:users/lists/get-memberships', useClass: ep___users_lists_getMemberships.default }; const $users_notes: Provider = { provide: 'ep:users/notes', useClass: ep___users_notes.default }; const $users_pages: Provider = { provide: 'ep:users/pages', useClass: ep___users_pages.default }; const $users_flashs: Provider = { provide: 'ep:users/flashs', useClass: ep___users_flashs.default }; @@ -1035,7 +1039,9 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention $users_lists_update, $users_lists_favorite, $users_lists_unfavorite, - $users_lists_create_from_public, + $users_lists_createFromPublic, + $users_lists_updateMembership, + $users_lists_getMemberships, $users_notes, $users_pages, $users_flashs, @@ -1378,7 +1384,9 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention $users_lists_update, $users_lists_favorite, $users_lists_unfavorite, - $users_lists_create_from_public, + $users_lists_createFromPublic, + $users_lists_updateMembership, + $users_lists_getMemberships, $users_notes, $users_pages, $users_flashs, diff --git a/packages/backend/src/server/api/StreamingApiServerService.ts b/packages/backend/src/server/api/StreamingApiServerService.ts index 9acaa688c508..badcec1b33f8 100644 --- a/packages/backend/src/server/api/StreamingApiServerService.ts +++ b/packages/backend/src/server/api/StreamingApiServerService.ts @@ -14,6 +14,7 @@ import { NotificationService } from '@/core/NotificationService.js'; import { bindThis } from '@/decorators.js'; import { CacheService } from '@/core/CacheService.js'; import { MiLocalUser } from '@/models/User.js'; +import { UserService } from '@/core/UserService.js'; import { AuthenticateService, AuthenticationError } from './AuthenticateService.js'; import MainStreamConnection from './stream/Connection.js'; import { ChannelsService } from './stream/ChannelsService.js'; @@ -37,6 +38,7 @@ export class StreamingApiServerService { private authenticateService: AuthenticateService, private channelsService: ChannelsService, private notificationService: NotificationService, + private usersService: UserService, ) { } @@ -130,14 +132,10 @@ export class StreamingApiServerService { this.#connections.set(connection, Date.now()); const userUpdateIntervalId = user ? setInterval(() => { - this.usersRepository.update(user.id, { - lastActiveDate: new Date(), - }); + this.usersService.updateLastActiveDate(user); }, 1000 * 60 * 5) : null; if (user) { - this.usersRepository.update(user.id, { - lastActiveDate: new Date(), - }); + this.usersService.updateLastActiveDate(user); } connection.once('close', () => { diff --git a/packages/backend/src/server/api/endpoints.ts b/packages/backend/src/server/api/endpoints.ts index ab20a708ef43..0c87bfd409b7 100644 --- a/packages/backend/src/server/api/endpoints.ts +++ b/packages/backend/src/server/api/endpoints.ts @@ -334,8 +334,10 @@ import * as ep___users_lists_push from './endpoints/users/lists/push.js'; import * as ep___users_lists_show from './endpoints/users/lists/show.js'; import * as ep___users_lists_favorite from './endpoints/users/lists/favorite.js'; import * as ep___users_lists_unfavorite from './endpoints/users/lists/unfavorite.js'; -import * as ep___users_lists_create_from_public from './endpoints/users/lists/create-from-public.js'; +import * as ep___users_lists_createFromPublic from './endpoints/users/lists/create-from-public.js'; import * as ep___users_lists_update from './endpoints/users/lists/update.js'; +import * as ep___users_lists_updateMembership from './endpoints/users/lists/update-membership.js'; +import * as ep___users_lists_getMemberships from './endpoints/users/lists/get-memberships.js'; import * as ep___users_notes from './endpoints/users/notes.js'; import * as ep___users_pages from './endpoints/users/pages.js'; import * as ep___users_flashs from './endpoints/users/flashs.js'; @@ -681,7 +683,9 @@ const eps = [ ['users/lists/favorite', ep___users_lists_favorite], ['users/lists/unfavorite', ep___users_lists_unfavorite], ['users/lists/update', ep___users_lists_update], - ['users/lists/create-from-public', ep___users_lists_create_from_public], + ['users/lists/create-from-public', ep___users_lists_createFromPublic], + ['users/lists/update-membership', ep___users_lists_updateMembership], + ['users/lists/get-memberships', ep___users_lists_getMemberships], ['users/notes', ep___users_notes], ['users/pages', ep___users_pages], ['users/flashs', ep___users_flashs], diff --git a/packages/backend/src/server/api/endpoints/following/update.ts b/packages/backend/src/server/api/endpoints/following/update.ts index 25f393e51745..db17d151dfdd 100644 --- a/packages/backend/src/server/api/endpoints/following/update.ts +++ b/packages/backend/src/server/api/endpoints/following/update.ts @@ -57,8 +57,9 @@ export const paramDef = { properties: { userId: { type: 'string', format: 'misskey:id' }, notify: { type: 'string', enum: ['normal', 'none'] }, + withReplies: { type: 'boolean' }, }, - required: ['userId', 'notify'], + required: ['userId'], } as const; @Injectable() @@ -98,7 +99,8 @@ export default class extends Endpoint { // eslint- await this.followingsRepository.update({ id: exist.id, }, { - notify: ps.notify === 'none' ? null : ps.notify, + notify: ps.notify != null ? (ps.notify === 'none' ? null : ps.notify) : undefined, + withReplies: ps.withReplies != null ? ps.withReplies : undefined, }); return await this.userEntityService.pack(follower.id, me); diff --git a/packages/backend/src/server/api/endpoints/roles/notes.ts b/packages/backend/src/server/api/endpoints/roles/notes.ts index 6dc35907e1f2..f2533efa36e5 100644 --- a/packages/backend/src/server/api/endpoints/roles/notes.ts +++ b/packages/backend/src/server/api/endpoints/roles/notes.ts @@ -53,8 +53,8 @@ export const paramDef = { @Injectable() export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( - @Inject(DI.redis) - private redisClient: Redis.Redis, + @Inject(DI.redisForTimelines) + private redisForTimelines: Redis.Redis, @Inject(DI.notesRepository) private notesRepository: NotesRepository, @@ -79,7 +79,7 @@ export default class extends Endpoint { // eslint- return []; } const limit = ps.limit + (ps.untilId ? 1 : 0) + (ps.sinceId ? 1 : 0); // untilIdに指定したものも含まれるため+1 - const noteIdsRes = await this.redisClient.xrevrange( + const noteIdsRes = await this.redisForTimelines.xrevrange( `roleTimeline:${role.id}`, ps.untilId ? this.idService.parse(ps.untilId).date.getTime() : ps.untilDate ?? '+', ps.sinceId ? this.idService.parse(ps.sinceId).date.getTime() : ps.sinceDate ?? '-', diff --git a/packages/backend/src/server/api/endpoints/users/lists/create-from-public.ts b/packages/backend/src/server/api/endpoints/users/lists/create-from-public.ts index eae55905d35f..f2f6c4303a68 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/create-from-public.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/create-from-public.ts @@ -4,7 +4,7 @@ */ import { Inject, Injectable } from '@nestjs/common'; -import type { UserListsRepository, UserListJoiningsRepository, BlockingsRepository } from '@/models/_.js'; +import type { UserListsRepository, UserListMembershipsRepository, BlockingsRepository } from '@/models/_.js'; import { IdService } from '@/core/IdService.js'; import type { MiUserList } from '@/models/UserList.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; @@ -76,8 +76,8 @@ export default class extends Endpoint { // eslint- @Inject(DI.userListsRepository) private userListsRepository: UserListsRepository, - @Inject(DI.userListJoiningsRepository) - private userListJoiningsRepository: UserListJoiningsRepository, + @Inject(DI.userListMembershipsRepository) + private userListMembershipsRepository: UserListMembershipsRepository, @Inject(DI.blockingsRepository) private blockingsRepository: BlockingsRepository, @@ -110,7 +110,7 @@ export default class extends Endpoint { // eslint- name: ps.name, } as MiUserList).then(x => this.userListsRepository.findOneByOrFail(x.identifiers[0])); - const users = (await this.userListJoiningsRepository.findBy({ + const users = (await this.userListMembershipsRepository.findBy({ userListId: ps.listId, })).map(x => x.userId); @@ -132,7 +132,7 @@ export default class extends Endpoint { // eslint- } } - const exist = await this.userListJoiningsRepository.exist({ + const exist = await this.userListMembershipsRepository.exist({ where: { userListId: userList.id, userId: currentUser.id, diff --git a/packages/backend/src/server/api/endpoints/users/lists/get-memberships.ts b/packages/backend/src/server/api/endpoints/users/lists/get-memberships.ts new file mode 100644 index 000000000000..ae8b4e9b8124 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/users/lists/get-memberships.ts @@ -0,0 +1,79 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { Inject, Injectable } from '@nestjs/common'; +import type { UserListsRepository, UserListFavoritesRepository, UserListMembershipsRepository } from '@/models/_.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { UserListEntityService } from '@/core/entities/UserListEntityService.js'; +import { DI } from '@/di-symbols.js'; +import { QueryService } from '@/core/QueryService.js'; +import { ApiError } from '../../../error.js'; + +export const meta = { + tags: ['lists', 'account'], + + requireCredential: false, + + kind: 'read:account', + + errors: { + noSuchList: { + message: 'No such list.', + code: 'NO_SUCH_LIST', + id: '7bc05c21-1d7a-41ae-88f1-66820f4dc686', + }, + }, +} as const; + +export const paramDef = { + type: 'object', + properties: { + listId: { type: 'string', format: 'misskey:id' }, + forPublic: { type: 'boolean', default: false }, + limit: { type: 'integer', minimum: 1, maximum: 100, default: 30 }, + sinceId: { type: 'string', format: 'misskey:id' }, + untilId: { type: 'string', format: 'misskey:id' }, + }, + required: ['listId'], +} as const; + +@Injectable() // eslint-disable-next-line import/no-default-export +export default class extends Endpoint { + constructor( + @Inject(DI.userListsRepository) + private userListsRepository: UserListsRepository, + + @Inject(DI.userListMembershipsRepository) + private userListMembershipsRepository: UserListMembershipsRepository, + + private userListEntityService: UserListEntityService, + private queryService: QueryService, + ) { + super(meta, paramDef, async (ps, me) => { + // Fetch the list + const userList = await this.userListsRepository.findOneBy(!ps.forPublic && me !== null ? { + id: ps.listId, + userId: me.id, + } : { + id: ps.listId, + isPublic: true, + }); + + if (userList == null) { + throw new ApiError(meta.errors.noSuchList); + } + + const query = this.queryService.makePaginationQuery(this.userListMembershipsRepository.createQueryBuilder('membership'), ps.sinceId, ps.untilId) + .andWhere('membership.userListId = :userListId', { userListId: userList.id }) + .innerJoinAndSelect('membership.user', 'user'); + + const memberships = await query + .limit(ps.limit) + .getMany(); + + return this.userListEntityService.packMembershipsMany(memberships); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/users/lists/push.ts b/packages/backend/src/server/api/endpoints/users/lists/push.ts index 72a6a7380d9b..c4ceec575b92 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/push.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/push.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import ms from 'ms'; -import type { UserListsRepository, UserListJoiningsRepository, BlockingsRepository } from '@/models/_.js'; +import type { UserListsRepository, UserListMembershipsRepository, BlockingsRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { GetterService } from '@/server/api/GetterService.js'; import { UserListService } from '@/core/UserListService.js'; @@ -76,8 +76,8 @@ export default class extends Endpoint { // eslint- @Inject(DI.userListsRepository) private userListsRepository: UserListsRepository, - @Inject(DI.userListJoiningsRepository) - private userListJoiningsRepository: UserListJoiningsRepository, + @Inject(DI.userListMembershipsRepository) + private userListMembershipsRepository: UserListMembershipsRepository, @Inject(DI.blockingsRepository) private blockingsRepository: BlockingsRepository, @@ -115,7 +115,7 @@ export default class extends Endpoint { // eslint- } } - const exist = await this.userListJoiningsRepository.exist({ + const exist = await this.userListMembershipsRepository.exist({ where: { userListId: userList.id, userId: user.id, diff --git a/packages/backend/src/server/api/endpoints/users/lists/update-membership.ts b/packages/backend/src/server/api/endpoints/users/lists/update-membership.ts new file mode 100644 index 000000000000..b69465b940e8 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/users/lists/update-membership.ts @@ -0,0 +1,79 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { Inject, Injectable } from '@nestjs/common'; +import type { UserListsRepository } from '@/models/_.js'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { GetterService } from '@/server/api/GetterService.js'; +import { DI } from '@/di-symbols.js'; +import { UserListService } from '@/core/UserListService.js'; +import { ApiError } from '../../../error.js'; + +export const meta = { + tags: ['lists', 'users'], + + requireCredential: true, + + prohibitMoved: true, + + kind: 'write:account', + + errors: { + noSuchList: { + message: 'No such list.', + code: 'NO_SUCH_LIST', + id: '7f44670e-ab16-43b8-b4c1-ccd2ee89cc02', + }, + + noSuchUser: { + message: 'No such user.', + code: 'NO_SUCH_USER', + id: '588e7f72-c744-4a61-b180-d354e912bda2', + }, + }, +} as const; + +export const paramDef = { + type: 'object', + properties: { + listId: { type: 'string', format: 'misskey:id' }, + userId: { type: 'string', format: 'misskey:id' }, + withReplies: { type: 'boolean' }, + }, + required: ['listId', 'userId'], +} as const; + +@Injectable() +export default class extends Endpoint { // eslint-disable-line import/no-default-export + constructor( + @Inject(DI.userListsRepository) + private userListsRepository: UserListsRepository, + + private userListService: UserListService, + private getterService: GetterService, + ) { + super(meta, paramDef, async (ps, me) => { + // Fetch the list + const userList = await this.userListsRepository.findOneBy({ + id: ps.listId, + userId: me.id, + }); + + if (userList == null) { + throw new ApiError(meta.errors.noSuchList); + } + + // Fetch the user + const user = await this.getterService.getUser(ps.userId).catch(err => { + if (err.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); + throw err; + }); + + await this.userListService.updateMembership(user, userList, { + withReplies: ps.withReplies, + }); + }); + } +} diff --git a/packages/backend/src/server/api/stream/channels/user-list.ts b/packages/backend/src/server/api/stream/channels/user-list.ts index 8bbba0b6dbb9..42083aee67b3 100644 --- a/packages/backend/src/server/api/stream/channels/user-list.ts +++ b/packages/backend/src/server/api/stream/channels/user-list.ts @@ -4,7 +4,7 @@ */ import { Inject, Injectable } from '@nestjs/common'; -import type { UserListJoiningsRepository, UserListsRepository } from '@/models/_.js'; +import type { UserListMembershipsRepository, UserListsRepository } from '@/models/_.js'; import type { MiUser } from '@/models/User.js'; import { isUserRelated } from '@/misc/is-user-related.js'; import type { Packed } from '@/misc/json-schema.js'; @@ -23,7 +23,7 @@ class UserListChannel extends Channel { constructor( private userListsRepository: UserListsRepository, - private userListJoiningsRepository: UserListJoiningsRepository, + private userListMembershipsRepository: UserListMembershipsRepository, private noteEntityService: NoteEntityService, id: string, @@ -58,7 +58,7 @@ class UserListChannel extends Channel { @bindThis private async updateListUsers() { - const users = await this.userListJoiningsRepository.find({ + const users = await this.userListMembershipsRepository.find({ where: { userListId: this.listId, }, @@ -124,8 +124,8 @@ export class UserListChannelService { @Inject(DI.userListsRepository) private userListsRepository: UserListsRepository, - @Inject(DI.userListJoiningsRepository) - private userListJoiningsRepository: UserListJoiningsRepository, + @Inject(DI.userListMembershipsRepository) + private userListMembershipsRepository: UserListMembershipsRepository, private noteEntityService: NoteEntityService, ) { @@ -135,7 +135,7 @@ export class UserListChannelService { public create(id: string, connection: Channel['connection']): UserListChannel { return new UserListChannel( this.userListsRepository, - this.userListJoiningsRepository, + this.userListMembershipsRepository, this.noteEntityService, id, connection, From c74fff1812226a29ea975132fc828b21e8dda3aa Mon Sep 17 00:00:00 2001 From: syuilo Date: Wed, 4 Oct 2023 08:46:27 +0900 Subject: [PATCH 05/22] =?UTF-8?q?enhance:=20TL=E3=82=AD=E3=83=A3=E3=83=83?= =?UTF-8?q?=E3=82=B7=E3=83=A5=E5=AE=B9=E9=87=8F=E3=82=92=E8=A8=AD=E5=AE=9A?= =?UTF-8?q?=E3=81=A7=E3=81=8D=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- locales/index.d.ts | 1 + locales/ja-JP.yml | 1 + .../1696373953614-meta-cache-settings.js | 22 ++++ packages/backend/src/models/Meta.ts | 20 ++++ .../src/server/api/endpoints/admin/meta.ts | 103 ++++++++++-------- .../server/api/endpoints/admin/update-meta.ts | 20 ++++ .../backend/src/server/api/endpoints/meta.ts | 4 +- .../src/pages/admin/external-services.vue | 81 ++++++++++++++ packages/frontend/src/pages/admin/index.vue | 5 + .../frontend/src/pages/admin/settings.vue | 40 ++++--- packages/frontend/src/router.ts | 4 + 11 files changed, 238 insertions(+), 63 deletions(-) create mode 100644 packages/backend/migration/1696373953614-meta-cache-settings.js create mode 100644 packages/frontend/src/pages/admin/external-services.vue diff --git a/locales/index.d.ts b/locales/index.d.ts index f1e1f061db49..199d8b1f9e50 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -1131,6 +1131,7 @@ export interface Locale { "notificationRecieveConfig": string; "mutualFollow": string; "fileAttachedOnly": string; + "externalServices": string; "_announcement": { "forExistingUsers": string; "forExistingUsersDescription": string; diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index ecadb768ecf4..95d512d70601 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1128,6 +1128,7 @@ edited: "編集済み" notificationRecieveConfig: "通知の受信設定" mutualFollow: "相互フォロー" fileAttachedOnly: "ファイル付きのみ" +externalServices: "外部サービス" _announcement: forExistingUsers: "既存ユーザーのみ" diff --git a/packages/backend/migration/1696373953614-meta-cache-settings.js b/packages/backend/migration/1696373953614-meta-cache-settings.js new file mode 100644 index 000000000000..f994b76ef263 --- /dev/null +++ b/packages/backend/migration/1696373953614-meta-cache-settings.js @@ -0,0 +1,22 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class MetaCacheSettings1696373953614 { + name = 'MetaCacheSettings1696373953614' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" ADD "perLocalUserUserTimelineCacheMax" integer NOT NULL DEFAULT '300'`); + await queryRunner.query(`ALTER TABLE "meta" ADD "perRemoteUserUserTimelineCacheMax" integer NOT NULL DEFAULT '100'`); + await queryRunner.query(`ALTER TABLE "meta" ADD "perUserHomeTimelineCacheMax" integer NOT NULL DEFAULT '300'`); + await queryRunner.query(`ALTER TABLE "meta" ADD "perUserListTimelineCacheMax" integer NOT NULL DEFAULT '300'`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "perUserListTimelineCacheMax"`); + await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "perUserHomeTimelineCacheMax"`); + await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "perRemoteUserUserTimelineCacheMax"`); + await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "perLocalUserUserTimelineCacheMax"`); + } +} diff --git a/packages/backend/src/models/Meta.ts b/packages/backend/src/models/Meta.ts index e69bef8e9845..491d446723e5 100644 --- a/packages/backend/src/models/Meta.ts +++ b/packages/backend/src/models/Meta.ts @@ -471,4 +471,24 @@ export class MiMeta { length: 1024, array: true, default: '{ "admin", "administrator", "root", "system", "maintainer", "host", "mod", "moderator", "owner", "superuser", "staff", "auth", "i", "me", "everyone", "all", "mention", "mentions", "example", "user", "users", "account", "accounts", "official", "help", "helps", "support", "supports", "info", "information", "informations", "announce", "announces", "announcement", "announcements", "notice", "notification", "notifications", "dev", "developer", "developers", "tech", "misskey" }', }) public preservedUsernames: string[]; + + @Column('integer', { + default: 300, + }) + public perLocalUserUserTimelineCacheMax: number; + + @Column('integer', { + default: 100, + }) + public perRemoteUserUserTimelineCacheMax: number; + + @Column('integer', { + default: 300, + }) + public perUserHomeTimelineCacheMax: number; + + @Column('integer', { + default: 300, + }) + public perUserListTimelineCacheMax: number; } diff --git a/packages/backend/src/server/api/endpoints/admin/meta.ts b/packages/backend/src/server/api/endpoints/admin/meta.ts index c3ba07cdd086..53e36727847d 100644 --- a/packages/backend/src/server/api/endpoints/admin/meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/meta.ts @@ -105,40 +105,32 @@ export const meta = { type: 'boolean', optional: false, nullable: false, }, - userStarForReactionFallback: { - type: 'boolean', - optional: true, nullable: false, - }, pinnedUsers: { type: 'array', - optional: true, nullable: false, + optional: false, nullable: false, items: { type: 'string', - optional: false, nullable: false, }, }, hiddenTags: { type: 'array', - optional: true, nullable: false, + optional: false, nullable: false, items: { type: 'string', - optional: false, nullable: false, }, }, blockedHosts: { type: 'array', - optional: true, nullable: false, + optional: false, nullable: false, items: { type: 'string', - optional: false, nullable: false, }, }, sensitiveWords: { type: 'array', - optional: true, nullable: false, + optional: false, nullable: false, items: { type: 'string', - optional: false, nullable: false, }, }, preservedUsernames: { @@ -146,129 +138,124 @@ export const meta = { optional: false, nullable: false, items: { type: 'string', - optional: false, nullable: false, }, }, hcaptchaSecretKey: { type: 'string', - optional: true, nullable: true, + optional: false, nullable: true, }, recaptchaSecretKey: { type: 'string', - optional: true, nullable: true, + optional: false, nullable: true, }, turnstileSecretKey: { type: 'string', - optional: true, nullable: true, + optional: false, nullable: true, }, sensitiveMediaDetection: { type: 'string', - optional: true, nullable: false, + optional: false, nullable: false, }, sensitiveMediaDetectionSensitivity: { type: 'string', - optional: true, nullable: false, + optional: false, nullable: false, }, setSensitiveFlagAutomatically: { type: 'boolean', - optional: true, nullable: false, + optional: false, nullable: false, }, enableSensitiveMediaDetectionForVideos: { type: 'boolean', - optional: true, nullable: false, + optional: false, nullable: false, }, proxyAccountId: { type: 'string', - optional: true, nullable: true, + optional: false, nullable: true, format: 'id', }, - summaryProxy: { - type: 'string', - optional: true, nullable: true, - }, email: { type: 'string', - optional: true, nullable: true, + optional: false, nullable: true, }, smtpSecure: { type: 'boolean', - optional: true, nullable: false, + optional: false, nullable: false, }, smtpHost: { type: 'string', - optional: true, nullable: true, + optional: false, nullable: true, }, smtpPort: { type: 'number', - optional: true, nullable: true, + optional: false, nullable: true, }, smtpUser: { type: 'string', - optional: true, nullable: true, + optional: false, nullable: true, }, smtpPass: { type: 'string', - optional: true, nullable: true, + optional: false, nullable: true, }, swPrivateKey: { type: 'string', - optional: true, nullable: true, + optional: false, nullable: true, }, useObjectStorage: { type: 'boolean', - optional: true, nullable: false, + optional: false, nullable: false, }, objectStorageBaseUrl: { type: 'string', - optional: true, nullable: true, + optional: false, nullable: true, }, objectStorageBucket: { type: 'string', - optional: true, nullable: true, + optional: false, nullable: true, }, objectStoragePrefix: { type: 'string', - optional: true, nullable: true, + optional: false, nullable: true, }, objectStorageEndpoint: { type: 'string', - optional: true, nullable: true, + optional: false, nullable: true, }, objectStorageRegion: { type: 'string', - optional: true, nullable: true, + optional: false, nullable: true, }, objectStoragePort: { type: 'number', - optional: true, nullable: true, + optional: false, nullable: true, }, objectStorageAccessKey: { type: 'string', - optional: true, nullable: true, + optional: false, nullable: true, }, objectStorageSecretKey: { type: 'string', - optional: true, nullable: true, + optional: false, nullable: true, }, objectStorageUseSSL: { type: 'boolean', - optional: true, nullable: false, + optional: false, nullable: false, }, objectStorageUseProxy: { type: 'boolean', - optional: true, nullable: false, + optional: false, nullable: false, }, objectStorageSetPublicRead: { type: 'boolean', - optional: true, nullable: false, + optional: false, nullable: false, }, enableIpLogging: { type: 'boolean', - optional: true, nullable: false, + optional: false, nullable: false, }, enableActiveEmailValidation: { type: 'boolean', - optional: true, nullable: false, + optional: false, nullable: false, }, enableChartsForRemoteUser: { type: 'boolean', @@ -288,12 +275,28 @@ export const meta = { }, manifestJsonOverride: { type: 'string', - optional: true, nullable: false, + optional: false, nullable: false, }, policies: { type: 'object', optional: false, nullable: false, }, + perLocalUserUserTimelineCacheMax: { + type: 'number', + optional: false, nullable: false, + }, + perRemoteUserUserTimelineCacheMax: { + type: 'number', + optional: false, nullable: false, + }, + perUserHomeTimelineCacheMax: { + type: 'number', + optional: false, nullable: false, + }, + perUserListTimelineCacheMax: { + type: 'number', + optional: false, nullable: false, + }, }, }, } as const; @@ -313,7 +316,7 @@ export default class extends Endpoint { // eslint- private metaService: MetaService, ) { - super(meta, paramDef, async (ps, me) => { + super(meta, paramDef, async () => { const instance = await this.metaService.fetch(true); return { @@ -399,6 +402,10 @@ export default class extends Endpoint { // eslint- enableIdenticonGeneration: instance.enableIdenticonGeneration, policies: { ...DEFAULT_POLICIES, ...instance.policies }, manifestJsonOverride: instance.manifestJsonOverride, + perLocalUserUserTimelineCacheMax: instance.perLocalUserUserTimelineCacheMax, + perRemoteUserUserTimelineCacheMax: instance.perRemoteUserUserTimelineCacheMax, + perUserHomeTimelineCacheMax: instance.perUserHomeTimelineCacheMax, + perUserListTimelineCacheMax: instance.perUserListTimelineCacheMax, }; }); } diff --git a/packages/backend/src/server/api/endpoints/admin/update-meta.ts b/packages/backend/src/server/api/endpoints/admin/update-meta.ts index ea6ebdd1fe1a..247d3ba4e031 100644 --- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts @@ -108,6 +108,10 @@ export const paramDef = { serverRules: { type: 'array', items: { type: 'string' } }, preservedUsernames: { type: 'array', items: { type: 'string' } }, manifestJsonOverride: { type: 'string' }, + perLocalUserUserTimelineCacheMax: { type: 'integer' }, + perRemoteUserUserTimelineCacheMax: { type: 'integer' }, + perUserHomeTimelineCacheMax: { type: 'integer' }, + perUserListTimelineCacheMax: { type: 'integer' }, }, required: [], } as const; @@ -441,6 +445,22 @@ export default class extends Endpoint { // eslint- set.manifestJsonOverride = ps.manifestJsonOverride; } + if (ps.perLocalUserUserTimelineCacheMax !== undefined) { + set.perLocalUserUserTimelineCacheMax = ps.perLocalUserUserTimelineCacheMax; + } + + if (ps.perRemoteUserUserTimelineCacheMax !== undefined) { + set.perRemoteUserUserTimelineCacheMax = ps.perRemoteUserUserTimelineCacheMax; + } + + if (ps.perUserHomeTimelineCacheMax !== undefined) { + set.perUserHomeTimelineCacheMax = ps.perUserHomeTimelineCacheMax; + } + + if (ps.perUserListTimelineCacheMax !== undefined) { + set.perUserListTimelineCacheMax = ps.perUserListTimelineCacheMax; + } + const before = await this.metaService.fetch(true); await this.metaService.update(set); diff --git a/packages/backend/src/server/api/endpoints/meta.ts b/packages/backend/src/server/api/endpoints/meta.ts index fa6486ed1834..271b3f6fb27a 100644 --- a/packages/backend/src/server/api/endpoints/meta.ts +++ b/packages/backend/src/server/api/endpoints/meta.ts @@ -214,11 +214,11 @@ export const meta = { type: 'boolean', optional: false, nullable: false, }, - localTimeLine: { + localTimeline: { type: 'boolean', optional: false, nullable: false, }, - globalTimeLine: { + globalTimeline: { type: 'boolean', optional: false, nullable: false, }, diff --git a/packages/frontend/src/pages/admin/external-services.vue b/packages/frontend/src/pages/admin/external-services.vue new file mode 100644 index 000000000000..5944bf500a15 --- /dev/null +++ b/packages/frontend/src/pages/admin/external-services.vue @@ -0,0 +1,81 @@ + + + + + + + diff --git a/packages/frontend/src/pages/admin/index.vue b/packages/frontend/src/pages/admin/index.vue index 944ba7b950e1..a508c20cf317 100644 --- a/packages/frontend/src/pages/admin/index.vue +++ b/packages/frontend/src/pages/admin/index.vue @@ -198,6 +198,11 @@ const menuDef = $computed(() => [{ text: i18n.ts.proxyAccount, to: '/admin/proxy-account', active: currentPage?.route.name === 'proxy-account', + }, { + icon: 'ti ti-link', + text: i18n.ts.externalServices, + to: '/admin/external-services', + active: currentPage?.route.name === 'external-services', }, { icon: 'ti ti-adjustments', text: i18n.ts.other, diff --git a/packages/frontend/src/pages/admin/settings.vue b/packages/frontend/src/pages/admin/settings.vue index f93678d72802..09a6cc7e2cdd 100644 --- a/packages/frontend/src/pages/admin/settings.vue +++ b/packages/frontend/src/pages/admin/settings.vue @@ -81,16 +81,24 @@ SPDX-License-Identifier: AGPL-3.0-only - +
- - - + + + + + + + + + + + + + + - - -
@@ -133,8 +141,10 @@ let cacheRemoteSensitiveFiles: boolean = $ref(false); let enableServiceWorker: boolean = $ref(false); let swPublicKey: any = $ref(null); let swPrivateKey: any = $ref(null); -let deeplAuthKey: string = $ref(''); -let deeplIsPro: boolean = $ref(false); +let perLocalUserUserTimelineCacheMax: number = $ref(0); +let perRemoteUserUserTimelineCacheMax: number = $ref(0); +let perUserHomeTimelineCacheMax: number = $ref(0); +let perUserListTimelineCacheMax: number = $ref(0); async function init(): Promise { const meta = await os.api('admin/meta'); @@ -149,8 +159,10 @@ async function init(): Promise { enableServiceWorker = meta.enableServiceWorker; swPublicKey = meta.swPublickey; swPrivateKey = meta.swPrivateKey; - deeplAuthKey = meta.deeplAuthKey; - deeplIsPro = meta.deeplIsPro; + perLocalUserUserTimelineCacheMax = meta.perLocalUserUserTimelineCacheMax; + perRemoteUserUserTimelineCacheMax = meta.perRemoteUserUserTimelineCacheMax; + perUserHomeTimelineCacheMax = meta.perUserHomeTimelineCacheMax; + perUserListTimelineCacheMax = meta.perUserListTimelineCacheMax; } function save(): void { @@ -166,8 +178,10 @@ function save(): void { enableServiceWorker, swPublicKey, swPrivateKey, - deeplAuthKey, - deeplIsPro, + perLocalUserUserTimelineCacheMax, + perRemoteUserUserTimelineCacheMax, + perUserHomeTimelineCacheMax, + perUserListTimelineCacheMax, }).then(() => { fetchInstance(); }); diff --git a/packages/frontend/src/router.ts b/packages/frontend/src/router.ts index 415d2f197422..20314711a4b8 100644 --- a/packages/frontend/src/router.ts +++ b/packages/frontend/src/router.ts @@ -435,6 +435,10 @@ export const routes = [{ path: '/proxy-account', name: 'proxy-account', component: page(() => import('./pages/admin/proxy-account.vue')), + }, { + path: '/external-services', + name: 'external-services', + component: page(() => import('./pages/admin/external-services.vue')), }, { path: '/other-settings', name: 'other-settings', From 0c8e019ce6f75a82705d0e08df856d288c7ec673 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Tue, 10 Oct 2023 22:20:16 +0900 Subject: [PATCH 06/22] fix: type error (merge miss) --- .../src/server/api/endpoints/notes/user-list-timeline.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts b/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts index 8ea3810e20ac..e536a43de599 100644 --- a/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts @@ -5,7 +5,7 @@ import { Brackets } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import type { NotesRepository, UserListsRepository, UserListJoiningsRepository } from '@/models/_.js'; +import type { NotesRepository, UserListsRepository, UserListMembershipsRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; @@ -69,8 +69,8 @@ export default class extends Endpoint { // eslint- @Inject(DI.userListsRepository) private userListsRepository: UserListsRepository, - @Inject(DI.userListJoiningsRepository) - private userListJoiningsRepository: UserListJoiningsRepository, + @Inject(DI.userListMembershipsRepository) + private userListMembershipsRepository: UserListMembershipsRepository, private noteEntityService: NoteEntityService, private queryService: QueryService, @@ -88,7 +88,7 @@ export default class extends Endpoint { // eslint- //#region Construct query const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId) - .innerJoin(this.userListJoiningsRepository.metadata.targetName, 'userListJoining', 'userListJoining.userId = note.userId') + .innerJoin(this.userListMembershipsRepository.metadata.targetName, 'userListJoining', 'userListJoining.userId = note.userId') .innerJoinAndSelect('note.user', 'user') .leftJoinAndSelect('note.reply', 'reply') .leftJoinAndSelect('note.renote', 'renote') From 70f398466a0defd58e979f349690a1e4aa8fc982 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Tue, 10 Oct 2023 22:32:29 +0900 Subject: [PATCH 07/22] =?UTF-8?q?enhance(backend):=20UserListMembership?= =?UTF-8?q?=E3=81=AB=E3=83=A6=E3=83=BC=E3=82=B6=E3=83=BC=E3=83=AA=E3=82=B9?= =?UTF-8?q?=E3=83=88=E3=81=AE=E4=BD=9C=E6=88=90=E8=80=85ID=E3=82=92?= =?UTF-8?q?=E9=9D=9E=E6=AD=A3=E8=A6=8F=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 17b83ff4c13e873b63262c349ea9c7bade0d656a --- .../migration/1696807733453-userListUserId.js | 22 +++++++++++++++++++ .../1696808725134-userListUserId-2.js | 16 ++++++++++++++ .../backend/src/core/AccountMoveService.ts | 3 ++- packages/backend/src/core/UserListService.ts | 1 + .../backend/src/models/UserListMembership.ts | 7 ++++++ 5 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 packages/backend/migration/1696807733453-userListUserId.js create mode 100644 packages/backend/migration/1696808725134-userListUserId-2.js diff --git a/packages/backend/migration/1696807733453-userListUserId.js b/packages/backend/migration/1696807733453-userListUserId.js new file mode 100644 index 000000000000..f4ec58f36aa0 --- /dev/null +++ b/packages/backend/migration/1696807733453-userListUserId.js @@ -0,0 +1,22 @@ + +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class UserListUserId1696807733453 { + name = 'UserListUserId1696807733453' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "user_list_membership" ADD "userListUserId" character varying(32) NOT NULL DEFAULT ''`); + const memberships = await queryRunner.query(`SELECT "id", "userListId" FROM "user_list_membership"`); + for(let i = 0; i < memberships.length; i++) { + const userList = await queryRunner.query(`SELECT "userId" FROM "user_list" WHERE "id" = $1`, [memberships[i].userListId]); + await queryRunner.query(`UPDATE "user_list_membership" SET "userListUserId" = $1 WHERE "id" = $2`, [userList[0].userId, memberships[i].id]); + } + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "user_list_membership" DROP COLUMN "userListUserId"`); + } +} diff --git a/packages/backend/migration/1696808725134-userListUserId-2.js b/packages/backend/migration/1696808725134-userListUserId-2.js new file mode 100644 index 000000000000..22d8704a9bfa --- /dev/null +++ b/packages/backend/migration/1696808725134-userListUserId-2.js @@ -0,0 +1,16 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class UserListUserId21696808725134 { + name = 'UserListUserId21696808725134' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "user_list_membership" ALTER COLUMN "userListUserId" DROP DEFAULT`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "user_list_membership" ALTER COLUMN "userListUserId" SET DEFAULT ''`); + } +} diff --git a/packages/backend/src/core/AccountMoveService.ts b/packages/backend/src/core/AccountMoveService.ts index ba3413007d84..db64f42754cc 100644 --- a/packages/backend/src/core/AccountMoveService.ts +++ b/packages/backend/src/core/AccountMoveService.ts @@ -228,7 +228,7 @@ export class AccountMoveService { }, }).then(memberships => memberships.map(membership => membership.userListId)); - const newMemberships: Map = new Map(); + const newMemberships: Map = new Map(); // 重複しないようにIDを生成 const genId = (): string => { @@ -244,6 +244,7 @@ export class AccountMoveService { createdAt: new Date(), userId: dst.id, userListId: membership.userListId, + userListUserId: membership.userListUserId, }); } diff --git a/packages/backend/src/core/UserListService.ts b/packages/backend/src/core/UserListService.ts index bece1e442ef5..5b4e7a711eca 100644 --- a/packages/backend/src/core/UserListService.ts +++ b/packages/backend/src/core/UserListService.ts @@ -97,6 +97,7 @@ export class UserListService implements OnApplicationShutdown { createdAt: new Date(), userId: target.id, userListId: list.id, + userListUserId: list.userId, } as MiUserListMembership); this.globalEventService.publishInternalEvent('userListMemberAdded', { userListId: list.id, memberId: target.id }); diff --git a/packages/backend/src/models/UserListMembership.ts b/packages/backend/src/models/UserListMembership.ts index 3a50e4874ba9..fc15cb8099ad 100644 --- a/packages/backend/src/models/UserListMembership.ts +++ b/packages/backend/src/models/UserListMembership.ts @@ -50,4 +50,11 @@ export class MiUserListMembership { default: true, }) public withReplies: boolean; + + //#region Denormalized fields + @Column({ + ...id(), + }) + public userListUserId: MiUser['id']; + //#endregion } From 0f55162dfa6b9b0c793f8dbb1d437802009fa807 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Tue, 10 Oct 2023 23:17:09 +0900 Subject: [PATCH 08/22] fix: antenna become not working --- locales/index.d.ts | 2 +- packages/backend/src/core/AntennaService.ts | 5 +++++ packages/backend/src/server/api/endpoints/antennas/notes.ts | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/locales/index.d.ts b/locales/index.d.ts index 199d8b1f9e50..d6fcf1ee81c7 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -1131,7 +1131,7 @@ export interface Locale { "notificationRecieveConfig": string; "mutualFollow": string; "fileAttachedOnly": string; - "externalServices": string; + "externalServices": string; "_announcement": { "forExistingUsers": string; "forExistingUsersDescription": string; diff --git a/packages/backend/src/core/AntennaService.ts b/packages/backend/src/core/AntennaService.ts index ca7624b1d4fb..7bb50ab6451a 100644 --- a/packages/backend/src/core/AntennaService.ts +++ b/packages/backend/src/core/AntennaService.ts @@ -86,6 +86,11 @@ export class AntennaService implements OnApplicationShutdown { const redisPipeline = this.redisForTimelines.pipeline(); for (const antenna of matchedAntennas) { + redisPipeline.xadd( + `antennaTimeline:${antenna.id}`, + 'MAXLEN', '~', '200', + '*', + 'note', note.id); this.redisTimelineService.push(`antennaTimeline:${antenna.id}`, note.id, 200, redisPipeline); this.globalEventService.publishAntennaStream(antenna.id, 'note', note); } diff --git a/packages/backend/src/server/api/endpoints/antennas/notes.ts b/packages/backend/src/server/api/endpoints/antennas/notes.ts index eaae7bff62f1..8477dd68daef 100644 --- a/packages/backend/src/server/api/endpoints/antennas/notes.ts +++ b/packages/backend/src/server/api/endpoints/antennas/notes.ts @@ -56,7 +56,7 @@ export const paramDef = { @Injectable() export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( - @Inject(DI.redis) + @Inject(DI.redisForTimelines) private redisClient: Redis.Redis, @Inject(DI.notesRepository) From a3cfff3646bcddbfed6de75fc8fdc5c3d8592bff Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Wed, 11 Oct 2023 12:04:22 +0900 Subject: [PATCH 09/22] =?UTF-8?q?Revert=20"enhance:=20TL=E3=82=AD=E3=83=A3?= =?UTF-8?q?=E3=83=83=E3=82=B7=E3=83=A5=E5=AE=B9=E9=87=8F=E3=82=92=E8=A8=AD?= =?UTF-8?q?=E5=AE=9A=E3=81=A7=E3=81=8D=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB?= =?UTF-8?q?"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit c74fff1812226a29ea975132fc828b21e8dda3aa. --- locales/index.d.ts | 1 - locales/ja-JP.yml | 1 - .../1696373953614-meta-cache-settings.js | 22 ---- packages/backend/src/models/Meta.ts | 20 ---- .../src/server/api/endpoints/admin/meta.ts | 103 ++++++++---------- .../server/api/endpoints/admin/update-meta.ts | 20 ---- .../backend/src/server/api/endpoints/meta.ts | 4 +- .../src/pages/admin/external-services.vue | 81 -------------- packages/frontend/src/pages/admin/index.vue | 5 - .../frontend/src/pages/admin/settings.vue | 40 +++---- packages/frontend/src/router.ts | 4 - 11 files changed, 63 insertions(+), 238 deletions(-) delete mode 100644 packages/backend/migration/1696373953614-meta-cache-settings.js delete mode 100644 packages/frontend/src/pages/admin/external-services.vue diff --git a/locales/index.d.ts b/locales/index.d.ts index d6fcf1ee81c7..f1e1f061db49 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -1131,7 +1131,6 @@ export interface Locale { "notificationRecieveConfig": string; "mutualFollow": string; "fileAttachedOnly": string; - "externalServices": string; "_announcement": { "forExistingUsers": string; "forExistingUsersDescription": string; diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 95d512d70601..ecadb768ecf4 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1128,7 +1128,6 @@ edited: "編集済み" notificationRecieveConfig: "通知の受信設定" mutualFollow: "相互フォロー" fileAttachedOnly: "ファイル付きのみ" -externalServices: "外部サービス" _announcement: forExistingUsers: "既存ユーザーのみ" diff --git a/packages/backend/migration/1696373953614-meta-cache-settings.js b/packages/backend/migration/1696373953614-meta-cache-settings.js deleted file mode 100644 index f994b76ef263..000000000000 --- a/packages/backend/migration/1696373953614-meta-cache-settings.js +++ /dev/null @@ -1,22 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and other misskey contributors - * SPDX-License-Identifier: AGPL-3.0-only - */ - -export class MetaCacheSettings1696373953614 { - name = 'MetaCacheSettings1696373953614' - - async up(queryRunner) { - await queryRunner.query(`ALTER TABLE "meta" ADD "perLocalUserUserTimelineCacheMax" integer NOT NULL DEFAULT '300'`); - await queryRunner.query(`ALTER TABLE "meta" ADD "perRemoteUserUserTimelineCacheMax" integer NOT NULL DEFAULT '100'`); - await queryRunner.query(`ALTER TABLE "meta" ADD "perUserHomeTimelineCacheMax" integer NOT NULL DEFAULT '300'`); - await queryRunner.query(`ALTER TABLE "meta" ADD "perUserListTimelineCacheMax" integer NOT NULL DEFAULT '300'`); - } - - async down(queryRunner) { - await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "perUserListTimelineCacheMax"`); - await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "perUserHomeTimelineCacheMax"`); - await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "perRemoteUserUserTimelineCacheMax"`); - await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "perLocalUserUserTimelineCacheMax"`); - } -} diff --git a/packages/backend/src/models/Meta.ts b/packages/backend/src/models/Meta.ts index 491d446723e5..e69bef8e9845 100644 --- a/packages/backend/src/models/Meta.ts +++ b/packages/backend/src/models/Meta.ts @@ -471,24 +471,4 @@ export class MiMeta { length: 1024, array: true, default: '{ "admin", "administrator", "root", "system", "maintainer", "host", "mod", "moderator", "owner", "superuser", "staff", "auth", "i", "me", "everyone", "all", "mention", "mentions", "example", "user", "users", "account", "accounts", "official", "help", "helps", "support", "supports", "info", "information", "informations", "announce", "announces", "announcement", "announcements", "notice", "notification", "notifications", "dev", "developer", "developers", "tech", "misskey" }', }) public preservedUsernames: string[]; - - @Column('integer', { - default: 300, - }) - public perLocalUserUserTimelineCacheMax: number; - - @Column('integer', { - default: 100, - }) - public perRemoteUserUserTimelineCacheMax: number; - - @Column('integer', { - default: 300, - }) - public perUserHomeTimelineCacheMax: number; - - @Column('integer', { - default: 300, - }) - public perUserListTimelineCacheMax: number; } diff --git a/packages/backend/src/server/api/endpoints/admin/meta.ts b/packages/backend/src/server/api/endpoints/admin/meta.ts index 53e36727847d..c3ba07cdd086 100644 --- a/packages/backend/src/server/api/endpoints/admin/meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/meta.ts @@ -105,32 +105,40 @@ export const meta = { type: 'boolean', optional: false, nullable: false, }, + userStarForReactionFallback: { + type: 'boolean', + optional: true, nullable: false, + }, pinnedUsers: { type: 'array', - optional: false, nullable: false, + optional: true, nullable: false, items: { type: 'string', + optional: false, nullable: false, }, }, hiddenTags: { type: 'array', - optional: false, nullable: false, + optional: true, nullable: false, items: { type: 'string', + optional: false, nullable: false, }, }, blockedHosts: { type: 'array', - optional: false, nullable: false, + optional: true, nullable: false, items: { type: 'string', + optional: false, nullable: false, }, }, sensitiveWords: { type: 'array', - optional: false, nullable: false, + optional: true, nullable: false, items: { type: 'string', + optional: false, nullable: false, }, }, preservedUsernames: { @@ -138,124 +146,129 @@ export const meta = { optional: false, nullable: false, items: { type: 'string', + optional: false, nullable: false, }, }, hcaptchaSecretKey: { type: 'string', - optional: false, nullable: true, + optional: true, nullable: true, }, recaptchaSecretKey: { type: 'string', - optional: false, nullable: true, + optional: true, nullable: true, }, turnstileSecretKey: { type: 'string', - optional: false, nullable: true, + optional: true, nullable: true, }, sensitiveMediaDetection: { type: 'string', - optional: false, nullable: false, + optional: true, nullable: false, }, sensitiveMediaDetectionSensitivity: { type: 'string', - optional: false, nullable: false, + optional: true, nullable: false, }, setSensitiveFlagAutomatically: { type: 'boolean', - optional: false, nullable: false, + optional: true, nullable: false, }, enableSensitiveMediaDetectionForVideos: { type: 'boolean', - optional: false, nullable: false, + optional: true, nullable: false, }, proxyAccountId: { type: 'string', - optional: false, nullable: true, + optional: true, nullable: true, format: 'id', }, + summaryProxy: { + type: 'string', + optional: true, nullable: true, + }, email: { type: 'string', - optional: false, nullable: true, + optional: true, nullable: true, }, smtpSecure: { type: 'boolean', - optional: false, nullable: false, + optional: true, nullable: false, }, smtpHost: { type: 'string', - optional: false, nullable: true, + optional: true, nullable: true, }, smtpPort: { type: 'number', - optional: false, nullable: true, + optional: true, nullable: true, }, smtpUser: { type: 'string', - optional: false, nullable: true, + optional: true, nullable: true, }, smtpPass: { type: 'string', - optional: false, nullable: true, + optional: true, nullable: true, }, swPrivateKey: { type: 'string', - optional: false, nullable: true, + optional: true, nullable: true, }, useObjectStorage: { type: 'boolean', - optional: false, nullable: false, + optional: true, nullable: false, }, objectStorageBaseUrl: { type: 'string', - optional: false, nullable: true, + optional: true, nullable: true, }, objectStorageBucket: { type: 'string', - optional: false, nullable: true, + optional: true, nullable: true, }, objectStoragePrefix: { type: 'string', - optional: false, nullable: true, + optional: true, nullable: true, }, objectStorageEndpoint: { type: 'string', - optional: false, nullable: true, + optional: true, nullable: true, }, objectStorageRegion: { type: 'string', - optional: false, nullable: true, + optional: true, nullable: true, }, objectStoragePort: { type: 'number', - optional: false, nullable: true, + optional: true, nullable: true, }, objectStorageAccessKey: { type: 'string', - optional: false, nullable: true, + optional: true, nullable: true, }, objectStorageSecretKey: { type: 'string', - optional: false, nullable: true, + optional: true, nullable: true, }, objectStorageUseSSL: { type: 'boolean', - optional: false, nullable: false, + optional: true, nullable: false, }, objectStorageUseProxy: { type: 'boolean', - optional: false, nullable: false, + optional: true, nullable: false, }, objectStorageSetPublicRead: { type: 'boolean', - optional: false, nullable: false, + optional: true, nullable: false, }, enableIpLogging: { type: 'boolean', - optional: false, nullable: false, + optional: true, nullable: false, }, enableActiveEmailValidation: { type: 'boolean', - optional: false, nullable: false, + optional: true, nullable: false, }, enableChartsForRemoteUser: { type: 'boolean', @@ -275,28 +288,12 @@ export const meta = { }, manifestJsonOverride: { type: 'string', - optional: false, nullable: false, + optional: true, nullable: false, }, policies: { type: 'object', optional: false, nullable: false, }, - perLocalUserUserTimelineCacheMax: { - type: 'number', - optional: false, nullable: false, - }, - perRemoteUserUserTimelineCacheMax: { - type: 'number', - optional: false, nullable: false, - }, - perUserHomeTimelineCacheMax: { - type: 'number', - optional: false, nullable: false, - }, - perUserListTimelineCacheMax: { - type: 'number', - optional: false, nullable: false, - }, }, }, } as const; @@ -316,7 +313,7 @@ export default class extends Endpoint { // eslint- private metaService: MetaService, ) { - super(meta, paramDef, async () => { + super(meta, paramDef, async (ps, me) => { const instance = await this.metaService.fetch(true); return { @@ -402,10 +399,6 @@ export default class extends Endpoint { // eslint- enableIdenticonGeneration: instance.enableIdenticonGeneration, policies: { ...DEFAULT_POLICIES, ...instance.policies }, manifestJsonOverride: instance.manifestJsonOverride, - perLocalUserUserTimelineCacheMax: instance.perLocalUserUserTimelineCacheMax, - perRemoteUserUserTimelineCacheMax: instance.perRemoteUserUserTimelineCacheMax, - perUserHomeTimelineCacheMax: instance.perUserHomeTimelineCacheMax, - perUserListTimelineCacheMax: instance.perUserListTimelineCacheMax, }; }); } diff --git a/packages/backend/src/server/api/endpoints/admin/update-meta.ts b/packages/backend/src/server/api/endpoints/admin/update-meta.ts index 247d3ba4e031..ea6ebdd1fe1a 100644 --- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts @@ -108,10 +108,6 @@ export const paramDef = { serverRules: { type: 'array', items: { type: 'string' } }, preservedUsernames: { type: 'array', items: { type: 'string' } }, manifestJsonOverride: { type: 'string' }, - perLocalUserUserTimelineCacheMax: { type: 'integer' }, - perRemoteUserUserTimelineCacheMax: { type: 'integer' }, - perUserHomeTimelineCacheMax: { type: 'integer' }, - perUserListTimelineCacheMax: { type: 'integer' }, }, required: [], } as const; @@ -445,22 +441,6 @@ export default class extends Endpoint { // eslint- set.manifestJsonOverride = ps.manifestJsonOverride; } - if (ps.perLocalUserUserTimelineCacheMax !== undefined) { - set.perLocalUserUserTimelineCacheMax = ps.perLocalUserUserTimelineCacheMax; - } - - if (ps.perRemoteUserUserTimelineCacheMax !== undefined) { - set.perRemoteUserUserTimelineCacheMax = ps.perRemoteUserUserTimelineCacheMax; - } - - if (ps.perUserHomeTimelineCacheMax !== undefined) { - set.perUserHomeTimelineCacheMax = ps.perUserHomeTimelineCacheMax; - } - - if (ps.perUserListTimelineCacheMax !== undefined) { - set.perUserListTimelineCacheMax = ps.perUserListTimelineCacheMax; - } - const before = await this.metaService.fetch(true); await this.metaService.update(set); diff --git a/packages/backend/src/server/api/endpoints/meta.ts b/packages/backend/src/server/api/endpoints/meta.ts index 271b3f6fb27a..fa6486ed1834 100644 --- a/packages/backend/src/server/api/endpoints/meta.ts +++ b/packages/backend/src/server/api/endpoints/meta.ts @@ -214,11 +214,11 @@ export const meta = { type: 'boolean', optional: false, nullable: false, }, - localTimeline: { + localTimeLine: { type: 'boolean', optional: false, nullable: false, }, - globalTimeline: { + globalTimeLine: { type: 'boolean', optional: false, nullable: false, }, diff --git a/packages/frontend/src/pages/admin/external-services.vue b/packages/frontend/src/pages/admin/external-services.vue deleted file mode 100644 index 5944bf500a15..000000000000 --- a/packages/frontend/src/pages/admin/external-services.vue +++ /dev/null @@ -1,81 +0,0 @@ - - - - - - - diff --git a/packages/frontend/src/pages/admin/index.vue b/packages/frontend/src/pages/admin/index.vue index a508c20cf317..944ba7b950e1 100644 --- a/packages/frontend/src/pages/admin/index.vue +++ b/packages/frontend/src/pages/admin/index.vue @@ -198,11 +198,6 @@ const menuDef = $computed(() => [{ text: i18n.ts.proxyAccount, to: '/admin/proxy-account', active: currentPage?.route.name === 'proxy-account', - }, { - icon: 'ti ti-link', - text: i18n.ts.externalServices, - to: '/admin/external-services', - active: currentPage?.route.name === 'external-services', }, { icon: 'ti ti-adjustments', text: i18n.ts.other, diff --git a/packages/frontend/src/pages/admin/settings.vue b/packages/frontend/src/pages/admin/settings.vue index 09a6cc7e2cdd..f93678d72802 100644 --- a/packages/frontend/src/pages/admin/settings.vue +++ b/packages/frontend/src/pages/admin/settings.vue @@ -81,24 +81,16 @@ SPDX-License-Identifier: AGPL-3.0-only - +
- - - - - - - - - - - - - - + + + + + +
@@ -141,10 +133,8 @@ let cacheRemoteSensitiveFiles: boolean = $ref(false); let enableServiceWorker: boolean = $ref(false); let swPublicKey: any = $ref(null); let swPrivateKey: any = $ref(null); -let perLocalUserUserTimelineCacheMax: number = $ref(0); -let perRemoteUserUserTimelineCacheMax: number = $ref(0); -let perUserHomeTimelineCacheMax: number = $ref(0); -let perUserListTimelineCacheMax: number = $ref(0); +let deeplAuthKey: string = $ref(''); +let deeplIsPro: boolean = $ref(false); async function init(): Promise { const meta = await os.api('admin/meta'); @@ -159,10 +149,8 @@ async function init(): Promise { enableServiceWorker = meta.enableServiceWorker; swPublicKey = meta.swPublickey; swPrivateKey = meta.swPrivateKey; - perLocalUserUserTimelineCacheMax = meta.perLocalUserUserTimelineCacheMax; - perRemoteUserUserTimelineCacheMax = meta.perRemoteUserUserTimelineCacheMax; - perUserHomeTimelineCacheMax = meta.perUserHomeTimelineCacheMax; - perUserListTimelineCacheMax = meta.perUserListTimelineCacheMax; + deeplAuthKey = meta.deeplAuthKey; + deeplIsPro = meta.deeplIsPro; } function save(): void { @@ -178,10 +166,8 @@ function save(): void { enableServiceWorker, swPublicKey, swPrivateKey, - perLocalUserUserTimelineCacheMax, - perRemoteUserUserTimelineCacheMax, - perUserHomeTimelineCacheMax, - perUserListTimelineCacheMax, + deeplAuthKey, + deeplIsPro, }).then(() => { fetchInstance(); }); diff --git a/packages/frontend/src/router.ts b/packages/frontend/src/router.ts index 20314711a4b8..415d2f197422 100644 --- a/packages/frontend/src/router.ts +++ b/packages/frontend/src/router.ts @@ -435,10 +435,6 @@ export const routes = [{ path: '/proxy-account', name: 'proxy-account', component: page(() => import('./pages/admin/proxy-account.vue')), - }, { - path: '/external-services', - name: 'external-services', - component: page(() => import('./pages/admin/external-services.vue')), }, { path: '/other-settings', name: 'other-settings', From f4daa129a0d1624cbca7472de69f7fc64ce5a30a Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Wed, 11 Oct 2023 12:10:01 +0900 Subject: [PATCH 10/22] chore: revert configurable TL length --- .../backend/src/core/NoteCreateService.ts | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts index 5e9035b88b47..042a08955014 100644 --- a/packages/backend/src/core/NoteCreateService.ts +++ b/packages/backend/src/core/NoteCreateService.ts @@ -841,7 +841,7 @@ export class NoteCreateService implements OnApplicationShutdown { if (note.channelId) { this.redisTimelineService.push(`channelTimeline:${note.channelId}`, note.id, this.config.perChannelMaxNoteCacheCount, r); - this.redisTimelineService.push(`userTimelineWithChannel:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax : meta.perRemoteUserUserTimelineCacheMax, r); + this.redisTimelineService.push(`userTimelineWithChannel:${user.id}`, note.id, 300, r); const channelFollowings = await this.channelFollowingsRepository.find({ where: { @@ -851,9 +851,9 @@ export class NoteCreateService implements OnApplicationShutdown { }); for (const channelFollowing of channelFollowings) { - this.redisTimelineService.push(`homeTimeline:${channelFollowing.followerId}`, note.id, meta.perUserHomeTimelineCacheMax, r); + this.redisTimelineService.push(`homeTimeline:${channelFollowing.followerId}`, note.id, 300, r); if (note.fileIds.length > 0) { - this.redisTimelineService.push(`homeTimelineWithFiles:${channelFollowing.followerId}`, note.id, meta.perUserHomeTimelineCacheMax / 2, r); + this.redisTimelineService.push(`homeTimelineWithFiles:${channelFollowing.followerId}`, note.id, 300 / 2, r); } } } else { @@ -891,9 +891,9 @@ export class NoteCreateService implements OnApplicationShutdown { if (!following.withReplies) continue; } - this.redisTimelineService.push(`homeTimeline:${following.followerId}`, note.id, meta.perUserHomeTimelineCacheMax, r); + this.redisTimelineService.push(`homeTimeline:${following.followerId}`, note.id, 300, r); if (note.fileIds.length > 0) { - this.redisTimelineService.push(`homeTimelineWithFiles:${following.followerId}`, note.id, meta.perUserHomeTimelineCacheMax / 2, r); + this.redisTimelineService.push(`homeTimelineWithFiles:${following.followerId}`, note.id, 300 / 2, r); } } @@ -909,26 +909,26 @@ export class NoteCreateService implements OnApplicationShutdown { if (!userListMembership.withReplies) continue; } - this.redisTimelineService.push(`userListTimeline:${userListMembership.userListId}`, note.id, meta.perUserListTimelineCacheMax, r); + this.redisTimelineService.push(`userListTimeline:${userListMembership.userListId}`, note.id, 300, r); if (note.fileIds.length > 0) { - this.redisTimelineService.push(`userListTimelineWithFiles:${userListMembership.userListId}`, note.id, meta.perUserListTimelineCacheMax / 2, r); + this.redisTimelineService.push(`userListTimelineWithFiles:${userListMembership.userListId}`, note.id, 300 / 2, r); } } if (note.visibility !== 'specified' || !note.visibleUserIds.some(v => v === user.id)) { // 自分自身のHTL - this.redisTimelineService.push(`homeTimeline:${user.id}`, note.id, meta.perUserHomeTimelineCacheMax, r); + this.redisTimelineService.push(`homeTimeline:${user.id}`, note.id, 300, r); if (note.fileIds.length > 0) { - this.redisTimelineService.push(`homeTimelineWithFiles:${user.id}`, note.id, meta.perUserHomeTimelineCacheMax / 2, r); + this.redisTimelineService.push(`homeTimelineWithFiles:${user.id}`, note.id, 300 / 2, r); } } // 自分自身以外への返信 if (note.replyId && note.replyUserId !== note.userId) { - this.redisTimelineService.push(`userTimelineWithReplies:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax : meta.perRemoteUserUserTimelineCacheMax, r); + this.redisTimelineService.push(`userTimelineWithReplies:${user.id}`, note.id, 300, r); } else { - this.redisTimelineService.push(`userTimeline:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax : meta.perRemoteUserUserTimelineCacheMax, r); + this.redisTimelineService.push(`userTimeline:${user.id}`, note.id, 300, r); if (note.fileIds.length > 0) { - this.redisTimelineService.push(`userTimelineWithFiles:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax / 2 : meta.perRemoteUserUserTimelineCacheMax / 2, r); + this.redisTimelineService.push(`userTimelineWithFiles:${user.id}`, note.id, 300 / 2, r); } if (note.visibility === 'public' && note.userHost == null) { From ab3c01872285501699400a26d18f1c97c8f3b7c2 Mon Sep 17 00:00:00 2001 From: syuilo Date: Wed, 11 Oct 2023 10:15:44 +0900 Subject: [PATCH 11/22] =?UTF-8?q?enhance:=20=E3=83=AD=E3=83=BC=E3=82=AB?= =?UTF-8?q?=E3=83=AB=E3=82=BF=E3=82=A4=E3=83=A0=E3=83=A9=E3=82=A4=E3=83=B3?= =?UTF-8?q?=E3=80=81=E3=82=BD=E3=83=BC=E3=82=B7=E3=83=A3=E3=83=AB=E3=82=BF?= =?UTF-8?q?=E3=82=A4=E3=83=A0=E3=83=A9=E3=82=A4=E3=83=B3=E3=81=A7=E8=BF=94?= =?UTF-8?q?=E4=BF=A1=E3=82=92=E5=90=AB=E3=82=80=E3=81=8B=E3=81=A9=E3=81=86?= =?UTF-8?q?=E3=81=8B=E8=A8=AD=E5=AE=9A=E5=8F=AF=E8=83=BD=E3=81=AB=20(redis?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E3=81=AE=E3=81=BF)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Picks 7a8d5e58400a51218d673211219111238a8b7ae8 --- packages/backend/src/core/NoteCreateService.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts index 042a08955014..1d73acc9ef2a 100644 --- a/packages/backend/src/core/NoteCreateService.ts +++ b/packages/backend/src/core/NoteCreateService.ts @@ -925,6 +925,10 @@ export class NoteCreateService implements OnApplicationShutdown { // 自分自身以外への返信 if (note.replyId && note.replyUserId !== note.userId) { this.redisTimelineService.push(`userTimelineWithReplies:${user.id}`, note.id, 300, r); + + if (note.visibility === 'public' && note.userHost == null) { + this.redisTimelineService.push('localTimelineWithReplies', note.id, 300, r); + } } else { this.redisTimelineService.push(`userTimeline:${user.id}`, note.id, 300, r); if (note.fileIds.length > 0) { From 7a0125f613e9dff0366a4b701e74d8142358443a Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Thu, 12 Oct 2023 11:05:06 +0900 Subject: [PATCH 12/22] revert: adding withReplies, isHibernated, redisForTimelines, and user_list_joining -> user_list_membership --- .config/docker_example.yml | 8 -- .config/example.yml | 10 --- .devcontainer/devcontainer.yml | 8 -- chart/files/default.yml | 8 -- packages/backend/jest.config.cjs | 2 - .../migration/1696222183852-withReplies.js | 20 ----- .../1696323464251-user-list-membership.js | 11 --- .../migration/1696331570827-hibernation.js | 17 ---- packages/backend/src/GlobalModule.ts | 14 +--- packages/backend/src/config.ts | 5 +- .../backend/src/core/AccountMoveService.ts | 32 ++++---- packages/backend/src/core/AntennaService.ts | 14 ++-- packages/backend/src/core/CacheService.ts | 2 +- packages/backend/src/core/CoreModule.ts | 6 -- .../backend/src/core/NoteCreateService.ts | 62 +++------------ .../backend/src/core/NotificationService.ts | 8 +- .../backend/src/core/RedisTimelineService.ts | 18 ++--- .../backend/src/core/UserBlockingService.ts | 8 +- .../backend/src/core/UserFollowingService.ts | 6 +- packages/backend/src/core/UserListService.ts | 36 +++------ packages/backend/src/core/UserService.ts | 53 ------------- .../src/core/entities/NoteEntityService.ts | 6 +- .../src/core/entities/UserEntityService.ts | 1 - .../core/entities/UserListEntityService.ts | 24 +----- packages/backend/src/di-symbols.ts | 3 +- packages/backend/src/models/Following.ts | 12 --- .../backend/src/models/RepositoryModule.ts | 12 +-- packages/backend/src/models/User.ts | 5 -- ...erListMembership.ts => UserListJoining.ts} | 6 +- packages/backend/src/models/_.ts | 6 +- .../backend/src/models/json-schema/user.ts | 4 - packages/backend/src/postgres.ts | 4 +- .../ExportAntennasProcessorService.ts | 10 +-- .../ExportUserListsProcessorService.ts | 10 +-- .../ImportUserListsProcessorService.ts | 8 +- .../backend/src/server/api/EndpointsModule.ts | 16 +--- .../server/api/StreamingApiServerService.ts | 10 ++- packages/backend/src/server/api/endpoints.ts | 8 +- .../server/api/endpoints/antennas/notes.ts | 2 +- .../server/api/endpoints/following/update.ts | 6 +- .../api/endpoints/notes/user-list-timeline.ts | 8 +- .../src/server/api/endpoints/roles/notes.ts | 6 +- .../users/lists/create-from-public.ts | 10 +-- .../endpoints/users/lists/get-memberships.ts | 79 ------------------- .../server/api/endpoints/users/lists/push.ts | 8 +- .../users/lists/update-membership.ts | 79 ------------------- .../server/api/stream/channels/user-list.ts | 12 +-- 47 files changed, 138 insertions(+), 565 deletions(-) delete mode 100644 packages/backend/migration/1696222183852-withReplies.js delete mode 100644 packages/backend/migration/1696323464251-user-list-membership.js delete mode 100644 packages/backend/migration/1696331570827-hibernation.js delete mode 100644 packages/backend/src/core/UserService.ts rename packages/backend/src/models/{UserListMembership.ts => UserListJoining.ts} (90%) delete mode 100644 packages/backend/src/server/api/endpoints/users/lists/get-memberships.ts delete mode 100644 packages/backend/src/server/api/endpoints/users/lists/update-membership.ts diff --git a/.config/docker_example.yml b/.config/docker_example.yml index 29217462958c..940b095fe29c 100644 --- a/.config/docker_example.yml +++ b/.config/docker_example.yml @@ -95,14 +95,6 @@ redis: # #prefix: example-prefix # #db: 1 -#redisForTimelines: -# host: redis -# port: 6379 -# #family: 0 # 0=Both, 4=IPv4, 6=IPv6 -# #pass: example-pass -# #prefix: example-prefix -# #db: 1 - # ┌───────────────────────────┐ #───┘ MeiliSearch configuration └───────────────────────────── diff --git a/.config/example.yml b/.config/example.yml index 0e4f2f5a15cd..03864a32994f 100644 --- a/.config/example.yml +++ b/.config/example.yml @@ -105,16 +105,6 @@ redis: # # You can specify more ioredis options... # #username: example-username -#redisForTimelines: -# host: localhost -# port: 6379 -# #family: 0 # 0=Both, 4=IPv4, 6=IPv6 -# #pass: example-pass -# #prefix: example-prefix -# #db: 1 -# # You can specify more ioredis options... -# #username: example-username - # ┌───────────────────────────┐ #───┘ MeiliSearch configuration └───────────────────────────── diff --git a/.devcontainer/devcontainer.yml b/.devcontainer/devcontainer.yml index 3d57d1245daa..5dcd41599acd 100644 --- a/.devcontainer/devcontainer.yml +++ b/.devcontainer/devcontainer.yml @@ -95,14 +95,6 @@ redis: # #prefix: example-prefix # #db: 1 -#redisForTimelines: -# host: redis -# port: 6379 -# #family: 0 # 0=Both, 4=IPv4, 6=IPv6 -# #pass: example-pass -# #prefix: example-prefix -# #db: 1 - # ┌───────────────────────────┐ #───┘ MeiliSearch configuration └───────────────────────────── diff --git a/chart/files/default.yml b/chart/files/default.yml index 87b2f677ebe2..90b574b99f40 100644 --- a/chart/files/default.yml +++ b/chart/files/default.yml @@ -116,14 +116,6 @@ redis: # #prefix: example-prefix # #db: 1 -#redisForTimelines: -# host: redis -# port: 6379 -# #family: 0 # 0=Both, 4=IPv4, 6=IPv6 -# #pass: example-pass -# #prefix: example-prefix -# #db: 1 - # ┌───────────────────────────┐ #───┘ MeiliSearch configuration └───────────────────────────── diff --git a/packages/backend/jest.config.cjs b/packages/backend/jest.config.cjs index 97d777c86287..6b1afec73492 100644 --- a/packages/backend/jest.config.cjs +++ b/packages/backend/jest.config.cjs @@ -216,6 +216,4 @@ module.exports = { maxWorkers: 1, // Make it use worker (that can be killed and restarted) logHeapUsage: true, // To debug when out-of-memory happens on CI workerIdleMemoryLimit: '1GiB', // Limit the worker to 1GB (GitHub Workflows dies at 2GB) - - maxConcurrency: 32, }; diff --git a/packages/backend/migration/1696222183852-withReplies.js b/packages/backend/migration/1696222183852-withReplies.js deleted file mode 100644 index a2fd6aef053b..000000000000 --- a/packages/backend/migration/1696222183852-withReplies.js +++ /dev/null @@ -1,20 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and other misskey contributors - * SPDX-License-Identifier: AGPL-3.0-only - */ - -export class WithReplies1696222183852 { - name = 'WithReplies1696222183852' - - async up(queryRunner) { - await queryRunner.query(`ALTER TABLE "following" ADD "withReplies" boolean NOT NULL DEFAULT false`); - await queryRunner.query(`ALTER TABLE "user_list_joining" ADD "withReplies" boolean NOT NULL DEFAULT true`); - await queryRunner.query(`CREATE INDEX "IDX_d74d8ab5efa7e3bb82825c0fa2" ON "following" ("followeeId", "followerHost") `); - } - - async down(queryRunner) { - await queryRunner.query(`DROP INDEX "public"."IDX_d74d8ab5efa7e3bb82825c0fa2"`); - await queryRunner.query(`ALTER TABLE "user_list_joining" DROP COLUMN "withReplies"`); - await queryRunner.query(`ALTER TABLE "following" DROP COLUMN "withReplies"`); - } -} diff --git a/packages/backend/migration/1696323464251-user-list-membership.js b/packages/backend/migration/1696323464251-user-list-membership.js deleted file mode 100644 index 7534040c4c9b..000000000000 --- a/packages/backend/migration/1696323464251-user-list-membership.js +++ /dev/null @@ -1,11 +0,0 @@ -export class UserListMembership1696323464251 { - name = 'UserListMembership1696323464251' - - async up(queryRunner) { - await queryRunner.query(`ALTER TABLE "user_list_joining" RENAME TO "user_list_membership"`); - } - - async down(queryRunner) { - await queryRunner.query(`ALTER TABLE "user_list_membership" RENAME TO "user_list_joining"`); - } -} diff --git a/packages/backend/migration/1696331570827-hibernation.js b/packages/backend/migration/1696331570827-hibernation.js deleted file mode 100644 index 119d35913f7b..000000000000 --- a/packages/backend/migration/1696331570827-hibernation.js +++ /dev/null @@ -1,17 +0,0 @@ -export class Hibernation1696331570827 { - name = 'Hibernation1696331570827' - - async up(queryRunner) { - await queryRunner.query(`DROP INDEX "public"."IDX_d74d8ab5efa7e3bb82825c0fa2"`); - await queryRunner.query(`ALTER TABLE "user" ADD "isHibernated" boolean NOT NULL DEFAULT false`); - await queryRunner.query(`ALTER TABLE "following" ADD "isFollowerHibernated" boolean NOT NULL DEFAULT false`); - await queryRunner.query(`CREATE INDEX "IDX_ce62b50d882d4e9dee10ad0d2f" ON "following" ("followeeId", "followerHost", "isFollowerHibernated") `); - } - - async down(queryRunner) { - await queryRunner.query(`DROP INDEX "public"."IDX_ce62b50d882d4e9dee10ad0d2f"`); - await queryRunner.query(`ALTER TABLE "following" DROP COLUMN "isFollowerHibernated"`); - await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "isHibernated"`); - await queryRunner.query(`CREATE INDEX "IDX_d74d8ab5efa7e3bb82825c0fa2" ON "following" ("followeeId", "followerHost") `); - } -} diff --git a/packages/backend/src/GlobalModule.ts b/packages/backend/src/GlobalModule.ts index 3e9d19f82598..9f1ee9fcaa3f 100644 --- a/packages/backend/src/GlobalModule.ts +++ b/packages/backend/src/GlobalModule.ts @@ -70,19 +70,11 @@ const $redisForSub: Provider = { inject: [DI.config], }; -const $redisForTimelines: Provider = { - provide: DI.redisForTimelines, - useFactory: (config: Config) => { - return new Redis.Redis(config.redisForTimelines); - }, - inject: [DI.config], -}; - @Global() @Module({ imports: [RepositoryModule], - providers: [$config, $db, $meilisearch, $redis, $redisForPub, $redisForSub, $redisForTimelines], - exports: [$config, $db, $meilisearch, $redis, $redisForPub, $redisForSub, $redisForTimelines, RepositoryModule], + providers: [$config, $db, $meilisearch, $redis, $redisForPub, $redisForSub], + exports: [$config, $db, $meilisearch, $redis, $redisForPub, $redisForSub, RepositoryModule], }) export class GlobalModule implements OnApplicationShutdown { constructor( @@ -90,7 +82,6 @@ export class GlobalModule implements OnApplicationShutdown { @Inject(DI.redis) private redisClient: Redis.Redis, @Inject(DI.redisForPub) private redisForPub: Redis.Redis, @Inject(DI.redisForSub) private redisForSub: Redis.Redis, - @Inject(DI.redisForTimelines) private redisForTimelines: Redis.Redis, ) {} public async dispose(): Promise { @@ -107,7 +98,6 @@ export class GlobalModule implements OnApplicationShutdown { this.redisClient.disconnect(), this.redisForPub.disconnect(), this.redisForSub.disconnect(), - this.redisForTimelines.disconnect(), ]); } diff --git a/packages/backend/src/config.ts b/packages/backend/src/config.ts index 45367b8e5e61..81fbf4de5561 100644 --- a/packages/backend/src/config.ts +++ b/packages/backend/src/config.ts @@ -47,7 +47,6 @@ type Source = { redis: RedisOptionsSource; redisForPubsub?: RedisOptionsSource; redisForJobQueue?: RedisOptionsSource; - redisForTimelines?: RedisOptionsSource; meilisearch?: { host: string; port: string; @@ -96,6 +95,7 @@ type Source = { abuseDiscordHook?: string; disableAbuseRepository?: boolean; maxWebImageSize?: number; + withRepliesInHomeTL?: boolean; } }; @@ -168,7 +168,6 @@ export type Config = { redis: RedisOptions & RedisOptionsSource; redisForPubsub: RedisOptions & RedisOptionsSource; redisForJobQueue: RedisOptions & RedisOptionsSource; - redisForTimelines: RedisOptions & RedisOptionsSource; perChannelMaxNoteCacheCount: number; perUserNotificationsMaxCount: number; deactivateAntennaThreshold: number; @@ -178,6 +177,7 @@ export type Config = { abuseDiscordHook?: string; disableAbuseRepository?: boolean; maxWebImageSize?: number; + withRepliesInHomeTL?: boolean, } }; @@ -243,7 +243,6 @@ export function loadConfig(): Config { redis, redisForPubsub: config.redisForPubsub ? convertRedisOptions(config.redisForPubsub, host) : redis, redisForJobQueue: config.redisForJobQueue ? convertRedisOptions(config.redisForJobQueue, host) : redis, - redisForTimelines: config.redisForTimelines ? convertRedisOptions(config.redisForTimelines, host) : redis, id: config.id, proxy: config.proxy, proxySmtp: config.proxySmtp, diff --git a/packages/backend/src/core/AccountMoveService.ts b/packages/backend/src/core/AccountMoveService.ts index db64f42754cc..17f6f3169edd 100644 --- a/packages/backend/src/core/AccountMoveService.ts +++ b/packages/backend/src/core/AccountMoveService.ts @@ -9,7 +9,7 @@ import { IsNull, In, MoreThan, Not } from 'typeorm'; import { bindThis } from '@/decorators.js'; import { DI } from '@/di-symbols.js'; import type { MiLocalUser, MiRemoteUser, MiUser } from '@/models/User.js'; -import type { BlockingsRepository, FollowingsRepository, InstancesRepository, MutingsRepository, UserListMembershipsRepository, UsersRepository } from '@/models/_.js'; +import type { BlockingsRepository, FollowingsRepository, InstancesRepository, MutingsRepository, UserListJoiningsRepository, UsersRepository } from '@/models/_.js'; import type { RelationshipJobData, ThinUser } from '@/queue/types.js'; import { IdService } from '@/core/IdService.js'; @@ -42,8 +42,8 @@ export class AccountMoveService { @Inject(DI.mutingsRepository) private mutingsRepository: MutingsRepository, - @Inject(DI.userListMembershipsRepository) - private userListMembershipsRepository: UserListMembershipsRepository, + @Inject(DI.userListJoiningsRepository) + private userListJoiningsRepository: UserListJoiningsRepository, @Inject(DI.instancesRepository) private instancesRepository: InstancesRepository, @@ -215,41 +215,41 @@ export class AccountMoveService { @bindThis public async updateLists(src: ThinUser, dst: MiUser): Promise { // Return if there is no list to be updated. - const oldMemberships = await this.userListMembershipsRepository.find({ + const oldJoinings = await this.userListJoiningsRepository.find({ where: { userId: src.id, }, }); - if (oldMemberships.length === 0) return; + if (oldJoinings.length === 0) return; - const existingUserListIds = await this.userListMembershipsRepository.find({ + const existingUserListIds = await this.userListJoiningsRepository.find({ where: { userId: dst.id, }, - }).then(memberships => memberships.map(membership => membership.userListId)); + }).then(joinings => joinings.map(joining => joining.userListId)); - const newMemberships: Map = new Map(); + const newJoinings: Map = new Map(); // 重複しないようにIDを生成 const genId = (): string => { let id: string; do { id = this.idService.genId(); - } while (newMemberships.has(id)); + } while (newJoinings.has(id)); return id; }; - for (const membership of oldMemberships) { - if (existingUserListIds.includes(membership.userListId)) continue; // skip if dst exists in this user's list - newMemberships.set(genId(), { + for (const joining of oldJoinings) { + if (existingUserListIds.includes(joining.userListId)) continue; // skip if dst exists in this user's list + newJoinings.set(genId(), { createdAt: new Date(), userId: dst.id, - userListId: membership.userListId, - userListUserId: membership.userListUserId, + userListId: joining.userListId, + userListUserId: joining.userListUserId, }); } - const arrayToInsert = Array.from(newMemberships.entries()).map(entry => ({ ...entry[1], id: entry[0] })); - await this.userListMembershipsRepository.insert(arrayToInsert); + const arrayToInsert = Array.from(newJoinings.entries()).map(entry => ({ ...entry[1], id: entry[0] })); + await this.userListJoiningsRepository.insert(arrayToInsert); // Have the proxy account follow the new account in the same way as UserListService.push if (this.userEntityService.isRemoteUser(dst)) { diff --git a/packages/backend/src/core/AntennaService.ts b/packages/backend/src/core/AntennaService.ts index 7bb50ab6451a..0e456d7f0ee6 100644 --- a/packages/backend/src/core/AntennaService.ts +++ b/packages/backend/src/core/AntennaService.ts @@ -12,7 +12,7 @@ import { GlobalEventService } from '@/core/GlobalEventService.js'; import * as Acct from '@/misc/acct.js'; import type { Packed } from '@/misc/json-schema.js'; import { DI } from '@/di-symbols.js'; -import type { AntennasRepository, UserListMembershipsRepository } from '@/models/_.js'; +import type { AntennasRepository, UserListJoiningsRepository } from '@/models/_.js'; import { UtilityService } from '@/core/UtilityService.js'; import { bindThis } from '@/decorators.js'; import type { GlobalEvents } from '@/core/GlobalEventService.js'; @@ -25,8 +25,8 @@ export class AntennaService implements OnApplicationShutdown { private antennas: MiAntenna[]; constructor( - @Inject(DI.redisForTimelines) - private redisForTimelines: Redis.Redis, + @Inject(DI.redis) + private redisClient: Redis.Redis, @Inject(DI.redisForSub) private redisForSub: Redis.Redis, @@ -34,8 +34,8 @@ export class AntennaService implements OnApplicationShutdown { @Inject(DI.antennasRepository) private antennasRepository: AntennasRepository, - @Inject(DI.userListMembershipsRepository) - private userListMembershipsRepository: UserListMembershipsRepository, + @Inject(DI.userListJoiningsRepository) + private userListJoiningsRepository: UserListJoiningsRepository, private utilityService: UtilityService, private globalEventService: GlobalEventService, @@ -83,7 +83,7 @@ export class AntennaService implements OnApplicationShutdown { const antennasWithMatchResult = await Promise.all(antennas.map(antenna => this.checkHitAntenna(antenna, note, noteUser).then(hit => [antenna, hit] as const))); const matchedAntennas = antennasWithMatchResult.filter(([, hit]) => hit).map(([antenna]) => antenna); - const redisPipeline = this.redisForTimelines.pipeline(); + const redisPipeline = this.redisClient.pipeline(); for (const antenna of matchedAntennas) { redisPipeline.xadd( @@ -110,7 +110,7 @@ export class AntennaService implements OnApplicationShutdown { if (antenna.src === 'home') { // TODO } else if (antenna.src === 'list') { - const listUsers = (await this.userListMembershipsRepository.findBy({ + const listUsers = (await this.userListJoiningsRepository.findBy({ userListId: antenna.userListId!, })).map(x => x.userId); diff --git a/packages/backend/src/core/CacheService.ts b/packages/backend/src/core/CacheService.ts index bf821326f2a5..561979c4bf14 100644 --- a/packages/backend/src/core/CacheService.ts +++ b/packages/backend/src/core/CacheService.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import * as Redis from 'ioredis'; -import type { BlockingsRepository, ChannelFollowingsRepository, FollowingsRepository, MutingsRepository, RenoteMutingsRepository, MiUserProfile, UserProfilesRepository, UsersRepository, MiFollowing } from '@/models/_.js'; +import type { BlockingsRepository, ChannelFollowingsRepository, FollowingsRepository, MutingsRepository, RenoteMutingsRepository, MiUserProfile, UserProfilesRepository, UsersRepository } from '@/models/_.js'; import { MemoryKVCache, RedisKVCache } from '@/misc/cache.js'; import type { MiLocalUser, MiUser } from '@/models/User.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/core/CoreModule.ts b/packages/backend/src/core/CoreModule.ts index 8298bae9b5d4..3d8d64786126 100644 --- a/packages/backend/src/core/CoreModule.ts +++ b/packages/backend/src/core/CoreModule.ts @@ -46,7 +46,6 @@ import { SignupService } from './SignupService.js'; import { WebAuthnService } from './WebAuthnService.js'; import { UserBlockingService } from './UserBlockingService.js'; import { CacheService } from './CacheService.js'; -import { UserService } from './UserService.js'; import { UserFollowingService } from './UserFollowingService.js'; import { UserKeypairService } from './UserKeypairService.js'; import { UserListService } from './UserListService.js'; @@ -176,7 +175,6 @@ const $SignupService: Provider = { provide: 'SignupService', useExisting: Signup const $WebAuthnService: Provider = { provide: 'WebAuthnService', useExisting: WebAuthnService }; const $UserBlockingService: Provider = { provide: 'UserBlockingService', useExisting: UserBlockingService }; const $CacheService: Provider = { provide: 'CacheService', useExisting: CacheService }; -const $UserService: Provider = { provide: 'UserService', useExisting: UserService }; const $UserFollowingService: Provider = { provide: 'UserFollowingService', useExisting: UserFollowingService }; const $UserKeypairService: Provider = { provide: 'UserKeypairService', useExisting: UserKeypairService }; const $UserListService: Provider = { provide: 'UserListService', useExisting: UserListService }; @@ -308,7 +306,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting WebAuthnService, UserBlockingService, CacheService, - UserService, UserFollowingService, UserKeypairService, UserListService, @@ -434,7 +431,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting $WebAuthnService, $UserBlockingService, $CacheService, - $UserService, $UserFollowingService, $UserKeypairService, $UserListService, @@ -560,7 +556,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting WebAuthnService, UserBlockingService, CacheService, - UserService, UserFollowingService, UserKeypairService, UserListService, @@ -685,7 +680,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting $WebAuthnService, $UserBlockingService, $CacheService, - $UserService, $UserFollowingService, $UserKeypairService, $UserListService, diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts index 1d73acc9ef2a..ad20fbec81d6 100644 --- a/packages/backend/src/core/NoteCreateService.ts +++ b/packages/backend/src/core/NoteCreateService.ts @@ -14,7 +14,7 @@ import { extractCustomEmojisFromMfm } from '@/misc/extract-custom-emojis-from-mf import { extractHashtags } from '@/misc/extract-hashtags.js'; import type { IMentionedRemoteUsers } from '@/models/Note.js'; import { MiNote } from '@/models/Note.js'; -import type { ChannelFollowingsRepository, ChannelsRepository, FollowingsRepository, InstancesRepository, MutedNotesRepository, MiFollowing, MutingsRepository, NotesRepository, NoteThreadMutingsRepository, UserListMembershipsRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js'; +import type { ChannelFollowingsRepository, ChannelsRepository, FollowingsRepository, InstancesRepository, MutedNotesRepository, MiFollowing, MutingsRepository, NotesRepository, NoteThreadMutingsRepository, UserListJoiningsRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js'; import type { MiDriveFile } from '@/models/DriveFile.js'; import type { MiApp } from '@/models/App.js'; import { concat } from '@/misc/prelude/array.js'; @@ -158,8 +158,8 @@ export class NoteCreateService implements OnApplicationShutdown { @Inject(DI.db) private db: DataSource, - @Inject(DI.redisForTimelines) - private redisForTimelines: Redis.Redis, + @Inject(DI.redis) + private redisClient: Redis.Redis, @Inject(DI.usersRepository) private usersRepository: UsersRepository, @@ -176,8 +176,8 @@ export class NoteCreateService implements OnApplicationShutdown { @Inject(DI.userProfilesRepository) private userProfilesRepository: UserProfilesRepository, - @Inject(DI.userListMembershipsRepository) - private userListMembershipsRepository: UserListMembershipsRepository, + @Inject(DI.userListJoiningsRepository) + private userListJoiningsRepository: UserListJoiningsRepository, @Inject(DI.mutedNotesRepository) private mutedNotesRepository: MutedNotesRepository, @@ -353,7 +353,7 @@ export class NoteCreateService implements OnApplicationShutdown { const note = await this.insertNote(user, data, tags, emojis, mentionedUsers); if (data.channel) { - this.redisForTimelines.xadd( + this.redisClient.xadd( `channelTimeline:${data.channel.id}`, 'MAXLEN', '~', this.config.perChannelMaxNoteCacheCount.toString(), '*', @@ -836,7 +836,7 @@ export class NoteCreateService implements OnApplicationShutdown { private async pushToTl(note: MiNote, user: { id: MiUser['id']; host: MiUser['host']; }) { const meta = await this.metaService.fetch(); - const r = this.redisForTimelines.pipeline(); + const r = this.redisClient.pipeline(); if (note.channelId) { this.redisTimelineService.push(`channelTimeline:${note.channelId}`, note.id, this.config.perChannelMaxNoteCacheCount, r); @@ -864,11 +864,10 @@ export class NoteCreateService implements OnApplicationShutdown { where: { followeeId: user.id, followerHost: IsNull(), - isFollowerHibernated: false, }, - select: ['followerId', 'withReplies'], + select: ['followerId'], }), - this.userListMembershipsRepository.find({ + this.userListJoiningsRepository.find({ where: { userId: user.id, }, @@ -888,7 +887,7 @@ export class NoteCreateService implements OnApplicationShutdown { // 自分自身以外への返信 if (note.replyId && note.replyUserId !== note.userId) { - if (!following.withReplies) continue; + if (!this.config.nirila.withRepliesInHomeTL) continue; } this.redisTimelineService.push(`homeTimeline:${following.followerId}`, note.id, 300, r); @@ -944,53 +943,12 @@ export class NoteCreateService implements OnApplicationShutdown { } if (Math.random() < 0.1) { - process.nextTick(() => { - this.checkHibernation(followings); - }); } } r.exec(); } - @bindThis - public async checkHibernation(followings: MiFollowing[]) { - if (followings.length === 0) return; - - const shuffle = (array: MiFollowing[]) => { - for (let i = array.length - 1; i > 0; i--) { - const j = Math.floor(Math.random() * (i + 1)); - [array[i], array[j]] = [array[j], array[i]]; - } - return array; - }; - - // ランダムに最大1000件サンプリング - const samples = shuffle(followings).slice(0, Math.min(followings.length, 1000)); - - const hibernatedUsers = await this.usersRepository.find({ - where: { - id: In(samples.map(x => x.followerId)), - lastActiveDate: LessThan(new Date(Date.now() - (1000 * 60 * 60 * 24 * 50))), - }, - select: ['id'], - }); - - if (hibernatedUsers.length > 0) { - this.usersRepository.update({ - id: In(hibernatedUsers.map(x => x.id)), - }, { - isHibernated: true, - }); - - this.followingsRepository.update({ - followerId: In(hibernatedUsers.map(x => x.id)), - }, { - isFollowerHibernated: true, - }); - } - } - @bindThis public dispose(): void { this.#shutdownController.abort(); diff --git a/packages/backend/src/core/NotificationService.ts b/packages/backend/src/core/NotificationService.ts index 32d54d257688..ca05989a4a67 100644 --- a/packages/backend/src/core/NotificationService.ts +++ b/packages/backend/src/core/NotificationService.ts @@ -99,19 +99,19 @@ export class NotificationService implements OnApplicationShutdown { } if (recieveConfig?.type === 'following') { - const isFollowing = await this.cacheService.userFollowingsCache.fetch(notifieeId).then(followings => Object.hasOwn(followings, notifierId)); + const isFollowing = await this.cacheService.userFollowingsCache.fetch(notifieeId).then(followings => followings.has(notifierId)); if (!isFollowing) { return null; } } else if (recieveConfig?.type === 'follower') { - const isFollower = await this.cacheService.userFollowingsCache.fetch(notifierId).then(followings => Object.hasOwn(followings, notifieeId)); + const isFollower = await this.cacheService.userFollowingsCache.fetch(notifierId).then(followings => followings.has(notifieeId)); if (!isFollower) { return null; } } else if (recieveConfig?.type === 'mutualFollow') { const [isFollowing, isFollower] = await Promise.all([ - this.cacheService.userFollowingsCache.fetch(notifieeId).then(followings => Object.hasOwn(followings, notifierId)), - this.cacheService.userFollowingsCache.fetch(notifierId).then(followings => Object.hasOwn(followings, notifieeId)), + this.cacheService.userFollowingsCache.fetch(notifieeId).then(followings => followings.has(notifierId)), + this.cacheService.userFollowingsCache.fetch(notifierId).then(followings => followings.has(notifieeId)), ]); if (!isFollowing && !isFollower) { return null; diff --git a/packages/backend/src/core/RedisTimelineService.ts b/packages/backend/src/core/RedisTimelineService.ts index 94541759cc08..bff134a93b4b 100644 --- a/packages/backend/src/core/RedisTimelineService.ts +++ b/packages/backend/src/core/RedisTimelineService.ts @@ -12,8 +12,8 @@ import { IdService } from '@/core/IdService.js'; @Injectable() export class RedisTimelineService { constructor( - @Inject(DI.redisForTimelines) - private redisForTimelines: Redis.Redis, + @Inject(DI.redis) + private redisClient: Redis.Redis, private idService: IdService, ) { @@ -30,9 +30,9 @@ export class RedisTimelineService { } } else { // 末尾のIDを取得 - this.redisForTimelines.lindex('list:' + tl, -1).then(lastId => { + this.redisClient.lindex('list:' + tl, -1).then(lastId => { if (lastId == null || (this.idService.parse(id).date.getTime() > this.idService.parse(lastId).date.getTime())) { - this.redisForTimelines.lpush('list:' + tl, id); + this.redisClient.lpush('list:' + tl, id); } else { Promise.resolve(); } @@ -43,23 +43,23 @@ export class RedisTimelineService { @bindThis public get(name: string, untilId?: string | null, sinceId?: string | null) { if (untilId && sinceId) { - return this.redisForTimelines.lrange('list:' + name, 0, -1) + return this.redisClient.lrange('list:' + name, 0, -1) .then(ids => ids.filter(id => id < untilId && id > sinceId).sort((a, b) => a > b ? -1 : 1)); } else if (untilId) { - return this.redisForTimelines.lrange('list:' + name, 0, -1) + return this.redisClient.lrange('list:' + name, 0, -1) .then(ids => ids.filter(id => id < untilId).sort((a, b) => a > b ? -1 : 1)); } else if (sinceId) { - return this.redisForTimelines.lrange('list:' + name, 0, -1) + return this.redisClient.lrange('list:' + name, 0, -1) .then(ids => ids.filter(id => id > sinceId).sort((a, b) => a < b ? -1 : 1)); } else { - return this.redisForTimelines.lrange('list:' + name, 0, -1) + return this.redisClient.lrange('list:' + name, 0, -1) .then(ids => ids.sort((a, b) => a > b ? -1 : 1)); } } @bindThis public getMulti(name: string[], untilId?: string | null, sinceId?: string | null): Promise { - const pipeline = this.redisForTimelines.pipeline(); + const pipeline = this.redisClient.pipeline(); for (const n of name) { pipeline.lrange('list:' + n, 0, -1); } diff --git a/packages/backend/src/core/UserBlockingService.ts b/packages/backend/src/core/UserBlockingService.ts index 087dfd92147c..37031e341e4f 100644 --- a/packages/backend/src/core/UserBlockingService.ts +++ b/packages/backend/src/core/UserBlockingService.ts @@ -11,7 +11,7 @@ import type { MiBlocking } from '@/models/Blocking.js'; import { QueueService } from '@/core/QueueService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; -import type { FollowRequestsRepository, BlockingsRepository, UserListsRepository, UserListMembershipsRepository } from '@/models/_.js'; +import type { FollowRequestsRepository, BlockingsRepository, UserListsRepository, UserListJoiningsRepository } from '@/models/_.js'; import Logger from '@/logger.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { ApRendererService } from '@/core/activitypub/ApRendererService.js'; @@ -38,8 +38,8 @@ export class UserBlockingService implements OnModuleInit { @Inject(DI.userListsRepository) private userListsRepository: UserListsRepository, - @Inject(DI.userListMembershipsRepository) - private userListMembershipsRepository: UserListMembershipsRepository, + @Inject(DI.userListJoiningsRepository) + private userListJoiningsRepository: UserListJoiningsRepository, private cacheService: CacheService, private userEntityService: UserEntityService, @@ -149,7 +149,7 @@ export class UserBlockingService implements OnModuleInit { }); for (const userList of userLists) { - await this.userListMembershipsRepository.delete({ + await this.userListJoiningsRepository.delete({ userListId: userList.id, userId: user.id, }); diff --git a/packages/backend/src/core/UserFollowingService.ts b/packages/backend/src/core/UserFollowingService.ts index beffcc2e9c97..230f6ef261b8 100644 --- a/packages/backend/src/core/UserFollowingService.ts +++ b/packages/backend/src/core/UserFollowingService.ts @@ -123,11 +123,7 @@ export class UserFollowingService implements OnModuleInit { // フォロワーがBotであり、フォロー対象がBotからのフォローに慎重である or // フォロワーがローカルユーザーであり、フォロー対象がリモートユーザーである // 上記のいずれかに当てはまる場合はすぐフォローせずにフォローリクエストを発行しておく - if ( - followee.isLocked || - (followeeProfile.carefulBot && follower.isBot) || - (this.userEntityService.isLocalUser(follower) && this.userEntityService.isRemoteUser(followee) && process.env.FORCE_FOLLOW_REMOTE_USER_FOR_TESTING !== 'true') - ) { + if (followee.isLocked || (followeeProfile.carefulBot && follower.isBot) || (this.userEntityService.isLocalUser(follower) && this.userEntityService.isRemoteUser(followee))) { let autoAccept = false; // 鍵アカウントであっても、既にフォローされていた場合はスルー diff --git a/packages/backend/src/core/UserListService.ts b/packages/backend/src/core/UserListService.ts index 5b4e7a711eca..98f32fbbfb5d 100644 --- a/packages/backend/src/core/UserListService.ts +++ b/packages/backend/src/core/UserListService.ts @@ -5,10 +5,10 @@ import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common'; import * as Redis from 'ioredis'; -import type { UserListMembershipsRepository } from '@/models/_.js'; +import type { UserListJoiningsRepository } from '@/models/_.js'; import type { MiUser } from '@/models/User.js'; import type { MiUserList } from '@/models/UserList.js'; -import type { MiUserListMembership } from '@/models/UserListMembership.js'; +import type { MiUserListJoining } from '@/models/UserListJoining.js'; import { IdService } from '@/core/IdService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; @@ -33,8 +33,8 @@ export class UserListService implements OnApplicationShutdown { @Inject(DI.redisForSub) private redisForSub: Redis.Redis, - @Inject(DI.userListMembershipsRepository) - private userListMembershipsRepository: UserListMembershipsRepository, + @Inject(DI.userListJoiningsRepository) + private userListJoiningsRepository: UserListJoiningsRepository, private userEntityService: UserEntityService, private idService: IdService, @@ -46,7 +46,7 @@ export class UserListService implements OnApplicationShutdown { this.membersCache = new RedisKVCache>(this.redisClient, 'userListMembers', { lifetime: 1000 * 60 * 30, // 30m memoryCacheLifetime: 1000 * 60, // 1m - fetcher: (key) => this.userListMembershipsRepository.find({ where: { userListId: key }, select: ['userId'] }).then(xs => new Set(xs.map(x => x.userId))), + fetcher: (key) => this.userListJoiningsRepository.find({ where: { userListId: key }, select: ['userId'] }).then(xs => new Set(xs.map(x => x.userId))), toRedisConverter: (value) => JSON.stringify(Array.from(value)), fromRedisConverter: (value) => new Set(JSON.parse(value)), }); @@ -85,20 +85,20 @@ export class UserListService implements OnApplicationShutdown { @bindThis public async addMember(target: MiUser, list: MiUserList, me: MiUser) { - const currentCount = await this.userListMembershipsRepository.countBy({ + const currentCount = await this.userListJoiningsRepository.countBy({ userListId: list.id, }); if (currentCount > (await this.roleService.getUserPolicies(me.id)).userEachUserListsLimit) { throw new UserListService.TooManyUsersError(); } - await this.userListMembershipsRepository.insert({ + await this.userListJoiningsRepository.insert({ id: this.idService.genId(), createdAt: new Date(), userId: target.id, userListId: list.id, userListUserId: list.userId, - } as MiUserListMembership); + } as MiUserListJoining); this.globalEventService.publishInternalEvent('userListMemberAdded', { userListId: list.id, memberId: target.id }); this.globalEventService.publishUserListStream(list.id, 'userAdded', await this.userEntityService.pack(target)); @@ -114,7 +114,7 @@ export class UserListService implements OnApplicationShutdown { @bindThis public async removeMember(target: MiUser, list: MiUserList) { - await this.userListMembershipsRepository.delete({ + await this.userListJoiningsRepository.delete({ userId: target.id, userListId: list.id, }); @@ -123,24 +123,6 @@ export class UserListService implements OnApplicationShutdown { this.globalEventService.publishUserListStream(list.id, 'userRemoved', await this.userEntityService.pack(target)); } - @bindThis - public async updateMembership(target: MiUser, list: MiUserList, options: { withReplies?: boolean }) { - const membership = await this.userListMembershipsRepository.findOneBy({ - userId: target.id, - userListId: list.id, - }); - - if (membership == null) { - throw new Error('User is not a member of the list'); - } - - await this.userListMembershipsRepository.update({ - id: membership.id, - }, { - withReplies: options.withReplies, - }); - } - @bindThis public dispose(): void { this.redisForSub.off('message', this.onMessage); diff --git a/packages/backend/src/core/UserService.ts b/packages/backend/src/core/UserService.ts deleted file mode 100644 index d16e1be61513..000000000000 --- a/packages/backend/src/core/UserService.ts +++ /dev/null @@ -1,53 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and other misskey contributors - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import { Inject, Injectable } from '@nestjs/common'; -import type { FollowingsRepository, UsersRepository } from '@/models/_.js'; -import type { MiUser } from '@/models/User.js'; -import { DI } from '@/di-symbols.js'; -import { bindThis } from '@/decorators.js'; - -@Injectable() -export class UserService { - constructor( - @Inject(DI.usersRepository) - private usersRepository: UsersRepository, - - @Inject(DI.followingsRepository) - private followingsRepository: FollowingsRepository, - ) { - } - - @bindThis - public async updateLastActiveDate(user: MiUser): Promise { - if (user.isHibernated) { - const result = await this.usersRepository.createQueryBuilder().update() - .set({ - lastActiveDate: new Date(), - }) - .where('id = :id', { id: user.id }) - .returning('*') - .execute() - .then((response) => { - return response.raw[0]; - }); - const wokeUp = result.isHibernated; - if (wokeUp) { - this.usersRepository.update(user.id, { - isHibernated: false, - }); - this.followingsRepository.update({ - followerId: user.id, - }, { - isFollowerHibernated: false, - }); - } - } else { - this.usersRepository.update(user.id, { - lastActiveDate: new Date(), - }); - } - } -} diff --git a/packages/backend/src/core/entities/NoteEntityService.ts b/packages/backend/src/core/entities/NoteEntityService.ts index 824f8fa71d86..bf42e98ce0a9 100644 --- a/packages/backend/src/core/entities/NoteEntityService.ts +++ b/packages/backend/src/core/entities/NoteEntityService.ts @@ -98,13 +98,13 @@ export class NoteEntityService implements OnModuleInit { } else if (meId === packedNote.userId) { hide = false; } else if (packedNote.reply && (meId === packedNote.reply.userId)) { - // 自分の投稿に対するリプライ + // 自分の投稿に対するリプライ hide = false; } else if (packedNote.mentions && packedNote.mentions.some(id => meId === id)) { - // 自分へのメンション + // 自分へのメンション hide = false; } else { - // フォロワーかどうか + // フォロワーかどうか const isFollowing = await this.followingsRepository.exist({ where: { followeeId: packedNote.userId, diff --git a/packages/backend/src/core/entities/UserEntityService.ts b/packages/backend/src/core/entities/UserEntityService.ts index 171dda8fdc88..a47b3d51ac2b 100644 --- a/packages/backend/src/core/entities/UserEntityService.ts +++ b/packages/backend/src/core/entities/UserEntityService.ts @@ -487,7 +487,6 @@ export class UserEntityService implements OnModuleInit { isMuted: relation.isMuted, isRenoteMuted: relation.isRenoteMuted, notify: relation.following?.notify ?? 'none', - withReplies: relation.following?.withReplies ?? false, } : {}), } as Promiseable> as Promiseable>; diff --git a/packages/backend/src/core/entities/UserListEntityService.ts b/packages/backend/src/core/entities/UserListEntityService.ts index 06b6e852b146..a7f28851943f 100644 --- a/packages/backend/src/core/entities/UserListEntityService.ts +++ b/packages/backend/src/core/entities/UserListEntityService.ts @@ -5,12 +5,11 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import type { MiUserListMembership, UserListMembershipsRepository, UserListsRepository } from '@/models/_.js'; +import type { UserListJoiningsRepository, UserListsRepository } from '@/models/_.js'; import type { Packed } from '@/misc/json-schema.js'; import type { } from '@/models/Blocking.js'; import type { MiUserList } from '@/models/UserList.js'; import { bindThis } from '@/decorators.js'; -import { UserEntityService } from './UserEntityService.js'; @Injectable() export class UserListEntityService { @@ -18,10 +17,8 @@ export class UserListEntityService { @Inject(DI.userListsRepository) private userListsRepository: UserListsRepository, - @Inject(DI.userListMembershipsRepository) - private userListMembershipsRepository: UserListMembershipsRepository, - - private userEntityService: UserEntityService, + @Inject(DI.userListJoiningsRepository) + private userListJoiningsRepository: UserListJoiningsRepository, ) { } @@ -31,7 +28,7 @@ export class UserListEntityService { ): Promise> { const userList = typeof src === 'object' ? src : await this.userListsRepository.findOneByOrFail({ id: src }); - const users = await this.userListMembershipsRepository.findBy({ + const users = await this.userListJoiningsRepository.findBy({ userListId: userList.id, }); @@ -43,18 +40,5 @@ export class UserListEntityService { isPublic: userList.isPublic, }; } - - @bindThis - public async packMembershipsMany( - memberships: MiUserListMembership[], - ) { - return Promise.all(memberships.map(async x => ({ - id: x.id, - createdAt: x.createdAt.toISOString(), - userId: x.userId, - user: await this.userEntityService.pack(x.userId), - withReplies: x.withReplies, - }))); - } } diff --git a/packages/backend/src/di-symbols.ts b/packages/backend/src/di-symbols.ts index d992d9e2f50e..72ec98cebe89 100644 --- a/packages/backend/src/di-symbols.ts +++ b/packages/backend/src/di-symbols.ts @@ -10,7 +10,6 @@ export const DI = { redis: Symbol('redis'), redisForPub: Symbol('redisForPub'), redisForSub: Symbol('redisForSub'), - redisForTimelines: Symbol('redisForTimelines'), //#region Repositories usersRepository: Symbol('usersRepository'), @@ -31,7 +30,7 @@ export const DI = { userPublickeysRepository: Symbol('userPublickeysRepository'), userListsRepository: Symbol('userListsRepository'), userListFavoritesRepository: Symbol('userListFavoritesRepository'), - userListMembershipsRepository: Symbol('userListMembershipsRepository'), + userListJoiningsRepository: Symbol('userListJoiningsRepository'), userNotePiningsRepository: Symbol('userNotePiningsRepository'), userIpsRepository: Symbol('userIpsRepository'), usedUsernamesRepository: Symbol('usedUsernamesRepository'), diff --git a/packages/backend/src/models/Following.ts b/packages/backend/src/models/Following.ts index 607538b1e79c..8c9f965fadd4 100644 --- a/packages/backend/src/models/Following.ts +++ b/packages/backend/src/models/Following.ts @@ -9,7 +9,6 @@ import { MiUser } from './User.js'; @Entity('following') @Index(['followerId', 'followeeId'], { unique: true }) -@Index(['followeeId', 'followerHost', 'isFollowerHibernated']) export class MiFollowing { @PrimaryColumn(id()) public id: string; @@ -46,17 +45,6 @@ export class MiFollowing { @JoinColumn() public follower: MiUser | null; - @Column('boolean', { - default: false, - }) - public isFollowerHibernated: boolean; - - // タイムラインにその人のリプライまで含めるかどうか - @Column('boolean', { - default: false, - }) - public withReplies: boolean; - @Index() @Column('varchar', { length: 32, diff --git a/packages/backend/src/models/RepositoryModule.ts b/packages/backend/src/models/RepositoryModule.ts index fce63d9b46dd..766e7ce21c70 100644 --- a/packages/backend/src/models/RepositoryModule.ts +++ b/packages/backend/src/models/RepositoryModule.ts @@ -5,7 +5,7 @@ import { Module } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import { MiAbuseUserReport, MiAccessToken, MiAd, MiAnnouncement, MiAnnouncementRead, MiAntenna, MiApp, MiAuthSession, MiBlocking, MiChannel, MiChannelFavorite, MiChannelFollowing, MiClip, MiClipFavorite, MiClipNote, MiDriveFile, MiDriveFolder, MiEmoji, MiFlash, MiFlashLike, MiFollowRequest, MiFollowing, MiGalleryLike, MiGalleryPost, MiHashtag, MiInstance, MiMeta, MiModerationLog, MiMutedNote, MiMuting, MiNote, MiNoteFavorite, MiNoteReaction, MiNoteThreadMuting, MiNoteUnread, MiPage, MiPageLike, MiPasswordResetRequest, MiPoll, MiPollVote, MiPromoNote, MiPromoRead, MiRegistrationTicket, MiRegistryItem, MiRelay, MiRenoteMuting, MiRetentionAggregation, MiRole, MiRoleAssignment, MiSignin, MiSwSubscription, MiUsedUsername, MiUser, MiUserIp, MiUserKeypair, MiUserList, MiUserListFavorite, MiUserListMembership, MiUserMemo, MiUserNotePining, MiUserPending, MiUserProfile, MiUserPublickey, MiUserSecurityKey, MiWebhook } from './_.js'; +import { MiAbuseUserReport, MiAccessToken, MiAd, MiAnnouncement, MiAnnouncementRead, MiAntenna, MiApp, MiAuthSession, MiBlocking, MiChannel, MiChannelFavorite, MiChannelFollowing, MiClip, MiClipFavorite, MiClipNote, MiDriveFile, MiDriveFolder, MiEmoji, MiFlash, MiFlashLike, MiFollowRequest, MiFollowing, MiGalleryLike, MiGalleryPost, MiHashtag, MiInstance, MiMeta, MiModerationLog, MiMutedNote, MiMuting, MiNote, MiNoteFavorite, MiNoteReaction, MiNoteThreadMuting, MiNoteUnread, MiPage, MiPageLike, MiPasswordResetRequest, MiPoll, MiPollVote, MiPromoNote, MiPromoRead, MiRegistrationTicket, MiRegistryItem, MiRelay, MiRenoteMuting, MiRetentionAggregation, MiRole, MiRoleAssignment, MiSignin, MiSwSubscription, MiUsedUsername, MiUser, MiUserIp, MiUserKeypair, MiUserList, MiUserListFavorite, MiUserListJoining, MiUserMemo, MiUserNotePining, MiUserPending, MiUserProfile, MiUserPublickey, MiUserSecurityKey, MiWebhook } from './_.js'; import type { DataSource } from 'typeorm'; import type { Provider } from '@nestjs/common'; @@ -117,9 +117,9 @@ const $userListFavoritesRepository: Provider = { inject: [DI.db], }; -const $userListMembershipsRepository: Provider = { - provide: DI.userListMembershipsRepository, - useFactory: (db: DataSource) => db.getRepository(MiUserListMembership), +const $userListJoiningsRepository: Provider = { + provide: DI.userListJoiningsRepository, + useFactory: (db: DataSource) => db.getRepository(MiUserListJoining), inject: [DI.db], }; @@ -421,7 +421,7 @@ const $userMemosRepository: Provider = { $userPublickeysRepository, $userListsRepository, $userListFavoritesRepository, - $userListMembershipsRepository, + $userListJoiningsRepository, $userNotePiningsRepository, $userIpsRepository, $usedUsernamesRepository, @@ -488,7 +488,7 @@ const $userMemosRepository: Provider = { $userPublickeysRepository, $userListsRepository, $userListFavoritesRepository, - $userListMembershipsRepository, + $userListJoiningsRepository, $userNotePiningsRepository, $userIpsRepository, $usedUsernamesRepository, diff --git a/packages/backend/src/models/User.ts b/packages/backend/src/models/User.ts index 4d961c4290b9..b040d302ce7c 100644 --- a/packages/backend/src/models/User.ts +++ b/packages/backend/src/models/User.ts @@ -187,11 +187,6 @@ export class MiUser { }) public isExplorable: boolean; - @Column('boolean', { - default: false, - }) - public isHibernated: boolean; - // アカウントが削除されたかどうかのフラグだが、完全に削除される際は物理削除なので実質削除されるまでの「削除が進行しているかどうか」のフラグ @Column('boolean', { default: false, diff --git a/packages/backend/src/models/UserListMembership.ts b/packages/backend/src/models/UserListJoining.ts similarity index 90% rename from packages/backend/src/models/UserListMembership.ts rename to packages/backend/src/models/UserListJoining.ts index fc15cb8099ad..e76c93822e36 100644 --- a/packages/backend/src/models/UserListMembership.ts +++ b/packages/backend/src/models/UserListJoining.ts @@ -8,14 +8,14 @@ import { id } from './util/id.js'; import { MiUser } from './User.js'; import { MiUserList } from './UserList.js'; -@Entity('user_list_membership') +@Entity('user_list_joining') @Index(['userId', 'userListId'], { unique: true }) -export class MiUserListMembership { +export class MiUserListJoining { @PrimaryColumn(id()) public id: string; @Column('timestamp with time zone', { - comment: 'The created date of the UserListMembership.', + comment: 'The created date of the UserListJoining.', }) public createdAt: Date; diff --git a/packages/backend/src/models/_.ts b/packages/backend/src/models/_.ts index cc527ad21049..6be7bd0df6ba 100644 --- a/packages/backend/src/models/_.ts +++ b/packages/backend/src/models/_.ts @@ -53,7 +53,7 @@ import { MiUser } from '@/models/User.js'; import { MiUserIp } from '@/models/UserIp.js'; import { MiUserKeypair } from '@/models/UserKeypair.js'; import { MiUserList } from '@/models/UserList.js'; -import { MiUserListMembership } from '@/models/UserListMembership.js'; +import { MiUserListJoining } from '@/models/UserListJoining.js'; import { MiUserNotePining } from '@/models/UserNotePining.js'; import { MiUserPending } from '@/models/UserPending.js'; import { MiUserProfile } from '@/models/UserProfile.js'; @@ -122,7 +122,7 @@ export { MiUserKeypair, MiUserList, MiUserListFavorite, - MiUserListMembership, + MiUserListJoining, MiUserNotePining, MiUserPending, MiUserProfile, @@ -189,7 +189,7 @@ export type UserIpsRepository = Repository; export type UserKeypairsRepository = Repository; export type UserListsRepository = Repository; export type UserListFavoritesRepository = Repository; -export type UserListMembershipsRepository = Repository; +export type UserListJoiningsRepository = Repository; export type UserNotePiningsRepository = Repository; export type UserPendingsRepository = Repository; export type UserProfilesRepository = Repository; diff --git a/packages/backend/src/models/json-schema/user.ts b/packages/backend/src/models/json-schema/user.ts index 57d2d976ff7a..0181ea50e8dd 100644 --- a/packages/backend/src/models/json-schema/user.ts +++ b/packages/backend/src/models/json-schema/user.ts @@ -277,10 +277,6 @@ export const packedUserDetailedNotMeOnlySchema = { type: 'string', nullable: false, optional: true, }, - withReplies: { - type: 'boolean', - nullable: false, optional: true, - }, //#endregion }, } as const; diff --git a/packages/backend/src/postgres.ts b/packages/backend/src/postgres.ts index 0366203b8537..10126eab2bbd 100644 --- a/packages/backend/src/postgres.ts +++ b/packages/backend/src/postgres.ts @@ -62,7 +62,7 @@ import { MiUserIp } from '@/models/UserIp.js'; import { MiUserKeypair } from '@/models/UserKeypair.js'; import { MiUserList } from '@/models/UserList.js'; import { MiUserListFavorite } from '@/models/UserListFavorite.js'; -import { MiUserListMembership } from '@/models/UserListMembership.js'; +import { MiUserListJoining } from '@/models/UserListJoining.js'; import { MiUserNotePining } from '@/models/UserNotePining.js'; import { MiUserPending } from '@/models/UserPending.js'; import { MiUserProfile } from '@/models/UserProfile.js'; @@ -138,7 +138,7 @@ export const entities = [ MiUserPublickey, MiUserList, MiUserListFavorite, - MiUserListMembership, + MiUserListJoining, MiUserNotePining, MiUserSecurityKey, MiUsedUsername, diff --git a/packages/backend/src/queue/processors/ExportAntennasProcessorService.ts b/packages/backend/src/queue/processors/ExportAntennasProcessorService.ts index a0afbee3baa1..f941fb6e858c 100644 --- a/packages/backend/src/queue/processors/ExportAntennasProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportAntennasProcessorService.ts @@ -8,7 +8,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { format as DateFormat } from 'date-fns'; import { In } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import type { AntennasRepository, UsersRepository, UserListMembershipsRepository, MiUser } from '@/models/_.js'; +import type { AntennasRepository, UsersRepository, UserListJoiningsRepository, MiUser } from '@/models/_.js'; import Logger from '@/logger.js'; import { DriveService } from '@/core/DriveService.js'; import { bindThis } from '@/decorators.js'; @@ -29,8 +29,8 @@ export class ExportAntennasProcessorService { @Inject(DI.antennasRepository) private antennsRepository: AntennasRepository, - @Inject(DI.userListMembershipsRepository) - private userListMembershipsRepository: UserListMembershipsRepository, + @Inject(DI.userListJoiningsRepository) + private userListJoiningsRepository: UserListJoiningsRepository, private driveService: DriveService, private utilityService: UtilityService, @@ -65,9 +65,9 @@ export class ExportAntennasProcessorService { for (const [index, antenna] of antennas.entries()) { let users: MiUser[] | undefined; if (antenna.userListId !== null) { - const memberships = await this.userListMembershipsRepository.findBy({ userListId: antenna.userListId }); + const joinings = await this.userListJoiningsRepository.findBy({ userListId: antenna.userListId }); users = await this.usersRepository.findBy({ - id: In(memberships.map(j => j.userId)), + id: In(joinings.map(j => j.userId)), }); } write(JSON.stringify({ diff --git a/packages/backend/src/queue/processors/ExportUserListsProcessorService.ts b/packages/backend/src/queue/processors/ExportUserListsProcessorService.ts index a3f9441dc282..7baaa7081a86 100644 --- a/packages/backend/src/queue/processors/ExportUserListsProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportUserListsProcessorService.ts @@ -8,7 +8,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { In } from 'typeorm'; import { format as dateFormat } from 'date-fns'; import { DI } from '@/di-symbols.js'; -import type { UserListMembershipsRepository, UserListsRepository, UsersRepository } from '@/models/_.js'; +import type { UserListJoiningsRepository, UserListsRepository, UsersRepository } from '@/models/_.js'; import type Logger from '@/logger.js'; import { DriveService } from '@/core/DriveService.js'; import { createTemp } from '@/misc/create-temp.js'; @@ -29,8 +29,8 @@ export class ExportUserListsProcessorService { @Inject(DI.userListsRepository) private userListsRepository: UserListsRepository, - @Inject(DI.userListMembershipsRepository) - private userListMembershipsRepository: UserListMembershipsRepository, + @Inject(DI.userListJoiningsRepository) + private userListJoiningsRepository: UserListJoiningsRepository, private utilityService: UtilityService, private driveService: DriveService, @@ -61,9 +61,9 @@ export class ExportUserListsProcessorService { const stream = fs.createWriteStream(path, { flags: 'a' }); for (const list of lists) { - const memberships = await this.userListMembershipsRepository.findBy({ userListId: list.id }); + const joinings = await this.userListJoiningsRepository.findBy({ userListId: list.id }); const users = await this.usersRepository.findBy({ - id: In(memberships.map(j => j.userId)), + id: In(joinings.map(j => j.userId)), }); for (const u of users) { diff --git a/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts b/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts index 9be36a9d0d1e..60a0d1605f11 100644 --- a/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts @@ -6,7 +6,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { IsNull } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import type { UsersRepository, DriveFilesRepository, UserListMembershipsRepository, UserListsRepository } from '@/models/_.js'; +import type { UsersRepository, DriveFilesRepository, UserListJoiningsRepository, UserListsRepository } from '@/models/_.js'; import type Logger from '@/logger.js'; import * as Acct from '@/misc/acct.js'; import { RemoteUserResolveService } from '@/core/RemoteUserResolveService.js'; @@ -33,8 +33,8 @@ export class ImportUserListsProcessorService { @Inject(DI.userListsRepository) private userListsRepository: UserListsRepository, - @Inject(DI.userListMembershipsRepository) - private userListMembershipsRepository: UserListMembershipsRepository, + @Inject(DI.userListJoiningsRepository) + private userListJoiningsRepository: UserListJoiningsRepository, private utilityService: UtilityService, private idService: IdService, @@ -99,7 +99,7 @@ export class ImportUserListsProcessorService { target = await this.remoteUserResolveService.resolveUser(username, host); } - if (await this.userListMembershipsRepository.findOneBy({ userListId: list!.id, userId: target.id }) != null) continue; + if (await this.userListJoiningsRepository.findOneBy({ userListId: list!.id, userId: target.id }) != null) continue; this.userListService.addMember(target, list!, user); } catch (e) { diff --git a/packages/backend/src/server/api/EndpointsModule.ts b/packages/backend/src/server/api/EndpointsModule.ts index b6df8a7a6760..41a11bfb1913 100644 --- a/packages/backend/src/server/api/EndpointsModule.ts +++ b/packages/backend/src/server/api/EndpointsModule.ts @@ -335,9 +335,7 @@ import * as ep___users_lists_show from './endpoints/users/lists/show.js'; import * as ep___users_lists_update from './endpoints/users/lists/update.js'; import * as ep___users_lists_favorite from './endpoints/users/lists/favorite.js'; import * as ep___users_lists_unfavorite from './endpoints/users/lists/unfavorite.js'; -import * as ep___users_lists_createFromPublic from './endpoints/users/lists/create-from-public.js'; -import * as ep___users_lists_updateMembership from './endpoints/users/lists/update-membership.js'; -import * as ep___users_lists_getMemberships from './endpoints/users/lists/get-memberships.js'; +import * as ep___users_lists_create_from_public from './endpoints/users/lists/create-from-public.js'; import * as ep___users_notes from './endpoints/users/notes.js'; import * as ep___users_pages from './endpoints/users/pages.js'; import * as ep___users_flashs from './endpoints/users/flashs.js'; @@ -685,9 +683,7 @@ const $users_lists_show: Provider = { provide: 'ep:users/lists/show', useClass: const $users_lists_update: Provider = { provide: 'ep:users/lists/update', useClass: ep___users_lists_update.default }; const $users_lists_favorite: Provider = { provide: 'ep:users/lists/favorite', useClass: ep___users_lists_favorite.default }; const $users_lists_unfavorite: Provider = { provide: 'ep:users/lists/unfavorite', useClass: ep___users_lists_unfavorite.default }; -const $users_lists_createFromPublic: Provider = { provide: 'ep:users/lists/create-from-public', useClass: ep___users_lists_createFromPublic.default }; -const $users_lists_updateMembership: Provider = { provide: 'ep:users/lists/update-membership', useClass: ep___users_lists_updateMembership.default }; -const $users_lists_getMemberships: Provider = { provide: 'ep:users/lists/get-memberships', useClass: ep___users_lists_getMemberships.default }; +const $users_lists_create_from_public: Provider = { provide: 'ep:users/lists/create-from-public', useClass: ep___users_lists_create_from_public.default }; const $users_notes: Provider = { provide: 'ep:users/notes', useClass: ep___users_notes.default }; const $users_pages: Provider = { provide: 'ep:users/pages', useClass: ep___users_pages.default }; const $users_flashs: Provider = { provide: 'ep:users/flashs', useClass: ep___users_flashs.default }; @@ -1039,9 +1035,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention $users_lists_update, $users_lists_favorite, $users_lists_unfavorite, - $users_lists_createFromPublic, - $users_lists_updateMembership, - $users_lists_getMemberships, + $users_lists_create_from_public, $users_notes, $users_pages, $users_flashs, @@ -1384,9 +1378,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention $users_lists_update, $users_lists_favorite, $users_lists_unfavorite, - $users_lists_createFromPublic, - $users_lists_updateMembership, - $users_lists_getMemberships, + $users_lists_create_from_public, $users_notes, $users_pages, $users_flashs, diff --git a/packages/backend/src/server/api/StreamingApiServerService.ts b/packages/backend/src/server/api/StreamingApiServerService.ts index badcec1b33f8..9acaa688c508 100644 --- a/packages/backend/src/server/api/StreamingApiServerService.ts +++ b/packages/backend/src/server/api/StreamingApiServerService.ts @@ -14,7 +14,6 @@ import { NotificationService } from '@/core/NotificationService.js'; import { bindThis } from '@/decorators.js'; import { CacheService } from '@/core/CacheService.js'; import { MiLocalUser } from '@/models/User.js'; -import { UserService } from '@/core/UserService.js'; import { AuthenticateService, AuthenticationError } from './AuthenticateService.js'; import MainStreamConnection from './stream/Connection.js'; import { ChannelsService } from './stream/ChannelsService.js'; @@ -38,7 +37,6 @@ export class StreamingApiServerService { private authenticateService: AuthenticateService, private channelsService: ChannelsService, private notificationService: NotificationService, - private usersService: UserService, ) { } @@ -132,10 +130,14 @@ export class StreamingApiServerService { this.#connections.set(connection, Date.now()); const userUpdateIntervalId = user ? setInterval(() => { - this.usersService.updateLastActiveDate(user); + this.usersRepository.update(user.id, { + lastActiveDate: new Date(), + }); }, 1000 * 60 * 5) : null; if (user) { - this.usersService.updateLastActiveDate(user); + this.usersRepository.update(user.id, { + lastActiveDate: new Date(), + }); } connection.once('close', () => { diff --git a/packages/backend/src/server/api/endpoints.ts b/packages/backend/src/server/api/endpoints.ts index 0c87bfd409b7..ab20a708ef43 100644 --- a/packages/backend/src/server/api/endpoints.ts +++ b/packages/backend/src/server/api/endpoints.ts @@ -334,10 +334,8 @@ import * as ep___users_lists_push from './endpoints/users/lists/push.js'; import * as ep___users_lists_show from './endpoints/users/lists/show.js'; import * as ep___users_lists_favorite from './endpoints/users/lists/favorite.js'; import * as ep___users_lists_unfavorite from './endpoints/users/lists/unfavorite.js'; -import * as ep___users_lists_createFromPublic from './endpoints/users/lists/create-from-public.js'; +import * as ep___users_lists_create_from_public from './endpoints/users/lists/create-from-public.js'; import * as ep___users_lists_update from './endpoints/users/lists/update.js'; -import * as ep___users_lists_updateMembership from './endpoints/users/lists/update-membership.js'; -import * as ep___users_lists_getMemberships from './endpoints/users/lists/get-memberships.js'; import * as ep___users_notes from './endpoints/users/notes.js'; import * as ep___users_pages from './endpoints/users/pages.js'; import * as ep___users_flashs from './endpoints/users/flashs.js'; @@ -683,9 +681,7 @@ const eps = [ ['users/lists/favorite', ep___users_lists_favorite], ['users/lists/unfavorite', ep___users_lists_unfavorite], ['users/lists/update', ep___users_lists_update], - ['users/lists/create-from-public', ep___users_lists_createFromPublic], - ['users/lists/update-membership', ep___users_lists_updateMembership], - ['users/lists/get-memberships', ep___users_lists_getMemberships], + ['users/lists/create-from-public', ep___users_lists_create_from_public], ['users/notes', ep___users_notes], ['users/pages', ep___users_pages], ['users/flashs', ep___users_flashs], diff --git a/packages/backend/src/server/api/endpoints/antennas/notes.ts b/packages/backend/src/server/api/endpoints/antennas/notes.ts index 8477dd68daef..eaae7bff62f1 100644 --- a/packages/backend/src/server/api/endpoints/antennas/notes.ts +++ b/packages/backend/src/server/api/endpoints/antennas/notes.ts @@ -56,7 +56,7 @@ export const paramDef = { @Injectable() export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( - @Inject(DI.redisForTimelines) + @Inject(DI.redis) private redisClient: Redis.Redis, @Inject(DI.notesRepository) diff --git a/packages/backend/src/server/api/endpoints/following/update.ts b/packages/backend/src/server/api/endpoints/following/update.ts index db17d151dfdd..25f393e51745 100644 --- a/packages/backend/src/server/api/endpoints/following/update.ts +++ b/packages/backend/src/server/api/endpoints/following/update.ts @@ -57,9 +57,8 @@ export const paramDef = { properties: { userId: { type: 'string', format: 'misskey:id' }, notify: { type: 'string', enum: ['normal', 'none'] }, - withReplies: { type: 'boolean' }, }, - required: ['userId'], + required: ['userId', 'notify'], } as const; @Injectable() @@ -99,8 +98,7 @@ export default class extends Endpoint { // eslint- await this.followingsRepository.update({ id: exist.id, }, { - notify: ps.notify != null ? (ps.notify === 'none' ? null : ps.notify) : undefined, - withReplies: ps.withReplies != null ? ps.withReplies : undefined, + notify: ps.notify === 'none' ? null : ps.notify, }); return await this.userEntityService.pack(follower.id, me); diff --git a/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts b/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts index e536a43de599..8ea3810e20ac 100644 --- a/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts @@ -5,7 +5,7 @@ import { Brackets } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; -import type { NotesRepository, UserListsRepository, UserListMembershipsRepository } from '@/models/_.js'; +import type { NotesRepository, UserListsRepository, UserListJoiningsRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { QueryService } from '@/core/QueryService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; @@ -69,8 +69,8 @@ export default class extends Endpoint { // eslint- @Inject(DI.userListsRepository) private userListsRepository: UserListsRepository, - @Inject(DI.userListMembershipsRepository) - private userListMembershipsRepository: UserListMembershipsRepository, + @Inject(DI.userListJoiningsRepository) + private userListJoiningsRepository: UserListJoiningsRepository, private noteEntityService: NoteEntityService, private queryService: QueryService, @@ -88,7 +88,7 @@ export default class extends Endpoint { // eslint- //#region Construct query const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId) - .innerJoin(this.userListMembershipsRepository.metadata.targetName, 'userListJoining', 'userListJoining.userId = note.userId') + .innerJoin(this.userListJoiningsRepository.metadata.targetName, 'userListJoining', 'userListJoining.userId = note.userId') .innerJoinAndSelect('note.user', 'user') .leftJoinAndSelect('note.reply', 'reply') .leftJoinAndSelect('note.renote', 'renote') diff --git a/packages/backend/src/server/api/endpoints/roles/notes.ts b/packages/backend/src/server/api/endpoints/roles/notes.ts index f2533efa36e5..6dc35907e1f2 100644 --- a/packages/backend/src/server/api/endpoints/roles/notes.ts +++ b/packages/backend/src/server/api/endpoints/roles/notes.ts @@ -53,8 +53,8 @@ export const paramDef = { @Injectable() export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( - @Inject(DI.redisForTimelines) - private redisForTimelines: Redis.Redis, + @Inject(DI.redis) + private redisClient: Redis.Redis, @Inject(DI.notesRepository) private notesRepository: NotesRepository, @@ -79,7 +79,7 @@ export default class extends Endpoint { // eslint- return []; } const limit = ps.limit + (ps.untilId ? 1 : 0) + (ps.sinceId ? 1 : 0); // untilIdに指定したものも含まれるため+1 - const noteIdsRes = await this.redisForTimelines.xrevrange( + const noteIdsRes = await this.redisClient.xrevrange( `roleTimeline:${role.id}`, ps.untilId ? this.idService.parse(ps.untilId).date.getTime() : ps.untilDate ?? '+', ps.sinceId ? this.idService.parse(ps.sinceId).date.getTime() : ps.sinceDate ?? '-', diff --git a/packages/backend/src/server/api/endpoints/users/lists/create-from-public.ts b/packages/backend/src/server/api/endpoints/users/lists/create-from-public.ts index f2f6c4303a68..eae55905d35f 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/create-from-public.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/create-from-public.ts @@ -4,7 +4,7 @@ */ import { Inject, Injectable } from '@nestjs/common'; -import type { UserListsRepository, UserListMembershipsRepository, BlockingsRepository } from '@/models/_.js'; +import type { UserListsRepository, UserListJoiningsRepository, BlockingsRepository } from '@/models/_.js'; import { IdService } from '@/core/IdService.js'; import type { MiUserList } from '@/models/UserList.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; @@ -76,8 +76,8 @@ export default class extends Endpoint { // eslint- @Inject(DI.userListsRepository) private userListsRepository: UserListsRepository, - @Inject(DI.userListMembershipsRepository) - private userListMembershipsRepository: UserListMembershipsRepository, + @Inject(DI.userListJoiningsRepository) + private userListJoiningsRepository: UserListJoiningsRepository, @Inject(DI.blockingsRepository) private blockingsRepository: BlockingsRepository, @@ -110,7 +110,7 @@ export default class extends Endpoint { // eslint- name: ps.name, } as MiUserList).then(x => this.userListsRepository.findOneByOrFail(x.identifiers[0])); - const users = (await this.userListMembershipsRepository.findBy({ + const users = (await this.userListJoiningsRepository.findBy({ userListId: ps.listId, })).map(x => x.userId); @@ -132,7 +132,7 @@ export default class extends Endpoint { // eslint- } } - const exist = await this.userListMembershipsRepository.exist({ + const exist = await this.userListJoiningsRepository.exist({ where: { userListId: userList.id, userId: currentUser.id, diff --git a/packages/backend/src/server/api/endpoints/users/lists/get-memberships.ts b/packages/backend/src/server/api/endpoints/users/lists/get-memberships.ts deleted file mode 100644 index ae8b4e9b8124..000000000000 --- a/packages/backend/src/server/api/endpoints/users/lists/get-memberships.ts +++ /dev/null @@ -1,79 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and other misskey contributors - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import { Inject, Injectable } from '@nestjs/common'; -import type { UserListsRepository, UserListFavoritesRepository, UserListMembershipsRepository } from '@/models/_.js'; -import { Endpoint } from '@/server/api/endpoint-base.js'; -import { UserListEntityService } from '@/core/entities/UserListEntityService.js'; -import { DI } from '@/di-symbols.js'; -import { QueryService } from '@/core/QueryService.js'; -import { ApiError } from '../../../error.js'; - -export const meta = { - tags: ['lists', 'account'], - - requireCredential: false, - - kind: 'read:account', - - errors: { - noSuchList: { - message: 'No such list.', - code: 'NO_SUCH_LIST', - id: '7bc05c21-1d7a-41ae-88f1-66820f4dc686', - }, - }, -} as const; - -export const paramDef = { - type: 'object', - properties: { - listId: { type: 'string', format: 'misskey:id' }, - forPublic: { type: 'boolean', default: false }, - limit: { type: 'integer', minimum: 1, maximum: 100, default: 30 }, - sinceId: { type: 'string', format: 'misskey:id' }, - untilId: { type: 'string', format: 'misskey:id' }, - }, - required: ['listId'], -} as const; - -@Injectable() // eslint-disable-next-line import/no-default-export -export default class extends Endpoint { - constructor( - @Inject(DI.userListsRepository) - private userListsRepository: UserListsRepository, - - @Inject(DI.userListMembershipsRepository) - private userListMembershipsRepository: UserListMembershipsRepository, - - private userListEntityService: UserListEntityService, - private queryService: QueryService, - ) { - super(meta, paramDef, async (ps, me) => { - // Fetch the list - const userList = await this.userListsRepository.findOneBy(!ps.forPublic && me !== null ? { - id: ps.listId, - userId: me.id, - } : { - id: ps.listId, - isPublic: true, - }); - - if (userList == null) { - throw new ApiError(meta.errors.noSuchList); - } - - const query = this.queryService.makePaginationQuery(this.userListMembershipsRepository.createQueryBuilder('membership'), ps.sinceId, ps.untilId) - .andWhere('membership.userListId = :userListId', { userListId: userList.id }) - .innerJoinAndSelect('membership.user', 'user'); - - const memberships = await query - .limit(ps.limit) - .getMany(); - - return this.userListEntityService.packMembershipsMany(memberships); - }); - } -} diff --git a/packages/backend/src/server/api/endpoints/users/lists/push.ts b/packages/backend/src/server/api/endpoints/users/lists/push.ts index c4ceec575b92..72a6a7380d9b 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/push.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/push.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import ms from 'ms'; -import type { UserListsRepository, UserListMembershipsRepository, BlockingsRepository } from '@/models/_.js'; +import type { UserListsRepository, UserListJoiningsRepository, BlockingsRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { GetterService } from '@/server/api/GetterService.js'; import { UserListService } from '@/core/UserListService.js'; @@ -76,8 +76,8 @@ export default class extends Endpoint { // eslint- @Inject(DI.userListsRepository) private userListsRepository: UserListsRepository, - @Inject(DI.userListMembershipsRepository) - private userListMembershipsRepository: UserListMembershipsRepository, + @Inject(DI.userListJoiningsRepository) + private userListJoiningsRepository: UserListJoiningsRepository, @Inject(DI.blockingsRepository) private blockingsRepository: BlockingsRepository, @@ -115,7 +115,7 @@ export default class extends Endpoint { // eslint- } } - const exist = await this.userListMembershipsRepository.exist({ + const exist = await this.userListJoiningsRepository.exist({ where: { userListId: userList.id, userId: user.id, diff --git a/packages/backend/src/server/api/endpoints/users/lists/update-membership.ts b/packages/backend/src/server/api/endpoints/users/lists/update-membership.ts deleted file mode 100644 index b69465b940e8..000000000000 --- a/packages/backend/src/server/api/endpoints/users/lists/update-membership.ts +++ /dev/null @@ -1,79 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and other misskey contributors - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import { Inject, Injectable } from '@nestjs/common'; -import type { UserListsRepository } from '@/models/_.js'; -import { Endpoint } from '@/server/api/endpoint-base.js'; -import { GetterService } from '@/server/api/GetterService.js'; -import { DI } from '@/di-symbols.js'; -import { UserListService } from '@/core/UserListService.js'; -import { ApiError } from '../../../error.js'; - -export const meta = { - tags: ['lists', 'users'], - - requireCredential: true, - - prohibitMoved: true, - - kind: 'write:account', - - errors: { - noSuchList: { - message: 'No such list.', - code: 'NO_SUCH_LIST', - id: '7f44670e-ab16-43b8-b4c1-ccd2ee89cc02', - }, - - noSuchUser: { - message: 'No such user.', - code: 'NO_SUCH_USER', - id: '588e7f72-c744-4a61-b180-d354e912bda2', - }, - }, -} as const; - -export const paramDef = { - type: 'object', - properties: { - listId: { type: 'string', format: 'misskey:id' }, - userId: { type: 'string', format: 'misskey:id' }, - withReplies: { type: 'boolean' }, - }, - required: ['listId', 'userId'], -} as const; - -@Injectable() -export default class extends Endpoint { // eslint-disable-line import/no-default-export - constructor( - @Inject(DI.userListsRepository) - private userListsRepository: UserListsRepository, - - private userListService: UserListService, - private getterService: GetterService, - ) { - super(meta, paramDef, async (ps, me) => { - // Fetch the list - const userList = await this.userListsRepository.findOneBy({ - id: ps.listId, - userId: me.id, - }); - - if (userList == null) { - throw new ApiError(meta.errors.noSuchList); - } - - // Fetch the user - const user = await this.getterService.getUser(ps.userId).catch(err => { - if (err.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); - throw err; - }); - - await this.userListService.updateMembership(user, userList, { - withReplies: ps.withReplies, - }); - }); - } -} diff --git a/packages/backend/src/server/api/stream/channels/user-list.ts b/packages/backend/src/server/api/stream/channels/user-list.ts index 42083aee67b3..8bbba0b6dbb9 100644 --- a/packages/backend/src/server/api/stream/channels/user-list.ts +++ b/packages/backend/src/server/api/stream/channels/user-list.ts @@ -4,7 +4,7 @@ */ import { Inject, Injectable } from '@nestjs/common'; -import type { UserListMembershipsRepository, UserListsRepository } from '@/models/_.js'; +import type { UserListJoiningsRepository, UserListsRepository } from '@/models/_.js'; import type { MiUser } from '@/models/User.js'; import { isUserRelated } from '@/misc/is-user-related.js'; import type { Packed } from '@/misc/json-schema.js'; @@ -23,7 +23,7 @@ class UserListChannel extends Channel { constructor( private userListsRepository: UserListsRepository, - private userListMembershipsRepository: UserListMembershipsRepository, + private userListJoiningsRepository: UserListJoiningsRepository, private noteEntityService: NoteEntityService, id: string, @@ -58,7 +58,7 @@ class UserListChannel extends Channel { @bindThis private async updateListUsers() { - const users = await this.userListMembershipsRepository.find({ + const users = await this.userListJoiningsRepository.find({ where: { userListId: this.listId, }, @@ -124,8 +124,8 @@ export class UserListChannelService { @Inject(DI.userListsRepository) private userListsRepository: UserListsRepository, - @Inject(DI.userListMembershipsRepository) - private userListMembershipsRepository: UserListMembershipsRepository, + @Inject(DI.userListJoiningsRepository) + private userListJoiningsRepository: UserListJoiningsRepository, private noteEntityService: NoteEntityService, ) { @@ -135,7 +135,7 @@ export class UserListChannelService { public create(id: string, connection: Channel['connection']): UserListChannel { return new UserListChannel( this.userListsRepository, - this.userListMembershipsRepository, + this.userListJoiningsRepository, this.noteEntityService, id, connection, From d8547d96cf9cad09108593ae33824ed591402aa5 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Thu, 12 Oct 2023 11:28:48 +0900 Subject: [PATCH 13/22] revert: deny RN of direct note --- .../backend/src/core/NoteCreateService.ts | 35 +++++++------------ 1 file changed, 12 insertions(+), 23 deletions(-) diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts index ad20fbec81d6..0ff295117c91 100644 --- a/packages/backend/src/core/NoteCreateService.ts +++ b/packages/backend/src/core/NoteCreateService.ts @@ -259,30 +259,19 @@ export class NoteCreateService implements OnApplicationShutdown { } } - if (data.renote) { - switch (data.renote.visibility) { - case 'public': - // public noteは無条件にrenote可能 - break; - case 'home': - // home noteはhome以下にrenote可能 - if (data.visibility === 'public') { - data.visibility = 'home'; - } - break; - case 'followers': - // 他人のfollowers noteはreject - if (data.renote.userId !== user.id) { - throw new Error('Renote target is not public or home'); - } + // Renote対象が「ホームまたは全体」以外の公開範囲ならreject + if (data.renote && data.renote.visibility !== 'public' && data.renote.visibility !== 'home' && data.renote.userId !== user.id) { + throw new Error('Renote target is not public or home'); + } - // Renote対象がfollowersならfollowersにする - data.visibility = 'followers'; - break; - case 'specified': - // specified / direct noteはreject - throw new Error('Renote target is not public or home'); - } + // Renote対象がpublicではないならhomeにする + if (data.renote && data.renote.visibility !== 'public' && data.visibility === 'public') { + data.visibility = 'home'; + } + + // Renote対象がfollowersならfollowersにする + if (data.renote && data.renote.visibility === 'followers') { + data.visibility = 'followers'; } // 返信対象がpublicではないならhomeにする From f10e1591acfdc40a484dfca2bfabb9c52b62e89e Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Thu, 12 Oct 2023 11:30:05 +0900 Subject: [PATCH 14/22] revert: Renote Count --- packages/backend/src/core/NoteCreateService.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts index 0ff295117c91..b4e3005d3f42 100644 --- a/packages/backend/src/core/NoteCreateService.ts +++ b/packages/backend/src/core/NoteCreateService.ts @@ -530,8 +530,9 @@ export class NoteCreateService implements OnApplicationShutdown { }); } - if (data.renote && data.renote.userId !== user.id && !user.isBot) { - this.incRenoteCount(data.renote); + // この投稿を除く指定したユーザーによる指定したノートのリノートが存在しないとき + if (data.renote && (await this.noteEntityService.countSameRenotes(user.id, data.renote.id, note.id) === 0)) { + if (!user.isBot) this.incRenoteCount(data.renote); } if (data.poll && data.poll.expiresAt) { From 3646db86cfe26f96659f8539edbc1a1b0d71b77a Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Thu, 12 Oct 2023 11:34:35 +0900 Subject: [PATCH 15/22] fix: old roleTimeline not updated --- packages/backend/src/core/RoleService.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/backend/src/core/RoleService.ts b/packages/backend/src/core/RoleService.ts index 2c3547e4acd9..d8f9f4639c84 100644 --- a/packages/backend/src/core/RoleService.ts +++ b/packages/backend/src/core/RoleService.ts @@ -474,6 +474,11 @@ export class RoleService implements OnApplicationShutdown { const redisPipeline = this.redisClient.pipeline(); for (const role of roles) { + redisPipeline.xadd( + `roleTimeline:${role.id}`, + 'MAXLEN', '~', '1000', + '*', + 'note', note.id); this.redisTimelineService.push(`roleTimeline:${role.id}`, note.id, 1000, redisPipeline); this.globalEventService.publishRoleTimelineStream(role.id, 'note', note); } From fefdb4b55639b1cfc35db0f49381ab34749ad6e9 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Thu, 12 Oct 2023 11:35:46 +0900 Subject: [PATCH 16/22] fix: withReplies remains in UserListJoining --- packages/backend/src/config.ts | 6 +++++- packages/backend/src/core/NoteCreateService.ts | 4 ++-- packages/backend/src/models/UserListJoining.ts | 6 ------ 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/packages/backend/src/config.ts b/packages/backend/src/config.ts index 81fbf4de5561..6945866baaa0 100644 --- a/packages/backend/src/config.ts +++ b/packages/backend/src/config.ts @@ -96,6 +96,7 @@ type Source = { disableAbuseRepository?: boolean; maxWebImageSize?: number; withRepliesInHomeTL?: boolean; + withRepliesInUserList?: boolean; } }; @@ -178,6 +179,7 @@ export type Config = { disableAbuseRepository?: boolean; maxWebImageSize?: number; withRepliesInHomeTL?: boolean, + withRepliesInUserList: boolean, } }; @@ -221,7 +223,9 @@ export function loadConfig(): Config { return { // to avoid merge conflict in the future, this is at top - nirila: config.nirila ?? {}, + nirila: Object.assign({ + withRepliesInUserList: true, + }, config.nirila ?? {}), version, url: url.origin, port: config.port ?? parseInt(process.env.PORT ?? '', 10), diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts index b4e3005d3f42..c7929b49405f 100644 --- a/packages/backend/src/core/NoteCreateService.ts +++ b/packages/backend/src/core/NoteCreateService.ts @@ -861,7 +861,7 @@ export class NoteCreateService implements OnApplicationShutdown { where: { userId: user.id, }, - select: ['userListId', 'userListUserId', 'withReplies'], + select: ['userListId', 'userListUserId'], }), ]); @@ -895,7 +895,7 @@ export class NoteCreateService implements OnApplicationShutdown { // 自分自身以外への返信 if (note.replyId && note.replyUserId !== note.userId) { - if (!userListMembership.withReplies) continue; + if (!this.config.nirila.withRepliesInHomeTL) continue; } this.redisTimelineService.push(`userListTimeline:${userListMembership.userListId}`, note.id, 300, r); diff --git a/packages/backend/src/models/UserListJoining.ts b/packages/backend/src/models/UserListJoining.ts index e76c93822e36..8f6a4edc7c62 100644 --- a/packages/backend/src/models/UserListJoining.ts +++ b/packages/backend/src/models/UserListJoining.ts @@ -45,12 +45,6 @@ export class MiUserListJoining { @JoinColumn() public userList: MiUserList | null; - // タイムラインにその人のリプライまで含めるかどうか - @Column('boolean', { - default: true, - }) - public withReplies: boolean; - //#region Denormalized fields @Column({ ...id(), From d1dab2eba7aca51e9bafb7635bbd71838589c20b Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Thu, 12 Oct 2023 12:15:25 +0900 Subject: [PATCH 17/22] chore: use JOIN instead of Denormalized fields --- packages/backend/src/core/NoteCreateService.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts index c7929b49405f..dda702d0eda9 100644 --- a/packages/backend/src/core/NoteCreateService.ts +++ b/packages/backend/src/core/NoteCreateService.ts @@ -861,13 +861,14 @@ export class NoteCreateService implements OnApplicationShutdown { where: { userId: user.id, }, - select: ['userListId', 'userListUserId'], + select: ['userList', 'userListId'], + relations: ['userList'], }), ]); if (note.visibility === 'followers') { // TODO: 重そうだから何とかしたい Set 使う? - userListMemberships = userListMemberships.filter(x => followings.some(f => f.followerId === x.userListUserId)); + userListMemberships = userListMemberships.filter(x => followings.some(f => f.followerId === x.userList!.userId)); } // TODO: あまりにも数が多いと redisPipeline.exec に失敗する(理由は不明)ため、3万件程度を目安に分割して実行するようにする @@ -890,7 +891,7 @@ export class NoteCreateService implements OnApplicationShutdown { // ダイレクトのとき、そのリストが対象外のユーザーの場合 if ( note.visibility === 'specified' && - !note.visibleUserIds.some(v => v === userListMembership.userListUserId) + !note.visibleUserIds.some(v => v === userListMembership.userList!.userId) ) continue; // 自分自身以外への返信 From 27c9cbc2f97879233c28e3f4a5d3960ec0f40f5f Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Thu, 12 Oct 2023 12:16:25 +0900 Subject: [PATCH 18/22] chore: remove userListUserId Denormalized field from MiUserListJoining --- .../migration/1696807733453-userListUserId.js | 22 ------------------- .../1696808725134-userListUserId-2.js | 16 -------------- .../backend/src/models/UserListJoining.ts | 7 ------ 3 files changed, 45 deletions(-) delete mode 100644 packages/backend/migration/1696807733453-userListUserId.js delete mode 100644 packages/backend/migration/1696808725134-userListUserId-2.js diff --git a/packages/backend/migration/1696807733453-userListUserId.js b/packages/backend/migration/1696807733453-userListUserId.js deleted file mode 100644 index f4ec58f36aa0..000000000000 --- a/packages/backend/migration/1696807733453-userListUserId.js +++ /dev/null @@ -1,22 +0,0 @@ - -/* - * SPDX-FileCopyrightText: syuilo and other misskey contributors - * SPDX-License-Identifier: AGPL-3.0-only - */ - -export class UserListUserId1696807733453 { - name = 'UserListUserId1696807733453' - - async up(queryRunner) { - await queryRunner.query(`ALTER TABLE "user_list_membership" ADD "userListUserId" character varying(32) NOT NULL DEFAULT ''`); - const memberships = await queryRunner.query(`SELECT "id", "userListId" FROM "user_list_membership"`); - for(let i = 0; i < memberships.length; i++) { - const userList = await queryRunner.query(`SELECT "userId" FROM "user_list" WHERE "id" = $1`, [memberships[i].userListId]); - await queryRunner.query(`UPDATE "user_list_membership" SET "userListUserId" = $1 WHERE "id" = $2`, [userList[0].userId, memberships[i].id]); - } - } - - async down(queryRunner) { - await queryRunner.query(`ALTER TABLE "user_list_membership" DROP COLUMN "userListUserId"`); - } -} diff --git a/packages/backend/migration/1696808725134-userListUserId-2.js b/packages/backend/migration/1696808725134-userListUserId-2.js deleted file mode 100644 index 22d8704a9bfa..000000000000 --- a/packages/backend/migration/1696808725134-userListUserId-2.js +++ /dev/null @@ -1,16 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and other misskey contributors - * SPDX-License-Identifier: AGPL-3.0-only - */ - -export class UserListUserId21696808725134 { - name = 'UserListUserId21696808725134' - - async up(queryRunner) { - await queryRunner.query(`ALTER TABLE "user_list_membership" ALTER COLUMN "userListUserId" DROP DEFAULT`); - } - - async down(queryRunner) { - await queryRunner.query(`ALTER TABLE "user_list_membership" ALTER COLUMN "userListUserId" SET DEFAULT ''`); - } -} diff --git a/packages/backend/src/models/UserListJoining.ts b/packages/backend/src/models/UserListJoining.ts index 8f6a4edc7c62..4918f2f70001 100644 --- a/packages/backend/src/models/UserListJoining.ts +++ b/packages/backend/src/models/UserListJoining.ts @@ -44,11 +44,4 @@ export class MiUserListJoining { }) @JoinColumn() public userList: MiUserList | null; - - //#region Denormalized fields - @Column({ - ...id(), - }) - public userListUserId: MiUser['id']; - //#endregion } From 0864a9959cd5ae552e3569497ad74f8a8e617768 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Thu, 12 Oct 2023 12:17:05 +0900 Subject: [PATCH 19/22] fix: style --- packages/backend/src/core/AntennaService.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/backend/src/core/AntennaService.ts b/packages/backend/src/core/AntennaService.ts index 0e456d7f0ee6..6d761d579e43 100644 --- a/packages/backend/src/core/AntennaService.ts +++ b/packages/backend/src/core/AntennaService.ts @@ -91,6 +91,7 @@ export class AntennaService implements OnApplicationShutdown { 'MAXLEN', '~', '200', '*', 'note', note.id); + this.redisTimelineService.push(`antennaTimeline:${antenna.id}`, note.id, 200, redisPipeline); this.globalEventService.publishAntennaStream(antenna.id, 'note', note); } From 73cd4bd834abb295c4e2baacbdbd4b99d2b147c5 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Thu, 12 Oct 2023 12:18:33 +0900 Subject: [PATCH 20/22] fix: usage of userListUserId remains --- packages/backend/src/core/AccountMoveService.ts | 3 +-- packages/backend/src/core/UserListService.ts | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/backend/src/core/AccountMoveService.ts b/packages/backend/src/core/AccountMoveService.ts index 17f6f3169edd..ec1d013922b7 100644 --- a/packages/backend/src/core/AccountMoveService.ts +++ b/packages/backend/src/core/AccountMoveService.ts @@ -228,7 +228,7 @@ export class AccountMoveService { }, }).then(joinings => joinings.map(joining => joining.userListId)); - const newJoinings: Map = new Map(); + const newJoinings: Map = new Map(); // 重複しないようにIDを生成 const genId = (): string => { @@ -244,7 +244,6 @@ export class AccountMoveService { createdAt: new Date(), userId: dst.id, userListId: joining.userListId, - userListUserId: joining.userListUserId, }); } diff --git a/packages/backend/src/core/UserListService.ts b/packages/backend/src/core/UserListService.ts index 98f32fbbfb5d..93dc5edbbafe 100644 --- a/packages/backend/src/core/UserListService.ts +++ b/packages/backend/src/core/UserListService.ts @@ -97,7 +97,6 @@ export class UserListService implements OnApplicationShutdown { createdAt: new Date(), userId: target.id, userListId: list.id, - userListUserId: list.userId, } as MiUserListJoining); this.globalEventService.publishInternalEvent('userListMemberAdded', { userListId: list.id, memberId: target.id }); From 690d4a5b35285c7b8582469548523398bc5cd4a5 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Thu, 12 Oct 2023 12:49:56 +0900 Subject: [PATCH 21/22] =?UTF-8?q?pick=20=E3=83=95=E3=82=A9=E3=83=AD?= =?UTF-8?q?=E3=83=BC=E3=81=97=E3=81=A6=E3=81=84=E3=82=8B=E3=83=A6=E3=83=BC?= =?UTF-8?q?=E3=82=B6=E3=83=BC=E3=81=8B=E3=82=89=E3=81=AE=E8=87=AA=E5=88=86?= =?UTF-8?q?=E3=81=AE=E6=8A=95=E7=A8=BF=E3=81=B8=E3=81=AE=E8=BF=94=E4=BF=A1?= =?UTF-8?q?=E3=81=8C=E3=82=BF=E3=82=A4=E3=83=A0=E3=83=A9=E3=82=A4=E3=83=B3?= =?UTF-8?q?=E3=81=AB=E5=90=AB=E3=81=BE=E3=82=8C=E3=81=AA=E3=81=84=E5=95=8F?= =?UTF-8?q?=E9=A1=8C=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1f0c27edf29b7f01c15a39d83a82bb3089afee36 --- packages/backend/src/core/NoteCreateService.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts index dda702d0eda9..b917550cfd4f 100644 --- a/packages/backend/src/core/NoteCreateService.ts +++ b/packages/backend/src/core/NoteCreateService.ts @@ -876,8 +876,8 @@ export class NoteCreateService implements OnApplicationShutdown { // 基本的にvisibleUserIdsには自身のidが含まれている前提であること if (note.visibility === 'specified' && !note.visibleUserIds.some(v => v === following.followerId)) continue; - // 自分自身以外への返信 - if (note.replyId && note.replyUserId !== note.userId) { + // 「自分自身への返信 or そのフォロワーへの返信」のどちらでもない場合 + if (note.replyId && !(note.replyUserId === note.userId || note.replyUserId === following.followerId)) { if (!this.config.nirila.withRepliesInHomeTL) continue; } @@ -894,8 +894,8 @@ export class NoteCreateService implements OnApplicationShutdown { !note.visibleUserIds.some(v => v === userListMembership.userList!.userId) ) continue; - // 自分自身以外への返信 - if (note.replyId && note.replyUserId !== note.userId) { + // 「自分自身への返信 or そのリストの作成者への返信」のどちらでもない場合 + if (note.replyId && !(note.replyUserId === note.userId || note.replyUserId === userListMembership.userList!.userId)) { if (!this.config.nirila.withRepliesInHomeTL) continue; } From dc024b49bc22a5d12d411101f1d619ce13d9d2de Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Thu, 12 Oct 2023 12:56:48 +0900 Subject: [PATCH 22/22] =?UTF-8?q?docs(changelog):=202023.10.x=E5=90=91?= =?UTF-8?q?=E3=81=91=E3=81=AETL=E3=82=92=E5=86=85=E9=83=A8=E7=9A=84?= =?UTF-8?q?=E3=81=AB=E6=A7=8B=E7=AF=89=E3=81=99=E3=82=8B=E3=82=88=E3=81=86?= =?UTF-8?q?=E3=81=AB=E3=81=AA=E3=82=8A=E3=81=BE=E3=81=97=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d4f9e3a11b55..deac69fc9493 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,9 @@ ### Client ### Server +- 2023.10.x向けのTLを内部的に構築するようになりました + - これによりこの2023.10.x以降に更新したあとも2023.9.3-kinel.4更新後のnoteが見れるようになります + - 本来の2023.10.xでは更新以前のnoteがTLで見えないという仕様がありました。 ## 2023.9.3-kinel.3 (unreleased)