summaryrefslogtreecommitdiff
path: root/packages/backend/src/server/api
diff options
context:
space:
mode:
authorsyuilo <Syuilotan@yahoo.co.jp>2023-04-08 21:27:21 +0900
committerGitHub <noreply@github.com>2023-04-08 21:27:21 +0900
commita096f621cf5a47c3330935c2b9b5bfe54dfc0091 (patch)
treeb3b6a1a1ce5105091bebc80b96cfd5a73402da80 /packages/backend/src/server/api
parentMerge pull request #10402 from misskey-dev/develop (diff)
parent[ci skip] Update CHANGELOG.md (diff)
downloadmisskey-a096f621cf5a47c3330935c2b9b5bfe54dfc0091.tar.gz
misskey-a096f621cf5a47c3330935c2b9b5bfe54dfc0091.tar.bz2
misskey-a096f621cf5a47c3330935c2b9b5bfe54dfc0091.zip
Merge pull request #10506 from misskey-dev/develop
13.11.0
Diffstat (limited to 'packages/backend/src/server/api')
-rw-r--r--packages/backend/src/server/api/AuthenticateService.ts14
-rw-r--r--packages/backend/src/server/api/EndpointsModule.ts24
-rw-r--r--packages/backend/src/server/api/StreamingApiServerService.ts29
-rw-r--r--packages/backend/src/server/api/endpoints.ts12
-rw-r--r--packages/backend/src/server/api/endpoints/admin/accounts/delete.ts5
-rw-r--r--packages/backend/src/server/api/endpoints/admin/emoji/add-aliases-bulk.ts34
-rw-r--r--packages/backend/src/server/api/endpoints/admin/emoji/copy.ts2
-rw-r--r--packages/backend/src/server/api/endpoints/admin/emoji/delete-bulk.ts35
-rw-r--r--packages/backend/src/server/api/endpoints/admin/emoji/delete.ts36
-rw-r--r--packages/backend/src/server/api/endpoints/admin/emoji/remove-aliases-bulk.ts34
-rw-r--r--packages/backend/src/server/api/endpoints/admin/emoji/set-aliases-bulk.ts30
-rw-r--r--packages/backend/src/server/api/endpoints/admin/emoji/set-category-bulk.ts30
-rw-r--r--packages/backend/src/server/api/endpoints/admin/emoji/update.ts46
-rw-r--r--packages/backend/src/server/api/endpoints/admin/suspend-user.ts21
-rw-r--r--packages/backend/src/server/api/endpoints/antennas/notes.ts46
-rw-r--r--packages/backend/src/server/api/endpoints/channels/favorite.ts61
-rw-r--r--packages/backend/src/server/api/endpoints/channels/follow.ts3
-rw-r--r--packages/backend/src/server/api/endpoints/channels/my-favorites.ts54
-rw-r--r--packages/backend/src/server/api/endpoints/channels/show.ts2
-rw-r--r--packages/backend/src/server/api/endpoints/channels/timeline.ts82
-rw-r--r--packages/backend/src/server/api/endpoints/channels/unfavorite.ts56
-rw-r--r--packages/backend/src/server/api/endpoints/channels/unfollow.ts4
-rw-r--r--packages/backend/src/server/api/endpoints/channels/update.ts11
-rw-r--r--packages/backend/src/server/api/endpoints/clips/notes.ts6
-rw-r--r--packages/backend/src/server/api/endpoints/emojis.ts4
-rw-r--r--packages/backend/src/server/api/endpoints/i/known-as.ts92
-rw-r--r--packages/backend/src/server/api/endpoints/i/move.ts140
-rw-r--r--packages/backend/src/server/api/endpoints/i/notifications.ts106
-rw-r--r--packages/backend/src/server/api/endpoints/i/regenerate-token.ts7
-rw-r--r--packages/backend/src/server/api/endpoints/i/revoke-token.ts3
-rw-r--r--packages/backend/src/server/api/endpoints/i/update.ts19
-rw-r--r--packages/backend/src/server/api/endpoints/mute/create.ts18
-rw-r--r--packages/backend/src/server/api/endpoints/mute/delete.ts13
-rw-r--r--packages/backend/src/server/api/endpoints/notes.ts8
-rw-r--r--packages/backend/src/server/api/endpoints/notes/children.ts8
-rw-r--r--packages/backend/src/server/api/endpoints/notes/featured.ts8
-rw-r--r--packages/backend/src/server/api/endpoints/notes/global-timeline.ts8
-rw-r--r--packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts6
-rw-r--r--packages/backend/src/server/api/endpoints/notes/local-timeline.ts8
-rw-r--r--packages/backend/src/server/api/endpoints/notes/mentions.ts8
-rw-r--r--packages/backend/src/server/api/endpoints/notes/reactions.ts2
-rw-r--r--packages/backend/src/server/api/endpoints/notes/renotes.ts10
-rw-r--r--packages/backend/src/server/api/endpoints/notes/replies.ts8
-rw-r--r--packages/backend/src/server/api/endpoints/notes/search-by-tag.ts8
-rw-r--r--packages/backend/src/server/api/endpoints/notes/search.ts8
-rw-r--r--packages/backend/src/server/api/endpoints/notes/state.ts2
-rw-r--r--packages/backend/src/server/api/endpoints/notes/timeline.ts8
-rw-r--r--packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts6
-rw-r--r--packages/backend/src/server/api/endpoints/notifications/mark-all-as-read.ts22
-rw-r--r--packages/backend/src/server/api/endpoints/notifications/read.ts57
-rw-r--r--packages/backend/src/server/api/endpoints/renote-mute/create.ts2
-rw-r--r--packages/backend/src/server/api/endpoints/renote-mute/delete.ts2
-rw-r--r--packages/backend/src/server/api/endpoints/users/notes.ts8
-rw-r--r--packages/backend/src/server/api/stream/channel.ts12
-rw-r--r--packages/backend/src/server/api/stream/channels/antenna.ts6
-rw-r--r--packages/backend/src/server/api/stream/channels/channel.ts6
-rw-r--r--packages/backend/src/server/api/stream/channels/global-timeline.ts6
-rw-r--r--packages/backend/src/server/api/stream/channels/hashtag.ts6
-rw-r--r--packages/backend/src/server/api/stream/channels/home-timeline.ts11
-rw-r--r--packages/backend/src/server/api/stream/channels/hybrid-timeline.ts8
-rw-r--r--packages/backend/src/server/api/stream/channels/local-timeline.ts6
-rw-r--r--packages/backend/src/server/api/stream/channels/main.ts4
-rw-r--r--packages/backend/src/server/api/stream/channels/user-list.ts6
-rw-r--r--packages/backend/src/server/api/stream/index.ts193
-rw-r--r--packages/backend/src/server/api/stream/types.ts25
65 files changed, 732 insertions, 833 deletions
diff --git a/packages/backend/src/server/api/AuthenticateService.ts b/packages/backend/src/server/api/AuthenticateService.ts
index a1895e3705..6548c475b2 100644
--- a/packages/backend/src/server/api/AuthenticateService.ts
+++ b/packages/backend/src/server/api/AuthenticateService.ts
@@ -3,9 +3,9 @@ import { DI } from '@/di-symbols.js';
import type { AccessTokensRepository, AppsRepository, UsersRepository } from '@/models/index.js';
import type { LocalUser } from '@/models/entities/User.js';
import type { AccessToken } from '@/models/entities/AccessToken.js';
-import { KVCache } from '@/misc/cache.js';
+import { MemoryKVCache } from '@/misc/cache.js';
import type { App } from '@/models/entities/App.js';
-import { UserCacheService } from '@/core/UserCacheService.js';
+import { CacheService } from '@/core/CacheService.js';
import isNativeToken from '@/misc/is-native-token.js';
import { bindThis } from '@/decorators.js';
@@ -18,7 +18,7 @@ export class AuthenticationError extends Error {
@Injectable()
export class AuthenticateService {
- private appCache: KVCache<App>;
+ private appCache: MemoryKVCache<App>;
constructor(
@Inject(DI.usersRepository)
@@ -30,9 +30,9 @@ export class AuthenticateService {
@Inject(DI.appsRepository)
private appsRepository: AppsRepository,
- private userCacheService: UserCacheService,
+ private cacheService: CacheService,
) {
- this.appCache = new KVCache<App>(Infinity);
+ this.appCache = new MemoryKVCache<App>(Infinity);
}
@bindThis
@@ -42,7 +42,7 @@ export class AuthenticateService {
}
if (isNativeToken(token)) {
- const user = await this.userCacheService.localUserByNativeTokenCache.fetch(token,
+ const user = await this.cacheService.localUserByNativeTokenCache.fetch(token,
() => this.usersRepository.findOneBy({ token }) as Promise<LocalUser | null>);
if (user == null) {
@@ -67,7 +67,7 @@ export class AuthenticateService {
lastUsedAt: new Date(),
});
- const user = await this.userCacheService.localUserByIdCache.fetch(accessToken.userId,
+ const user = await this.cacheService.localUserByIdCache.fetch(accessToken.userId,
() => this.usersRepository.findOneBy({
id: accessToken.userId,
}) as Promise<LocalUser>);
diff --git a/packages/backend/src/server/api/EndpointsModule.ts b/packages/backend/src/server/api/EndpointsModule.ts
index 835e884193..5a53b3faf7 100644
--- a/packages/backend/src/server/api/EndpointsModule.ts
+++ b/packages/backend/src/server/api/EndpointsModule.ts
@@ -95,6 +95,9 @@ import * as ep___channels_show from './endpoints/channels/show.js';
import * as ep___channels_timeline from './endpoints/channels/timeline.js';
import * as ep___channels_unfollow from './endpoints/channels/unfollow.js';
import * as ep___channels_update from './endpoints/channels/update.js';
+import * as ep___channels_favorite from './endpoints/channels/favorite.js';
+import * as ep___channels_unfavorite from './endpoints/channels/unfavorite.js';
+import * as ep___channels_myFavorites from './endpoints/channels/my-favorites.js';
import * as ep___charts_activeUsers from './endpoints/charts/active-users.js';
import * as ep___charts_apRequest from './endpoints/charts/ap-request.js';
import * as ep___charts_drive from './endpoints/charts/drive.js';
@@ -217,6 +220,8 @@ import * as ep___i_signinHistory from './endpoints/i/signin-history.js';
import * as ep___i_unpin from './endpoints/i/unpin.js';
import * as ep___i_updateEmail from './endpoints/i/update-email.js';
import * as ep___i_update from './endpoints/i/update.js';
+import * as ep___i_move from './endpoints/i/move.js';
+import * as ep___i_knownAs from './endpoints/i/known-as.js';
import * as ep___i_webhooks_create from './endpoints/i/webhooks/create.js';
import * as ep___i_webhooks_show from './endpoints/i/webhooks/show.js';
import * as ep___i_webhooks_list from './endpoints/i/webhooks/list.js';
@@ -265,7 +270,6 @@ import * as ep___notes_unrenote from './endpoints/notes/unrenote.js';
import * as ep___notes_userListTimeline from './endpoints/notes/user-list-timeline.js';
import * as ep___notifications_create from './endpoints/notifications/create.js';
import * as ep___notifications_markAllAsRead from './endpoints/notifications/mark-all-as-read.js';
-import * as ep___notifications_read from './endpoints/notifications/read.js';
import * as ep___pagePush from './endpoints/page-push.js';
import * as ep___pages_create from './endpoints/pages/create.js';
import * as ep___pages_delete from './endpoints/pages/delete.js';
@@ -424,6 +428,9 @@ const $channels_show: Provider = { provide: 'ep:channels/show', useClass: ep___c
const $channels_timeline: Provider = { provide: 'ep:channels/timeline', useClass: ep___channels_timeline.default };
const $channels_unfollow: Provider = { provide: 'ep:channels/unfollow', useClass: ep___channels_unfollow.default };
const $channels_update: Provider = { provide: 'ep:channels/update', useClass: ep___channels_update.default };
+const $channels_favorite: Provider = { provide: 'ep:channels/favorite', useClass: ep___channels_favorite.default };
+const $channels_unfavorite: Provider = { provide: 'ep:channels/unfavorite', useClass: ep___channels_unfavorite.default };
+const $channels_myFavorites: Provider = { provide: 'ep:channels/my-favorites', useClass: ep___channels_myFavorites.default };
const $charts_activeUsers: Provider = { provide: 'ep:charts/active-users', useClass: ep___charts_activeUsers.default };
const $charts_apRequest: Provider = { provide: 'ep:charts/ap-request', useClass: ep___charts_apRequest.default };
const $charts_drive: Provider = { provide: 'ep:charts/drive', useClass: ep___charts_drive.default };
@@ -546,6 +553,8 @@ const $i_signinHistory: Provider = { provide: 'ep:i/signin-history', useClass: e
const $i_unpin: Provider = { provide: 'ep:i/unpin', useClass: ep___i_unpin.default };
const $i_updateEmail: Provider = { provide: 'ep:i/update-email', useClass: ep___i_updateEmail.default };
const $i_update: Provider = { provide: 'ep:i/update', useClass: ep___i_update.default };
+const $i_move: Provider = { provide: 'ep:i/move', useClass: ep___i_move.default };
+const $i_knownAs: Provider = { provide: 'ep:i/known-as', useClass: ep___i_knownAs.default };
const $i_webhooks_create: Provider = { provide: 'ep:i/webhooks/create', useClass: ep___i_webhooks_create.default };
const $i_webhooks_list: Provider = { provide: 'ep:i/webhooks/list', useClass: ep___i_webhooks_list.default };
const $i_webhooks_show: Provider = { provide: 'ep:i/webhooks/show', useClass: ep___i_webhooks_show.default };
@@ -594,7 +603,6 @@ const $notes_unrenote: Provider = { provide: 'ep:notes/unrenote', useClass: ep__
const $notes_userListTimeline: Provider = { provide: 'ep:notes/user-list-timeline', useClass: ep___notes_userListTimeline.default };
const $notifications_create: Provider = { provide: 'ep:notifications/create', useClass: ep___notifications_create.default };
const $notifications_markAllAsRead: Provider = { provide: 'ep:notifications/mark-all-as-read', useClass: ep___notifications_markAllAsRead.default };
-const $notifications_read: Provider = { provide: 'ep:notifications/read', useClass: ep___notifications_read.default };
const $pagePush: Provider = { provide: 'ep:page-push', useClass: ep___pagePush.default };
const $pages_create: Provider = { provide: 'ep:pages/create', useClass: ep___pages_create.default };
const $pages_delete: Provider = { provide: 'ep:pages/delete', useClass: ep___pages_delete.default };
@@ -757,6 +765,9 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
$channels_timeline,
$channels_unfollow,
$channels_update,
+ $channels_favorite,
+ $channels_unfavorite,
+ $channels_myFavorites,
$charts_activeUsers,
$charts_apRequest,
$charts_drive,
@@ -879,6 +890,8 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
$i_unpin,
$i_updateEmail,
$i_update,
+ $i_move,
+ $i_knownAs,
$i_webhooks_create,
$i_webhooks_list,
$i_webhooks_show,
@@ -927,7 +940,6 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
$notes_userListTimeline,
$notifications_create,
$notifications_markAllAsRead,
- $notifications_read,
$pagePush,
$pages_create,
$pages_delete,
@@ -1084,6 +1096,9 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
$channels_timeline,
$channels_unfollow,
$channels_update,
+ $channels_favorite,
+ $channels_unfavorite,
+ $channels_myFavorites,
$charts_activeUsers,
$charts_apRequest,
$charts_drive,
@@ -1206,6 +1221,8 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
$i_unpin,
$i_updateEmail,
$i_update,
+ $i_move,
+ $i_knownAs,
$i_webhooks_create,
$i_webhooks_list,
$i_webhooks_show,
@@ -1254,7 +1271,6 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
$notes_userListTimeline,
$notifications_create,
$notifications_markAllAsRead,
- $notifications_read,
$pagePush,
$pages_create,
$pages_delete,
diff --git a/packages/backend/src/server/api/StreamingApiServerService.ts b/packages/backend/src/server/api/StreamingApiServerService.ts
index 13526f277d..e0e5b71a82 100644
--- a/packages/backend/src/server/api/StreamingApiServerService.ts
+++ b/packages/backend/src/server/api/StreamingApiServerService.ts
@@ -9,6 +9,7 @@ import { NoteReadService } from '@/core/NoteReadService.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
import { NotificationService } from '@/core/NotificationService.js';
import { bindThis } from '@/decorators.js';
+import { CacheService } from '@/core/CacheService.js';
import { AuthenticateService } from './AuthenticateService.js';
import MainStreamConnection from './stream/index.js';
import { ChannelsService } from './stream/ChannelsService.js';
@@ -21,8 +22,8 @@ export class StreamingApiServerService {
@Inject(DI.config)
private config: Config,
- @Inject(DI.redisSubscriber)
- private redisSubscriber: Redis.Redis,
+ @Inject(DI.redisForPubsub)
+ private redisForPubsub: Redis.Redis,
@Inject(DI.usersRepository)
private usersRepository: UsersRepository,
@@ -45,7 +46,7 @@ export class StreamingApiServerService {
@Inject(DI.userProfilesRepository)
private userProfilesRepository: UserProfilesRepository,
- private globalEventService: GlobalEventService,
+ private cacheService: CacheService,
private noteReadService: NoteReadService,
private authenticateService: AuthenticateService,
private channelsService: ChannelsService,
@@ -73,8 +74,6 @@ export class StreamingApiServerService {
return;
}
- const connection = request.accept();
-
const ev = new EventEmitter();
async function onRedisMessage(_: string, data: string): Promise<void> {
@@ -82,22 +81,22 @@ export class StreamingApiServerService {
ev.emit(parsed.channel, parsed.message);
}
- this.redisSubscriber.on('message', onRedisMessage);
+ this.redisForPubsub.on('message', onRedisMessage);
const main = new MainStreamConnection(
- this.followingsRepository,
- this.mutingsRepository,
- this.renoteMutingsRepository,
- this.blockingsRepository,
- this.channelFollowingsRepository,
- this.userProfilesRepository,
this.channelsService,
- this.globalEventService,
this.noteReadService,
this.notificationService,
- connection, ev, user, miapp,
+ this.cacheService,
+ ev, user, miapp,
);
+ await main.init();
+
+ const connection = request.accept();
+
+ main.init2(connection);
+
const intervalId = user ? setInterval(() => {
this.usersRepository.update(user.id, {
lastActiveDate: new Date(),
@@ -112,7 +111,7 @@ export class StreamingApiServerService {
connection.once('close', () => {
ev.removeAllListeners();
main.dispose();
- this.redisSubscriber.off('message', onRedisMessage);
+ this.redisForPubsub.off('message', onRedisMessage);
if (intervalId) clearInterval(intervalId);
});
diff --git a/packages/backend/src/server/api/endpoints.ts b/packages/backend/src/server/api/endpoints.ts
index f6fc79fc70..fd268c7912 100644
--- a/packages/backend/src/server/api/endpoints.ts
+++ b/packages/backend/src/server/api/endpoints.ts
@@ -95,6 +95,9 @@ import * as ep___channels_show from './endpoints/channels/show.js';
import * as ep___channels_timeline from './endpoints/channels/timeline.js';
import * as ep___channels_unfollow from './endpoints/channels/unfollow.js';
import * as ep___channels_update from './endpoints/channels/update.js';
+import * as ep___channels_favorite from './endpoints/channels/favorite.js';
+import * as ep___channels_unfavorite from './endpoints/channels/unfavorite.js';
+import * as ep___channels_myFavorites from './endpoints/channels/my-favorites.js';
import * as ep___charts_activeUsers from './endpoints/charts/active-users.js';
import * as ep___charts_apRequest from './endpoints/charts/ap-request.js';
import * as ep___charts_drive from './endpoints/charts/drive.js';
@@ -217,6 +220,8 @@ import * as ep___i_signinHistory from './endpoints/i/signin-history.js';
import * as ep___i_unpin from './endpoints/i/unpin.js';
import * as ep___i_updateEmail from './endpoints/i/update-email.js';
import * as ep___i_update from './endpoints/i/update.js';
+import * as ep___i_move from './endpoints/i/move.js';
+import * as ep___i_knownAs from './endpoints/i/known-as.js';
import * as ep___i_webhooks_create from './endpoints/i/webhooks/create.js';
import * as ep___i_webhooks_show from './endpoints/i/webhooks/show.js';
import * as ep___i_webhooks_list from './endpoints/i/webhooks/list.js';
@@ -265,7 +270,6 @@ import * as ep___notes_unrenote from './endpoints/notes/unrenote.js';
import * as ep___notes_userListTimeline from './endpoints/notes/user-list-timeline.js';
import * as ep___notifications_create from './endpoints/notifications/create.js';
import * as ep___notifications_markAllAsRead from './endpoints/notifications/mark-all-as-read.js';
-import * as ep___notifications_read from './endpoints/notifications/read.js';
import * as ep___pagePush from './endpoints/page-push.js';
import * as ep___pages_create from './endpoints/pages/create.js';
import * as ep___pages_delete from './endpoints/pages/delete.js';
@@ -422,6 +426,9 @@ const eps = [
['channels/timeline', ep___channels_timeline],
['channels/unfollow', ep___channels_unfollow],
['channels/update', ep___channels_update],
+ ['channels/favorite', ep___channels_favorite],
+ ['channels/unfavorite', ep___channels_unfavorite],
+ ['channels/my-favorites', ep___channels_myFavorites],
['charts/active-users', ep___charts_activeUsers],
['charts/ap-request', ep___charts_apRequest],
['charts/drive', ep___charts_drive],
@@ -544,6 +551,8 @@ const eps = [
['i/unpin', ep___i_unpin],
['i/update-email', ep___i_updateEmail],
['i/update', ep___i_update],
+ //['i/move', ep___i_move],
+ //['i/known-as', ep___i_knownAs],
['i/webhooks/create', ep___i_webhooks_create],
['i/webhooks/list', ep___i_webhooks_list],
['i/webhooks/show', ep___i_webhooks_show],
@@ -592,7 +601,6 @@ const eps = [
['notes/user-list-timeline', ep___notes_userListTimeline],
['notifications/create', ep___notifications_create],
['notifications/mark-all-as-read', ep___notifications_markAllAsRead],
- ['notifications/read', ep___notifications_read],
['page-push', ep___pagePush],
['pages/create', ep___pages_create],
['pages/delete', ep___pages_delete],
diff --git a/packages/backend/src/server/api/endpoints/admin/accounts/delete.ts b/packages/backend/src/server/api/endpoints/admin/accounts/delete.ts
index e9f72676f0..16232813a8 100644
--- a/packages/backend/src/server/api/endpoints/admin/accounts/delete.ts
+++ b/packages/backend/src/server/api/endpoints/admin/accounts/delete.ts
@@ -61,11 +61,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
await this.usersRepository.update(user.id, {
isDeleted: true,
});
-
- if (this.userEntityService.isLocalUser(user)) {
- // Terminate streaming
- this.globalEventService.publishUserEvent(user.id, 'terminate', {});
- }
});
}
}
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/add-aliases-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/add-aliases-bulk.ts
index 4e4f845b0b..6e604ed885 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/add-aliases-bulk.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/add-aliases-bulk.ts
@@ -1,10 +1,6 @@
import { Inject, Injectable } from '@nestjs/common';
-import { DataSource, In } from 'typeorm';
import { Endpoint } from '@/server/api/endpoint-base.js';
-import type { EmojisRepository } from '@/models/index.js';
-import { DI } from '@/di-symbols.js';
-import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js';
-import { GlobalEventService } from '@/core/GlobalEventService.js';
+import { CustomEmojiService } from '@/core/CustomEmojiService.js';
export const meta = {
tags: ['admin'],
@@ -26,38 +22,14 @@ export const paramDef = {
required: ['ids', 'aliases'],
} as const;
-// TODO: ロジックをサービスに切り出す
-
// eslint-disable-next-line import/no-default-export
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> {
constructor(
- @Inject(DI.db)
- private db: DataSource,
-
- @Inject(DI.emojisRepository)
- private emojisRepository: EmojisRepository,
-
- private emojiEntityService: EmojiEntityService,
- private globalEventService: GlobalEventService,
+ private customEmojiService: CustomEmojiService,
) {
super(meta, paramDef, async (ps, me) => {
- const emojis = await this.emojisRepository.findBy({
- id: In(ps.ids),
- });
-
- for (const emoji of emojis) {
- await this.emojisRepository.update(emoji.id, {
- updatedAt: new Date(),
- aliases: [...new Set(emoji.aliases.concat(ps.aliases))],
- });
- }
-
- await this.db.queryResultCache?.remove(['meta_emojis']);
-
- this.globalEventService.publishBroadcastStream('emojiUpdated', {
- emojis: await this.emojiEntityService.packDetailedMany(ps.ids),
- });
+ await this.customEmojiService.addAliasesBulk(ps.ids, ps.aliases);
});
}
}
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts b/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts
index fea11a67d6..82dca9cc70 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts
@@ -90,8 +90,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
license: emoji.license,
}).then(x => this.emojisRepository.findOneByOrFail(x.identifiers[0]));
- await this.db.queryResultCache?.remove(['meta_emojis']);
-
this.globalEventService.publishBroadcastStream('emojiAdded', {
emoji: await this.emojiEntityService.packDetailed(copied.id),
});
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/delete-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/delete-bulk.ts
index 84aad020af..9f8263629b 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/delete-bulk.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/delete-bulk.ts
@@ -1,11 +1,6 @@
import { Inject, Injectable } from '@nestjs/common';
-import { DataSource, In } from 'typeorm';
import { Endpoint } from '@/server/api/endpoint-base.js';
-import type { EmojisRepository } from '@/models/index.js';
-import { DI } from '@/di-symbols.js';
-import { ModerationLogService } from '@/core/ModerationLogService.js';
-import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js';
-import { GlobalEventService } from '@/core/GlobalEventService.js';
+import { CustomEmojiService } from '@/core/CustomEmojiService.js';
export const meta = {
tags: ['admin'],
@@ -24,38 +19,14 @@ export const paramDef = {
required: ['ids'],
} as const;
-// TODO: ロジックをサービスに切り出す
-
// eslint-disable-next-line import/no-default-export
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> {
constructor(
- @Inject(DI.db)
- private db: DataSource,
-
- @Inject(DI.emojisRepository)
- private emojisRepository: EmojisRepository,
-
- private moderationLogService: ModerationLogService,
- private emojiEntityService: EmojiEntityService,
- private globalEventService: GlobalEventService,
+ private customEmojiService: CustomEmojiService,
) {
super(meta, paramDef, async (ps, me) => {
- const emojis = await this.emojisRepository.findBy({
- id: In(ps.ids),
- });
-
- for (const emoji of emojis) {
- await this.emojisRepository.delete(emoji.id);
- await this.db.queryResultCache?.remove(['meta_emojis']);
- this.moderationLogService.insertModerationLog(me, 'deleteEmoji', {
- emoji: emoji,
- });
- }
-
- this.globalEventService.publishBroadcastStream('emojiDeleted', {
- emojis: await this.emojiEntityService.packDetailedMany(emojis),
- });
+ await this.customEmojiService.deleteBulk(ps.ids);
});
}
}
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/delete.ts b/packages/backend/src/server/api/endpoints/admin/emoji/delete.ts
index 90a5856a1b..429c819fe0 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/delete.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/delete.ts
@@ -1,12 +1,6 @@
import { Inject, Injectable } from '@nestjs/common';
-import { DataSource } from 'typeorm';
import { Endpoint } from '@/server/api/endpoint-base.js';
-import type { EmojisRepository } from '@/models/index.js';
-import { DI } from '@/di-symbols.js';
-import { ModerationLogService } from '@/core/ModerationLogService.js';
-import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js';
-import { GlobalEventService } from '@/core/GlobalEventService.js';
-import { ApiError } from '../../../error.js';
+import { CustomEmojiService } from '@/core/CustomEmojiService.js';
export const meta = {
tags: ['admin'],
@@ -31,38 +25,14 @@ export const paramDef = {
required: ['id'],
} as const;
-// TODO: ロジックをサービスに切り出す
-
// eslint-disable-next-line import/no-default-export
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> {
constructor(
- @Inject(DI.db)
- private db: DataSource,
-
- @Inject(DI.emojisRepository)
- private emojisRepository: EmojisRepository,
-
- private moderationLogService: ModerationLogService,
- private emojiEntityService: EmojiEntityService,
- private globalEventService: GlobalEventService,
+ private customEmojiService: CustomEmojiService,
) {
super(meta, paramDef, async (ps, me) => {
- const emoji = await this.emojisRepository.findOneBy({ id: ps.id });
-
- if (emoji == null) throw new ApiError(meta.errors.noSuchEmoji);
-
- await this.emojisRepository.delete(emoji.id);
-
- await this.db.queryResultCache?.remove(['meta_emojis']);
-
- this.globalEventService.publishBroadcastStream('emojiDeleted', {
- emojis: [await this.emojiEntityService.packDetailed(emoji)],
- });
-
- this.moderationLogService.insertModerationLog(me, 'deleteEmoji', {
- emoji: emoji,
- });
+ await this.customEmojiService.delete(ps.id);
});
}
}
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/remove-aliases-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/remove-aliases-bulk.ts
index 3935183502..83f882cac5 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/remove-aliases-bulk.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/remove-aliases-bulk.ts
@@ -1,10 +1,6 @@
import { Inject, Injectable } from '@nestjs/common';
-import { DataSource, In } from 'typeorm';
import { Endpoint } from '@/server/api/endpoint-base.js';
-import type { EmojisRepository } from '@/models/index.js';
-import { DI } from '@/di-symbols.js';
-import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js';
-import { GlobalEventService } from '@/core/GlobalEventService.js';
+import { CustomEmojiService } from '@/core/CustomEmojiService.js';
export const meta = {
tags: ['admin'],
@@ -26,38 +22,14 @@ export const paramDef = {
required: ['ids', 'aliases'],
} as const;
-// TODO: ロジックをサービスに切り出す
-
// eslint-disable-next-line import/no-default-export
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> {
constructor(
- @Inject(DI.db)
- private db: DataSource,
-
- @Inject(DI.emojisRepository)
- private emojisRepository: EmojisRepository,
-
- private emojiEntityService: EmojiEntityService,
- private globalEventService: GlobalEventService,
+ private customEmojiService: CustomEmojiService,
) {
super(meta, paramDef, async (ps, me) => {
- const emojis = await this.emojisRepository.findBy({
- id: In(ps.ids),
- });
-
- for (const emoji of emojis) {
- await this.emojisRepository.update(emoji.id, {
- updatedAt: new Date(),
- aliases: emoji.aliases.filter(x => !ps.aliases.includes(x)),
- });
- }
-
- await this.db.queryResultCache?.remove(['meta_emojis']);
-
- this.globalEventService.publishBroadcastStream('emojiUpdated', {
- emojis: await this.emojiEntityService.packDetailedMany(ps.ids),
- });
+ await this.customEmojiService.removeAliasesBulk(ps.ids, ps.aliases);
});
}
}
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/set-aliases-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/set-aliases-bulk.ts
index 6a875f9c83..1d3a432bb7 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/set-aliases-bulk.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/set-aliases-bulk.ts
@@ -1,10 +1,6 @@
import { Inject, Injectable } from '@nestjs/common';
-import { DataSource, In } from 'typeorm';
import { Endpoint } from '@/server/api/endpoint-base.js';
-import type { EmojisRepository } from '@/models/index.js';
-import { DI } from '@/di-symbols.js';
-import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js';
-import { GlobalEventService } from '@/core/GlobalEventService.js';
+import { CustomEmojiService } from '@/core/CustomEmojiService.js';
export const meta = {
tags: ['admin'],
@@ -26,34 +22,14 @@ export const paramDef = {
required: ['ids', 'aliases'],
} as const;
-// TODO: ロジックをサービスに切り出す
-
// eslint-disable-next-line import/no-default-export
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> {
constructor(
- @Inject(DI.db)
- private db: DataSource,
-
- @Inject(DI.emojisRepository)
- private emojisRepository: EmojisRepository,
-
- private emojiEntityService: EmojiEntityService,
- private globalEventService: GlobalEventService,
+ private customEmojiService: CustomEmojiService,
) {
super(meta, paramDef, async (ps, me) => {
- await this.emojisRepository.update({
- id: In(ps.ids),
- }, {
- updatedAt: new Date(),
- aliases: ps.aliases,
- });
-
- await this.db.queryResultCache?.remove(['meta_emojis']);
-
- this.globalEventService.publishBroadcastStream('emojiUpdated', {
- emojis: await this.emojiEntityService.packDetailedMany(ps.ids),
- });
+ await this.customEmojiService.setAliasesBulk(ps.ids, ps.aliases);
});
}
}
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/set-category-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/set-category-bulk.ts
index d3b999c0ed..453968c7a9 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/set-category-bulk.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/set-category-bulk.ts
@@ -1,10 +1,6 @@
import { Inject, Injectable } from '@nestjs/common';
-import { DataSource, In } from 'typeorm';
import { Endpoint } from '@/server/api/endpoint-base.js';
-import type { EmojisRepository } from '@/models/index.js';
-import { DI } from '@/di-symbols.js';
-import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js';
-import { GlobalEventService } from '@/core/GlobalEventService.js';
+import { CustomEmojiService } from '@/core/CustomEmojiService.js';
export const meta = {
tags: ['admin'],
@@ -28,34 +24,14 @@ export const paramDef = {
required: ['ids'],
} as const;
-// TODO: ロジックをサービスに切り出す
-
// eslint-disable-next-line import/no-default-export
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> {
constructor(
- @Inject(DI.db)
- private db: DataSource,
-
- @Inject(DI.emojisRepository)
- private emojisRepository: EmojisRepository,
-
- private emojiEntityService: EmojiEntityService,
- private globalEventService: GlobalEventService,
+ private customEmojiService: CustomEmojiService,
) {
super(meta, paramDef, async (ps, me) => {
- await this.emojisRepository.update({
- id: In(ps.ids),
- }, {
- updatedAt: new Date(),
- category: ps.category,
- });
-
- await this.db.queryResultCache?.remove(['meta_emojis']);
-
- this.globalEventService.publishBroadcastStream('emojiUpdated', {
- emojis: await this.emojiEntityService.packDetailedMany(ps.ids),
- });
+ await this.customEmojiService.setCategoryBulk(ps.ids, ps.category ?? null);
});
}
}
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/update.ts b/packages/backend/src/server/api/endpoints/admin/emoji/update.ts
index bc0475e05c..f63348b60b 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/update.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/update.ts
@@ -1,10 +1,6 @@
import { Inject, Injectable } from '@nestjs/common';
-import { DataSource, IsNull } from 'typeorm';
import { Endpoint } from '@/server/api/endpoint-base.js';
-import type { EmojisRepository } from '@/models/index.js';
-import { DI } from '@/di-symbols.js';
-import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js';
-import { GlobalEventService } from '@/core/GlobalEventService.js';
+import { CustomEmojiService } from '@/core/CustomEmojiService.js';
import { ApiError } from '../../../error.js';
export const meta = {
@@ -45,51 +41,19 @@ export const paramDef = {
required: ['id', 'name', 'aliases'],
} as const;
-// TODO: ロジックをサービスに切り出す
-
// eslint-disable-next-line import/no-default-export
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> {
constructor(
- @Inject(DI.db)
- private db: DataSource,
-
- @Inject(DI.emojisRepository)
- private emojisRepository: EmojisRepository,
-
- private emojiEntityService: EmojiEntityService,
- private globalEventService: GlobalEventService,
+ private customEmojiService: CustomEmojiService,
) {
super(meta, paramDef, async (ps, me) => {
- const emoji = await this.emojisRepository.findOneBy({ id: ps.id });
- const sameNameEmoji = await this.emojisRepository.findOneBy({ name: ps.name, host: IsNull() });
- if (emoji == null) throw new ApiError(meta.errors.noSuchEmoji);
- if (sameNameEmoji != null && sameNameEmoji.id !== ps.id) throw new ApiError(meta.errors.sameNameEmojiExists);
- await this.emojisRepository.update(emoji.id, {
- updatedAt: new Date(),
+ await this.customEmojiService.update(ps.id, {
name: ps.name,
- category: ps.category,
+ category: ps.category ?? null,
aliases: ps.aliases,
- license: ps.license,
+ license: ps.license ?? null,
});
-
- await this.db.queryResultCache?.remove(['meta_emojis']);
-
- const updated = await this.emojiEntityService.packDetailed(emoji.id);
-
- if (emoji.name === ps.name) {
- this.globalEventService.publishBroadcastStream('emojiUpdated', {
- emojis: [updated],
- });
- } else {
- this.globalEventService.publishBroadcastStream('emojiDeleted', {
- emojis: [await this.emojiEntityService.packDetailed(emoji)],
- });
-
- this.globalEventService.publishBroadcastStream('emojiAdded', {
- emoji: updated,
- });
- }
});
}
}
diff --git a/packages/backend/src/server/api/endpoints/admin/suspend-user.ts b/packages/backend/src/server/api/endpoints/admin/suspend-user.ts
index 3ad6c7c484..3c99225272 100644
--- a/packages/backend/src/server/api/endpoints/admin/suspend-user.ts
+++ b/packages/backend/src/server/api/endpoints/admin/suspend-user.ts
@@ -1,6 +1,6 @@
import { Inject, Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
-import type { UsersRepository, FollowingsRepository, NotificationsRepository } from '@/models/index.js';
+import type { UsersRepository, FollowingsRepository } from '@/models/index.js';
import type { User } from '@/models/entities/User.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
import { ModerationLogService } from '@/core/ModerationLogService.js';
@@ -36,9 +36,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
@Inject(DI.followingsRepository)
private followingsRepository: FollowingsRepository,
- @Inject(DI.notificationsRepository)
- private notificationsRepository: NotificationsRepository,
-
private userEntityService: UserEntityService,
private userFollowingService: UserFollowingService,
private userSuspendService: UserSuspendService,
@@ -65,15 +62,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
targetId: user.id,
});
- // Terminate streaming
- if (this.userEntityService.isLocalUser(user)) {
- this.globalEventService.publishUserEvent(user.id, 'terminate', {});
- }
-
(async () => {
await this.userSuspendService.doPostSuspend(user).catch(e => {});
await this.unFollowAll(user).catch(e => {});
- await this.readAllNotify(user).catch(e => {});
})();
});
}
@@ -96,14 +87,4 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
await this.userFollowingService.unfollow(follower, followee, true);
}
}
-
- @bindThis
- private async readAllNotify(notifier: User) {
- await this.notificationsRepository.update({
- notifierId: notifier.id,
- isRead: false,
- }, {
- isRead: true,
- });
- }
}
diff --git a/packages/backend/src/server/api/endpoints/antennas/notes.ts b/packages/backend/src/server/api/endpoints/antennas/notes.ts
index 039ba1115a..f08c20ae48 100644
--- a/packages/backend/src/server/api/endpoints/antennas/notes.ts
+++ b/packages/backend/src/server/api/endpoints/antennas/notes.ts
@@ -1,10 +1,12 @@
import { Inject, Injectable } from '@nestjs/common';
+import Redis from 'ioredis';
import { Endpoint } from '@/server/api/endpoint-base.js';
-import type { NotesRepository, AntennaNotesRepository, AntennasRepository } from '@/models/index.js';
+import type { NotesRepository, AntennasRepository } from '@/models/index.js';
import { QueryService } from '@/core/QueryService.js';
import { NoteReadService } from '@/core/NoteReadService.js';
import { DI } from '@/di-symbols.js';
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
+import { IdService } from '@/core/IdService.js';
import { ApiError } from '../../error.js';
export const meta = {
@@ -50,15 +52,16 @@ export const paramDef = {
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> {
constructor(
+ @Inject(DI.redis)
+ private redisClient: Redis.Redis,
+
@Inject(DI.notesRepository)
private notesRepository: NotesRepository,
@Inject(DI.antennasRepository)
private antennasRepository: AntennasRepository,
- @Inject(DI.antennaNotesRepository)
- private antennaNotesRepository: AntennaNotesRepository,
-
+ private idService: IdService,
private noteEntityService: NoteEntityService,
private queryService: QueryService,
private noteReadService: NoteReadService,
@@ -73,29 +76,36 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
throw new ApiError(meta.errors.noSuchAntenna);
}
- const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'),
- ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
- .innerJoin(this.antennaNotesRepository.metadata.targetName, 'antennaNote', 'antennaNote.noteId = note.id')
+ const noteIdsRes = await this.redisClient.xrevrange(
+ `antennaTimeline:${antenna.id}`,
+ ps.untilId ? this.idService.parse(ps.untilId).date.getTime() : '+',
+ '-',
+ 'COUNT', ps.limit + 1); // untilIdに指定したものも含まれるため+1
+
+ if (noteIdsRes.length === 0) {
+ return [];
+ }
+
+ const noteIds = noteIdsRes.map(x => x[1][1]).filter(x => x !== ps.untilId);
+
+ if (noteIds.length === 0) {
+ return [];
+ }
+
+ const query = this.notesRepository.createQueryBuilder('note')
+ .where('note.id IN (:...noteIds)', { noteIds: noteIds })
.innerJoinAndSelect('note.user', 'user')
- .leftJoinAndSelect('user.avatar', 'avatar')
- .leftJoinAndSelect('user.banner', 'banner')
.leftJoinAndSelect('note.reply', 'reply')
.leftJoinAndSelect('note.renote', 'renote')
.leftJoinAndSelect('reply.user', 'replyUser')
- .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar')
- .leftJoinAndSelect('replyUser.banner', 'replyUserBanner')
- .leftJoinAndSelect('renote.user', 'renoteUser')
- .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar')
- .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner')
- .andWhere('antennaNote.antennaId = :antennaId', { antennaId: antenna.id });
+ .leftJoinAndSelect('renote.user', 'renoteUser');
this.queryService.generateVisibilityQuery(query, me);
this.queryService.generateMutedUserQuery(query, me);
this.queryService.generateBlockedUserQuery(query, me);
- const notes = await query
- .take(ps.limit)
- .getMany();
+ const notes = await query.getMany();
+ notes.sort((a, b) => a.id > b.id ? -1 : 1);
if (notes.length > 0) {
this.noteReadService.read(me.id, notes);
diff --git a/packages/backend/src/server/api/endpoints/channels/favorite.ts b/packages/backend/src/server/api/endpoints/channels/favorite.ts
new file mode 100644
index 0000000000..f52b45ccf3
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/channels/favorite.ts
@@ -0,0 +1,61 @@
+import { Inject, Injectable } from '@nestjs/common';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import type { ChannelFavoritesRepository, ChannelsRepository } from '@/models/index.js';
+import { IdService } from '@/core/IdService.js';
+import { DI } from '@/di-symbols.js';
+import { ApiError } from '../../error.js';
+
+export const meta = {
+ tags: ['channels'],
+
+ requireCredential: true,
+
+ kind: 'write:channels',
+
+ errors: {
+ noSuchChannel: {
+ message: 'No such channel.',
+ code: 'NO_SUCH_CHANNEL',
+ id: '4938f5f3-6167-4c04-9149-6607b7542861',
+ },
+ },
+} as const;
+
+export const paramDef = {
+ type: 'object',
+ properties: {
+ channelId: { type: 'string', format: 'misskey:id' },
+ },
+ required: ['channelId'],
+} as const;
+
+// eslint-disable-next-line import/no-default-export
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.channelsRepository)
+ private channelsRepository: ChannelsRepository,
+
+ @Inject(DI.channelFavoritesRepository)
+ private channelFavoritesRepository: ChannelFavoritesRepository,
+
+ private idService: IdService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const channel = await this.channelsRepository.findOneBy({
+ id: ps.channelId,
+ });
+
+ if (channel == null) {
+ throw new ApiError(meta.errors.noSuchChannel);
+ }
+
+ await this.channelFavoritesRepository.insert({
+ id: this.idService.genId(),
+ createdAt: new Date(),
+ userId: me.id,
+ channelId: channel.id,
+ });
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/channels/follow.ts b/packages/backend/src/server/api/endpoints/channels/follow.ts
index 91693918f2..8ab59991c7 100644
--- a/packages/backend/src/server/api/endpoints/channels/follow.ts
+++ b/packages/backend/src/server/api/endpoints/channels/follow.ts
@@ -41,7 +41,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
private channelFollowingsRepository: ChannelFollowingsRepository,
private idService: IdService,
- private globalEventService: GlobalEventService,
) {
super(meta, paramDef, async (ps, me) => {
const channel = await this.channelsRepository.findOneBy({
@@ -58,8 +57,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
followerId: me.id,
followeeId: channel.id,
});
-
- this.globalEventService.publishUserEvent(me.id, 'followChannel', channel);
});
}
}
diff --git a/packages/backend/src/server/api/endpoints/channels/my-favorites.ts b/packages/backend/src/server/api/endpoints/channels/my-favorites.ts
new file mode 100644
index 0000000000..60525ed060
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/channels/my-favorites.ts
@@ -0,0 +1,54 @@
+import { Inject, Injectable } from '@nestjs/common';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import type { ChannelFavoritesRepository } from '@/models/index.js';
+import { QueryService } from '@/core/QueryService.js';
+import { ChannelEntityService } from '@/core/entities/ChannelEntityService.js';
+import { DI } from '@/di-symbols.js';
+
+export const meta = {
+ tags: ['channels', 'account'],
+
+ requireCredential: true,
+
+ kind: 'read:channels',
+
+ res: {
+ type: 'array',
+ optional: false, nullable: false,
+ items: {
+ type: 'object',
+ optional: false, nullable: false,
+ ref: 'Channel',
+ },
+ },
+} as const;
+
+export const paramDef = {
+ type: 'object',
+ properties: {
+ },
+ required: [],
+} as const;
+
+// eslint-disable-next-line import/no-default-export
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.channelFavoritesRepository)
+ private channelFavoritesRepository: ChannelFavoritesRepository,
+
+ private channelEntityService: ChannelEntityService,
+ private queryService: QueryService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const query = this.channelFavoritesRepository.createQueryBuilder('favorite')
+ .andWhere('favorite.userId = :meId', { meId: me.id })
+ .leftJoinAndSelect('favorite.channel', 'channel');
+
+ const favorites = await query
+ .getMany();
+
+ return await Promise.all(favorites.map(x => this.channelEntityService.pack(x.channel!, me)));
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/channels/show.ts b/packages/backend/src/server/api/endpoints/channels/show.ts
index 8718615db2..070d14631e 100644
--- a/packages/backend/src/server/api/endpoints/channels/show.ts
+++ b/packages/backend/src/server/api/endpoints/channels/show.ts
@@ -51,7 +51,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
throw new ApiError(meta.errors.noSuchChannel);
}
- return await this.channelEntityService.pack(channel, me);
+ return await this.channelEntityService.pack(channel, me, true);
});
}
}
diff --git a/packages/backend/src/server/api/endpoints/channels/timeline.ts b/packages/backend/src/server/api/endpoints/channels/timeline.ts
index cdaa400137..2556557b24 100644
--- a/packages/backend/src/server/api/endpoints/channels/timeline.ts
+++ b/packages/backend/src/server/api/endpoints/channels/timeline.ts
@@ -1,10 +1,12 @@
import { Inject, Injectable } from '@nestjs/common';
+import Redis from 'ioredis';
import { Endpoint } from '@/server/api/endpoint-base.js';
-import type { ChannelsRepository, NotesRepository } from '@/models/index.js';
+import type { ChannelsRepository, Note, NotesRepository } from '@/models/index.js';
import { QueryService } from '@/core/QueryService.js';
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
import ActiveUsersChart from '@/core/chart/charts/active-users.js';
import { DI } from '@/di-symbols.js';
+import { IdService } from '@/core/IdService.js';
import { ApiError } from '../../error.js';
export const meta = {
@@ -48,12 +50,16 @@ export const paramDef = {
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> {
constructor(
+ @Inject(DI.redis)
+ private redisClient: Redis.Redis,
+
@Inject(DI.notesRepository)
private notesRepository: NotesRepository,
@Inject(DI.channelsRepository)
private channelsRepository: ChannelsRepository,
+ private idService: IdService,
private noteEntityService: NoteEntityService,
private queryService: QueryService,
private activeUsersChart: ActiveUsersChart,
@@ -67,30 +73,60 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
throw new ApiError(meta.errors.noSuchChannel);
}
- //#region Construct query
- const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
- .andWhere('note.channelId = :channelId', { channelId: channel.id })
- .innerJoinAndSelect('note.user', 'user')
- .leftJoinAndSelect('user.avatar', 'avatar')
- .leftJoinAndSelect('user.banner', 'banner')
- .leftJoinAndSelect('note.reply', 'reply')
- .leftJoinAndSelect('note.renote', 'renote')
- .leftJoinAndSelect('reply.user', 'replyUser')
- .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar')
- .leftJoinAndSelect('replyUser.banner', 'replyUserBanner')
- .leftJoinAndSelect('renote.user', 'renoteUser')
- .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar')
- .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner')
- .leftJoinAndSelect('note.channel', 'channel');
+ let timeline: Note[] = [];
- if (me) {
- this.queryService.generateMutedUserQuery(query, me);
- this.queryService.generateMutedNoteQuery(query, me);
- this.queryService.generateBlockedUserQuery(query, me);
- }
- //#endregion
+ const noteIdsRes = await this.redisClient.xrevrange(
+ `channelTimeline:${channel.id}`,
+ ps.untilId ? this.idService.parse(ps.untilId).date.getTime() : '+',
+ '-',
+ 'COUNT', ps.limit + 1); // untilIdに指定したものも含まれるため+1
+
+ if (noteIdsRes.length === 0) {
+ //#region Construct query
+ const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
+ .andWhere('note.channelId = :channelId', { channelId: channel.id })
+ .innerJoinAndSelect('note.user', 'user')
+ .leftJoinAndSelect('note.reply', 'reply')
+ .leftJoinAndSelect('note.renote', 'renote')
+ .leftJoinAndSelect('reply.user', 'replyUser')
+ .leftJoinAndSelect('renote.user', 'renoteUser')
+ .leftJoinAndSelect('note.channel', 'channel');
+
+ if (me) {
+ this.queryService.generateMutedUserQuery(query, me);
+ this.queryService.generateMutedNoteQuery(query, me);
+ this.queryService.generateBlockedUserQuery(query, me);
+ }
+ //#endregion
+
+ timeline = await query.take(ps.limit).getMany();
+ } else {
+ const noteIds = noteIdsRes.map(x => x[1][1]).filter(x => x !== ps.untilId);
- const timeline = await query.take(ps.limit).getMany();
+ if (noteIds.length === 0) {
+ return [];
+ }
+
+ //#region Construct query
+ const query = this.notesRepository.createQueryBuilder('note')
+ .where('note.id IN (:...noteIds)', { noteIds: noteIds })
+ .innerJoinAndSelect('note.user', 'user')
+ .leftJoinAndSelect('note.reply', 'reply')
+ .leftJoinAndSelect('note.renote', 'renote')
+ .leftJoinAndSelect('reply.user', 'replyUser')
+ .leftJoinAndSelect('renote.user', 'renoteUser')
+ .leftJoinAndSelect('note.channel', 'channel');
+
+ if (me) {
+ this.queryService.generateMutedUserQuery(query, me);
+ this.queryService.generateMutedNoteQuery(query, me);
+ this.queryService.generateBlockedUserQuery(query, me);
+ }
+ //#endregion
+
+ timeline = await query.getMany();
+ timeline.sort((a, b) => a.id > b.id ? -1 : 1);
+ }
if (me) this.activeUsersChart.read(me);
diff --git a/packages/backend/src/server/api/endpoints/channels/unfavorite.ts b/packages/backend/src/server/api/endpoints/channels/unfavorite.ts
new file mode 100644
index 0000000000..0c3f6c4855
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/channels/unfavorite.ts
@@ -0,0 +1,56 @@
+import { Inject, Injectable } from '@nestjs/common';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import type { ChannelFavoritesRepository, ChannelsRepository } from '@/models/index.js';
+import { DI } from '@/di-symbols.js';
+import { ApiError } from '../../error.js';
+
+export const meta = {
+ tags: ['channels'],
+
+ requireCredential: true,
+
+ kind: 'write:channels',
+
+ errors: {
+ noSuchChannel: {
+ message: 'No such channel.',
+ code: 'NO_SUCH_CHANNEL',
+ id: '353c68dd-131a-476c-aa99-88a345e83668',
+ },
+ },
+} as const;
+
+export const paramDef = {
+ type: 'object',
+ properties: {
+ channelId: { type: 'string', format: 'misskey:id' },
+ },
+ required: ['channelId'],
+} as const;
+
+// eslint-disable-next-line import/no-default-export
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.channelsRepository)
+ private channelsRepository: ChannelsRepository,
+
+ @Inject(DI.channelFavoritesRepository)
+ private channelFavoritesRepository: ChannelFavoritesRepository,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const channel = await this.channelsRepository.findOneBy({
+ id: ps.channelId,
+ });
+
+ if (channel == null) {
+ throw new ApiError(meta.errors.noSuchChannel);
+ }
+
+ await this.channelFavoritesRepository.delete({
+ userId: me.id,
+ channelId: channel.id,
+ });
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/channels/unfollow.ts b/packages/backend/src/server/api/endpoints/channels/unfollow.ts
index ac2ef825be..855ba47f8c 100644
--- a/packages/backend/src/server/api/endpoints/channels/unfollow.ts
+++ b/packages/backend/src/server/api/endpoints/channels/unfollow.ts
@@ -38,8 +38,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
@Inject(DI.channelFollowingsRepository)
private channelFollowingsRepository: ChannelFollowingsRepository,
-
- private globalEventService: GlobalEventService,
) {
super(meta, paramDef, async (ps, me) => {
const channel = await this.channelsRepository.findOneBy({
@@ -54,8 +52,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
followerId: me.id,
followeeId: channel.id,
});
-
- this.globalEventService.publishUserEvent(me.id, 'unfollowChannel', channel);
});
}
}
diff --git a/packages/backend/src/server/api/endpoints/channels/update.ts b/packages/backend/src/server/api/endpoints/channels/update.ts
index a86cc2565a..084b3f919e 100644
--- a/packages/backend/src/server/api/endpoints/channels/update.ts
+++ b/packages/backend/src/server/api/endpoints/channels/update.ts
@@ -3,8 +3,8 @@ import { Endpoint } from '@/server/api/endpoint-base.js';
import type { DriveFilesRepository, ChannelsRepository } from '@/models/index.js';
import { ChannelEntityService } from '@/core/entities/ChannelEntityService.js';
import { DI } from '@/di-symbols.js';
-import { ApiError } from '../../error.js';
import { RoleService } from '@/core/RoleService.js';
+import { ApiError } from '../../error.js';
export const meta = {
tags: ['channels'],
@@ -47,6 +47,12 @@ export const paramDef = {
name: { type: 'string', minLength: 1, maxLength: 128 },
description: { type: 'string', nullable: true, minLength: 1, maxLength: 2048 },
bannerId: { type: 'string', format: 'misskey:id', nullable: true },
+ pinnedNoteIds: {
+ type: 'array',
+ items: {
+ type: 'string', format: 'misskey:id',
+ },
+ },
},
required: ['channelId'],
} as const;
@@ -64,7 +70,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
private channelEntityService: ChannelEntityService,
private roleService: RoleService,
- ) {
+ ) {
super(meta, paramDef, async (ps, me) => {
const channel = await this.channelsRepository.findOneBy({
id: ps.channelId,
@@ -97,6 +103,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
await this.channelsRepository.update(channel.id, {
...(ps.name !== undefined ? { name: ps.name } : {}),
...(ps.description !== undefined ? { description: ps.description } : {}),
+ ...(ps.pinnedNoteIds !== undefined ? { pinnedNoteIds: ps.pinnedNoteIds } : {}),
...(banner ? { bannerId: banner.id } : {}),
});
diff --git a/packages/backend/src/server/api/endpoints/clips/notes.ts b/packages/backend/src/server/api/endpoints/clips/notes.ts
index 6818d31cc4..dcb415b752 100644
--- a/packages/backend/src/server/api/endpoints/clips/notes.ts
+++ b/packages/backend/src/server/api/endpoints/clips/notes.ts
@@ -75,16 +75,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId)
.innerJoin(this.clipNotesRepository.metadata.targetName, 'clipNote', 'clipNote.noteId = note.id')
.innerJoinAndSelect('note.user', 'user')
- .leftJoinAndSelect('user.avatar', 'avatar')
- .leftJoinAndSelect('user.banner', 'banner')
.leftJoinAndSelect('note.reply', 'reply')
.leftJoinAndSelect('note.renote', 'renote')
.leftJoinAndSelect('reply.user', 'replyUser')
- .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar')
- .leftJoinAndSelect('replyUser.banner', 'replyUserBanner')
.leftJoinAndSelect('renote.user', 'renoteUser')
- .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar')
- .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner')
.andWhere('clipNote.clipId = :clipId', { clipId: clip.id });
if (me) {
diff --git a/packages/backend/src/server/api/endpoints/emojis.ts b/packages/backend/src/server/api/endpoints/emojis.ts
index 0711fe4a57..13cc709d31 100644
--- a/packages/backend/src/server/api/endpoints/emojis.ts
+++ b/packages/backend/src/server/api/endpoints/emojis.ts
@@ -58,10 +58,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
category: 'ASC',
name: 'ASC',
},
- cache: {
- id: 'meta_emojis',
- milliseconds: 3600000, // 1 hour
- },
});
return {
diff --git a/packages/backend/src/server/api/endpoints/i/known-as.ts b/packages/backend/src/server/api/endpoints/i/known-as.ts
new file mode 100644
index 0000000000..964704d82b
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/i/known-as.ts
@@ -0,0 +1,92 @@
+import { Injectable } from '@nestjs/common';
+import ms from 'ms';
+
+import { User } from '@/models/entities/User.js';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { ApiError } from '@/server/api/error.js';
+
+import { AccountMoveService } from '@/core/AccountMoveService.js';
+import { RemoteUserResolveService } from '@/core/RemoteUserResolveService.js';
+import { UserEntityService } from '@/core/entities/UserEntityService.js';
+import { ApiLoggerService } from '@/server/api/ApiLoggerService.js';
+
+export const meta = {
+ tags: ['users'],
+
+ secure: true,
+ requireCredential: true,
+
+ limit: {
+ duration: ms('1day'),
+ max: 30,
+ },
+
+ errors: {
+ noSuchUser: {
+ message: 'No such user.',
+ code: 'NO_SUCH_USER',
+ id: 'fcd2eef9-a9b2-4c4f-8624-038099e90aa5',
+ },
+ notRemote: {
+ message: 'User is not remote. You can only migrate from other instances.',
+ code: 'NOT_REMOTE',
+ id: '4362f8dc-731f-4ad8-a694-be2a88922a24',
+ },
+ uriNull: {
+ message: 'User ActivityPup URI is null.',
+ code: 'URI_NULL',
+ id: 'bf326f31-d430-4f97-9933-5d61e4d48a23',
+ },
+ },
+} as const;
+
+export const paramDef = {
+ type: 'object',
+ properties: {
+ alsoKnownAs: { type: 'string' },
+ },
+ required: ['alsoKnownAs'],
+} as const;
+
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ private userEntityService: UserEntityService,
+ private remoteUserResolveService: RemoteUserResolveService,
+ private apiLoggerService: ApiLoggerService,
+ private accountMoveService: AccountMoveService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ // Check parameter
+ if (!ps.alsoKnownAs) throw new ApiError(meta.errors.noSuchUser);
+
+ let unfiltered = ps.alsoKnownAs;
+ const updates = {} as Partial<User>;
+
+ if (!unfiltered) {
+ updates.alsoKnownAs = null;
+ } else {
+ // Parse user's input into the old account
+ if (unfiltered.startsWith('acct:')) unfiltered = unfiltered.substring(5);
+ if (unfiltered.startsWith('@')) unfiltered = unfiltered.substring(1);
+ if (!unfiltered.includes('@')) throw new ApiError(meta.errors.notRemote);
+
+ const userAddress = unfiltered.split('@');
+ // Retrieve the old account
+ const knownAs = await this.remoteUserResolveService.resolveUser(userAddress[0], userAddress[1]).catch((e) => {
+ this.apiLoggerService.logger.warn(`failed to resolve remote user: ${e}`);
+ throw new ApiError(meta.errors.noSuchUser);
+ });
+
+ const toUrl: string | null = knownAs.uri;
+ if (!toUrl) throw new ApiError(meta.errors.uriNull);
+ // Only allow moving from a remote account
+ if (this.userEntityService.isLocalUser(knownAs)) throw new ApiError(meta.errors.notRemote);
+
+ updates.alsoKnownAs = updates.alsoKnownAs?.concat([toUrl]) ?? [toUrl];
+ }
+
+ return await this.accountMoveService.createAlias(me, updates);
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/i/move.ts b/packages/backend/src/server/api/endpoints/i/move.ts
new file mode 100644
index 0000000000..ac76e1f620
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/i/move.ts
@@ -0,0 +1,140 @@
+import { Inject, Injectable } from '@nestjs/common';
+import ms from 'ms';
+
+import type { Config } from '@/config.js';
+import { DI } from '@/di-symbols.js';
+
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { ApiError } from '@/server/api/error.js';
+
+import { AccountMoveService } from '@/core/AccountMoveService.js';
+import { RemoteUserResolveService } from '@/core/RemoteUserResolveService.js';
+import { UserEntityService } from '@/core/entities/UserEntityService.js';
+import { ApiLoggerService } from '@/server/api/ApiLoggerService.js';
+import { GetterService } from '@/server/api/GetterService.js';
+import { ApPersonService } from '@/core/activitypub/models/ApPersonService.js';
+
+export const meta = {
+ tags: ['users'],
+
+ secure: true,
+ requireCredential: true,
+ limit: {
+ duration: ms('1day'),
+ max: 5,
+ },
+
+ errors: {
+ noSuchMoveTarget: {
+ message: 'No such move target.',
+ code: 'NO_SUCH_MOVE_TARGET',
+ id: 'b5c90186-4ab0-49c8-9bba-a1f76c202ba4',
+ },
+ remoteAccountForbids: {
+ message:
+ 'Remote account doesn\'t have proper \'Known As\' alias. Did you remember to set it?',
+ code: 'REMOTE_ACCOUNT_FORBIDS',
+ id: 'b5c90186-4ab0-49c8-9bba-a1f766282ba4',
+ },
+ notRemote: {
+ message: 'User is not remote. You can only migrate to other instances.',
+ code: 'NOT_REMOTE',
+ id: '4362f8dc-731f-4ad8-a694-be2a88922a24',
+ },
+ rootForbidden: {
+ message: 'The root can\'t migrate.',
+ code: 'NOT_ROOT_FORBIDDEN',
+ id: '4362e8dc-731f-4ad8-a694-be2a88922a24',
+ },
+ noSuchUser: {
+ message: 'No such user.',
+ code: 'NO_SUCH_USER',
+ id: 'fcd2eef9-a9b2-4c4f-8624-038099e90aa5',
+ },
+ uriNull: {
+ message: 'User ActivityPup URI is null.',
+ code: 'URI_NULL',
+ id: 'bf326f31-d430-4f97-9933-5d61e4d48a23',
+ },
+ localUriNull: {
+ message: 'Local User ActivityPup URI is null.',
+ code: 'URI_NULL',
+ id: '95ba11b9-90e8-43a5-ba16-7acc1ab32e71',
+ },
+ alreadyMoved: {
+ message: 'Account was already moved to another account.',
+ code: 'ALREADY_MOVED',
+ id: 'b234a14e-9ebe-4581-8000-074b3c215962',
+ },
+ },
+} as const;
+
+export const paramDef = {
+ type: 'object',
+ properties: {
+ moveToAccount: { type: 'string' },
+ },
+ required: ['moveToAccount'],
+} as const;
+
+// eslint-disable-next-line import/no-default-export
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.config)
+ private config: Config,
+
+ private userEntityService: UserEntityService,
+ private remoteUserResolveService: RemoteUserResolveService,
+ private apiLoggerService: ApiLoggerService,
+ private accountMoveService: AccountMoveService,
+ private getterService: GetterService,
+ private apPersonService: ApPersonService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ // check parameter
+ if (!ps.moveToAccount) throw new ApiError(meta.errors.noSuchMoveTarget);
+ // abort if user is the root
+ if (me.isRoot) throw new ApiError(meta.errors.rootForbidden);
+ // abort if user has already moved
+ if (me.movedToUri) throw new ApiError(meta.errors.alreadyMoved);
+
+ let unfiltered = ps.moveToAccount;
+ if (!unfiltered) throw new ApiError(meta.errors.noSuchMoveTarget);
+
+ // parse user's input into the destination account
+ if (unfiltered.startsWith('acct:')) unfiltered = unfiltered.substring(5);
+ if (unfiltered.startsWith('@')) unfiltered = unfiltered.substring(1);
+ if (!unfiltered.includes('@')) throw new ApiError(meta.errors.notRemote);
+
+ const userAddress = unfiltered.split('@');
+ // retrieve the destination account
+ let moveTo = await this.remoteUserResolveService.resolveUser(userAddress[0], userAddress[1]).catch((e) => {
+ this.apiLoggerService.logger.warn(`failed to resolve remote user: ${e}`);
+ throw new ApiError(meta.errors.noSuchMoveTarget);
+ });
+ const remoteMoveTo = await this.getterService.getRemoteUser(moveTo.id);
+ if (!remoteMoveTo.uri) throw new ApiError(meta.errors.uriNull);
+
+ // update local db
+ await this.apPersonService.updatePerson(remoteMoveTo.uri);
+ // retrieve updated user
+ moveTo = await this.apPersonService.resolvePerson(remoteMoveTo.uri);
+ // only allow moving to a remote account
+ if (this.userEntityService.isLocalUser(moveTo)) throw new ApiError(meta.errors.notRemote);
+
+ let allowed = false;
+
+ const fromUrl = `${this.config.url}/users/${me.id}`;
+ // make sure that the user has indicated the old account as an alias
+ moveTo.alsoKnownAs?.forEach((elem) => {
+ if (fromUrl.includes(elem)) allowed = true;
+ });
+
+ // abort if unintended
+ if (!(allowed && moveTo.uri && fromUrl)) throw new ApiError(meta.errors.remoteAccountForbids);
+
+ return await this.accountMoveService.moveToRemote(me, moveTo);
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/i/notifications.ts b/packages/backend/src/server/api/endpoints/i/notifications.ts
index e3897d38bd..f27b4e86d4 100644
--- a/packages/backend/src/server/api/endpoints/i/notifications.ts
+++ b/packages/backend/src/server/api/endpoints/i/notifications.ts
@@ -1,6 +1,7 @@
-import { Brackets } from 'typeorm';
+import { Brackets, In } from 'typeorm';
+import Redis from 'ioredis';
import { Inject, Injectable } from '@nestjs/common';
-import type { UsersRepository, FollowingsRepository, MutingsRepository, UserProfilesRepository, NotificationsRepository } from '@/models/index.js';
+import type { UsersRepository, FollowingsRepository, MutingsRepository, UserProfilesRepository, NotesRepository } from '@/models/index.js';
import { obsoleteNotificationTypes, notificationTypes } from '@/types.js';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { QueryService } from '@/core/QueryService.js';
@@ -8,6 +9,8 @@ import { NoteReadService } from '@/core/NoteReadService.js';
import { NotificationEntityService } from '@/core/entities/NotificationEntityService.js';
import { NotificationService } from '@/core/NotificationService.js';
import { DI } from '@/di-symbols.js';
+import { IdService } from '@/core/IdService.js';
+import { Notification } from '@/models/entities/Notification.js';
export const meta = {
tags: ['account', 'notifications'],
@@ -38,8 +41,6 @@ export const paramDef = {
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
sinceId: { type: 'string', format: 'misskey:id' },
untilId: { type: 'string', format: 'misskey:id' },
- following: { type: 'boolean', default: false },
- unreadOnly: { type: 'boolean', default: false },
markAsRead: { type: 'boolean', default: true },
// 後方互換のため、廃止された通知タイプも受け付ける
includeTypes: { type: 'array', items: {
@@ -56,21 +57,22 @@ export const paramDef = {
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> {
constructor(
+ @Inject(DI.redis)
+ private redisClient: Redis.Redis,
+
@Inject(DI.usersRepository)
private usersRepository: UsersRepository,
- @Inject(DI.followingsRepository)
- private followingsRepository: FollowingsRepository,
-
@Inject(DI.mutingsRepository)
private mutingsRepository: MutingsRepository,
@Inject(DI.userProfilesRepository)
private userProfilesRepository: UserProfilesRepository,
- @Inject(DI.notificationsRepository)
- private notificationsRepository: NotificationsRepository,
+ @Inject(DI.notesRepository)
+ private notesRepository: NotesRepository,
+ private idService: IdService,
private notificationEntityService: NotificationEntityService,
private notificationService: NotificationService,
private queryService: QueryService,
@@ -89,85 +91,39 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
const includeTypes = ps.includeTypes && ps.includeTypes.filter(type => !(obsoleteNotificationTypes).includes(type as any)) as typeof notificationTypes[number][];
const excludeTypes = ps.excludeTypes && ps.excludeTypes.filter(type => !(obsoleteNotificationTypes).includes(type as any)) as typeof notificationTypes[number][];
- const followingQuery = this.followingsRepository.createQueryBuilder('following')
- .select('following.followeeId')
- .where('following.followerId = :followerId', { followerId: me.id });
-
- const mutingQuery = this.mutingsRepository.createQueryBuilder('muting')
- .select('muting.muteeId')
- .where('muting.muterId = :muterId', { muterId: me.id });
-
- const mutingInstanceQuery = this.userProfilesRepository.createQueryBuilder('user_profile')
- .select('user_profile.mutedInstances')
- .where('user_profile.userId = :muterId', { muterId: me.id });
-
- const suspendedQuery = this.usersRepository.createQueryBuilder('users')
- .select('users.id')
- .where('users.isSuspended = TRUE');
-
- const query = this.queryService.makePaginationQuery(this.notificationsRepository.createQueryBuilder('notification'), ps.sinceId, ps.untilId)
- .andWhere('notification.notifieeId = :meId', { meId: me.id })
- .leftJoinAndSelect('notification.notifier', 'notifier')
- .leftJoinAndSelect('notification.note', 'note')
- .leftJoinAndSelect('notifier.avatar', 'notifierAvatar')
- .leftJoinAndSelect('notifier.banner', 'notifierBanner')
- .leftJoinAndSelect('note.user', 'user')
- .leftJoinAndSelect('user.avatar', 'avatar')
- .leftJoinAndSelect('user.banner', 'banner')
- .leftJoinAndSelect('note.reply', 'reply')
- .leftJoinAndSelect('note.renote', 'renote')
- .leftJoinAndSelect('reply.user', 'replyUser')
- .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar')
- .leftJoinAndSelect('replyUser.banner', 'replyUserBanner')
- .leftJoinAndSelect('renote.user', 'renoteUser')
- .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar')
- .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner');
-
- // muted users
- query.andWhere(new Brackets(qb => { qb
- .where(`notification.notifierId NOT IN (${ mutingQuery.getQuery() })`)
- .orWhere('notification.notifierId IS NULL');
- }));
- query.setParameters(mutingQuery.getParameters());
+ const notificationsRes = await this.redisClient.xrevrange(
+ `notificationTimeline:${me.id}`,
+ ps.untilId ? this.idService.parse(ps.untilId).date.getTime() : '+',
+ '-',
+ 'COUNT', ps.limit + 1); // untilIdに指定したものも含まれるため+1
- // muted instances
- query.andWhere(new Brackets(qb => { qb
- .andWhere('notifier.host IS NULL')
- .orWhere(`NOT (( ${mutingInstanceQuery.getQuery()} )::jsonb ? notifier.host)`);
- }));
- query.setParameters(mutingInstanceQuery.getParameters());
-
- // suspended users
- query.andWhere(new Brackets(qb => { qb
- .where(`notification.notifierId NOT IN (${ suspendedQuery.getQuery() })`)
- .orWhere('notification.notifierId IS NULL');
- }));
-
- if (ps.following) {
- query.andWhere(`((notification.notifierId IN (${ followingQuery.getQuery() })) OR (notification.notifierId = :meId))`, { meId: me.id });
- query.setParameters(followingQuery.getParameters());
+ if (notificationsRes.length === 0) {
+ return [];
}
+ let notifications = notificationsRes.map(x => JSON.parse(x[1][1])).filter(x => x.id !== ps.untilId) as Notification[];
+
if (includeTypes && includeTypes.length > 0) {
- query.andWhere('notification.type IN (:...includeTypes)', { includeTypes });
+ notifications = notifications.filter(notification => includeTypes.includes(notification.type));
} else if (excludeTypes && excludeTypes.length > 0) {
- query.andWhere('notification.type NOT IN (:...excludeTypes)', { excludeTypes });
+ notifications = notifications.filter(notification => !excludeTypes.includes(notification.type));
}
- if (ps.unreadOnly) {
- query.andWhere('notification.isRead = false');
+ if (notifications.length === 0) {
+ return [];
}
- const notifications = await query.take(ps.limit).getMany();
-
// Mark all as read
- if (notifications.length > 0 && ps.markAsRead) {
- this.notificationService.readNotification(me.id, notifications.map(x => x.id));
+ if (ps.markAsRead) {
+ this.notificationService.readAllNotification(me.id);
}
- const notes = notifications.filter(notification => ['mention', 'reply', 'quote'].includes(notification.type)).map(notification => notification.note!);
+ const noteIds = notifications
+ .filter(notification => ['mention', 'reply', 'quote'].includes(notification.type))
+ .map(notification => notification.noteId!);
- if (notes.length > 0) {
+ if (noteIds.length > 0) {
+ const notes = await this.notesRepository.findBy({ id: In(noteIds) });
this.noteReadService.read(me.id, notes);
}
diff --git a/packages/backend/src/server/api/endpoints/i/regenerate-token.ts b/packages/backend/src/server/api/endpoints/i/regenerate-token.ts
index f942f43cc8..23ff63f5e9 100644
--- a/packages/backend/src/server/api/endpoints/i/regenerate-token.ts
+++ b/packages/backend/src/server/api/endpoints/i/regenerate-token.ts
@@ -34,7 +34,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
) {
super(meta, paramDef, async (ps, me) => {
const freshUser = await this.usersRepository.findOneByOrFail({ id: me.id });
- const oldToken = freshUser.token;
+ const oldToken = freshUser.token!;
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: me.id });
@@ -54,11 +54,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
// Publish event
this.globalEventService.publishInternalEvent('userTokenRegenerated', { id: me.id, oldToken, newToken });
this.globalEventService.publishMainStream(me.id, 'myTokenRegenerated');
-
- // Terminate streaming
- setTimeout(() => {
- this.globalEventService.publishUserEvent(me.id, 'terminate', {});
- }, 5000);
});
}
}
diff --git a/packages/backend/src/server/api/endpoints/i/revoke-token.ts b/packages/backend/src/server/api/endpoints/i/revoke-token.ts
index 5e1dddb6b7..93daeb0cd7 100644
--- a/packages/backend/src/server/api/endpoints/i/revoke-token.ts
+++ b/packages/backend/src/server/api/endpoints/i/revoke-token.ts
@@ -35,9 +35,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
id: ps.tokenId,
userId: me.id,
});
-
- // Terminate streaming
- this.globalEventService.publishUserEvent(me.id, 'terminate');
}
});
}
diff --git a/packages/backend/src/server/api/endpoints/i/update.ts b/packages/backend/src/server/api/endpoints/i/update.ts
index b1eaab3908..be1c72b207 100644
--- a/packages/backend/src/server/api/endpoints/i/update.ts
+++ b/packages/backend/src/server/api/endpoints/i/update.ts
@@ -18,6 +18,8 @@ import { AccountUpdateService } from '@/core/AccountUpdateService.js';
import { HashtagService } from '@/core/HashtagService.js';
import { DI } from '@/di-symbols.js';
import { RoleService } from '@/core/RoleService.js';
+import { CacheService } from '@/core/CacheService.js';
+import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
import { ApiError } from '../../error.js';
export const meta = {
@@ -147,11 +149,13 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
private pagesRepository: PagesRepository,
private userEntityService: UserEntityService,
+ private driveFileEntityService: DriveFileEntityService,
private globalEventService: GlobalEventService,
private userFollowingService: UserFollowingService,
private accountUpdateService: AccountUpdateService,
private hashtagService: HashtagService,
private roleService: RoleService,
+ private cacheService: CacheService,
) {
super(meta, paramDef, async (ps, _user, token) => {
const user = await this.usersRepository.findOneByOrFail({ id: _user.id });
@@ -168,8 +172,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
if (ps.location !== undefined) profileUpdates.location = ps.location;
if (ps.birthday !== undefined) profileUpdates.birthday = ps.birthday;
if (ps.ffVisibility !== undefined) profileUpdates.ffVisibility = ps.ffVisibility;
- if (ps.avatarId !== undefined) updates.avatarId = ps.avatarId;
- if (ps.bannerId !== undefined) updates.bannerId = ps.bannerId;
if (ps.mutedWords !== undefined) {
// TODO: ちゃんと数える
const length = JSON.stringify(ps.mutedWords).length;
@@ -215,6 +217,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
if (avatar == null || avatar.userId !== user.id) throw new ApiError(meta.errors.noSuchAvatar);
if (!avatar.type.startsWith('image/')) throw new ApiError(meta.errors.avatarNotAnImage);
+
+ updates.avatarId = avatar.id;
+ updates.avatarUrl = this.driveFileEntityService.getPublicUrl(avatar, 'avatar');
+ updates.avatarBlurhash = avatar.blurhash;
}
if (ps.bannerId) {
@@ -222,6 +228,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
if (banner == null || banner.userId !== user.id) throw new ApiError(meta.errors.noSuchBanner);
if (!banner.type.startsWith('image/')) throw new ApiError(meta.errors.bannerNotAnImage);
+
+ updates.bannerId = banner.id;
+ updates.bannerUrl = this.driveFileEntityService.getPublicUrl(banner);
+ updates.bannerBlurhash = banner.blurhash;
}
if (ps.pinnedPageId) {
@@ -276,9 +286,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
includeSecrets: isSecure,
});
+ const updatedProfile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id });
+
+ this.cacheService.userProfileCache.set(user.id, updatedProfile);
+
// Publish meUpdated event
this.globalEventService.publishMainStream(user.id, 'meUpdated', iObj);
- this.globalEventService.publishUserEvent(user.id, 'updateUserProfile', await this.userProfilesRepository.findOneByOrFail({ userId: user.id }));
// 鍵垢を解除したとき、溜まっていたフォローリクエストがあるならすべて承認
if (user.isLocked && ps.isLocked === false) {
diff --git a/packages/backend/src/server/api/endpoints/mute/create.ts b/packages/backend/src/server/api/endpoints/mute/create.ts
index 9099eea52e..6e24e1024d 100644
--- a/packages/backend/src/server/api/endpoints/mute/create.ts
+++ b/packages/backend/src/server/api/endpoints/mute/create.ts
@@ -1,12 +1,10 @@
import { Inject, Injectable } from '@nestjs/common';
import ms from 'ms';
import { Endpoint } from '@/server/api/endpoint-base.js';
-import { IdService } from '@/core/IdService.js';
import type { MutingsRepository } from '@/models/index.js';
-import type { Muting } from '@/models/entities/Muting.js';
-import { GlobalEventService } from '@/core/GlobalEventService.js';
import { DI } from '@/di-symbols.js';
import { GetterService } from '@/server/api/GetterService.js';
+import { UserMutingService } from '@/core/UserMutingService.js';
import { ApiError } from '../../error.js';
export const meta = {
@@ -62,9 +60,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
@Inject(DI.mutingsRepository)
private mutingsRepository: MutingsRepository,
- private globalEventService: GlobalEventService,
private getterService: GetterService,
- private idService: IdService,
+ private userMutingService: UserMutingService,
) {
super(meta, paramDef, async (ps, me) => {
const muter = me;
@@ -94,16 +91,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
return;
}
- // Create mute
- await this.mutingsRepository.insert({
- id: this.idService.genId(),
- createdAt: new Date(),
- expiresAt: ps.expiresAt ? new Date(ps.expiresAt) : null,
- muterId: muter.id,
- muteeId: mutee.id,
- } as Muting);
-
- this.globalEventService.publishUserEvent(me.id, 'mute', mutee);
+ await this.userMutingService.mute(muter, mutee, ps.expiresAt ? new Date(ps.expiresAt) : null);
});
}
}
diff --git a/packages/backend/src/server/api/endpoints/mute/delete.ts b/packages/backend/src/server/api/endpoints/mute/delete.ts
index 612c4a4c04..90b74590be 100644
--- a/packages/backend/src/server/api/endpoints/mute/delete.ts
+++ b/packages/backend/src/server/api/endpoints/mute/delete.ts
@@ -1,10 +1,10 @@
import { Inject, Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import type { MutingsRepository } from '@/models/index.js';
-import { GlobalEventService } from '@/core/GlobalEventService.js';
import { DI } from '@/di-symbols.js';
-import { ApiError } from '../../error.js';
import { GetterService } from '@/server/api/GetterService.js';
+import { UserMutingService } from '@/core/UserMutingService.js';
+import { ApiError } from '../../error.js';
export const meta = {
tags: ['account'],
@@ -49,7 +49,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
@Inject(DI.mutingsRepository)
private mutingsRepository: MutingsRepository,
- private globalEventService: GlobalEventService,
+ private userMutingService: UserMutingService,
private getterService: GetterService,
) {
super(meta, paramDef, async (ps, me) => {
@@ -76,12 +76,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
throw new ApiError(meta.errors.notMuting);
}
- // Delete mute
- await this.mutingsRepository.delete({
- id: exist.id,
- });
-
- this.globalEventService.publishUserEvent(me.id, 'unmute', mutee);
+ await this.userMutingService.unmute([exist]);
});
}
}
diff --git a/packages/backend/src/server/api/endpoints/notes.ts b/packages/backend/src/server/api/endpoints/notes.ts
index 0a8f2292ac..5fbc7aba58 100644
--- a/packages/backend/src/server/api/endpoints/notes.ts
+++ b/packages/backend/src/server/api/endpoints/notes.ts
@@ -49,16 +49,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
.andWhere('note.visibility = \'public\'')
.andWhere('note.localOnly = FALSE')
.innerJoinAndSelect('note.user', 'user')
- .leftJoinAndSelect('user.avatar', 'avatar')
- .leftJoinAndSelect('user.banner', 'banner')
.leftJoinAndSelect('note.reply', 'reply')
.leftJoinAndSelect('note.renote', 'renote')
.leftJoinAndSelect('reply.user', 'replyUser')
- .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar')
- .leftJoinAndSelect('replyUser.banner', 'replyUserBanner')
- .leftJoinAndSelect('renote.user', 'renoteUser')
- .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar')
- .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner');
+ .leftJoinAndSelect('renote.user', 'renoteUser');
if (ps.local) {
query.andWhere('note.userHost IS NULL');
diff --git a/packages/backend/src/server/api/endpoints/notes/children.ts b/packages/backend/src/server/api/endpoints/notes/children.ts
index ea7a825f9d..26f2d6772d 100644
--- a/packages/backend/src/server/api/endpoints/notes/children.ts
+++ b/packages/backend/src/server/api/endpoints/notes/children.ts
@@ -57,16 +57,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
}));
}))
.innerJoinAndSelect('note.user', 'user')
- .leftJoinAndSelect('user.avatar', 'avatar')
- .leftJoinAndSelect('user.banner', 'banner')
.leftJoinAndSelect('note.reply', 'reply')
.leftJoinAndSelect('note.renote', 'renote')
.leftJoinAndSelect('reply.user', 'replyUser')
- .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar')
- .leftJoinAndSelect('replyUser.banner', 'replyUserBanner')
- .leftJoinAndSelect('renote.user', 'renoteUser')
- .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar')
- .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner');
+ .leftJoinAndSelect('renote.user', 'renoteUser');
this.queryService.generateVisibilityQuery(query, me);
if (me) {
diff --git a/packages/backend/src/server/api/endpoints/notes/featured.ts b/packages/backend/src/server/api/endpoints/notes/featured.ts
index 6bf17b222a..bdb06498bc 100644
--- a/packages/backend/src/server/api/endpoints/notes/featured.ts
+++ b/packages/backend/src/server/api/endpoints/notes/featured.ts
@@ -53,16 +53,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
.andWhere('note.createdAt > :date', { date: new Date(Date.now() - day) })
.andWhere('note.visibility = \'public\'')
.innerJoinAndSelect('note.user', 'user')
- .leftJoinAndSelect('user.avatar', 'avatar')
- .leftJoinAndSelect('user.banner', 'banner')
.leftJoinAndSelect('note.reply', 'reply')
.leftJoinAndSelect('note.renote', 'renote')
.leftJoinAndSelect('reply.user', 'replyUser')
- .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar')
- .leftJoinAndSelect('replyUser.banner', 'replyUserBanner')
- .leftJoinAndSelect('renote.user', 'renoteUser')
- .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar')
- .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner');
+ .leftJoinAndSelect('renote.user', 'renoteUser');
if (ps.channelId) query.andWhere('note.channelId = :channelId', { channelId: ps.channelId });
diff --git a/packages/backend/src/server/api/endpoints/notes/global-timeline.ts b/packages/backend/src/server/api/endpoints/notes/global-timeline.ts
index 9118d33936..c11c1eac40 100644
--- a/packages/backend/src/server/api/endpoints/notes/global-timeline.ts
+++ b/packages/backend/src/server/api/endpoints/notes/global-timeline.ts
@@ -73,16 +73,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
.andWhere('note.visibility = \'public\'')
.andWhere('note.channelId IS NULL')
.innerJoinAndSelect('note.user', 'user')
- .leftJoinAndSelect('user.avatar', 'avatar')
- .leftJoinAndSelect('user.banner', 'banner')
.leftJoinAndSelect('note.reply', 'reply')
.leftJoinAndSelect('note.renote', 'renote')
.leftJoinAndSelect('reply.user', 'replyUser')
- .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar')
- .leftJoinAndSelect('replyUser.banner', 'replyUserBanner')
- .leftJoinAndSelect('renote.user', 'renoteUser')
- .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar')
- .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner');
+ .leftJoinAndSelect('renote.user', 'renoteUser');
this.queryService.generateRepliesQuery(query, me);
if (me) {
diff --git a/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts b/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts
index 8a7ec65ab4..89abd91c7e 100644
--- a/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts
+++ b/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts
@@ -91,16 +91,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
.orWhere('(note.visibility = \'public\') AND (note.userHost IS NULL)');
}))
.innerJoinAndSelect('note.user', 'user')
- .leftJoinAndSelect('user.avatar', 'avatar')
- .leftJoinAndSelect('user.banner', 'banner')
.leftJoinAndSelect('note.reply', 'reply')
.leftJoinAndSelect('note.renote', 'renote')
.leftJoinAndSelect('reply.user', 'replyUser')
- .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar')
- .leftJoinAndSelect('replyUser.banner', 'replyUserBanner')
.leftJoinAndSelect('renote.user', 'renoteUser')
- .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar')
- .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner')
.setParameters(followingQuery.getParameters());
this.queryService.generateChannelQuery(query, me);
diff --git a/packages/backend/src/server/api/endpoints/notes/local-timeline.ts b/packages/backend/src/server/api/endpoints/notes/local-timeline.ts
index 8c1c07a9f4..afdafc7c55 100644
--- a/packages/backend/src/server/api/endpoints/notes/local-timeline.ts
+++ b/packages/backend/src/server/api/endpoints/notes/local-timeline.ts
@@ -80,16 +80,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
.andWhere('note.id > :minId', { minId: this.idService.genId(new Date(Date.now() - (1000 * 60 * 60 * 24 * 10))) }) // 10日前まで
.andWhere('(note.visibility = \'public\') AND (note.userHost IS NULL)')
.innerJoinAndSelect('note.user', 'user')
- .leftJoinAndSelect('user.avatar', 'avatar')
- .leftJoinAndSelect('user.banner', 'banner')
.leftJoinAndSelect('note.reply', 'reply')
.leftJoinAndSelect('note.renote', 'renote')
.leftJoinAndSelect('reply.user', 'replyUser')
- .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar')
- .leftJoinAndSelect('replyUser.banner', 'replyUserBanner')
- .leftJoinAndSelect('renote.user', 'renoteUser')
- .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar')
- .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner');
+ .leftJoinAndSelect('renote.user', 'renoteUser');
this.queryService.generateChannelQuery(query, me);
this.queryService.generateRepliesQuery(query, me);
diff --git a/packages/backend/src/server/api/endpoints/notes/mentions.ts b/packages/backend/src/server/api/endpoints/notes/mentions.ts
index dcb0d0adcb..4e9f604d8d 100644
--- a/packages/backend/src/server/api/endpoints/notes/mentions.ts
+++ b/packages/backend/src/server/api/endpoints/notes/mentions.ts
@@ -60,16 +60,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
.orWhere(`'{"${me.id}"}' <@ note.visibleUserIds`);
}))
.innerJoinAndSelect('note.user', 'user')
- .leftJoinAndSelect('user.avatar', 'avatar')
- .leftJoinAndSelect('user.banner', 'banner')
.leftJoinAndSelect('note.reply', 'reply')
.leftJoinAndSelect('note.renote', 'renote')
.leftJoinAndSelect('reply.user', 'replyUser')
- .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar')
- .leftJoinAndSelect('replyUser.banner', 'replyUserBanner')
- .leftJoinAndSelect('renote.user', 'renoteUser')
- .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar')
- .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner');
+ .leftJoinAndSelect('renote.user', 'renoteUser');
this.queryService.generateVisibilityQuery(query, me);
this.queryService.generateMutedUserQuery(query, me);
diff --git a/packages/backend/src/server/api/endpoints/notes/reactions.ts b/packages/backend/src/server/api/endpoints/notes/reactions.ts
index f758bfe9b1..4772c4f809 100644
--- a/packages/backend/src/server/api/endpoints/notes/reactions.ts
+++ b/packages/backend/src/server/api/endpoints/notes/reactions.ts
@@ -75,7 +75,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
order: {
id: -1,
},
- relations: ['user', 'user.avatar', 'user.banner', 'note'],
+ relations: ['user', 'note'],
});
return await Promise.all(reactions.map(reaction => this.noteReactionEntityService.pack(reaction, me)));
diff --git a/packages/backend/src/server/api/endpoints/notes/renotes.ts b/packages/backend/src/server/api/endpoints/notes/renotes.ts
index 026a1baa3e..d406855660 100644
--- a/packages/backend/src/server/api/endpoints/notes/renotes.ts
+++ b/packages/backend/src/server/api/endpoints/notes/renotes.ts
@@ -4,8 +4,8 @@ import { Endpoint } from '@/server/api/endpoint-base.js';
import { QueryService } from '@/core/QueryService.js';
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
import { DI } from '@/di-symbols.js';
-import { ApiError } from '../../error.js';
import { GetterService } from '@/server/api/GetterService.js';
+import { ApiError } from '../../error.js';
export const meta = {
tags: ['notes'],
@@ -62,16 +62,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId)
.andWhere('note.renoteId = :renoteId', { renoteId: note.id })
.innerJoinAndSelect('note.user', 'user')
- .leftJoinAndSelect('user.avatar', 'avatar')
- .leftJoinAndSelect('user.banner', 'banner')
.leftJoinAndSelect('note.reply', 'reply')
.leftJoinAndSelect('note.renote', 'renote')
.leftJoinAndSelect('reply.user', 'replyUser')
- .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar')
- .leftJoinAndSelect('replyUser.banner', 'replyUserBanner')
- .leftJoinAndSelect('renote.user', 'renoteUser')
- .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar')
- .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner');
+ .leftJoinAndSelect('renote.user', 'renoteUser');
this.queryService.generateVisibilityQuery(query, me);
if (me) this.queryService.generateMutedUserQuery(query, me);
diff --git a/packages/backend/src/server/api/endpoints/notes/replies.ts b/packages/backend/src/server/api/endpoints/notes/replies.ts
index 4df95962c8..f2af71d55f 100644
--- a/packages/backend/src/server/api/endpoints/notes/replies.ts
+++ b/packages/backend/src/server/api/endpoints/notes/replies.ts
@@ -46,16 +46,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId)
.andWhere('note.replyId = :replyId', { replyId: ps.noteId })
.innerJoinAndSelect('note.user', 'user')
- .leftJoinAndSelect('user.avatar', 'avatar')
- .leftJoinAndSelect('user.banner', 'banner')
.leftJoinAndSelect('note.reply', 'reply')
.leftJoinAndSelect('note.renote', 'renote')
.leftJoinAndSelect('reply.user', 'replyUser')
- .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar')
- .leftJoinAndSelect('replyUser.banner', 'replyUserBanner')
- .leftJoinAndSelect('renote.user', 'renoteUser')
- .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar')
- .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner');
+ .leftJoinAndSelect('renote.user', 'renoteUser');
this.queryService.generateVisibilityQuery(query, me);
if (me) this.queryService.generateMutedUserQuery(query, me);
diff --git a/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts b/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts
index da1a4bcc46..2956bf1cbd 100644
--- a/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts
+++ b/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts
@@ -71,16 +71,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
super(meta, paramDef, async (ps, me) => {
const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId)
.innerJoinAndSelect('note.user', 'user')
- .leftJoinAndSelect('user.avatar', 'avatar')
- .leftJoinAndSelect('user.banner', 'banner')
.leftJoinAndSelect('note.reply', 'reply')
.leftJoinAndSelect('note.renote', 'renote')
.leftJoinAndSelect('reply.user', 'replyUser')
- .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar')
- .leftJoinAndSelect('replyUser.banner', 'replyUserBanner')
- .leftJoinAndSelect('renote.user', 'renoteUser')
- .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar')
- .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner');
+ .leftJoinAndSelect('renote.user', 'renoteUser');
this.queryService.generateVisibilityQuery(query, me);
if (me) this.queryService.generateMutedUserQuery(query, me);
diff --git a/packages/backend/src/server/api/endpoints/notes/search.ts b/packages/backend/src/server/api/endpoints/notes/search.ts
index 5db5b6267f..fb5abd917f 100644
--- a/packages/backend/src/server/api/endpoints/notes/search.ts
+++ b/packages/backend/src/server/api/endpoints/notes/search.ts
@@ -85,16 +85,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
query
.andWhere('note.text ILIKE :q', { q: `%${ sqlLikeEscape(ps.query) }%` })
.innerJoinAndSelect('note.user', 'user')
- .leftJoinAndSelect('user.avatar', 'avatar')
- .leftJoinAndSelect('user.banner', 'banner')
.leftJoinAndSelect('note.reply', 'reply')
.leftJoinAndSelect('note.renote', 'renote')
.leftJoinAndSelect('reply.user', 'replyUser')
- .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar')
- .leftJoinAndSelect('replyUser.banner', 'replyUserBanner')
- .leftJoinAndSelect('renote.user', 'renoteUser')
- .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar')
- .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner');
+ .leftJoinAndSelect('renote.user', 'renoteUser');
this.queryService.generateVisibilityQuery(query, me);
if (me) this.queryService.generateMutedUserQuery(query, me);
diff --git a/packages/backend/src/server/api/endpoints/notes/state.ts b/packages/backend/src/server/api/endpoints/notes/state.ts
index d0036f0fb7..93517ab10c 100644
--- a/packages/backend/src/server/api/endpoints/notes/state.ts
+++ b/packages/backend/src/server/api/endpoints/notes/state.ts
@@ -59,7 +59,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
this.noteThreadMutingsRepository.count({
where: {
userId: me.id,
- threadId: note.threadId || note.id,
+ threadId: note.threadId ?? note.id,
},
take: 1,
}),
diff --git a/packages/backend/src/server/api/endpoints/notes/timeline.ts b/packages/backend/src/server/api/endpoints/notes/timeline.ts
index d9e72d2603..c6ee1e5c2b 100644
--- a/packages/backend/src/server/api/endpoints/notes/timeline.ts
+++ b/packages/backend/src/server/api/endpoints/notes/timeline.ts
@@ -70,16 +70,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
.andWhere('note.id > :minId', { minId: this.idService.genId(new Date(Date.now() - (1000 * 60 * 60 * 24 * 10))) }) // 10日前まで
.innerJoinAndSelect('note.user', 'user')
- .leftJoinAndSelect('user.avatar', 'avatar')
- .leftJoinAndSelect('user.banner', 'banner')
.leftJoinAndSelect('note.reply', 'reply')
.leftJoinAndSelect('note.renote', 'renote')
.leftJoinAndSelect('reply.user', 'replyUser')
- .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar')
- .leftJoinAndSelect('replyUser.banner', 'replyUserBanner')
- .leftJoinAndSelect('renote.user', 'renoteUser')
- .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar')
- .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner');
+ .leftJoinAndSelect('renote.user', 'renoteUser');
if (followees.length > 0) {
const meOrFolloweeIds = [me.id, ...followees.map(f => f.followeeId)];
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 9b23103fd4..afc9bc4213 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
@@ -84,16 +84,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId)
.innerJoin(this.userListJoiningsRepository.metadata.targetName, 'userListJoining', 'userListJoining.userId = note.userId')
.innerJoinAndSelect('note.user', 'user')
- .leftJoinAndSelect('user.avatar', 'avatar')
- .leftJoinAndSelect('user.banner', 'banner')
.leftJoinAndSelect('note.reply', 'reply')
.leftJoinAndSelect('note.renote', 'renote')
.leftJoinAndSelect('reply.user', 'replyUser')
- .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar')
- .leftJoinAndSelect('replyUser.banner', 'replyUserBanner')
.leftJoinAndSelect('renote.user', 'renoteUser')
- .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar')
- .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner')
.andWhere('userListJoining.userListId = :userListId', { userListId: list.id });
this.queryService.generateVisibilityQuery(query, me);
diff --git a/packages/backend/src/server/api/endpoints/notifications/mark-all-as-read.ts b/packages/backend/src/server/api/endpoints/notifications/mark-all-as-read.ts
index 09134cf48f..e601bf9d5b 100644
--- a/packages/backend/src/server/api/endpoints/notifications/mark-all-as-read.ts
+++ b/packages/backend/src/server/api/endpoints/notifications/mark-all-as-read.ts
@@ -1,9 +1,7 @@
import { Inject, Injectable } from '@nestjs/common';
-import type { NotificationsRepository } from '@/models/index.js';
import { Endpoint } from '@/server/api/endpoint-base.js';
-import { GlobalEventService } from '@/core/GlobalEventService.js';
-import { PushNotificationService } from '@/core/PushNotificationService.js';
import { DI } from '@/di-symbols.js';
+import { NotificationService } from '@/core/NotificationService.js';
export const meta = {
tags: ['notifications', 'account'],
@@ -23,24 +21,10 @@ export const paramDef = {
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> {
constructor(
- @Inject(DI.notificationsRepository)
- private notificationsRepository: NotificationsRepository,
-
- private globalEventService: GlobalEventService,
- private pushNotificationService: PushNotificationService,
+ private notificationService: NotificationService,
) {
super(meta, paramDef, async (ps, me) => {
- // Update documents
- await this.notificationsRepository.update({
- notifieeId: me.id,
- isRead: false,
- }, {
- isRead: true,
- });
-
- // 全ての通知を読みましたよというイベントを発行
- this.globalEventService.publishMainStream(me.id, 'readAllNotifications');
- this.pushNotificationService.pushNotification(me.id, 'readAllNotifications', undefined);
+ this.notificationService.readAllNotification(me.id, true);
});
}
}
diff --git a/packages/backend/src/server/api/endpoints/notifications/read.ts b/packages/backend/src/server/api/endpoints/notifications/read.ts
deleted file mode 100644
index 6262c47fd0..0000000000
--- a/packages/backend/src/server/api/endpoints/notifications/read.ts
+++ /dev/null
@@ -1,57 +0,0 @@
-import { Injectable } from '@nestjs/common';
-import { Endpoint } from '@/server/api/endpoint-base.js';
-import { NotificationService } from '@/core/NotificationService.js';
-
-export const meta = {
- tags: ['notifications', 'account'],
-
- requireCredential: true,
-
- kind: 'write:notifications',
-
- description: 'Mark a notification as read.',
-
- errors: {
- noSuchNotification: {
- message: 'No such notification.',
- code: 'NO_SUCH_NOTIFICATION',
- id: 'efa929d5-05b5-47d1-beec-e6a4dbed011e',
- },
- },
-} as const;
-
-export const paramDef = {
- oneOf: [
- {
- type: 'object',
- properties: {
- notificationId: { type: 'string', format: 'misskey:id' },
- },
- required: ['notificationId'],
- },
- {
- type: 'object',
- properties: {
- notificationIds: {
- type: 'array',
- items: { type: 'string', format: 'misskey:id' },
- maxItems: 100,
- },
- },
- required: ['notificationIds'],
- },
- ],
-} as const;
-
-// eslint-disable-next-line import/no-default-export
-@Injectable()
-export default class extends Endpoint<typeof meta, typeof paramDef> {
- constructor(
- private notificationService: NotificationService,
- ) {
- super(meta, paramDef, async (ps, me) => {
- if ('notificationId' in ps) return this.notificationService.readNotification(me.id, [ps.notificationId]);
- return this.notificationService.readNotification(me.id, ps.notificationIds);
- });
- }
-}
diff --git a/packages/backend/src/server/api/endpoints/renote-mute/create.ts b/packages/backend/src/server/api/endpoints/renote-mute/create.ts
index 051a005b67..b285269617 100644
--- a/packages/backend/src/server/api/endpoints/renote-mute/create.ts
+++ b/packages/backend/src/server/api/endpoints/renote-mute/create.ts
@@ -92,8 +92,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
muterId: muter.id,
muteeId: mutee.id,
} as RenoteMuting);
-
- // publishUserEvent(user.id, 'mute', mutee);
});
}
}
diff --git a/packages/backend/src/server/api/endpoints/renote-mute/delete.ts b/packages/backend/src/server/api/endpoints/renote-mute/delete.ts
index 51a895fb7e..70901a1406 100644
--- a/packages/backend/src/server/api/endpoints/renote-mute/delete.ts
+++ b/packages/backend/src/server/api/endpoints/renote-mute/delete.ts
@@ -80,8 +80,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
await this.renoteMutingsRepository.delete({
id: exist.id,
});
-
- // publishUserEvent(user.id, 'unmute', mutee);
});
}
}
diff --git a/packages/backend/src/server/api/endpoints/users/notes.ts b/packages/backend/src/server/api/endpoints/users/notes.ts
index aab32cc58c..aaf94734a3 100644
--- a/packages/backend/src/server/api/endpoints/users/notes.ts
+++ b/packages/backend/src/server/api/endpoints/users/notes.ts
@@ -74,16 +74,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
.andWhere('note.userId = :userId', { userId: user.id })
.innerJoinAndSelect('note.user', 'user')
- .leftJoinAndSelect('user.avatar', 'avatar')
- .leftJoinAndSelect('user.banner', 'banner')
.leftJoinAndSelect('note.reply', 'reply')
.leftJoinAndSelect('note.renote', 'renote')
.leftJoinAndSelect('reply.user', 'replyUser')
- .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar')
- .leftJoinAndSelect('replyUser.banner', 'replyUserBanner')
- .leftJoinAndSelect('renote.user', 'renoteUser')
- .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar')
- .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner');
+ .leftJoinAndSelect('renote.user', 'renoteUser');
this.queryService.generateVisibilityQuery(query, me);
if (me) {
diff --git a/packages/backend/src/server/api/stream/channel.ts b/packages/backend/src/server/api/stream/channel.ts
index 32935325aa..e67aec9ecd 100644
--- a/packages/backend/src/server/api/stream/channel.ts
+++ b/packages/backend/src/server/api/stream/channel.ts
@@ -23,16 +23,16 @@ export default abstract class Channel {
return this.connection.following;
}
- protected get muting() {
- return this.connection.muting;
+ protected get userIdsWhoMeMuting() {
+ return this.connection.userIdsWhoMeMuting;
}
- protected get renoteMuting() {
- return this.connection.renoteMuting;
+ protected get userIdsWhoMeMutingRenotes() {
+ return this.connection.userIdsWhoMeMutingRenotes;
}
- protected get blocking() {
- return this.connection.blocking;
+ protected get userIdsWhoBlockingMe() {
+ return this.connection.userIdsWhoBlockingMe;
}
protected get followingChannels() {
diff --git a/packages/backend/src/server/api/stream/channels/antenna.ts b/packages/backend/src/server/api/stream/channels/antenna.ts
index e2a42fbfe9..d48dea7258 100644
--- a/packages/backend/src/server/api/stream/channels/antenna.ts
+++ b/packages/backend/src/server/api/stream/channels/antenna.ts
@@ -35,11 +35,11 @@ class AntennaChannel extends Channel {
const note = await this.noteEntityService.pack(data.body.id, this.user, { detail: true });
// 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
- if (isUserRelated(note, this.muting)) return;
+ if (isUserRelated(note, this.userIdsWhoMeMuting)) return;
// 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する
- if (isUserRelated(note, this.blocking)) return;
+ if (isUserRelated(note, this.userIdsWhoBlockingMe)) return;
- if (note.renote && !note.text && isUserRelated(note, this.renoteMuting)) return;
+ if (note.renote && !note.text && isUserRelated(note, this.userIdsWhoMeMutingRenotes)) return;
this.connection.cacheNote(note);
diff --git a/packages/backend/src/server/api/stream/channels/channel.ts b/packages/backend/src/server/api/stream/channels/channel.ts
index 12caa7f233..9e5b40997b 100644
--- a/packages/backend/src/server/api/stream/channels/channel.ts
+++ b/packages/backend/src/server/api/stream/channels/channel.ts
@@ -47,11 +47,11 @@ class ChannelChannel extends Channel {
}
// 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
- if (isUserRelated(note, this.muting)) return;
+ if (isUserRelated(note, this.userIdsWhoMeMuting)) return;
// 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する
- if (isUserRelated(note, this.blocking)) return;
+ if (isUserRelated(note, this.userIdsWhoBlockingMe)) return;
- if (note.renote && !note.text && isUserRelated(note, this.renoteMuting)) return;
+ if (note.renote && !note.text && isUserRelated(note, this.userIdsWhoMeMutingRenotes)) return;
this.connection.cacheNote(note);
diff --git a/packages/backend/src/server/api/stream/channels/global-timeline.ts b/packages/backend/src/server/api/stream/channels/global-timeline.ts
index d79247cd6e..5454836fe1 100644
--- a/packages/backend/src/server/api/stream/channels/global-timeline.ts
+++ b/packages/backend/src/server/api/stream/channels/global-timeline.ts
@@ -64,11 +64,11 @@ class GlobalTimelineChannel extends Channel {
if (isInstanceMuted(note, new Set<string>(this.userProfile?.mutedInstances ?? []))) return;
// 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
- if (isUserRelated(note, this.muting)) return;
+ if (isUserRelated(note, this.userIdsWhoMeMuting)) return;
// 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する
- if (isUserRelated(note, this.blocking)) return;
+ if (isUserRelated(note, this.userIdsWhoBlockingMe)) return;
- if (note.renote && !note.text && isUserRelated(note, this.renoteMuting)) return;
+ if (note.renote && !note.text && isUserRelated(note, this.userIdsWhoMeMutingRenotes)) return;
// 流れてきたNoteがミュートすべきNoteだったら無視する
// TODO: 将来的には、単にMutedNoteテーブルにレコードがあるかどうかで判定したい(以下の理由により難しそうではある)
diff --git a/packages/backend/src/server/api/stream/channels/hashtag.ts b/packages/backend/src/server/api/stream/channels/hashtag.ts
index 98dc858ded..0268fdedde 100644
--- a/packages/backend/src/server/api/stream/channels/hashtag.ts
+++ b/packages/backend/src/server/api/stream/channels/hashtag.ts
@@ -46,11 +46,11 @@ class HashtagChannel extends Channel {
}
// 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
- if (isUserRelated(note, this.muting)) return;
+ if (isUserRelated(note, this.userIdsWhoMeMuting)) return;
// 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する
- if (isUserRelated(note, this.blocking)) return;
+ if (isUserRelated(note, this.userIdsWhoBlockingMe)) return;
- if (note.renote && !note.text && isUserRelated(note, this.renoteMuting)) return;
+ if (note.renote && !note.text && isUserRelated(note, this.userIdsWhoMeMutingRenotes)) return;
this.connection.cacheNote(note);
diff --git a/packages/backend/src/server/api/stream/channels/home-timeline.ts b/packages/backend/src/server/api/stream/channels/home-timeline.ts
index c623fef64a..ee874ad81e 100644
--- a/packages/backend/src/server/api/stream/channels/home-timeline.ts
+++ b/packages/backend/src/server/api/stream/channels/home-timeline.ts
@@ -24,7 +24,6 @@ class HomeTimelineChannel extends Channel {
@bindThis
public async init(params: any) {
- // Subscribe events
this.subscriber.on('notesStream', this.onNote);
}
@@ -38,7 +37,7 @@ class HomeTimelineChannel extends Channel {
}
// Ignore notes from instances the user has muted
- if (isInstanceMuted(note, new Set<string>(this.userProfile?.mutedInstances ?? []))) return;
+ if (isInstanceMuted(note, new Set<string>(this.userProfile!.mutedInstances ?? []))) return;
if (['followers', 'specified'].includes(note.visibility)) {
note = await this.noteEntityService.pack(note.id, this.user!, {
@@ -71,18 +70,18 @@ class HomeTimelineChannel extends Channel {
}
// 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
- if (isUserRelated(note, this.muting)) return;
+ if (isUserRelated(note, this.userIdsWhoMeMuting)) return;
// 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する
- if (isUserRelated(note, this.blocking)) return;
+ if (isUserRelated(note, this.userIdsWhoBlockingMe)) return;
- if (note.renote && !note.text && isUserRelated(note, this.renoteMuting)) return;
+ if (note.renote && !note.text && isUserRelated(note, this.userIdsWhoMeMutingRenotes)) return;
// 流れてきたNoteがミュートすべきNoteだったら無視する
// TODO: 将来的には、単にMutedNoteテーブルにレコードがあるかどうかで判定したい(以下の理由により難しそうではある)
// 現状では、ワードミュートにおけるMutedNoteレコードの追加処理はストリーミングに流す処理と並列で行われるため、
// レコードが追加されるNoteでも追加されるより先にここのストリーミングの処理に到達することが起こる。
// そのためレコードが存在するかのチェックでは不十分なので、改めてcheckWordMuteを呼んでいる
- if (this.userProfile && await checkWordMute(note, this.user, this.userProfile.mutedWords)) return;
+ if (await checkWordMute(note, this.user, this.userProfile!.mutedWords)) return;
this.connection.cacheNote(note);
diff --git a/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts b/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts
index f54767bc9d..4f7b4e78b6 100644
--- a/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts
+++ b/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts
@@ -72,7 +72,7 @@ class HybridTimelineChannel extends Channel {
}
// Ignore notes from instances the user has muted
- if (isInstanceMuted(note, new Set<string>(this.userProfile?.mutedInstances ?? []))) return;
+ if (isInstanceMuted(note, new Set<string>(this.userProfile!.mutedInstances ?? []))) return;
// 関係ない返信は除外
if (note.reply && !this.user!.showTimelineReplies) {
@@ -82,11 +82,11 @@ class HybridTimelineChannel extends Channel {
}
// 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
- if (isUserRelated(note, this.muting)) return;
+ if (isUserRelated(note, this.userIdsWhoMeMuting)) return;
// 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する
- if (isUserRelated(note, this.blocking)) return;
+ if (isUserRelated(note, this.userIdsWhoBlockingMe)) return;
- if (note.renote && !note.text && isUserRelated(note, this.renoteMuting)) return;
+ if (note.renote && !note.text && isUserRelated(note, this.userIdsWhoMeMutingRenotes)) return;
// 流れてきたNoteがミュートすべきNoteだったら無視する
// TODO: 将来的には、単にMutedNoteテーブルにレコードがあるかどうかで判定したい(以下の理由により難しそうではある)
diff --git a/packages/backend/src/server/api/stream/channels/local-timeline.ts b/packages/backend/src/server/api/stream/channels/local-timeline.ts
index eb0642900d..836c5aae6c 100644
--- a/packages/backend/src/server/api/stream/channels/local-timeline.ts
+++ b/packages/backend/src/server/api/stream/channels/local-timeline.ts
@@ -61,11 +61,11 @@ class LocalTimelineChannel extends Channel {
}
// 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
- if (isUserRelated(note, this.muting)) return;
+ if (isUserRelated(note, this.userIdsWhoMeMuting)) return;
// 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する
- if (isUserRelated(note, this.blocking)) return;
+ if (isUserRelated(note, this.userIdsWhoBlockingMe)) return;
- if (note.renote && !note.text && isUserRelated(note, this.renoteMuting)) return;
+ if (note.renote && !note.text && isUserRelated(note, this.userIdsWhoMeMutingRenotes)) return;
// 流れてきたNoteがミュートすべきNoteだったら無視する
// TODO: 将来的には、単にMutedNoteテーブルにレコードがあるかどうかで判定したい(以下の理由により難しそうではある)
diff --git a/packages/backend/src/server/api/stream/channels/main.ts b/packages/backend/src/server/api/stream/channels/main.ts
index 4dd16b530a..139320ce35 100644
--- a/packages/backend/src/server/api/stream/channels/main.ts
+++ b/packages/backend/src/server/api/stream/channels/main.ts
@@ -26,7 +26,7 @@ class MainChannel extends Channel {
case 'notification': {
// Ignore notifications from instances the user has muted
if (isUserFromMutedInstance(data.body, new Set<string>(this.userProfile?.mutedInstances ?? []))) return;
- if (data.body.userId && this.muting.has(data.body.userId)) return;
+ if (data.body.userId && this.userIdsWhoMeMuting.has(data.body.userId)) return;
if (data.body.note && data.body.note.isHidden) {
const note = await this.noteEntityService.pack(data.body.note.id, this.user, {
@@ -40,7 +40,7 @@ class MainChannel extends Channel {
case 'mention': {
if (isInstanceMuted(data.body, new Set<string>(this.userProfile?.mutedInstances ?? []))) return;
- if (this.muting.has(data.body.userId)) return;
+ if (this.userIdsWhoMeMuting.has(data.body.userId)) return;
if (data.body.isHidden) {
const note = await this.noteEntityService.pack(data.body.id, this.user, {
detail: true,
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 8a42e99a54..8802fc5ab8 100644
--- a/packages/backend/src/server/api/stream/channels/user-list.ts
+++ b/packages/backend/src/server/api/stream/channels/user-list.ts
@@ -89,11 +89,11 @@ class UserListChannel extends Channel {
}
// 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
- if (isUserRelated(note, this.muting)) return;
+ if (isUserRelated(note, this.userIdsWhoMeMuting)) return;
// 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する
- if (isUserRelated(note, this.blocking)) return;
+ if (isUserRelated(note, this.userIdsWhoBlockingMe)) return;
- if (note.renote && !note.text && isUserRelated(note, this.renoteMuting)) return;
+ if (note.renote && !note.text && isUserRelated(note, this.userIdsWhoMeMutingRenotes)) return;
this.send('note', note);
}
diff --git a/packages/backend/src/server/api/stream/index.ts b/packages/backend/src/server/api/stream/index.ts
index 7c6eb9a20a..a6f9145952 100644
--- a/packages/backend/src/server/api/stream/index.ts
+++ b/packages/backend/src/server/api/stream/index.ts
@@ -1,13 +1,11 @@
import type { User } from '@/models/entities/User.js';
-import type { Channel as ChannelModel } from '@/models/entities/Channel.js';
-import type { FollowingsRepository, MutingsRepository, RenoteMutingsRepository, UserProfilesRepository, ChannelFollowingsRepository, BlockingsRepository } from '@/models/index.js';
import type { AccessToken } from '@/models/entities/AccessToken.js';
-import type { UserProfile } from '@/models/entities/UserProfile.js';
import type { Packed } from '@/misc/json-schema.js';
-import type { GlobalEventService } from '@/core/GlobalEventService.js';
import type { NoteReadService } from '@/core/NoteReadService.js';
import type { NotificationService } from '@/core/NotificationService.js';
import { bindThis } from '@/decorators.js';
+import { CacheService } from '@/core/CacheService.js';
+import { UserProfile } from '@/models/index.js';
import type { ChannelsService } from './ChannelsService.js';
import type * as websocket from 'websocket';
import type { EventEmitter } from 'events';
@@ -19,106 +17,71 @@ import type { StreamEventEmitter, StreamMessages } from './types.js';
*/
export default class Connection {
public user?: User;
- public userProfile?: UserProfile | null;
- public following: Set<User['id']> = new Set();
- public muting: Set<User['id']> = new Set();
- public renoteMuting: Set<User['id']> = new Set();
- public blocking: Set<User['id']> = new Set(); // "被"blocking
- public followingChannels: Set<ChannelModel['id']> = new Set();
public token?: AccessToken;
private wsConnection: websocket.connection;
public subscriber: StreamEventEmitter;
private channels: Channel[] = [];
private subscribingNotes: any = {};
private cachedNotes: Packed<'Note'>[] = [];
+ public userProfile: UserProfile | null = null;
+ public following: Set<string> = new Set();
+ public followingChannels: Set<string> = new Set();
+ public userIdsWhoMeMuting: Set<string> = new Set();
+ public userIdsWhoBlockingMe: Set<string> = new Set();
+ public userIdsWhoMeMutingRenotes: Set<string> = new Set();
+ private fetchIntervalId: NodeJS.Timer | null = null;
constructor(
- private followingsRepository: FollowingsRepository,
- private mutingsRepository: MutingsRepository,
- private renoteMutingsRepository: RenoteMutingsRepository,
- private blockingsRepository: BlockingsRepository,
- private channelFollowingsRepository: ChannelFollowingsRepository,
- private userProfilesRepository: UserProfilesRepository,
private channelsService: ChannelsService,
- private globalEventService: GlobalEventService,
private noteReadService: NoteReadService,
private notificationService: NotificationService,
+ private cacheService: CacheService,
- wsConnection: websocket.connection,
subscriber: EventEmitter,
user: User | null | undefined,
token: AccessToken | null | undefined,
) {
- this.wsConnection = wsConnection;
this.subscriber = subscriber;
if (user) this.user = user;
if (token) this.token = token;
+ }
- //this.onWsConnectionMessage = this.onWsConnectionMessage.bind(this);
- //this.onUserEvent = this.onUserEvent.bind(this);
- //this.onNoteStreamMessage = this.onNoteStreamMessage.bind(this);
- //this.onBroadcastMessage = this.onBroadcastMessage.bind(this);
-
- this.wsConnection.on('message', this.onWsConnectionMessage);
-
- this.subscriber.on('broadcast', data => {
- this.onBroadcastMessage(data);
- });
+ @bindThis
+ public async fetch() {
+ if (this.user == null) return;
+ const [userProfile, following, followingChannels, userIdsWhoMeMuting, userIdsWhoBlockingMe, userIdsWhoMeMutingRenotes] = await Promise.all([
+ this.cacheService.userProfileCache.fetch(this.user.id),
+ this.cacheService.userFollowingsCache.fetch(this.user.id),
+ this.cacheService.userFollowingChannelsCache.fetch(this.user.id),
+ this.cacheService.userMutingsCache.fetch(this.user.id),
+ this.cacheService.userBlockedCache.fetch(this.user.id),
+ this.cacheService.renoteMutingsCache.fetch(this.user.id),
+ ]);
+ this.userProfile = userProfile;
+ this.following = following;
+ this.followingChannels = followingChannels;
+ this.userIdsWhoMeMuting = userIdsWhoMeMuting;
+ this.userIdsWhoBlockingMe = userIdsWhoBlockingMe;
+ this.userIdsWhoMeMutingRenotes = userIdsWhoMeMutingRenotes;
+ }
- if (this.user) {
- this.updateFollowing();
- this.updateMuting();
- this.updateRenoteMuting();
- this.updateBlocking();
- this.updateFollowingChannels();
- this.updateUserProfile();
+ @bindThis
+ public async init() {
+ if (this.user != null) {
+ await this.fetch();
- this.subscriber.on(`user:${this.user.id}`, this.onUserEvent);
+ this.fetchIntervalId = setInterval(this.fetch, 1000 * 10);
}
}
@bindThis
- private onUserEvent(data: StreamMessages['user']['payload']) { // { type, body }と展開するとそれぞれ型が分離してしまう
- switch (data.type) {
- case 'follow':
- this.following.add(data.body.id);
- break;
-
- case 'unfollow':
- this.following.delete(data.body.id);
- break;
-
- case 'mute':
- this.muting.add(data.body.id);
- break;
-
- case 'unmute':
- this.muting.delete(data.body.id);
- break;
-
- // TODO: renote mute events
- // TODO: block events
-
- case 'followChannel':
- this.followingChannels.add(data.body.id);
- break;
-
- case 'unfollowChannel':
- this.followingChannels.delete(data.body.id);
- break;
-
- case 'updateUserProfile':
- this.userProfile = data.body;
- break;
-
- case 'terminate':
- this.wsConnection.close();
- this.dispose();
- break;
+ public async init2(wsConnection: websocket.connection) {
+ this.wsConnection = wsConnection;
+ this.wsConnection.on('message', this.onWsConnectionMessage);
- default:
- break;
- }
+ this.subscriber.on('broadcast', data => {
+ this.onBroadcastMessage(data);
+ });
}
/**
@@ -186,17 +149,13 @@ export default class Connection {
if (note == null) return;
if (this.user && (note.userId !== this.user.id)) {
- this.noteReadService.read(this.user.id, [note], {
- following: this.following,
- followingChannels: this.followingChannels,
- });
+ this.noteReadService.read(this.user.id, [note]);
}
}
@bindThis
private onReadNotification(payload: any) {
- if (!payload.id) return;
- this.notificationService.readNotification(this.user!.id, [payload.id]);
+ this.notificationService.readAllNotification(this.user!.id);
}
/**
@@ -322,78 +281,12 @@ export default class Connection {
}
}
- @bindThis
- private async updateFollowing() {
- const followings = await this.followingsRepository.find({
- where: {
- followerId: this.user!.id,
- },
- select: ['followeeId'],
- });
-
- this.following = new Set<string>(followings.map(x => x.followeeId));
- }
-
- @bindThis
- private async updateMuting() {
- const mutings = await this.mutingsRepository.find({
- where: {
- muterId: this.user!.id,
- },
- select: ['muteeId'],
- });
-
- this.muting = new Set<string>(mutings.map(x => x.muteeId));
- }
-
- @bindThis
- private async updateRenoteMuting() {
- const renoteMutings = await this.renoteMutingsRepository.find({
- where: {
- muterId: this.user!.id,
- },
- select: ['muteeId'],
- });
-
- this.renoteMuting = new Set<string>(renoteMutings.map(x => x.muteeId));
- }
-
- @bindThis
- private async updateBlocking() { // ここでいうBlockingは被Blockingの意
- const blockings = await this.blockingsRepository.find({
- where: {
- blockeeId: this.user!.id,
- },
- select: ['blockerId'],
- });
-
- this.blocking = new Set<string>(blockings.map(x => x.blockerId));
- }
-
- @bindThis
- private async updateFollowingChannels() {
- const followings = await this.channelFollowingsRepository.find({
- where: {
- followerId: this.user!.id,
- },
- select: ['followeeId'],
- });
-
- this.followingChannels = new Set<string>(followings.map(x => x.followeeId));
- }
-
- @bindThis
- private async updateUserProfile() {
- this.userProfile = await this.userProfilesRepository.findOneBy({
- userId: this.user!.id,
- });
- }
-
/**
* ストリームが切れたとき
*/
@bindThis
public dispose() {
+ if (this.fetchIntervalId) clearInterval(this.fetchIntervalId);
for (const c of this.channels.filter(c => c.dispose)) {
if (c.dispose) c.dispose();
}
diff --git a/packages/backend/src/server/api/stream/types.ts b/packages/backend/src/server/api/stream/types.ts
index b8f50e0546..ed73897e73 100644
--- a/packages/backend/src/server/api/stream/types.ts
+++ b/packages/backend/src/server/api/stream/types.ts
@@ -19,7 +19,7 @@ import type { EventEmitter } from 'events';
//#region Stream type-body definitions
export interface InternalStreamTypes {
userChangeSuspendedState: { id: User['id']; isSuspended: User['isSuspended']; };
- userTokenRegenerated: { id: User['id']; oldToken: User['token']; newToken: User['token']; };
+ userTokenRegenerated: { id: User['id']; oldToken: string; newToken: string; };
remoteUserUpdated: { id: User['id']; };
follow: { followerId: User['id']; followeeId: User['id']; };
unfollow: { followerId: User['id']; followeeId: User['id']; };
@@ -38,6 +38,11 @@ export interface InternalStreamTypes {
antennaDeleted: Antenna;
antennaUpdated: Antenna;
metaUpdated: Meta;
+ followChannel: { userId: User['id']; channelId: Channel['id']; };
+ unfollowChannel: { userId: User['id']; channelId: Channel['id']; };
+ updateUserProfile: UserProfile;
+ mute: { muterId: User['id']; muteeId: User['id']; };
+ unmute: { muterId: User['id']; muteeId: User['id']; };
}
export interface BroadcastTypes {
@@ -56,18 +61,6 @@ export interface BroadcastTypes {
};
}
-export interface UserStreamTypes {
- terminate: Record<string, unknown>;
- followChannel: Channel;
- unfollowChannel: Channel;
- updateUserProfile: UserProfile;
- mute: User;
- unmute: User;
- follow: Packed<'UserDetailedNotMe'>;
- unfollow: Packed<'User'>;
- userAdded: Packed<'User'>;
-}
-
export interface MainStreamTypes {
notification: Packed<'Notification'>;
mention: Packed<'Note'>;
@@ -97,8 +90,6 @@ export interface MainStreamTypes {
readAllAntennas: undefined;
unreadAntenna: Antenna;
readAllAnnouncements: undefined;
- readAllChannels: undefined;
- unreadChannel: Note['id'];
myTokenRegenerated: undefined;
signin: Signin;
registryUpdated: {
@@ -202,10 +193,6 @@ export type StreamMessages = {
name: 'broadcast';
payload: EventUnionFromDictionary<SerializedAll<BroadcastTypes>>;
};
- user: {
- name: `user:${User['id']}`;
- payload: EventUnionFromDictionary<SerializedAll<UserStreamTypes>>;
- };
main: {
name: `mainStream:${User['id']}`;
payload: EventUnionFromDictionary<SerializedAll<MainStreamTypes>>;