summaryrefslogtreecommitdiff
path: root/packages/backend/src/server/api
diff options
context:
space:
mode:
authordakkar <dakkar@thenautilus.net>2024-08-18 18:32:27 +0000
committerdakkar <dakkar@thenautilus.net>2024-08-18 18:32:27 +0000
commitf5560783ea24eda75edc1223daa3c1690204fd9f (patch)
tree9f30d06979bcd45522bbbf154efbbd082f4356bf /packages/backend/src/server/api
parentmerge: make the cap of `activeRateLimitRequests` match the rate limit (!602) (diff)
parentMerge branch 'develop' into feature/misskey-2024.07 (diff)
downloadsharkey-f5560783ea24eda75edc1223daa3c1690204fd9f.tar.gz
sharkey-f5560783ea24eda75edc1223daa3c1690204fd9f.tar.bz2
sharkey-f5560783ea24eda75edc1223daa3c1690204fd9f.zip
merge: misskey 2024.7 (!583)
View MR for information: https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/583 Approved-by: Marie <github@yuugi.dev> Approved-by: Julia Johannesen <julia@insertdomain.name>
Diffstat (limited to 'packages/backend/src/server/api')
-rw-r--r--packages/backend/src/server/api/ApiCallService.ts42
-rw-r--r--packages/backend/src/server/api/EndpointsModule.ts42
-rw-r--r--packages/backend/src/server/api/RateLimiterService.ts36
-rw-r--r--packages/backend/src/server/api/SigninService.ts4
-rw-r--r--packages/backend/src/server/api/SignupApiService.ts4
-rw-r--r--packages/backend/src/server/api/endpoints.ts38
-rw-r--r--packages/backend/src/server/api/endpoints/admin/abuse-report/notification-recipient/create.ts122
-rw-r--r--packages/backend/src/server/api/endpoints/admin/abuse-report/notification-recipient/delete.ts44
-rw-r--r--packages/backend/src/server/api/endpoints/admin/abuse-report/notification-recipient/list.ts55
-rw-r--r--packages/backend/src/server/api/endpoints/admin/abuse-report/notification-recipient/show.ts64
-rw-r--r--packages/backend/src/server/api/endpoints/admin/abuse-report/notification-recipient/update.ts128
-rw-r--r--packages/backend/src/server/api/endpoints/admin/ad/create.ts4
-rw-r--r--packages/backend/src/server/api/endpoints/admin/ad/update.ts6
-rw-r--r--packages/backend/src/server/api/endpoints/admin/announcements/list.ts9
-rw-r--r--packages/backend/src/server/api/endpoints/admin/drive/show-file.ts2
-rw-r--r--packages/backend/src/server/api/endpoints/admin/invite/create.ts4
-rw-r--r--packages/backend/src/server/api/endpoints/admin/meta.ts11
-rw-r--r--packages/backend/src/server/api/endpoints/admin/queue/stats.ts5
-rw-r--r--packages/backend/src/server/api/endpoints/admin/resolve-abuse-user-report.ts53
-rw-r--r--packages/backend/src/server/api/endpoints/admin/roles/update.ts14
-rw-r--r--packages/backend/src/server/api/endpoints/admin/system-webhook/create.ts85
-rw-r--r--packages/backend/src/server/api/endpoints/admin/system-webhook/delete.ts44
-rw-r--r--packages/backend/src/server/api/endpoints/admin/system-webhook/list.ts60
-rw-r--r--packages/backend/src/server/api/endpoints/admin/system-webhook/show.ts62
-rw-r--r--packages/backend/src/server/api/endpoints/admin/system-webhook/update.ts91
-rw-r--r--packages/backend/src/server/api/endpoints/admin/update-meta.ts15
-rw-r--r--packages/backend/src/server/api/endpoints/antennas/create.ts6
-rw-r--r--packages/backend/src/server/api/endpoints/app/create.ts4
-rw-r--r--packages/backend/src/server/api/endpoints/auth/session/generate.ts4
-rw-r--r--packages/backend/src/server/api/endpoints/channels/create.ts4
-rw-r--r--packages/backend/src/server/api/endpoints/clips/update.ts4
-rw-r--r--packages/backend/src/server/api/endpoints/drive/folders/create.ts4
-rw-r--r--packages/backend/src/server/api/endpoints/drive/folders/update.ts9
-rw-r--r--packages/backend/src/server/api/endpoints/flash/create.ts4
-rw-r--r--packages/backend/src/server/api/endpoints/gallery/posts/create.ts7
-rw-r--r--packages/backend/src/server/api/endpoints/gallery/posts/update.ts25
-rw-r--r--packages/backend/src/server/api/endpoints/i/import-antennas.ts2
-rw-r--r--packages/backend/src/server/api/endpoints/i/update.ts32
-rw-r--r--packages/backend/src/server/api/endpoints/i/webhooks/create.ts6
-rw-r--r--packages/backend/src/server/api/endpoints/i/webhooks/update.ts6
-rw-r--r--packages/backend/src/server/api/endpoints/invite/create.ts4
-rw-r--r--packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts14
-rw-r--r--packages/backend/src/server/api/endpoints/notes/polls/vote.ts4
-rw-r--r--packages/backend/src/server/api/endpoints/notes/reactions/create.ts7
-rw-r--r--packages/backend/src/server/api/endpoints/notes/timeline.ts2
-rw-r--r--packages/backend/src/server/api/endpoints/pages/create.ts4
-rw-r--r--packages/backend/src/server/api/endpoints/pages/update.ts23
-rw-r--r--packages/backend/src/server/api/endpoints/pinned-users.ts3
-rw-r--r--packages/backend/src/server/api/endpoints/renote-mute/create.ts23
-rw-r--r--packages/backend/src/server/api/endpoints/renote-mute/delete.ts8
-rw-r--r--packages/backend/src/server/api/endpoints/users/lists/create-from-public.ts6
-rw-r--r--packages/backend/src/server/api/endpoints/users/lists/create.ts6
-rw-r--r--packages/backend/src/server/api/endpoints/users/reactions.ts19
-rw-r--r--packages/backend/src/server/api/endpoints/users/report-abuse.ts65
-rw-r--r--packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts101
-rw-r--r--packages/backend/src/server/api/endpoints/users/search.ts108
-rw-r--r--packages/backend/src/server/api/openapi/OpenApiServerService.ts2
-rw-r--r--packages/backend/src/server/api/openapi/gen-spec.ts1
-rw-r--r--packages/backend/src/server/api/stream/Connection.ts59
-rw-r--r--packages/backend/src/server/api/stream/channel.ts13
-rw-r--r--packages/backend/src/server/api/stream/channels/admin.ts3
-rw-r--r--packages/backend/src/server/api/stream/channels/antenna.ts6
-rw-r--r--packages/backend/src/server/api/stream/channels/bubble-timeline.ts9
-rw-r--r--packages/backend/src/server/api/stream/channels/channel.ts8
-rw-r--r--packages/backend/src/server/api/stream/channels/drive.ts3
-rw-r--r--packages/backend/src/server/api/stream/channels/global-timeline.ts9
-rw-r--r--packages/backend/src/server/api/stream/channels/hashtag.ts7
-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.ts23
-rw-r--r--packages/backend/src/server/api/stream/channels/local-timeline.ts11
-rw-r--r--packages/backend/src/server/api/stream/channels/main.ts3
-rw-r--r--packages/backend/src/server/api/stream/channels/queue-stats.ts10
-rw-r--r--packages/backend/src/server/api/stream/channels/reversi-game.ts33
-rw-r--r--packages/backend/src/server/api/stream/channels/reversi.ts3
-rw-r--r--packages/backend/src/server/api/stream/channels/role-timeline.ts6
-rw-r--r--packages/backend/src/server/api/stream/channels/server-stats.ts8
-rw-r--r--packages/backend/src/server/api/stream/channels/user-list.ts10
77 files changed, 1303 insertions, 493 deletions
diff --git a/packages/backend/src/server/api/ApiCallService.ts b/packages/backend/src/server/api/ApiCallService.ts
index 271ef80554..47f64f6609 100644
--- a/packages/backend/src/server/api/ApiCallService.ts
+++ b/packages/backend/src/server/api/ApiCallService.ts
@@ -73,6 +73,16 @@ export class ApiCallService implements OnApplicationShutdown {
reply.header('WWW-Authenticate', `Bearer realm="Misskey", error="insufficient_scope", error_description="${err.message}"`);
}
statusCode = statusCode ?? 403;
+ } else if (err.code === 'RATE_LIMIT_EXCEEDED') {
+ const info: unknown = err.info;
+ const unixEpochInSeconds = Date.now();
+ if (typeof(info) === 'object' && info && 'resetMs' in info && typeof(info.resetMs) === 'number') {
+ const cooldownInSeconds = Math.ceil((info.resetMs - unixEpochInSeconds) / 1000);
+ // もしかするとマイナスになる可能性がなくはないのでマイナスだったら0にしておく
+ reply.header('Retry-After', Math.max(cooldownInSeconds, 0).toString(10));
+ } else {
+ this.logger.warn(`rate limit information has unexpected type ${typeof(err.info?.reset)}`);
+ }
} else if (!statusCode) {
statusCode = 500;
}
@@ -93,7 +103,7 @@ export class ApiCallService implements OnApplicationShutdown {
}
}
- #onExecError(ep: IEndpoint, data: any, err: Error): void {
+ #onExecError(ep: IEndpoint, data: any, err: Error, userId?: MiUser['id']): void {
if (err instanceof ApiError || err instanceof AuthenticationError) {
throw err;
} else {
@@ -108,10 +118,13 @@ export class ApiCallService implements OnApplicationShutdown {
id: errId,
},
});
- console.error(err, errId);
if (this.config.sentryForBackend) {
Sentry.captureMessage(`Internal error occurred in ${ep.name}: ${err.message}`, {
+ level: 'error',
+ user: {
+ id: userId,
+ },
extra: {
ep: ep.name,
ps: data,
@@ -305,12 +318,17 @@ export class ApiCallService implements OnApplicationShutdown {
if (factor > 0) {
// Rate limit
await this.rateLimiterService.limit(limit as IEndpointMeta['limit'] & { key: NonNullable<string> }, limitActor, factor).catch(err => {
- throw new ApiError({
- message: 'Rate limit exceeded. Please try again later.',
- code: 'RATE_LIMIT_EXCEEDED',
- id: 'd5826d14-3982-4d2e-8011-b9e9f02499ef',
- httpStatusCode: 429,
- });
+ if ('info' in err) {
+ // errはLimiter.LimiterInfoであることが期待される
+ throw new ApiError({
+ message: 'Rate limit exceeded. Please try again later.',
+ code: 'RATE_LIMIT_EXCEEDED',
+ id: 'd5826d14-3982-4d2e-8011-b9e9f02499ef',
+ httpStatusCode: 429,
+ }, err.info);
+ } else {
+ throw new TypeError('information must be a rate-limiter information.');
+ }
});
}
}
@@ -410,9 +428,13 @@ export class ApiCallService implements OnApplicationShutdown {
// API invoking
if (this.config.sentryForBackend) {
- return await Sentry.startSpan({ name: 'API: ' + ep.name }, () => ep.exec(data, user, token, file, request.ip, request.headers).catch((err: Error) => this.#onExecError(ep, data, err)));
+ return await Sentry.startSpan({
+ name: 'API: ' + ep.name,
+ }, () => ep.exec(data, user, token, file, request.ip, request.headers)
+ .catch((err: Error) => this.#onExecError(ep, data, err, user?.id)));
} else {
- return await ep.exec(data, user, token, file, request.ip, request.headers).catch((err: Error) => this.#onExecError(ep, data, err));
+ return await ep.exec(data, user, token, file, request.ip, request.headers)
+ .catch((err: Error) => this.#onExecError(ep, data, err, user?.id));
}
}
diff --git a/packages/backend/src/server/api/EndpointsModule.ts b/packages/backend/src/server/api/EndpointsModule.ts
index f44635fba0..4a08410ceb 100644
--- a/packages/backend/src/server/api/EndpointsModule.ts
+++ b/packages/backend/src/server/api/EndpointsModule.ts
@@ -6,8 +6,13 @@
import { Module } from '@nestjs/common';
import { CoreModule } from '@/core/CoreModule.js';
-import * as ep___admin_meta from './endpoints/admin/meta.js';
+import * as ep___admin_abuseReport_notificationRecipient_list from '@/server/api/endpoints/admin/abuse-report/notification-recipient/list.js';
+import * as ep___admin_abuseReport_notificationRecipient_show from '@/server/api/endpoints/admin/abuse-report/notification-recipient/show.js';
+import * as ep___admin_abuseReport_notificationRecipient_create from '@/server/api/endpoints/admin/abuse-report/notification-recipient/create.js';
+import * as ep___admin_abuseReport_notificationRecipient_update from '@/server/api/endpoints/admin/abuse-report/notification-recipient/update.js';
+import * as ep___admin_abuseReport_notificationRecipient_delete from '@/server/api/endpoints/admin/abuse-report/notification-recipient/delete.js';
import * as ep___admin_abuseUserReports from './endpoints/admin/abuse-user-reports.js';
+import * as ep___admin_meta from './endpoints/admin/meta.js';
import * as ep___admin_accounts_create from './endpoints/admin/accounts/create.js';
import * as ep___admin_accounts_delete from './endpoints/admin/accounts/delete.js';
import * as ep___admin_accounts_findByEmail from './endpoints/admin/accounts/find-by-email.js';
@@ -87,6 +92,11 @@ import * as ep___admin_roles_assign from './endpoints/admin/roles/assign.js';
import * as ep___admin_roles_unassign from './endpoints/admin/roles/unassign.js';
import * as ep___admin_roles_updateDefaultPolicies from './endpoints/admin/roles/update-default-policies.js';
import * as ep___admin_roles_users from './endpoints/admin/roles/users.js';
+import * as ep___admin_systemWebhook_create from './endpoints/admin/system-webhook/create.js';
+import * as ep___admin_systemWebhook_delete from './endpoints/admin/system-webhook/delete.js';
+import * as ep___admin_systemWebhook_list from './endpoints/admin/system-webhook/list.js';
+import * as ep___admin_systemWebhook_show from './endpoints/admin/system-webhook/show.js';
+import * as ep___admin_systemWebhook_update from './endpoints/admin/system-webhook/update.js';
import * as ep___announcements from './endpoints/announcements.js';
import * as ep___announcements_show from './endpoints/announcements/show.js';
import * as ep___antennas_create from './endpoints/antennas/create.js';
@@ -394,6 +404,11 @@ import type { Provider } from '@nestjs/common';
const $admin_meta: Provider = { provide: 'ep:admin/meta', useClass: ep___admin_meta.default };
const $admin_abuseUserReports: Provider = { provide: 'ep:admin/abuse-user-reports', useClass: ep___admin_abuseUserReports.default };
+const $admin_abuseReport_notificationRecipient_list: Provider = { provide: 'ep:admin/abuse-report/notification-recipient/list', useClass: ep___admin_abuseReport_notificationRecipient_list.default };
+const $admin_abuseReport_notificationRecipient_show: Provider = { provide: 'ep:admin/abuse-report/notification-recipient/show', useClass: ep___admin_abuseReport_notificationRecipient_show.default };
+const $admin_abuseReport_notificationRecipient_create: Provider = { provide: 'ep:admin/abuse-report/notification-recipient/create', useClass: ep___admin_abuseReport_notificationRecipient_create.default };
+const $admin_abuseReport_notificationRecipient_update: Provider = { provide: 'ep:admin/abuse-report/notification-recipient/update', useClass: ep___admin_abuseReport_notificationRecipient_update.default };
+const $admin_abuseReport_notificationRecipient_delete: Provider = { provide: 'ep:admin/abuse-report/notification-recipient/delete', useClass: ep___admin_abuseReport_notificationRecipient_delete.default };
const $admin_accounts_create: Provider = { provide: 'ep:admin/accounts/create', useClass: ep___admin_accounts_create.default };
const $admin_accounts_delete: Provider = { provide: 'ep:admin/accounts/delete', useClass: ep___admin_accounts_delete.default };
const $admin_accounts_findByEmail: Provider = { provide: 'ep:admin/accounts/find-by-email', useClass: ep___admin_accounts_findByEmail.default };
@@ -473,6 +488,11 @@ const $admin_roles_assign: Provider = { provide: 'ep:admin/roles/assign', useCla
const $admin_roles_unassign: Provider = { provide: 'ep:admin/roles/unassign', useClass: ep___admin_roles_unassign.default };
const $admin_roles_updateDefaultPolicies: Provider = { provide: 'ep:admin/roles/update-default-policies', useClass: ep___admin_roles_updateDefaultPolicies.default };
const $admin_roles_users: Provider = { provide: 'ep:admin/roles/users', useClass: ep___admin_roles_users.default };
+const $admin_systemWebhook_create: Provider = { provide: 'ep:admin/system-webhook/create', useClass: ep___admin_systemWebhook_create.default };
+const $admin_systemWebhook_delete: Provider = { provide: 'ep:admin/system-webhook/delete', useClass: ep___admin_systemWebhook_delete.default };
+const $admin_systemWebhook_list: Provider = { provide: 'ep:admin/system-webhook/list', useClass: ep___admin_systemWebhook_list.default };
+const $admin_systemWebhook_show: Provider = { provide: 'ep:admin/system-webhook/show', useClass: ep___admin_systemWebhook_show.default };
+const $admin_systemWebhook_update: Provider = { provide: 'ep:admin/system-webhook/update', useClass: ep___admin_systemWebhook_update.default };
const $announcements: Provider = { provide: 'ep:announcements', useClass: ep___announcements.default };
const $announcements_show: Provider = { provide: 'ep:announcements/show', useClass: ep___announcements_show.default };
const $antennas_create: Provider = { provide: 'ep:antennas/create', useClass: ep___antennas_create.default };
@@ -784,6 +804,11 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
ApiLoggerService,
$admin_meta,
$admin_abuseUserReports,
+ $admin_abuseReport_notificationRecipient_list,
+ $admin_abuseReport_notificationRecipient_show,
+ $admin_abuseReport_notificationRecipient_create,
+ $admin_abuseReport_notificationRecipient_update,
+ $admin_abuseReport_notificationRecipient_delete,
$admin_accounts_create,
$admin_accounts_delete,
$admin_accounts_findByEmail,
@@ -863,6 +888,11 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
$admin_roles_unassign,
$admin_roles_updateDefaultPolicies,
$admin_roles_users,
+ $admin_systemWebhook_create,
+ $admin_systemWebhook_delete,
+ $admin_systemWebhook_list,
+ $admin_systemWebhook_show,
+ $admin_systemWebhook_update,
$announcements,
$announcements_show,
$antennas_create,
@@ -1168,6 +1198,11 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
exports: [
$admin_meta,
$admin_abuseUserReports,
+ $admin_abuseReport_notificationRecipient_list,
+ $admin_abuseReport_notificationRecipient_show,
+ $admin_abuseReport_notificationRecipient_create,
+ $admin_abuseReport_notificationRecipient_update,
+ $admin_abuseReport_notificationRecipient_delete,
$admin_accounts_create,
$admin_accounts_delete,
$admin_accounts_findByEmail,
@@ -1247,6 +1282,11 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
$admin_roles_unassign,
$admin_roles_updateDefaultPolicies,
$admin_roles_users,
+ $admin_systemWebhook_create,
+ $admin_systemWebhook_delete,
+ $admin_systemWebhook_list,
+ $admin_systemWebhook_show,
+ $admin_systemWebhook_update,
$announcements,
$announcements_show,
$antennas_create,
diff --git a/packages/backend/src/server/api/RateLimiterService.ts b/packages/backend/src/server/api/RateLimiterService.ts
index 0439cdfe5e..52d73baa0a 100644
--- a/packages/backend/src/server/api/RateLimiterService.ts
+++ b/packages/backend/src/server/api/RateLimiterService.ts
@@ -32,11 +32,13 @@ export class RateLimiterService {
@bindThis
public limit(limitation: IEndpointMeta['limit'] & { key: NonNullable<string> }, actor: string, factor = 1) {
- return new Promise<void>((ok, reject) => {
- if (this.disabled) ok();
+ {
+ if (this.disabled) {
+ return Promise.resolve();
+ }
// Short-term limit
- const min = (): void => {
+ const min = new Promise<void>((ok, reject) => {
const minIntervalLimiter = new Limiter({
id: `${actor}:${limitation.key}:min`,
duration: limitation.minInterval! * factor,
@@ -46,25 +48,25 @@ export class RateLimiterService {
minIntervalLimiter.get((err, info) => {
if (err) {
- return reject('ERR');
+ return reject({ code: 'ERR', info });
}
this.logger.debug(`${actor} ${limitation.key} min remaining: ${info.remaining}`);
if (info.remaining === 0) {
- reject('BRIEF_REQUEST_INTERVAL');
+ return reject({ code: 'BRIEF_REQUEST_INTERVAL', info });
} else {
if (hasLongTermLimit) {
- max();
+ return max.then(ok, reject);
} else {
- ok();
+ return ok();
}
}
});
- };
+ });
// Long term limit
- const max = (): void => {
+ const max = new Promise<void>((ok, reject) => {
const limiter = new Limiter({
id: `${actor}:${limitation.key}`,
duration: limitation.duration! * factor,
@@ -74,18 +76,18 @@ export class RateLimiterService {
limiter.get((err, info) => {
if (err) {
- return reject('ERR');
+ return reject({ code: 'ERR', info });
}
this.logger.debug(`${actor} ${limitation.key} max remaining: ${info.remaining}`);
if (info.remaining === 0) {
- reject('RATE_LIMIT_EXCEEDED');
+ return reject({ code: 'RATE_LIMIT_EXCEEDED', info });
} else {
- ok();
+ return ok();
}
});
- };
+ });
const hasShortTermLimit = typeof limitation.minInterval === 'number';
@@ -94,12 +96,12 @@ export class RateLimiterService {
typeof limitation.max === 'number';
if (hasShortTermLimit) {
- min();
+ return min;
} else if (hasLongTermLimit) {
- max();
+ return max;
} else {
- ok();
+ return Promise.resolve();
}
- });
+ }
}
}
diff --git a/packages/backend/src/server/api/SigninService.ts b/packages/backend/src/server/api/SigninService.ts
index 714e56e8c3..70306c3113 100644
--- a/packages/backend/src/server/api/SigninService.ts
+++ b/packages/backend/src/server/api/SigninService.ts
@@ -29,13 +29,13 @@ export class SigninService {
public signin(request: FastifyRequest, reply: FastifyReply, user: MiLocalUser) {
setImmediate(async () => {
// Append signin history
- const record = await this.signinsRepository.insert({
+ const record = await this.signinsRepository.insertOne({
id: this.idService.gen(),
userId: user.id,
ip: request.ip,
headers: request.headers as any,
success: true,
- }).then(x => this.signinsRepository.findOneByOrFail(x.identifiers[0]));
+ });
// Publish signin event
this.globalEventService.publishMainStream(user.id, 'signin', await this.signinEntityService.pack(record));
diff --git a/packages/backend/src/server/api/SignupApiService.ts b/packages/backend/src/server/api/SignupApiService.ts
index 9c221314ac..f89c3954f8 100644
--- a/packages/backend/src/server/api/SignupApiService.ts
+++ b/packages/backend/src/server/api/SignupApiService.ts
@@ -196,14 +196,14 @@ export class SignupApiService {
//const salt = await bcrypt.genSalt(8);
const hash = await argon2.hash(password);
- const pendingUser = await this.userPendingsRepository.insert({
+ const pendingUser = await this.userPendingsRepository.insertOne({
id: this.idService.gen(),
code,
email: emailAddress!,
username: username,
password: hash,
reason: reason,
- }).then(x => this.userPendingsRepository.findOneByOrFail(x.identifiers[0]));
+ });
const link = `${this.config.url}/signup-complete/${code}`;
diff --git a/packages/backend/src/server/api/endpoints.ts b/packages/backend/src/server/api/endpoints.ts
index 89f60933ef..e2fcd1a9d0 100644
--- a/packages/backend/src/server/api/endpoints.ts
+++ b/packages/backend/src/server/api/endpoints.ts
@@ -6,8 +6,18 @@
import { permissions } from 'misskey-js';
import type { KeyOf, Schema } from '@/misc/json-schema.js';
-import * as ep___admin_meta from './endpoints/admin/meta.js';
+import * as ep___admin_abuseReport_notificationRecipient_list
+ from '@/server/api/endpoints/admin/abuse-report/notification-recipient/list.js';
+import * as ep___admin_abuseReport_notificationRecipient_show
+ from '@/server/api/endpoints/admin/abuse-report/notification-recipient/show.js';
+import * as ep___admin_abuseReport_notificationRecipient_create
+ from '@/server/api/endpoints/admin/abuse-report/notification-recipient/create.js';
+import * as ep___admin_abuseReport_notificationRecipient_update
+ from '@/server/api/endpoints/admin/abuse-report/notification-recipient/update.js';
+import * as ep___admin_abuseReport_notificationRecipient_delete
+ from '@/server/api/endpoints/admin/abuse-report/notification-recipient/delete.js';
import * as ep___admin_abuseUserReports from './endpoints/admin/abuse-user-reports.js';
+import * as ep___admin_meta from './endpoints/admin/meta.js';
import * as ep___admin_accounts_create from './endpoints/admin/accounts/create.js';
import * as ep___admin_accounts_delete from './endpoints/admin/accounts/delete.js';
import * as ep___admin_accounts_findByEmail from './endpoints/admin/accounts/find-by-email.js';
@@ -44,7 +54,8 @@ import * as ep___admin_emoji_setCategoryBulk from './endpoints/admin/emoji/set-c
import * as ep___admin_emoji_setLicenseBulk from './endpoints/admin/emoji/set-license-bulk.js';
import * as ep___admin_emoji_update from './endpoints/admin/emoji/update.js';
import * as ep___admin_federation_deleteAllFiles from './endpoints/admin/federation/delete-all-files.js';
-import * as ep___admin_federation_refreshRemoteInstanceMetadata from './endpoints/admin/federation/refresh-remote-instance-metadata.js';
+import * as ep___admin_federation_refreshRemoteInstanceMetadata
+ from './endpoints/admin/federation/refresh-remote-instance-metadata.js';
import * as ep___admin_federation_removeAllFollowing from './endpoints/admin/federation/remove-all-following.js';
import * as ep___admin_federation_updateInstance from './endpoints/admin/federation/update-instance.js';
import * as ep___admin_getIndexStats from './endpoints/admin/get-index-stats.js';
@@ -87,6 +98,11 @@ import * as ep___admin_roles_assign from './endpoints/admin/roles/assign.js';
import * as ep___admin_roles_unassign from './endpoints/admin/roles/unassign.js';
import * as ep___admin_roles_updateDefaultPolicies from './endpoints/admin/roles/update-default-policies.js';
import * as ep___admin_roles_users from './endpoints/admin/roles/users.js';
+import * as ep___admin_systemWebhook_create from './endpoints/admin/system-webhook/create.js';
+import * as ep___admin_systemWebhook_delete from './endpoints/admin/system-webhook/delete.js';
+import * as ep___admin_systemWebhook_list from './endpoints/admin/system-webhook/list.js';
+import * as ep___admin_systemWebhook_show from './endpoints/admin/system-webhook/show.js';
+import * as ep___admin_systemWebhook_update from './endpoints/admin/system-webhook/update.js';
import * as ep___announcements from './endpoints/announcements.js';
import * as ep___announcements_show from './endpoints/announcements/show.js';
import * as ep___antennas_create from './endpoints/antennas/create.js';
@@ -392,6 +408,11 @@ import * as ep___reversi_verify from './endpoints/reversi/verify.js';
const eps = [
['admin/meta', ep___admin_meta],
['admin/abuse-user-reports', ep___admin_abuseUserReports],
+ ['admin/abuse-report/notification-recipient/list', ep___admin_abuseReport_notificationRecipient_list],
+ ['admin/abuse-report/notification-recipient/show', ep___admin_abuseReport_notificationRecipient_show],
+ ['admin/abuse-report/notification-recipient/create', ep___admin_abuseReport_notificationRecipient_create],
+ ['admin/abuse-report/notification-recipient/update', ep___admin_abuseReport_notificationRecipient_update],
+ ['admin/abuse-report/notification-recipient/delete', ep___admin_abuseReport_notificationRecipient_delete],
['admin/accounts/create', ep___admin_accounts_create],
['admin/accounts/delete', ep___admin_accounts_delete],
['admin/accounts/find-by-email', ep___admin_accounts_findByEmail],
@@ -471,6 +492,11 @@ const eps = [
['admin/roles/unassign', ep___admin_roles_unassign],
['admin/roles/update-default-policies', ep___admin_roles_updateDefaultPolicies],
['admin/roles/users', ep___admin_roles_users],
+ ['admin/system-webhook/create', ep___admin_systemWebhook_create],
+ ['admin/system-webhook/delete', ep___admin_systemWebhook_delete],
+ ['admin/system-webhook/list', ep___admin_systemWebhook_list],
+ ['admin/system-webhook/show', ep___admin_systemWebhook_show],
+ ['admin/system-webhook/update', ep___admin_systemWebhook_update],
['announcements', ep___announcements],
['announcements/show', ep___announcements_show],
['antennas/create', ep___antennas_create],
@@ -899,8 +925,12 @@ export interface IEndpoint {
const endpoints: IEndpoint[] = (eps as [string, any]).map(([name, ep]) => {
return {
name: name,
- get meta() { return ep.meta ?? {}; },
- get params() { return ep.paramDef; },
+ get meta() {
+ return ep.meta ?? {};
+ },
+ get params() {
+ return ep.paramDef;
+ },
};
});
diff --git a/packages/backend/src/server/api/endpoints/admin/abuse-report/notification-recipient/create.ts b/packages/backend/src/server/api/endpoints/admin/abuse-report/notification-recipient/create.ts
new file mode 100644
index 0000000000..bdfbcba518
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/admin/abuse-report/notification-recipient/create.ts
@@ -0,0 +1,122 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { Inject, Injectable } from '@nestjs/common';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { ApiError } from '@/server/api/error.js';
+import {
+ AbuseReportNotificationRecipientEntityService,
+} from '@/core/entities/AbuseReportNotificationRecipientEntityService.js';
+import { AbuseReportNotificationService } from '@/core/AbuseReportNotificationService.js';
+import { DI } from '@/di-symbols.js';
+import type { UserProfilesRepository } from '@/models/_.js';
+
+export const meta = {
+ tags: ['admin', 'abuse-report', 'notification-recipient'],
+
+ requireCredential: true,
+ requireModerator: true,
+ secure: true,
+ kind: 'write:admin:abuse-report:notification-recipient',
+
+ res: {
+ type: 'object',
+ ref: 'AbuseReportNotificationRecipient',
+ },
+
+ errors: {
+ correlationCheckEmail: {
+ message: 'If "method" is email, "userId" must be set.',
+ code: 'CORRELATION_CHECK_EMAIL',
+ id: '348bb8ae-575a-6fe9-4327-5811999def8f',
+ httpStatusCode: 400,
+ },
+ correlationCheckWebhook: {
+ message: 'If "method" is webhook, "systemWebhookId" must be set.',
+ code: 'CORRELATION_CHECK_WEBHOOK',
+ id: 'b0c15051-de2d-29ef-260c-9585cddd701a',
+ httpStatusCode: 400,
+ },
+ emailAddressNotSet: {
+ message: 'Email address is not set.',
+ code: 'EMAIL_ADDRESS_NOT_SET',
+ id: '7cc1d85e-2f58-fc31-b644-3de8d0d3421f',
+ httpStatusCode: 400,
+ },
+ },
+} as const;
+
+export const paramDef = {
+ type: 'object',
+ properties: {
+ isActive: {
+ type: 'boolean',
+ },
+ name: {
+ type: 'string',
+ minLength: 1,
+ maxLength: 255,
+ },
+ method: {
+ type: 'string',
+ enum: ['email', 'webhook'],
+ },
+ userId: {
+ type: 'string',
+ format: 'misskey:id',
+ },
+ systemWebhookId: {
+ type: 'string',
+ format: 'misskey:id',
+ },
+ },
+ required: [
+ 'isActive',
+ 'name',
+ 'method',
+ ],
+} as const;
+
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
+ constructor(
+ @Inject(DI.userProfilesRepository)
+ private userProfilesRepository: UserProfilesRepository,
+ private abuseReportNotificationService: AbuseReportNotificationService,
+ private abuseReportNotificationRecipientEntityService: AbuseReportNotificationRecipientEntityService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ if (ps.method === 'email') {
+ const userProfile = await this.userProfilesRepository.findOneBy({ userId: ps.userId });
+ if (!ps.userId || !userProfile) {
+ throw new ApiError(meta.errors.correlationCheckEmail);
+ }
+
+ if (!userProfile.email || !userProfile.emailVerified) {
+ throw new ApiError(meta.errors.emailAddressNotSet);
+ }
+ }
+
+ if (ps.method === 'webhook' && !ps.systemWebhookId) {
+ throw new ApiError(meta.errors.correlationCheckWebhook);
+ }
+
+ const userId = ps.method === 'email' ? ps.userId : null;
+ const systemWebhookId = ps.method === 'webhook' ? ps.systemWebhookId : null;
+ const result = await this.abuseReportNotificationService.createRecipient(
+ {
+ isActive: ps.isActive,
+ name: ps.name,
+ method: ps.method,
+ userId: userId ?? null,
+ systemWebhookId: systemWebhookId ?? null,
+ },
+ me,
+ );
+
+ return this.abuseReportNotificationRecipientEntityService.pack(result);
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/admin/abuse-report/notification-recipient/delete.ts b/packages/backend/src/server/api/endpoints/admin/abuse-report/notification-recipient/delete.ts
new file mode 100644
index 0000000000..b6dc44e09c
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/admin/abuse-report/notification-recipient/delete.ts
@@ -0,0 +1,44 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { Injectable } from '@nestjs/common';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { AbuseReportNotificationService } from '@/core/AbuseReportNotificationService.js';
+
+export const meta = {
+ tags: ['admin', 'abuse-report', 'notification-recipient'],
+
+ requireCredential: true,
+ requireModerator: true,
+ secure: true,
+ kind: 'write:admin:abuse-report:notification-recipient',
+} as const;
+
+export const paramDef = {
+ type: 'object',
+ properties: {
+ id: {
+ type: 'string',
+ format: 'misskey:id',
+ },
+ },
+ required: [
+ 'id',
+ ],
+} as const;
+
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
+ constructor(
+ private abuseReportNotificationService: AbuseReportNotificationService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ await this.abuseReportNotificationService.deleteRecipient(
+ ps.id,
+ me,
+ );
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/admin/abuse-report/notification-recipient/list.ts b/packages/backend/src/server/api/endpoints/admin/abuse-report/notification-recipient/list.ts
new file mode 100644
index 0000000000..dad9161a8a
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/admin/abuse-report/notification-recipient/list.ts
@@ -0,0 +1,55 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { Injectable } from '@nestjs/common';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import {
+ AbuseReportNotificationRecipientEntityService,
+} from '@/core/entities/AbuseReportNotificationRecipientEntityService.js';
+import { AbuseReportNotificationService } from '@/core/AbuseReportNotificationService.js';
+
+export const meta = {
+ tags: ['admin', 'abuse-report', 'notification-recipient'],
+
+ requireCredential: true,
+ requireModerator: true,
+ secure: true,
+ kind: 'read:admin:abuse-report:notification-recipient',
+
+ res: {
+ type: 'array',
+ items: {
+ type: 'object',
+ ref: 'AbuseReportNotificationRecipient',
+ },
+ },
+} as const;
+
+export const paramDef = {
+ type: 'object',
+ properties: {
+ method: {
+ type: 'array',
+ items: {
+ type: 'string',
+ enum: ['email', 'webhook'],
+ },
+ },
+ },
+ required: [],
+} as const;
+
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
+ constructor(
+ private abuseReportNotificationService: AbuseReportNotificationService,
+ private abuseReportNotificationRecipientEntityService: AbuseReportNotificationRecipientEntityService,
+ ) {
+ super(meta, paramDef, async (ps) => {
+ const recipients = await this.abuseReportNotificationService.fetchRecipients({ method: ps.method });
+ return this.abuseReportNotificationRecipientEntityService.packMany(recipients);
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/admin/abuse-report/notification-recipient/show.ts b/packages/backend/src/server/api/endpoints/admin/abuse-report/notification-recipient/show.ts
new file mode 100644
index 0000000000..557798f946
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/admin/abuse-report/notification-recipient/show.ts
@@ -0,0 +1,64 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { Injectable } from '@nestjs/common';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import {
+ AbuseReportNotificationRecipientEntityService,
+} from '@/core/entities/AbuseReportNotificationRecipientEntityService.js';
+import { AbuseReportNotificationService } from '@/core/AbuseReportNotificationService.js';
+import { ApiError } from '@/server/api/error.js';
+
+export const meta = {
+ tags: ['admin', 'abuse-report', 'notification-recipient'],
+
+ requireCredential: true,
+ requireModerator: true,
+ secure: true,
+ kind: 'read:admin:abuse-report:notification-recipient',
+
+ res: {
+ type: 'object',
+ ref: 'AbuseReportNotificationRecipient',
+ },
+
+ errors: {
+ noSuchRecipient: {
+ message: 'No such recipient.',
+ code: 'NO_SUCH_RECIPIENT',
+ id: '013de6a8-f757-04cb-4d73-cc2a7e3368e4',
+ kind: 'server',
+ httpStatusCode: 404,
+ },
+ },
+} as const;
+
+export const paramDef = {
+ type: 'object',
+ properties: {
+ id: {
+ type: 'string',
+ format: 'misskey:id',
+ },
+ },
+ required: ['id'],
+} as const;
+
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
+ constructor(
+ private abuseReportNotificationService: AbuseReportNotificationService,
+ private abuseReportNotificationRecipientEntityService: AbuseReportNotificationRecipientEntityService,
+ ) {
+ super(meta, paramDef, async (ps) => {
+ const recipients = await this.abuseReportNotificationService.fetchRecipients({ ids: [ps.id] });
+ if (recipients.length === 0) {
+ throw new ApiError(meta.errors.noSuchRecipient);
+ }
+
+ return this.abuseReportNotificationRecipientEntityService.pack(recipients[0]);
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/admin/abuse-report/notification-recipient/update.ts b/packages/backend/src/server/api/endpoints/admin/abuse-report/notification-recipient/update.ts
new file mode 100644
index 0000000000..bd4b485217
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/admin/abuse-report/notification-recipient/update.ts
@@ -0,0 +1,128 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { Inject, Injectable } from '@nestjs/common';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { ApiError } from '@/server/api/error.js';
+import {
+ AbuseReportNotificationRecipientEntityService,
+} from '@/core/entities/AbuseReportNotificationRecipientEntityService.js';
+import { AbuseReportNotificationService } from '@/core/AbuseReportNotificationService.js';
+import { DI } from '@/di-symbols.js';
+import type { UserProfilesRepository } from '@/models/_.js';
+
+export const meta = {
+ tags: ['admin', 'abuse-report', 'notification-recipient'],
+
+ requireCredential: true,
+ requireModerator: true,
+ secure: true,
+ kind: 'write:admin:abuse-report:notification-recipient',
+
+ res: {
+ type: 'object',
+ ref: 'AbuseReportNotificationRecipient',
+ },
+
+ errors: {
+ correlationCheckEmail: {
+ message: 'If "method" is email, "userId" must be set.',
+ code: 'CORRELATION_CHECK_EMAIL',
+ id: '348bb8ae-575a-6fe9-4327-5811999def8f',
+ httpStatusCode: 400,
+ },
+ correlationCheckWebhook: {
+ message: 'If "method" is webhook, "systemWebhookId" must be set.',
+ code: 'CORRELATION_CHECK_WEBHOOK',
+ id: 'b0c15051-de2d-29ef-260c-9585cddd701a',
+ httpStatusCode: 400,
+ },
+ emailAddressNotSet: {
+ message: 'Email address is not set.',
+ code: 'EMAIL_ADDRESS_NOT_SET',
+ id: '7cc1d85e-2f58-fc31-b644-3de8d0d3421f',
+ httpStatusCode: 400,
+ },
+ },
+} as const;
+
+export const paramDef = {
+ type: 'object',
+ properties: {
+ id: {
+ type: 'string',
+ format: 'misskey:id',
+ },
+ isActive: {
+ type: 'boolean',
+ },
+ name: {
+ type: 'string',
+ minLength: 1,
+ maxLength: 255,
+ },
+ method: {
+ type: 'string',
+ enum: ['email', 'webhook'],
+ },
+ userId: {
+ type: 'string',
+ format: 'misskey:id',
+ },
+ systemWebhookId: {
+ type: 'string',
+ format: 'misskey:id',
+ },
+ },
+ required: [
+ 'id',
+ 'isActive',
+ 'name',
+ 'method',
+ ],
+} as const;
+
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
+ constructor(
+ @Inject(DI.userProfilesRepository)
+ private userProfilesRepository: UserProfilesRepository,
+ private abuseReportNotificationService: AbuseReportNotificationService,
+ private abuseReportNotificationRecipientEntityService: AbuseReportNotificationRecipientEntityService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ if (ps.method === 'email') {
+ const userProfile = await this.userProfilesRepository.findOneBy({ userId: ps.userId });
+ if (!ps.userId || !userProfile) {
+ throw new ApiError(meta.errors.correlationCheckEmail);
+ }
+
+ if (!userProfile.email || !userProfile.emailVerified) {
+ throw new ApiError(meta.errors.emailAddressNotSet);
+ }
+ }
+
+ if (ps.method === 'webhook' && !ps.systemWebhookId) {
+ throw new ApiError(meta.errors.correlationCheckWebhook);
+ }
+
+ const userId = ps.method === 'email' ? ps.userId : null;
+ const systemWebhookId = ps.method === 'webhook' ? ps.systemWebhookId : null;
+ const result = await this.abuseReportNotificationService.updateRecipient(
+ {
+ id: ps.id,
+ isActive: ps.isActive,
+ name: ps.name,
+ method: ps.method,
+ userId: userId ?? null,
+ systemWebhookId: systemWebhookId ?? null,
+ },
+ me,
+ );
+
+ return this.abuseReportNotificationRecipientEntityService.pack(result);
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/admin/ad/create.ts b/packages/backend/src/server/api/endpoints/admin/ad/create.ts
index 1e7a9fb3ec..955154f4fb 100644
--- a/packages/backend/src/server/api/endpoints/admin/ad/create.ts
+++ b/packages/backend/src/server/api/endpoints/admin/ad/create.ts
@@ -50,7 +50,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private moderationLogService: ModerationLogService,
) {
super(meta, paramDef, async (ps, me) => {
- const ad = await this.adsRepository.insert({
+ const ad = await this.adsRepository.insertOne({
id: this.idService.gen(),
expiresAt: new Date(ps.expiresAt),
startsAt: new Date(ps.startsAt),
@@ -61,7 +61,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
ratio: ps.ratio,
place: ps.place,
memo: ps.memo,
- }).then(r => this.adsRepository.findOneByOrFail({ id: r.identifiers[0].id }));
+ });
this.moderationLogService.log(me, 'createAd', {
adId: ad.id,
diff --git a/packages/backend/src/server/api/endpoints/admin/ad/update.ts b/packages/backend/src/server/api/endpoints/admin/ad/update.ts
index 62358457ff..4e3d731aca 100644
--- a/packages/backend/src/server/api/endpoints/admin/ad/update.ts
+++ b/packages/backend/src/server/api/endpoints/admin/ad/update.ts
@@ -40,7 +40,7 @@ export const paramDef = {
startsAt: { type: 'integer' },
dayOfWeek: { type: 'integer' },
},
- required: ['id', 'memo', 'url', 'imageUrl', 'place', 'priority', 'ratio', 'expiresAt', 'startsAt', 'dayOfWeek'],
+ required: ['id'],
} as const;
@Injectable()
@@ -63,8 +63,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
ratio: ps.ratio,
memo: ps.memo,
imageUrl: ps.imageUrl,
- expiresAt: new Date(ps.expiresAt),
- startsAt: new Date(ps.startsAt),
+ expiresAt: ps.expiresAt ? new Date(ps.expiresAt) : undefined,
+ startsAt: ps.startsAt ? new Date(ps.startsAt) : undefined,
dayOfWeek: ps.dayOfWeek,
});
diff --git a/packages/backend/src/server/api/endpoints/admin/announcements/list.ts b/packages/backend/src/server/api/endpoints/admin/announcements/list.ts
index 87eaad31a3..7596bf44e3 100644
--- a/packages/backend/src/server/api/endpoints/admin/announcements/list.ts
+++ b/packages/backend/src/server/api/endpoints/admin/announcements/list.ts
@@ -69,6 +69,7 @@ export const paramDef = {
sinceId: { type: 'string', format: 'misskey:id' },
untilId: { type: 'string', format: 'misskey:id' },
userId: { type: 'string', format: 'misskey:id', nullable: true },
+ status: { type: 'string', enum: ['all', 'active', 'archived'], default: 'active' },
},
required: [],
} as const;
@@ -87,7 +88,13 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
) {
super(meta, paramDef, async (ps, me) => {
const query = this.queryService.makePaginationQuery(this.announcementsRepository.createQueryBuilder('announcement'), ps.sinceId, ps.untilId);
- query.andWhere('announcement.isActive = true');
+
+ if (ps.status === 'archived') {
+ query.andWhere('announcement.isActive = false');
+ } else if (ps.status === 'active') {
+ query.andWhere('announcement.isActive = true');
+ }
+
if (ps.userId) {
query.andWhere('announcement.userId = :userId', { userId: ps.userId });
} else {
diff --git a/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts b/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts
index 459d8880fa..a7136d8c8c 100644
--- a/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts
+++ b/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts
@@ -61,7 +61,7 @@ export const meta = {
name: {
type: 'string',
optional: false, nullable: false,
- example: 'lenna.jpg',
+ example: '192.jpg',
},
type: {
type: 'string',
diff --git a/packages/backend/src/server/api/endpoints/admin/invite/create.ts b/packages/backend/src/server/api/endpoints/admin/invite/create.ts
index 0f551e1ba2..5ecae3161a 100644
--- a/packages/backend/src/server/api/endpoints/admin/invite/create.ts
+++ b/packages/backend/src/server/api/endpoints/admin/invite/create.ts
@@ -66,11 +66,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
const ticketsPromises = [];
for (let i = 0; i < ps.count; i++) {
- ticketsPromises.push(this.registrationTicketsRepository.insert({
+ ticketsPromises.push(this.registrationTicketsRepository.insertOne({
id: this.idService.gen(),
expiresAt: ps.expiresAt ? new Date(ps.expiresAt) : null,
code: generateInviteCode(),
- }).then(x => this.registrationTicketsRepository.findOneByOrFail(x.identifiers[0])));
+ }));
}
const tickets = await Promise.all(ticketsPromises);
diff --git a/packages/backend/src/server/api/endpoints/admin/meta.ts b/packages/backend/src/server/api/endpoints/admin/meta.ts
index ca4d63b834..063bb6751b 100644
--- a/packages/backend/src/server/api/endpoints/admin/meta.ts
+++ b/packages/backend/src/server/api/endpoints/admin/meta.ts
@@ -132,6 +132,16 @@ export const meta = {
nullable: false,
},
},
+ mediaSilencedHosts: {
+ type: 'array',
+ optional: false,
+ nullable: false,
+ items: {
+ type: 'string',
+ optional: false,
+ nullable: false,
+ },
+ },
pinnedUsers: {
type: 'array',
optional: false, nullable: false,
@@ -586,6 +596,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
hiddenTags: instance.hiddenTags,
blockedHosts: instance.blockedHosts,
silencedHosts: instance.silencedHosts,
+ mediaSilencedHosts: instance.mediaSilencedHosts,
sensitiveWords: instance.sensitiveWords,
prohibitedWords: instance.prohibitedWords,
preservedUsernames: instance.preservedUsernames,
diff --git a/packages/backend/src/server/api/endpoints/admin/queue/stats.ts b/packages/backend/src/server/api/endpoints/admin/queue/stats.ts
index 9694b3fa40..d7f9e4eaa3 100644
--- a/packages/backend/src/server/api/endpoints/admin/queue/stats.ts
+++ b/packages/backend/src/server/api/endpoints/admin/queue/stats.ts
@@ -5,7 +5,7 @@
import { Inject, Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
-import type { DbQueue, DeliverQueue, EndedPollNotificationQueue, InboxQueue, ObjectStorageQueue, SystemQueue, WebhookDeliverQueue } from '@/core/QueueModule.js';
+import type { DbQueue, DeliverQueue, EndedPollNotificationQueue, InboxQueue, ObjectStorageQueue, SystemQueue, UserWebhookDeliverQueue, SystemWebhookDeliverQueue } from '@/core/QueueModule.js';
export const meta = {
tags: ['admin'],
@@ -53,7 +53,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
@Inject('queue:inbox') public inboxQueue: InboxQueue,
@Inject('queue:db') public dbQueue: DbQueue,
@Inject('queue:objectStorage') public objectStorageQueue: ObjectStorageQueue,
- @Inject('queue:webhookDeliver') public webhookDeliverQueue: WebhookDeliverQueue,
+ @Inject('queue:userWebhookDeliver') public userWebhookDeliverQueue: UserWebhookDeliverQueue,
+ @Inject('queue:systemWebhookDeliver') public systemWebhookDeliverQueue: SystemWebhookDeliverQueue,
) {
super(meta, paramDef, async (ps, me) => {
const deliverJobCounts = await this.deliverQueue.getJobCounts();
diff --git a/packages/backend/src/server/api/endpoints/admin/resolve-abuse-user-report.ts b/packages/backend/src/server/api/endpoints/admin/resolve-abuse-user-report.ts
index 8b0456068b..9b79100fcf 100644
--- a/packages/backend/src/server/api/endpoints/admin/resolve-abuse-user-report.ts
+++ b/packages/backend/src/server/api/endpoints/admin/resolve-abuse-user-report.ts
@@ -5,12 +5,10 @@
import { Inject, Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
-import type { UsersRepository, AbuseUserReportsRepository } from '@/models/_.js';
-import { InstanceActorService } from '@/core/InstanceActorService.js';
-import { QueueService } from '@/core/QueueService.js';
-import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
+import type { AbuseUserReportsRepository } from '@/models/_.js';
import { DI } from '@/di-symbols.js';
-import { ModerationLogService } from '@/core/ModerationLogService.js';
+import { ApiError } from '@/server/api/error.js';
+import { AbuseReportService } from '@/core/AbuseReportService.js';
export const meta = {
tags: ['admin'],
@@ -18,6 +16,16 @@ export const meta = {
requireCredential: true,
requireModerator: true,
kind: 'write:admin:resolve-abuse-user-report',
+
+ errors: {
+ noSuchAbuseReport: {
+ message: 'No such abuse report.',
+ code: 'NO_SUCH_ABUSE_REPORT',
+ id: 'ac3794dd-2ce4-d878-e546-73c60c06b398',
+ kind: 'server',
+ httpStatusCode: 404,
+ },
+ },
} as const;
export const paramDef = {
@@ -29,47 +37,20 @@ export const paramDef = {
required: ['reportId'],
} as const;
-// TODO: ロジックをサービスに切り出す
-
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor(
- @Inject(DI.usersRepository)
- private usersRepository: UsersRepository,
-
@Inject(DI.abuseUserReportsRepository)
private abuseUserReportsRepository: AbuseUserReportsRepository,
-
- private queueService: QueueService,
- private instanceActorService: InstanceActorService,
- private apRendererService: ApRendererService,
- private moderationLogService: ModerationLogService,
+ private abuseReportService: AbuseReportService,
) {
super(meta, paramDef, async (ps, me) => {
const report = await this.abuseUserReportsRepository.findOneBy({ id: ps.reportId });
-
- if (report == null) {
- throw new Error('report not found');
+ if (!report) {
+ throw new ApiError(meta.errors.noSuchAbuseReport);
}
- if (ps.forward && report.targetUserHost != null) {
- const actor = await this.instanceActorService.getInstanceActor();
- const targetUser = await this.usersRepository.findOneByOrFail({ id: report.targetUserId });
-
- this.queueService.deliver(actor, this.apRendererService.addContext(this.apRendererService.renderFlag(actor, targetUser.uri!, report.comment)), targetUser.inbox, false);
- }
-
- await this.abuseUserReportsRepository.update(report.id, {
- resolved: true,
- assigneeId: me.id,
- forwarded: ps.forward && report.targetUserHost != null,
- });
-
- this.moderationLogService.log(me, 'resolveAbuseReport', {
- reportId: report.id,
- report: report,
- forwarded: ps.forward && report.targetUserHost != null,
- });
+ await this.abuseReportService.resolve([{ reportId: report.id, forward: ps.forward }], me);
});
}
}
diff --git a/packages/backend/src/server/api/endpoints/admin/roles/update.ts b/packages/backend/src/server/api/endpoints/admin/roles/update.ts
index 5242e0be2f..465ad7aaaf 100644
--- a/packages/backend/src/server/api/endpoints/admin/roles/update.ts
+++ b/packages/backend/src/server/api/endpoints/admin/roles/update.ts
@@ -6,7 +6,6 @@
import { Inject, Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import type { RolesRepository } from '@/models/_.js';
-import { GlobalEventService } from '@/core/GlobalEventService.js';
import { DI } from '@/di-symbols.js';
import { ApiError } from '@/server/api/error.js';
import { RoleService } from '@/core/RoleService.js';
@@ -50,19 +49,6 @@ export const paramDef = {
},
required: [
'roleId',
- 'name',
- 'description',
- 'color',
- 'iconUrl',
- 'target',
- 'condFormula',
- 'isPublic',
- 'isModerator',
- 'isAdministrator',
- 'asBadge',
- 'canEditMembersByModerator',
- 'displayOrder',
- 'policies',
],
} as const;
diff --git a/packages/backend/src/server/api/endpoints/admin/system-webhook/create.ts b/packages/backend/src/server/api/endpoints/admin/system-webhook/create.ts
new file mode 100644
index 0000000000..28071e7a33
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/admin/system-webhook/create.ts
@@ -0,0 +1,85 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { Injectable } from '@nestjs/common';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { SystemWebhookEntityService } from '@/core/entities/SystemWebhookEntityService.js';
+import { systemWebhookEventTypes } from '@/models/SystemWebhook.js';
+import { SystemWebhookService } from '@/core/SystemWebhookService.js';
+
+export const meta = {
+ tags: ['admin', 'system-webhook'],
+
+ requireCredential: true,
+ requireModerator: true,
+ secure: true,
+ kind: 'write:admin:system-webhook',
+
+ res: {
+ type: 'object',
+ ref: 'SystemWebhook',
+ },
+} as const;
+
+export const paramDef = {
+ type: 'object',
+ properties: {
+ isActive: {
+ type: 'boolean',
+ },
+ name: {
+ type: 'string',
+ minLength: 1,
+ maxLength: 255,
+ },
+ on: {
+ type: 'array',
+ items: {
+ type: 'string',
+ enum: systemWebhookEventTypes,
+ },
+ },
+ url: {
+ type: 'string',
+ minLength: 1,
+ maxLength: 1024,
+ },
+ secret: {
+ type: 'string',
+ minLength: 1,
+ maxLength: 1024,
+ },
+ },
+ required: [
+ 'isActive',
+ 'name',
+ 'on',
+ 'url',
+ 'secret',
+ ],
+} as const;
+
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
+ constructor(
+ private systemWebhookService: SystemWebhookService,
+ private systemWebhookEntityService: SystemWebhookEntityService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const result = await this.systemWebhookService.createSystemWebhook(
+ {
+ isActive: ps.isActive,
+ name: ps.name,
+ on: ps.on,
+ url: ps.url,
+ secret: ps.secret,
+ },
+ me,
+ );
+
+ return this.systemWebhookEntityService.pack(result);
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/admin/system-webhook/delete.ts b/packages/backend/src/server/api/endpoints/admin/system-webhook/delete.ts
new file mode 100644
index 0000000000..9cdfc7e70f
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/admin/system-webhook/delete.ts
@@ -0,0 +1,44 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { Injectable } from '@nestjs/common';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { SystemWebhookService } from '@/core/SystemWebhookService.js';
+
+export const meta = {
+ tags: ['admin', 'system-webhook'],
+
+ requireCredential: true,
+ requireModerator: true,
+ secure: true,
+ kind: 'write:admin:system-webhook',
+} as const;
+
+export const paramDef = {
+ type: 'object',
+ properties: {
+ id: {
+ type: 'string',
+ format: 'misskey:id',
+ },
+ },
+ required: [
+ 'id',
+ ],
+} as const;
+
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
+ constructor(
+ private systemWebhookService: SystemWebhookService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ await this.systemWebhookService.deleteSystemWebhook(
+ ps.id,
+ me,
+ );
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/admin/system-webhook/list.ts b/packages/backend/src/server/api/endpoints/admin/system-webhook/list.ts
new file mode 100644
index 0000000000..7a440a774e
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/admin/system-webhook/list.ts
@@ -0,0 +1,60 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { Injectable } from '@nestjs/common';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { SystemWebhookEntityService } from '@/core/entities/SystemWebhookEntityService.js';
+import { systemWebhookEventTypes } from '@/models/SystemWebhook.js';
+import { SystemWebhookService } from '@/core/SystemWebhookService.js';
+
+export const meta = {
+ tags: ['admin', 'system-webhook'],
+
+ requireCredential: true,
+ requireModerator: true,
+ secure: true,
+ kind: 'write:admin:system-webhook',
+
+ res: {
+ type: 'array',
+ items: {
+ type: 'object',
+ ref: 'SystemWebhook',
+ },
+ },
+} as const;
+
+export const paramDef = {
+ type: 'object',
+ properties: {
+ isActive: {
+ type: 'boolean',
+ },
+ on: {
+ type: 'array',
+ items: {
+ type: 'string',
+ enum: systemWebhookEventTypes,
+ },
+ },
+ },
+ required: [],
+} as const;
+
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
+ constructor(
+ private systemWebhookService: SystemWebhookService,
+ private systemWebhookEntityService: SystemWebhookEntityService,
+ ) {
+ super(meta, paramDef, async (ps) => {
+ const webhooks = await this.systemWebhookService.fetchSystemWebhooks({
+ isActive: ps.isActive,
+ on: ps.on,
+ });
+ return this.systemWebhookEntityService.packMany(webhooks);
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/admin/system-webhook/show.ts b/packages/backend/src/server/api/endpoints/admin/system-webhook/show.ts
new file mode 100644
index 0000000000..75862c96a7
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/admin/system-webhook/show.ts
@@ -0,0 +1,62 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { Injectable } from '@nestjs/common';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { SystemWebhookEntityService } from '@/core/entities/SystemWebhookEntityService.js';
+import { ApiError } from '@/server/api/error.js';
+import { SystemWebhookService } from '@/core/SystemWebhookService.js';
+
+export const meta = {
+ tags: ['admin', 'system-webhook'],
+
+ requireCredential: true,
+ requireModerator: true,
+ secure: true,
+ kind: 'write:admin:system-webhook',
+
+ res: {
+ type: 'object',
+ ref: 'SystemWebhook',
+ },
+
+ errors: {
+ noSuchSystemWebhook: {
+ message: 'No such SystemWebhook.',
+ code: 'NO_SUCH_SYSTEM_WEBHOOK',
+ id: '38dd1ffe-04b4-6ff5-d8ba-4e6a6ae22c9d',
+ kind: 'server',
+ httpStatusCode: 404,
+ },
+ },
+} as const;
+
+export const paramDef = {
+ type: 'object',
+ properties: {
+ id: {
+ type: 'string',
+ format: 'misskey:id',
+ },
+ },
+ required: ['id'],
+} as const;
+
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
+ constructor(
+ private systemWebhookService: SystemWebhookService,
+ private systemWebhookEntityService: SystemWebhookEntityService,
+ ) {
+ super(meta, paramDef, async (ps) => {
+ const webhooks = await this.systemWebhookService.fetchSystemWebhooks({ ids: [ps.id] });
+ if (webhooks.length === 0) {
+ throw new ApiError(meta.errors.noSuchSystemWebhook);
+ }
+
+ return this.systemWebhookEntityService.pack(webhooks[0]);
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/admin/system-webhook/update.ts b/packages/backend/src/server/api/endpoints/admin/system-webhook/update.ts
new file mode 100644
index 0000000000..8d68bb8f87
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/admin/system-webhook/update.ts
@@ -0,0 +1,91 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { Injectable } from '@nestjs/common';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { SystemWebhookEntityService } from '@/core/entities/SystemWebhookEntityService.js';
+import { systemWebhookEventTypes } from '@/models/SystemWebhook.js';
+import { SystemWebhookService } from '@/core/SystemWebhookService.js';
+
+export const meta = {
+ tags: ['admin', 'system-webhook'],
+
+ requireCredential: true,
+ requireModerator: true,
+ secure: true,
+ kind: 'write:admin:system-webhook',
+
+ res: {
+ type: 'object',
+ ref: 'SystemWebhook',
+ },
+} as const;
+
+export const paramDef = {
+ type: 'object',
+ properties: {
+ id: {
+ type: 'string',
+ format: 'misskey:id',
+ },
+ isActive: {
+ type: 'boolean',
+ },
+ name: {
+ type: 'string',
+ minLength: 1,
+ maxLength: 255,
+ },
+ on: {
+ type: 'array',
+ items: {
+ type: 'string',
+ enum: systemWebhookEventTypes,
+ },
+ },
+ url: {
+ type: 'string',
+ minLength: 1,
+ maxLength: 1024,
+ },
+ secret: {
+ type: 'string',
+ minLength: 1,
+ maxLength: 1024,
+ },
+ },
+ required: [
+ 'id',
+ 'isActive',
+ 'name',
+ 'on',
+ 'url',
+ 'secret',
+ ],
+} as const;
+
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
+ constructor(
+ private systemWebhookService: SystemWebhookService,
+ private systemWebhookEntityService: SystemWebhookEntityService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const result = await this.systemWebhookService.updateSystemWebhook(
+ {
+ id: ps.id,
+ isActive: ps.isActive,
+ name: ps.name,
+ on: ps.on,
+ url: ps.url,
+ secret: ps.secret,
+ },
+ me,
+ );
+
+ return this.systemWebhookEntityService.pack(result);
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/admin/update-meta.ts b/packages/backend/src/server/api/endpoints/admin/update-meta.ts
index 015a1e1f7c..6bda1ae6ad 100644
--- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts
+++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts
@@ -158,6 +158,13 @@ export const paramDef = {
type: 'string',
},
},
+ mediaSilencedHosts: {
+ type: 'array',
+ nullable: true,
+ items: {
+ type: 'string',
+ },
+ },
summalyProxy: {
type: 'string', nullable: true,
description: '[Deprecated] Use "urlPreviewSummaryProxyUrl" instead.',
@@ -211,6 +218,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
return h !== '' && h !== lv && !set.blockedHosts?.includes(h);
});
}
+ if (Array.isArray(ps.mediaSilencedHosts)) {
+ let lastValue = '';
+ set.mediaSilencedHosts = ps.mediaSilencedHosts.sort().filter((h) => {
+ const lv = lastValue;
+ lastValue = h;
+ return h !== '' && h !== lv && !set.blockedHosts?.includes(h);
+ });
+ }
if (ps.themeColor !== undefined) {
set.themeColor = ps.themeColor;
}
diff --git a/packages/backend/src/server/api/endpoints/antennas/create.ts b/packages/backend/src/server/api/endpoints/antennas/create.ts
index 6b7bacb054..577b9e1b1f 100644
--- a/packages/backend/src/server/api/endpoints/antennas/create.ts
+++ b/packages/backend/src/server/api/endpoints/antennas/create.ts
@@ -93,7 +93,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
const currentAntennasCount = await this.antennasRepository.countBy({
userId: me.id,
});
- if (currentAntennasCount > (await this.roleService.getUserPolicies(me.id)).antennaLimit) {
+ if (currentAntennasCount >= (await this.roleService.getUserPolicies(me.id)).antennaLimit) {
throw new ApiError(meta.errors.tooManyAntennas);
}
@@ -112,7 +112,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
const now = new Date();
- const antenna = await this.antennasRepository.insert({
+ const antenna = await this.antennasRepository.insertOne({
id: this.idService.gen(now.getTime()),
lastUsedAt: now,
userId: me.id,
@@ -127,7 +127,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
excludeBots: ps.excludeBots,
withReplies: ps.withReplies,
withFile: ps.withFile,
- }).then(x => this.antennasRepository.findOneByOrFail(x.identifiers[0]));
+ });
this.globalEventService.publishInternalEvent('antennaCreated', antenna);
diff --git a/packages/backend/src/server/api/endpoints/app/create.ts b/packages/backend/src/server/api/endpoints/app/create.ts
index 492705d6f9..ba847fc4f0 100644
--- a/packages/backend/src/server/api/endpoints/app/create.ts
+++ b/packages/backend/src/server/api/endpoints/app/create.ts
@@ -54,7 +54,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
const permission = unique(ps.permission.map(v => v.replace(/^(.+)(\/|-)(read|write)$/, '$3:$1')));
// Create account
- const app = await this.appsRepository.insert({
+ const app = await this.appsRepository.insertOne({
id: this.idService.gen(),
userId: me ? me.id : null,
name: ps.name,
@@ -62,7 +62,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
permission,
callbackUrl: ps.callbackUrl,
secret: secret,
- }).then(x => this.appsRepository.findOneByOrFail(x.identifiers[0]));
+ });
return await this.appEntityService.pack(app, null, {
detail: true,
diff --git a/packages/backend/src/server/api/endpoints/auth/session/generate.ts b/packages/backend/src/server/api/endpoints/auth/session/generate.ts
index 26dd893138..f8ddfdb75c 100644
--- a/packages/backend/src/server/api/endpoints/auth/session/generate.ts
+++ b/packages/backend/src/server/api/endpoints/auth/session/generate.ts
@@ -78,11 +78,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
const token = randomUUID();
// Create session token document
- const doc = await this.authSessionsRepository.insert({
+ const doc = await this.authSessionsRepository.insertOne({
id: this.idService.gen(),
appId: app.id,
token: token,
- }).then(x => this.authSessionsRepository.findOneByOrFail(x.identifiers[0]));
+ });
return {
token: doc.token,
diff --git a/packages/backend/src/server/api/endpoints/channels/create.ts b/packages/backend/src/server/api/endpoints/channels/create.ts
index 2866db5424..e3a6d2d670 100644
--- a/packages/backend/src/server/api/endpoints/channels/create.ts
+++ b/packages/backend/src/server/api/endpoints/channels/create.ts
@@ -80,7 +80,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
}
}
- const channel = await this.channelsRepository.insert({
+ const channel = await this.channelsRepository.insertOne({
id: this.idService.gen(),
userId: me.id,
name: ps.name,
@@ -89,7 +89,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
isSensitive: ps.isSensitive ?? false,
...(ps.color !== undefined ? { color: ps.color } : {}),
allowRenoteToExternal: ps.allowRenoteToExternal ?? true,
- } as MiChannel).then(x => this.channelsRepository.findOneByOrFail(x.identifiers[0]));
+ } as MiChannel);
return await this.channelEntityService.pack(channel, me);
});
diff --git a/packages/backend/src/server/api/endpoints/clips/update.ts b/packages/backend/src/server/api/endpoints/clips/update.ts
index 3b44ba81b3..603a3ccf3d 100644
--- a/packages/backend/src/server/api/endpoints/clips/update.ts
+++ b/packages/backend/src/server/api/endpoints/clips/update.ts
@@ -3,7 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { Inject, Injectable } from '@nestjs/common';
+import { Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { ClipEntityService } from '@/core/entities/ClipEntityService.js';
import { ClipService } from '@/core/ClipService.js';
@@ -41,7 +41,7 @@ export const paramDef = {
isPublic: { type: 'boolean' },
description: { type: 'string', nullable: true, minLength: 1, maxLength: 2048 },
},
- required: ['clipId', 'name'],
+ required: ['clipId'],
} as const;
@Injectable()
diff --git a/packages/backend/src/server/api/endpoints/drive/folders/create.ts b/packages/backend/src/server/api/endpoints/drive/folders/create.ts
index c94070d9ff..08d9d9cdc3 100644
--- a/packages/backend/src/server/api/endpoints/drive/folders/create.ts
+++ b/packages/backend/src/server/api/endpoints/drive/folders/create.ts
@@ -75,12 +75,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
}
// Create folder
- const folder = await this.driveFoldersRepository.insert({
+ const folder = await this.driveFoldersRepository.insertOne({
id: this.idService.gen(),
name: ps.name,
parentId: parent !== null ? parent.id : null,
userId: me.id,
- }).then(x => this.driveFoldersRepository.findOneByOrFail(x.identifiers[0]));
+ });
const folderObj = await this.driveFolderEntityService.pack(folder);
diff --git a/packages/backend/src/server/api/endpoints/drive/folders/update.ts b/packages/backend/src/server/api/endpoints/drive/folders/update.ts
index 52b8b335b5..62b04e1df3 100644
--- a/packages/backend/src/server/api/endpoints/drive/folders/update.ts
+++ b/packages/backend/src/server/api/endpoints/drive/folders/update.ts
@@ -95,15 +95,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
// Check if the circular reference will occur
const checkCircle = async (folderId: string): Promise<boolean> => {
- // Fetch folder
- const folder2 = await this.driveFoldersRepository.findOneBy({
+ const folder2 = await this.driveFoldersRepository.findOneByOrFail({
id: folderId,
});
- if (folder2!.id === folder!.id) {
+ if (folder2.id === folder.id) {
return true;
- } else if (folder2!.parentId) {
- return await checkCircle(folder2!.parentId);
+ } else if (folder2.parentId) {
+ return await checkCircle(folder2.parentId);
} else {
return false;
}
diff --git a/packages/backend/src/server/api/endpoints/flash/create.ts b/packages/backend/src/server/api/endpoints/flash/create.ts
index 361496e17e..64f13a577e 100644
--- a/packages/backend/src/server/api/endpoints/flash/create.ts
+++ b/packages/backend/src/server/api/endpoints/flash/create.ts
@@ -59,7 +59,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private idService: IdService,
) {
super(meta, paramDef, async (ps, me) => {
- const flash = await this.flashsRepository.insert({
+ const flash = await this.flashsRepository.insertOne({
id: this.idService.gen(),
userId: me.id,
updatedAt: new Date(),
@@ -68,7 +68,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
script: ps.script,
permissions: ps.permissions,
visibility: ps.visibility,
- }).then(x => this.flashsRepository.findOneByOrFail(x.identifiers[0]));
+ });
return await this.flashEntityService.pack(flash);
});
diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/create.ts b/packages/backend/src/server/api/endpoints/gallery/posts/create.ts
index b07cdf1ed9..504a9c789e 100644
--- a/packages/backend/src/server/api/endpoints/gallery/posts/create.ts
+++ b/packages/backend/src/server/api/endpoints/gallery/posts/create.ts
@@ -12,7 +12,6 @@ import type { MiDriveFile } from '@/models/DriveFile.js';
import { IdService } from '@/core/IdService.js';
import { GalleryPostEntityService } from '@/core/entities/GalleryPostEntityService.js';
import { DI } from '@/di-symbols.js';
-import { isNotNull } from '@/misc/is-not-null.js';
export const meta = {
tags: ['gallery'],
@@ -70,13 +69,13 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
id: fileId,
userId: me.id,
}),
- ))).filter(isNotNull);
+ ))).filter(x => x != null);
if (files.length === 0) {
throw new Error();
}
- const post = await this.galleryPostsRepository.insert(new MiGalleryPost({
+ const post = await this.galleryPostsRepository.insertOne(new MiGalleryPost({
id: this.idService.gen(),
updatedAt: new Date(),
title: ps.title,
@@ -84,7 +83,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
userId: me.id,
isSensitive: ps.isSensitive,
fileIds: files.map(file => file.id),
- })).then(x => this.galleryPostsRepository.findOneByOrFail(x.identifiers[0]));
+ }));
return await this.galleryPostEntityService.pack(post, me);
});
diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/update.ts b/packages/backend/src/server/api/endpoints/gallery/posts/update.ts
index 8bd83ff5ba..5243ee9603 100644
--- a/packages/backend/src/server/api/endpoints/gallery/posts/update.ts
+++ b/packages/backend/src/server/api/endpoints/gallery/posts/update.ts
@@ -10,7 +10,6 @@ import type { DriveFilesRepository, GalleryPostsRepository } from '@/models/_.js
import type { MiDriveFile } from '@/models/DriveFile.js';
import { GalleryPostEntityService } from '@/core/entities/GalleryPostEntityService.js';
import { DI } from '@/di-symbols.js';
-import { isNotNull } from '@/misc/is-not-null.js';
export const meta = {
tags: ['gallery'],
@@ -48,7 +47,7 @@ export const paramDef = {
} },
isSensitive: { type: 'boolean', default: false },
},
- required: ['postId', 'title', 'fileIds'],
+ required: ['postId'],
} as const;
@Injectable()
@@ -63,15 +62,19 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private galleryPostEntityService: GalleryPostEntityService,
) {
super(meta, paramDef, async (ps, me) => {
- const files = (await Promise.all(ps.fileIds.map(fileId =>
- this.driveFilesRepository.findOneBy({
- id: fileId,
- userId: me.id,
- }),
- ))).filter(isNotNull);
+ let files: Array<MiDriveFile> | undefined;
- if (files.length === 0) {
- throw new Error();
+ if (ps.fileIds) {
+ files = (await Promise.all(ps.fileIds.map(fileId =>
+ this.driveFilesRepository.findOneBy({
+ id: fileId,
+ userId: me.id,
+ }),
+ ))).filter(x => x != null);
+
+ if (files.length === 0) {
+ throw new Error();
+ }
}
await this.galleryPostsRepository.update({
@@ -82,7 +85,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
title: ps.title,
description: ps.description,
isSensitive: ps.isSensitive,
- fileIds: files.map(file => file.id),
+ fileIds: files ? files.map(file => file.id) : undefined,
});
const post = await this.galleryPostsRepository.findOneByOrFail({ id: ps.postId });
diff --git a/packages/backend/src/server/api/endpoints/i/import-antennas.ts b/packages/backend/src/server/api/endpoints/i/import-antennas.ts
index b4661a93e2..bc46163e3d 100644
--- a/packages/backend/src/server/api/endpoints/i/import-antennas.ts
+++ b/packages/backend/src/server/api/endpoints/i/import-antennas.ts
@@ -78,7 +78,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
if (file.size === 0) throw new ApiError(meta.errors.emptyFile);
const antennas: (_Antenna & { userListAccts: string[] | null })[] = JSON.parse(await this.downloadService.downloadTextFile(file.url));
const currentAntennasCount = await this.antennasRepository.countBy({ userId: me.id });
- if (currentAntennasCount + antennas.length > (await this.roleService.getUserPolicies(me.id)).antennaLimit) {
+ if (currentAntennasCount + antennas.length >= (await this.roleService.getUserPolicies(me.id)).antennaLimit) {
throw new ApiError(meta.errors.tooManyAntennas);
}
this.queueService.createImportAntennasJob(me, antennas);
diff --git a/packages/backend/src/server/api/endpoints/i/update.ts b/packages/backend/src/server/api/endpoints/i/update.ts
index aa2f85845f..6cc22e7994 100644
--- a/packages/backend/src/server/api/endpoints/i/update.ts
+++ b/packages/backend/src/server/api/endpoints/i/update.ts
@@ -25,7 +25,7 @@ import { UserFollowingService } from '@/core/UserFollowingService.js';
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 { RolePolicies, RoleService } from '@/core/RoleService.js';
import { CacheService } from '@/core/CacheService.js';
import { RemoteUserResolveService } from '@/core/RemoteUserResolveService.js';
import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
@@ -272,8 +272,16 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
const profileUpdates = {} as Partial<MiUserProfile>;
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id });
+ let policies: RolePolicies | null = null;
- if (ps.name !== undefined) updates.name = ps.name;
+ if (ps.name !== undefined) {
+ if (ps.name === null) {
+ updates.name = null;
+ } else {
+ const trimmedName = ps.name.trim();
+ updates.name = trimmedName === '' ? null : trimmedName;
+ }
+ }
if (ps.description !== undefined) profileUpdates.description = ps.description;
if (ps.lang !== undefined) profileUpdates.lang = ps.lang;
if (ps.location !== undefined) profileUpdates.location = ps.location;
@@ -306,14 +314,16 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
}
if (ps.mutedWords !== undefined) {
- checkMuteWordCount(ps.mutedWords, (await this.roleService.getUserPolicies(user.id)).wordMuteLimit);
+ policies ??= await this.roleService.getUserPolicies(user.id);
+ checkMuteWordCount(ps.mutedWords, policies.wordMuteLimit);
validateMuteWordRegex(ps.mutedWords);
profileUpdates.mutedWords = ps.mutedWords;
profileUpdates.enableWordMute = ps.mutedWords.length > 0;
}
if (ps.hardMutedWords !== undefined) {
- checkMuteWordCount(ps.hardMutedWords, (await this.roleService.getUserPolicies(user.id)).wordMuteLimit);
+ policies ??= await this.roleService.getUserPolicies(user.id);
+ checkMuteWordCount(ps.hardMutedWords, policies.wordMuteLimit);
validateMuteWordRegex(ps.hardMutedWords);
profileUpdates.hardMutedWords = ps.hardMutedWords;
}
@@ -333,12 +343,16 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
if (typeof ps.injectFeaturedNote === 'boolean') profileUpdates.injectFeaturedNote = ps.injectFeaturedNote;
if (typeof ps.receiveAnnouncementEmail === 'boolean') profileUpdates.receiveAnnouncementEmail = ps.receiveAnnouncementEmail;
if (typeof ps.alwaysMarkNsfw === 'boolean') {
- if ((await roleService.getUserPolicies(user.id)).alwaysMarkNsfw) throw new ApiError(meta.errors.restrictedByRole);
+ policies ??= await this.roleService.getUserPolicies(user.id);
+ if (policies.alwaysMarkNsfw) throw new ApiError(meta.errors.restrictedByRole);
profileUpdates.alwaysMarkNsfw = ps.alwaysMarkNsfw;
}
if (ps.emailNotificationTypes !== undefined) profileUpdates.emailNotificationTypes = ps.emailNotificationTypes;
if (ps.avatarId) {
+ policies ??= await this.roleService.getUserPolicies(user.id);
+ if (!policies.canUpdateBioMedia) throw new ApiError(meta.errors.restrictedByRole);
+
const avatar = await this.driveFilesRepository.findOneBy({ id: ps.avatarId });
if (avatar == null || avatar.userId !== user.id) throw new ApiError(meta.errors.noSuchAvatar);
@@ -354,6 +368,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
}
if (ps.bannerId) {
+ policies ??= await this.roleService.getUserPolicies(user.id);
+ if (!policies.canUpdateBioMedia) throw new ApiError(meta.errors.restrictedByRole);
+
const banner = await this.driveFilesRepository.findOneBy({ id: ps.bannerId });
if (banner == null || banner.userId !== user.id) throw new ApiError(meta.errors.noSuchBanner);
@@ -384,14 +401,15 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
}
if (ps.avatarDecorations) {
+ policies ??= await this.roleService.getUserPolicies(user.id);
const decorations = await this.avatarDecorationService.getAll(true);
- const [myRoles, myPolicies] = await Promise.all([this.roleService.getUserRoles(user.id), this.roleService.getUserPolicies(user.id)]);
+ const myRoles = await this.roleService.getUserRoles(user.id);
const allRoles = await this.roleService.getRoles();
const decorationIds = decorations
.filter(d => d.roleIdsThatCanBeUsedThisDecoration.filter(roleId => allRoles.some(r => r.id === roleId)).length === 0 || myRoles.some(r => d.roleIdsThatCanBeUsedThisDecoration.includes(r.id)))
.map(d => d.id);
- if (ps.avatarDecorations.length > myPolicies.avatarDecorationLimit) throw new ApiError(meta.errors.restrictedByRole);
+ if (ps.avatarDecorations.length > policies.avatarDecorationLimit) throw new ApiError(meta.errors.restrictedByRole);
updates.avatarDecorations = ps.avatarDecorations.filter(d => decorationIds.includes(d.id)).map(d => ({
id: d.id,
diff --git a/packages/backend/src/server/api/endpoints/i/webhooks/create.ts b/packages/backend/src/server/api/endpoints/i/webhooks/create.ts
index 535a3ea308..9eb7f5b3a0 100644
--- a/packages/backend/src/server/api/endpoints/i/webhooks/create.ts
+++ b/packages/backend/src/server/api/endpoints/i/webhooks/create.ts
@@ -85,18 +85,18 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
const currentWebhooksCount = await this.webhooksRepository.countBy({
userId: me.id,
});
- if (currentWebhooksCount > (await this.roleService.getUserPolicies(me.id)).webhookLimit) {
+ if (currentWebhooksCount >= (await this.roleService.getUserPolicies(me.id)).webhookLimit) {
throw new ApiError(meta.errors.tooManyWebhooks);
}
- const webhook = await this.webhooksRepository.insert({
+ const webhook = await this.webhooksRepository.insertOne({
id: this.idService.gen(),
userId: me.id,
name: ps.name,
url: ps.url,
secret: ps.secret,
on: ps.on,
- }).then(x => this.webhooksRepository.findOneByOrFail(x.identifiers[0]));
+ });
this.globalEventService.publishInternalEvent('webhookCreated', webhook);
diff --git a/packages/backend/src/server/api/endpoints/i/webhooks/update.ts b/packages/backend/src/server/api/endpoints/i/webhooks/update.ts
index 6e380d76f8..07a25bd82a 100644
--- a/packages/backend/src/server/api/endpoints/i/webhooks/update.ts
+++ b/packages/backend/src/server/api/endpoints/i/webhooks/update.ts
@@ -34,13 +34,13 @@ export const paramDef = {
webhookId: { type: 'string', format: 'misskey:id' },
name: { type: 'string', minLength: 1, maxLength: 100 },
url: { type: 'string', minLength: 1, maxLength: 1024 },
- secret: { type: 'string', maxLength: 1024, default: '' },
+ secret: { type: 'string', nullable: true, maxLength: 1024 },
on: { type: 'array', items: {
type: 'string', enum: webhookEventTypes,
} },
active: { type: 'boolean' },
},
- required: ['webhookId', 'name', 'url', 'on', 'active'],
+ required: ['webhookId'],
} as const;
// TODO: ロジックをサービスに切り出す
@@ -66,7 +66,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
await this.webhooksRepository.update(webhook.id, {
name: ps.name,
url: ps.url,
- secret: ps.secret,
+ secret: ps.secret === null ? '' : ps.secret,
on: ps.on,
active: ps.active,
});
diff --git a/packages/backend/src/server/api/endpoints/invite/create.ts b/packages/backend/src/server/api/endpoints/invite/create.ts
index 0ff125ad9c..a70b587da7 100644
--- a/packages/backend/src/server/api/endpoints/invite/create.ts
+++ b/packages/backend/src/server/api/endpoints/invite/create.ts
@@ -66,13 +66,13 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
}
}
- const ticket = await this.registrationTicketsRepository.insert({
+ const ticket = await this.registrationTicketsRepository.insertOne({
id: this.idService.gen(),
createdBy: me,
createdById: me.id,
expiresAt: policies.inviteExpirationTime ? new Date(Date.now() + (policies.inviteExpirationTime * 1000 * 60)) : null,
code: generateInviteCode(),
- }).then(x => this.registrationTicketsRepository.findOneByOrFail(x.identifiers[0]));
+ });
return await this.inviteCodeEntityService.pack(ticket, 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 ce5ddadb9e..fdc9a77956 100644
--- a/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts
+++ b/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts
@@ -141,9 +141,16 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
timelineConfig = [
`homeTimeline:${me.id}`,
'localTimeline',
+ `localTimelineWithReplyTo:${me.id}`,
];
}
+ const [
+ followings,
+ ] = await Promise.all([
+ this.cacheService.userFollowingsCache.fetch(me.id),
+ ]);
+
const redisTimeline = await this.fanoutTimelineEndpointService.timeline({
untilId,
sinceId,
@@ -155,6 +162,13 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
alwaysIncludeMyNotes: true,
excludePureRenotes: !ps.withRenotes,
excludeBots: !ps.withBots,
+ noteFilter: note => {
+ if (note.reply && note.reply.visibility === 'followers') {
+ if (!Object.hasOwn(followings, note.reply.userId) && note.reply.userId !== me.id) return false;
+ }
+
+ return true;
+ },
dbFallback: async (untilId, sinceId, limit) => await this.getFromDb({
untilId,
sinceId,
diff --git a/packages/backend/src/server/api/endpoints/notes/polls/vote.ts b/packages/backend/src/server/api/endpoints/notes/polls/vote.ts
index a91c506afd..f33f49075b 100644
--- a/packages/backend/src/server/api/endpoints/notes/polls/vote.ts
+++ b/packages/backend/src/server/api/endpoints/notes/polls/vote.ts
@@ -144,12 +144,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
}
// Create vote
- const vote = await this.pollVotesRepository.insert({
+ const vote = await this.pollVotesRepository.insertOne({
id: this.idService.gen(createdAt.getTime()),
noteId: note.id,
userId: me.id,
choice: ps.choice,
- }).then(x => this.pollVotesRepository.findOneByOrFail(x.identifiers[0]));
+ });
// Increment votes count
const index = ps.choice + 1; // In SQL, array index is 1 based
diff --git a/packages/backend/src/server/api/endpoints/notes/reactions/create.ts b/packages/backend/src/server/api/endpoints/notes/reactions/create.ts
index b9899608bf..0f0dcca605 100644
--- a/packages/backend/src/server/api/endpoints/notes/reactions/create.ts
+++ b/packages/backend/src/server/api/endpoints/notes/reactions/create.ts
@@ -36,6 +36,12 @@ export const meta = {
code: 'YOU_HAVE_BEEN_BLOCKED',
id: '20ef5475-9f38-4e4c-bd33-de6d979498ec',
},
+
+ cannotReactToRenote: {
+ message: 'You cannot react to Renote.',
+ code: 'CANNOT_REACT_TO_RENOTE',
+ id: 'eaccdc08-ddef-43fe-908f-d108faad57f5',
+ },
},
} as const;
@@ -62,6 +68,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
await this.reactionService.create(me, note, ps.reaction).catch(err => {
if (err.id === '51c42bb4-931a-456b-bff7-e5a8a70dd298') throw new ApiError(meta.errors.alreadyReacted);
if (err.id === 'e70412a4-7197-4726-8e74-f3e0deb92aa7') throw new ApiError(meta.errors.youHaveBeenBlocked);
+ if (err.id === '12c35529-3c79-4327-b1cc-e2cf63a71925') throw new ApiError(meta.errors.cannotReactToRenote);
throw err;
});
return;
diff --git a/packages/backend/src/server/api/endpoints/notes/timeline.ts b/packages/backend/src/server/api/endpoints/notes/timeline.ts
index 1e5869663f..1a14703e6e 100644
--- a/packages/backend/src/server/api/endpoints/notes/timeline.ts
+++ b/packages/backend/src/server/api/endpoints/notes/timeline.ts
@@ -116,7 +116,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
excludePureRenotes: !ps.withRenotes,
noteFilter: note => {
if (note.reply && note.reply.visibility === 'followers') {
- if (!Object.hasOwn(followings, note.reply.userId)) return false;
+ if (!Object.hasOwn(followings, note.reply.userId) && note.reply.userId !== me.id) return false;
}
if (!ps.withBots && note.user?.isBot) return false;
diff --git a/packages/backend/src/server/api/endpoints/pages/create.ts b/packages/backend/src/server/api/endpoints/pages/create.ts
index 3a02d359f8..fa03b0b457 100644
--- a/packages/backend/src/server/api/endpoints/pages/create.ts
+++ b/packages/backend/src/server/api/endpoints/pages/create.ts
@@ -102,7 +102,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
}
});
- const page = await this.pagesRepository.insert(new MiPage({
+ const page = await this.pagesRepository.insertOne(new MiPage({
id: this.idService.gen(),
updatedAt: new Date(),
title: ps.title,
@@ -117,7 +117,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
alignCenter: ps.alignCenter,
hideTitleWhenPinned: ps.hideTitleWhenPinned,
font: ps.font,
- })).then(x => this.pagesRepository.findOneByOrFail(x.identifiers[0]));
+ }));
return await this.pageEntityService.pack(page);
});
diff --git a/packages/backend/src/server/api/endpoints/pages/update.ts b/packages/backend/src/server/api/endpoints/pages/update.ts
index b8e5e70a25..f11bbbcb1a 100644
--- a/packages/backend/src/server/api/endpoints/pages/update.ts
+++ b/packages/backend/src/server/api/endpoints/pages/update.ts
@@ -70,7 +70,7 @@ export const paramDef = {
alignCenter: { type: 'boolean' },
hideTitleWhenPinned: { type: 'boolean' },
},
- required: ['pageId', 'title', 'name', 'content', 'variables', 'script'],
+ required: ['pageId'],
} as const;
@Injectable()
@@ -91,9 +91,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
throw new ApiError(meta.errors.accessDenied);
}
- let eyeCatchingImage = null;
if (ps.eyeCatchingImageId != null) {
- eyeCatchingImage = await this.driveFilesRepository.findOneBy({
+ const eyeCatchingImage = await this.driveFilesRepository.findOneBy({
id: ps.eyeCatchingImageId,
userId: me.id,
});
@@ -116,23 +115,15 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
await this.pagesRepository.update(page.id, {
updatedAt: new Date(),
title: ps.title,
- // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
- name: ps.name === undefined ? page.name : ps.name,
+ name: ps.name,
summary: ps.summary === undefined ? page.summary : ps.summary,
content: ps.content,
variables: ps.variables,
script: ps.script,
- // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
- alignCenter: ps.alignCenter === undefined ? page.alignCenter : ps.alignCenter,
- // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
- hideTitleWhenPinned: ps.hideTitleWhenPinned === undefined ? page.hideTitleWhenPinned : ps.hideTitleWhenPinned,
- // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
- font: ps.font === undefined ? page.font : ps.font,
- eyeCatchingImageId: ps.eyeCatchingImageId === null
- ? null
- : ps.eyeCatchingImageId === undefined
- ? page.eyeCatchingImageId
- : eyeCatchingImage!.id,
+ alignCenter: ps.alignCenter,
+ hideTitleWhenPinned: ps.hideTitleWhenPinned,
+ font: ps.font,
+ eyeCatchingImageId: ps.eyeCatchingImageId,
});
});
}
diff --git a/packages/backend/src/server/api/endpoints/pinned-users.ts b/packages/backend/src/server/api/endpoints/pinned-users.ts
index 784766bcb5..15832ef7f8 100644
--- a/packages/backend/src/server/api/endpoints/pinned-users.ts
+++ b/packages/backend/src/server/api/endpoints/pinned-users.ts
@@ -12,7 +12,6 @@ import { Endpoint } from '@/server/api/endpoint-base.js';
import { MetaService } from '@/core/MetaService.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { DI } from '@/di-symbols.js';
-import { isNotNull } from '@/misc/is-not-null.js';
export const meta = {
tags: ['users'],
@@ -53,7 +52,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
host: acct.host ?? IsNull(),
})));
- return await this.userEntityService.packMany(users.filter(isNotNull), me, { schema: 'UserDetailed' });
+ return await this.userEntityService.packMany(users.filter(x => x != null), me, { schema: 'UserDetailed' });
});
}
}
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 39bf0cc428..84a1f010d4 100644
--- a/packages/backend/src/server/api/endpoints/renote-mute/create.ts
+++ b/packages/backend/src/server/api/endpoints/renote-mute/create.ts
@@ -6,12 +6,11 @@
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 { RenoteMutingsRepository } from '@/models/_.js';
-import type { MiRenoteMuting } from '@/models/RenoteMuting.js';
import { DI } from '@/di-symbols.js';
import { GetterService } from '@/server/api/GetterService.js';
import { ApiError } from '../../error.js';
+import { UserRenoteMutingService } from "@/core/UserRenoteMutingService.js";
+import type { RenoteMutingsRepository } from '@/models/_.js';
export const meta = {
tags: ['account'],
@@ -62,7 +61,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private renoteMutingsRepository: RenoteMutingsRepository,
private getterService: GetterService,
- private idService: IdService,
+ private userRenoteMutingService: UserRenoteMutingService,
) {
super(meta, paramDef, async (ps, me) => {
const muter = me;
@@ -79,21 +78,19 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
});
// Check if already muting
- const exist = await this.renoteMutingsRepository.findOneBy({
- muterId: muter.id,
- muteeId: mutee.id,
+ const exist = await this.renoteMutingsRepository.exists({
+ where: {
+ muterId: muter.id,
+ muteeId: mutee.id,
+ },
});
- if (exist != null) {
+ if (exist === true) {
throw new ApiError(meta.errors.alreadyMuting);
}
// Create mute
- await this.renoteMutingsRepository.insert({
- id: this.idService.gen(),
- muterId: muter.id,
- muteeId: mutee.id,
- } as MiRenoteMuting);
+ await this.userRenoteMutingService.mute(muter, 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 6e037cc07e..1a584b8404 100644
--- a/packages/backend/src/server/api/endpoints/renote-mute/delete.ts
+++ b/packages/backend/src/server/api/endpoints/renote-mute/delete.ts
@@ -5,10 +5,11 @@
import { Inject, Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
-import type { RenoteMutingsRepository } from '@/models/_.js';
import { DI } from '@/di-symbols.js';
import { GetterService } from '@/server/api/GetterService.js';
import { ApiError } from '../../error.js';
+import { UserRenoteMutingService } from "@/core/UserRenoteMutingService.js";
+import type { RenoteMutingsRepository } from '@/models/_.js';
export const meta = {
tags: ['account'],
@@ -53,6 +54,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private renoteMutingsRepository: RenoteMutingsRepository,
private getterService: GetterService,
+ private userRenoteMutingService: UserRenoteMutingService,
) {
super(meta, paramDef, async (ps, me) => {
const muter = me;
@@ -79,9 +81,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
}
// Delete mute
- await this.renoteMutingsRepository.delete({
- id: exist.id,
- });
+ await this.userRenoteMutingService.unmute([exist]);
});
}
}
diff --git a/packages/backend/src/server/api/endpoints/users/lists/create-from-public.ts b/packages/backend/src/server/api/endpoints/users/lists/create-from-public.ts
index e2db71c5c7..7e44d501ab 100644
--- a/packages/backend/src/server/api/endpoints/users/lists/create-from-public.ts
+++ b/packages/backend/src/server/api/endpoints/users/lists/create-from-public.ts
@@ -100,15 +100,15 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
const currentCount = await this.userListsRepository.countBy({
userId: me.id,
});
- if (currentCount > (await this.roleService.getUserPolicies(me.id)).userListLimit) {
+ if (currentCount >= (await this.roleService.getUserPolicies(me.id)).userListLimit) {
throw new ApiError(meta.errors.tooManyUserLists);
}
- const userList = await this.userListsRepository.insert({
+ const userList = await this.userListsRepository.insertOne({
id: this.idService.gen(),
userId: me.id,
name: ps.name,
- } as MiUserList).then(x => this.userListsRepository.findOneByOrFail(x.identifiers[0]));
+ } as MiUserList);
const users = (await this.userListMembershipsRepository.findBy({
userListId: ps.listId,
diff --git a/packages/backend/src/server/api/endpoints/users/lists/create.ts b/packages/backend/src/server/api/endpoints/users/lists/create.ts
index 952580e639..7daf05ba4e 100644
--- a/packages/backend/src/server/api/endpoints/users/lists/create.ts
+++ b/packages/backend/src/server/api/endpoints/users/lists/create.ts
@@ -61,15 +61,15 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
const currentCount = await this.userListsRepository.countBy({
userId: me.id,
});
- if (currentCount > (await this.roleService.getUserPolicies(me.id)).userListLimit) {
+ if (currentCount >= (await this.roleService.getUserPolicies(me.id)).userListLimit) {
throw new ApiError(meta.errors.tooManyUserLists);
}
- const userList = await this.userListsRepository.insert({
+ const userList = await this.userListsRepository.insertOne({
id: this.idService.gen(),
userId: me.id,
name: ps.name,
- } as MiUserList).then(x => this.userListsRepository.findOneByOrFail(x.identifiers[0]));
+ } as MiUserList);
return await this.userListEntityService.pack(userList);
});
diff --git a/packages/backend/src/server/api/endpoints/users/reactions.ts b/packages/backend/src/server/api/endpoints/users/reactions.ts
index aca883a052..7805ae3288 100644
--- a/packages/backend/src/server/api/endpoints/users/reactions.ts
+++ b/packages/backend/src/server/api/endpoints/users/reactions.ts
@@ -12,6 +12,7 @@ import { DI } from '@/di-symbols.js';
import { CacheService } from '@/core/CacheService.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { RoleService } from '@/core/RoleService.js';
+import { isUserRelated } from '@/misc/is-user-related.js';
import { ApiError } from '../../error.js';
export const meta = {
@@ -74,6 +75,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private roleService: RoleService,
) {
super(meta, paramDef, async (ps, me) => {
+ const userIdsWhoBlockingMe = me ? await this.cacheService.userBlockedCache.fetch(me.id) : new Set<string>();
const iAmModerator = me ? await this.roleService.isModerator(me) : false; // Moderators can see reactions of all users
if (!iAmModerator) {
const user = await this.cacheService.findUserById(ps.userId);
@@ -85,8 +87,15 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
if ((me == null || me.id !== ps.userId) && !profile.publicReactions) {
throw new ApiError(meta.errors.reactionsNotPublic);
}
+
+ // early return if me is blocked by requesting user
+ if (userIdsWhoBlockingMe.has(ps.userId)) {
+ return [];
+ }
}
+ const userIdsWhoMeMuting = me ? await this.cacheService.userMutingsCache.fetch(me.id) : new Set<string>();
+
const query = this.queryService.makePaginationQuery(this.noteReactionsRepository.createQueryBuilder('reaction'),
ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
.andWhere('reaction.userId = :userId', { userId: ps.userId })
@@ -94,9 +103,15 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
this.queryService.generateVisibilityQuery(query, me);
- const reactions = await query
+ const reactions = (await query
.limit(ps.limit)
- .getMany();
+ .getMany()).filter(reaction => {
+ if (reaction.note?.userId === ps.userId) return true; // we can see reactions to note of requesting user
+ if (me && isUserRelated(reaction.note, userIdsWhoBlockingMe)) return false;
+ if (me && isUserRelated(reaction.note, userIdsWhoMeMuting)) return false;
+
+ return true;
+ });
return await this.noteReactionEntityService.packMany(reactions, me, { withNote: true });
});
diff --git a/packages/backend/src/server/api/endpoints/users/report-abuse.ts b/packages/backend/src/server/api/endpoints/users/report-abuse.ts
index 0685858d77..5ff6de37d2 100644
--- a/packages/backend/src/server/api/endpoints/users/report-abuse.ts
+++ b/packages/backend/src/server/api/endpoints/users/report-abuse.ts
@@ -3,17 +3,11 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import sanitizeHtml from 'sanitize-html';
-import { Inject, Injectable } from '@nestjs/common';
-import type { AbuseUserReportsRepository, UserProfilesRepository } from '@/models/_.js';
-import { IdService } from '@/core/IdService.js';
+import { Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
-import { GlobalEventService } from '@/core/GlobalEventService.js';
-import { MetaService } from '@/core/MetaService.js';
-import { EmailService } from '@/core/EmailService.js';
-import { DI } from '@/di-symbols.js';
import { GetterService } from '@/server/api/GetterService.js';
import { RoleService } from '@/core/RoleService.js';
+import { AbuseReportService } from '@/core/AbuseReportService.js';
import { ApiError } from '../../error.js';
export const meta = {
@@ -57,71 +51,32 @@ export const paramDef = {
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor(
- @Inject(DI.abuseUserReportsRepository)
- private abuseUserReportsRepository: AbuseUserReportsRepository,
-
- @Inject(DI.userProfilesRepository)
- private userProfilesRepository: UserProfilesRepository,
-
- private idService: IdService,
- private metaService: MetaService,
- private emailService: EmailService,
private getterService: GetterService,
private roleService: RoleService,
- private globalEventService: GlobalEventService,
+ private abuseReportService: AbuseReportService,
) {
super(meta, paramDef, async (ps, me) => {
// Lookup user
- const user = await this.getterService.getUser(ps.userId).catch(err => {
+ const targetUser = await this.getterService.getUser(ps.userId).catch(err => {
if (err.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser);
throw err;
});
- if (user.id === me.id) {
+ if (targetUser.id === me.id) {
throw new ApiError(meta.errors.cannotReportYourself);
}
- if (await this.roleService.isAdministrator(user)) {
+ if (await this.roleService.isAdministrator(targetUser)) {
throw new ApiError(meta.errors.cannotReportAdmin);
}
- const report = await this.abuseUserReportsRepository.insert({
- id: this.idService.gen(),
- targetUserId: user.id,
- targetUserHost: user.host,
+ await this.abuseReportService.report([{
+ targetUserId: targetUser.id,
+ targetUserHost: targetUser.host,
reporterId: me.id,
reporterHost: null,
comment: ps.comment,
- }).then(x => this.abuseUserReportsRepository.findOneByOrFail(x.identifiers[0]));
-
- // Publish event to moderators
- setImmediate(async () => {
- const moderators = await this.roleService.getModerators();
-
- for (const moderator of moderators) {
- this.globalEventService.publishAdminStream(moderator.id, 'newAbuseUserReport', {
- id: report.id,
- targetUserId: report.targetUserId,
- reporterId: report.reporterId,
- comment: report.comment,
- });
-
- const profile = await this.userProfilesRepository.findOneBy({ userId: moderator.id });
-
- if (profile?.email) {
- this.emailService.sendEmail(profile.email, 'New abuse report',
- sanitizeHtml(ps.comment),
- sanitizeHtml(ps.comment));
- }
- }
-
- const meta = await this.metaService.fetch();
- if (meta.maintainerEmail) {
- this.emailService.sendEmail(meta.maintainerEmail, 'New abuse report',
- sanitizeHtml(ps.comment),
- sanitizeHtml(ps.comment));
- }
- });
+ }]);
});
}
}
diff --git a/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts b/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts
index 7b3bdab327..8ff952dcb5 100644
--- a/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts
+++ b/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts
@@ -3,15 +3,9 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { Brackets } from 'typeorm';
-import { Inject, Injectable } from '@nestjs/common';
-import type { UsersRepository, FollowingsRepository } from '@/models/_.js';
-import type { Config } from '@/config.js';
-import type { MiUser } from '@/models/User.js';
+import { Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
-import { UserEntityService } from '@/core/entities/UserEntityService.js';
-import { DI } from '@/di-symbols.js';
-import { sqlLikeEscape } from '@/misc/sql-like-escape.js';
+import { UserSearchService } from '@/core/UserSearchService.js';
export const meta = {
tags: ['users'],
@@ -49,89 +43,16 @@ export const paramDef = {
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor(
- @Inject(DI.config)
- private config: Config,
-
- @Inject(DI.usersRepository)
- private usersRepository: UsersRepository,
-
- @Inject(DI.followingsRepository)
- private followingsRepository: FollowingsRepository,
-
- private userEntityService: UserEntityService,
+ private userSearchService: UserSearchService,
) {
- super(meta, paramDef, async (ps, me) => {
- const setUsernameAndHostQuery = (query = this.usersRepository.createQueryBuilder('user')) => {
- if (ps.username) {
- query.andWhere('user.usernameLower LIKE :username', { username: sqlLikeEscape(ps.username.toLowerCase()) + '%' });
- }
-
- if (ps.host) {
- if (ps.host === this.config.hostname || ps.host === '.') {
- query.andWhere('user.host IS NULL');
- } else {
- query.andWhere('user.host LIKE :host', {
- host: sqlLikeEscape(ps.host.toLowerCase()) + '%',
- });
- }
- }
-
- return query;
- };
-
- const activeThreshold = new Date(Date.now() - (1000 * 60 * 60 * 24 * 30)); // 30日
-
- let users: MiUser[] = [];
-
- if (me) {
- const followingQuery = this.followingsRepository.createQueryBuilder('following')
- .select('following.followeeId')
- .where('following.followerId = :followerId', { followerId: me.id });
-
- const query = setUsernameAndHostQuery()
- .andWhere(`user.id IN (${ followingQuery.getQuery() })`)
- .andWhere('user.id != :meId', { meId: me.id })
- .andWhere('user.isSuspended = FALSE')
- .andWhere(new Brackets(qb => {
- qb
- .where('user.updatedAt IS NULL')
- .orWhere('user.updatedAt > :activeThreshold', { activeThreshold: activeThreshold });
- }));
-
- query.setParameters(followingQuery.getParameters());
-
- users = await query
- .orderBy('user.usernameLower', 'ASC')
- .limit(ps.limit)
- .getMany();
-
- if (users.length < ps.limit) {
- const otherQuery = setUsernameAndHostQuery()
- .andWhere(`user.id NOT IN (${ followingQuery.getQuery() })`)
- .andWhere('user.isSuspended = FALSE')
- .andWhere('user.updatedAt IS NOT NULL');
-
- otherQuery.setParameters(followingQuery.getParameters());
-
- const otherUsers = await otherQuery
- .orderBy('user.updatedAt', 'DESC')
- .limit(ps.limit - users.length)
- .getMany();
-
- users = users.concat(otherUsers);
- }
- } else {
- const query = setUsernameAndHostQuery()
- .andWhere('user.isSuspended = FALSE')
- .andWhere('user.updatedAt IS NOT NULL');
-
- users = await query
- .orderBy('user.updatedAt', 'DESC')
- .limit(ps.limit - users.length)
- .getMany();
- }
-
- return await this.userEntityService.packMany(users, me, { schema: ps.detail ? 'UserDetailed' : 'UserLite' });
+ super(meta, paramDef, (ps, me) => {
+ return this.userSearchService.search({
+ username: ps.username,
+ host: ps.host,
+ }, {
+ limit: ps.limit,
+ detail: ps.detail,
+ }, me);
});
}
}
diff --git a/packages/backend/src/server/api/endpoints/users/search.ts b/packages/backend/src/server/api/endpoints/users/search.ts
index df9d9f6312..0b0136066d 100644
--- a/packages/backend/src/server/api/endpoints/users/search.ts
+++ b/packages/backend/src/server/api/endpoints/users/search.ts
@@ -57,88 +57,66 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
const activeThreshold = new Date(Date.now() - (1000 * 60 * 60 * 24 * 30)); // 30日
ps.query = ps.query.trim();
- const isUsername = ps.query.startsWith('@');
+ const isUsername = ps.query.startsWith('@') && !ps.query.includes(' ') && ps.query.indexOf('@', 1) === -1;
let users: MiUser[] = [];
- if (isUsername) {
- const usernameQuery = this.usersRepository.createQueryBuilder('user')
- .where('user.usernameLower LIKE :username', { username: sqlLikeEscape(ps.query.replace('@', '').toLowerCase()) + '%' })
- .andWhere(new Brackets(qb => {
- qb
- .where('user.updatedAt IS NULL')
- .orWhere('user.updatedAt > :activeThreshold', { activeThreshold: activeThreshold });
- }))
- .andWhere('user.isSuspended = FALSE');
+ const nameQuery = this.usersRepository.createQueryBuilder('user')
+ .where(new Brackets(qb => {
+ qb.where('user.name ILIKE :query', { query: '%' + sqlLikeEscape(ps.query) + '%' });
+
+ if (isUsername) {
+ qb.orWhere('user.usernameLower LIKE :username', { username: sqlLikeEscape(ps.query.replace('@', '').toLowerCase()) + '%' });
+ } else if (this.userEntityService.validateLocalUsername(ps.query)) { // Also search username if it qualifies as username
+ qb.orWhere('user.usernameLower LIKE :username', { username: '%' + sqlLikeEscape(ps.query.toLowerCase()) + '%' });
+ }
+ }))
+ .andWhere(new Brackets(qb => {
+ qb
+ .where('user.updatedAt IS NULL')
+ .orWhere('user.updatedAt > :activeThreshold', { activeThreshold: activeThreshold });
+ }))
+ .andWhere('user.isSuspended = FALSE');
+
+ if (ps.origin === 'local') {
+ nameQuery.andWhere('user.host IS NULL');
+ } else if (ps.origin === 'remote') {
+ nameQuery.andWhere('user.host IS NOT NULL');
+ }
+
+ users = await nameQuery
+ .orderBy('user.updatedAt', 'DESC', 'NULLS LAST')
+ .limit(ps.limit)
+ .offset(ps.offset)
+ .getMany();
+
+ if (users.length < ps.limit) {
+ const profQuery = this.userProfilesRepository.createQueryBuilder('prof')
+ .select('prof.userId')
+ .where('prof.description ILIKE :query', { query: '%' + sqlLikeEscape(ps.query) + '%' });
if (ps.origin === 'local') {
- usernameQuery.andWhere('user.host IS NULL');
+ profQuery.andWhere('prof.userHost IS NULL');
} else if (ps.origin === 'remote') {
- usernameQuery.andWhere('user.host IS NOT NULL');
+ profQuery.andWhere('prof.userHost IS NOT NULL');
}
- users = await usernameQuery
- .orderBy('user.updatedAt', 'DESC', 'NULLS LAST')
- .limit(ps.limit)
- .offset(ps.offset)
- .getMany();
- } else {
- const nameQuery = this.usersRepository.createQueryBuilder('user')
- .where(new Brackets(qb => {
- qb.where('user.name ILIKE :query', { query: '%' + sqlLikeEscape(ps.query) + '%' });
-
- // Also search username if it qualifies as username
- if (this.userEntityService.validateLocalUsername(ps.query)) {
- qb.orWhere('user.usernameLower LIKE :username', { username: '%' + sqlLikeEscape(ps.query.toLowerCase()) + '%' });
- }
- }))
+ const query = this.usersRepository.createQueryBuilder('user')
+ .where(`user.id IN (${ profQuery.getQuery() })`)
.andWhere(new Brackets(qb => {
qb
.where('user.updatedAt IS NULL')
.orWhere('user.updatedAt > :activeThreshold', { activeThreshold: activeThreshold });
}))
- .andWhere('user.isSuspended = FALSE');
-
- if (ps.origin === 'local') {
- nameQuery.andWhere('user.host IS NULL');
- } else if (ps.origin === 'remote') {
- nameQuery.andWhere('user.host IS NOT NULL');
- }
+ .andWhere('user.isSuspended = FALSE')
+ .setParameters(profQuery.getParameters());
- users = await nameQuery
+ users = users.concat(await query
.orderBy('user.updatedAt', 'DESC', 'NULLS LAST')
.limit(ps.limit)
.offset(ps.offset)
- .getMany();
-
- if (users.length < ps.limit) {
- const profQuery = this.userProfilesRepository.createQueryBuilder('prof')
- .select('prof.userId')
- .where('prof.description ILIKE :query', { query: '%' + sqlLikeEscape(ps.query) + '%' });
-
- if (ps.origin === 'local') {
- profQuery.andWhere('prof.userHost IS NULL');
- } else if (ps.origin === 'remote') {
- profQuery.andWhere('prof.userHost IS NOT NULL');
- }
-
- const query = this.usersRepository.createQueryBuilder('user')
- .where(`user.id IN (${ profQuery.getQuery() })`)
- .andWhere(new Brackets(qb => {
- qb
- .where('user.updatedAt IS NULL')
- .orWhere('user.updatedAt > :activeThreshold', { activeThreshold: activeThreshold });
- }))
- .andWhere('user.isSuspended = FALSE')
- .setParameters(profQuery.getParameters());
-
- users = users.concat(await query
- .orderBy('user.updatedAt', 'DESC', 'NULLS LAST')
- .limit(ps.limit)
- .offset(ps.offset)
- .getMany(),
- );
- }
+ .getMany(),
+ );
}
return await this.userEntityService.packMany(users, me, { schema: ps.detail ? 'UserDetailed' : 'UserLite' });
diff --git a/packages/backend/src/server/api/openapi/OpenApiServerService.ts b/packages/backend/src/server/api/openapi/OpenApiServerService.ts
index 5210e4d2bc..f124aa9f39 100644
--- a/packages/backend/src/server/api/openapi/OpenApiServerService.ts
+++ b/packages/backend/src/server/api/openapi/OpenApiServerService.ts
@@ -25,7 +25,7 @@ export class OpenApiServerService {
public createServer(fastify: FastifyInstance, _options: FastifyPluginOptions, done: (err?: Error) => void) {
fastify.get('/api-doc', async (_request, reply) => {
reply.header('Cache-Control', 'public, max-age=86400');
- return await reply.sendFile('/redoc.html', staticAssets);
+ return await reply.sendFile('/api-doc.html', staticAssets);
});
fastify.get('/api.json', (_request, reply) => {
reply.header('Cache-Control', 'public, max-age=600');
diff --git a/packages/backend/src/server/api/openapi/gen-spec.ts b/packages/backend/src/server/api/openapi/gen-spec.ts
index 436f9a44bb..fb5954fee0 100644
--- a/packages/backend/src/server/api/openapi/gen-spec.ts
+++ b/packages/backend/src/server/api/openapi/gen-spec.ts
@@ -15,7 +15,6 @@ export function genOpenapiSpec(config: Config, includeSelfRef = false) {
info: {
version: config.version,
title: 'Misskey API',
- 'x-logo': { url: '/static-assets/api-doc.png' },
},
externalDocs: {
diff --git a/packages/backend/src/server/api/stream/Connection.ts b/packages/backend/src/server/api/stream/Connection.ts
index 7ea92eb797..9378b6c62b 100644
--- a/packages/backend/src/server/api/stream/Connection.ts
+++ b/packages/backend/src/server/api/stream/Connection.ts
@@ -14,6 +14,7 @@ import { CacheService } from '@/core/CacheService.js';
import { MiFollowing, MiUserProfile } from '@/models/_.js';
import type { StreamEventEmitter, GlobalEvents } from '@/core/GlobalEventService.js';
import { ChannelFollowingService } from '@/core/ChannelFollowingService.js';
+import type { JsonObject } from '@/misc/json-value.js';
import type { ChannelsService } from './ChannelsService.js';
import type { EventEmitter } from 'events';
import type Channel from './channel.js';
@@ -33,7 +34,7 @@ export default class Connection {
private wsConnection: WebSocket.WebSocket;
public subscriber: StreamEventEmitter;
private channels: Channel[] = [];
- private subscribingNotes: any = {};
+ private subscribingNotes: Partial<Record<string, number>> = {};
private cachedNotes: Packed<'Note'>[] = [];
public userProfile: MiUserProfile | null = null;
public following: Record<string, Pick<MiFollowing, 'withReplies'> | undefined> = {};
@@ -64,7 +65,7 @@ export default class Connection {
if (token) this.token = token;
if (rateLimiter) this.rateLimiter = rateLimiter;
- this.logger = loggerService.getLogger('streaming', 'coral', false);
+ this.logger = loggerService.getLogger('streaming', 'coral');
}
@bindThis
@@ -115,7 +116,7 @@ export default class Connection {
*/
@bindThis
private async onWsConnectionMessage(data: WebSocket.RawData) {
- let obj: Record<string, any>;
+ let obj: JsonObject;
if (this.closingConnection) return;
@@ -148,6 +149,8 @@ export default class Connection {
const { type, body } = obj;
+ if (typeof body !== 'object' || body === null || Array.isArray(body)) return;
+
switch (type) {
case 'readNotification': this.onReadNotification(body); break;
case 'subNote': this.onSubscribeNote(body); break;
@@ -188,7 +191,7 @@ export default class Connection {
}
@bindThis
- private readNote(body: any) {
+ private readNote(body: JsonObject) {
const id = body.id;
const note = this.cachedNotes.find(n => n.id === id);
@@ -200,7 +203,7 @@ export default class Connection {
}
@bindThis
- private onReadNotification(payload: any) {
+ private onReadNotification(payload: JsonObject) {
this.notificationService.readAllNotification(this.user!.id);
}
@@ -208,16 +211,14 @@ export default class Connection {
* 投稿購読要求時
*/
@bindThis
- private onSubscribeNote(payload: any) {
- if (!payload.id) return;
-
- if (this.subscribingNotes[payload.id] == null) {
- this.subscribingNotes[payload.id] = 0;
- }
+ private onSubscribeNote(payload: JsonObject) {
+ if (!payload.id || typeof payload.id !== 'string') return;
- this.subscribingNotes[payload.id]++;
+ const current = this.subscribingNotes[payload.id] ?? 0;
+ const updated = current + 1;
+ this.subscribingNotes[payload.id] = updated;
- if (this.subscribingNotes[payload.id] === 1) {
+ if (updated === 1) {
this.subscriber.on(`noteStream:${payload.id}`, this.onNoteStreamMessage);
}
}
@@ -226,11 +227,14 @@ export default class Connection {
* 投稿購読解除要求時
*/
@bindThis
- private onUnsubscribeNote(payload: any) {
- if (!payload.id) return;
+ private onUnsubscribeNote(payload: JsonObject) {
+ if (!payload.id || typeof payload.id !== 'string') return;
- this.subscribingNotes[payload.id]--;
- if (this.subscribingNotes[payload.id] <= 0) {
+ const current = this.subscribingNotes[payload.id];
+ if (current == null) return;
+ const updated = current - 1;
+ this.subscribingNotes[payload.id] = updated;
+ if (updated <= 0) {
delete this.subscribingNotes[payload.id];
this.subscriber.off(`noteStream:${payload.id}`, this.onNoteStreamMessage);
}
@@ -261,17 +265,22 @@ export default class Connection {
* チャンネル接続要求時
*/
@bindThis
- private onChannelConnectRequested(payload: any) {
+ private onChannelConnectRequested(payload: JsonObject) {
const { channel, id, params, pong } = payload;
- this.connectChannel(id, params, channel, pong);
+ if (typeof id !== 'string') return;
+ if (typeof channel !== 'string') return;
+ if (typeof pong !== 'boolean' && typeof pong !== 'undefined' && pong !== null) return;
+ if (typeof params !== 'undefined' && (typeof params !== 'object' || params === null || Array.isArray(params))) return;
+ this.connectChannel(id, params, channel, pong ?? undefined);
}
/**
* チャンネル切断要求時
*/
@bindThis
- private onChannelDisconnectRequested(payload: any) {
+ private onChannelDisconnectRequested(payload: JsonObject) {
const { id } = payload;
+ if (typeof id !== 'string') return;
this.disconnectChannel(id);
}
@@ -279,7 +288,7 @@ export default class Connection {
* クライアントにメッセージ送信
*/
@bindThis
- public sendMessageToWs(type: string, payload: any) {
+ public sendMessageToWs(type: string, payload: JsonObject) {
this.wsConnection.send(JSON.stringify({
type: type,
body: payload,
@@ -290,7 +299,7 @@ export default class Connection {
* チャンネルに接続
*/
@bindThis
- public connectChannel(id: string, params: any, channel: string, pong = false) {
+ public connectChannel(id: string, params: JsonObject | undefined, channel: string, pong = false) {
if (this.channels.length >= MAX_CHANNELS_PER_CONNECTION) {
return;
}
@@ -341,7 +350,11 @@ export default class Connection {
* @param data メッセージ
*/
@bindThis
- private onChannelMessageRequested(data: any) {
+ private onChannelMessageRequested(data: JsonObject) {
+ if (typeof data.id !== 'string') return;
+ if (typeof data.type !== 'string') return;
+ if (typeof data.body === 'undefined') return;
+
const channel = this.channels.find(c => c.id === data.id);
if (channel != null && channel.onMessage != null) {
channel.onMessage(data.type, data.body);
diff --git a/packages/backend/src/server/api/stream/channel.ts b/packages/backend/src/server/api/stream/channel.ts
index 49b0ae1d5b..ae9c7e3e99 100644
--- a/packages/backend/src/server/api/stream/channel.ts
+++ b/packages/backend/src/server/api/stream/channel.ts
@@ -8,6 +8,7 @@ import { isInstanceMuted } from '@/misc/is-instance-muted.js';
import { isUserRelated } from '@/misc/is-user-related.js';
import { isRenotePacked, isQuotePacked } from '@/misc/is-renote.js';
import type { Packed } from '@/misc/json-schema.js';
+import type { JsonObject, JsonValue } from '@/misc/json-value.js';
import type Connection from './Connection.js';
/**
@@ -81,10 +82,12 @@ export default abstract class Channel {
this.connection = connection;
}
+ public send(payload: { type: string, body: JsonValue }): void
+ public send(type: string, payload: JsonValue): void
@bindThis
- public send(typeOrPayload: any, payload?: any) {
- const type = payload === undefined ? typeOrPayload.type : typeOrPayload;
- const body = payload === undefined ? typeOrPayload.body : payload;
+ public send(typeOrPayload: { type: string, body: JsonValue } | string, payload?: JsonValue) {
+ const type = payload === undefined ? (typeOrPayload as { type: string, body: JsonValue }).type : (typeOrPayload as string);
+ const body = payload === undefined ? (typeOrPayload as { type: string, body: JsonValue }).body : payload;
this.connection.sendMessageToWs('channel', {
id: this.id,
@@ -93,11 +96,11 @@ export default abstract class Channel {
});
}
- public abstract init(params: any): void;
+ public abstract init(params: JsonObject): void;
public dispose?(): void;
- public onMessage?(type: string, body: any): void;
+ public onMessage?(type: string, body: JsonValue): void;
}
export type MiChannelService<T extends boolean> = {
diff --git a/packages/backend/src/server/api/stream/channels/admin.ts b/packages/backend/src/server/api/stream/channels/admin.ts
index 92b6d2ac04..355d5dba21 100644
--- a/packages/backend/src/server/api/stream/channels/admin.ts
+++ b/packages/backend/src/server/api/stream/channels/admin.ts
@@ -5,6 +5,7 @@
import { Injectable } from '@nestjs/common';
import { bindThis } from '@/decorators.js';
+import type { JsonObject } from '@/misc/json-value.js';
import Channel, { type MiChannelService } from '../channel.js';
class AdminChannel extends Channel {
@@ -14,7 +15,7 @@ class AdminChannel extends Channel {
public static kind = 'read:admin:stream';
@bindThis
- public async init(params: any) {
+ public async init(params: JsonObject) {
// Subscribe admin stream
this.subscriber.on(`adminStream:${this.user!.id}`, data => {
this.send(data);
diff --git a/packages/backend/src/server/api/stream/channels/antenna.ts b/packages/backend/src/server/api/stream/channels/antenna.ts
index 4a1d2dd109..53dc7f18b6 100644
--- a/packages/backend/src/server/api/stream/channels/antenna.ts
+++ b/packages/backend/src/server/api/stream/channels/antenna.ts
@@ -7,6 +7,7 @@ import { Injectable } from '@nestjs/common';
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
import { bindThis } from '@/decorators.js';
import type { GlobalEvents } from '@/core/GlobalEventService.js';
+import type { JsonObject } from '@/misc/json-value.js';
import Channel, { type MiChannelService } from '../channel.js';
class AntennaChannel extends Channel {
@@ -27,8 +28,9 @@ class AntennaChannel extends Channel {
}
@bindThis
- public async init(params: any) {
- this.antennaId = params.antennaId as string;
+ public async init(params: JsonObject) {
+ if (typeof params.antennaId !== 'string') return;
+ this.antennaId = params.antennaId;
// Subscribe stream
this.subscriber.on(`antennaStream:${this.antennaId}`, this.onEvent);
diff --git a/packages/backend/src/server/api/stream/channels/bubble-timeline.ts b/packages/backend/src/server/api/stream/channels/bubble-timeline.ts
index 01be2d2089..647e9cab81 100644
--- a/packages/backend/src/server/api/stream/channels/bubble-timeline.ts
+++ b/packages/backend/src/server/api/stream/channels/bubble-timeline.ts
@@ -11,6 +11,7 @@ import { bindThis } from '@/decorators.js';
import { RoleService } from '@/core/RoleService.js';
import type { MiMeta } from '@/models/Meta.js';
import { isRenotePacked, isQuotePacked } from '@/misc/is-renote.js';
+import type { JsonObject } from '@/misc/json-value.js';
import Channel, { MiChannelService } from '../channel.js';
class BubbleTimelineChannel extends Channel {
@@ -35,13 +36,13 @@ class BubbleTimelineChannel extends Channel {
}
@bindThis
- public async init(params: any) {
+ public async init(params: JsonObject) {
const policies = await this.roleService.getUserPolicies(this.user ? this.user.id : null);
if (!policies.btlAvailable) return;
- this.withRenotes = params.withRenotes ?? true;
- this.withFiles = params.withFiles ?? false;
- this.withBots = params.withBots ?? true;
+ this.withRenotes = !!(params.withRenotes ?? true);
+ this.withFiles = !!(params.withFiles ?? false);
+ this.withBots = !!(params.withBots ?? true);
this.instance = await this.metaService.fetch();
// Subscribe events
diff --git a/packages/backend/src/server/api/stream/channels/channel.ts b/packages/backend/src/server/api/stream/channels/channel.ts
index 865e4fed19..226e161122 100644
--- a/packages/backend/src/server/api/stream/channels/channel.ts
+++ b/packages/backend/src/server/api/stream/channels/channel.ts
@@ -8,6 +8,7 @@ import type { Packed } from '@/misc/json-schema.js';
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
import { bindThis } from '@/decorators.js';
import { isRenotePacked, isQuotePacked } from '@/misc/is-renote.js';
+import type { JsonObject } from '@/misc/json-value.js';
import Channel, { type MiChannelService } from '../channel.js';
class ChannelChannel extends Channel {
@@ -30,9 +31,10 @@ class ChannelChannel extends Channel {
@bindThis
public async init(params: any) {
- this.channelId = params.channelId as string;
- this.withFiles = params.withFiles ?? false;
- this.withRenotes = params.withRenotes ?? true;
+ if (typeof params.channelId !== 'string') return;
+ this.channelId = params.channelId;
+ this.withFiles = !!(params.withFiles ?? false);
+ this.withRenotes = !!(params.withRenotes ?? true);
// Subscribe stream
this.subscriber.on('notesStream', this.onNote);
diff --git a/packages/backend/src/server/api/stream/channels/drive.ts b/packages/backend/src/server/api/stream/channels/drive.ts
index 0d9b486305..03768f3d23 100644
--- a/packages/backend/src/server/api/stream/channels/drive.ts
+++ b/packages/backend/src/server/api/stream/channels/drive.ts
@@ -5,6 +5,7 @@
import { Injectable } from '@nestjs/common';
import { bindThis } from '@/decorators.js';
+import type { JsonObject } from '@/misc/json-value.js';
import Channel, { type MiChannelService } from '../channel.js';
class DriveChannel extends Channel {
@@ -14,7 +15,7 @@ class DriveChannel extends Channel {
public static kind = 'read:account';
@bindThis
- public async init(params: any) {
+ public async init(params: JsonObject) {
// Subscribe drive stream
this.subscriber.on(`driveStream:${this.user!.id}`, data => {
this.send(data);
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 0a894147a2..6fe76747ee 100644
--- a/packages/backend/src/server/api/stream/channels/global-timeline.ts
+++ b/packages/backend/src/server/api/stream/channels/global-timeline.ts
@@ -10,6 +10,7 @@ import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
import { bindThis } from '@/decorators.js';
import { RoleService } from '@/core/RoleService.js';
import { isRenotePacked, isQuotePacked } from '@/misc/is-renote.js';
+import type { JsonObject } from '@/misc/json-value.js';
import Channel, { type MiChannelService } from '../channel.js';
class GlobalTimelineChannel extends Channel {
@@ -33,13 +34,13 @@ class GlobalTimelineChannel extends Channel {
}
@bindThis
- public async init(params: any) {
+ public async init(params: JsonObject) {
const policies = await this.roleService.getUserPolicies(this.user ? this.user.id : null);
if (!policies.gtlAvailable) return;
- this.withRenotes = params.withRenotes ?? true;
- this.withFiles = params.withFiles ?? false;
- this.withBots = params.withBots ?? true;
+ this.withRenotes = !!(params.withRenotes ?? true);
+ this.withFiles = !!(params.withFiles ?? false);
+ this.withBots = !!(params.withBots ?? true);
// Subscribe events
this.subscriber.on('notesStream', this.onNote);
diff --git a/packages/backend/src/server/api/stream/channels/hashtag.ts b/packages/backend/src/server/api/stream/channels/hashtag.ts
index 57bada5d9c..8105f15cb1 100644
--- a/packages/backend/src/server/api/stream/channels/hashtag.ts
+++ b/packages/backend/src/server/api/stream/channels/hashtag.ts
@@ -9,6 +9,7 @@ import type { Packed } from '@/misc/json-schema.js';
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
import { bindThis } from '@/decorators.js';
import { isRenotePacked, isQuotePacked } from '@/misc/is-renote.js';
+import type { JsonObject } from '@/misc/json-value.js';
import Channel, { type MiChannelService } from '../channel.js';
class HashtagChannel extends Channel {
@@ -28,11 +29,11 @@ class HashtagChannel extends Channel {
}
@bindThis
- public async init(params: any) {
+ public async init(params: JsonObject) {
+ if (!Array.isArray(params.q)) return;
+ if (!params.q.every(x => Array.isArray(x) && x.every(y => typeof y === 'string'))) return;
this.q = params.q;
- if (this.q == null) return;
-
// Subscribe stream
this.subscriber.on('notesStream', this.onNote);
}
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 84ff241469..359ab3e223 100644
--- a/packages/backend/src/server/api/stream/channels/home-timeline.ts
+++ b/packages/backend/src/server/api/stream/channels/home-timeline.ts
@@ -8,6 +8,7 @@ import type { Packed } from '@/misc/json-schema.js';
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
import { bindThis } from '@/decorators.js';
import { isRenotePacked, isQuotePacked } from '@/misc/is-renote.js';
+import type { JsonObject } from '@/misc/json-value.js';
import Channel, { type MiChannelService } from '../channel.js';
class HomeTimelineChannel extends Channel {
@@ -29,9 +30,9 @@ class HomeTimelineChannel extends Channel {
}
@bindThis
- public async init(params: any) {
- this.withRenotes = params.withRenotes ?? true;
- this.withFiles = params.withFiles ?? false;
+ public async init(params: JsonObject) {
+ this.withRenotes = !!(params.withRenotes ?? true);
+ this.withFiles = !!(params.withFiles ?? false);
this.subscriber.on('notesStream', this.onNote);
}
@@ -59,7 +60,7 @@ class HomeTimelineChannel extends Channel {
const reply = note.reply;
if (this.following[note.userId]?.withReplies) {
// 自分のフォローしていないユーザーの visibility: followers な投稿への返信は弾く
- if (reply.visibility === 'followers' && !Object.hasOwn(this.following, reply.userId)) return;
+ if (reply.visibility === 'followers' && !Object.hasOwn(this.following, reply.userId) && reply.userId !== this.user!.id) return;
} else {
// 「チャンネル接続主への返信」でもなければ、「チャンネル接続主が行った返信」でもなければ、「投稿者の投稿者自身への返信」でもない場合
if (reply.userId !== this.user!.id && !isMe && reply.userId !== note.userId) return;
@@ -74,7 +75,7 @@ class HomeTimelineChannel extends Channel {
if (note.renote.reply) {
const reply = note.renote.reply;
// 自分のフォローしていないユーザーの visibility: followers な投稿への返信のリノートは弾く
- if (reply.visibility === 'followers' && !Object.hasOwn(this.following, reply.userId)) return;
+ if (reply.visibility === 'followers' && !Object.hasOwn(this.following, reply.userId) && reply.userId !== this.user!.id) return;
}
}
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 b83d3ec817..01645fe657 100644
--- a/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts
+++ b/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts
@@ -10,6 +10,7 @@ import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
import { bindThis } from '@/decorators.js';
import { RoleService } from '@/core/RoleService.js';
import { isRenotePacked, isQuotePacked } from '@/misc/is-renote.js';
+import type { JsonObject } from '@/misc/json-value.js';
import Channel, { type MiChannelService } from '../channel.js';
class HybridTimelineChannel extends Channel {
@@ -35,14 +36,14 @@ class HybridTimelineChannel extends Channel {
}
@bindThis
- public async init(params: any): Promise<void> {
+ public async init(params: JsonObject): Promise<void> {
const policies = await this.roleService.getUserPolicies(this.user ? this.user.id : null);
if (!policies.ltlAvailable) return;
- this.withRenotes = params.withRenotes ?? true;
- this.withReplies = params.withReplies ?? false;
- this.withBots = params.withBots ?? true;
- this.withFiles = params.withFiles ?? false;
+ this.withRenotes = !!(params.withRenotes ?? true);
+ this.withReplies = !!(params.withReplies ?? false);
+ this.withFiles = !!(params.withFiles ?? false);
+ this.withBots = !!(params.withBots ?? true);
// Subscribe events
this.subscriber.on('notesStream', this.onNote);
@@ -78,7 +79,7 @@ class HybridTimelineChannel extends Channel {
const reply = note.reply;
if ((this.following[note.userId]?.withReplies ?? false) || this.withReplies) {
// 自分のフォローしていないユーザーの visibility: followers な投稿への返信は弾く
- if (reply.visibility === 'followers' && !Object.hasOwn(this.following, reply.userId)) return;
+ if (reply.visibility === 'followers' && !Object.hasOwn(this.following, reply.userId) && reply.userId !== this.user!.id) return;
} else {
// 「チャンネル接続主への返信」でもなければ、「チャンネル接続主が行った返信」でもなければ、「投稿者の投稿者自身への返信」でもない場合
if (reply.userId !== this.user!.id && !isMe && reply.userId !== note.userId) return;
@@ -87,7 +88,15 @@ class HybridTimelineChannel extends Channel {
if (note.user.isSilenced && !this.following[note.userId] && note.userId !== this.user!.id) return;
- if (isRenotePacked(note) && !isQuotePacked(note) && !this.withRenotes) return;
+ // 純粋なリノート(引用リノートでないリノート)の場合
+ if (isRenotePacked(note) && !isQuotePacked(note) && note.renote) {
+ if (!this.withRenotes) return;
+ if (note.renote.reply) {
+ const reply = note.renote.reply;
+ // 自分のフォローしていないユーザーの visibility: followers な投稿への返信のリノートは弾く
+ if (reply.visibility === 'followers' && !Object.hasOwn(this.following, reply.userId) && reply.userId !== this.user!.id) return;
+ }
+ }
if (this.user && note.renoteId && !note.text) {
if (note.renote && Object.keys(note.renote.reactions).length > 0) {
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 48cc76c497..1f9d25b44d 100644
--- a/packages/backend/src/server/api/stream/channels/local-timeline.ts
+++ b/packages/backend/src/server/api/stream/channels/local-timeline.ts
@@ -10,6 +10,7 @@ import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
import { bindThis } from '@/decorators.js';
import { RoleService } from '@/core/RoleService.js';
import { isQuotePacked, isRenotePacked } from '@/misc/is-renote.js';
+import type { JsonObject } from '@/misc/json-value.js';
import Channel, { type MiChannelService } from '../channel.js';
class LocalTimelineChannel extends Channel {
@@ -34,14 +35,14 @@ class LocalTimelineChannel extends Channel {
}
@bindThis
- public async init(params: any) {
+ public async init(params: JsonObject) {
const policies = await this.roleService.getUserPolicies(this.user ? this.user.id : null);
if (!policies.ltlAvailable) return;
- this.withRenotes = params.withRenotes ?? true;
- this.withReplies = params.withReplies ?? false;
- this.withBots = params.withBots ?? true;
- this.withFiles = params.withFiles ?? false;
+ this.withRenotes = !!(params.withRenotes ?? true);
+ this.withReplies = !!(params.withReplies ?? false);
+ this.withFiles = !!(params.withFiles ?? false);
+ this.withBots = !!(params.withBots ?? true);
// Subscribe events
this.subscriber.on('notesStream', this.onNote);
diff --git a/packages/backend/src/server/api/stream/channels/main.ts b/packages/backend/src/server/api/stream/channels/main.ts
index a12976d69d..863d7f4c4e 100644
--- a/packages/backend/src/server/api/stream/channels/main.ts
+++ b/packages/backend/src/server/api/stream/channels/main.ts
@@ -7,6 +7,7 @@ import { Injectable } from '@nestjs/common';
import { isInstanceMuted, isUserFromMutedInstance } from '@/misc/is-instance-muted.js';
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
import { bindThis } from '@/decorators.js';
+import type { JsonObject } from '@/misc/json-value.js';
import Channel, { type MiChannelService } from '../channel.js';
class MainChannel extends Channel {
@@ -25,7 +26,7 @@ class MainChannel extends Channel {
}
@bindThis
- public async init(params: any) {
+ public async init(params: JsonObject) {
// Subscribe main stream channel
this.subscriber.on(`mainStream:${this.user!.id}`, async data => {
switch (data.type) {
diff --git a/packages/backend/src/server/api/stream/channels/queue-stats.ts b/packages/backend/src/server/api/stream/channels/queue-stats.ts
index 061aa76904..ff7e740226 100644
--- a/packages/backend/src/server/api/stream/channels/queue-stats.ts
+++ b/packages/backend/src/server/api/stream/channels/queue-stats.ts
@@ -6,6 +6,7 @@
import Xev from 'xev';
import { Injectable } from '@nestjs/common';
import { bindThis } from '@/decorators.js';
+import type { JsonObject, JsonValue } from '@/misc/json-value.js';
import Channel, { type MiChannelService } from '../channel.js';
const ev = new Xev();
@@ -22,19 +23,22 @@ class QueueStatsChannel extends Channel {
}
@bindThis
- public async init(params: any) {
+ public async init(params: JsonObject) {
ev.addListener('queueStats', this.onStats);
}
@bindThis
- private onStats(stats: any) {
+ private onStats(stats: JsonObject) {
this.send('stats', stats);
}
@bindThis
- public onMessage(type: string, body: any) {
+ public onMessage(type: string, body: JsonValue) {
switch (type) {
case 'requestLog':
+ if (typeof body !== 'object' || body === null || Array.isArray(body)) return;
+ if (typeof body.id !== 'string') return;
+ if (typeof body.length !== 'number') return;
ev.once(`queueStatsLog:${body.id}`, statsLog => {
this.send('statsLog', statsLog);
});
diff --git a/packages/backend/src/server/api/stream/channels/reversi-game.ts b/packages/backend/src/server/api/stream/channels/reversi-game.ts
index f4a3a09367..17823a164a 100644
--- a/packages/backend/src/server/api/stream/channels/reversi-game.ts
+++ b/packages/backend/src/server/api/stream/channels/reversi-game.ts
@@ -9,6 +9,7 @@ import { DI } from '@/di-symbols.js';
import { bindThis } from '@/decorators.js';
import { ReversiService } from '@/core/ReversiService.js';
import { ReversiGameEntityService } from '@/core/entities/ReversiGameEntityService.js';
+import type { JsonObject, JsonValue } from '@/misc/json-value.js';
import Channel, { type MiChannelService } from '../channel.js';
class ReversiGameChannel extends Channel {
@@ -28,25 +29,41 @@ class ReversiGameChannel extends Channel {
}
@bindThis
- public async init(params: any) {
- this.gameId = params.gameId as string;
+ public async init(params: JsonObject) {
+ if (typeof params.gameId !== 'string') return;
+ this.gameId = params.gameId;
this.subscriber.on(`reversiGameStream:${this.gameId}`, this.send);
}
@bindThis
- public onMessage(type: string, body: any) {
+ public onMessage(type: string, body: JsonValue) {
switch (type) {
- case 'ready': this.ready(body); break;
- case 'updateSettings': this.updateSettings(body.key, body.value); break;
- case 'cancel': this.cancelGame(); break;
- case 'putStone': this.putStone(body.pos, body.id); break;
+ case 'ready':
+ if (typeof body !== 'boolean') return;
+ this.ready(body);
+ break;
+ case 'updateSettings':
+ if (typeof body !== 'object' || body === null || Array.isArray(body)) return;
+ if (typeof body.key !== 'string') return;
+ if (typeof body.value !== 'object' || body.value === null || Array.isArray(body.value)) return;
+ this.updateSettings(body.key, body.value);
+ break;
+ case 'cancel':
+ this.cancelGame();
+ break;
+ case 'putStone':
+ if (typeof body !== 'object' || body === null || Array.isArray(body)) return;
+ if (typeof body.pos !== 'number') return;
+ if (typeof body.id !== 'string') return;
+ this.putStone(body.pos, body.id);
+ break;
case 'claimTimeIsUp': this.claimTimeIsUp(); break;
}
}
@bindThis
- private async updateSettings(key: string, value: any) {
+ private async updateSettings(key: string, value: JsonObject) {
if (this.user == null) return;
this.reversiService.updateSettings(this.gameId!, this.user, key, value);
diff --git a/packages/backend/src/server/api/stream/channels/reversi.ts b/packages/backend/src/server/api/stream/channels/reversi.ts
index 3998a0fd36..6e88939724 100644
--- a/packages/backend/src/server/api/stream/channels/reversi.ts
+++ b/packages/backend/src/server/api/stream/channels/reversi.ts
@@ -5,6 +5,7 @@
import { Injectable } from '@nestjs/common';
import { bindThis } from '@/decorators.js';
+import type { JsonObject } from '@/misc/json-value.js';
import Channel, { type MiChannelService } from '../channel.js';
class ReversiChannel extends Channel {
@@ -21,7 +22,7 @@ class ReversiChannel extends Channel {
}
@bindThis
- public async init(params: any) {
+ public async init(params: JsonObject) {
this.subscriber.on(`reversiStream:${this.user!.id}`, this.send);
}
diff --git a/packages/backend/src/server/api/stream/channels/role-timeline.ts b/packages/backend/src/server/api/stream/channels/role-timeline.ts
index 6a4ad22460..fcfa26c38b 100644
--- a/packages/backend/src/server/api/stream/channels/role-timeline.ts
+++ b/packages/backend/src/server/api/stream/channels/role-timeline.ts
@@ -8,6 +8,7 @@ import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
import { bindThis } from '@/decorators.js';
import { RoleService } from '@/core/RoleService.js';
import type { GlobalEvents } from '@/core/GlobalEventService.js';
+import type { JsonObject } from '@/misc/json-value.js';
import Channel, { type MiChannelService } from '../channel.js';
class RoleTimelineChannel extends Channel {
@@ -28,8 +29,9 @@ class RoleTimelineChannel extends Channel {
}
@bindThis
- public async init(params: any) {
- this.roleId = params.roleId as string;
+ public async init(params: JsonObject) {
+ if (typeof params.roleId !== 'string') return;
+ this.roleId = params.roleId;
this.subscriber.on(`roleTimelineStream:${this.roleId}`, this.onEvent);
}
diff --git a/packages/backend/src/server/api/stream/channels/server-stats.ts b/packages/backend/src/server/api/stream/channels/server-stats.ts
index eb4d8c9992..6258afba35 100644
--- a/packages/backend/src/server/api/stream/channels/server-stats.ts
+++ b/packages/backend/src/server/api/stream/channels/server-stats.ts
@@ -6,6 +6,7 @@
import Xev from 'xev';
import { Injectable } from '@nestjs/common';
import { bindThis } from '@/decorators.js';
+import type { JsonObject, JsonValue } from '@/misc/json-value.js';
import Channel, { type MiChannelService } from '../channel.js';
const ev = new Xev();
@@ -22,19 +23,20 @@ class ServerStatsChannel extends Channel {
}
@bindThis
- public async init(params: any) {
+ public async init(params: JsonObject) {
ev.addListener('serverStats', this.onStats);
}
@bindThis
- private onStats(stats: any) {
+ private onStats(stats: JsonObject) {
this.send('stats', stats);
}
@bindThis
- public onMessage(type: string, body: any) {
+ public onMessage(type: string, body: JsonValue) {
switch (type) {
case 'requestLog':
+ if (typeof body !== 'object' || body === null || Array.isArray(body)) return;
ev.once(`serverStatsLog:${body.id}`, statsLog => {
this.send('statsLog', statsLog);
});
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 14b30a157c..4f38351e94 100644
--- a/packages/backend/src/server/api/stream/channels/user-list.ts
+++ b/packages/backend/src/server/api/stream/channels/user-list.ts
@@ -10,6 +10,7 @@ import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
import { DI } from '@/di-symbols.js';
import { bindThis } from '@/decorators.js';
import { isRenotePacked, isQuotePacked } from '@/misc/is-renote.js';
+import type { JsonObject } from '@/misc/json-value.js';
import Channel, { type MiChannelService } from '../channel.js';
class UserListChannel extends Channel {
@@ -36,10 +37,11 @@ class UserListChannel extends Channel {
}
@bindThis
- public async init(params: any) {
- this.listId = params.listId as string;
- this.withFiles = params.withFiles ?? false;
- this.withRenotes = params.withRenotes ?? true;
+ public async init(params: JsonObject) {
+ if (typeof params.listId !== 'string') return;
+ this.listId = params.listId;
+ this.withFiles = !!(params.withFiles ?? false);
+ this.withRenotes = !!(params.withRenotes ?? true);
// Check existence and owner
const listExist = await this.userListsRepository.exists({