summaryrefslogtreecommitdiff
path: root/packages/backend/src
diff options
context:
space:
mode:
authorsyuilo <Syuilotan@yahoo.co.jp>2023-04-11 15:51:07 +0900
committerGitHub <noreply@github.com>2023-04-11 15:51:07 +0900
commit75b28d6782d9e1a37dd40444fbccffdf4331737a (patch)
tree1b6a1b33ca19a579d7d24d92fef4bfb05ae1e86e /packages/backend/src
parentMerge pull request #10543 from misskey-dev/develop (diff)
parentfix(client): noPaging: true with gallery/featured (diff)
downloadmisskey-75b28d6782d9e1a37dd40444fbccffdf4331737a.tar.gz
misskey-75b28d6782d9e1a37dd40444fbccffdf4331737a.tar.bz2
misskey-75b28d6782d9e1a37dd40444fbccffdf4331737a.zip
Merge pull request #10578 from misskey-dev/develop
Release: 13.11.2
Diffstat (limited to 'packages/backend/src')
-rw-r--r--packages/backend/src/GlobalModule.ts30
-rw-r--r--packages/backend/src/core/AntennaService.ts10
-rw-r--r--packages/backend/src/core/CacheService.ts8
-rw-r--r--packages/backend/src/core/CustomEmojiService.ts8
-rw-r--r--packages/backend/src/core/GlobalEventService.ts6
-rw-r--r--packages/backend/src/core/MetaService.ts8
-rw-r--r--packages/backend/src/core/NoteCreateService.ts2
-rw-r--r--packages/backend/src/core/NotificationService.ts3
-rw-r--r--packages/backend/src/core/PushNotificationService.ts26
-rw-r--r--packages/backend/src/core/RoleService.ts8
-rw-r--r--packages/backend/src/core/WebhookService.ts8
-rw-r--r--packages/backend/src/di-symbols.ts3
-rw-r--r--packages/backend/src/misc/cache.ts4
-rw-r--r--packages/backend/src/server/api/ApiServerService.ts2
-rw-r--r--packages/backend/src/server/api/EndpointsModule.ts4
-rw-r--r--packages/backend/src/server/api/StreamingApiServerService.ts8
-rw-r--r--packages/backend/src/server/api/endpoints.ts2
-rw-r--r--packages/backend/src/server/api/endpoints/channels/search.ts67
-rw-r--r--packages/backend/src/server/api/stream/channels/local-timeline.ts4
19 files changed, 158 insertions, 53 deletions
diff --git a/packages/backend/src/GlobalModule.ts b/packages/backend/src/GlobalModule.ts
index cb713b25ad..174d0d8beb 100644
--- a/packages/backend/src/GlobalModule.ts
+++ b/packages/backend/src/GlobalModule.ts
@@ -37,8 +37,24 @@ const $redis: Provider = {
inject: [DI.config],
};
-const $redisForPubsub: Provider = {
- provide: DI.redisForPubsub,
+const $redisForPub: Provider = {
+ provide: DI.redisForPub,
+ useFactory: (config) => {
+ const redis = new Redis({
+ port: config.redisForPubsub.port,
+ host: config.redisForPubsub.host,
+ family: config.redisForPubsub.family == null ? 0 : config.redisForPubsub.family,
+ password: config.redisForPubsub.pass,
+ keyPrefix: `${config.redisForPubsub.prefix}:`,
+ db: config.redisForPubsub.db ?? 0,
+ });
+ return redis;
+ },
+ inject: [DI.config],
+};
+
+const $redisForSub: Provider = {
+ provide: DI.redisForSub,
useFactory: (config) => {
const redis = new Redis({
port: config.redisForPubsub.port,
@@ -57,14 +73,15 @@ const $redisForPubsub: Provider = {
@Global()
@Module({
imports: [RepositoryModule],
- providers: [$config, $db, $redis, $redisForPubsub],
- exports: [$config, $db, $redis, $redisForPubsub, RepositoryModule],
+ providers: [$config, $db, $redis, $redisForPub, $redisForSub],
+ exports: [$config, $db, $redis, $redisForPub, $redisForSub, RepositoryModule],
})
export class GlobalModule implements OnApplicationShutdown {
constructor(
@Inject(DI.db) private db: DataSource,
@Inject(DI.redis) private redisClient: Redis.Redis,
- @Inject(DI.redisForPubsub) private redisForPubsub: Redis.Redis,
+ @Inject(DI.redisForPub) private redisForPub: Redis.Redis,
+ @Inject(DI.redisForSub) private redisForSub: Redis.Redis,
) {}
async onApplicationShutdown(signal: string): Promise<void> {
@@ -79,7 +96,8 @@ export class GlobalModule implements OnApplicationShutdown {
await Promise.all([
this.db.destroy(),
this.redisClient.disconnect(),
- this.redisForPubsub.disconnect(),
+ this.redisForPub.disconnect(),
+ this.redisForSub.disconnect(),
]);
}
}
diff --git a/packages/backend/src/core/AntennaService.ts b/packages/backend/src/core/AntennaService.ts
index 35266ac16d..02e0b455fd 100644
--- a/packages/backend/src/core/AntennaService.ts
+++ b/packages/backend/src/core/AntennaService.ts
@@ -27,8 +27,8 @@ export class AntennaService implements OnApplicationShutdown {
@Inject(DI.redis)
private redisClient: Redis.Redis,
- @Inject(DI.redisForPubsub)
- private redisForPubsub: Redis.Redis,
+ @Inject(DI.redisForSub)
+ private redisForSub: Redis.Redis,
@Inject(DI.mutingsRepository)
private mutingsRepository: MutingsRepository,
@@ -52,12 +52,12 @@ export class AntennaService implements OnApplicationShutdown {
this.antennasFetched = false;
this.antennas = [];
- this.redisForPubsub.on('message', this.onRedisMessage);
+ this.redisForSub.on('message', this.onRedisMessage);
}
@bindThis
public onApplicationShutdown(signal?: string | undefined) {
- this.redisForPubsub.off('message', this.onRedisMessage);
+ this.redisForSub.off('message', this.onRedisMessage);
}
@bindThis
@@ -95,7 +95,7 @@ export class AntennaService implements OnApplicationShutdown {
this.redisClient.xadd(
`antennaTimeline:${antenna.id}`,
'MAXLEN', '~', '200',
- `${this.idService.parse(note.id).date.getTime()}-*`,
+ '*',
'note', note.id);
this.globalEventService.publishAntennaStream(antenna.id, 'note', note);
diff --git a/packages/backend/src/core/CacheService.ts b/packages/backend/src/core/CacheService.ts
index d74f3e8788..561face5c3 100644
--- a/packages/backend/src/core/CacheService.ts
+++ b/packages/backend/src/core/CacheService.ts
@@ -27,8 +27,8 @@ export class CacheService implements OnApplicationShutdown {
@Inject(DI.redis)
private redisClient: Redis.Redis,
- @Inject(DI.redisForPubsub)
- private redisForPubsub: Redis.Redis,
+ @Inject(DI.redisForSub)
+ private redisForSub: Redis.Redis,
@Inject(DI.usersRepository)
private usersRepository: UsersRepository,
@@ -116,7 +116,7 @@ export class CacheService implements OnApplicationShutdown {
fromRedisConverter: (value) => new Set(JSON.parse(value)),
});
- this.redisForPubsub.on('message', this.onMessage);
+ this.redisForSub.on('message', this.onMessage);
}
@bindThis
@@ -167,6 +167,6 @@ export class CacheService implements OnApplicationShutdown {
@bindThis
public onApplicationShutdown(signal?: string | undefined) {
- this.redisForPubsub.off('message', this.onMessage);
+ this.redisForSub.off('message', this.onMessage);
}
}
diff --git a/packages/backend/src/core/CustomEmojiService.ts b/packages/backend/src/core/CustomEmojiService.ts
index dc365986fe..3de936dd65 100644
--- a/packages/backend/src/core/CustomEmojiService.ts
+++ b/packages/backend/src/core/CustomEmojiService.ts
@@ -43,12 +43,8 @@ export class CustomEmojiService {
lifetime: 1000 * 60 * 30, // 30m
memoryCacheLifetime: 1000 * 60 * 3, // 3m
fetcher: () => this.emojisRepository.find({ where: { host: IsNull() } }).then(emojis => new Map(emojis.map(emoji => [emoji.name, emoji]))),
- toRedisConverter: (value) => JSON.stringify(value.values()),
- fromRedisConverter: (value) => {
- // 原因不明だが配列以外が入ってくることがあるため
- if (!Array.isArray(JSON.parse(value))) return undefined;
- return new Map(JSON.parse(value).map((x: Emoji) => [x.name, x]));
- }, // TODO: Date型の変換
+ toRedisConverter: (value) => JSON.stringify(Array.from(value.values())),
+ fromRedisConverter: (value) => new Map(JSON.parse(value).map((x: Emoji) => [x.name, x])), // TODO: Date型の変換
});
}
diff --git a/packages/backend/src/core/GlobalEventService.ts b/packages/backend/src/core/GlobalEventService.ts
index 25c064a2b4..9f4de5f985 100644
--- a/packages/backend/src/core/GlobalEventService.ts
+++ b/packages/backend/src/core/GlobalEventService.ts
@@ -26,8 +26,8 @@ export class GlobalEventService {
@Inject(DI.config)
private config: Config,
- @Inject(DI.redis)
- private redisClient: Redis.Redis,
+ @Inject(DI.redisForPub)
+ private redisForPub: Redis.Redis,
) {
}
@@ -37,7 +37,7 @@ export class GlobalEventService {
{ type: type, body: null } :
{ type: type, body: value };
- this.redisClient.publish(this.config.host, JSON.stringify({
+ this.redisForPub.publish(this.config.host, JSON.stringify({
channel: channel,
message: message,
}));
diff --git a/packages/backend/src/core/MetaService.ts b/packages/backend/src/core/MetaService.ts
index 2b6160c82e..1322927c2c 100644
--- a/packages/backend/src/core/MetaService.ts
+++ b/packages/backend/src/core/MetaService.ts
@@ -14,8 +14,8 @@ export class MetaService implements OnApplicationShutdown {
private intervalId: NodeJS.Timer;
constructor(
- @Inject(DI.redisForPubsub)
- private redisForPubsub: Redis.Redis,
+ @Inject(DI.redisForSub)
+ private redisForSub: Redis.Redis,
@Inject(DI.db)
private db: DataSource,
@@ -33,7 +33,7 @@ export class MetaService implements OnApplicationShutdown {
}, 1000 * 60 * 5);
}
- this.redisForPubsub.on('message', this.onMessage);
+ this.redisForSub.on('message', this.onMessage);
}
@bindThis
@@ -122,6 +122,6 @@ export class MetaService implements OnApplicationShutdown {
@bindThis
public onApplicationShutdown(signal?: string | undefined) {
clearInterval(this.intervalId);
- this.redisForPubsub.off('message', this.onMessage);
+ this.redisForSub.off('message', this.onMessage);
}
}
diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts
index 5c4d13f178..fb7ee7080a 100644
--- a/packages/backend/src/core/NoteCreateService.ts
+++ b/packages/backend/src/core/NoteCreateService.ts
@@ -329,7 +329,7 @@ export class NoteCreateService implements OnApplicationShutdown {
this.redisClient.xadd(
`channelTimeline:${data.channel.id}`,
'MAXLEN', '~', '1000',
- `${this.idService.parse(note.id).date.getTime()}-*`,
+ '*',
'note', note.id);
}
diff --git a/packages/backend/src/core/NotificationService.ts b/packages/backend/src/core/NotificationService.ts
index c44dddea41..6691c42836 100644
--- a/packages/backend/src/core/NotificationService.ts
+++ b/packages/backend/src/core/NotificationService.ts
@@ -66,6 +66,7 @@ export class NotificationService implements OnApplicationShutdown {
@bindThis
private postReadAllNotifications(userId: User['id']) {
this.globalEventService.publishMainStream(userId, 'readAllNotifications');
+ this.pushNotificationService.pushNotification(userId, 'readAllNotifications', undefined);
}
@bindThis
@@ -99,7 +100,7 @@ export class NotificationService implements OnApplicationShutdown {
const redisIdPromise = this.redisClient.xadd(
`notificationTimeline:${notifieeId}`,
'MAXLEN', '~', '300',
- `${this.idService.parse(notification.id).date.getTime()}-*`,
+ '*',
'data', JSON.stringify(notification));
const packed = await this.notificationEntityService.pack(notification, notifieeId, {});
diff --git a/packages/backend/src/core/PushNotificationService.ts b/packages/backend/src/core/PushNotificationService.ts
index 69020f7e84..9b44cf6413 100644
--- a/packages/backend/src/core/PushNotificationService.ts
+++ b/packages/backend/src/core/PushNotificationService.ts
@@ -1,12 +1,14 @@
import { Inject, Injectable } from '@nestjs/common';
import push from 'web-push';
+import Redis from 'ioredis';
import { DI } from '@/di-symbols.js';
import type { Config } from '@/config.js';
import type { Packed } from '@/misc/json-schema';
import { getNoteSummary } from '@/misc/get-note-summary.js';
-import type { SwSubscriptionsRepository } from '@/models/index.js';
+import type { SwSubscription, SwSubscriptionsRepository } from '@/models/index.js';
import { MetaService } from '@/core/MetaService.js';
import { bindThis } from '@/decorators.js';
+import { RedisKVCache } from '@/misc/cache.js';
// Defined also packages/sw/types.ts#L13
type PushNotificationsTypes = {
@@ -15,6 +17,7 @@ type PushNotificationsTypes = {
antenna: { id: string, name: string };
note: Packed<'Note'>;
};
+ 'readAllNotifications': undefined;
};
// Reduce length because push message servers have character limits
@@ -40,15 +43,27 @@ function truncateBody<T extends keyof PushNotificationsTypes>(type: T, body: Pus
@Injectable()
export class PushNotificationService {
+ private subscriptionsCache: RedisKVCache<SwSubscription[]>;
+
constructor(
@Inject(DI.config)
private config: Config,
+ @Inject(DI.redis)
+ private redisClient: Redis.Redis,
+
@Inject(DI.swSubscriptionsRepository)
private swSubscriptionsRepository: SwSubscriptionsRepository,
private metaService: MetaService,
) {
+ this.subscriptionsCache = new RedisKVCache<SwSubscription[]>(this.redisClient, 'userSwSubscriptions', {
+ lifetime: 1000 * 60 * 60 * 1, // 1h
+ memoryCacheLifetime: 1000 * 60 * 3, // 3m
+ fetcher: (key) => this.swSubscriptionsRepository.findBy({ userId: key }),
+ toRedisConverter: (value) => JSON.stringify(value),
+ fromRedisConverter: (value) => JSON.parse(value),
+ });
}
@bindThis
@@ -62,12 +77,13 @@ export class PushNotificationService {
meta.swPublicKey,
meta.swPrivateKey);
- // Fetch
- const subscriptions = await this.swSubscriptionsRepository.findBy({
- userId: userId,
- });
+ const subscriptions = await this.subscriptionsCache.fetch(userId);
for (const subscription of subscriptions) {
+ if ([
+ 'readAllNotifications',
+ ].includes(type) && !subscription.sendReadMessage) continue;
+
const pushSubscription = {
endpoint: subscription.endpoint,
keys: {
diff --git a/packages/backend/src/core/RoleService.ts b/packages/backend/src/core/RoleService.ts
index c8ebe1adb7..77645e3f06 100644
--- a/packages/backend/src/core/RoleService.ts
+++ b/packages/backend/src/core/RoleService.ts
@@ -64,8 +64,8 @@ export class RoleService implements OnApplicationShutdown {
public static NotAssignedError = class extends Error {};
constructor(
- @Inject(DI.redisForPubsub)
- private redisForPubsub: Redis.Redis,
+ @Inject(DI.redisForSub)
+ private redisForSub: Redis.Redis,
@Inject(DI.usersRepository)
private usersRepository: UsersRepository,
@@ -87,7 +87,7 @@ export class RoleService implements OnApplicationShutdown {
this.rolesCache = new MemorySingleCache<Role[]>(1000 * 60 * 60 * 1);
this.roleAssignmentByUserIdCache = new MemoryKVCache<RoleAssignment[]>(1000 * 60 * 60 * 1);
- this.redisForPubsub.on('message', this.onMessage);
+ this.redisForSub.on('message', this.onMessage);
}
@bindThis
@@ -400,6 +400,6 @@ export class RoleService implements OnApplicationShutdown {
@bindThis
public onApplicationShutdown(signal?: string | undefined) {
- this.redisForPubsub.off('message', this.onMessage);
+ this.redisForSub.off('message', this.onMessage);
}
}
diff --git a/packages/backend/src/core/WebhookService.ts b/packages/backend/src/core/WebhookService.ts
index 85594f8557..926115613b 100644
--- a/packages/backend/src/core/WebhookService.ts
+++ b/packages/backend/src/core/WebhookService.ts
@@ -13,14 +13,14 @@ export class WebhookService implements OnApplicationShutdown {
private webhooks: Webhook[] = [];
constructor(
- @Inject(DI.redisForPubsub)
- private redisForPubsub: Redis.Redis,
+ @Inject(DI.redisForSub)
+ private redisForSub: Redis.Redis,
@Inject(DI.webhooksRepository)
private webhooksRepository: WebhooksRepository,
) {
//this.onMessage = this.onMessage.bind(this);
- this.redisForPubsub.on('message', this.onMessage);
+ this.redisForSub.on('message', this.onMessage);
}
@bindThis
@@ -82,6 +82,6 @@ export class WebhookService implements OnApplicationShutdown {
@bindThis
public onApplicationShutdown(signal?: string | undefined) {
- this.redisForPubsub.off('message', this.onMessage);
+ this.redisForSub.off('message', this.onMessage);
}
}
diff --git a/packages/backend/src/di-symbols.ts b/packages/backend/src/di-symbols.ts
index 482e8f83e1..d4b1fb31b1 100644
--- a/packages/backend/src/di-symbols.ts
+++ b/packages/backend/src/di-symbols.ts
@@ -2,7 +2,8 @@ export const DI = {
config: Symbol('config'),
db: Symbol('db'),
redis: Symbol('redis'),
- redisForPubsub: Symbol('redisForPubsub'),
+ redisForPub: Symbol('redisForPub'),
+ redisForSub: Symbol('redisForSub'),
//#region Repositories
usersRepository: Symbol('usersRepository'),
diff --git a/packages/backend/src/misc/cache.ts b/packages/backend/src/misc/cache.ts
index a4abd4f878..d35414acf7 100644
--- a/packages/backend/src/misc/cache.ts
+++ b/packages/backend/src/misc/cache.ts
@@ -8,7 +8,7 @@ export class RedisKVCache<T> {
private memoryCache: MemoryKVCache<T>;
private fetcher: (key: string) => Promise<T>;
private toRedisConverter: (value: T) => string;
- private fromRedisConverter: (value: string) => T | undefined; // undefined means no cache
+ private fromRedisConverter: (value: string) => T;
constructor(redisClient: RedisKVCache<T>['redisClient'], name: RedisKVCache<T>['name'], opts: {
lifetime: RedisKVCache<T>['lifetime'];
@@ -92,7 +92,7 @@ export class RedisSingleCache<T> {
private memoryCache: MemorySingleCache<T>;
private fetcher: () => Promise<T>;
private toRedisConverter: (value: T) => string;
- private fromRedisConverter: (value: string) => T | undefined; // undefined means no cache
+ private fromRedisConverter: (value: string) => T;
constructor(redisClient: RedisSingleCache<T>['redisClient'], name: RedisSingleCache<T>['name'], opts: {
lifetime: RedisSingleCache<T>['lifetime'];
diff --git a/packages/backend/src/server/api/ApiServerService.ts b/packages/backend/src/server/api/ApiServerService.ts
index b806ad5ca3..d3b1c7786d 100644
--- a/packages/backend/src/server/api/ApiServerService.ts
+++ b/packages/backend/src/server/api/ApiServerService.ts
@@ -89,7 +89,7 @@ export class ApiServerService {
Params: { endpoint: string; },
Body: Record<string, unknown>,
Querystring: Record<string, unknown>,
- }>('/' + endpoint.name, { bodyLimit: 1024 * 32 }, async (request, reply) => {
+ }>('/' + endpoint.name, { bodyLimit: 1024 * 1024 }, async (request, reply) => {
if (request.method === 'GET' && !endpoint.meta.allowGet) {
reply.code(405);
reply.send();
diff --git a/packages/backend/src/server/api/EndpointsModule.ts b/packages/backend/src/server/api/EndpointsModule.ts
index 5a53b3faf7..ca89d82853 100644
--- a/packages/backend/src/server/api/EndpointsModule.ts
+++ b/packages/backend/src/server/api/EndpointsModule.ts
@@ -98,6 +98,7 @@ 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___channels_search from './endpoints/channels/search.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';
@@ -431,6 +432,7 @@ const $channels_update: Provider = { provide: 'ep:channels/update', useClass: ep
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 $channels_search: Provider = { provide: 'ep:channels/search', useClass: ep___channels_search.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 };
@@ -768,6 +770,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
$channels_favorite,
$channels_unfavorite,
$channels_myFavorites,
+ $channels_search,
$charts_activeUsers,
$charts_apRequest,
$charts_drive,
@@ -1099,6 +1102,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
$channels_favorite,
$channels_unfavorite,
$channels_myFavorites,
+ $channels_search,
$charts_activeUsers,
$charts_apRequest,
$charts_drive,
diff --git a/packages/backend/src/server/api/StreamingApiServerService.ts b/packages/backend/src/server/api/StreamingApiServerService.ts
index e0e5b71a82..769a4490d6 100644
--- a/packages/backend/src/server/api/StreamingApiServerService.ts
+++ b/packages/backend/src/server/api/StreamingApiServerService.ts
@@ -22,8 +22,8 @@ export class StreamingApiServerService {
@Inject(DI.config)
private config: Config,
- @Inject(DI.redisForPubsub)
- private redisForPubsub: Redis.Redis,
+ @Inject(DI.redisForSub)
+ private redisForSub: Redis.Redis,
@Inject(DI.usersRepository)
private usersRepository: UsersRepository,
@@ -81,7 +81,7 @@ export class StreamingApiServerService {
ev.emit(parsed.channel, parsed.message);
}
- this.redisForPubsub.on('message', onRedisMessage);
+ this.redisForSub.on('message', onRedisMessage);
const main = new MainStreamConnection(
this.channelsService,
@@ -111,7 +111,7 @@ export class StreamingApiServerService {
connection.once('close', () => {
ev.removeAllListeners();
main.dispose();
- this.redisForPubsub.off('message', onRedisMessage);
+ this.redisForSub.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 fd268c7912..dab897117d 100644
--- a/packages/backend/src/server/api/endpoints.ts
+++ b/packages/backend/src/server/api/endpoints.ts
@@ -98,6 +98,7 @@ 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___channels_search from './endpoints/channels/search.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';
@@ -429,6 +430,7 @@ const eps = [
['channels/favorite', ep___channels_favorite],
['channels/unfavorite', ep___channels_unfavorite],
['channels/my-favorites', ep___channels_myFavorites],
+ ['channels/search', ep___channels_search],
['charts/active-users', ep___charts_activeUsers],
['charts/ap-request', ep___charts_apRequest],
['charts/drive', ep___charts_drive],
diff --git a/packages/backend/src/server/api/endpoints/channels/search.ts b/packages/backend/src/server/api/endpoints/channels/search.ts
new file mode 100644
index 0000000000..a954ba224c
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/channels/search.ts
@@ -0,0 +1,67 @@
+import { Inject, Injectable } from '@nestjs/common';
+import { Brackets } from 'typeorm';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { QueryService } from '@/core/QueryService.js';
+import type { ChannelsRepository } from '@/models/index.js';
+import { ChannelEntityService } from '@/core/entities/ChannelEntityService.js';
+import { DI } from '@/di-symbols.js';
+import { sqlLikeEscape } from '@/misc/sql-like-escape.js';
+
+export const meta = {
+ tags: ['channels'],
+
+ requireCredential: false,
+
+ res: {
+ type: 'array',
+ optional: false, nullable: false,
+ items: {
+ type: 'object',
+ optional: false, nullable: false,
+ ref: 'Channel',
+ },
+ },
+} as const;
+
+export const paramDef = {
+ type: 'object',
+ properties: {
+ query: { type: 'string' },
+ type: { type: 'string', enum: ['nameAndDescription', 'nameOnly'], default: 'nameAndDescription' },
+ sinceId: { type: 'string', format: 'misskey:id' },
+ untilId: { type: 'string', format: 'misskey:id' },
+ limit: { type: 'integer', minimum: 1, maximum: 100, default: 5 },
+ },
+ required: ['query'],
+} 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,
+
+ private channelEntityService: ChannelEntityService,
+ private queryService: QueryService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const query = this.queryService.makePaginationQuery(this.channelsRepository.createQueryBuilder('channel'), ps.sinceId, ps.untilId);
+
+ if (ps.type === 'nameAndDescription') {
+ query.andWhere(new Brackets(qb => { qb
+ .where('channel.name ILIKE :q', { q: `%${ sqlLikeEscape(ps.query) }%` })
+ .orWhere('channel.description ILIKE :q', { q: `%${ sqlLikeEscape(ps.query) }%` });
+ }));
+ } else {
+ query.andWhere('channel.name ILIKE :q', { q: `%${ sqlLikeEscape(ps.query) }%` });
+ }
+
+ const channels = await query
+ .take(ps.limit)
+ .getMany();
+
+ return await Promise.all(channels.map(x => this.channelEntityService.pack(x, me)));
+ });
+ }
+}
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 836c5aae6c..09b0005ac1 100644
--- a/packages/backend/src/server/api/stream/channels/local-timeline.ts
+++ b/packages/backend/src/server/api/stream/channels/local-timeline.ts
@@ -54,10 +54,10 @@ class LocalTimelineChannel extends Channel {
}
// 関係ない返信は除外
- if (note.reply && !this.user!.showTimelineReplies) {
+ if (note.reply && this.user && !this.user.showTimelineReplies) {
const reply = note.reply;
// 「チャンネル接続主への返信」でもなければ、「チャンネル接続主が行った返信」でもなければ、「投稿者の投稿者自身への返信」でもない場合
- if (reply.userId !== this.user!.id && note.userId !== this.user!.id && reply.userId !== note.userId) return;
+ if (reply.userId !== this.user.id && note.userId !== this.user.id && reply.userId !== note.userId) return;
}
// 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する