diff --git a/CHANGELOG.md b/CHANGELOG.md index 3decae2b9c..78716e5434 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ - Fix: 自分のフォローしているユーザーの自分のフォローしていないユーザーの visibility: followers な投稿への返信がストリーミングで流れてくる問題を修正 - Fix: RedisへのTLキャッシュが有効の場合にHTL/LTL/STLが空になることがある問題を修正 - Fix: STLでフォローしていないチャンネルが取得される問題を修正 +- Fix: フォローしているチャンネルをフォロー解除した時(またはその逆)、タイムラインに反映される間隔を改善 ## 2023.10.2 diff --git a/packages/backend/src/core/CacheService.ts b/packages/backend/src/core/CacheService.ts index 22c510cc37..f7d599f57e 100644 --- a/packages/backend/src/core/CacheService.ts +++ b/packages/backend/src/core/CacheService.ts @@ -197,6 +197,14 @@ export class CacheService implements OnApplicationShutdown { this.userFollowingsCache.delete(body.followerId); break; } + case 'followChannel': { + this.userFollowingChannelsCache.refresh(body.userId) + break; + } + case 'unfollowChannel': { + this.userFollowingChannelsCache.delete(body.userId) + break; + } default: break; } diff --git a/packages/backend/src/core/ChannelFollowingService.ts b/packages/backend/src/core/ChannelFollowingService.ts new file mode 100644 index 0000000000..4693177ae5 --- /dev/null +++ b/packages/backend/src/core/ChannelFollowingService.ts @@ -0,0 +1,61 @@ +import { Inject, Injectable, OnModuleInit } from '@nestjs/common'; +import { DI } from '@/di-symbols.js'; +import type { ChannelFollowingsRepository } from '@/models/_.js'; +import { MiChannel } from '@/models/_.js'; +import { IdService } from '@/core/IdService.js'; +import { CacheService } from '@/core/CacheService.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { bindThis } from '@/decorators.js'; +import type { MiLocalUser } from '@/models/User.js'; + +@Injectable() +export class ChannelFollowingService implements OnModuleInit { + constructor( + @Inject(DI.channelFollowingsRepository) + private channelFollowingsRepository: ChannelFollowingsRepository, + private idService: IdService, + private cacheService: CacheService, + private globalEventService: GlobalEventService, + ) { + } + + onModuleInit() { + } + + @bindThis + public async follow( + requestUser: MiLocalUser, + targetChannel: MiChannel, + ): Promise { + await this.channelFollowingsRepository.insert({ + id: this.idService.gen(), + followerId: requestUser.id, + followeeId: targetChannel.id, + }); + + this.cacheService.userFollowingsCache.refresh(requestUser.id); + + this.globalEventService.publishInternalEvent('followChannel', { + userId: requestUser.id, + channelId: targetChannel.id, + }); + } + + @bindThis + public async unfollow( + requestUser: MiLocalUser, + targetChannel: MiChannel, + ): Promise { + await this.channelFollowingsRepository.delete({ + followerId: requestUser.id, + followeeId: targetChannel.id, + }); + + this.cacheService.userFollowingsCache.refresh(requestUser.id); + + this.globalEventService.publishInternalEvent('unfollowChannel', { + userId: requestUser.id, + channelId: targetChannel.id, + }); + } +} diff --git a/packages/backend/src/core/CoreModule.ts b/packages/backend/src/core/CoreModule.ts index b46afb1909..c17ea9999a 100644 --- a/packages/backend/src/core/CoreModule.ts +++ b/packages/backend/src/core/CoreModule.ts @@ -63,6 +63,7 @@ import { SearchService } from './SearchService.js'; import { ClipService } from './ClipService.js'; import { FeaturedService } from './FeaturedService.js'; import { FunoutTimelineService } from './FunoutTimelineService.js'; +import { ChannelFollowingService } from './ChannelFollowingService.js'; import { ChartLoggerService } from './chart/ChartLoggerService.js'; import FederationChart from './chart/charts/federation.js'; import NotesChart from './chart/charts/notes.js'; @@ -193,6 +194,7 @@ const $SearchService: Provider = { provide: 'SearchService', useExisting: Search const $ClipService: Provider = { provide: 'ClipService', useExisting: ClipService }; const $FeaturedService: Provider = { provide: 'FeaturedService', useExisting: FeaturedService }; const $FunoutTimelineService: Provider = { provide: 'FunoutTimelineService', useExisting: FunoutTimelineService }; +const $ChannelFollowingService: Provider = { provide: 'ChannelFollowingService', useExisting: ChannelFollowingService }; const $ChartLoggerService: Provider = { provide: 'ChartLoggerService', useExisting: ChartLoggerService }; const $FederationChart: Provider = { provide: 'FederationChart', useExisting: FederationChart }; @@ -327,6 +329,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting ClipService, FeaturedService, FunoutTimelineService, + ChannelFollowingService, ChartLoggerService, FederationChart, NotesChart, @@ -454,6 +457,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting $ClipService, $FeaturedService, $FunoutTimelineService, + $ChannelFollowingService, $ChartLoggerService, $FederationChart, $NotesChart, @@ -582,6 +586,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting ClipService, FeaturedService, FunoutTimelineService, + ChannelFollowingService, FederationChart, NotesChart, UsersChart, @@ -708,6 +713,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting $ClipService, $FeaturedService, $FunoutTimelineService, + $ChannelFollowingService, $FederationChart, $NotesChart, $UsersChart, diff --git a/packages/backend/src/server/api/endpoints/channels/follow.ts b/packages/backend/src/server/api/endpoints/channels/follow.ts index 76ec6be805..bb5a477eb8 100644 --- a/packages/backend/src/server/api/endpoints/channels/follow.ts +++ b/packages/backend/src/server/api/endpoints/channels/follow.ts @@ -5,9 +5,9 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { ChannelFollowingsRepository, ChannelsRepository } from '@/models/_.js'; -import { IdService } from '@/core/IdService.js'; +import type { ChannelsRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; +import { ChannelFollowingService } from '@/core/ChannelFollowingService.js'; import { ApiError } from '../../error.js'; export const meta = { @@ -41,11 +41,7 @@ export default class extends Endpoint { // eslint- constructor( @Inject(DI.channelsRepository) private channelsRepository: ChannelsRepository, - - @Inject(DI.channelFollowingsRepository) - private channelFollowingsRepository: ChannelFollowingsRepository, - - private idService: IdService, + private channelFollowingService: ChannelFollowingService, ) { super(meta, paramDef, async (ps, me) => { const channel = await this.channelsRepository.findOneBy({ @@ -56,11 +52,7 @@ export default class extends Endpoint { // eslint- throw new ApiError(meta.errors.noSuchChannel); } - await this.channelFollowingsRepository.insert({ - id: this.idService.gen(), - followerId: me.id, - followeeId: channel.id, - }); + await this.channelFollowingService.follow(me, channel); }); } } diff --git a/packages/backend/src/server/api/endpoints/channels/unfollow.ts b/packages/backend/src/server/api/endpoints/channels/unfollow.ts index 46883dd548..c95332c7f8 100644 --- a/packages/backend/src/server/api/endpoints/channels/unfollow.ts +++ b/packages/backend/src/server/api/endpoints/channels/unfollow.ts @@ -5,8 +5,9 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import type { ChannelFollowingsRepository, ChannelsRepository } from '@/models/_.js'; +import type { ChannelsRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; +import { ChannelFollowingService } from '@/core/ChannelFollowingService.js'; import { ApiError } from '../../error.js'; export const meta = { @@ -40,9 +41,7 @@ export default class extends Endpoint { // eslint- constructor( @Inject(DI.channelsRepository) private channelsRepository: ChannelsRepository, - - @Inject(DI.channelFollowingsRepository) - private channelFollowingsRepository: ChannelFollowingsRepository, + private channelFollowingService: ChannelFollowingService, ) { super(meta, paramDef, async (ps, me) => { const channel = await this.channelsRepository.findOneBy({ @@ -53,10 +52,7 @@ export default class extends Endpoint { // eslint- throw new ApiError(meta.errors.noSuchChannel); } - await this.channelFollowingsRepository.delete({ - followerId: me.id, - followeeId: channel.id, - }); + await this.channelFollowingService.unfollow(me, channel); }); } }