Merge remote-tracking branch 'refs/remotes/misskey-original/develop' into develop
# Conflicts: # packages/backend/src/core/QueueModule.ts # packages/backend/src/core/QueueService.ts # packages/backend/src/core/chart/charts/federation.ts # packages/backend/src/models/RepositoryModule.ts # packages/backend/src/models/_.ts # packages/backend/src/queue/types.ts # packages/backend/src/server/api/EndpointsModule.ts # packages/backend/src/server/api/endpoints.ts # packages/backend/src/server/api/endpoints/admin/queue/stats.ts # packages/backend/src/server/api/endpoints/users/report-abuse.ts # packages/backend/src/server/web/ClientServerService.ts # packages/frontend/src/components/MkButton.vue # packages/frontend/src/components/MkChannelFollowButton.vue # packages/frontend/src/components/MkClickerGame.vue # packages/frontend/src/components/MkSwitch.vue
This commit is contained in:
commit
833031ee60
|
@ -10,7 +10,7 @@
|
|||
"ghcr.io/devcontainers-contrib/features/corepack:1": {}
|
||||
},
|
||||
"forwardPorts": [3000],
|
||||
"postCreateCommand": "sudo chmod 755 .devcontainer/init.sh && .devcontainer/init.sh",
|
||||
"postCreateCommand": "/bin/bash .devcontainer/init.sh",
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"extensions": [
|
||||
|
|
|
@ -8,6 +8,7 @@ services:
|
|||
|
||||
volumes:
|
||||
- ../:/workspace:cached
|
||||
- node_modules:/workspace/node_modules
|
||||
|
||||
command: sleep infinity
|
||||
|
||||
|
@ -46,6 +47,7 @@ services:
|
|||
volumes:
|
||||
postgres-data:
|
||||
redis-data:
|
||||
node_modules:
|
||||
|
||||
networks:
|
||||
internal_network:
|
||||
|
|
|
@ -2,7 +2,8 @@
|
|||
|
||||
set -xe
|
||||
|
||||
sudo chown -R node /workspace
|
||||
sudo chown node node_modules
|
||||
git config --global --add safe.directory /workspace
|
||||
git submodule update --init
|
||||
corepack install
|
||||
corepack enable
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
## Unreleased
|
||||
|
||||
### General
|
||||
-
|
||||
- Feat: 通報を受けた際、または解決した際に、予め登録した宛先に通知を飛ばせるように(mail or webhook) #13705
|
||||
- Fix: 配信停止したインスタンス一覧が見れなくなる問題を修正
|
||||
|
||||
### Client
|
||||
-
|
||||
- Fix: ユーザーページの追加情報のラベルを投稿者のサーバーの絵文字で表示する (#13968)
|
||||
|
||||
### Server
|
||||
-
|
||||
- チャート生成時にinstance.suspentionStateに置き換えられたinstance.isSuspendedが参照されてしまう問題を修正
|
||||
|
||||
|
||||
## 2024.5.0
|
||||
|
|
|
@ -9715,6 +9715,10 @@ export interface Locale extends ILocale {
|
|||
* Webhookを作成
|
||||
*/
|
||||
"createWebhook": string;
|
||||
/**
|
||||
* Webhookを編集
|
||||
*/
|
||||
"modifyWebhook": string;
|
||||
/**
|
||||
* 名前
|
||||
*/
|
||||
|
@ -9761,6 +9765,72 @@ export interface Locale extends ILocale {
|
|||
*/
|
||||
"mention": string;
|
||||
};
|
||||
"_systemEvents": {
|
||||
/**
|
||||
* ユーザーから通報があったとき
|
||||
*/
|
||||
"abuseReport": string;
|
||||
/**
|
||||
* ユーザーからの通報を処理したとき
|
||||
*/
|
||||
"abuseReportResolved": string;
|
||||
};
|
||||
/**
|
||||
* Webhookを削除しますか?
|
||||
*/
|
||||
"deleteConfirm": string;
|
||||
};
|
||||
"_abuseReport": {
|
||||
"_notificationRecipient": {
|
||||
/**
|
||||
* 通報の通知先を追加
|
||||
*/
|
||||
"createRecipient": string;
|
||||
/**
|
||||
* 通報の通知先を編集
|
||||
*/
|
||||
"modifyRecipient": string;
|
||||
/**
|
||||
* 通知先の種類
|
||||
*/
|
||||
"recipientType": string;
|
||||
"_recipientType": {
|
||||
/**
|
||||
* メール
|
||||
*/
|
||||
"mail": string;
|
||||
/**
|
||||
* Webhook
|
||||
*/
|
||||
"webhook": string;
|
||||
"_captions": {
|
||||
/**
|
||||
* モデレーター権限を持つユーザーのメールアドレスに通知を送ります(通報を受けた時のみ)
|
||||
*/
|
||||
"mail": string;
|
||||
/**
|
||||
* 指定したSystemWebhookに通知を送ります(通報を受けた時と通報を解決した時にそれぞれ発信)
|
||||
*/
|
||||
"webhook": string;
|
||||
};
|
||||
};
|
||||
/**
|
||||
* キーワード
|
||||
*/
|
||||
"keywords": string;
|
||||
/**
|
||||
* 通知先ユーザー
|
||||
*/
|
||||
"notifiedUser": string;
|
||||
/**
|
||||
* 使用するWebhook
|
||||
*/
|
||||
"notifiedWebhook": string;
|
||||
/**
|
||||
* 通知先を削除しますか?
|
||||
*/
|
||||
"deleteConfirm": string;
|
||||
};
|
||||
};
|
||||
"_moderationLogTypes": {
|
||||
/**
|
||||
|
@ -9907,6 +9977,30 @@ export interface Locale extends ILocale {
|
|||
* ユーザーのバナーを解除
|
||||
*/
|
||||
"unsetUserBanner": string;
|
||||
/**
|
||||
* SystemWebhookを作成
|
||||
*/
|
||||
"createSystemWebhook": string;
|
||||
/**
|
||||
* SystemWebhookを更新
|
||||
*/
|
||||
"updateSystemWebhook": string;
|
||||
/**
|
||||
* SystemWebhookを削除
|
||||
*/
|
||||
"deleteSystemWebhook": string;
|
||||
/**
|
||||
* 通報の通知先を作成
|
||||
*/
|
||||
"createAbuseReportNotificationRecipient": string;
|
||||
/**
|
||||
* 通報の通知先を更新
|
||||
*/
|
||||
"updateAbuseReportNotificationRecipient": string;
|
||||
/**
|
||||
* 通報の通知先を削除
|
||||
*/
|
||||
"deleteAbuseReportNotificationRecipient": string;
|
||||
};
|
||||
"_fileViewer": {
|
||||
/**
|
||||
|
|
|
@ -2573,6 +2573,7 @@ _drivecleaner:
|
|||
|
||||
_webhookSettings:
|
||||
createWebhook: "Webhookを作成"
|
||||
modifyWebhook: "Webhookを編集"
|
||||
name: "名前"
|
||||
secret: "シークレット"
|
||||
events: "Webhookを実行するタイミング"
|
||||
|
@ -2585,6 +2586,26 @@ _webhookSettings:
|
|||
renote: "Renoteされたとき"
|
||||
reaction: "リアクションがあったとき"
|
||||
mention: "メンションされたとき"
|
||||
_systemEvents:
|
||||
abuseReport: "ユーザーから通報があったとき"
|
||||
abuseReportResolved: "ユーザーからの通報を処理したとき"
|
||||
deleteConfirm: "Webhookを削除しますか?"
|
||||
|
||||
_abuseReport:
|
||||
_notificationRecipient:
|
||||
createRecipient: "通報の通知先を追加"
|
||||
modifyRecipient: "通報の通知先を編集"
|
||||
recipientType: "通知先の種類"
|
||||
_recipientType:
|
||||
mail: "メール"
|
||||
webhook: "Webhook"
|
||||
_captions:
|
||||
mail: "モデレーター権限を持つユーザーのメールアドレスに通知を送ります(通報を受けた時のみ)"
|
||||
webhook: "指定したSystemWebhookに通知を送ります(通報を受けた時と通報を解決した時にそれぞれ発信)"
|
||||
keywords: "キーワード"
|
||||
notifiedUser: "通知先ユーザー"
|
||||
notifiedWebhook: "使用するWebhook"
|
||||
deleteConfirm: "通知先を削除しますか?"
|
||||
|
||||
_moderationLogTypes:
|
||||
createRole: "ロールを作成"
|
||||
|
@ -2623,6 +2644,12 @@ _moderationLogTypes:
|
|||
deleteAvatarDecoration: "アイコンデコレーションを削除"
|
||||
unsetUserAvatar: "ユーザーのアイコンを解除"
|
||||
unsetUserBanner: "ユーザーのバナーを解除"
|
||||
createSystemWebhook: "SystemWebhookを作成"
|
||||
updateSystemWebhook: "SystemWebhookを更新"
|
||||
deleteSystemWebhook: "SystemWebhookを削除"
|
||||
createAbuseReportNotificationRecipient: "通報の通知先を作成"
|
||||
updateAbuseReportNotificationRecipient: "通報の通知先を更新"
|
||||
deleteAbuseReportNotificationRecipient: "通報の通知先を削除"
|
||||
|
||||
_fileViewer:
|
||||
title: "ファイルの詳細"
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export class AbuseReportNotification1713656541000 {
|
||||
name = 'AbuseReportNotification1713656541000'
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`
|
||||
CREATE TABLE "system_webhook" (
|
||||
"id" varchar(32) NOT NULL,
|
||||
"isActive" boolean NOT NULL DEFAULT true,
|
||||
"updatedAt" timestamp with time zone NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"latestSentAt" timestamp with time zone NULL DEFAULT NULL,
|
||||
"latestStatus" integer NULL DEFAULT NULL,
|
||||
"name" varchar(255) NOT NULL,
|
||||
"on" varchar(128) [] NOT NULL DEFAULT '{}'::character varying[],
|
||||
"url" varchar(1024) NOT NULL,
|
||||
"secret" varchar(1024) NOT NULL,
|
||||
CONSTRAINT "PK_system_webhook_id" PRIMARY KEY ("id")
|
||||
);
|
||||
CREATE INDEX "IDX_system_webhook_isActive" ON "system_webhook" ("isActive");
|
||||
CREATE INDEX "IDX_system_webhook_on" ON "system_webhook" USING gin ("on");
|
||||
|
||||
CREATE TABLE "abuse_report_notification_recipient" (
|
||||
"id" varchar(32) NOT NULL,
|
||||
"isActive" boolean NOT NULL DEFAULT true,
|
||||
"updatedAt" timestamp with time zone NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"name" varchar(255) NOT NULL,
|
||||
"method" varchar(64) NOT NULL,
|
||||
"userId" varchar(32) NULL DEFAULT NULL,
|
||||
"systemWebhookId" varchar(32) NULL DEFAULT NULL,
|
||||
CONSTRAINT "PK_abuse_report_notification_recipient_id" PRIMARY KEY ("id"),
|
||||
CONSTRAINT "FK_abuse_report_notification_recipient_userId1" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION,
|
||||
CONSTRAINT "FK_abuse_report_notification_recipient_userId2" FOREIGN KEY ("userId") REFERENCES "user_profile"("userId") ON DELETE CASCADE ON UPDATE NO ACTION,
|
||||
CONSTRAINT "FK_abuse_report_notification_recipient_systemWebhookId" FOREIGN KEY ("systemWebhookId") REFERENCES "system_webhook"("id") ON DELETE CASCADE ON UPDATE NO ACTION
|
||||
);
|
||||
CREATE INDEX "IDX_abuse_report_notification_recipient_isActive" ON "abuse_report_notification_recipient" ("isActive");
|
||||
CREATE INDEX "IDX_abuse_report_notification_recipient_method" ON "abuse_report_notification_recipient" ("method");
|
||||
CREATE INDEX "IDX_abuse_report_notification_recipient_userId" ON "abuse_report_notification_recipient" ("userId");
|
||||
CREATE INDEX "IDX_abuse_report_notification_recipient_systemWebhookId" ON "abuse_report_notification_recipient" ("systemWebhookId");
|
||||
`);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE "abuse_report_notification_recipient" DROP CONSTRAINT "FK_abuse_report_notification_recipient_userId1";
|
||||
ALTER TABLE "abuse_report_notification_recipient" DROP CONSTRAINT "FK_abuse_report_notification_recipient_userId2";
|
||||
ALTER TABLE "abuse_report_notification_recipient" DROP CONSTRAINT "FK_abuse_report_notification_recipient_systemWebhookId";
|
||||
DROP INDEX "IDX_abuse_report_notification_recipient_isActive";
|
||||
DROP INDEX "IDX_abuse_report_notification_recipient_method";
|
||||
DROP INDEX "IDX_abuse_report_notification_recipient_userId";
|
||||
DROP INDEX "IDX_abuse_report_notification_recipient_systemWebhookId";
|
||||
DROP TABLE "abuse_report_notification_recipient";
|
||||
|
||||
DROP INDEX "IDX_system_webhook_isActive";
|
||||
DROP INDEX "IDX_system_webhook_on";
|
||||
DROP TABLE "system_webhook";
|
||||
`);
|
||||
}
|
||||
}
|
|
@ -7,7 +7,7 @@ import { LoggerService } from '@nestjs/common';
|
|||
import Logger from '@/logger.js';
|
||||
|
||||
const logger = new Logger('core', 'cyan');
|
||||
const nestLogger = logger.createSubLogger('nest', 'green', false);
|
||||
const nestLogger = logger.createSubLogger('nest', 'green');
|
||||
|
||||
export class NestLogger implements LoggerService {
|
||||
/**
|
||||
|
|
|
@ -25,7 +25,7 @@ Error.stackTraceLimit = Infinity;
|
|||
EventEmitter.defaultMaxListeners = 128;
|
||||
|
||||
const logger = new Logger('core', 'cyan');
|
||||
const clusterLogger = logger.createSubLogger('cluster', 'orange', false);
|
||||
const clusterLogger = logger.createSubLogger('cluster', 'orange');
|
||||
const ev = new Xev();
|
||||
|
||||
//#region Events
|
||||
|
|
|
@ -25,7 +25,7 @@ const _dirname = dirname(_filename);
|
|||
const meta = JSON.parse(fs.readFileSync(`${_dirname}/../../../../built/meta.json`, 'utf-8'));
|
||||
|
||||
const logger = new Logger('core', 'cyan');
|
||||
const bootLogger = logger.createSubLogger('boot', 'magenta', false);
|
||||
const bootLogger = logger.createSubLogger('boot', 'magenta');
|
||||
|
||||
const themeColor = chalk.hex('#86b300');
|
||||
|
||||
|
|
|
@ -4,13 +4,36 @@
|
|||
*/
|
||||
|
||||
import cluster from 'node:cluster';
|
||||
import * as Sentry from '@sentry/node';
|
||||
import { nodeProfilingIntegration } from '@sentry/profiling-node';
|
||||
import { envOption } from '@/env.js';
|
||||
import { loadConfig } from '@/config.js';
|
||||
import { jobQueue, server } from './common.js';
|
||||
|
||||
/**
|
||||
* Init worker process
|
||||
*/
|
||||
export async function workerMain() {
|
||||
const config = loadConfig();
|
||||
|
||||
if (config.sentryForBackend) {
|
||||
Sentry.init({
|
||||
integrations: [
|
||||
...(config.sentryForBackend.enableNodeProfiling ? [nodeProfilingIntegration()] : []),
|
||||
],
|
||||
|
||||
// Performance Monitoring
|
||||
tracesSampleRate: 1.0, // Capture 100% of the transactions
|
||||
|
||||
// Set sampling rate for profiling - this is relative to tracesSampleRate
|
||||
profilesSampleRate: 1.0,
|
||||
|
||||
maxBreadcrumbs: 0,
|
||||
|
||||
...config.sentryForBackend.options,
|
||||
});
|
||||
}
|
||||
|
||||
if (envOption.onlyServer) {
|
||||
await server();
|
||||
} else if (envOption.onlyQueue) {
|
||||
|
|
|
@ -0,0 +1,406 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Inject, Injectable, type OnApplicationShutdown } from '@nestjs/common';
|
||||
import { Brackets, In, IsNull, Not } from 'typeorm';
|
||||
import * as Redis from 'ioredis';
|
||||
import sanitizeHtml from 'sanitize-html';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { GlobalEvents, GlobalEventService } from '@/core/GlobalEventService.js';
|
||||
import { isNotNull } from '@/misc/is-not-null.js';
|
||||
import type {
|
||||
AbuseReportNotificationRecipientRepository,
|
||||
MiAbuseReportNotificationRecipient,
|
||||
MiAbuseUserReport,
|
||||
MiUser,
|
||||
} from '@/models/_.js';
|
||||
import { EmailService } from '@/core/EmailService.js';
|
||||
import { MetaService } from '@/core/MetaService.js';
|
||||
import { RoleService } from '@/core/RoleService.js';
|
||||
import { RecipientMethod } from '@/models/AbuseReportNotificationRecipient.js';
|
||||
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||
import { SystemWebhookService } from '@/core/SystemWebhookService.js';
|
||||
import { IdService } from './IdService.js';
|
||||
|
||||
@Injectable()
|
||||
export class AbuseReportNotificationService implements OnApplicationShutdown {
|
||||
constructor(
|
||||
@Inject(DI.abuseReportNotificationRecipientRepository)
|
||||
private abuseReportNotificationRecipientRepository: AbuseReportNotificationRecipientRepository,
|
||||
@Inject(DI.redisForSub)
|
||||
private redisForSub: Redis.Redis,
|
||||
private idService: IdService,
|
||||
private roleService: RoleService,
|
||||
private systemWebhookService: SystemWebhookService,
|
||||
private emailService: EmailService,
|
||||
private metaService: MetaService,
|
||||
private moderationLogService: ModerationLogService,
|
||||
private globalEventService: GlobalEventService,
|
||||
) {
|
||||
this.redisForSub.on('message', this.onMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* 管理者用Redisイベントを用いて{@link abuseReports}の内容を管理者各位に通知する.
|
||||
* 通知先ユーザは{@link RoleService.getModeratorIds}の取得結果に依る.
|
||||
*
|
||||
* @see RoleService.getModeratorIds
|
||||
* @see GlobalEventService.publishAdminStream
|
||||
*/
|
||||
@bindThis
|
||||
public async notifyAdminStream(abuseReports: MiAbuseUserReport[]) {
|
||||
if (abuseReports.length <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const moderatorIds = await this.roleService.getModeratorIds(true, true);
|
||||
|
||||
for (const moderatorId of moderatorIds) {
|
||||
for (const abuseReport of abuseReports) {
|
||||
this.globalEventService.publishAdminStream(
|
||||
moderatorId,
|
||||
'newAbuseUserReport',
|
||||
{
|
||||
id: abuseReport.id,
|
||||
targetUserId: abuseReport.targetUserId,
|
||||
reporterId: abuseReport.reporterId,
|
||||
comment: abuseReport.comment,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mailを用いて{@link abuseReports}の内容を管理者各位に通知する.
|
||||
* メールアドレスの送信先は以下の通り.
|
||||
* - モデレータ権限所有者ユーザ(設定画面からメールアドレスの設定を行っているユーザに限る)
|
||||
* - metaテーブルに設定されているメールアドレス
|
||||
*
|
||||
* @see EmailService.sendEmail
|
||||
*/
|
||||
@bindThis
|
||||
public async notifyMail(abuseReports: MiAbuseUserReport[]) {
|
||||
if (abuseReports.length <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const recipientEMailAddresses = await this.fetchEMailRecipients().then(it => it
|
||||
.filter(it => it.isActive && it.userProfile?.emailVerified)
|
||||
.map(it => it.userProfile?.email)
|
||||
.filter(isNotNull),
|
||||
);
|
||||
|
||||
// 送信先の鮮度を保つため、毎回取得する
|
||||
const meta = await this.metaService.fetch(true);
|
||||
recipientEMailAddresses.push(
|
||||
...(meta.email ? [meta.email] : []),
|
||||
);
|
||||
|
||||
if (recipientEMailAddresses.length <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const mailAddress of recipientEMailAddresses) {
|
||||
await Promise.all(
|
||||
abuseReports.map(it => {
|
||||
// TODO: 送信処理はJobQueue化したい
|
||||
return this.emailService.sendEmail(
|
||||
mailAddress,
|
||||
'New Abuse Report',
|
||||
sanitizeHtml(it.comment),
|
||||
sanitizeHtml(it.comment),
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* SystemWebhookを用いて{@link abuseReports}の内容を管理者各位に通知する.
|
||||
* ここではJobQueueへのエンキューのみを行うため、即時実行されない.
|
||||
*
|
||||
* @see SystemWebhookService.enqueueSystemWebhook
|
||||
*/
|
||||
@bindThis
|
||||
public async notifySystemWebhook(
|
||||
abuseReports: MiAbuseUserReport[],
|
||||
type: 'abuseReport' | 'abuseReportResolved',
|
||||
) {
|
||||
if (abuseReports.length <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const recipientWebhookIds = await this.fetchWebhookRecipients()
|
||||
.then(it => it
|
||||
.filter(it => it.isActive && it.systemWebhookId && it.method === 'webhook')
|
||||
.map(it => it.systemWebhookId)
|
||||
.filter(isNotNull));
|
||||
for (const webhookId of recipientWebhookIds) {
|
||||
await Promise.all(
|
||||
abuseReports.map(it => {
|
||||
return this.systemWebhookService.enqueueSystemWebhook(
|
||||
webhookId,
|
||||
type,
|
||||
it,
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 通報の通知先一覧を取得する.
|
||||
*
|
||||
* @param {Object} [params] クエリの取得条件
|
||||
* @param {Object} [params.method] 取得する通知先の通知方法
|
||||
* @param {Object} [opts] 動作時の詳細なオプション
|
||||
* @param {boolean} [opts.removeUnauthorized] 副作用としてモデレータ権限を持たない送信先ユーザをDBから削除するかどうか(default: true)
|
||||
* @param {boolean} [opts.joinUser] 通知先のユーザ情報をJOINするかどうか(default: false)
|
||||
* @param {boolean} [opts.joinSystemWebhook] 通知先のSystemWebhook情報をJOINするかどうか(default: false)
|
||||
* @see removeUnauthorizedRecipientUsers
|
||||
*/
|
||||
@bindThis
|
||||
public async fetchRecipients(
|
||||
params?: {
|
||||
ids?: MiAbuseReportNotificationRecipient['id'][],
|
||||
method?: RecipientMethod[],
|
||||
},
|
||||
opts?: {
|
||||
removeUnauthorized?: boolean,
|
||||
joinUser?: boolean,
|
||||
joinSystemWebhook?: boolean,
|
||||
},
|
||||
): Promise<MiAbuseReportNotificationRecipient[]> {
|
||||
const query = this.abuseReportNotificationRecipientRepository.createQueryBuilder('recipient');
|
||||
|
||||
if (opts?.joinUser) {
|
||||
query.innerJoinAndSelect('user', 'user', 'recipient.userId = user.id');
|
||||
query.innerJoinAndSelect('recipient.userProfile', 'userProfile');
|
||||
}
|
||||
|
||||
if (opts?.joinSystemWebhook) {
|
||||
query.innerJoinAndSelect('recipient.systemWebhook', 'systemWebhook');
|
||||
}
|
||||
|
||||
if (params?.ids) {
|
||||
query.andWhere({ id: In(params.ids) });
|
||||
}
|
||||
|
||||
if (params?.method) {
|
||||
query.andWhere(new Brackets(qb => {
|
||||
if (params.method?.includes('email')) {
|
||||
qb.orWhere({ method: 'email', userId: Not(IsNull()) });
|
||||
}
|
||||
if (params.method?.includes('webhook')) {
|
||||
qb.orWhere({ method: 'webhook', userId: IsNull() });
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
const recipients = await query.getMany();
|
||||
if (recipients.length <= 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// アサイン有効期限切れはイベントで拾えないので、このタイミングでチェック及び削除(オプション)
|
||||
return (opts?.removeUnauthorized ?? true)
|
||||
? await this.removeUnauthorizedRecipientUsers(recipients)
|
||||
: recipients;
|
||||
}
|
||||
|
||||
/**
|
||||
* EMailの通知先一覧を取得する.
|
||||
* リレーション先の{@link MiUser}および{@link MiUserProfile}も同時に取得する.
|
||||
*
|
||||
* @param {Object} [opts]
|
||||
* @param {boolean} [opts.removeUnauthorized] 副作用としてモデレータ権限を持たない送信先ユーザをDBから削除するかどうか(default: true)
|
||||
* @see removeUnauthorizedRecipientUsers
|
||||
*/
|
||||
@bindThis
|
||||
public async fetchEMailRecipients(opts?: {
|
||||
removeUnauthorized?: boolean
|
||||
}): Promise<MiAbuseReportNotificationRecipient[]> {
|
||||
return this.fetchRecipients({ method: ['email'] }, { joinUser: true, ...opts });
|
||||
}
|
||||
|
||||
/**
|
||||
* Webhookの通知先一覧を取得する.
|
||||
* リレーション先の{@link MiSystemWebhook}も同時に取得する.
|
||||
*/
|
||||
@bindThis
|
||||
public fetchWebhookRecipients(): Promise<MiAbuseReportNotificationRecipient[]> {
|
||||
return this.fetchRecipients({ method: ['webhook'] }, { joinSystemWebhook: true });
|
||||
}
|
||||
|
||||
/**
|
||||
* 通知先を作成する.
|
||||
*/
|
||||
@bindThis
|
||||
public async createRecipient(
|
||||
params: {
|
||||
isActive: MiAbuseReportNotificationRecipient['isActive'];
|
||||
name: MiAbuseReportNotificationRecipient['name'];
|
||||
method: MiAbuseReportNotificationRecipient['method'];
|
||||
userId: MiAbuseReportNotificationRecipient['userId'];
|
||||
systemWebhookId: MiAbuseReportNotificationRecipient['systemWebhookId'];
|
||||
},
|
||||
updater: MiUser,
|
||||
): Promise<MiAbuseReportNotificationRecipient> {
|
||||
const id = this.idService.gen();
|
||||
await this.abuseReportNotificationRecipientRepository.insert({
|
||||
...params,
|
||||
id,
|
||||
});
|
||||
|
||||
const created = await this.abuseReportNotificationRecipientRepository.findOneByOrFail({ id: id });
|
||||
|
||||
this.moderationLogService
|
||||
.log(updater, 'createAbuseReportNotificationRecipient', {
|
||||
recipientId: id,
|
||||
recipient: created,
|
||||
})
|
||||
.then();
|
||||
|
||||
return created;
|
||||
}
|
||||
|
||||
/**
|
||||
* 通知先を更新する.
|
||||
*/
|
||||
@bindThis
|
||||
public async updateRecipient(
|
||||
params: {
|
||||
id: MiAbuseReportNotificationRecipient['id'];
|
||||
isActive: MiAbuseReportNotificationRecipient['isActive'];
|
||||
name: MiAbuseReportNotificationRecipient['name'];
|
||||
method: MiAbuseReportNotificationRecipient['method'];
|
||||
userId: MiAbuseReportNotificationRecipient['userId'];
|
||||
systemWebhookId: MiAbuseReportNotificationRecipient['systemWebhookId'];
|
||||
},
|
||||
updater: MiUser,
|
||||
): Promise<MiAbuseReportNotificationRecipient> {
|
||||
const beforeEntity = await this.abuseReportNotificationRecipientRepository.findOneByOrFail({ id: params.id });
|
||||
|
||||
await this.abuseReportNotificationRecipientRepository.update(params.id, {
|
||||
isActive: params.isActive,
|
||||
updatedAt: new Date(),
|
||||
name: params.name,
|
||||
method: params.method,
|
||||
userId: params.userId,
|
||||
systemWebhookId: params.systemWebhookId,
|
||||
});
|
||||
|
||||
const afterEntity = await this.abuseReportNotificationRecipientRepository.findOneByOrFail({ id: params.id });
|
||||
|
||||
this.moderationLogService
|
||||
.log(updater, 'updateAbuseReportNotificationRecipient', {
|
||||
recipientId: params.id,
|
||||
before: beforeEntity,
|
||||
after: afterEntity,
|
||||
})
|
||||
.then();
|
||||
|
||||
return afterEntity;
|
||||
}
|
||||
|
||||
/**
|
||||
* 通知先を削除する.
|
||||
*/
|
||||
@bindThis
|
||||
public async deleteRecipient(
|
||||
id: MiAbuseReportNotificationRecipient['id'],
|
||||
updater: MiUser,
|
||||
) {
|
||||
const entity = await this.abuseReportNotificationRecipientRepository.findBy({ id });
|
||||
|
||||
await this.abuseReportNotificationRecipientRepository.delete(id);
|
||||
|
||||
this.moderationLogService
|
||||
.log(updater, 'deleteAbuseReportNotificationRecipient', {
|
||||
recipientId: id,
|
||||
recipient: entity,
|
||||
})
|
||||
.then();
|
||||
}
|
||||
|
||||
/**
|
||||
* モデレータ権限を持たない(*1)通知先ユーザを削除する.
|
||||
*
|
||||
* *1: 以下の両方を満たすものの事を言う
|
||||
* - 通知先にユーザIDが設定されている
|
||||
* - 付与ロールにモデレータ権限がない or アサインの有効期限が切れている
|
||||
*
|
||||
* @param recipients 通知先一覧の配列
|
||||
* @returns {@lisk recipients}からモデレータ権限を持たない通知先を削除した配列
|
||||
*/
|
||||
@bindThis
|
||||
private async removeUnauthorizedRecipientUsers(recipients: MiAbuseReportNotificationRecipient[]): Promise<MiAbuseReportNotificationRecipient[]> {
|
||||
const userRecipients = recipients.filter(it => it.userId !== null);
|
||||
const recipientUserIds = new Set(userRecipients.map(it => it.userId).filter(isNotNull));
|
||||
if (recipientUserIds.size <= 0) {
|
||||
// ユーザが通知先として設定されていない場合、この関数での処理を行うべきレコードが無い
|
||||
return recipients;
|
||||
}
|
||||
|
||||
// モデレータ権限の有無で通知先設定を振り分ける
|
||||
const authorizedUserIds = await this.roleService.getModeratorIds(true, true);
|
||||
const authorizedUserRecipients = Array.of<MiAbuseReportNotificationRecipient>();
|
||||
const unauthorizedUserRecipients = Array.of<MiAbuseReportNotificationRecipient>();
|
||||
for (const recipient of userRecipients) {
|
||||
// eslint-disable-next-line
|
||||
if (authorizedUserIds.includes(recipient.userId!)) {
|
||||
authorizedUserRecipients.push(recipient);
|
||||
} else {
|
||||
unauthorizedUserRecipients.push(recipient);
|
||||
}
|
||||
}
|
||||
|
||||
// モデレータ権限を持たない通知先をDBから削除する
|
||||
if (unauthorizedUserRecipients.length > 0) {
|
||||
await this.abuseReportNotificationRecipientRepository.delete(unauthorizedUserRecipients.map(it => it.id));
|
||||
}
|
||||
const nonUserRecipients = recipients.filter(it => it.userId === null);
|
||||
return [...nonUserRecipients, ...authorizedUserRecipients].sort((a, b) => a.id.localeCompare(b.id));
|
||||
}
|
||||
|
||||
@bindThis
|
||||
private async onMessage(_: string, data: string): Promise<void> {
|
||||
const obj = JSON.parse(data);
|
||||
if (obj.channel !== 'internal') {
|
||||
return;
|
||||
}
|
||||
|
||||
const { type } = obj.message as GlobalEvents['internal']['payload'];
|
||||
switch (type) {
|
||||
case 'roleUpdated':
|
||||
case 'roleDeleted':
|
||||
case 'userRoleUnassigned': {
|
||||
// 場合によってはキャッシュ更新よりも先にここが呼ばれてしまう可能性があるのでnextTickで遅延実行
|
||||
process.nextTick(async () => {
|
||||
const recipients = await this.abuseReportNotificationRecipientRepository.findBy({
|
||||
userId: Not(IsNull()),
|
||||
});
|
||||
await this.removeUnauthorizedRecipientUsers(recipients);
|
||||
});
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public dispose(): void {
|
||||
this.redisForSub.off('message', this.onMessage);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public onApplicationShutdown(signal?: string | undefined): void {
|
||||
this.dispose();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,128 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { In } from 'typeorm';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import type { AbuseUserReportsRepository, MiAbuseUserReport, MiUser, UsersRepository } from '@/models/_.js';
|
||||
import { AbuseReportNotificationService } from '@/core/AbuseReportNotificationService.js';
|
||||
import { QueueService } from '@/core/QueueService.js';
|
||||
import { InstanceActorService } from '@/core/InstanceActorService.js';
|
||||
import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
|
||||
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||
import { IdService } from './IdService.js';
|
||||
|
||||
@Injectable()
|
||||
export class AbuseReportService {
|
||||
constructor(
|
||||
@Inject(DI.abuseUserReportsRepository)
|
||||
private abuseUserReportsRepository: AbuseUserReportsRepository,
|
||||
@Inject(DI.usersRepository)
|
||||
private usersRepository: UsersRepository,
|
||||
private idService: IdService,
|
||||
private abuseReportNotificationService: AbuseReportNotificationService,
|
||||
private queueService: QueueService,
|
||||
private instanceActorService: InstanceActorService,
|
||||
private apRendererService: ApRendererService,
|
||||
private moderationLogService: ModerationLogService,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* ユーザからの通報をDBに記録し、その内容を下記の手段で管理者各位に通知する.
|
||||
* - 管理者用Redisイベント
|
||||
* - EMail(モデレータ権限所有者ユーザ+metaテーブルに設定されているメールアドレス)
|
||||
* - SystemWebhook
|
||||
*
|
||||
* @param params 通報内容. もし複数件の通報に対応した時のために、あらかじめ複数件を処理できる前提で考える
|
||||
* @see AbuseReportNotificationService.notify
|
||||
*/
|
||||
@bindThis
|
||||
public async report(params: {
|
||||
targetUserId: MiAbuseUserReport['targetUserId'],
|
||||
targetUserHost: MiAbuseUserReport['targetUserHost'],
|
||||
reporterId: MiAbuseUserReport['reporterId'],
|
||||
reporterHost: MiAbuseUserReport['reporterHost'],
|
||||
comment: string,
|
||||
}[]) {
|
||||
const entities = params.map(param => {
|
||||
return {
|
||||
id: this.idService.gen(),
|
||||
targetUserId: param.targetUserId,
|
||||
targetUserHost: param.targetUserHost,
|
||||
reporterId: param.reporterId,
|
||||
reporterHost: param.reporterHost,
|
||||
comment: param.comment,
|
||||
};
|
||||
});
|
||||
|
||||
const reports = Array.of<MiAbuseUserReport>();
|
||||
for (const entity of entities) {
|
||||
const report = await this.abuseUserReportsRepository.insertOne(entity);
|
||||
reports.push(report);
|
||||
}
|
||||
|
||||
return Promise.all([
|
||||
this.abuseReportNotificationService.notifyAdminStream(reports),
|
||||
this.abuseReportNotificationService.notifySystemWebhook(reports, 'abuseReport'),
|
||||
this.abuseReportNotificationService.notifyMail(reports),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通報を解決し、その内容を下記の手段で管理者各位に通知する.
|
||||
* - SystemWebhook
|
||||
*
|
||||
* @param params 通報内容. もし複数件の通報に対応した時のために、あらかじめ複数件を処理できる前提で考える
|
||||
* @param operator 通報を処理したユーザ
|
||||
* @see AbuseReportNotificationService.notify
|
||||
*/
|
||||
@bindThis
|
||||
public async resolve(
|
||||
params: {
|
||||
reportId: string;
|
||||
forward: boolean;
|
||||
}[],
|
||||
operator: MiUser,
|
||||
) {
|
||||
const paramsMap = new Map(params.map(it => [it.reportId, it]));
|
||||
const reports = await this.abuseUserReportsRepository.findBy({
|
||||
id: In(params.map(it => it.reportId)),
|
||||
});
|
||||
|
||||
for (const report of reports) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const ps = paramsMap.get(report.id)!;
|
||||
|
||||
await this.abuseUserReportsRepository.update(report.id, {
|
||||
resolved: true,
|
||||
assigneeId: operator.id,
|
||||
forwarded: ps.forward && report.targetUserHost !== null,
|
||||
});
|
||||
|
||||
if (ps.forward && report.targetUserHost != null) {
|
||||
const actor = await this.instanceActorService.getInstanceActor();
|
||||
const targetUser = await this.usersRepository.findOneByOrFail({ id: report.targetUserId });
|
||||
|
||||
// eslint-disable-next-line
|
||||
const flag = this.apRendererService.renderFlag(actor, targetUser.uri!, report.comment);
|
||||
const contextAssignedFlag = this.apRendererService.addContext(flag);
|
||||
this.queueService.deliver(actor, contextAssignedFlag, targetUser.inbox, false);
|
||||
}
|
||||
|
||||
this.moderationLogService
|
||||
.log(operator, 'resolveAbuseReport', {
|
||||
reportId: report.id,
|
||||
report: report,
|
||||
forwarded: ps.forward && report.targetUserHost !== null,
|
||||
})
|
||||
.then();
|
||||
}
|
||||
|
||||
return this.abuseUserReportsRepository.findBy({ id: In(reports.map(it => it.id)) })
|
||||
.then(reports => this.abuseReportNotificationService.notifySystemWebhook(reports, 'abuseReportResolved'));
|
||||
}
|
||||
}
|
|
@ -67,7 +67,7 @@ export class AnnouncementService {
|
|||
|
||||
@bindThis
|
||||
public async create(values: Partial<MiAnnouncement>, moderator?: MiUser): Promise<{ raw: MiAnnouncement; packed: Packed<'Announcement'> }> {
|
||||
const announcement = await this.announcementsRepository.insert({
|
||||
const announcement = await this.announcementsRepository.insertOne({
|
||||
id: this.idService.gen(),
|
||||
updatedAt: null,
|
||||
title: values.title,
|
||||
|
@ -79,7 +79,7 @@ export class AnnouncementService {
|
|||
silence: values.silence,
|
||||
needConfirmationToRead: values.needConfirmationToRead,
|
||||
userId: values.userId,
|
||||
}).then(x => this.announcementsRepository.findOneByOrFail(x.identifiers[0]));
|
||||
});
|
||||
|
||||
const packed = await this.announcementEntityService.pack(announcement);
|
||||
|
||||
|
|
|
@ -55,10 +55,10 @@ export class AvatarDecorationService implements OnApplicationShutdown {
|
|||
|
||||
@bindThis
|
||||
public async create(options: Partial<MiAvatarDecoration>, moderator?: MiUser): Promise<MiAvatarDecoration> {
|
||||
const created = await this.avatarDecorationsRepository.insert({
|
||||
const created = await this.avatarDecorationsRepository.insertOne({
|
||||
id: this.idService.gen(),
|
||||
...options,
|
||||
}).then(x => this.avatarDecorationsRepository.findOneByOrFail(x.identifiers[0]));
|
||||
});
|
||||
|
||||
this.globalEventService.publishInternalEvent('avatarDecorationCreated', created);
|
||||
|
||||
|
|
|
@ -45,13 +45,13 @@ export class ClipService {
|
|||
throw new ClipService.TooManyClipsError();
|
||||
}
|
||||
|
||||
const clip = await this.clipsRepository.insert({
|
||||
const clip = await this.clipsRepository.insertOne({
|
||||
id: this.idService.gen(),
|
||||
userId: me.id,
|
||||
name: name,
|
||||
isPublic: isPublic,
|
||||
description: description,
|
||||
}).then(x => this.clipsRepository.findOneByOrFail(x.identifiers[0]));
|
||||
});
|
||||
|
||||
return clip;
|
||||
}
|
||||
|
|
|
@ -5,6 +5,13 @@
|
|||
|
||||
import { Module } from '@nestjs/common';
|
||||
import { FanoutTimelineEndpointService } from '@/core/FanoutTimelineEndpointService.js';
|
||||
import { AbuseReportService } from '@/core/AbuseReportService.js';
|
||||
import { SystemWebhookEntityService } from '@/core/entities/SystemWebhookEntityService.js';
|
||||
import {
|
||||
AbuseReportNotificationRecipientEntityService,
|
||||
} from '@/core/entities/AbuseReportNotificationRecipientEntityService.js';
|
||||
import { AbuseReportNotificationService } from '@/core/AbuseReportNotificationService.js';
|
||||
import { SystemWebhookService } from '@/core/SystemWebhookService.js';
|
||||
import { AccountMoveService } from './AccountMoveService.js';
|
||||
import { AccountUpdateService } from './AccountUpdateService.js';
|
||||
import { AiService } from './AiService.js';
|
||||
|
@ -57,7 +64,7 @@ import { UserMutingService } from './UserMutingService.js';
|
|||
import { UserSuspendService } from './UserSuspendService.js';
|
||||
import { UserAuthService } from './UserAuthService.js';
|
||||
import { VideoProcessingService } from './VideoProcessingService.js';
|
||||
import { WebhookService } from './WebhookService.js';
|
||||
import { UserWebhookService } from './UserWebhookService.js';
|
||||
import { ProxyAccountService } from './ProxyAccountService.js';
|
||||
import { UtilityService } from './UtilityService.js';
|
||||
import { FileInfoService } from './FileInfoService.js';
|
||||
|
@ -146,6 +153,8 @@ import type { Provider } from '@nestjs/common';
|
|||
|
||||
//#region 文字列ベースでのinjection用(循環参照対応のため)
|
||||
const $LoggerService: Provider = { provide: 'LoggerService', useExisting: LoggerService };
|
||||
const $AbuseReportService: Provider = { provide: 'AbuseReportService', useExisting: AbuseReportService };
|
||||
const $AbuseReportNotificationService: Provider = { provide: 'AbuseReportNotificationService', useExisting: AbuseReportNotificationService };
|
||||
const $AccountMoveService: Provider = { provide: 'AccountMoveService', useExisting: AccountMoveService };
|
||||
const $AccountUpdateService: Provider = { provide: 'AccountUpdateService', useExisting: AccountUpdateService };
|
||||
const $AiService: Provider = { provide: 'AiService', useExisting: AiService };
|
||||
|
@ -199,7 +208,8 @@ const $UserMutingService: Provider = { provide: 'UserMutingService', useExisting
|
|||
const $UserSuspendService: Provider = { provide: 'UserSuspendService', useExisting: UserSuspendService };
|
||||
const $UserAuthService: Provider = { provide: 'UserAuthService', useExisting: UserAuthService };
|
||||
const $VideoProcessingService: Provider = { provide: 'VideoProcessingService', useExisting: VideoProcessingService };
|
||||
const $WebhookService: Provider = { provide: 'WebhookService', useExisting: WebhookService };
|
||||
const $UserWebhookService: Provider = { provide: 'UserWebhookService', useExisting: UserWebhookService };
|
||||
const $SystemWebhookService: Provider = { provide: 'SystemWebhookService', useExisting: SystemWebhookService };
|
||||
const $UtilityService: Provider = { provide: 'UtilityService', useExisting: UtilityService };
|
||||
const $FileInfoService: Provider = { provide: 'FileInfoService', useExisting: FileInfoService };
|
||||
const $SearchService: Provider = { provide: 'SearchService', useExisting: SearchService };
|
||||
|
@ -228,6 +238,7 @@ const $ChartManagementService: Provider = { provide: 'ChartManagementService', u
|
|||
|
||||
const $AbuseUserReportEntityService: Provider = { provide: 'AbuseUserReportEntityService', useExisting: AbuseUserReportEntityService };
|
||||
const $AnnouncementEntityService: Provider = { provide: 'AnnouncementEntityService', useExisting: AnnouncementEntityService };
|
||||
const $AbuseReportNotificationRecipientEntityService: Provider = { provide: 'AbuseReportNotificationRecipientEntityService', useExisting: AbuseReportNotificationRecipientEntityService };
|
||||
const $AntennaEntityService: Provider = { provide: 'AntennaEntityService', useExisting: AntennaEntityService };
|
||||
const $AppEntityService: Provider = { provide: 'AppEntityService', useExisting: AppEntityService };
|
||||
const $AuthSessionEntityService: Provider = { provide: 'AuthSessionEntityService', useExisting: AuthSessionEntityService };
|
||||
|
@ -262,6 +273,7 @@ const $FlashLikeEntityService: Provider = { provide: 'FlashLikeEntityService', u
|
|||
const $RoleEntityService: Provider = { provide: 'RoleEntityService', useExisting: RoleEntityService };
|
||||
const $ReversiGameEntityService: Provider = { provide: 'ReversiGameEntityService', useExisting: ReversiGameEntityService };
|
||||
const $MetaEntityService: Provider = { provide: 'MetaEntityService', useExisting: MetaEntityService };
|
||||
const $SystemWebhookEntityService: Provider = { provide: 'SystemWebhookEntityService', useExisting: SystemWebhookEntityService };
|
||||
|
||||
const $ApAudienceService: Provider = { provide: 'ApAudienceService', useExisting: ApAudienceService };
|
||||
const $ApDbResolverService: Provider = { provide: 'ApDbResolverService', useExisting: ApDbResolverService };
|
||||
|
@ -289,6 +301,8 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||
],
|
||||
providers: [
|
||||
LoggerService,
|
||||
AbuseReportService,
|
||||
AbuseReportNotificationService,
|
||||
AccountMoveService,
|
||||
AccountUpdateService,
|
||||
AiService,
|
||||
|
@ -342,7 +356,8 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||
UserSuspendService,
|
||||
UserAuthService,
|
||||
VideoProcessingService,
|
||||
WebhookService,
|
||||
UserWebhookService,
|
||||
SystemWebhookService,
|
||||
UtilityService,
|
||||
FileInfoService,
|
||||
SearchService,
|
||||
|
@ -371,6 +386,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||
|
||||
AbuseUserReportEntityService,
|
||||
AnnouncementEntityService,
|
||||
AbuseReportNotificationRecipientEntityService,
|
||||
AntennaEntityService,
|
||||
AppEntityService,
|
||||
AuthSessionEntityService,
|
||||
|
@ -405,6 +421,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||
RoleEntityService,
|
||||
ReversiGameEntityService,
|
||||
MetaEntityService,
|
||||
SystemWebhookEntityService,
|
||||
|
||||
ApAudienceService,
|
||||
ApDbResolverService,
|
||||
|
@ -428,6 +445,8 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||
|
||||
//#region 文字列ベースでのinjection用(循環参照対応のため)
|
||||
$LoggerService,
|
||||
$AbuseReportService,
|
||||
$AbuseReportNotificationService,
|
||||
$AccountMoveService,
|
||||
$AccountUpdateService,
|
||||
$AiService,
|
||||
|
@ -481,7 +500,8 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||
$UserSuspendService,
|
||||
$UserAuthService,
|
||||
$VideoProcessingService,
|
||||
$WebhookService,
|
||||
$UserWebhookService,
|
||||
$SystemWebhookService,
|
||||
$UtilityService,
|
||||
$FileInfoService,
|
||||
$SearchService,
|
||||
|
@ -510,6 +530,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||
|
||||
$AbuseUserReportEntityService,
|
||||
$AnnouncementEntityService,
|
||||
$AbuseReportNotificationRecipientEntityService,
|
||||
$AntennaEntityService,
|
||||
$AppEntityService,
|
||||
$AuthSessionEntityService,
|
||||
|
@ -544,6 +565,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||
$RoleEntityService,
|
||||
$ReversiGameEntityService,
|
||||
$MetaEntityService,
|
||||
$SystemWebhookEntityService,
|
||||
|
||||
$ApAudienceService,
|
||||
$ApDbResolverService,
|
||||
|
@ -568,6 +590,8 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||
exports: [
|
||||
QueueModule,
|
||||
LoggerService,
|
||||
AbuseReportService,
|
||||
AbuseReportNotificationService,
|
||||
AccountMoveService,
|
||||
AccountUpdateService,
|
||||
AiService,
|
||||
|
@ -621,7 +645,8 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||
UserSuspendService,
|
||||
UserAuthService,
|
||||
VideoProcessingService,
|
||||
WebhookService,
|
||||
UserWebhookService,
|
||||
SystemWebhookService,
|
||||
UtilityService,
|
||||
FileInfoService,
|
||||
SearchService,
|
||||
|
@ -649,6 +674,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||
|
||||
AbuseUserReportEntityService,
|
||||
AnnouncementEntityService,
|
||||
AbuseReportNotificationRecipientEntityService,
|
||||
AntennaEntityService,
|
||||
AppEntityService,
|
||||
AuthSessionEntityService,
|
||||
|
@ -683,6 +709,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||
RoleEntityService,
|
||||
ReversiGameEntityService,
|
||||
MetaEntityService,
|
||||
SystemWebhookEntityService,
|
||||
|
||||
ApAudienceService,
|
||||
ApDbResolverService,
|
||||
|
@ -706,6 +733,8 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||
|
||||
//#region 文字列ベースでのinjection用(循環参照対応のため)
|
||||
$LoggerService,
|
||||
$AbuseReportService,
|
||||
$AbuseReportNotificationService,
|
||||
$AccountMoveService,
|
||||
$AccountUpdateService,
|
||||
$AiService,
|
||||
|
@ -759,7 +788,8 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||
$UserSuspendService,
|
||||
$UserAuthService,
|
||||
$VideoProcessingService,
|
||||
$WebhookService,
|
||||
$UserWebhookService,
|
||||
$SystemWebhookService,
|
||||
$UtilityService,
|
||||
$FileInfoService,
|
||||
$SearchService,
|
||||
|
@ -787,6 +817,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||
|
||||
$AbuseUserReportEntityService,
|
||||
$AnnouncementEntityService,
|
||||
$AbuseReportNotificationRecipientEntityService,
|
||||
$AntennaEntityService,
|
||||
$AppEntityService,
|
||||
$AuthSessionEntityService,
|
||||
|
@ -821,6 +852,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||
$RoleEntityService,
|
||||
$ReversiGameEntityService,
|
||||
$MetaEntityService,
|
||||
$SystemWebhookEntityService,
|
||||
|
||||
$ApAudienceService,
|
||||
$ApDbResolverService,
|
||||
|
|
|
@ -106,7 +106,7 @@ export class CustomEmojiService implements OnApplicationShutdown {
|
|||
localOnly: boolean;
|
||||
roleIdsThatCanBeUsedThisEmojiAsReaction: MiRole['id'][];
|
||||
}, moderator?: MiUser): Promise<MiEmoji> {
|
||||
const emoji = await this.emojisRepository.insert({
|
||||
const emoji = await this.emojisRepository.insertOne({
|
||||
id: this.idService.gen(),
|
||||
updatedAt: new Date(),
|
||||
name: data.name,
|
||||
|
@ -120,7 +120,7 @@ export class CustomEmojiService implements OnApplicationShutdown {
|
|||
isSensitive: data.isSensitive,
|
||||
localOnly: data.localOnly,
|
||||
roleIdsThatCanBeUsedThisEmojiAsReaction: data.roleIdsThatCanBeUsedThisEmojiAsReaction,
|
||||
}).then(x => this.emojisRepository.findOneByOrFail(x.identifiers[0]));
|
||||
});
|
||||
|
||||
if (data.host == null) {
|
||||
this.localEmojisCache.refresh();
|
||||
|
|
|
@ -220,7 +220,7 @@ export class DriveService {
|
|||
file.size = size;
|
||||
file.storedInternal = false;
|
||||
|
||||
return await this.driveFilesRepository.insert(file).then(x => this.driveFilesRepository.findOneByOrFail(x.identifiers[0]));
|
||||
return await this.driveFilesRepository.insertOne(file);
|
||||
} else { // use internal storage
|
||||
const accessKey = randomUUID();
|
||||
const thumbnailAccessKey = 'thumbnail-' + randomUUID();
|
||||
|
@ -254,7 +254,7 @@ export class DriveService {
|
|||
file.md5 = hash;
|
||||
file.size = size;
|
||||
|
||||
return await this.driveFilesRepository.insert(file).then(x => this.driveFilesRepository.findOneByOrFail(x.identifiers[0]));
|
||||
return await this.driveFilesRepository.insertOne(file);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -615,7 +615,7 @@ export class DriveService {
|
|||
file.type = info.type.mime;
|
||||
file.storedInternal = false;
|
||||
|
||||
file = await this.driveFilesRepository.insert(file).then(x => this.driveFilesRepository.findOneByOrFail(x.identifiers[0]));
|
||||
file = await this.driveFilesRepository.insertOne(file);
|
||||
} catch (err) {
|
||||
// duplicate key error (when already registered)
|
||||
if (isDuplicateKeyValueError(err)) {
|
||||
|
|
|
@ -16,6 +16,7 @@ import type { UserProfilesRepository } from '@/models/_.js';
|
|||
import { LoggerService } from '@/core/LoggerService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { HttpRequestService } from '@/core/HttpRequestService.js';
|
||||
import { QueueService } from '@/core/QueueService.js';
|
||||
|
||||
@Injectable()
|
||||
export class EmailService {
|
||||
|
@ -32,6 +33,7 @@ export class EmailService {
|
|||
private loggerService: LoggerService,
|
||||
private utilityService: UtilityService,
|
||||
private httpRequestService: HttpRequestService,
|
||||
private queueService: QueueService,
|
||||
) {
|
||||
this.logger = this.loggerService.getLogger('email');
|
||||
}
|
||||
|
|
|
@ -55,11 +55,11 @@ export class FederatedInstanceService implements OnApplicationShutdown {
|
|||
const index = await this.instancesRepository.findOneBy({ host });
|
||||
|
||||
if (index == null) {
|
||||
const i = await this.instancesRepository.insert({
|
||||
const i = await this.instancesRepository.insertOne({
|
||||
id: this.idService.gen(),
|
||||
host,
|
||||
firstRetrievedAt: new Date(),
|
||||
}).then(x => this.instancesRepository.findOneByOrFail(x.identifiers[0]));
|
||||
});
|
||||
|
||||
this.federatedInstanceCache.set(host, i);
|
||||
return i;
|
||||
|
|
|
@ -18,6 +18,7 @@ import type { MiAbuseUserReport } from '@/models/AbuseUserReport.js';
|
|||
import type { MiSignin } from '@/models/Signin.js';
|
||||
import type { MiPage } from '@/models/Page.js';
|
||||
import type { MiWebhook } from '@/models/Webhook.js';
|
||||
import type { MiSystemWebhook } from '@/models/SystemWebhook.js';
|
||||
import type { MiMeta } from '@/models/Meta.js';
|
||||
import { MiAvatarDecoration, MiReversiGame, MiRole, MiRoleAssignment } from '@/models/_.js';
|
||||
import type { Packed } from '@/misc/json-schema.js';
|
||||
|
@ -229,6 +230,9 @@ export interface InternalEventTypes {
|
|||
webhookCreated: MiWebhook;
|
||||
webhookDeleted: MiWebhook;
|
||||
webhookUpdated: MiWebhook;
|
||||
systemWebhookCreated: MiSystemWebhook;
|
||||
systemWebhookDeleted: MiSystemWebhook;
|
||||
systemWebhookUpdated: MiSystemWebhook;
|
||||
antennaCreated: MiAntenna;
|
||||
antennaDeleted: MiAntenna;
|
||||
antennaUpdated: MiAntenna;
|
||||
|
|
|
@ -15,7 +15,7 @@ export class LoggerService {
|
|||
}
|
||||
|
||||
@bindThis
|
||||
public getLogger(domain: string, color?: KEYWORD | undefined, store?: boolean) {
|
||||
return new Logger(domain, color, store);
|
||||
public getLogger(domain: string, color?: KEYWORD | undefined) {
|
||||
return new Logger(domain, color);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,7 +35,7 @@ import InstanceChart from '@/core/chart/charts/instance.js';
|
|||
import ActiveUsersChart from '@/core/chart/charts/active-users.js';
|
||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||
import { NotificationService } from '@/core/NotificationService.js';
|
||||
import { WebhookService } from '@/core/WebhookService.js';
|
||||
import { UserWebhookService } from '@/core/UserWebhookService.js';
|
||||
import { HashtagService } from '@/core/HashtagService.js';
|
||||
import { AntennaService } from '@/core/AntennaService.js';
|
||||
import { QueueService } from '@/core/QueueService.js';
|
||||
|
@ -205,7 +205,7 @@ export class NoteCreateService implements OnApplicationShutdown {
|
|||
private federatedInstanceService: FederatedInstanceService,
|
||||
private hashtagService: HashtagService,
|
||||
private antennaService: AntennaService,
|
||||
private webhookService: WebhookService,
|
||||
private webhookService: UserWebhookService,
|
||||
private featuredService: FeaturedService,
|
||||
private remoteUserResolveService: RemoteUserResolveService,
|
||||
private apDeliverManagerService: ApDeliverManagerService,
|
||||
|
@ -653,7 +653,7 @@ export class NoteCreateService implements OnApplicationShutdown {
|
|||
this.webhookService.getActiveWebhooks().then(webhooks => {
|
||||
webhooks = webhooks.filter(x => x.userId === user.id && x.on.includes('note'));
|
||||
for (const webhook of webhooks) {
|
||||
this.queueService.webhookDeliver(webhook, 'note', {
|
||||
this.queueService.userWebhookDeliver(webhook, 'note', {
|
||||
note: noteObj,
|
||||
});
|
||||
}
|
||||
|
@ -680,7 +680,7 @@ export class NoteCreateService implements OnApplicationShutdown {
|
|||
|
||||
const webhooks = (await this.webhookService.getActiveWebhooks()).filter(x => x.userId === data.reply!.userId && x.on.includes('reply'));
|
||||
for (const webhook of webhooks) {
|
||||
this.queueService.webhookDeliver(webhook, 'reply', {
|
||||
this.queueService.userWebhookDeliver(webhook, 'reply', {
|
||||
note: noteObj,
|
||||
});
|
||||
}
|
||||
|
@ -703,7 +703,7 @@ export class NoteCreateService implements OnApplicationShutdown {
|
|||
|
||||
const webhooks = (await this.webhookService.getActiveWebhooks()).filter(x => x.userId === data.renote!.userId && x.on.includes('renote'));
|
||||
for (const webhook of webhooks) {
|
||||
this.queueService.webhookDeliver(webhook, 'renote', {
|
||||
this.queueService.userWebhookDeliver(webhook, 'renote', {
|
||||
note: noteObj,
|
||||
});
|
||||
}
|
||||
|
@ -835,7 +835,7 @@ export class NoteCreateService implements OnApplicationShutdown {
|
|||
|
||||
const webhooks = (await this.webhookService.getActiveWebhooks()).filter(x => x.userId === u.id && x.on.includes('mention'));
|
||||
for (const webhook of webhooks) {
|
||||
this.queueService.webhookDeliver(webhook, 'mention', {
|
||||
this.queueService.userWebhookDeliver(webhook, 'mention', {
|
||||
note: detailPackedNote,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -7,10 +7,17 @@ import { Inject, Module, OnApplicationShutdown } from '@nestjs/common';
|
|||
import * as Bull from 'bullmq';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { Config } from '@/config.js';
|
||||
import { QUEUE, baseQueueOptions } from '@/queue/const.js';
|
||||
import { baseQueueOptions, QUEUE } from '@/queue/const.js';
|
||||
import { allSettled } from '@/misc/promise-tracker.js';
|
||||
import {
|
||||
DeliverJobData,
|
||||
EndedPollNotificationJobData,
|
||||
InboxJobData,
|
||||
RelationshipJobData,
|
||||
UserWebhookDeliverJobData,
|
||||
SystemWebhookDeliverJobData,
|
||||
} from '../queue/types.js';
|
||||
import type { Provider } from '@nestjs/common';
|
||||
import type { DeliverJobData, InboxJobData, EndedPollNotificationJobData, WebhookDeliverJobData, RelationshipJobData, ScheduleNotePostJobData } from '../queue/types.js';
|
||||
|
||||
export type SystemQueue = Bull.Queue<Record<string, unknown>>;
|
||||
export type EndedPollNotificationQueue = Bull.Queue<EndedPollNotificationJobData>;
|
||||
|
@ -20,7 +27,8 @@ export type InboxQueue = Bull.Queue<InboxJobData>;
|
|||
export type DbQueue = Bull.Queue;
|
||||
export type RelationshipQueue = Bull.Queue<RelationshipJobData>;
|
||||
export type ObjectStorageQueue = Bull.Queue;
|
||||
export type WebhookDeliverQueue = Bull.Queue<WebhookDeliverJobData>;
|
||||
export type UserWebhookDeliverQueue = Bull.Queue<UserWebhookDeliverJobData>;
|
||||
export type SystemWebhookDeliverQueue = Bull.Queue<SystemWebhookDeliverJobData>;
|
||||
|
||||
const $system: Provider = {
|
||||
provide: 'queue:system',
|
||||
|
@ -70,9 +78,15 @@ const $objectStorage: Provider = {
|
|||
inject: [DI.config],
|
||||
};
|
||||
|
||||
const $webhookDeliver: Provider = {
|
||||
provide: 'queue:webhookDeliver',
|
||||
useFactory: (config: Config) => new Bull.Queue(QUEUE.WEBHOOK_DELIVER, baseQueueOptions(config, QUEUE.WEBHOOK_DELIVER)),
|
||||
const $userWebhookDeliver: Provider = {
|
||||
provide: 'queue:userWebhookDeliver',
|
||||
useFactory: (config: Config) => new Bull.Queue(QUEUE.USER_WEBHOOK_DELIVER, baseQueueOptions(config, QUEUE.USER_WEBHOOK_DELIVER)),
|
||||
inject: [DI.config],
|
||||
};
|
||||
|
||||
const $systemWebhookDeliver: Provider = {
|
||||
provide: 'queue:systemWebhookDeliver',
|
||||
useFactory: (config: Config) => new Bull.Queue(QUEUE.SYSTEM_WEBHOOK_DELIVER, baseQueueOptions(config, QUEUE.SYSTEM_WEBHOOK_DELIVER)),
|
||||
inject: [DI.config],
|
||||
};
|
||||
|
||||
|
@ -88,7 +102,8 @@ const $webhookDeliver: Provider = {
|
|||
$db,
|
||||
$relationship,
|
||||
$objectStorage,
|
||||
$webhookDeliver,
|
||||
$userWebhookDeliver,
|
||||
$systemWebhookDeliver,
|
||||
],
|
||||
exports: [
|
||||
$system,
|
||||
|
@ -99,7 +114,8 @@ const $webhookDeliver: Provider = {
|
|||
$db,
|
||||
$relationship,
|
||||
$objectStorage,
|
||||
$webhookDeliver,
|
||||
$userWebhookDeliver,
|
||||
$systemWebhookDeliver,
|
||||
],
|
||||
})
|
||||
export class QueueModule implements OnApplicationShutdown {
|
||||
|
@ -112,7 +128,8 @@ export class QueueModule implements OnApplicationShutdown {
|
|||
@Inject('queue:db') public dbQueue: DbQueue,
|
||||
@Inject('queue:relationship') public relationshipQueue: RelationshipQueue,
|
||||
@Inject('queue:objectStorage') public objectStorageQueue: ObjectStorageQueue,
|
||||
@Inject('queue:webhookDeliver') public webhookDeliverQueue: WebhookDeliverQueue,
|
||||
@Inject('queue:userWebhookDeliver') public userWebhookDeliverQueue: UserWebhookDeliverQueue,
|
||||
@Inject('queue:systemWebhookDeliver') public systemWebhookDeliverQueue: SystemWebhookDeliverQueue,
|
||||
) {}
|
||||
|
||||
public async dispose(): Promise<void> {
|
||||
|
@ -128,7 +145,8 @@ export class QueueModule implements OnApplicationShutdown {
|
|||
this.dbQueue.close(),
|
||||
this.relationshipQueue.close(),
|
||||
this.objectStorageQueue.close(),
|
||||
this.webhookDeliverQueue.close(),
|
||||
this.userWebhookDeliverQueue.close(),
|
||||
this.systemWebhookDeliverQueue.close(),
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
|
@ -8,15 +8,35 @@ import { Inject, Injectable } from '@nestjs/common';
|
|||
import type { IActivity } from '@/core/activitypub/type.js';
|
||||
import type { MiDriveFile } from '@/models/DriveFile.js';
|
||||
import type { MiWebhook, webhookEventTypes } from '@/models/Webhook.js';
|
||||
import type { MiSystemWebhook, SystemWebhookEventType } from '@/models/SystemWebhook.js';
|
||||
import type { Config } from '@/config.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import type { Antenna } from '@/server/api/endpoints/i/import-antennas.js';
|
||||
import type { DbQueue, DeliverQueue, EndedPollNotificationQueue, InboxQueue, ObjectStorageQueue, RelationshipQueue, SystemQueue, WebhookDeliverQueue, ScheduleNotePostQueue } from './QueueModule.js';
|
||||
import type { DbJobData, DeliverJobData, RelationshipJobData, ThinUser } from '../queue/types.js';
|
||||
import { ApRequestCreator } from '@/core/activitypub/ApRequestService.js';
|
||||
|
||||
import type {
|
||||
DbJobData,
|
||||
DeliverJobData,
|
||||
RelationshipJobData,
|
||||
SystemWebhookDeliverJobData,
|
||||
ThinUser,
|
||||
UserWebhookDeliverJobData,
|
||||
} from '../queue/types.js';
|
||||
import type {
|
||||
DbQueue,
|
||||
DeliverQueue,
|
||||
EndedPollNotificationQueue,
|
||||
InboxQueue,
|
||||
ObjectStorageQueue,
|
||||
RelationshipQueue,
|
||||
SystemQueue,
|
||||
UserWebhookDeliverQueue,
|
||||
SystemWebhookDeliverQueue,
|
||||
ScheduleNotePostQueue
|
||||
} from './QueueModule.js';
|
||||
import type httpSignature from '@peertube/http-signature';
|
||||
import type * as Bull from 'bullmq';
|
||||
import { ApRequestCreator } from '@/core/activitypub/ApRequestService.js';
|
||||
|
||||
@Injectable()
|
||||
export class QueueService {
|
||||
|
@ -32,7 +52,8 @@ export class QueueService {
|
|||
@Inject('queue:db') public dbQueue: DbQueue,
|
||||
@Inject('queue:relationship') public relationshipQueue: RelationshipQueue,
|
||||
@Inject('queue:objectStorage') public objectStorageQueue: ObjectStorageQueue,
|
||||
@Inject('queue:webhookDeliver') public webhookDeliverQueue: WebhookDeliverQueue,
|
||||
@Inject('queue:userWebhookDeliver') public userWebhookDeliverQueue: UserWebhookDeliverQueue,
|
||||
@Inject('queue:systemWebhookDeliver') public systemWebhookDeliverQueue: SystemWebhookDeliverQueue,
|
||||
) {
|
||||
this.systemQueue.add('tickCharts', {
|
||||
}, {
|
||||
|
@ -432,9 +453,13 @@ export class QueueService {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @see UserWebhookDeliverJobData
|
||||
* @see WebhookDeliverProcessorService
|
||||
*/
|
||||
@bindThis
|
||||
public webhookDeliver(webhook: MiWebhook, type: typeof webhookEventTypes[number], content: unknown) {
|
||||
const data = {
|
||||
public userWebhookDeliver(webhook: MiWebhook, type: typeof webhookEventTypes[number], content: unknown) {
|
||||
const data: UserWebhookDeliverJobData = {
|
||||
type,
|
||||
content,
|
||||
webhookId: webhook.id,
|
||||
|
@ -445,7 +470,33 @@ export class QueueService {
|
|||
eventId: randomUUID(),
|
||||
};
|
||||
|
||||
return this.webhookDeliverQueue.add(webhook.id, data, {
|
||||
return this.userWebhookDeliverQueue.add(webhook.id, data, {
|
||||
attempts: 4,
|
||||
backoff: {
|
||||
type: 'custom',
|
||||
},
|
||||
removeOnComplete: true,
|
||||
removeOnFail: true,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @see SystemWebhookDeliverJobData
|
||||
* @see WebhookDeliverProcessorService
|
||||
*/
|
||||
@bindThis
|
||||
public systemWebhookDeliver(webhook: MiSystemWebhook, type: SystemWebhookEventType, content: unknown) {
|
||||
const data: SystemWebhookDeliverJobData = {
|
||||
type,
|
||||
content,
|
||||
webhookId: webhook.id,
|
||||
to: webhook.url,
|
||||
secret: webhook.secret,
|
||||
createdAt: Date.now(),
|
||||
eventId: randomUUID(),
|
||||
};
|
||||
|
||||
return this.systemWebhookDeliverQueue.add(webhook.id, data, {
|
||||
attempts: 4,
|
||||
backoff: {
|
||||
type: 'custom',
|
||||
|
|
|
@ -53,11 +53,11 @@ export class RelayService {
|
|||
|
||||
@bindThis
|
||||
public async addRelay(inbox: string): Promise<MiRelay> {
|
||||
const relay = await this.relaysRepository.insert({
|
||||
const relay = await this.relaysRepository.insertOne({
|
||||
id: this.idService.gen(),
|
||||
inbox,
|
||||
status: 'requesting',
|
||||
}).then(x => this.relaysRepository.findOneByOrFail(x.identifiers[0]));
|
||||
});
|
||||
|
||||
const relayActor = await this.getRelayActor();
|
||||
const follow = await this.apRendererService.renderFollowRelay(relay, relayActor);
|
||||
|
|
|
@ -281,7 +281,7 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit {
|
|||
|
||||
@bindThis
|
||||
private async matched(parentId: MiUser['id'], childId: MiUser['id'], options: { noIrregularRules: boolean; }): Promise<MiReversiGame> {
|
||||
const game = await this.reversiGamesRepository.insert({
|
||||
const game = await this.reversiGamesRepository.insertOne({
|
||||
id: this.idService.gen(),
|
||||
user1Id: parentId,
|
||||
user2Id: childId,
|
||||
|
@ -294,10 +294,7 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit {
|
|||
bw: 'random',
|
||||
isLlotheo: false,
|
||||
noIrregularRules: options.noIrregularRules,
|
||||
}).then(x => this.reversiGamesRepository.findOneOrFail({
|
||||
where: { id: x.identifiers[0].id },
|
||||
relations: ['user1', 'user2'],
|
||||
}));
|
||||
}, { relations: ['user1', 'user2'] });
|
||||
this.cacheGame(game);
|
||||
|
||||
const packed = await this.reversiGameEntityService.packDetail(game);
|
||||
|
|
|
@ -429,14 +429,32 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
|
|||
}
|
||||
|
||||
@bindThis
|
||||
public async getModeratorIds(includeAdmins = true): Promise<MiUser['id'][]> {
|
||||
public async getModeratorIds(includeAdmins = true, excludeExpire = false): Promise<MiUser['id'][]> {
|
||||
const roles = await this.rolesCache.fetch(() => this.rolesRepository.findBy({}));
|
||||
const moderatorRoles = includeAdmins ? roles.filter(r => r.isModerator || r.isAdministrator) : roles.filter(r => r.isModerator);
|
||||
const assigns = moderatorRoles.length > 0 ? await this.roleAssignmentsRepository.findBy({
|
||||
roleId: In(moderatorRoles.map(r => r.id)),
|
||||
}) : [];
|
||||
const moderatorRoles = includeAdmins
|
||||
? roles.filter(r => r.isModerator || r.isAdministrator)
|
||||
: roles.filter(r => r.isModerator);
|
||||
|
||||
// TODO: isRootなアカウントも含める
|
||||
return assigns.map(a => a.userId);
|
||||
const assigns = moderatorRoles.length > 0
|
||||
? await this.roleAssignmentsRepository.findBy({ roleId: In(moderatorRoles.map(r => r.id)) })
|
||||
: [];
|
||||
|
||||
const now = Date.now();
|
||||
const result = [
|
||||
// Setを経由して重複を除去(ユーザIDは重複する可能性があるので)
|
||||
...new Set(
|
||||
assigns
|
||||
.filter(it =>
|
||||
(excludeExpire)
|
||||
? (it.expiresAt == null || it.expiresAt.getTime() > now)
|
||||
: true,
|
||||
)
|
||||
.map(a => a.userId),
|
||||
),
|
||||
];
|
||||
|
||||
return result.sort((x, y) => x.localeCompare(y));
|
||||
}
|
||||
|
||||
@bindThis
|
||||
|
@ -490,12 +508,12 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
|
|||
}
|
||||
}
|
||||
|
||||
const created = await this.roleAssignmentsRepository.insert({
|
||||
const created = await this.roleAssignmentsRepository.insertOne({
|
||||
id: this.idService.gen(now),
|
||||
expiresAt: expiresAt,
|
||||
roleId: roleId,
|
||||
userId: userId,
|
||||
}).then(x => this.roleAssignmentsRepository.findOneByOrFail(x.identifiers[0]));
|
||||
});
|
||||
|
||||
this.rolesRepository.update(roleId, {
|
||||
lastUsedAt: new Date(),
|
||||
|
@ -577,7 +595,7 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
|
|||
@bindThis
|
||||
public async create(values: Partial<MiRole>, moderator?: MiUser): Promise<MiRole> {
|
||||
const date = new Date();
|
||||
const created = await this.rolesRepository.insert({
|
||||
const created = await this.rolesRepository.insertOne({
|
||||
id: this.idService.gen(date.getTime()),
|
||||
updatedAt: date,
|
||||
lastUsedAt: date,
|
||||
|
@ -595,7 +613,7 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
|
|||
canEditMembersByModerator: values.canEditMembersByModerator,
|
||||
displayOrder: values.displayOrder,
|
||||
policies: values.policies,
|
||||
}).then(x => this.rolesRepository.findOneByOrFail(x.identifiers[0]));
|
||||
});
|
||||
|
||||
this.globalEventService.publishInternalEvent('roleCreated', created);
|
||||
|
||||
|
|
|
@ -0,0 +1,233 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import * as Redis from 'ioredis';
|
||||
import type { MiUser, SystemWebhooksRepository } from '@/models/_.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { GlobalEvents, GlobalEventService } from '@/core/GlobalEventService.js';
|
||||
import { MiSystemWebhook, type SystemWebhookEventType } from '@/models/SystemWebhook.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import { QueueService } from '@/core/QueueService.js';
|
||||
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||
import { LoggerService } from '@/core/LoggerService.js';
|
||||
import Logger from '@/logger.js';
|
||||
import type { OnApplicationShutdown } from '@nestjs/common';
|
||||
|
||||
@Injectable()
|
||||
export class SystemWebhookService implements OnApplicationShutdown {
|
||||
private logger: Logger;
|
||||
private activeSystemWebhooksFetched = false;
|
||||
private activeSystemWebhooks: MiSystemWebhook[] = [];
|
||||
|
||||
constructor(
|
||||
@Inject(DI.redisForSub)
|
||||
private redisForSub: Redis.Redis,
|
||||
@Inject(DI.systemWebhooksRepository)
|
||||
private systemWebhooksRepository: SystemWebhooksRepository,
|
||||
private idService: IdService,
|
||||
private queueService: QueueService,
|
||||
private moderationLogService: ModerationLogService,
|
||||
private loggerService: LoggerService,
|
||||
private globalEventService: GlobalEventService,
|
||||
) {
|
||||
this.redisForSub.on('message', this.onMessage);
|
||||
this.logger = this.loggerService.getLogger('webhook');
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async fetchActiveSystemWebhooks() {
|
||||
if (!this.activeSystemWebhooksFetched) {
|
||||
this.activeSystemWebhooks = await this.systemWebhooksRepository.findBy({
|
||||
isActive: true,
|
||||
});
|
||||
this.activeSystemWebhooksFetched = true;
|
||||
}
|
||||
|
||||
return this.activeSystemWebhooks;
|
||||
}
|
||||
|
||||
/**
|
||||
* SystemWebhook の一覧を取得する.
|
||||
*/
|
||||
@bindThis
|
||||
public async fetchSystemWebhooks(params?: {
|
||||
ids?: MiSystemWebhook['id'][];
|
||||
isActive?: MiSystemWebhook['isActive'];
|
||||
on?: MiSystemWebhook['on'];
|
||||
}): Promise<MiSystemWebhook[]> {
|
||||
const query = this.systemWebhooksRepository.createQueryBuilder('systemWebhook');
|
||||
if (params) {
|
||||
if (params.ids && params.ids.length > 0) {
|
||||
query.andWhere('systemWebhook.id IN (:...ids)', { ids: params.ids });
|
||||
}
|
||||
if (params.isActive !== undefined) {
|
||||
query.andWhere('systemWebhook.isActive = :isActive', { isActive: params.isActive });
|
||||
}
|
||||
if (params.on && params.on.length > 0) {
|
||||
query.andWhere(':on <@ systemWebhook.on', { on: params.on });
|
||||
}
|
||||
}
|
||||
|
||||
return query.getMany();
|
||||
}
|
||||
|
||||
/**
|
||||
* SystemWebhook を作成する.
|
||||
*/
|
||||
@bindThis
|
||||
public async createSystemWebhook(
|
||||
params: {
|
||||
isActive: MiSystemWebhook['isActive'];
|
||||
name: MiSystemWebhook['name'];
|
||||
on: MiSystemWebhook['on'];
|
||||
url: MiSystemWebhook['url'];
|
||||
secret: MiSystemWebhook['secret'];
|
||||
},
|
||||
updater: MiUser,
|
||||
): Promise<MiSystemWebhook> {
|
||||
const id = this.idService.gen();
|
||||
await this.systemWebhooksRepository.insert({
|
||||
...params,
|
||||
id,
|
||||
});
|
||||
|
||||
const webhook = await this.systemWebhooksRepository.findOneByOrFail({ id });
|
||||
this.globalEventService.publishInternalEvent('systemWebhookCreated', webhook);
|
||||
this.moderationLogService
|
||||
.log(updater, 'createSystemWebhook', {
|
||||
systemWebhookId: webhook.id,
|
||||
webhook: webhook,
|
||||
})
|
||||
.then();
|
||||
|
||||
return webhook;
|
||||
}
|
||||
|
||||
/**
|
||||
* SystemWebhook を更新する.
|
||||
*/
|
||||
@bindThis
|
||||
public async updateSystemWebhook(
|
||||
params: {
|
||||
id: MiSystemWebhook['id'];
|
||||
isActive: MiSystemWebhook['isActive'];
|
||||
name: MiSystemWebhook['name'];
|
||||
on: MiSystemWebhook['on'];
|
||||
url: MiSystemWebhook['url'];
|
||||
secret: MiSystemWebhook['secret'];
|
||||
},
|
||||
updater: MiUser,
|
||||
): Promise<MiSystemWebhook> {
|
||||
const beforeEntity = await this.systemWebhooksRepository.findOneByOrFail({ id: params.id });
|
||||
await this.systemWebhooksRepository.update(beforeEntity.id, {
|
||||
updatedAt: new Date(),
|
||||
isActive: params.isActive,
|
||||
name: params.name,
|
||||
on: params.on,
|
||||
url: params.url,
|
||||
secret: params.secret,
|
||||
});
|
||||
|
||||
const afterEntity = await this.systemWebhooksRepository.findOneByOrFail({ id: beforeEntity.id });
|
||||
this.globalEventService.publishInternalEvent('systemWebhookUpdated', afterEntity);
|
||||
this.moderationLogService
|
||||
.log(updater, 'updateSystemWebhook', {
|
||||
systemWebhookId: beforeEntity.id,
|
||||
before: beforeEntity,
|
||||
after: afterEntity,
|
||||
})
|
||||
.then();
|
||||
|
||||
return afterEntity;
|
||||
}
|
||||
|
||||
/**
|
||||
* SystemWebhook を削除する.
|
||||
*/
|
||||
@bindThis
|
||||
public async deleteSystemWebhook(id: MiSystemWebhook['id'], updater: MiUser) {
|
||||
const webhook = await this.systemWebhooksRepository.findOneByOrFail({ id });
|
||||
await this.systemWebhooksRepository.delete(id);
|
||||
|
||||
this.globalEventService.publishInternalEvent('systemWebhookDeleted', webhook);
|
||||
this.moderationLogService
|
||||
.log(updater, 'deleteSystemWebhook', {
|
||||
systemWebhookId: webhook.id,
|
||||
webhook,
|
||||
})
|
||||
.then();
|
||||
}
|
||||
|
||||
/**
|
||||
* SystemWebhook をWebhook配送キューに追加する
|
||||
* @see QueueService.systemWebhookDeliver
|
||||
*/
|
||||
@bindThis
|
||||
public async enqueueSystemWebhook(webhook: MiSystemWebhook | MiSystemWebhook['id'], type: SystemWebhookEventType, content: unknown) {
|
||||
const webhookEntity = typeof webhook === 'string'
|
||||
? (await this.fetchActiveSystemWebhooks()).find(a => a.id === webhook)
|
||||
: webhook;
|
||||
if (!webhookEntity || !webhookEntity.isActive) {
|
||||
this.logger.info(`Webhook is not active or not found : ${webhook}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!webhookEntity.on.includes(type)) {
|
||||
this.logger.info(`Webhook ${webhookEntity.id} is not listening to ${type}`);
|
||||
return;
|
||||
}
|
||||
|
||||
return this.queueService.systemWebhookDeliver(webhookEntity, type, content);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
private async onMessage(_: string, data: string): Promise<void> {
|
||||
const obj = JSON.parse(data);
|
||||
if (obj.channel !== 'internal') {
|
||||
return;
|
||||
}
|
||||
|
||||
const { type, body } = obj.message as GlobalEvents['internal']['payload'];
|
||||
switch (type) {
|
||||
case 'systemWebhookCreated': {
|
||||
if (body.isActive) {
|
||||
this.activeSystemWebhooks.push(MiSystemWebhook.deserialize(body));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'systemWebhookUpdated': {
|
||||
if (body.isActive) {
|
||||
const i = this.activeSystemWebhooks.findIndex(a => a.id === body.id);
|
||||
if (i > -1) {
|
||||
this.activeSystemWebhooks[i] = MiSystemWebhook.deserialize(body);
|
||||
} else {
|
||||
this.activeSystemWebhooks.push(MiSystemWebhook.deserialize(body));
|
||||
}
|
||||
} else {
|
||||
this.activeSystemWebhooks = this.activeSystemWebhooks.filter(a => a.id !== body.id);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'systemWebhookDeleted': {
|
||||
this.activeSystemWebhooks = this.activeSystemWebhooks.filter(a => a.id !== body.id);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public dispose(): void {
|
||||
this.redisForSub.off('message', this.onMessage);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public onApplicationShutdown(signal?: string | undefined): void {
|
||||
this.dispose();
|
||||
}
|
||||
}
|
|
@ -16,7 +16,7 @@ import Logger from '@/logger.js';
|
|||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
|
||||
import { LoggerService } from '@/core/LoggerService.js';
|
||||
import { WebhookService } from '@/core/WebhookService.js';
|
||||
import { UserWebhookService } from '@/core/UserWebhookService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { CacheService } from '@/core/CacheService.js';
|
||||
import { UserFollowingService } from '@/core/UserFollowingService.js';
|
||||
|
@ -46,7 +46,7 @@ export class UserBlockingService implements OnModuleInit {
|
|||
private idService: IdService,
|
||||
private queueService: QueueService,
|
||||
private globalEventService: GlobalEventService,
|
||||
private webhookService: WebhookService,
|
||||
private webhookService: UserWebhookService,
|
||||
private apRendererService: ApRendererService,
|
||||
private loggerService: LoggerService,
|
||||
) {
|
||||
|
@ -121,7 +121,7 @@ export class UserBlockingService implements OnModuleInit {
|
|||
|
||||
const webhooks = (await this.webhookService.getActiveWebhooks()).filter(x => x.userId === follower.id && x.on.includes('unfollow'));
|
||||
for (const webhook of webhooks) {
|
||||
this.queueService.webhookDeliver(webhook, 'unfollow', {
|
||||
this.queueService.userWebhookDeliver(webhook, 'unfollow', {
|
||||
user: packed,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error.js
|
|||
import type { Packed } from '@/misc/json-schema.js';
|
||||
import InstanceChart from '@/core/chart/charts/instance.js';
|
||||
import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
|
||||
import { WebhookService } from '@/core/WebhookService.js';
|
||||
import { UserWebhookService } from '@/core/UserWebhookService.js';
|
||||
import { NotificationService } from '@/core/NotificationService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { FollowingsRepository, FollowRequestsRepository, InstancesRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js';
|
||||
|
@ -82,7 +82,7 @@ export class UserFollowingService implements OnModuleInit {
|
|||
private metaService: MetaService,
|
||||
private notificationService: NotificationService,
|
||||
private federatedInstanceService: FederatedInstanceService,
|
||||
private webhookService: WebhookService,
|
||||
private webhookService: UserWebhookService,
|
||||
private apRendererService: ApRendererService,
|
||||
private accountMoveService: AccountMoveService,
|
||||
private fanoutTimelineService: FanoutTimelineService,
|
||||
|
@ -331,7 +331,7 @@ export class UserFollowingService implements OnModuleInit {
|
|||
|
||||
const webhooks = (await this.webhookService.getActiveWebhooks()).filter(x => x.userId === follower.id && x.on.includes('follow'));
|
||||
for (const webhook of webhooks) {
|
||||
this.queueService.webhookDeliver(webhook, 'follow', {
|
||||
this.queueService.userWebhookDeliver(webhook, 'follow', {
|
||||
user: packed,
|
||||
});
|
||||
}
|
||||
|
@ -345,7 +345,7 @@ export class UserFollowingService implements OnModuleInit {
|
|||
|
||||
const webhooks = (await this.webhookService.getActiveWebhooks()).filter(x => x.userId === followee.id && x.on.includes('followed'));
|
||||
for (const webhook of webhooks) {
|
||||
this.queueService.webhookDeliver(webhook, 'followed', {
|
||||
this.queueService.userWebhookDeliver(webhook, 'followed', {
|
||||
user: packed,
|
||||
});
|
||||
}
|
||||
|
@ -398,7 +398,7 @@ export class UserFollowingService implements OnModuleInit {
|
|||
|
||||
const webhooks = (await this.webhookService.getActiveWebhooks()).filter(x => x.userId === follower.id && x.on.includes('unfollow'));
|
||||
for (const webhook of webhooks) {
|
||||
this.queueService.webhookDeliver(webhook, 'unfollow', {
|
||||
this.queueService.userWebhookDeliver(webhook, 'unfollow', {
|
||||
user: packed,
|
||||
});
|
||||
}
|
||||
|
@ -517,7 +517,7 @@ export class UserFollowingService implements OnModuleInit {
|
|||
followerId: follower.id,
|
||||
});
|
||||
|
||||
const followRequest = await this.followRequestsRepository.insert({
|
||||
const followRequest = await this.followRequestsRepository.insertOne({
|
||||
id: this.idService.gen(),
|
||||
followerId: follower.id,
|
||||
followeeId: followee.id,
|
||||
|
@ -531,7 +531,7 @@ export class UserFollowingService implements OnModuleInit {
|
|||
followeeHost: followee.host,
|
||||
followeeInbox: this.userEntityService.isRemoteUser(followee) ? followee.inbox : undefined,
|
||||
followeeSharedInbox: this.userEntityService.isRemoteUser(followee) ? followee.sharedInbox : undefined,
|
||||
}).then(x => this.followRequestsRepository.findOneByOrFail(x.identifiers[0]));
|
||||
});
|
||||
|
||||
// Publish receiveRequest event
|
||||
if (this.userEntityService.isLocalUser(followee)) {
|
||||
|
@ -740,7 +740,7 @@ export class UserFollowingService implements OnModuleInit {
|
|||
|
||||
const webhooks = (await this.webhookService.getActiveWebhooks()).filter(x => x.userId === follower.id && x.on.includes('unfollow'));
|
||||
for (const webhook of webhooks) {
|
||||
this.queueService.webhookDeliver(webhook, 'unfollow', {
|
||||
this.queueService.userWebhookDeliver(webhook, 'unfollow', {
|
||||
user: packedFollowee,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -0,0 +1,99 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import * as Redis from 'ioredis';
|
||||
import type { WebhooksRepository } from '@/models/_.js';
|
||||
import type { MiWebhook } from '@/models/Webhook.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { GlobalEvents } from '@/core/GlobalEventService.js';
|
||||
import type { OnApplicationShutdown } from '@nestjs/common';
|
||||
|
||||
@Injectable()
|
||||
export class UserWebhookService implements OnApplicationShutdown {
|
||||
private activeWebhooksFetched = false;
|
||||
private activeWebhooks: MiWebhook[] = [];
|
||||
|
||||
constructor(
|
||||
@Inject(DI.redisForSub)
|
||||
private redisForSub: Redis.Redis,
|
||||
@Inject(DI.webhooksRepository)
|
||||
private webhooksRepository: WebhooksRepository,
|
||||
) {
|
||||
this.redisForSub.on('message', this.onMessage);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async getActiveWebhooks() {
|
||||
if (!this.activeWebhooksFetched) {
|
||||
this.activeWebhooks = await this.webhooksRepository.findBy({
|
||||
active: true,
|
||||
});
|
||||
this.activeWebhooksFetched = true;
|
||||
}
|
||||
|
||||
return this.activeWebhooks;
|
||||
}
|
||||
|
||||
@bindThis
|
||||
private async onMessage(_: string, data: string): Promise<void> {
|
||||
const obj = JSON.parse(data);
|
||||
if (obj.channel !== 'internal') {
|
||||
return;
|
||||
}
|
||||
|
||||
const { type, body } = obj.message as GlobalEvents['internal']['payload'];
|
||||
switch (type) {
|
||||
case 'webhookCreated': {
|
||||
if (body.active) {
|
||||
this.activeWebhooks.push({ // TODO: このあたりのデシリアライズ処理は各modelファイル内に関数としてexportしたい
|
||||
...body,
|
||||
latestSentAt: body.latestSentAt ? new Date(body.latestSentAt) : null,
|
||||
user: null, // joinなカラムは通常取ってこないので
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'webhookUpdated': {
|
||||
if (body.active) {
|
||||
const i = this.activeWebhooks.findIndex(a => a.id === body.id);
|
||||
if (i > -1) {
|
||||
this.activeWebhooks[i] = { // TODO: このあたりのデシリアライズ処理は各modelファイル内に関数としてexportしたい
|
||||
...body,
|
||||
latestSentAt: body.latestSentAt ? new Date(body.latestSentAt) : null,
|
||||
user: null, // joinなカラムは通常取ってこないので
|
||||
};
|
||||
} else {
|
||||
this.activeWebhooks.push({ // TODO: このあたりのデシリアライズ処理は各modelファイル内に関数としてexportしたい
|
||||
...body,
|
||||
latestSentAt: body.latestSentAt ? new Date(body.latestSentAt) : null,
|
||||
user: null, // joinなカラムは通常取ってこないので
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.activeWebhooks = this.activeWebhooks.filter(a => a.id !== body.id);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'webhookDeleted': {
|
||||
this.activeWebhooks = this.activeWebhooks.filter(a => a.id !== body.id);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public dispose(): void {
|
||||
this.redisForSub.off('message', this.onMessage);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public onApplicationShutdown(signal?: string | undefined): void {
|
||||
this.dispose();
|
||||
}
|
||||
}
|
|
@ -1,97 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import * as Redis from 'ioredis';
|
||||
import type { WebhooksRepository } from '@/models/_.js';
|
||||
import type { MiWebhook } from '@/models/Webhook.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import type { GlobalEvents } from '@/core/GlobalEventService.js';
|
||||
import type { OnApplicationShutdown } from '@nestjs/common';
|
||||
|
||||
@Injectable()
|
||||
export class WebhookService implements OnApplicationShutdown {
|
||||
private webhooksFetched = false;
|
||||
private webhooks: MiWebhook[] = [];
|
||||
|
||||
constructor(
|
||||
@Inject(DI.redisForSub)
|
||||
private redisForSub: Redis.Redis,
|
||||
|
||||
@Inject(DI.webhooksRepository)
|
||||
private webhooksRepository: WebhooksRepository,
|
||||
) {
|
||||
//this.onMessage = this.onMessage.bind(this);
|
||||
this.redisForSub.on('message', this.onMessage);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async getActiveWebhooks() {
|
||||
if (!this.webhooksFetched) {
|
||||
this.webhooks = await this.webhooksRepository.findBy({
|
||||
active: true,
|
||||
});
|
||||
this.webhooksFetched = true;
|
||||
}
|
||||
|
||||
return this.webhooks;
|
||||
}
|
||||
|
||||
@bindThis
|
||||
private async onMessage(_: string, data: string): Promise<void> {
|
||||
const obj = JSON.parse(data);
|
||||
|
||||
if (obj.channel === 'internal') {
|
||||
const { type, body } = obj.message as GlobalEvents['internal']['payload'];
|
||||
switch (type) {
|
||||
case 'webhookCreated':
|
||||
if (body.active) {
|
||||
this.webhooks.push({ // TODO: このあたりのデシリアライズ処理は各modelファイル内に関数としてexportしたい
|
||||
...body,
|
||||
latestSentAt: body.latestSentAt ? new Date(body.latestSentAt) : null,
|
||||
user: null, // joinなカラムは通常取ってこないので
|
||||
});
|
||||
}
|
||||
break;
|
||||
case 'webhookUpdated':
|
||||
if (body.active) {
|
||||
const i = this.webhooks.findIndex(a => a.id === body.id);
|
||||
if (i > -1) {
|
||||
this.webhooks[i] = { // TODO: このあたりのデシリアライズ処理は各modelファイル内に関数としてexportしたい
|
||||
...body,
|
||||
latestSentAt: body.latestSentAt ? new Date(body.latestSentAt) : null,
|
||||
user: null, // joinなカラムは通常取ってこないので
|
||||
};
|
||||
} else {
|
||||
this.webhooks.push({ // TODO: このあたりのデシリアライズ処理は各modelファイル内に関数としてexportしたい
|
||||
...body,
|
||||
latestSentAt: body.latestSentAt ? new Date(body.latestSentAt) : null,
|
||||
user: null, // joinなカラムは通常取ってこないので
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.webhooks = this.webhooks.filter(a => a.id !== body.id);
|
||||
}
|
||||
break;
|
||||
case 'webhookDeleted':
|
||||
this.webhooks = this.webhooks.filter(a => a.id !== body.id);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public dispose(): void {
|
||||
this.redisForSub.off('message', this.onMessage);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public onApplicationShutdown(signal?: string | undefined): void {
|
||||
this.dispose();
|
||||
}
|
||||
}
|
|
@ -30,6 +30,7 @@ import { bindThis } from '@/decorators.js';
|
|||
import type { MiRemoteUser } from '@/models/User.js';
|
||||
import { isNotNull } from '@/misc/is-not-null.js';
|
||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||
import { AbuseReportService } from '@/core/AbuseReportService.js';
|
||||
import { getApHrefNullable, getApId, getApIds, getApType, isAccept, isActor, isAdd, isAnnounce, isBlock, isCollection, isCollectionOrOrderedCollection, isCreate, isDelete, isFlag, isFollow, isLike, isMove, isPost, isReject, isRemove, isTombstone, isUndo, isUpdate, validActor, validPost } from './type.js';
|
||||
import { ApNoteService } from './models/ApNoteService.js';
|
||||
import { ApLoggerService } from './ApLoggerService.js';
|
||||
|
@ -58,9 +59,6 @@ export class ApInboxService {
|
|||
@Inject(DI.followingsRepository)
|
||||
private followingsRepository: FollowingsRepository,
|
||||
|
||||
@Inject(DI.abuseUserReportsRepository)
|
||||
private abuseUserReportsRepository: AbuseUserReportsRepository,
|
||||
|
||||
@Inject(DI.followRequestsRepository)
|
||||
private followRequestsRepository: FollowRequestsRepository,
|
||||
|
||||
|
@ -69,6 +67,7 @@ export class ApInboxService {
|
|||
private utilityService: UtilityService,
|
||||
private idService: IdService,
|
||||
private metaService: MetaService,
|
||||
private abuseReportService: AbuseReportService,
|
||||
private userFollowingService: UserFollowingService,
|
||||
private apAudienceService: ApAudienceService,
|
||||
private reactionService: ReactionService,
|
||||
|
@ -547,14 +546,13 @@ export class ApInboxService {
|
|||
});
|
||||
if (users.length < 1) return 'skip';
|
||||
|
||||
await this.abuseUserReportsRepository.insert({
|
||||
id: this.idService.gen(),
|
||||
await this.abuseReportService.report([{
|
||||
targetUserId: users[0].id,
|
||||
targetUserHost: users[0].host,
|
||||
reporterId: actor.id,
|
||||
reporterHost: actor.host,
|
||||
comment: `${activity.content}\n${JSON.stringify(uris, null, 2)}`,
|
||||
});
|
||||
}]);
|
||||
|
||||
return 'ok';
|
||||
}
|
||||
|
|
|
@ -493,7 +493,7 @@ export class ApNoteService {
|
|||
|
||||
this.logger.info(`register emoji host=${host}, name=${name}`);
|
||||
|
||||
return await this.emojisRepository.insert({
|
||||
return await this.emojisRepository.insertOne({
|
||||
id: this.idService.gen(),
|
||||
host,
|
||||
name,
|
||||
|
@ -502,7 +502,7 @@ export class ApNoteService {
|
|||
publicUrl: tag.icon.url,
|
||||
updatedAt: new Date(),
|
||||
aliases: [],
|
||||
}).then(x => this.emojisRepository.findOneByOrFail(x.identifiers[0]));
|
||||
});
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,6 +14,6 @@ export class ChartLoggerService {
|
|||
constructor(
|
||||
private loggerService: LoggerService,
|
||||
) {
|
||||
this.logger = this.loggerService.getLogger('chart', 'white', process.env.NODE_ENV !== 'test');
|
||||
this.logger = this.loggerService.getLogger('chart', 'white');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -47,7 +47,7 @@ export default class FederationChart extends Chart<typeof schema> { // eslint-di
|
|||
|
||||
const suspendedInstancesQuery = this.instancesRepository.createQueryBuilder('instance')
|
||||
.select('instance.host')
|
||||
.where('instance.isSuspended = true');
|
||||
.where('instance.suspensionState != \'none\'');
|
||||
|
||||
const pubsubSubQuery = this.followingsRepository.createQueryBuilder('f')
|
||||
.select('f.followerHost')
|
||||
|
@ -89,7 +89,7 @@ export default class FederationChart extends Chart<typeof schema> { // eslint-di
|
|||
.select('COUNT(instance.id)')
|
||||
.where(`instance.host IN (${ subInstancesQuery.getQuery() })`)
|
||||
.andWhere(meta.blockedHosts.length === 0 ? '1=1' : 'instance.host NOT IN (:...blocked)', { blocked: meta.blockedHosts.flatMap(x => [x, `%.${x}`]) })
|
||||
.andWhere('instance.isSuspended = false')
|
||||
.andWhere('instance.suspensionState = \'none\'')
|
||||
.andWhere('instance.isNotResponding = false')
|
||||
.getRawOne()
|
||||
.then(x => parseInt(x.count, 10)),
|
||||
|
@ -97,7 +97,7 @@ export default class FederationChart extends Chart<typeof schema> { // eslint-di
|
|||
.select('COUNT(instance.id)')
|
||||
.where(`instance.host IN (${ pubInstancesQuery.getQuery() })`)
|
||||
.andWhere(meta.blockedHosts.length === 0 ? '1=1' : 'instance.host NOT IN (:...blocked)', { blocked: meta.blockedHosts.flatMap(x => [x, `%.${x}`]) })
|
||||
.andWhere('instance.isSuspended = false')
|
||||
.andWhere('instance.suspensionState = \'none\'')
|
||||
.andWhere('instance.isNotResponding = false')
|
||||
.getRawOne()
|
||||
.then(x => parseInt(x.count, 10)),
|
||||
|
|
|
@ -14,7 +14,8 @@ import { EntitySchema, LessThan, Between } from 'typeorm';
|
|||
import { dateUTC, isTimeSame, isTimeBefore, subtractTime, addTime } from '@/misc/prelude/time.js';
|
||||
import type Logger from '@/logger.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import type { Repository, DataSource } from 'typeorm';
|
||||
import { MiRepository, miRepository } from '@/models/_.js';
|
||||
import type { DataSource, Repository } from 'typeorm';
|
||||
|
||||
const COLUMN_PREFIX = '___' as const;
|
||||
const UNIQUE_TEMP_COLUMN_PREFIX = 'unique_temp___' as const;
|
||||
|
@ -145,10 +146,10 @@ export default abstract class Chart<T extends Schema> {
|
|||
group: string | null;
|
||||
}[] = [];
|
||||
// ↓にしたいけどfindOneとかで型エラーになる
|
||||
//private repositoryForHour: Repository<RawRecord<T>>;
|
||||
//private repositoryForDay: Repository<RawRecord<T>>;
|
||||
private repositoryForHour: Repository<{ id: number; group?: string | null; date: number; }>;
|
||||
private repositoryForDay: Repository<{ id: number; group?: string | null; date: number; }>;
|
||||
//private repositoryForHour: Repository<RawRecord<T>> & MiRepository<RawRecord<T>>;
|
||||
//private repositoryForDay: Repository<RawRecord<T>> & MiRepository<RawRecord<T>>;
|
||||
private repositoryForHour: Repository<{ id: number; group?: string | null; date: number; }> & MiRepository<{ id: number; group?: string | null; date: number; }>;
|
||||
private repositoryForDay: Repository<{ id: number; group?: string | null; date: number; }> & MiRepository<{ id: number; group?: string | null; date: number; }>;
|
||||
|
||||
/**
|
||||
* 1日に一回程度実行されれば良いような計算処理を入れる(主にCASCADE削除などアプリケーション側で感知できない変動によるズレの修正用)
|
||||
|
@ -211,6 +212,10 @@ export default abstract class Chart<T extends Schema> {
|
|||
} {
|
||||
const createEntity = (span: 'hour' | 'day'): EntitySchema => new EntitySchema({
|
||||
name:
|
||||
span === 'hour' ? `ChartX${name}` :
|
||||
span === 'day' ? `ChartDayX${name}` :
|
||||
new Error('not happen') as never,
|
||||
tableName:
|
||||
span === 'hour' ? `__chart__${camelToSnake(name)}` :
|
||||
span === 'day' ? `__chart_day__${camelToSnake(name)}` :
|
||||
new Error('not happen') as never,
|
||||
|
@ -271,8 +276,8 @@ export default abstract class Chart<T extends Schema> {
|
|||
this.logger = logger;
|
||||
|
||||
const { hour, day } = Chart.schemaToEntity(name, schema, grouped);
|
||||
this.repositoryForHour = db.getRepository<{ id: number; group?: string | null; date: number; }>(hour);
|
||||
this.repositoryForDay = db.getRepository<{ id: number; group?: string | null; date: number; }>(day);
|
||||
this.repositoryForHour = db.getRepository<{ id: number; group?: string | null; date: number; }>(hour).extend(miRepository as MiRepository<{ id: number; group?: string | null; date: number; }>);
|
||||
this.repositoryForDay = db.getRepository<{ id: number; group?: string | null; date: number; }>(day).extend(miRepository as MiRepository<{ id: number; group?: string | null; date: number; }>);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
|
@ -387,11 +392,11 @@ export default abstract class Chart<T extends Schema> {
|
|||
}
|
||||
|
||||
// 新規ログ挿入
|
||||
log = await repository.insert({
|
||||
log = await repository.insertOne({
|
||||
date: date,
|
||||
...(group ? { group: group } : {}),
|
||||
...columns,
|
||||
}).then(x => repository.findOneByOrFail(x.identifiers[0])) as RawRecord<T>;
|
||||
}) as RawRecord<T>;
|
||||
|
||||
this.logger.info(`${this.name + (group ? `:${group}` : '')}(${span}): New commit created`);
|
||||
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { In } from 'typeorm';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { AbuseReportNotificationRecipientRepository, MiAbuseReportNotificationRecipient } from '@/models/_.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
import { Packed } from '@/misc/json-schema.js';
|
||||
import { SystemWebhookEntityService } from '@/core/entities/SystemWebhookEntityService.js';
|
||||
import { isNotNull } from '@/misc/is-not-null.js';
|
||||
|
||||
@Injectable()
|
||||
export class AbuseReportNotificationRecipientEntityService {
|
||||
constructor(
|
||||
@Inject(DI.abuseReportNotificationRecipientRepository)
|
||||
private abuseReportNotificationRecipientRepository: AbuseReportNotificationRecipientRepository,
|
||||
private userEntityService: UserEntityService,
|
||||
private systemWebhookEntityService: SystemWebhookEntityService,
|
||||
) {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async pack(
|
||||
src: MiAbuseReportNotificationRecipient['id'] | MiAbuseReportNotificationRecipient,
|
||||
opts?: {
|
||||
users: Map<string, Packed<'UserLite'>>,
|
||||
webhooks: Map<string, Packed<'SystemWebhook'>>,
|
||||
},
|
||||
): Promise<Packed<'AbuseReportNotificationRecipient'>> {
|
||||
const recipient = typeof src === 'object'
|
||||
? src
|
||||
: await this.abuseReportNotificationRecipientRepository.findOneByOrFail({ id: src });
|
||||
const user = recipient.userId
|
||||
? (opts?.users.get(recipient.userId) ?? await this.userEntityService.pack<'UserLite'>(recipient.userId))
|
||||
: undefined;
|
||||
const webhook = recipient.systemWebhookId
|
||||
? (opts?.webhooks.get(recipient.systemWebhookId) ?? await this.systemWebhookEntityService.pack(recipient.systemWebhookId))
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
id: recipient.id,
|
||||
isActive: recipient.isActive,
|
||||
updatedAt: recipient.updatedAt.toISOString(),
|
||||
name: recipient.name,
|
||||
method: recipient.method,
|
||||
userId: recipient.userId ?? undefined,
|
||||
user: user,
|
||||
systemWebhookId: recipient.systemWebhookId ?? undefined,
|
||||
systemWebhook: webhook,
|
||||
};
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async packMany(
|
||||
src: MiAbuseReportNotificationRecipient['id'][] | MiAbuseReportNotificationRecipient[],
|
||||
): Promise<Packed<'AbuseReportNotificationRecipient'>[]> {
|
||||
const objs = src.filter((it): it is MiAbuseReportNotificationRecipient => typeof it === 'object');
|
||||
const ids = src.filter((it): it is MiAbuseReportNotificationRecipient['id'] => typeof it === 'string');
|
||||
if (ids.length > 0) {
|
||||
objs.push(
|
||||
...await this.abuseReportNotificationRecipientRepository.findBy({ id: In(ids) }),
|
||||
);
|
||||
}
|
||||
|
||||
const userIds = objs.map(it => it.userId).filter(isNotNull);
|
||||
const users: Map<string, Packed<'UserLite'>> = (userIds.length > 0)
|
||||
? await this.userEntityService.packMany(userIds)
|
||||
.then(it => new Map(it.map(it => [it.id, it])))
|
||||
: new Map();
|
||||
|
||||
const systemWebhookIds = objs.map(it => it.systemWebhookId).filter(isNotNull);
|
||||
const systemWebhooks: Map<string, Packed<'SystemWebhook'>> = (systemWebhookIds.length > 0)
|
||||
? await this.systemWebhookEntityService.packMany(systemWebhookIds)
|
||||
.then(it => new Map(it.map(it => [it.id, it])))
|
||||
: new Map();
|
||||
|
||||
return Promise
|
||||
.all(
|
||||
objs.map(it => this.pack(it, { users: users, webhooks: systemWebhooks })),
|
||||
)
|
||||
.then(it => it.sort((a, b) => a.id.localeCompare(b.id)));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { In } from 'typeorm';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { MiSystemWebhook, SystemWebhooksRepository } from '@/models/_.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { Packed } from '@/misc/json-schema.js';
|
||||
|
||||
@Injectable()
|
||||
export class SystemWebhookEntityService {
|
||||
constructor(
|
||||
@Inject(DI.systemWebhooksRepository)
|
||||
private systemWebhooksRepository: SystemWebhooksRepository,
|
||||
) {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async pack(
|
||||
src: MiSystemWebhook['id'] | MiSystemWebhook,
|
||||
opts?: {
|
||||
webhooks: Map<string, MiSystemWebhook>
|
||||
},
|
||||
): Promise<Packed<'SystemWebhook'>> {
|
||||
const webhook = typeof src === 'object'
|
||||
? src
|
||||
: opts?.webhooks.get(src) ?? await this.systemWebhooksRepository.findOneByOrFail({ id: src });
|
||||
|
||||
return {
|
||||
id: webhook.id,
|
||||
isActive: webhook.isActive,
|
||||
updatedAt: webhook.updatedAt.toISOString(),
|
||||
latestSentAt: webhook.latestSentAt?.toISOString() ?? null,
|
||||
latestStatus: webhook.latestStatus,
|
||||
name: webhook.name,
|
||||
on: webhook.on,
|
||||
url: webhook.url,
|
||||
secret: webhook.secret,
|
||||
};
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async packMany(src: MiSystemWebhook['id'][] | MiSystemWebhook[]): Promise<Packed<'SystemWebhook'>[]> {
|
||||
if (src.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const webhooks = Array.of<MiSystemWebhook>();
|
||||
webhooks.push(
|
||||
...src.filter((it): it is MiSystemWebhook => typeof it === 'object'),
|
||||
);
|
||||
|
||||
const ids = src.filter((it): it is MiSystemWebhook['id'] => typeof it === 'string');
|
||||
if (ids.length > 0) {
|
||||
webhooks.push(
|
||||
...await this.systemWebhooksRepository.findBy({ id: In(ids) }),
|
||||
);
|
||||
}
|
||||
|
||||
return Promise
|
||||
.all(
|
||||
webhooks.map(x =>
|
||||
this.pack(x, {
|
||||
webhooks: new Map(webhooks.map(x => [x.id, x])),
|
||||
}),
|
||||
),
|
||||
)
|
||||
.then(it => it.sort((a, b) => a.id.localeCompare(b.id)));
|
||||
}
|
||||
}
|
||||
|
|
@ -51,6 +51,7 @@ export const DI = {
|
|||
swSubscriptionsRepository: Symbol('swSubscriptionsRepository'),
|
||||
hashtagsRepository: Symbol('hashtagsRepository'),
|
||||
abuseUserReportsRepository: Symbol('abuseUserReportsRepository'),
|
||||
abuseReportNotificationRecipientRepository: Symbol('abuseReportNotificationRecipientRepository'),
|
||||
registrationTicketsRepository: Symbol('registrationTicketsRepository'),
|
||||
authSessionsRepository: Symbol('authSessionsRepository'),
|
||||
accessTokensRepository: Symbol('accessTokensRepository'),
|
||||
|
@ -72,6 +73,7 @@ export const DI = {
|
|||
channelFavoritesRepository: Symbol('channelFavoritesRepository'),
|
||||
registryItemsRepository: Symbol('registryItemsRepository'),
|
||||
webhooksRepository: Symbol('webhooksRepository'),
|
||||
systemWebhooksRepository: Symbol('systemWebhooksRepository'),
|
||||
adsRepository: Symbol('adsRepository'),
|
||||
passwordResetRequestsRepository: Symbol('passwordResetRequestsRepository'),
|
||||
retentionAggregationsRepository: Symbol('retentionAggregationsRepository'),
|
||||
|
|
|
@ -22,31 +22,27 @@ type Level = 'error' | 'success' | 'warning' | 'debug' | 'info';
|
|||
export default class Logger {
|
||||
private context: Context;
|
||||
private parentLogger: Logger | null = null;
|
||||
private store: boolean;
|
||||
|
||||
constructor(context: string, color?: KEYWORD, store = true) {
|
||||
constructor(context: string, color?: KEYWORD) {
|
||||
this.context = {
|
||||
name: context,
|
||||
color: color,
|
||||
};
|
||||
this.store = store;
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public createSubLogger(context: string, color?: KEYWORD, store = true): Logger {
|
||||
const logger = new Logger(context, color, store);
|
||||
public createSubLogger(context: string, color?: KEYWORD): Logger {
|
||||
const logger = new Logger(context, color);
|
||||
logger.parentLogger = this;
|
||||
return logger;
|
||||
}
|
||||
|
||||
@bindThis
|
||||
private log(level: Level, message: string, data?: Record<string, any> | null, important = false, subContexts: Context[] = [], store = true): void {
|
||||
private log(level: Level, message: string, data?: Record<string, any> | null, important = false, subContexts: Context[] = []): void {
|
||||
if (envOption.quiet) return;
|
||||
if (!this.store) store = false;
|
||||
if (level === 'debug') store = false;
|
||||
|
||||
if (this.parentLogger) {
|
||||
this.parentLogger.log(level, message, data, important, [this.context].concat(subContexts), store);
|
||||
this.parentLogger.log(level, message, data, important, [this.context].concat(subContexts));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -4,12 +4,12 @@
|
|||
*/
|
||||
|
||||
import {
|
||||
packedUserLiteSchema,
|
||||
packedUserDetailedNotMeOnlySchema,
|
||||
packedMeDetailedOnlySchema,
|
||||
packedUserDetailedNotMeSchema,
|
||||
packedMeDetailedSchema,
|
||||
packedUserDetailedNotMeOnlySchema,
|
||||
packedUserDetailedNotMeSchema,
|
||||
packedUserDetailedSchema,
|
||||
packedUserLiteSchema,
|
||||
packedUserSchema,
|
||||
} from '@/models/json-schema/user.js';
|
||||
import { packedNoteSchema } from '@/models/json-schema/note.js';
|
||||
|
@ -25,7 +25,7 @@ import { packedBlockingSchema } from '@/models/json-schema/blocking.js';
|
|||
import { packedNoteReactionSchema } from '@/models/json-schema/note-reaction.js';
|
||||
import { packedHashtagSchema } from '@/models/json-schema/hashtag.js';
|
||||
import { packedInviteCodeSchema } from '@/models/json-schema/invite-code.js';
|
||||
import { packedPageSchema, packedPageBlockSchema } from '@/models/json-schema/page.js';
|
||||
import { packedPageBlockSchema, packedPageSchema } from '@/models/json-schema/page.js';
|
||||
import { packedNoteFavoriteSchema } from '@/models/json-schema/note-favorite.js';
|
||||
import { packedChannelSchema } from '@/models/json-schema/channel.js';
|
||||
import { packedAntennaSchema } from '@/models/json-schema/antenna.js';
|
||||
|
@ -38,25 +38,27 @@ import { packedFlashSchema } from '@/models/json-schema/flash.js';
|
|||
import { packedAnnouncementSchema } from '@/models/json-schema/announcement.js';
|
||||
import { packedSigninSchema } from '@/models/json-schema/signin.js';
|
||||
import {
|
||||
packedRoleLiteSchema,
|
||||
packedRoleSchema,
|
||||
packedRolePoliciesSchema,
|
||||
packedRoleCondFormulaFollowersOrFollowingOrNotesSchema,
|
||||
packedRoleCondFormulaLogicsSchema,
|
||||
packedRoleCondFormulaValueNot,
|
||||
packedRoleCondFormulaValueIsLocalOrRemoteSchema,
|
||||
packedRoleCondFormulaValueAssignedRoleSchema,
|
||||
packedRoleCondFormulaValueCreatedSchema,
|
||||
packedRoleCondFormulaFollowersOrFollowingOrNotesSchema,
|
||||
packedRoleCondFormulaValueIsLocalOrRemoteSchema,
|
||||
packedRoleCondFormulaValueNot,
|
||||
packedRoleCondFormulaValueSchema,
|
||||
packedRoleCondFormulaValueUserSettingBooleanSchema,
|
||||
packedRoleLiteSchema,
|
||||
packedRolePoliciesSchema,
|
||||
packedRoleSchema,
|
||||
} from '@/models/json-schema/role.js';
|
||||
import { packedAdSchema } from '@/models/json-schema/ad.js';
|
||||
import { packedReversiGameLiteSchema, packedReversiGameDetailedSchema } from '@/models/json-schema/reversi-game.js';
|
||||
import { packedReversiGameDetailedSchema, packedReversiGameLiteSchema } from '@/models/json-schema/reversi-game.js';
|
||||
import {
|
||||
packedMetaLiteSchema,
|
||||
packedMetaDetailedOnlySchema,
|
||||
packedMetaDetailedSchema,
|
||||
packedMetaLiteSchema,
|
||||
} from '@/models/json-schema/meta.js';
|
||||
import { packedSystemWebhookSchema } from '@/models/json-schema/system-webhook.js';
|
||||
import { packedAbuseReportNotificationRecipientSchema } from '@/models/json-schema/abuse-report-notification-recipient.js';
|
||||
|
||||
export const refs = {
|
||||
UserLite: packedUserLiteSchema,
|
||||
|
@ -113,6 +115,8 @@ export const refs = {
|
|||
MetaLite: packedMetaLiteSchema,
|
||||
MetaDetailedOnly: packedMetaDetailedOnlySchema,
|
||||
MetaDetailed: packedMetaDetailedSchema,
|
||||
SystemWebhook: packedSystemWebhookSchema,
|
||||
AbuseReportNotificationRecipient: packedAbuseReportNotificationRecipientSchema,
|
||||
};
|
||||
|
||||
export type Packed<x extends keyof typeof refs> = SchemaType<typeof refs[x]>;
|
||||
|
|
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Column, Entity, Index, JoinColumn, ManyToOne, PrimaryColumn } from 'typeorm';
|
||||
import { MiSystemWebhook } from '@/models/SystemWebhook.js';
|
||||
import { MiUserProfile } from '@/models/UserProfile.js';
|
||||
import { id } from './util/id.js';
|
||||
import { MiUser } from './User.js';
|
||||
|
||||
/**
|
||||
* 通報受信時に通知を送信する方法.
|
||||
*/
|
||||
export type RecipientMethod = 'email' | 'webhook';
|
||||
|
||||
@Entity('abuse_report_notification_recipient')
|
||||
export class MiAbuseReportNotificationRecipient {
|
||||
@PrimaryColumn(id())
|
||||
public id: string;
|
||||
|
||||
/**
|
||||
* 有効かどうか.
|
||||
*/
|
||||
@Index()
|
||||
@Column('boolean', {
|
||||
default: true,
|
||||
})
|
||||
public isActive: boolean;
|
||||
|
||||
/**
|
||||
* 更新日時.
|
||||
*/
|
||||
@Column('timestamp with time zone', {
|
||||
default: () => 'CURRENT_TIMESTAMP',
|
||||
})
|
||||
public updatedAt: Date;
|
||||
|
||||
/**
|
||||
* 通知設定名.
|
||||
*/
|
||||
@Column('varchar', {
|
||||
length: 255,
|
||||
})
|
||||
public name: string;
|
||||
|
||||
/**
|
||||
* 通知方法.
|
||||
*/
|
||||
@Index()
|
||||
@Column('varchar', {
|
||||
length: 64,
|
||||
})
|
||||
public method: RecipientMethod;
|
||||
|
||||
/**
|
||||
* 通知先のユーザID.
|
||||
*/
|
||||
@Index()
|
||||
@Column({
|
||||
...id(),
|
||||
nullable: true,
|
||||
})
|
||||
public userId: MiUser['id'] | null;
|
||||
|
||||
/**
|
||||
* 通知先のユーザ.
|
||||
*/
|
||||
@ManyToOne(type => MiUser, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn({ name: 'userId', referencedColumnName: 'id', foreignKeyConstraintName: 'FK_abuse_report_notification_recipient_userId1' })
|
||||
public user: MiUser | null;
|
||||
|
||||
/**
|
||||
* 通知先のユーザプロフィール.
|
||||
*/
|
||||
@ManyToOne(type => MiUserProfile, {})
|
||||
@JoinColumn({ name: 'userId', referencedColumnName: 'userId', foreignKeyConstraintName: 'FK_abuse_report_notification_recipient_userId2' })
|
||||
public userProfile: MiUserProfile | null;
|
||||
|
||||
/**
|
||||
* 通知先のシステムWebhookId.
|
||||
*/
|
||||
@Index()
|
||||
@Column({
|
||||
...id(),
|
||||
nullable: true,
|
||||
})
|
||||
public systemWebhookId: string | null;
|
||||
|
||||
/**
|
||||
* 通知先のシステムWebhook.
|
||||
*/
|
||||
@ManyToOne(type => MiSystemWebhook, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
public systemWebhook: MiSystemWebhook | null;
|
||||
}
|
|
@ -3,9 +3,12 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import type { Provider } from '@nestjs/common';
|
||||
import { Module } from '@nestjs/common';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import {
|
||||
MiAbuseReportNotificationRecipient,
|
||||
|
||||
MiAbuseUserReport,
|
||||
MiAccessToken,
|
||||
MiAd,
|
||||
|
@ -25,7 +28,10 @@ import {
|
|||
MiDriveFile,
|
||||
MiDriveFolder,
|
||||
MiEmoji,
|
||||
MiEmojiRequest,MiFlash,
|
||||
MiEmojiRequest,
|
||||
MiBubbleGameRecord,
|
||||
MiFollowRequest,
|
||||
MiFlash,
|
||||
MiFlashLike,
|
||||
MiFollowRequest,
|
||||
MiFollowing,
|
||||
|
@ -48,8 +54,12 @@ import {
|
|||
MiPollVote,
|
||||
MiPromoNote,
|
||||
MiPromoRead,
|
||||
MiRepository,
|
||||
miRepository,
|
||||
MiRegistrationTicket,
|
||||
MiRegistryItem,
|
||||
MiReversiGame,
|
||||
MiSystemWebhook,
|
||||
MiRelay,
|
||||
MiRenoteMuting,
|
||||
MiRetentionAggregation,
|
||||
|
@ -75,17 +85,16 @@ import {
|
|||
MiBubbleGameRecord
|
||||
, MiReversiGame } from './_.js';
|
||||
import type { DataSource } from 'typeorm';
|
||||
import type { Provider } from '@nestjs/common';
|
||||
|
||||
const $usersRepository: Provider = {
|
||||
provide: DI.usersRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(MiUser),
|
||||
useFactory: (db: DataSource) => db.getRepository(MiUser).extend(miRepository as MiRepository<MiUser>),
|
||||
inject: [DI.db],
|
||||
};
|
||||
|
||||
const $notesRepository: Provider = {
|
||||
provide: DI.notesRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(MiNote),
|
||||
useFactory: (db: DataSource) => db.getRepository(MiNote).extend(miRepository as MiRepository<MiNote>),
|
||||
inject: [DI.db],
|
||||
};
|
||||
|
||||
|
@ -97,151 +106,151 @@ const $scheduledNotesRepository: Provider = {
|
|||
|
||||
const $announcementsRepository: Provider = {
|
||||
provide: DI.announcementsRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(MiAnnouncement),
|
||||
useFactory: (db: DataSource) => db.getRepository(MiAnnouncement).extend(miRepository as MiRepository<MiAnnouncement>),
|
||||
inject: [DI.db],
|
||||
};
|
||||
|
||||
const $announcementReadsRepository: Provider = {
|
||||
provide: DI.announcementReadsRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(MiAnnouncementRead),
|
||||
useFactory: (db: DataSource) => db.getRepository(MiAnnouncementRead).extend(miRepository as MiRepository<MiAnnouncementRead>),
|
||||
inject: [DI.db],
|
||||
};
|
||||
|
||||
const $appsRepository: Provider = {
|
||||
provide: DI.appsRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(MiApp),
|
||||
useFactory: (db: DataSource) => db.getRepository(MiApp).extend(miRepository as MiRepository<MiApp>),
|
||||
inject: [DI.db],
|
||||
};
|
||||
|
||||
const $avatarDecorationsRepository: Provider = {
|
||||
provide: DI.avatarDecorationsRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(MiAvatarDecoration),
|
||||
useFactory: (db: DataSource) => db.getRepository(MiAvatarDecoration).extend(miRepository as MiRepository<MiAvatarDecoration>),
|
||||
inject: [DI.db],
|
||||
};
|
||||
|
||||
const $noteFavoritesRepository: Provider = {
|
||||
provide: DI.noteFavoritesRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(MiNoteFavorite),
|
||||
useFactory: (db: DataSource) => db.getRepository(MiNoteFavorite).extend(miRepository as MiRepository<MiNoteFavorite>),
|
||||
inject: [DI.db],
|
||||
};
|
||||
|
||||
const $noteThreadMutingsRepository: Provider = {
|
||||
provide: DI.noteThreadMutingsRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(MiNoteThreadMuting),
|
||||
useFactory: (db: DataSource) => db.getRepository(MiNoteThreadMuting).extend(miRepository as MiRepository<MiNoteThreadMuting>),
|
||||
inject: [DI.db],
|
||||
};
|
||||
|
||||
const $noteReactionsRepository: Provider = {
|
||||
provide: DI.noteReactionsRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(MiNoteReaction),
|
||||
useFactory: (db: DataSource) => db.getRepository(MiNoteReaction).extend(miRepository as MiRepository<MiNoteReaction>),
|
||||
inject: [DI.db],
|
||||
};
|
||||
|
||||
const $noteUnreadsRepository: Provider = {
|
||||
provide: DI.noteUnreadsRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(MiNoteUnread),
|
||||
useFactory: (db: DataSource) => db.getRepository(MiNoteUnread).extend(miRepository as MiRepository<MiNoteUnread>),
|
||||
inject: [DI.db],
|
||||
};
|
||||
|
||||
const $pollsRepository: Provider = {
|
||||
provide: DI.pollsRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(MiPoll),
|
||||
useFactory: (db: DataSource) => db.getRepository(MiPoll).extend(miRepository as MiRepository<MiPoll>),
|
||||
inject: [DI.db],
|
||||
};
|
||||
|
||||
const $pollVotesRepository: Provider = {
|
||||
provide: DI.pollVotesRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(MiPollVote),
|
||||
useFactory: (db: DataSource) => db.getRepository(MiPollVote).extend(miRepository as MiRepository<MiPollVote>),
|
||||
inject: [DI.db],
|
||||
};
|
||||
|
||||
const $userProfilesRepository: Provider = {
|
||||
provide: DI.userProfilesRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(MiUserProfile),
|
||||
useFactory: (db: DataSource) => db.getRepository(MiUserProfile).extend(miRepository as MiRepository<MiUserProfile>),
|
||||
inject: [DI.db],
|
||||
};
|
||||
|
||||
const $userKeypairsRepository: Provider = {
|
||||
provide: DI.userKeypairsRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(MiUserKeypair),
|
||||
useFactory: (db: DataSource) => db.getRepository(MiUserKeypair).extend(miRepository as MiRepository<MiUserKeypair>),
|
||||
inject: [DI.db],
|
||||
};
|
||||
|
||||
const $userPendingsRepository: Provider = {
|
||||
provide: DI.userPendingsRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(MiUserPending),
|
||||
useFactory: (db: DataSource) => db.getRepository(MiUserPending).extend(miRepository as MiRepository<MiUserPending>),
|
||||
inject: [DI.db],
|
||||
};
|
||||
|
||||
const $userSecurityKeysRepository: Provider = {
|
||||
provide: DI.userSecurityKeysRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(MiUserSecurityKey),
|
||||
useFactory: (db: DataSource) => db.getRepository(MiUserSecurityKey).extend(miRepository as MiRepository<MiUserSecurityKey>),
|
||||
inject: [DI.db],
|
||||
};
|
||||
|
||||
const $userPublickeysRepository: Provider = {
|
||||
provide: DI.userPublickeysRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(MiUserPublickey),
|
||||
useFactory: (db: DataSource) => db.getRepository(MiUserPublickey).extend(miRepository as MiRepository<MiUserPublickey>),
|
||||
inject: [DI.db],
|
||||
};
|
||||
|
||||
const $userListsRepository: Provider = {
|
||||
provide: DI.userListsRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(MiUserList),
|
||||
useFactory: (db: DataSource) => db.getRepository(MiUserList).extend(miRepository as MiRepository<MiUserList>),
|
||||
inject: [DI.db],
|
||||
};
|
||||
|
||||
const $userListFavoritesRepository: Provider = {
|
||||
provide: DI.userListFavoritesRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(MiUserListFavorite),
|
||||
useFactory: (db: DataSource) => db.getRepository(MiUserListFavorite).extend(miRepository as MiRepository<MiUserListFavorite>),
|
||||
inject: [DI.db],
|
||||
};
|
||||
|
||||
const $userListMembershipsRepository: Provider = {
|
||||
provide: DI.userListMembershipsRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(MiUserListMembership),
|
||||
useFactory: (db: DataSource) => db.getRepository(MiUserListMembership).extend(miRepository as MiRepository<MiUserListMembership>),
|
||||
inject: [DI.db],
|
||||
};
|
||||
|
||||
const $userNotePiningsRepository: Provider = {
|
||||
provide: DI.userNotePiningsRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(MiUserNotePining),
|
||||
useFactory: (db: DataSource) => db.getRepository(MiUserNotePining).extend(miRepository as MiRepository<MiUserNotePining>),
|
||||
inject: [DI.db],
|
||||
};
|
||||
|
||||
const $userIpsRepository: Provider = {
|
||||
provide: DI.userIpsRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(MiUserIp),
|
||||
useFactory: (db: DataSource) => db.getRepository(MiUserIp).extend(miRepository as MiRepository<MiUserIp>),
|
||||
inject: [DI.db],
|
||||
};
|
||||
|
||||
const $usedUsernamesRepository: Provider = {
|
||||
provide: DI.usedUsernamesRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(MiUsedUsername),
|
||||
useFactory: (db: DataSource) => db.getRepository(MiUsedUsername).extend(miRepository as MiRepository<MiUsedUsername>),
|
||||
inject: [DI.db],
|
||||
};
|
||||
|
||||
const $followingsRepository: Provider = {
|
||||
provide: DI.followingsRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(MiFollowing),
|
||||
useFactory: (db: DataSource) => db.getRepository(MiFollowing).extend(miRepository as MiRepository<MiFollowing>),
|
||||
inject: [DI.db],
|
||||
};
|
||||
|
||||
const $followRequestsRepository: Provider = {
|
||||
provide: DI.followRequestsRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(MiFollowRequest),
|
||||
useFactory: (db: DataSource) => db.getRepository(MiFollowRequest).extend(miRepository as MiRepository<MiFollowRequest>),
|
||||
inject: [DI.db],
|
||||
};
|
||||
|
||||
const $instancesRepository: Provider = {
|
||||
provide: DI.instancesRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(MiInstance),
|
||||
useFactory: (db: DataSource) => db.getRepository(MiInstance).extend(miRepository as MiRepository<MiInstance>),
|
||||
inject: [DI.db],
|
||||
};
|
||||
|
||||
const $emojisRepository: Provider = {
|
||||
provide: DI.emojisRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(MiEmoji),
|
||||
useFactory: (db: DataSource) => db.getRepository(MiEmoji).extend(miRepository as MiRepository<MiEmoji>),
|
||||
inject: [DI.db],
|
||||
};
|
||||
|
||||
|
@ -253,247 +262,258 @@ const $emojiRequestsRepository: Provider = {
|
|||
|
||||
const $driveFilesRepository: Provider = {
|
||||
provide: DI.driveFilesRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(MiDriveFile),
|
||||
useFactory: (db: DataSource) => db.getRepository(MiDriveFile).extend(miRepository as MiRepository<MiDriveFile>),
|
||||
inject: [DI.db],
|
||||
};
|
||||
|
||||
const $driveFoldersRepository: Provider = {
|
||||
provide: DI.driveFoldersRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(MiDriveFolder),
|
||||
useFactory: (db: DataSource) => db.getRepository(MiDriveFolder).extend(miRepository as MiRepository<MiDriveFolder>),
|
||||
inject: [DI.db],
|
||||
};
|
||||
|
||||
const $metasRepository: Provider = {
|
||||
provide: DI.metasRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(MiMeta),
|
||||
useFactory: (db: DataSource) => db.getRepository(MiMeta).extend(miRepository as MiRepository<MiMeta>),
|
||||
inject: [DI.db],
|
||||
};
|
||||
|
||||
const $mutingsRepository: Provider = {
|
||||
provide: DI.mutingsRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(MiMuting),
|
||||
useFactory: (db: DataSource) => db.getRepository(MiMuting).extend(miRepository as MiRepository<MiMuting>),
|
||||
inject: [DI.db],
|
||||
};
|
||||
|
||||
const $renoteMutingsRepository: Provider = {
|
||||
provide: DI.renoteMutingsRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(MiRenoteMuting),
|
||||
useFactory: (db: DataSource) => db.getRepository(MiRenoteMuting).extend(miRepository as MiRepository<MiRenoteMuting>),
|
||||
inject: [DI.db],
|
||||
};
|
||||
|
||||
const $blockingsRepository: Provider = {
|
||||
provide: DI.blockingsRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(MiBlocking),
|
||||
useFactory: (db: DataSource) => db.getRepository(MiBlocking).extend(miRepository as MiRepository<MiBlocking>),
|
||||
inject: [DI.db],
|
||||
};
|
||||
|
||||
const $swSubscriptionsRepository: Provider = {
|
||||
provide: DI.swSubscriptionsRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(MiSwSubscription),
|
||||
useFactory: (db: DataSource) => db.getRepository(MiSwSubscription).extend(miRepository as MiRepository<MiSwSubscription>),
|
||||
inject: [DI.db],
|
||||
};
|
||||
|
||||
const $hashtagsRepository: Provider = {
|
||||
provide: DI.hashtagsRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(MiHashtag),
|
||||
useFactory: (db: DataSource) => db.getRepository(MiHashtag).extend(miRepository as MiRepository<MiHashtag>),
|
||||
inject: [DI.db],
|
||||
};
|
||||
|
||||
const $abuseUserReportsRepository: Provider = {
|
||||
provide: DI.abuseUserReportsRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(MiAbuseUserReport),
|
||||
useFactory: (db: DataSource) => db.getRepository(MiAbuseUserReport).extend(miRepository as MiRepository<MiAbuseUserReport>),
|
||||
inject: [DI.db],
|
||||
};
|
||||
|
||||
const $abuseReportNotificationRecipientRepository: Provider = {
|
||||
provide: DI.abuseReportNotificationRecipientRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(MiAbuseReportNotificationRecipient),
|
||||
inject: [DI.db],
|
||||
};
|
||||
|
||||
const $registrationTicketsRepository: Provider = {
|
||||
provide: DI.registrationTicketsRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(MiRegistrationTicket),
|
||||
useFactory: (db: DataSource) => db.getRepository(MiRegistrationTicket).extend(miRepository as MiRepository<MiRegistrationTicket>),
|
||||
inject: [DI.db],
|
||||
};
|
||||
|
||||
const $authSessionsRepository: Provider = {
|
||||
provide: DI.authSessionsRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(MiAuthSession),
|
||||
useFactory: (db: DataSource) => db.getRepository(MiAuthSession).extend(miRepository as MiRepository<MiAuthSession>),
|
||||
inject: [DI.db],
|
||||
};
|
||||
|
||||
const $accessTokensRepository: Provider = {
|
||||
provide: DI.accessTokensRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(MiAccessToken),
|
||||
useFactory: (db: DataSource) => db.getRepository(MiAccessToken).extend(miRepository as MiRepository<MiAccessToken>),
|
||||
inject: [DI.db],
|
||||
};
|
||||
|
||||
const $signinsRepository: Provider = {
|
||||
provide: DI.signinsRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(MiSignin),
|
||||
useFactory: (db: DataSource) => db.getRepository(MiSignin).extend(miRepository as MiRepository<MiSignin>),
|
||||
inject: [DI.db],
|
||||
};
|
||||
|
||||
const $pagesRepository: Provider = {
|
||||
provide: DI.pagesRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(MiPage),
|
||||
useFactory: (db: DataSource) => db.getRepository(MiPage).extend(miRepository as MiRepository<MiPage>),
|
||||
inject: [DI.db],
|
||||
};
|
||||
|
||||
const $pageLikesRepository: Provider = {
|
||||
provide: DI.pageLikesRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(MiPageLike),
|
||||
useFactory: (db: DataSource) => db.getRepository(MiPageLike).extend(miRepository as MiRepository<MiPageLike>),
|
||||
inject: [DI.db],
|
||||
};
|
||||
|
||||
const $galleryPostsRepository: Provider = {
|
||||
provide: DI.galleryPostsRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(MiGalleryPost),
|
||||
useFactory: (db: DataSource) => db.getRepository(MiGalleryPost).extend(miRepository as MiRepository<MiGalleryPost>),
|
||||
inject: [DI.db],
|
||||
};
|
||||
|
||||
const $galleryLikesRepository: Provider = {
|
||||
provide: DI.galleryLikesRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(MiGalleryLike),
|
||||
useFactory: (db: DataSource) => db.getRepository(MiGalleryLike).extend(miRepository as MiRepository<MiGalleryLike>),
|
||||
inject: [DI.db],
|
||||
};
|
||||
|
||||
const $moderationLogsRepository: Provider = {
|
||||
provide: DI.moderationLogsRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(MiModerationLog),
|
||||
useFactory: (db: DataSource) => db.getRepository(MiModerationLog).extend(miRepository as MiRepository<MiModerationLog>),
|
||||
inject: [DI.db],
|
||||
};
|
||||
|
||||
const $clipsRepository: Provider = {
|
||||
provide: DI.clipsRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(MiClip),
|
||||
useFactory: (db: DataSource) => db.getRepository(MiClip).extend(miRepository as MiRepository<MiClip>),
|
||||
inject: [DI.db],
|
||||
};
|
||||
|
||||
const $clipNotesRepository: Provider = {
|
||||
provide: DI.clipNotesRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(MiClipNote),
|
||||
useFactory: (db: DataSource) => db.getRepository(MiClipNote).extend(miRepository as MiRepository<MiClipNote>),
|
||||
inject: [DI.db],
|
||||
};
|
||||
|
||||
const $clipFavoritesRepository: Provider = {
|
||||
provide: DI.clipFavoritesRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(MiClipFavorite),
|
||||
useFactory: (db: DataSource) => db.getRepository(MiClipFavorite).extend(miRepository as MiRepository<MiClipFavorite>),
|
||||
inject: [DI.db],
|
||||
};
|
||||
|
||||
const $antennasRepository: Provider = {
|
||||
provide: DI.antennasRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(MiAntenna),
|
||||
useFactory: (db: DataSource) => db.getRepository(MiAntenna).extend(miRepository as MiRepository<MiAntenna>),
|
||||
inject: [DI.db],
|
||||
};
|
||||
|
||||
const $promoNotesRepository: Provider = {
|
||||
provide: DI.promoNotesRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(MiPromoNote),
|
||||
useFactory: (db: DataSource) => db.getRepository(MiPromoNote).extend(miRepository as MiRepository<MiPromoNote>),
|
||||
inject: [DI.db],
|
||||
};
|
||||
|
||||
const $promoReadsRepository: Provider = {
|
||||
provide: DI.promoReadsRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(MiPromoRead),
|
||||
useFactory: (db: DataSource) => db.getRepository(MiPromoRead).extend(miRepository as MiRepository<MiPromoRead>),
|
||||
inject: [DI.db],
|
||||
};
|
||||
|
||||
const $relaysRepository: Provider = {
|
||||
provide: DI.relaysRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(MiRelay),
|
||||
useFactory: (db: DataSource) => db.getRepository(MiRelay).extend(miRepository as MiRepository<MiRelay>),
|
||||
inject: [DI.db],
|
||||
};
|
||||
|
||||
const $channelsRepository: Provider = {
|
||||
provide: DI.channelsRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(MiChannel),
|
||||
useFactory: (db: DataSource) => db.getRepository(MiChannel).extend(miRepository as MiRepository<MiChannel>),
|
||||
inject: [DI.db],
|
||||
};
|
||||
|
||||
const $channelFollowingsRepository: Provider = {
|
||||
provide: DI.channelFollowingsRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(MiChannelFollowing),
|
||||
useFactory: (db: DataSource) => db.getRepository(MiChannelFollowing).extend(miRepository as MiRepository<MiChannelFollowing>),
|
||||
inject: [DI.db],
|
||||
};
|
||||
|
||||
const $channelFavoritesRepository: Provider = {
|
||||
provide: DI.channelFavoritesRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(MiChannelFavorite),
|
||||
useFactory: (db: DataSource) => db.getRepository(MiChannelFavorite).extend(miRepository as MiRepository<MiChannelFavorite>),
|
||||
inject: [DI.db],
|
||||
};
|
||||
|
||||
const $registryItemsRepository: Provider = {
|
||||
provide: DI.registryItemsRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(MiRegistryItem),
|
||||
useFactory: (db: DataSource) => db.getRepository(MiRegistryItem).extend(miRepository as MiRepository<MiRegistryItem>),
|
||||
inject: [DI.db],
|
||||
};
|
||||
|
||||
const $webhooksRepository: Provider = {
|
||||
provide: DI.webhooksRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(MiWebhook),
|
||||
useFactory: (db: DataSource) => db.getRepository(MiWebhook).extend(miRepository as MiRepository<MiWebhook>),
|
||||
inject: [DI.db],
|
||||
};
|
||||
|
||||
const $systemWebhooksRepository: Provider = {
|
||||
provide: DI.systemWebhooksRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(MiSystemWebhook),
|
||||
inject: [DI.db],
|
||||
};
|
||||
|
||||
const $adsRepository: Provider = {
|
||||
provide: DI.adsRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(MiAd),
|
||||
useFactory: (db: DataSource) => db.getRepository(MiAd).extend(miRepository as MiRepository<MiAd>),
|
||||
inject: [DI.db],
|
||||
};
|
||||
|
||||
const $passwordResetRequestsRepository: Provider = {
|
||||
provide: DI.passwordResetRequestsRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(MiPasswordResetRequest),
|
||||
useFactory: (db: DataSource) => db.getRepository(MiPasswordResetRequest).extend(miRepository as MiRepository<MiPasswordResetRequest>),
|
||||
inject: [DI.db],
|
||||
};
|
||||
|
||||
const $retentionAggregationsRepository: Provider = {
|
||||
provide: DI.retentionAggregationsRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(MiRetentionAggregation),
|
||||
useFactory: (db: DataSource) => db.getRepository(MiRetentionAggregation).extend(miRepository as MiRepository<MiRetentionAggregation>),
|
||||
inject: [DI.db],
|
||||
};
|
||||
|
||||
const $flashsRepository: Provider = {
|
||||
provide: DI.flashsRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(MiFlash),
|
||||
useFactory: (db: DataSource) => db.getRepository(MiFlash).extend(miRepository as MiRepository<MiFlash>),
|
||||
inject: [DI.db],
|
||||
};
|
||||
|
||||
const $flashLikesRepository: Provider = {
|
||||
provide: DI.flashLikesRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(MiFlashLike),
|
||||
useFactory: (db: DataSource) => db.getRepository(MiFlashLike).extend(miRepository as MiRepository<MiFlashLike>),
|
||||
inject: [DI.db],
|
||||
};
|
||||
|
||||
const $rolesRepository: Provider = {
|
||||
provide: DI.rolesRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(MiRole),
|
||||
useFactory: (db: DataSource) => db.getRepository(MiRole).extend(miRepository as MiRepository<MiRole>),
|
||||
inject: [DI.db],
|
||||
};
|
||||
|
||||
const $roleAssignmentsRepository: Provider = {
|
||||
provide: DI.roleAssignmentsRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(MiRoleAssignment),
|
||||
useFactory: (db: DataSource) => db.getRepository(MiRoleAssignment).extend(miRepository as MiRepository<MiRoleAssignment>),
|
||||
inject: [DI.db],
|
||||
};
|
||||
|
||||
const $userMemosRepository: Provider = {
|
||||
provide: DI.userMemosRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(MiUserMemo),
|
||||
useFactory: (db: DataSource) => db.getRepository(MiUserMemo).extend(miRepository as MiRepository<MiUserMemo>),
|
||||
inject: [DI.db],
|
||||
};
|
||||
|
||||
const $bubbleGameRecordsRepository: Provider = {
|
||||
provide: DI.bubbleGameRecordsRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(MiBubbleGameRecord),
|
||||
useFactory: (db: DataSource) => db.getRepository(MiBubbleGameRecord).extend(miRepository as MiRepository<MiBubbleGameRecord>),
|
||||
inject: [DI.db],
|
||||
};
|
||||
|
||||
const $reversiGamesRepository: Provider = {
|
||||
provide: DI.reversiGamesRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(MiReversiGame),
|
||||
useFactory: (db: DataSource) => db.getRepository(MiReversiGame).extend(miRepository as MiRepository<MiReversiGame>),
|
||||
inject: [DI.db],
|
||||
};
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
],
|
||||
imports: [],
|
||||
providers: [
|
||||
$usersRepository,
|
||||
$notesRepository,
|
||||
|
@ -533,6 +553,7 @@ const $reversiGamesRepository: Provider = {
|
|||
$swSubscriptionsRepository,
|
||||
$hashtagsRepository,
|
||||
$abuseUserReportsRepository,
|
||||
$abuseReportNotificationRecipientRepository,
|
||||
$registrationTicketsRepository,
|
||||
$authSessionsRepository,
|
||||
$accessTokensRepository,
|
||||
|
@ -554,6 +575,7 @@ const $reversiGamesRepository: Provider = {
|
|||
$channelFavoritesRepository,
|
||||
$registryItemsRepository,
|
||||
$webhooksRepository,
|
||||
$systemWebhooksRepository,
|
||||
$adsRepository,
|
||||
$passwordResetRequestsRepository,
|
||||
$retentionAggregationsRepository,
|
||||
|
@ -604,6 +626,7 @@ const $reversiGamesRepository: Provider = {
|
|||
$swSubscriptionsRepository,
|
||||
$hashtagsRepository,
|
||||
$abuseUserReportsRepository,
|
||||
$abuseReportNotificationRecipientRepository,
|
||||
$registrationTicketsRepository,
|
||||
$authSessionsRepository,
|
||||
$accessTokensRepository,
|
||||
|
@ -625,6 +648,7 @@ const $reversiGamesRepository: Provider = {
|
|||
$channelFavoritesRepository,
|
||||
$registryItemsRepository,
|
||||
$webhooksRepository,
|
||||
$systemWebhooksRepository,
|
||||
$adsRepository,
|
||||
$passwordResetRequestsRepository,
|
||||
$retentionAggregationsRepository,
|
||||
|
@ -637,4 +661,5 @@ const $reversiGamesRepository: Provider = {
|
|||
$reversiGamesRepository,
|
||||
],
|
||||
})
|
||||
export class RepositoryModule {}
|
||||
export class RepositoryModule {
|
||||
}
|
||||
|
|
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Column, Entity, Index, PrimaryColumn } from 'typeorm';
|
||||
import { Serialized } from '@/types.js';
|
||||
import { id } from './util/id.js';
|
||||
|
||||
export const systemWebhookEventTypes = [
|
||||
// ユーザからの通報を受けたとき
|
||||
'abuseReport',
|
||||
// 通報を処理したとき
|
||||
'abuseReportResolved',
|
||||
] as const;
|
||||
export type SystemWebhookEventType = typeof systemWebhookEventTypes[number];
|
||||
|
||||
@Entity('system_webhook')
|
||||
export class MiSystemWebhook {
|
||||
@PrimaryColumn(id())
|
||||
public id: string;
|
||||
|
||||
/**
|
||||
* 有効かどうか.
|
||||
*/
|
||||
@Index('IDX_system_webhook_isActive', { synchronize: false })
|
||||
@Column('boolean', {
|
||||
default: true,
|
||||
})
|
||||
public isActive: boolean;
|
||||
|
||||
/**
|
||||
* 更新日時.
|
||||
*/
|
||||
@Column('timestamp with time zone', {
|
||||
default: () => 'CURRENT_TIMESTAMP',
|
||||
})
|
||||
public updatedAt: Date;
|
||||
|
||||
/**
|
||||
* 最後に送信された日時.
|
||||
*/
|
||||
@Column('timestamp with time zone', {
|
||||
nullable: true,
|
||||
})
|
||||
public latestSentAt: Date | null;
|
||||
|
||||
/**
|
||||
* 最後に送信されたステータスコード
|
||||
*/
|
||||
@Column('integer', {
|
||||
nullable: true,
|
||||
})
|
||||
public latestStatus: number | null;
|
||||
|
||||
/**
|
||||
* 通知設定名.
|
||||
*/
|
||||
@Column('varchar', {
|
||||
length: 255,
|
||||
})
|
||||
public name: string;
|
||||
|
||||
/**
|
||||
* イベント種別.
|
||||
*/
|
||||
@Index('IDX_system_webhook_on', { synchronize: false })
|
||||
@Column('varchar', {
|
||||
length: 128,
|
||||
array: true,
|
||||
default: '{}',
|
||||
})
|
||||
public on: SystemWebhookEventType[];
|
||||
|
||||
/**
|
||||
* Webhook送信先のURL.
|
||||
*/
|
||||
@Column('varchar', {
|
||||
length: 1024,
|
||||
})
|
||||
public url: string;
|
||||
|
||||
/**
|
||||
* Webhook検証用の値.
|
||||
*/
|
||||
@Column('varchar', {
|
||||
length: 1024,
|
||||
})
|
||||
public secret: string;
|
||||
|
||||
static deserialize(obj: Serialized<MiSystemWebhook>): MiSystemWebhook {
|
||||
return {
|
||||
...obj,
|
||||
updatedAt: new Date(obj.updatedAt),
|
||||
latestSentAt: obj.latestSentAt ? new Date(obj.latestSentAt) : null,
|
||||
};
|
||||
}
|
||||
}
|
|
@ -3,7 +3,15 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { FindOneOptions, InsertQueryBuilder, ObjectLiteral, Repository, SelectQueryBuilder, TypeORMError } from 'typeorm';
|
||||
import { DriverUtils } from 'typeorm/driver/DriverUtils.js';
|
||||
import { RelationCountLoader } from 'typeorm/query-builder/relation-count/RelationCountLoader.js';
|
||||
import { RelationIdLoader } from 'typeorm/query-builder/relation-id/RelationIdLoader.js';
|
||||
import { RawSqlResultsToEntityTransformer } from 'typeorm/query-builder/transformer/RawSqlResultsToEntityTransformer.js';
|
||||
import { ObjectUtils } from 'typeorm/util/ObjectUtils.js';
|
||||
import { OrmUtils } from 'typeorm/util/OrmUtils.js';
|
||||
import { MiAbuseUserReport } from '@/models/AbuseUserReport.js';
|
||||
import { MiAbuseReportNotificationRecipient } from '@/models/AbuseReportNotificationRecipient.js';
|
||||
import { MiAccessToken } from '@/models/AccessToken.js';
|
||||
import { MiAd } from '@/models/Ad.js';
|
||||
import { MiAnnouncement } from '@/models/Announcement.js';
|
||||
|
@ -62,6 +70,7 @@ import { MiUserPublickey } from '@/models/UserPublickey.js';
|
|||
import { MiUserSecurityKey } from '@/models/UserSecurityKey.js';
|
||||
import { MiUserMemo } from '@/models/UserMemo.js';
|
||||
import { MiWebhook } from '@/models/Webhook.js';
|
||||
import { MiSystemWebhook } from '@/models/SystemWebhook.js';
|
||||
import { MiChannel } from '@/models/Channel.js';
|
||||
import { MiRetentionAggregation } from '@/models/RetentionAggregation.js';
|
||||
import { MiRole } from '@/models/Role.js';
|
||||
|
@ -71,12 +80,75 @@ import { MiFlashLike } from '@/models/FlashLike.js';
|
|||
import { MiUserListFavorite } from '@/models/UserListFavorite.js';
|
||||
import { MiBubbleGameRecord } from '@/models/BubbleGameRecord.js';
|
||||
import { MiReversiGame } from '@/models/ReversiGame.js';
|
||||
|
||||
import type { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity.js';
|
||||
import { MiScheduledNote } from './ScheduledNote.js';
|
||||
import type { Repository } from 'typeorm';
|
||||
|
||||
export interface MiRepository<T extends ObjectLiteral> {
|
||||
createTableColumnNames(this: Repository<T> & MiRepository<T>, queryBuilder: InsertQueryBuilder<T>): string[];
|
||||
createTableColumnNamesWithPrimaryKey(this: Repository<T> & MiRepository<T>, queryBuilder: InsertQueryBuilder<T>): string[];
|
||||
insertOne(this: Repository<T> & MiRepository<T>, entity: QueryDeepPartialEntity<T>, findOptions?: Pick<FindOneOptions<T>, 'relations'>): Promise<T>;
|
||||
selectAliasColumnNames(this: Repository<T> & MiRepository<T>, queryBuilder: InsertQueryBuilder<T>, builder: SelectQueryBuilder<T>): void;
|
||||
}
|
||||
|
||||
export const miRepository = {
|
||||
createTableColumnNames(queryBuilder) {
|
||||
// @ts-expect-error -- protected
|
||||
const insertedColumns = queryBuilder.getInsertedColumns();
|
||||
if (insertedColumns.length) {
|
||||
return insertedColumns.map(column => column.databaseName);
|
||||
}
|
||||
if (!queryBuilder.expressionMap.mainAlias?.hasMetadata && !queryBuilder.expressionMap.insertColumns.length) {
|
||||
// @ts-expect-error -- protected
|
||||
const valueSets = queryBuilder.getValueSets();
|
||||
if (valueSets.length === 1) {
|
||||
return Object.keys(valueSets[0]);
|
||||
}
|
||||
}
|
||||
return queryBuilder.expressionMap.insertColumns;
|
||||
},
|
||||
createTableColumnNamesWithPrimaryKey(queryBuilder) {
|
||||
const columnNames = this.createTableColumnNames(queryBuilder);
|
||||
if (!columnNames.includes('id')) {
|
||||
columnNames.unshift('id');
|
||||
}
|
||||
return columnNames;
|
||||
},
|
||||
async insertOne(entity, findOptions?) {
|
||||
const queryBuilder = this.createQueryBuilder().insert().values(entity);
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const mainAlias = queryBuilder.expressionMap.mainAlias!;
|
||||
const name = mainAlias.name;
|
||||
mainAlias.name = 't';
|
||||
const columnNames = this.createTableColumnNamesWithPrimaryKey(queryBuilder);
|
||||
queryBuilder.returning(columnNames.reduce((a, c) => `${a}, ${queryBuilder.escape(c)}`, '').slice(2));
|
||||
const builder = this.createQueryBuilder().addCommonTableExpression(queryBuilder, 'cte', { columnNames });
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
builder.expressionMap.mainAlias!.tablePath = 'cte';
|
||||
this.selectAliasColumnNames(queryBuilder, builder);
|
||||
if (findOptions) {
|
||||
builder.setFindOptions(findOptions);
|
||||
}
|
||||
const raw = await builder.execute();
|
||||
mainAlias.name = name;
|
||||
const relationId = await new RelationIdLoader(builder.connection, this.queryRunner, builder.expressionMap.relationIdAttributes).load(raw);
|
||||
const relationCount = await new RelationCountLoader(builder.connection, this.queryRunner, builder.expressionMap.relationCountAttributes).load(raw);
|
||||
const result = new RawSqlResultsToEntityTransformer(builder.expressionMap, builder.connection.driver, relationId, relationCount, this.queryRunner).transform(raw, mainAlias);
|
||||
return result[0];
|
||||
},
|
||||
selectAliasColumnNames(queryBuilder, builder) {
|
||||
let selectOrAddSelect = (selection: string, selectionAliasName?: string) => {
|
||||
selectOrAddSelect = (selection, selectionAliasName) => builder.addSelect(selection, selectionAliasName);
|
||||
return builder.select(selection, selectionAliasName);
|
||||
};
|
||||
for (const columnName of this.createTableColumnNamesWithPrimaryKey(queryBuilder)) {
|
||||
selectOrAddSelect(`${builder.alias}.${columnName}`, `${builder.alias}_${columnName}`);
|
||||
}
|
||||
},
|
||||
} satisfies MiRepository<ObjectLiteral>;
|
||||
|
||||
export {
|
||||
MiAbuseUserReport,
|
||||
MiAbuseReportNotificationRecipient,
|
||||
MiAccessToken,
|
||||
MiAd,
|
||||
MiAnnouncement,
|
||||
|
@ -136,6 +208,7 @@ export {
|
|||
MiUserPublickey,
|
||||
MiUserSecurityKey,
|
||||
MiWebhook,
|
||||
MiSystemWebhook,
|
||||
MiChannel,
|
||||
MiRetentionAggregation,
|
||||
MiRole,
|
||||
|
@ -147,72 +220,74 @@ export {
|
|||
MiReversiGame,
|
||||
};
|
||||
|
||||
export type AbuseUserReportsRepository = Repository<MiAbuseUserReport>;
|
||||
export type AccessTokensRepository = Repository<MiAccessToken>;
|
||||
export type AdsRepository = Repository<MiAd>;
|
||||
export type AnnouncementsRepository = Repository<MiAnnouncement>;
|
||||
export type AnnouncementReadsRepository = Repository<MiAnnouncementRead>;
|
||||
export type AntennasRepository = Repository<MiAntenna>;
|
||||
export type AppsRepository = Repository<MiApp>;
|
||||
export type AvatarDecorationsRepository = Repository<MiAvatarDecoration>;
|
||||
export type AuthSessionsRepository = Repository<MiAuthSession>;
|
||||
export type BlockingsRepository = Repository<MiBlocking>;
|
||||
export type ChannelFollowingsRepository = Repository<MiChannelFollowing>;
|
||||
export type ChannelFavoritesRepository = Repository<MiChannelFavorite>;
|
||||
export type ClipsRepository = Repository<MiClip>;
|
||||
export type ClipNotesRepository = Repository<MiClipNote>;
|
||||
export type ClipFavoritesRepository = Repository<MiClipFavorite>;
|
||||
export type DriveFilesRepository = Repository<MiDriveFile>;
|
||||
export type DriveFoldersRepository = Repository<MiDriveFolder>;
|
||||
export type EmojisRepository = Repository<MiEmoji>;
|
||||
export type AbuseUserReportsRepository = Repository<MiAbuseUserReport> & MiRepository<MiAbuseUserReport>;
|
||||
export type AbuseReportNotificationRecipientRepository = Repository<MiAbuseReportNotificationRecipient> & MiRepository<MiAbuseReportNotificationRecipient>;
|
||||
export type AccessTokensRepository = Repository<MiAccessToken> & MiRepository<MiAccessToken>;
|
||||
export type AdsRepository = Repository<MiAd> & MiRepository<MiAd>;
|
||||
export type AnnouncementsRepository = Repository<MiAnnouncement> & MiRepository<MiAnnouncement>;
|
||||
export type AnnouncementReadsRepository = Repository<MiAnnouncementRead> & MiRepository<MiAnnouncementRead>;
|
||||
export type AntennasRepository = Repository<MiAntenna> & MiRepository<MiAntenna>;
|
||||
export type AppsRepository = Repository<MiApp> & MiRepository<MiApp>;
|
||||
export type AvatarDecorationsRepository = Repository<MiAvatarDecoration> & MiRepository<MiAvatarDecoration>;
|
||||
export type AuthSessionsRepository = Repository<MiAuthSession> & MiRepository<MiAuthSession>;
|
||||
export type BlockingsRepository = Repository<MiBlocking> & MiRepository<MiBlocking>;
|
||||
export type ChannelFollowingsRepository = Repository<MiChannelFollowing> & MiRepository<MiChannelFollowing>;
|
||||
export type ChannelFavoritesRepository = Repository<MiChannelFavorite> & MiRepository<MiChannelFavorite>;
|
||||
export type ClipsRepository = Repository<MiClip> & MiRepository<MiClip>;
|
||||
export type ClipNotesRepository = Repository<MiClipNote> & MiRepository<MiClipNote>;
|
||||
export type ClipFavoritesRepository = Repository<MiClipFavorite> & MiRepository<MiClipFavorite>;
|
||||
export type DriveFilesRepository = Repository<MiDriveFile> & MiRepository<MiDriveFile>;
|
||||
export type DriveFoldersRepository = Repository<MiDriveFolder> & MiRepository<MiDriveFolder>;
|
||||
export type EmojisRepository = Repository<MiEmoji> & MiRepository<MiEmoji>;
|
||||
export type EmojiRequestsRepository = Repository<MiEmojiRequest>;
|
||||
export type FollowingsRepository = Repository<MiFollowing>;
|
||||
export type FollowRequestsRepository = Repository<MiFollowRequest>;
|
||||
export type GalleryLikesRepository = Repository<MiGalleryLike>;
|
||||
export type GalleryPostsRepository = Repository<MiGalleryPost>;
|
||||
export type HashtagsRepository = Repository<MiHashtag>;
|
||||
export type InstancesRepository = Repository<MiInstance>;
|
||||
export type MetasRepository = Repository<MiMeta>;
|
||||
export type ModerationLogsRepository = Repository<MiModerationLog>;
|
||||
export type MutingsRepository = Repository<MiMuting>;
|
||||
export type RenoteMutingsRepository = Repository<MiRenoteMuting>;
|
||||
export type NotesRepository = Repository<MiNote>;
|
||||
export type NoteFavoritesRepository = Repository<MiNoteFavorite>;
|
||||
export type NoteReactionsRepository = Repository<MiNoteReaction>;
|
||||
export type NoteThreadMutingsRepository = Repository<MiNoteThreadMuting>;
|
||||
export type NoteUnreadsRepository = Repository<MiNoteUnread>;
|
||||
export type FollowingsRepository = Repository<MiFollowing> & MiRepository<MiFollowing>;
|
||||
export type FollowRequestsRepository = Repository<MiFollowRequest> & MiRepository<MiFollowRequest>;
|
||||
export type GalleryLikesRepository = Repository<MiGalleryLike> & MiRepository<MiGalleryLike>;
|
||||
export type GalleryPostsRepository = Repository<MiGalleryPost> & MiRepository<MiGalleryPost>;
|
||||
export type HashtagsRepository = Repository<MiHashtag> & MiRepository<MiHashtag>;
|
||||
export type InstancesRepository = Repository<MiInstance> & MiRepository<MiInstance>;
|
||||
export type MetasRepository = Repository<MiMeta> & MiRepository<MiMeta>;
|
||||
export type ModerationLogsRepository = Repository<MiModerationLog> & MiRepository<MiModerationLog>;
|
||||
export type MutingsRepository = Repository<MiMuting> & MiRepository<MiMuting>;
|
||||
export type RenoteMutingsRepository = Repository<MiRenoteMuting> & MiRepository<MiRenoteMuting>;
|
||||
export type NotesRepository = Repository<MiNote> & MiRepository<MiNote>;
|
||||
export type NoteFavoritesRepository = Repository<MiNoteFavorite> & MiRepository<MiNoteFavorite>;
|
||||
export type NoteReactionsRepository = Repository<MiNoteReaction> & MiRepository<MiNoteReaction>;
|
||||
export type NoteThreadMutingsRepository = Repository<MiNoteThreadMuting> & MiRepository<MiNoteThreadMuting>;
|
||||
export type NoteUnreadsRepository = Repository<MiNoteUnread> & MiRepository<MiNoteUnread>;
|
||||
export type ScheduledNotesRepository = Repository<MiScheduledNote>;
|
||||
export type PagesRepository = Repository<MiPage>;
|
||||
export type PageLikesRepository = Repository<MiPageLike>;
|
||||
export type PasswordResetRequestsRepository = Repository<MiPasswordResetRequest>;
|
||||
export type PollsRepository = Repository<MiPoll>;
|
||||
export type PollVotesRepository = Repository<MiPollVote>;
|
||||
export type PromoNotesRepository = Repository<MiPromoNote>;
|
||||
export type PromoReadsRepository = Repository<MiPromoRead>;
|
||||
export type RegistrationTicketsRepository = Repository<MiRegistrationTicket>;
|
||||
export type RegistryItemsRepository = Repository<MiRegistryItem>;
|
||||
export type RelaysRepository = Repository<MiRelay>;
|
||||
export type SigninsRepository = Repository<MiSignin>;
|
||||
export type SwSubscriptionsRepository = Repository<MiSwSubscription>;
|
||||
export type UsedUsernamesRepository = Repository<MiUsedUsername>;
|
||||
export type UsersRepository = Repository<MiUser>;
|
||||
export type UserIpsRepository = Repository<MiUserIp>;
|
||||
export type UserKeypairsRepository = Repository<MiUserKeypair>;
|
||||
export type UserListsRepository = Repository<MiUserList>;
|
||||
export type UserListFavoritesRepository = Repository<MiUserListFavorite>;
|
||||
export type UserListMembershipsRepository = Repository<MiUserListMembership>;
|
||||
export type UserNotePiningsRepository = Repository<MiUserNotePining>;
|
||||
export type UserPendingsRepository = Repository<MiUserPending>;
|
||||
export type UserProfilesRepository = Repository<MiUserProfile>;
|
||||
export type UserPublickeysRepository = Repository<MiUserPublickey>;
|
||||
export type UserSecurityKeysRepository = Repository<MiUserSecurityKey>;
|
||||
export type WebhooksRepository = Repository<MiWebhook>;
|
||||
export type ChannelsRepository = Repository<MiChannel>;
|
||||
export type RetentionAggregationsRepository = Repository<MiRetentionAggregation>;
|
||||
export type RolesRepository = Repository<MiRole>;
|
||||
export type RoleAssignmentsRepository = Repository<MiRoleAssignment>;
|
||||
export type FlashsRepository = Repository<MiFlash>;
|
||||
export type FlashLikesRepository = Repository<MiFlashLike>;
|
||||
export type UserMemoRepository = Repository<MiUserMemo>;
|
||||
export type BubbleGameRecordsRepository = Repository<MiBubbleGameRecord>;
|
||||
export type ReversiGamesRepository = Repository<MiReversiGame>;
|
||||
export type PagesRepository = Repository<MiPage> & MiRepository<MiPage>;
|
||||
export type PageLikesRepository = Repository<MiPageLike> & MiRepository<MiPageLike>;
|
||||
export type PasswordResetRequestsRepository = Repository<MiPasswordResetRequest> & MiRepository<MiPasswordResetRequest>;
|
||||
export type PollsRepository = Repository<MiPoll> & MiRepository<MiPoll>;
|
||||
export type PollVotesRepository = Repository<MiPollVote> & MiRepository<MiPollVote>;
|
||||
export type PromoNotesRepository = Repository<MiPromoNote> & MiRepository<MiPromoNote>;
|
||||
export type PromoReadsRepository = Repository<MiPromoRead> & MiRepository<MiPromoRead>;
|
||||
export type RegistrationTicketsRepository = Repository<MiRegistrationTicket> & MiRepository<MiRegistrationTicket>;
|
||||
export type RegistryItemsRepository = Repository<MiRegistryItem> & MiRepository<MiRegistryItem>;
|
||||
export type RelaysRepository = Repository<MiRelay> & MiRepository<MiRelay>;
|
||||
export type SigninsRepository = Repository<MiSignin> & MiRepository<MiSignin>;
|
||||
export type SwSubscriptionsRepository = Repository<MiSwSubscription> & MiRepository<MiSwSubscription>;
|
||||
export type UsedUsernamesRepository = Repository<MiUsedUsername> & MiRepository<MiUsedUsername>;
|
||||
export type UsersRepository = Repository<MiUser> & MiRepository<MiUser>;
|
||||
export type UserIpsRepository = Repository<MiUserIp> & MiRepository<MiUserIp>;
|
||||
export type UserKeypairsRepository = Repository<MiUserKeypair> & MiRepository<MiUserKeypair>;
|
||||
export type UserListsRepository = Repository<MiUserList> & MiRepository<MiUserList>;
|
||||
export type UserListFavoritesRepository = Repository<MiUserListFavorite> & MiRepository<MiUserListFavorite>;
|
||||
export type UserListMembershipsRepository = Repository<MiUserListMembership> & MiRepository<MiUserListMembership>;
|
||||
export type UserNotePiningsRepository = Repository<MiUserNotePining> & MiRepository<MiUserNotePining>;
|
||||
export type UserPendingsRepository = Repository<MiUserPending> & MiRepository<MiUserPending>;
|
||||
export type UserProfilesRepository = Repository<MiUserProfile> & MiRepository<MiUserProfile>;
|
||||
export type UserPublickeysRepository = Repository<MiUserPublickey> & MiRepository<MiUserPublickey>;
|
||||
export type UserSecurityKeysRepository = Repository<MiUserSecurityKey> & MiRepository<MiUserSecurityKey>;
|
||||
export type WebhooksRepository = Repository<MiWebhook> & MiRepository<MiWebhook>;
|
||||
export type SystemWebhooksRepository = Repository<MiSystemWebhook> & MiRepository<MiWebhook>;
|
||||
export type ChannelsRepository = Repository<MiChannel> & MiRepository<MiChannel>;
|
||||
export type RetentionAggregationsRepository = Repository<MiRetentionAggregation> & MiRepository<MiRetentionAggregation>;
|
||||
export type RolesRepository = Repository<MiRole> & MiRepository<MiRole>;
|
||||
export type RoleAssignmentsRepository = Repository<MiRoleAssignment> & MiRepository<MiRoleAssignment>;
|
||||
export type FlashsRepository = Repository<MiFlash> & MiRepository<MiFlash>;
|
||||
export type FlashLikesRepository = Repository<MiFlashLike> & MiRepository<MiFlashLike>;
|
||||
export type UserMemoRepository = Repository<MiUserMemo> & MiRepository<MiUserMemo>;
|
||||
export type BubbleGameRecordsRepository = Repository<MiBubbleGameRecord> & MiRepository<MiBubbleGameRecord>;
|
||||
export type ReversiGamesRepository = Repository<MiReversiGame> & MiRepository<MiReversiGame>;
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export const packedAbuseReportNotificationRecipientSchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
isActive: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
updatedAt: {
|
||||
type: 'string',
|
||||
format: 'date-time',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
method: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
enum: ['email', 'webhook'],
|
||||
},
|
||||
userId: {
|
||||
type: 'string',
|
||||
optional: true, nullable: false,
|
||||
},
|
||||
user: {
|
||||
type: 'object',
|
||||
optional: true, nullable: false,
|
||||
ref: 'UserLite',
|
||||
},
|
||||
systemWebhookId: {
|
||||
type: 'string',
|
||||
optional: true, nullable: false,
|
||||
},
|
||||
systemWebhook: {
|
||||
type: 'object',
|
||||
optional: true, nullable: false,
|
||||
ref: 'SystemWebhook',
|
||||
},
|
||||
},
|
||||
} as const;
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { systemWebhookEventTypes } from '@/models/SystemWebhook.js';
|
||||
|
||||
export const packedSystemWebhookSchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
isActive: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
updatedAt: {
|
||||
type: 'string',
|
||||
format: 'date-time',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
latestSentAt: {
|
||||
type: 'string',
|
||||
format: 'date-time',
|
||||
optional: false, nullable: true,
|
||||
},
|
||||
latestStatus: {
|
||||
type: 'number',
|
||||
optional: false, nullable: true,
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
on: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
enum: systemWebhookEventTypes,
|
||||
},
|
||||
},
|
||||
url: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
secret: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
},
|
||||
} as const;
|
|
@ -5,13 +5,12 @@
|
|||
|
||||
// https://github.com/typeorm/typeorm/issues/2400
|
||||
import pg from 'pg';
|
||||
pg.types.setTypeParser(20, Number);
|
||||
|
||||
import { DataSource, Logger } from 'typeorm';
|
||||
import * as highlight from 'cli-highlight';
|
||||
import { entities as charts } from '@/core/chart/entities.js';
|
||||
|
||||
import { MiAbuseUserReport } from '@/models/AbuseUserReport.js';
|
||||
import { MiAbuseReportNotificationRecipient } from '@/models/AbuseReportNotificationRecipient.js';
|
||||
import { MiAccessToken } from '@/models/AccessToken.js';
|
||||
import { MiAd } from '@/models/Ad.js';
|
||||
import { MiAnnouncement } from '@/models/Announcement.js';
|
||||
|
@ -70,6 +69,7 @@ import { MiUserProfile } from '@/models/UserProfile.js';
|
|||
import { MiUserPublickey } from '@/models/UserPublickey.js';
|
||||
import { MiUserSecurityKey } from '@/models/UserSecurityKey.js';
|
||||
import { MiWebhook } from '@/models/Webhook.js';
|
||||
import { MiSystemWebhook } from '@/models/SystemWebhook.js';
|
||||
import { MiChannel } from '@/models/Channel.js';
|
||||
import { MiRetentionAggregation } from '@/models/RetentionAggregation.js';
|
||||
import { MiRole } from '@/models/Role.js';
|
||||
|
@ -85,9 +85,11 @@ import { Config } from '@/config.js';
|
|||
import MisskeyLogger from '@/logger.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
|
||||
pg.types.setTypeParser(20, Number);
|
||||
|
||||
export const dbLogger = new MisskeyLogger('db');
|
||||
|
||||
const sqlLogger = dbLogger.createSubLogger('sql', 'gray', false);
|
||||
const sqlLogger = dbLogger.createSubLogger('sql', 'gray');
|
||||
|
||||
class MyCustomLogger implements Logger {
|
||||
@bindThis
|
||||
|
@ -171,6 +173,7 @@ export const entities = [
|
|||
MiHashtag,
|
||||
MiSwSubscription,
|
||||
MiAbuseUserReport,
|
||||
MiAbuseReportNotificationRecipient,
|
||||
MiRegistrationTicket,
|
||||
MiSignin,
|
||||
MiModerationLog,
|
||||
|
@ -189,6 +192,7 @@ export const entities = [
|
|||
MiPasswordResetRequest,
|
||||
MiUserPending,
|
||||
MiWebhook,
|
||||
MiSystemWebhook,
|
||||
MiUserIp,
|
||||
MiRetentionAggregation,
|
||||
MiRole,
|
||||
|
|
|
@ -12,7 +12,8 @@ import { DeliverProcessorService } from './processors/DeliverProcessorService.js
|
|||
import { EndedPollNotificationProcessorService } from './processors/EndedPollNotificationProcessorService.js';
|
||||
import { ScheduleNotePostProcessorService } from './processors/ScheduleNotePostProcessorService.js';
|
||||
import { InboxProcessorService } from './processors/InboxProcessorService.js';
|
||||
import { WebhookDeliverProcessorService } from './processors/WebhookDeliverProcessorService.js';
|
||||
import { UserWebhookDeliverProcessorService } from './processors/UserWebhookDeliverProcessorService.js';
|
||||
import { SystemWebhookDeliverProcessorService } from './processors/SystemWebhookDeliverProcessorService.js';
|
||||
import { CheckExpiredMutingsProcessorService } from './processors/CheckExpiredMutingsProcessorService.js';
|
||||
import { CleanChartsProcessorService } from './processors/CleanChartsProcessorService.js';
|
||||
import { CleanProcessorService } from './processors/CleanProcessorService.js';
|
||||
|
@ -72,7 +73,8 @@ import { RelationshipProcessorService } from './processors/RelationshipProcessor
|
|||
DeleteFileProcessorService,
|
||||
CleanRemoteFilesProcessorService,
|
||||
RelationshipProcessorService,
|
||||
WebhookDeliverProcessorService,
|
||||
UserWebhookDeliverProcessorService,
|
||||
SystemWebhookDeliverProcessorService,
|
||||
EndedPollNotificationProcessorService,
|
||||
ScheduleNotePostProcessorService,
|
||||
DeliverProcessorService,
|
||||
|
|
|
@ -5,11 +5,13 @@
|
|||
|
||||
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
|
||||
import * as Bull from 'bullmq';
|
||||
import * as Sentry from '@sentry/node';
|
||||
import type { Config } from '@/config.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type Logger from '@/logger.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { WebhookDeliverProcessorService } from './processors/WebhookDeliverProcessorService.js';
|
||||
import { UserWebhookDeliverProcessorService } from './processors/UserWebhookDeliverProcessorService.js';
|
||||
import { SystemWebhookDeliverProcessorService } from './processors/SystemWebhookDeliverProcessorService.js';
|
||||
import { EndedPollNotificationProcessorService } from './processors/EndedPollNotificationProcessorService.js';
|
||||
import { ScheduleNotePostProcessorService } from './processors/ScheduleNotePostProcessorService.js';
|
||||
import { DeliverProcessorService } from './processors/DeliverProcessorService.js';
|
||||
|
@ -76,7 +78,8 @@ export class QueueProcessorService implements OnApplicationShutdown {
|
|||
private dbQueueWorker: Bull.Worker;
|
||||
private deliverQueueWorker: Bull.Worker;
|
||||
private inboxQueueWorker: Bull.Worker;
|
||||
private webhookDeliverQueueWorker: Bull.Worker;
|
||||
private userWebhookDeliverQueueWorker: Bull.Worker;
|
||||
private systemWebhookDeliverQueueWorker: Bull.Worker;
|
||||
private relationshipQueueWorker: Bull.Worker;
|
||||
private objectStorageQueueWorker: Bull.Worker;
|
||||
private endedPollNotificationQueueWorker: Bull.Worker;
|
||||
|
@ -87,7 +90,8 @@ export class QueueProcessorService implements OnApplicationShutdown {
|
|||
private config: Config,
|
||||
|
||||
private queueLoggerService: QueueLoggerService,
|
||||
private webhookDeliverProcessorService: WebhookDeliverProcessorService,
|
||||
private userWebhookDeliverProcessorService: UserWebhookDeliverProcessorService,
|
||||
private systemWebhookDeliverProcessorService: SystemWebhookDeliverProcessorService,
|
||||
private endedPollNotificationProcessorService: EndedPollNotificationProcessorService,
|
||||
private scheduleNotePostProcessorService: ScheduleNotePostProcessorService,
|
||||
private deliverProcessorService: DeliverProcessorService,
|
||||
|
@ -138,192 +142,352 @@ export class QueueProcessorService implements OnApplicationShutdown {
|
|||
}
|
||||
|
||||
//#region system
|
||||
this.systemQueueWorker = new Bull.Worker(QUEUE.SYSTEM, (job) => {
|
||||
switch (job.name) {
|
||||
case 'tickCharts': return this.tickChartsProcessorService.process();
|
||||
case 'resyncCharts': return this.resyncChartsProcessorService.process();
|
||||
case 'cleanCharts': return this.cleanChartsProcessorService.process();
|
||||
case 'aggregateRetention': return this.aggregateRetentionProcessorService.process();
|
||||
case 'checkExpiredMutings': return this.checkExpiredMutingsProcessorService.process();
|
||||
case 'clean': return this.cleanProcessorService.process();
|
||||
default: throw new Error(`unrecognized job type ${job.name} for system`);
|
||||
}
|
||||
}, {
|
||||
...baseQueueOptions(this.config, QUEUE.SYSTEM),
|
||||
autorun: false,
|
||||
});
|
||||
{
|
||||
const processer = (job: Bull.Job) => {
|
||||
switch (job.name) {
|
||||
case 'tickCharts': return this.tickChartsProcessorService.process();
|
||||
case 'resyncCharts': return this.resyncChartsProcessorService.process();
|
||||
case 'cleanCharts': return this.cleanChartsProcessorService.process();
|
||||
case 'aggregateRetention': return this.aggregateRetentionProcessorService.process();
|
||||
case 'checkExpiredMutings': return this.checkExpiredMutingsProcessorService.process();
|
||||
case 'clean': return this.cleanProcessorService.process();
|
||||
default: throw new Error(`unrecognized job type ${job.name} for system`);
|
||||
}
|
||||
};
|
||||
|
||||
const systemLogger = this.logger.createSubLogger('system');
|
||||
this.systemQueueWorker = new Bull.Worker(QUEUE.SYSTEM, (job) => {
|
||||
if (this.config.sentryForBackend) {
|
||||
return Sentry.startSpan({ name: 'Queue: System: ' + job.name }, () => processer(job));
|
||||
} else {
|
||||
return processer(job);
|
||||
}
|
||||
}, {
|
||||
...baseQueueOptions(this.config, QUEUE.SYSTEM),
|
||||
autorun: false,
|
||||
});
|
||||
|
||||
this.systemQueueWorker
|
||||
.on('active', (job) => systemLogger.debug(`active id=${job.id}`))
|
||||
.on('completed', (job, result) => systemLogger.debug(`completed(${result}) id=${job.id}`))
|
||||
.on('failed', (job, err) => systemLogger.warn(`failed(${err.stack}) id=${job ? job.id : '-'}`, { job, e: renderError(err) }))
|
||||
.on('error', (err: Error) => systemLogger.error(`error ${err.stack}`, { e: renderError(err) }))
|
||||
.on('stalled', (jobId) => systemLogger.warn(`stalled id=${jobId}`));
|
||||
const logger = this.logger.createSubLogger('system');
|
||||
|
||||
this.systemQueueWorker
|
||||
.on('active', (job) => logger.debug(`active id=${job.id}`))
|
||||
.on('completed', (job, result) => logger.debug(`completed(${result}) id=${job.id}`))
|
||||
.on('failed', (job, err: Error) => {
|
||||
logger.error(`failed(${err.stack}) id=${job ? job.id : '-'}`, { job, e: renderError(err) });
|
||||
if (config.sentryForBackend) {
|
||||
Sentry.captureMessage(`Queue: System: ${job?.name ?? '?'}: ${err.message}`, {
|
||||
level: 'error',
|
||||
extra: { job, err },
|
||||
});
|
||||
}
|
||||
})
|
||||
.on('error', (err: Error) => logger.error(`error ${err.stack}`, { e: renderError(err) }))
|
||||
.on('stalled', (jobId) => logger.warn(`stalled id=${jobId}`));
|
||||
}
|
||||
//#endregion
|
||||
|
||||
//#region db
|
||||
this.dbQueueWorker = new Bull.Worker(QUEUE.DB, (job) => {
|
||||
switch (job.name) {
|
||||
case 'deleteDriveFiles': return this.deleteDriveFilesProcessorService.process(job);
|
||||
case 'exportCustomEmojis': return this.exportCustomEmojisProcessorService.process(job);
|
||||
case 'exportNotes': return this.exportNotesProcessorService.process(job);
|
||||
case 'exportClips': return this.exportClipsProcessorService.process(job);
|
||||
case 'exportFavorites': return this.exportFavoritesProcessorService.process(job);
|
||||
case 'exportFollowing': return this.exportFollowingProcessorService.process(job);
|
||||
case 'exportMuting': return this.exportMutingProcessorService.process(job);
|
||||
case 'exportBlocking': return this.exportBlockingProcessorService.process(job);
|
||||
case 'exportUserLists': return this.exportUserListsProcessorService.process(job);
|
||||
case 'exportAntennas': return this.exportAntennasProcessorService.process(job);
|
||||
case 'importFollowing': return this.importFollowingProcessorService.process(job);
|
||||
case 'importFollowingToDb': return this.importFollowingProcessorService.processDb(job);
|
||||
case 'importMuting': return this.importMutingProcessorService.process(job);
|
||||
case 'importBlocking': return this.importBlockingProcessorService.process(job);
|
||||
case 'importBlockingToDb': return this.importBlockingProcessorService.processDb(job);
|
||||
case 'importUserLists': return this.importUserListsProcessorService.process(job);
|
||||
case 'importCustomEmojis': return this.importCustomEmojisProcessorService.process(job);
|
||||
case 'importAntennas': return this.importAntennasProcessorService.process(job);
|
||||
case 'deleteAccount': return this.deleteAccountProcessorService.process(job);
|
||||
default: throw new Error(`unrecognized job type ${job.name} for db`);
|
||||
}
|
||||
}, {
|
||||
...baseQueueOptions(this.config, QUEUE.DB),
|
||||
autorun: false,
|
||||
});
|
||||
{
|
||||
const processer = (job: Bull.Job) => {
|
||||
switch (job.name) {
|
||||
case 'deleteDriveFiles': return this.deleteDriveFilesProcessorService.process(job);
|
||||
case 'exportCustomEmojis': return this.exportCustomEmojisProcessorService.process(job);
|
||||
case 'exportNotes': return this.exportNotesProcessorService.process(job);
|
||||
case 'exportClips': return this.exportClipsProcessorService.process(job);
|
||||
case 'exportFavorites': return this.exportFavoritesProcessorService.process(job);
|
||||
case 'exportFollowing': return this.exportFollowingProcessorService.process(job);
|
||||
case 'exportMuting': return this.exportMutingProcessorService.process(job);
|
||||
case 'exportBlocking': return this.exportBlockingProcessorService.process(job);
|
||||
case 'exportUserLists': return this.exportUserListsProcessorService.process(job);
|
||||
case 'exportAntennas': return this.exportAntennasProcessorService.process(job);
|
||||
case 'importFollowing': return this.importFollowingProcessorService.process(job);
|
||||
case 'importFollowingToDb': return this.importFollowingProcessorService.processDb(job);
|
||||
case 'importMuting': return this.importMutingProcessorService.process(job);
|
||||
case 'importBlocking': return this.importBlockingProcessorService.process(job);
|
||||
case 'importBlockingToDb': return this.importBlockingProcessorService.processDb(job);
|
||||
case 'importUserLists': return this.importUserListsProcessorService.process(job);
|
||||
case 'importCustomEmojis': return this.importCustomEmojisProcessorService.process(job);
|
||||
case 'importAntennas': return this.importAntennasProcessorService.process(job);
|
||||
case 'deleteAccount': return this.deleteAccountProcessorService.process(job);
|
||||
default: throw new Error(`unrecognized job type ${job.name} for db`);
|
||||
}
|
||||
};
|
||||
|
||||
const dbLogger = this.logger.createSubLogger('db');
|
||||
this.dbQueueWorker = new Bull.Worker(QUEUE.DB, (job) => {
|
||||
if (this.config.sentryForBackend) {
|
||||
return Sentry.startSpan({ name: 'Queue: DB: ' + job.name }, () => processer(job));
|
||||
} else {
|
||||
return processer(job);
|
||||
}
|
||||
}, {
|
||||
...baseQueueOptions(this.config, QUEUE.DB),
|
||||
autorun: false,
|
||||
});
|
||||
|
||||
this.dbQueueWorker
|
||||
.on('active', (job) => dbLogger.debug(`active id=${job.id}`))
|
||||
.on('completed', (job, result) => dbLogger.debug(`completed(${result}) id=${job.id}`))
|
||||
.on('failed', (job, err) => dbLogger.warn(`failed(${err.stack}) id=${job ? job.id : '-'}`, { job, e: renderError(err) }))
|
||||
.on('error', (err: Error) => dbLogger.error(`error ${err.stack}`, { e: renderError(err) }))
|
||||
.on('stalled', (jobId) => dbLogger.warn(`stalled id=${jobId}`));
|
||||
const logger = this.logger.createSubLogger('db');
|
||||
|
||||
this.dbQueueWorker
|
||||
.on('active', (job) => logger.debug(`active id=${job.id}`))
|
||||
.on('completed', (job, result) => logger.debug(`completed(${result}) id=${job.id}`))
|
||||
.on('failed', (job, err) => {
|
||||
logger.error(`failed(${err.stack}) id=${job ? job.id : '-'}`, { job, e: renderError(err) });
|
||||
if (config.sentryForBackend) {
|
||||
Sentry.captureMessage(`Queue: DB: ${job?.name ?? '?'}: ${err.message}`, {
|
||||
level: 'error',
|
||||
extra: { job, err },
|
||||
});
|
||||
}
|
||||
})
|
||||
.on('error', (err: Error) => logger.error(`error ${err.stack}`, { e: renderError(err) }))
|
||||
.on('stalled', (jobId) => logger.warn(`stalled id=${jobId}`));
|
||||
}
|
||||
//#endregion
|
||||
|
||||
//#region deliver
|
||||
this.deliverQueueWorker = new Bull.Worker(QUEUE.DELIVER, (job) => this.deliverProcessorService.process(job), {
|
||||
...baseQueueOptions(this.config, QUEUE.DELIVER),
|
||||
autorun: false,
|
||||
concurrency: this.config.deliverJobConcurrency ?? 128,
|
||||
limiter: {
|
||||
max: this.config.deliverJobPerSec ?? 128,
|
||||
duration: 1000,
|
||||
},
|
||||
settings: {
|
||||
backoffStrategy: httpRelatedBackoff,
|
||||
},
|
||||
});
|
||||
{
|
||||
this.deliverQueueWorker = new Bull.Worker(QUEUE.DELIVER, (job) => {
|
||||
if (this.config.sentryForBackend) {
|
||||
return Sentry.startSpan({ name: 'Queue: Deliver' }, () => this.deliverProcessorService.process(job));
|
||||
} else {
|
||||
return this.deliverProcessorService.process(job);
|
||||
}
|
||||
}, {
|
||||
...baseQueueOptions(this.config, QUEUE.DELIVER),
|
||||
autorun: false,
|
||||
concurrency: this.config.deliverJobConcurrency ?? 128,
|
||||
limiter: {
|
||||
max: this.config.deliverJobPerSec ?? 128,
|
||||
duration: 1000,
|
||||
},
|
||||
settings: {
|
||||
backoffStrategy: httpRelatedBackoff,
|
||||
},
|
||||
});
|
||||
|
||||
const deliverLogger = this.logger.createSubLogger('deliver');
|
||||
const logger = this.logger.createSubLogger('deliver');
|
||||
|
||||
this.deliverQueueWorker
|
||||
.on('active', (job) => deliverLogger.debug(`active ${getJobInfo(job, true)} to=${job.data.to}`))
|
||||
.on('completed', (job, result) => deliverLogger.debug(`completed(${result}) ${getJobInfo(job, true)} to=${job.data.to}`))
|
||||
.on('failed', (job, err) => deliverLogger.warn(`failed(${err.stack}) ${getJobInfo(job)} to=${job ? job.data.to : '-'}`))
|
||||
.on('error', (err: Error) => deliverLogger.error(`error ${err.stack}`, { e: renderError(err) }))
|
||||
.on('stalled', (jobId) => deliverLogger.warn(`stalled id=${jobId}`));
|
||||
this.deliverQueueWorker
|
||||
.on('active', (job) => logger.debug(`active ${getJobInfo(job, true)} to=${job.data.to}`))
|
||||
.on('completed', (job, result) => logger.debug(`completed(${result}) ${getJobInfo(job, true)} to=${job.data.to}`))
|
||||
.on('failed', (job, err) => {
|
||||
logger.error(`failed(${err.stack}) ${getJobInfo(job)} to=${job ? job.data.to : '-'}`);
|
||||
if (config.sentryForBackend) {
|
||||
Sentry.captureMessage(`Queue: Deliver: ${err.message}`, {
|
||||
level: 'error',
|
||||
extra: { job, err },
|
||||
});
|
||||
}
|
||||
})
|
||||
.on('error', (err: Error) => logger.error(`error ${err.stack}`, { e: renderError(err) }))
|
||||
.on('stalled', (jobId) => logger.warn(`stalled id=${jobId}`));
|
||||
}
|
||||
//#endregion
|
||||
|
||||
//#region inbox
|
||||
this.inboxQueueWorker = new Bull.Worker(QUEUE.INBOX, (job) => this.inboxProcessorService.process(job), {
|
||||
...baseQueueOptions(this.config, QUEUE.INBOX),
|
||||
autorun: false,
|
||||
concurrency: this.config.inboxJobConcurrency ?? 16,
|
||||
limiter: {
|
||||
max: this.config.inboxJobPerSec ?? 32,
|
||||
duration: 1000,
|
||||
},
|
||||
settings: {
|
||||
backoffStrategy: httpRelatedBackoff,
|
||||
},
|
||||
});
|
||||
{
|
||||
this.inboxQueueWorker = new Bull.Worker(QUEUE.INBOX, (job) => {
|
||||
if (this.config.sentryForBackend) {
|
||||
return Sentry.startSpan({ name: 'Queue: Inbox' }, () => this.inboxProcessorService.process(job));
|
||||
} else {
|
||||
return this.inboxProcessorService.process(job);
|
||||
}
|
||||
}, {
|
||||
...baseQueueOptions(this.config, QUEUE.INBOX),
|
||||
autorun: false,
|
||||
concurrency: this.config.inboxJobConcurrency ?? 16,
|
||||
limiter: {
|
||||
max: this.config.inboxJobPerSec ?? 32,
|
||||
duration: 1000,
|
||||
},
|
||||
settings: {
|
||||
backoffStrategy: httpRelatedBackoff,
|
||||
},
|
||||
});
|
||||
|
||||
const inboxLogger = this.logger.createSubLogger('inbox');
|
||||
const logger = this.logger.createSubLogger('inbox');
|
||||
|
||||
this.inboxQueueWorker
|
||||
.on('active', (job) => inboxLogger.debug(`active ${getJobInfo(job, true)}`))
|
||||
.on('completed', (job, result) => inboxLogger.debug(`completed(${result}) ${getJobInfo(job, true)}`))
|
||||
.on('failed', (job, err) => inboxLogger.warn(`failed(${err.stack}) ${getJobInfo(job)} activity=${job ? (job.data.activity ? job.data.activity.id : 'none') : '-'}`, { job, e: renderError(err) }))
|
||||
.on('error', (err: Error) => inboxLogger.error(`error ${err.stack}`, { e: renderError(err) }))
|
||||
.on('stalled', (jobId) => inboxLogger.warn(`stalled id=${jobId}`));
|
||||
this.inboxQueueWorker
|
||||
.on('active', (job) => logger.debug(`active ${getJobInfo(job, true)}`))
|
||||
.on('completed', (job, result) => logger.debug(`completed(${result}) ${getJobInfo(job, true)}`))
|
||||
.on('failed', (job, err) => {
|
||||
logger.error(`failed(${err.stack}) ${getJobInfo(job)} activity=${job ? (job.data.activity ? job.data.activity.id : 'none') : '-'}`, { job, e: renderError(err) });
|
||||
if (config.sentryForBackend) {
|
||||
Sentry.captureMessage(`Queue: Inbox: ${err.message}`, {
|
||||
level: 'error',
|
||||
extra: { job, err },
|
||||
});
|
||||
}
|
||||
})
|
||||
.on('error', (err: Error) => logger.error(`error ${err.stack}`, { e: renderError(err) }))
|
||||
.on('stalled', (jobId) => logger.warn(`stalled id=${jobId}`));
|
||||
}
|
||||
//#endregion
|
||||
|
||||
//#region webhook deliver
|
||||
this.webhookDeliverQueueWorker = new Bull.Worker(QUEUE.WEBHOOK_DELIVER, (job) => this.webhookDeliverProcessorService.process(job), {
|
||||
...baseQueueOptions(this.config, QUEUE.WEBHOOK_DELIVER),
|
||||
autorun: false,
|
||||
concurrency: 64,
|
||||
limiter: {
|
||||
max: 64,
|
||||
duration: 1000,
|
||||
},
|
||||
settings: {
|
||||
backoffStrategy: httpRelatedBackoff,
|
||||
},
|
||||
});
|
||||
//#region user-webhook deliver
|
||||
{
|
||||
this.userWebhookDeliverQueueWorker = new Bull.Worker(QUEUE.USER_WEBHOOK_DELIVER, (job) => {
|
||||
if (this.config.sentryForBackend) {
|
||||
return Sentry.startSpan({ name: 'Queue: UserWebhookDeliver' }, () => this.userWebhookDeliverProcessorService.process(job));
|
||||
} else {
|
||||
return this.userWebhookDeliverProcessorService.process(job);
|
||||
}
|
||||
}, {
|
||||
...baseQueueOptions(this.config, QUEUE.USER_WEBHOOK_DELIVER),
|
||||
autorun: false,
|
||||
concurrency: 64,
|
||||
limiter: {
|
||||
max: 64,
|
||||
duration: 1000,
|
||||
},
|
||||
settings: {
|
||||
backoffStrategy: httpRelatedBackoff,
|
||||
},
|
||||
});
|
||||
|
||||
const webhookLogger = this.logger.createSubLogger('webhook');
|
||||
const logger = this.logger.createSubLogger('user-webhook');
|
||||
|
||||
this.webhookDeliverQueueWorker
|
||||
.on('active', (job) => webhookLogger.debug(`active ${getJobInfo(job, true)} to=${job.data.to}`))
|
||||
.on('completed', (job, result) => webhookLogger.debug(`completed(${result}) ${getJobInfo(job, true)} to=${job.data.to}`))
|
||||
.on('failed', (job, err) => webhookLogger.warn(`failed(${err.stack}) ${getJobInfo(job)} to=${job ? job.data.to : '-'}`))
|
||||
.on('error', (err: Error) => webhookLogger.error(`error ${err.stack}`, { e: renderError(err) }))
|
||||
.on('stalled', (jobId) => webhookLogger.warn(`stalled id=${jobId}`));
|
||||
this.userWebhookDeliverQueueWorker
|
||||
.on('active', (job) => logger.debug(`active ${getJobInfo(job, true)} to=${job.data.to}`))
|
||||
.on('completed', (job, result) => logger.debug(`completed(${result}) ${getJobInfo(job, true)} to=${job.data.to}`))
|
||||
.on('failed', (job, err) => {
|
||||
logger.error(`failed(${err.stack}) ${getJobInfo(job)} to=${job ? job.data.to : '-'}`);
|
||||
if (config.sentryForBackend) {
|
||||
Sentry.captureMessage(`Queue: UserWebhookDeliver: ${err.message}`, {
|
||||
level: 'error',
|
||||
extra: { job, err },
|
||||
});
|
||||
}
|
||||
})
|
||||
.on('error', (err: Error) => logger.error(`error ${err.stack}`, { e: renderError(err) }))
|
||||
.on('stalled', (jobId) => logger.warn(`stalled id=${jobId}`));
|
||||
}
|
||||
//#endregion
|
||||
|
||||
//#region system-webhook deliver
|
||||
{
|
||||
this.systemWebhookDeliverQueueWorker = new Bull.Worker(QUEUE.SYSTEM_WEBHOOK_DELIVER, (job) => {
|
||||
if (this.config.sentryForBackend) {
|
||||
return Sentry.startSpan({ name: 'Queue: SystemWebhookDeliver' }, () => this.systemWebhookDeliverProcessorService.process(job));
|
||||
} else {
|
||||
return this.systemWebhookDeliverProcessorService.process(job);
|
||||
}
|
||||
}, {
|
||||
...baseQueueOptions(this.config, QUEUE.SYSTEM_WEBHOOK_DELIVER),
|
||||
autorun: false,
|
||||
concurrency: 16,
|
||||
limiter: {
|
||||
max: 16,
|
||||
duration: 1000,
|
||||
},
|
||||
settings: {
|
||||
backoffStrategy: httpRelatedBackoff,
|
||||
},
|
||||
});
|
||||
|
||||
const logger = this.logger.createSubLogger('system-webhook');
|
||||
|
||||
this.systemWebhookDeliverQueueWorker
|
||||
.on('active', (job) => logger.debug(`active ${getJobInfo(job, true)} to=${job.data.to}`))
|
||||
.on('completed', (job, result) => logger.debug(`completed(${result}) ${getJobInfo(job, true)} to=${job.data.to}`))
|
||||
.on('failed', (job, err) => {
|
||||
logger.error(`failed(${err.stack}) ${getJobInfo(job)} to=${job ? job.data.to : '-'}`);
|
||||
if (config.sentryForBackend) {
|
||||
Sentry.captureMessage(`Queue: SystemWebhookDeliver: ${err.message}`, {
|
||||
level: 'error',
|
||||
extra: { job, err },
|
||||
});
|
||||
}
|
||||
})
|
||||
.on('error', (err: Error) => logger.error(`error ${err.stack}`, { e: renderError(err) }))
|
||||
.on('stalled', (jobId) => logger.warn(`stalled id=${jobId}`));
|
||||
}
|
||||
//#endregion
|
||||
|
||||
//#region relationship
|
||||
this.relationshipQueueWorker = new Bull.Worker(QUEUE.RELATIONSHIP, (job) => {
|
||||
switch (job.name) {
|
||||
case 'follow': return this.relationshipProcessorService.processFollow(job);
|
||||
case 'unfollow': return this.relationshipProcessorService.processUnfollow(job);
|
||||
case 'block': return this.relationshipProcessorService.processBlock(job);
|
||||
case 'unblock': return this.relationshipProcessorService.processUnblock(job);
|
||||
default: throw new Error(`unrecognized job type ${job.name} for relationship`);
|
||||
}
|
||||
}, {
|
||||
...baseQueueOptions(this.config, QUEUE.RELATIONSHIP),
|
||||
autorun: false,
|
||||
concurrency: this.config.relationshipJobConcurrency ?? 16,
|
||||
limiter: {
|
||||
max: this.config.relationshipJobPerSec ?? 64,
|
||||
duration: 1000,
|
||||
},
|
||||
});
|
||||
{
|
||||
const processer = (job: Bull.Job) => {
|
||||
switch (job.name) {
|
||||
case 'follow': return this.relationshipProcessorService.processFollow(job);
|
||||
case 'unfollow': return this.relationshipProcessorService.processUnfollow(job);
|
||||
case 'block': return this.relationshipProcessorService.processBlock(job);
|
||||
case 'unblock': return this.relationshipProcessorService.processUnblock(job);
|
||||
default: throw new Error(`unrecognized job type ${job.name} for relationship`);
|
||||
}
|
||||
};
|
||||
|
||||
const relationshipLogger = this.logger.createSubLogger('relationship');
|
||||
this.relationshipQueueWorker = new Bull.Worker(QUEUE.RELATIONSHIP, (job) => {
|
||||
if (this.config.sentryForBackend) {
|
||||
return Sentry.startSpan({ name: 'Queue: Relationship: ' + job.name }, () => processer(job));
|
||||
} else {
|
||||
return processer(job);
|
||||
}
|
||||
}, {
|
||||
...baseQueueOptions(this.config, QUEUE.RELATIONSHIP),
|
||||
autorun: false,
|
||||
concurrency: this.config.relationshipJobConcurrency ?? 16,
|
||||
limiter: {
|
||||
max: this.config.relationshipJobPerSec ?? 64,
|
||||
duration: 1000,
|
||||
},
|
||||
});
|
||||
|
||||
this.relationshipQueueWorker
|
||||
.on('active', (job) => relationshipLogger.debug(`active id=${job.id}`))
|
||||
.on('completed', (job, result) => relationshipLogger.debug(`completed(${result}) id=${job.id}`))
|
||||
.on('failed', (job, err) => relationshipLogger.warn(`failed(${err.stack}) id=${job ? job.id : '-'}`, { job, e: renderError(err) }))
|
||||
.on('error', (err: Error) => relationshipLogger.error(`error ${err.stack}`, { e: renderError(err) }))
|
||||
.on('stalled', (jobId) => relationshipLogger.warn(`stalled id=${jobId}`));
|
||||
const logger = this.logger.createSubLogger('relationship');
|
||||
|
||||
this.relationshipQueueWorker
|
||||
.on('active', (job) => logger.debug(`active id=${job.id}`))
|
||||
.on('completed', (job, result) => logger.debug(`completed(${result}) id=${job.id}`))
|
||||
.on('failed', (job, err) => {
|
||||
logger.error(`failed(${err.stack}) id=${job ? job.id : '-'}`, { job, e: renderError(err) });
|
||||
if (config.sentryForBackend) {
|
||||
Sentry.captureMessage(`Queue: Relationship: ${job?.name ?? '?'}: ${err.message}`, {
|
||||
level: 'error',
|
||||
extra: { job, err },
|
||||
});
|
||||
}
|
||||
})
|
||||
.on('error', (err: Error) => logger.error(`error ${err.stack}`, { e: renderError(err) }))
|
||||
.on('stalled', (jobId) => logger.warn(`stalled id=${jobId}`));
|
||||
}
|
||||
//#endregion
|
||||
|
||||
//#region object storage
|
||||
this.objectStorageQueueWorker = new Bull.Worker(QUEUE.OBJECT_STORAGE, (job) => {
|
||||
switch (job.name) {
|
||||
case 'deleteFile': return this.deleteFileProcessorService.process(job);
|
||||
case 'cleanRemoteFiles': return this.cleanRemoteFilesProcessorService.process(job);
|
||||
default: throw new Error(`unrecognized job type ${job.name} for objectStorage`);
|
||||
}
|
||||
}, {
|
||||
...baseQueueOptions(this.config, QUEUE.OBJECT_STORAGE),
|
||||
autorun: false,
|
||||
concurrency: 16,
|
||||
});
|
||||
{
|
||||
const processer = (job: Bull.Job) => {
|
||||
switch (job.name) {
|
||||
case 'deleteFile': return this.deleteFileProcessorService.process(job);
|
||||
case 'cleanRemoteFiles': return this.cleanRemoteFilesProcessorService.process(job);
|
||||
default: throw new Error(`unrecognized job type ${job.name} for objectStorage`);
|
||||
}
|
||||
};
|
||||
|
||||
const objectStorageLogger = this.logger.createSubLogger('objectStorage');
|
||||
this.objectStorageQueueWorker = new Bull.Worker(QUEUE.OBJECT_STORAGE, (job) => {
|
||||
if (this.config.sentryForBackend) {
|
||||
return Sentry.startSpan({ name: 'Queue: ObjectStorage: ' + job.name }, () => processer(job));
|
||||
} else {
|
||||
return processer(job);
|
||||
}
|
||||
}, {
|
||||
...baseQueueOptions(this.config, QUEUE.OBJECT_STORAGE),
|
||||
autorun: false,
|
||||
concurrency: 16,
|
||||
});
|
||||
|
||||
this.objectStorageQueueWorker
|
||||
.on('active', (job) => objectStorageLogger.debug(`active id=${job.id}`))
|
||||
.on('completed', (job, result) => objectStorageLogger.debug(`completed(${result}) id=${job.id}`))
|
||||
.on('failed', (job, err) => objectStorageLogger.warn(`failed(${err.stack}) id=${job ? job.id : '-'}`, { job, e: renderError(err) }))
|
||||
.on('error', (err: Error) => objectStorageLogger.error(`error ${err.stack}`, { e: renderError(err) }))
|
||||
.on('stalled', (jobId) => objectStorageLogger.warn(`stalled id=${jobId}`));
|
||||
const logger = this.logger.createSubLogger('objectStorage');
|
||||
|
||||
this.objectStorageQueueWorker
|
||||
.on('active', (job) => logger.debug(`active id=${job.id}`))
|
||||
.on('completed', (job, result) => logger.debug(`completed(${result}) id=${job.id}`))
|
||||
.on('failed', (job, err) => {
|
||||
logger.error(`failed(${err.stack}) id=${job ? job.id : '-'}`, { job, e: renderError(err) });
|
||||
if (config.sentryForBackend) {
|
||||
Sentry.captureMessage(`Queue: ObjectStorage: ${job?.name ?? '?'}: ${err.message}`, {
|
||||
level: 'error',
|
||||
extra: { job, err },
|
||||
});
|
||||
}
|
||||
})
|
||||
.on('error', (err: Error) => logger.error(`error ${err.stack}`, { e: renderError(err) }))
|
||||
.on('stalled', (jobId) => logger.warn(`stalled id=${jobId}`));
|
||||
}
|
||||
//#endregion
|
||||
|
||||
//#region schedule note post
|
||||
|
@ -334,10 +498,18 @@ export class QueueProcessorService implements OnApplicationShutdown {
|
|||
//#endregion
|
||||
|
||||
//#region ended poll notification
|
||||
this.endedPollNotificationQueueWorker = new Bull.Worker(QUEUE.ENDED_POLL_NOTIFICATION, (job) => this.endedPollNotificationProcessorService.process(job), {
|
||||
...baseQueueOptions(this.config, QUEUE.ENDED_POLL_NOTIFICATION),
|
||||
autorun: false,
|
||||
});
|
||||
{
|
||||
this.endedPollNotificationQueueWorker = new Bull.Worker(QUEUE.ENDED_POLL_NOTIFICATION, (job) => {
|
||||
if (this.config.sentryForBackend) {
|
||||
return Sentry.startSpan({ name: 'Queue: EndedPollNotification' }, () => this.endedPollNotificationProcessorService.process(job));
|
||||
} else {
|
||||
return this.endedPollNotificationProcessorService.process(job);
|
||||
}
|
||||
}, {
|
||||
...baseQueueOptions(this.config, QUEUE.ENDED_POLL_NOTIFICATION),
|
||||
autorun: false,
|
||||
});
|
||||
}
|
||||
//#endregion
|
||||
}
|
||||
|
||||
|
@ -348,7 +520,8 @@ export class QueueProcessorService implements OnApplicationShutdown {
|
|||
this.dbQueueWorker.run(),
|
||||
this.deliverQueueWorker.run(),
|
||||
this.inboxQueueWorker.run(),
|
||||
this.webhookDeliverQueueWorker.run(),
|
||||
this.userWebhookDeliverQueueWorker.run(),
|
||||
this.systemWebhookDeliverQueueWorker.run(),
|
||||
this.relationshipQueueWorker.run(),
|
||||
this.objectStorageQueueWorker.run(),
|
||||
this.endedPollNotificationQueueWorker.run(),
|
||||
|
@ -363,7 +536,8 @@ export class QueueProcessorService implements OnApplicationShutdown {
|
|||
this.dbQueueWorker.close(),
|
||||
this.deliverQueueWorker.close(),
|
||||
this.inboxQueueWorker.close(),
|
||||
this.webhookDeliverQueueWorker.close(),
|
||||
this.userWebhookDeliverQueueWorker.close(),
|
||||
this.systemWebhookDeliverQueueWorker.close(),
|
||||
this.relationshipQueueWorker.close(),
|
||||
this.objectStorageQueueWorker.close(),
|
||||
this.endedPollNotificationQueueWorker.close(),
|
||||
|
|
|
@ -15,7 +15,8 @@ export const QUEUE = {
|
|||
DB: 'db',
|
||||
RELATIONSHIP: 'relationship',
|
||||
OBJECT_STORAGE: 'objectStorage',
|
||||
WEBHOOK_DELIVER: 'webhookDeliver',
|
||||
USER_WEBHOOK_DELIVER: 'userWebhookDeliver',
|
||||
SYSTEM_WEBHOOK_DELIVER: 'systemWebhookDeliver',
|
||||
};
|
||||
|
||||
export function baseQueueOptions(config: Config, queueName: typeof QUEUE[keyof typeof QUEUE]): Bull.QueueOptions {
|
||||
|
|
|
@ -76,7 +76,7 @@ export class ImportAntennasProcessorService {
|
|||
this.logger.warn('Validation Failed');
|
||||
continue;
|
||||
}
|
||||
const result = await this.antennasRepository.insert({
|
||||
const result = await this.antennasRepository.insertOne({
|
||||
id: this.idService.gen(now.getTime()),
|
||||
lastUsedAt: now,
|
||||
userId: job.data.user.id,
|
||||
|
@ -91,7 +91,7 @@ export class ImportAntennasProcessorService {
|
|||
excludeBots: antenna.excludeBots,
|
||||
withReplies: antenna.withReplies,
|
||||
withFile: antenna.withFile,
|
||||
}).then(x => this.antennasRepository.findOneByOrFail(x.identifiers[0]));
|
||||
});
|
||||
this.logger.succ('Antenna created: ' + result.id);
|
||||
this.globalEventService.publishInternalEvent('antennaCreated', result);
|
||||
}
|
||||
|
|
|
@ -79,11 +79,11 @@ export class ImportUserListsProcessorService {
|
|||
});
|
||||
|
||||
if (list == null) {
|
||||
list = await this.userListsRepository.insert({
|
||||
list = await this.userListsRepository.insertOne({
|
||||
id: this.idService.gen(),
|
||||
userId: user.id,
|
||||
name: listName,
|
||||
}).then(x => this.userListsRepository.findOneByOrFail(x.identifiers[0]));
|
||||
});
|
||||
}
|
||||
|
||||
let target = this.utilityService.isSelfHost(host!) ? await this.usersRepository.findOneBy({
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import * as Bull from 'bullmq';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { SystemWebhooksRepository } from '@/models/_.js';
|
||||
import type { Config } from '@/config.js';
|
||||
import type Logger from '@/logger.js';
|
||||
import { HttpRequestService } from '@/core/HttpRequestService.js';
|
||||
import { StatusError } from '@/misc/status-error.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||
import { SystemWebhookDeliverJobData } from '../types.js';
|
||||
|
||||
@Injectable()
|
||||
export class SystemWebhookDeliverProcessorService {
|
||||
private logger: Logger;
|
||||
|
||||
constructor(
|
||||
@Inject(DI.config)
|
||||
private config: Config,
|
||||
|
||||
@Inject(DI.systemWebhooksRepository)
|
||||
private systemWebhooksRepository: SystemWebhooksRepository,
|
||||
|
||||
private httpRequestService: HttpRequestService,
|
||||
private queueLoggerService: QueueLoggerService,
|
||||
) {
|
||||
this.logger = this.queueLoggerService.logger.createSubLogger('webhook');
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async process(job: Bull.Job<SystemWebhookDeliverJobData>): Promise<string> {
|
||||
try {
|
||||
this.logger.debug(`delivering ${job.data.webhookId}`);
|
||||
|
||||
const res = await this.httpRequestService.send(job.data.to, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'User-Agent': 'Misskey-Hooks',
|
||||
'X-Misskey-Host': this.config.host,
|
||||
'X-Misskey-Hook-Id': job.data.webhookId,
|
||||
'X-Misskey-Hook-Secret': job.data.secret,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
server: this.config.url,
|
||||
hookId: job.data.webhookId,
|
||||
eventId: job.data.eventId,
|
||||
createdAt: job.data.createdAt,
|
||||
type: job.data.type,
|
||||
body: job.data.content,
|
||||
}),
|
||||
});
|
||||
|
||||
this.systemWebhooksRepository.update({ id: job.data.webhookId }, {
|
||||
latestSentAt: new Date(),
|
||||
latestStatus: res.status,
|
||||
});
|
||||
|
||||
return 'Success';
|
||||
} catch (res) {
|
||||
this.logger.error(res as Error);
|
||||
|
||||
this.systemWebhooksRepository.update({ id: job.data.webhookId }, {
|
||||
latestSentAt: new Date(),
|
||||
latestStatus: res instanceof StatusError ? res.statusCode : 1,
|
||||
});
|
||||
|
||||
if (res instanceof StatusError) {
|
||||
// 4xx
|
||||
if (!res.isRetryable) {
|
||||
throw new Bull.UnrecoverableError(`${res.statusCode} ${res.statusMessage}`);
|
||||
}
|
||||
|
||||
// 5xx etc.
|
||||
throw new Error(`${res.statusCode} ${res.statusMessage}`);
|
||||
} else {
|
||||
// DNS error, socket error, timeout ...
|
||||
throw res;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -13,10 +13,10 @@ import { HttpRequestService } from '@/core/HttpRequestService.js';
|
|||
import { StatusError } from '@/misc/status-error.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||
import type { WebhookDeliverJobData } from '../types.js';
|
||||
import { UserWebhookDeliverJobData } from '../types.js';
|
||||
|
||||
@Injectable()
|
||||
export class WebhookDeliverProcessorService {
|
||||
export class UserWebhookDeliverProcessorService {
|
||||
private logger: Logger;
|
||||
|
||||
constructor(
|
||||
|
@ -33,7 +33,7 @@ export class WebhookDeliverProcessorService {
|
|||
}
|
||||
|
||||
@bindThis
|
||||
public async process(job: Bull.Job<WebhookDeliverJobData>): Promise<string> {
|
||||
public async process(job: Bull.Job<UserWebhookDeliverJobData>): Promise<string> {
|
||||
try {
|
||||
this.logger.debug(`delivering ${job.data.webhookId}`);
|
||||
|
|
@ -121,7 +121,17 @@ type MinimumUser = {
|
|||
uri: MiUser['uri'];
|
||||
};
|
||||
|
||||
export type WebhookDeliverJobData = {
|
||||
export type SystemWebhookDeliverJobData = {
|
||||
type: string;
|
||||
content: unknown;
|
||||
webhookId: MiWebhook['id'];
|
||||
to: string;
|
||||
secret: string;
|
||||
createdAt: number;
|
||||
eventId: string;
|
||||
};
|
||||
|
||||
export type UserWebhookDeliverJobData = {
|
||||
type: string;
|
||||
content: unknown;
|
||||
webhookId: MiWebhook['id'];
|
||||
|
|
|
@ -53,7 +53,7 @@ export class FileServerService {
|
|||
private internalStorageService: InternalStorageService,
|
||||
private loggerService: LoggerService,
|
||||
) {
|
||||
this.logger = this.loggerService.getLogger('server', 'gray', false);
|
||||
this.logger = this.loggerService.getLogger('server', 'gray');
|
||||
|
||||
//this.createServer = this.createServer.bind(this);
|
||||
}
|
||||
|
|
|
@ -68,7 +68,7 @@ export class ServerService implements OnApplicationShutdown {
|
|||
private loggerService: LoggerService,
|
||||
private oauth2ProviderService: OAuth2ProviderService,
|
||||
) {
|
||||
this.logger = this.loggerService.getLogger('server', 'gray', false);
|
||||
this.logger = this.loggerService.getLogger('server', 'gray');
|
||||
}
|
||||
|
||||
@bindThis
|
||||
|
|
|
@ -93,7 +93,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 +108,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,
|
||||
|
@ -410,9 +413,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));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -7,8 +7,13 @@ import { Module } from '@nestjs/common';
|
|||
|
||||
import { CoreModule } from '@/core/CoreModule.js';
|
||||
import * as ep___users_lists_list_favorite from '@/server/api/endpoints/users/lists/list-favorite.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';
|
||||
|
@ -88,6 +93,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';
|
|||
import * as ep___emoji_speedtest from './endpoints/admin/emoji/speedtest.js';
|
||||
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 };
|
||||
|
@ -475,6 +490,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 };
|
||||
|
@ -785,6 +805,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,
|
||||
|
@ -866,6 +891,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,
|
||||
|
@ -1170,6 +1200,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,
|
||||
|
@ -1252,6 +1287,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,
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -202,13 +202,13 @@ export class SignupApiService {
|
|||
const salt = await bcrypt.genSalt(8);
|
||||
const hash = await bcrypt.hash(password, salt);
|
||||
|
||||
const pendingUser = await this.userPendingsRepository.insert({
|
||||
const pendingUser = await this.userPendingsRepository.insertOne({
|
||||
id: this.idService.gen(),
|
||||
code,
|
||||
email: emailAddress!,
|
||||
username: username,
|
||||
password: hash,
|
||||
}).then(x => this.userPendingsRepository.findOneByOrFail(x.identifiers[0]));
|
||||
});
|
||||
|
||||
const link = `${this.config.url}/signup-complete/${code}`;
|
||||
|
||||
|
|
|
@ -8,8 +8,18 @@ import type { KeyOf, Schema } from '@/misc/json-schema.js';
|
|||
import { RolePolicies } from '@/core/RoleService.js';
|
||||
import * as ep___admin_emoji_setlocalOnlyBulk from './endpoints/admin/emoji/set-localonly-bulk.js';
|
||||
import * as ep___admin_emoji_setisSensitiveBulk from './endpoints/admin/emoji/set-issensitive-bulk.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';
|
||||
|
@ -52,7 +62,8 @@ import * as ep___admin_emoji_update from './endpoints/admin/emoji/update.js';
|
|||
import * as ep___admin_emoji_updateRequest from './endpoints/admin/emoji/update-request.js';
|
||||
import * as ep___emoji_speedtest from './endpoints/admin/emoji/speedtest.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';
|
||||
|
@ -90,6 +101,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';
|
||||
|
@ -393,6 +409,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],
|
||||
|
@ -474,6 +495,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],
|
||||
|
@ -901,8 +927,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;
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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]);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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, ScheduleNotePostQueue } from '@/core/QueueModule.js';
|
||||
import type { DbQueue, DeliverQueue, EndedPollNotificationQueue, InboxQueue, ObjectStorageQueue, SystemQueue, WebhookDeliverQueue, UserWebhookDeliverQueue } from '@/core/QueueModule.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['admin'],
|
||||
|
@ -54,7 +54,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();
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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]);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -117,9 +117,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
|
||||
if (typeof ps.suspended === 'boolean') {
|
||||
if (ps.suspended) {
|
||||
query.andWhere('instance.isSuspended = TRUE');
|
||||
query.andWhere('instance.suspensionState != \'none\'');
|
||||
} else {
|
||||
query.andWhere('instance.isSuspended = FALSE');
|
||||
query.andWhere('instance.suspensionState = \'none\'');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
|
|
@ -76,7 +76,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
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 +84,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);
|
||||
});
|
||||
|
|
|
@ -89,14 +89,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
|
|
@ -104,11 +104,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
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,
|
||||
|
|
|
@ -65,11 +65,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
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);
|
||||
});
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { setImmediate } from 'node:timers/promises';
|
||||
import sanitizeHtml from 'sanitize-html';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
|
@ -17,6 +17,7 @@ import { NoteEntityService } from '@/core/entities/NoteEntityService.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 = {
|
||||
|
@ -74,19 +75,20 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
private roleService: RoleService,
|
||||
private noteEntityService: NoteEntityService,
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
|
@ -34,6 +34,8 @@ import type {
|
|||
ScheduleNotePostQueue,
|
||||
SystemQueue,
|
||||
WebhookDeliverQueue,
|
||||
UserWebhookDeliverQueue,
|
||||
SystemWebhookDeliverQueue,
|
||||
} from '@/core/QueueModule.js';
|
||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
||||
|
@ -120,7 +122,8 @@ export class ClientServerService {
|
|||
@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,
|
||||
) {
|
||||
//this.createServer = this.createServer.bind(this);
|
||||
}
|
||||
|
@ -250,7 +253,8 @@ export class ClientServerService {
|
|||
this.inboxQueue,
|
||||
this.dbQueue,
|
||||
this.objectStorageQueue,
|
||||
this.webhookDeliverQueue,
|
||||
this.userWebhookDeliverQueue,
|
||||
this.systemWebhookDeliverQueue,
|
||||
].map(q => new BullMQAdapter(q)),
|
||||
serverAdapter,
|
||||
});
|
||||
|
|
|
@ -100,6 +100,12 @@ export const moderationLogTypes = [
|
|||
'deleteAvatarDecoration',
|
||||
'unsetUserAvatar',
|
||||
'unsetUserBanner',
|
||||
'createSystemWebhook',
|
||||
'updateSystemWebhook',
|
||||
'deleteSystemWebhook',
|
||||
'createAbuseReportNotificationRecipient',
|
||||
'updateAbuseReportNotificationRecipient',
|
||||
'deleteAbuseReportNotificationRecipient',
|
||||
] as const;
|
||||
|
||||
export type ModerationLogPayloads = {
|
||||
|
@ -296,6 +302,32 @@ export type ModerationLogPayloads = {
|
|||
userHost: string | null;
|
||||
fileId: string;
|
||||
};
|
||||
createSystemWebhook: {
|
||||
systemWebhookId: string;
|
||||
webhook: any;
|
||||
};
|
||||
updateSystemWebhook: {
|
||||
systemWebhookId: string;
|
||||
before: any;
|
||||
after: any;
|
||||
};
|
||||
deleteSystemWebhook: {
|
||||
systemWebhookId: string;
|
||||
webhook: any;
|
||||
};
|
||||
createAbuseReportNotificationRecipient: {
|
||||
recipientId: string;
|
||||
recipient: any;
|
||||
};
|
||||
updateAbuseReportNotificationRecipient: {
|
||||
recipientId: string;
|
||||
before: any;
|
||||
after: any;
|
||||
};
|
||||
deleteAbuseReportNotificationRecipient: {
|
||||
recipientId: string;
|
||||
recipient: any;
|
||||
};
|
||||
};
|
||||
|
||||
export type MiMinimumUser = {
|
||||
|
|
|
@ -9,7 +9,7 @@ process.env.NODE_ENV = 'test';
|
|||
|
||||
import * as assert from 'assert';
|
||||
import { loadConfig } from '@/config.js';
|
||||
import { MiUser, UsersRepository } from '@/models/_.js';
|
||||
import { MiRepository, MiUser, UsersRepository, miRepository } from '@/models/_.js';
|
||||
import { secureRndstr } from '@/misc/secure-rndstr.js';
|
||||
import { jobQueue } from '@/boot/common.js';
|
||||
import { api, initTestDb, signup, sleep, successfulApiCall, uploadFile } from '../utils.js';
|
||||
|
@ -42,7 +42,7 @@ describe('Account Move', () => {
|
|||
dave = await signup({ username: 'dave' });
|
||||
eve = await signup({ username: 'eve' });
|
||||
frank = await signup({ username: 'frank' });
|
||||
Users = connection.getRepository(MiUser);
|
||||
Users = connection.getRepository(MiUser).extend(miRepository as MiRepository<MiUser>);
|
||||
}, 1000 * 60 * 2);
|
||||
|
||||
afterAll(async () => {
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
process.env.NODE_ENV = 'test';
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { ReversiMatchResponse } from 'misskey-js/entities.js';
|
||||
import { api, signup } from '../utils.js';
|
||||
import type * as misskey from 'misskey-js';
|
||||
|
||||
describe('ReversiGame', () => {
|
||||
let alice: misskey.entities.SignupResponse;
|
||||
let bob: misskey.entities.SignupResponse;
|
||||
|
||||
beforeAll(async () => {
|
||||
alice = await signup({ username: 'alice' });
|
||||
bob = await signup({ username: 'bob' });
|
||||
}, 1000 * 60 * 2);
|
||||
|
||||
test('matches when alice invites bob and bob accepts', async () => {
|
||||
const response1 = await api('reversi/match', { userId: bob.id }, alice);
|
||||
assert.strictEqual(response1.status, 204);
|
||||
assert.strictEqual(response1.body, null);
|
||||
const response2 = await api('reversi/match', { userId: alice.id }, bob);
|
||||
assert.strictEqual(response2.status, 200);
|
||||
assert.notStrictEqual(response2.body, null);
|
||||
const body = response2.body as ReversiMatchResponse;
|
||||
assert.strictEqual(body.user1.id, alice.id);
|
||||
assert.strictEqual(body.user2.id, bob.id);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,401 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { entities } from 'misskey-js';
|
||||
import { beforeEach, describe, test } from '@jest/globals';
|
||||
import Fastify from 'fastify';
|
||||
import { api, randomString, role, signup, startJobQueue, UserToken } from '../../utils.js';
|
||||
import type { INestApplicationContext } from '@nestjs/common';
|
||||
|
||||
const WEBHOOK_HOST = 'http://localhost:15080';
|
||||
const WEBHOOK_PORT = 15080;
|
||||
process.env.NODE_ENV = 'test';
|
||||
|
||||
describe('[シナリオ] ユーザ通報', () => {
|
||||
let queue: INestApplicationContext;
|
||||
let admin: entities.SignupResponse;
|
||||
let alice: entities.SignupResponse;
|
||||
let bob: entities.SignupResponse;
|
||||
|
||||
type SystemWebhookPayload = {
|
||||
server: string;
|
||||
hookId: string;
|
||||
eventId: string;
|
||||
createdAt: string;
|
||||
type: string;
|
||||
body: any;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------------------------
|
||||
|
||||
async function captureWebhook<T = SystemWebhookPayload>(postAction: () => Promise<void>): Promise<T> {
|
||||
const fastify = Fastify();
|
||||
|
||||
let timeoutHandle: NodeJS.Timeout | null = null;
|
||||
const result = await new Promise<string>(async (resolve, reject) => {
|
||||
fastify.all('/', async (req, res) => {
|
||||
timeoutHandle && clearTimeout(timeoutHandle);
|
||||
|
||||
const body = JSON.stringify(req.body);
|
||||
res.status(200).send('ok');
|
||||
await fastify.close();
|
||||
resolve(body);
|
||||
});
|
||||
|
||||
await fastify.listen({ port: WEBHOOK_PORT });
|
||||
|
||||
timeoutHandle = setTimeout(async () => {
|
||||
await fastify.close();
|
||||
reject(new Error('timeout'));
|
||||
}, 3000);
|
||||
|
||||
try {
|
||||
await postAction();
|
||||
} catch (e) {
|
||||
await fastify.close();
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
|
||||
await fastify.close();
|
||||
|
||||
return JSON.parse(result) as T;
|
||||
}
|
||||
|
||||
async function createSystemWebhook(args?: Partial<entities.AdminSystemWebhookCreateRequest>, credential?: UserToken): Promise<entities.AdminSystemWebhookCreateResponse> {
|
||||
const res = await api(
|
||||
'admin/system-webhook/create',
|
||||
{
|
||||
isActive: true,
|
||||
name: randomString(),
|
||||
on: ['abuseReport'],
|
||||
url: WEBHOOK_HOST,
|
||||
secret: randomString(),
|
||||
...args,
|
||||
},
|
||||
credential ?? admin,
|
||||
);
|
||||
return res.body;
|
||||
}
|
||||
|
||||
async function createAbuseReportNotificationRecipient(args?: Partial<entities.AdminAbuseReportNotificationRecipientCreateRequest>, credential?: UserToken): Promise<entities.AdminAbuseReportNotificationRecipientCreateResponse> {
|
||||
const res = await api(
|
||||
'admin/abuse-report/notification-recipient/create',
|
||||
{
|
||||
isActive: true,
|
||||
name: randomString(),
|
||||
method: 'webhook',
|
||||
...args,
|
||||
},
|
||||
credential ?? admin,
|
||||
);
|
||||
return res.body;
|
||||
}
|
||||
|
||||
async function createAbuseReport(args?: Partial<entities.UsersReportAbuseRequest>, credential?: UserToken): Promise<entities.EmptyResponse> {
|
||||
const res = await api(
|
||||
'users/report-abuse',
|
||||
{
|
||||
userId: alice.id,
|
||||
comment: randomString(),
|
||||
...args,
|
||||
},
|
||||
credential ?? admin,
|
||||
);
|
||||
return res.body;
|
||||
}
|
||||
|
||||
async function resolveAbuseReport(args?: Partial<entities.AdminResolveAbuseUserReportRequest>, credential?: UserToken): Promise<entities.EmptyResponse> {
|
||||
const res = await api(
|
||||
'admin/resolve-abuse-user-report',
|
||||
{
|
||||
reportId: admin.id,
|
||||
...args,
|
||||
},
|
||||
credential ?? admin,
|
||||
);
|
||||
return res.body;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------------------------
|
||||
|
||||
beforeAll(async () => {
|
||||
queue = await startJobQueue();
|
||||
admin = await signup({ username: 'admin' });
|
||||
alice = await signup({ username: 'alice' });
|
||||
bob = await signup({ username: 'bob' });
|
||||
|
||||
await role(admin, { isAdministrator: true });
|
||||
}, 1000 * 60 * 2);
|
||||
|
||||
afterAll(async () => {
|
||||
await queue.close();
|
||||
});
|
||||
|
||||
// -------------------------------------------------------------------------------------------
|
||||
|
||||
describe('SystemWebhook', () => {
|
||||
beforeEach(async () => {
|
||||
const webhooks = await api('admin/system-webhook/list', {}, admin);
|
||||
for (const webhook of webhooks.body) {
|
||||
await api('admin/system-webhook/delete', { id: webhook.id }, admin);
|
||||
}
|
||||
});
|
||||
|
||||
test('通報を受けた -> abuseReportが送出される', async () => {
|
||||
const webhook = await createSystemWebhook({
|
||||
on: ['abuseReport'],
|
||||
isActive: true,
|
||||
});
|
||||
await createAbuseReportNotificationRecipient({ systemWebhookId: webhook.id });
|
||||
|
||||
// 通報(bob -> alice)
|
||||
const abuse = {
|
||||
userId: alice.id,
|
||||
comment: randomString(),
|
||||
};
|
||||
const webhookBody = await captureWebhook(async () => {
|
||||
await createAbuseReport(abuse, bob);
|
||||
});
|
||||
|
||||
console.log(JSON.stringify(webhookBody, null, 2));
|
||||
|
||||
expect(webhookBody.hookId).toBe(webhook.id);
|
||||
expect(webhookBody.type).toBe('abuseReport');
|
||||
expect(webhookBody.body.targetUserId).toBe(alice.id);
|
||||
expect(webhookBody.body.reporterId).toBe(bob.id);
|
||||
expect(webhookBody.body.comment).toBe(abuse.comment);
|
||||
});
|
||||
|
||||
test('通報を受けた -> abuseReportが送出される -> 解決 -> abuseReportResolvedが送出される', async () => {
|
||||
const webhook = await createSystemWebhook({
|
||||
on: ['abuseReport', 'abuseReportResolved'],
|
||||
isActive: true,
|
||||
});
|
||||
await createAbuseReportNotificationRecipient({ systemWebhookId: webhook.id });
|
||||
|
||||
// 通報(bob -> alice)
|
||||
const abuse = {
|
||||
userId: alice.id,
|
||||
comment: randomString(),
|
||||
};
|
||||
const webhookBody1 = await captureWebhook(async () => {
|
||||
await createAbuseReport(abuse, bob);
|
||||
});
|
||||
|
||||
console.log(JSON.stringify(webhookBody1, null, 2));
|
||||
expect(webhookBody1.hookId).toBe(webhook.id);
|
||||
expect(webhookBody1.type).toBe('abuseReport');
|
||||
expect(webhookBody1.body.targetUserId).toBe(alice.id);
|
||||
expect(webhookBody1.body.reporterId).toBe(bob.id);
|
||||
expect(webhookBody1.body.assigneeId).toBeNull();
|
||||
expect(webhookBody1.body.resolved).toBe(false);
|
||||
expect(webhookBody1.body.comment).toBe(abuse.comment);
|
||||
|
||||
// 解決
|
||||
const webhookBody2 = await captureWebhook(async () => {
|
||||
await resolveAbuseReport({
|
||||
reportId: webhookBody1.body.id,
|
||||
forward: false,
|
||||
}, admin);
|
||||
});
|
||||
|
||||
console.log(JSON.stringify(webhookBody2, null, 2));
|
||||
expect(webhookBody2.hookId).toBe(webhook.id);
|
||||
expect(webhookBody2.type).toBe('abuseReportResolved');
|
||||
expect(webhookBody2.body.targetUserId).toBe(alice.id);
|
||||
expect(webhookBody2.body.reporterId).toBe(bob.id);
|
||||
expect(webhookBody2.body.assigneeId).toBe(admin.id);
|
||||
expect(webhookBody2.body.resolved).toBe(true);
|
||||
expect(webhookBody2.body.comment).toBe(abuse.comment);
|
||||
});
|
||||
|
||||
test('通報を受けた -> abuseReportが未許可の場合は送出されない', async () => {
|
||||
const webhook = await createSystemWebhook({
|
||||
on: [],
|
||||
isActive: true,
|
||||
});
|
||||
await createAbuseReportNotificationRecipient({ systemWebhookId: webhook.id });
|
||||
|
||||
// 通報(bob -> alice)
|
||||
const abuse = {
|
||||
userId: alice.id,
|
||||
comment: randomString(),
|
||||
};
|
||||
const webhookBody = await captureWebhook(async () => {
|
||||
await createAbuseReport(abuse, bob);
|
||||
}).catch(e => e.message);
|
||||
|
||||
expect(webhookBody).toBe('timeout');
|
||||
});
|
||||
|
||||
test('通報を受けた -> abuseReportが未許可の場合は送出されない -> 解決 -> abuseReportResolvedが送出される', async () => {
|
||||
const webhook = await createSystemWebhook({
|
||||
on: ['abuseReportResolved'],
|
||||
isActive: true,
|
||||
});
|
||||
await createAbuseReportNotificationRecipient({ systemWebhookId: webhook.id });
|
||||
|
||||
// 通報(bob -> alice)
|
||||
const abuse = {
|
||||
userId: alice.id,
|
||||
comment: randomString(),
|
||||
};
|
||||
const webhookBody1 = await captureWebhook(async () => {
|
||||
await createAbuseReport(abuse, bob);
|
||||
}).catch(e => e.message);
|
||||
|
||||
expect(webhookBody1).toBe('timeout');
|
||||
|
||||
const abuseReportId = (await api('admin/abuse-user-reports', {}, admin)).body[0].id;
|
||||
|
||||
// 解決
|
||||
const webhookBody2 = await captureWebhook(async () => {
|
||||
await resolveAbuseReport({
|
||||
reportId: abuseReportId,
|
||||
forward: false,
|
||||
}, admin);
|
||||
});
|
||||
|
||||
console.log(JSON.stringify(webhookBody2, null, 2));
|
||||
expect(webhookBody2.hookId).toBe(webhook.id);
|
||||
expect(webhookBody2.type).toBe('abuseReportResolved');
|
||||
expect(webhookBody2.body.targetUserId).toBe(alice.id);
|
||||
expect(webhookBody2.body.reporterId).toBe(bob.id);
|
||||
expect(webhookBody2.body.assigneeId).toBe(admin.id);
|
||||
expect(webhookBody2.body.resolved).toBe(true);
|
||||
expect(webhookBody2.body.comment).toBe(abuse.comment);
|
||||
});
|
||||
|
||||
test('通報を受けた -> abuseReportが送出される -> 解決 -> abuseReportResolvedが未許可の場合は送出されない', async () => {
|
||||
const webhook = await createSystemWebhook({
|
||||
on: ['abuseReport'],
|
||||
isActive: true,
|
||||
});
|
||||
await createAbuseReportNotificationRecipient({ systemWebhookId: webhook.id });
|
||||
|
||||
// 通報(bob -> alice)
|
||||
const abuse = {
|
||||
userId: alice.id,
|
||||
comment: randomString(),
|
||||
};
|
||||
const webhookBody1 = await captureWebhook(async () => {
|
||||
await createAbuseReport(abuse, bob);
|
||||
});
|
||||
|
||||
console.log(JSON.stringify(webhookBody1, null, 2));
|
||||
expect(webhookBody1.hookId).toBe(webhook.id);
|
||||
expect(webhookBody1.type).toBe('abuseReport');
|
||||
expect(webhookBody1.body.targetUserId).toBe(alice.id);
|
||||
expect(webhookBody1.body.reporterId).toBe(bob.id);
|
||||
expect(webhookBody1.body.assigneeId).toBeNull();
|
||||
expect(webhookBody1.body.resolved).toBe(false);
|
||||
expect(webhookBody1.body.comment).toBe(abuse.comment);
|
||||
|
||||
// 解決
|
||||
const webhookBody2 = await captureWebhook(async () => {
|
||||
await resolveAbuseReport({
|
||||
reportId: webhookBody1.body.id,
|
||||
forward: false,
|
||||
}, admin);
|
||||
}).catch(e => e.message);
|
||||
|
||||
expect(webhookBody2).toBe('timeout');
|
||||
});
|
||||
|
||||
test('通報を受けた -> abuseReportが未許可の場合は送出されない -> 解決 -> abuseReportResolvedが未許可の場合は送出されない', async () => {
|
||||
const webhook = await createSystemWebhook({
|
||||
on: [],
|
||||
isActive: true,
|
||||
});
|
||||
await createAbuseReportNotificationRecipient({ systemWebhookId: webhook.id });
|
||||
|
||||
// 通報(bob -> alice)
|
||||
const abuse = {
|
||||
userId: alice.id,
|
||||
comment: randomString(),
|
||||
};
|
||||
const webhookBody1 = await captureWebhook(async () => {
|
||||
await createAbuseReport(abuse, bob);
|
||||
}).catch(e => e.message);
|
||||
|
||||
expect(webhookBody1).toBe('timeout');
|
||||
|
||||
const abuseReportId = (await api('admin/abuse-user-reports', {}, admin)).body[0].id;
|
||||
|
||||
// 解決
|
||||
const webhookBody2 = await captureWebhook(async () => {
|
||||
await resolveAbuseReport({
|
||||
reportId: abuseReportId,
|
||||
forward: false,
|
||||
}, admin);
|
||||
}).catch(e => e.message);
|
||||
|
||||
expect(webhookBody2).toBe('timeout');
|
||||
});
|
||||
|
||||
test('通報を受けた -> Webhookが無効の場合は送出されない', async () => {
|
||||
const webhook = await createSystemWebhook({
|
||||
on: ['abuseReport', 'abuseReportResolved'],
|
||||
isActive: false,
|
||||
});
|
||||
await createAbuseReportNotificationRecipient({ systemWebhookId: webhook.id });
|
||||
|
||||
// 通報(bob -> alice)
|
||||
const abuse = {
|
||||
userId: alice.id,
|
||||
comment: randomString(),
|
||||
};
|
||||
const webhookBody1 = await captureWebhook(async () => {
|
||||
await createAbuseReport(abuse, bob);
|
||||
}).catch(e => e.message);
|
||||
|
||||
expect(webhookBody1).toBe('timeout');
|
||||
|
||||
const abuseReportId = (await api('admin/abuse-user-reports', {}, admin)).body[0].id;
|
||||
|
||||
// 解決
|
||||
const webhookBody2 = await captureWebhook(async () => {
|
||||
await resolveAbuseReport({
|
||||
reportId: abuseReportId,
|
||||
forward: false,
|
||||
}, admin);
|
||||
}).catch(e => e.message);
|
||||
|
||||
expect(webhookBody2).toBe('timeout');
|
||||
});
|
||||
|
||||
test('通報を受けた -> 通知設定が無効の場合は送出されない', async () => {
|
||||
const webhook = await createSystemWebhook({
|
||||
on: ['abuseReport', 'abuseReportResolved'],
|
||||
isActive: true,
|
||||
});
|
||||
await createAbuseReportNotificationRecipient({ systemWebhookId: webhook.id, isActive: false });
|
||||
|
||||
// 通報(bob -> alice)
|
||||
const abuse = {
|
||||
userId: alice.id,
|
||||
comment: randomString(),
|
||||
};
|
||||
const webhookBody1 = await captureWebhook(async () => {
|
||||
await createAbuseReport(abuse, bob);
|
||||
}).catch(e => e.message);
|
||||
|
||||
expect(webhookBody1).toBe('timeout');
|
||||
|
||||
const abuseReportId = (await api('admin/abuse-user-reports', {}, admin)).body[0].id;
|
||||
|
||||
// 解決
|
||||
const webhookBody2 = await captureWebhook(async () => {
|
||||
await resolveAbuseReport({
|
||||
reportId: abuseReportId,
|
||||
forward: false,
|
||||
}, admin);
|
||||
}).catch(e => e.message);
|
||||
|
||||
expect(webhookBody2).toBe('timeout');
|
||||
});
|
||||
});
|
||||
});
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue