Compare commits

...

35 Commits

Author SHA1 Message Date
github-actions[bot]
6078081c33 [skip ci] Release: 2024.5.0 2024-05-31 12:24:53 +00:00
github-actions[bot]
a59aa20be8 Bump version to 2024.5.0-rc.13 2024-05-31 12:18:52 +00:00
syuilo
61eec93f4e Revert "2024.5.0"
This reverts commit 27d1b7e615.
2024-05-31 21:16:35 +09:00
syuilo
27d1b7e615 2024.5.0 2024-05-31 21:09:19 +09:00
github-actions[bot]
316d192bc0 Bump version to 2024.5.0-rc.12 2024-05-31 12:05:47 +00:00
github-actions[bot]
46164f879b Bump version to 2024.5.0-rc.11 2024-05-31 11:20:13 +00:00
github-actions[bot]
374c8791d7 Bump version to 2024.5.0-rc.10 2024-05-31 11:13:42 +00:00
syuilo
e8f523f00a Merge branch 'develop' into release/2024.5.0 2024-05-31 20:11:55 +09:00
syuilo
030082f756 🎨 2024-05-31 19:35:27 +09:00
github-actions[bot]
dc55adbaf7 Bump version to 2024.5.0-rc.9 2024-05-31 07:06:41 +00:00
syuilo
90ba1ca1f9 Merge branch 'develop' into release/2024.5.0 2024-05-31 16:06:00 +09:00
zyoshoka
514a65e453 perf(backend): avoid N+1 selects from user table when packing many entities (#13911)
* perf(backend): avoid N+1 selects from `user` table when packing many entities

* perf(backend): use `packMany` instead of mapping to `pack`
2024-05-31 15:32:42 +09:00
syuilo
a3468fd05b Merge branch 'develop' into release/2024.5.0 2024-05-31 14:59:32 +09:00
syuilo
97be1a53ad Update 1717117195275-inquiryUrl.js 2024-05-31 14:59:02 +09:00
github-actions[bot]
1e007b63aa Bump version to 2024.5.0-rc.8 2024-05-31 04:38:45 +00:00
syuilo
a0c596b030 Merge branch 'develop' into release/2024.5.0 2024-05-31 13:28:25 +09:00
syuilo
eaa85f5aa3 fix test 2024-05-31 13:28:11 +09:00
syuilo
dfeaa1145b Merge branch 'develop' into release/2024.5.0 2024-05-31 13:19:49 +09:00
syuilo
0082747237 New Crowdin updates (#13892)
* New translations ja-jp.yml (Chinese Traditional)

* New translations ja-jp.yml (Chinese Traditional)

* New translations ja-jp.yml (Chinese Simplified)

* New translations ja-jp.yml (Chinese Traditional)
2024-05-31 13:19:37 +09:00
syuilo
5b8f8e7087 fix(backend): fix backward compatibility of antenna 2024-05-31 11:24:17 +09:00
syuilo
be11fd7508 enhance: サーバーのお問い合わせ先URLを設定できるように 2024-05-31 10:12:23 +09:00
syuilo
ac4a001e9f fix code style 2024-05-31 10:11:11 +09:00
KanariKanaru
24d4124ffc fix(frontend): ノートにテキストがなくてもファイルが5つ以上あるときは折りたたむように (#13907)
* fix: ノートにテキストがなくてもファイルが5つ以上あるときは折りたたむように

* 冗長な記述を修正

* Update CHANGELOG.md
2024-05-30 17:36:58 +09:00
zyoshoka
eaadd643eb chore(misskey-js): fix repository and add license in package.json (#13902) 2024-05-29 20:57:48 +09:00
Kisaragi
cf670e8a3d refactor(backend): avoid as any on CustomEmojiService.ts (#13903) 2024-05-29 07:12:50 +09:00
Kisaragi
e57ce4fa0f chore(backend): rename local variable (#13904)
much -> matched
2024-05-29 07:12:20 +09:00
Kisaragi
44cafbb9f2 refactor: avoid as any[] on FetchInstanceMetadataService.ts (#13905)
* refactor: avoid `as any[]` on FetchInstanceMetadataService.ts

* apply suggestion

Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com>

---------

Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com>
2024-05-29 07:11:29 +09:00
github-actions[bot]
f75e46752e Bump version to 2024.5.0-rc.7 2024-05-28 09:18:21 +00:00
github-actions[bot]
244adef70e Bump version to 2024.5.0-rc.6 2024-05-28 09:18:05 +00:00
syuilo
e2eb7e8ca9 Merge branch 'develop' into release/2024.5.0 2024-05-28 18:17:39 +09:00
syuilo
80f3cb96b0 feat: sentry integration (#13897)
* wip

* wip

* wip

* wip

* Update CHANGELOG.md

* Update ApiCallService.ts

* Update config.ts
2024-05-28 17:06:33 +09:00
tamaina
89b27d8587 fix(federation): InboxにきたCreate, AnnounceのobjectがBearcaps urlだった際はスキップするように (#13610)
* fix(federation): AnnounceのobjectがLike出なかったらキューにためない
Fix https://github.com/misskey-dev/misskey/issues/13552

* revert

* better reason handlings

* result

* improve announce handling

* skip bearcaps

* also announce
2024-05-28 14:36:06 +09:00
tamaina
1bb1a32986 [skip ci] update release manager actions 2024-05-28 00:03:12 +09:00
tamaina
de9e391e34 [skip ci] update release manager actions 2024-05-28 00:02:22 +09:00
Kisaragi
934f9f80bd docs: 「Feat: 個別のお知らせにリンクで飛べるように」のcherry-pick元を指定 (#13891)
* docs: 「Feat: 個別のお知らせにリンクで飛べるように」のcherry-pick元を指定
cc misskey-dev#13885

* Update CHANGELOG.md

Co-authored-by: Kisaragi <48310258+KisaragiEffective@users.noreply.github.com>

---------

Co-authored-by: かっこかり <67428053+kakkokari-gtyih@users.noreply.github.com>
2024-05-27 21:25:07 +09:00
65 changed files with 1334 additions and 267 deletions

View File

@@ -106,7 +106,7 @@ redis:
# ┌───────────────────────────┐
#───┘ MeiliSearch configuration └─────────────────────────────
# You can set scope to local (default value) or global
# You can set scope to local (default value) or global
# (include notes from remote).
#meilisearch:
@@ -136,6 +136,21 @@ redis:
id: 'aidx'
# ┌────────────────┐
#───┘ Error tracking └──────────────────────────────────────────
# Sentry is available for error tracking.
# See the Sentry documentation for more details on options.
#sentryForBackend:
# enableNodeProfiling: true
# options:
# dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0'
#sentryForFrontend:
# options:
# dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0'
# ┌─────────────────────┐
#───┘ Other configuration └─────────────────────────────────────
@@ -185,7 +200,7 @@ proxyRemoteFiles: true
signToActivityPubGet: true
# For security reasons, uploading attachments from the intranet is prohibited,
# but exceptions can be made from the following settings. Default value is "undefined".
# but exceptions can be made from the following settings. Default value is "undefined".
# Read changelog to learn more (Improvements of 12.90.0 (2021/09/04)).
#allowedPrivateNetworks: [
# '127.0.0.1/32'

View File

@@ -205,6 +205,21 @@ redis:
id: 'aidx'
# ┌────────────────┐
#───┘ Error tracking └──────────────────────────────────────────
# Sentry is available for error tracking.
# See the Sentry documentation for more details on options.
#sentryForBackend:
# enableNodeProfiling: true
# options:
# dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0'
#sentryForFrontend:
# options:
# dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0'
# ┌─────────────────────┐
#───┘ Other configuration └─────────────────────────────────────

View File

@@ -132,6 +132,21 @@ redis:
id: 'aidx'
# ┌────────────────┐
#───┘ Error tracking └──────────────────────────────────────────
# Sentry is available for error tracking.
# See the Sentry documentation for more details on options.
#sentryForBackend:
# enableNodeProfiling: true
# options:
# dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0'
#sentryForFrontend:
# options:
# dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0'
# ┌─────────────────────┐
#───┘ Other configuration └─────────────────────────────────────

View File

@@ -23,7 +23,7 @@ jobs:
# headがrelease/かつopenのPRを1つ取得
- name: Get PR
run: |
echo "pr_number=$(gh pr list --limit 1 --head "${{ github.ref_name }}" --json number --jq '.[] | .number')" >> $GITHUB_OUTPUT
echo "pr_number=$(gh pr list --limit 1 --head "$GITHUB_REF_NAME" --json number --jq '.[] | .number')" >> $GITHUB_OUTPUT
id: get_pr
- name: Get target version
uses: misskey-dev/release-manager-actions/.github/actions/get-target-version@v1

View File

@@ -22,9 +22,11 @@ jobs:
# PR情報を取得
- name: Get PR
run: |
pr_json=$(gh pr view ${{ github.event.pull_request.number }} --json isDraft,headRefName)
pr_json=$(gh pr view "$PR_NUMBER" --json isDraft,headRefName)
echo "ref=$(echo $pr_json | jq -r '.headRefName')" >> $GITHUB_OUTPUT
id: get_pr
env:
PR_NUMBER: ${{ github.event.pull_request.number }}
release:
uses: misskey-dev/release-manager-actions/.github/workflows/create-prerelease.yml@v1
needs: check

View File

@@ -6,6 +6,7 @@
- 管理者向け権限 `read:admin:show-users``read:admin:show-user` に統合されました。必要に応じてAPIトークンを再発行してください。
### General
- Feat: エラートラッキングにSentryを使用できるようになりました
- Enhance: URLプレビューの有効化・無効化を設定できるように #13569
- Enhance: アンテナでBotによるートを除外できるように
(Cherry-picked from https://github.com/MisskeyIO/misskey/pull/545)
@@ -19,6 +20,7 @@
- Enhance: Goneを出さずに終了したサーバーへの配信停止を自動的に行うように
- もしそのようなサーバーからから配信が届いた場合には自動的に配信を再開します
- Enhance: 配信停止の理由を表示するように
- Enhance: サーバーのお問い合わせ先URLを設定できるようになりました
- Fix: Play作成時に設定した公開範囲が機能していない問題を修正
- Fix: 正規化されていない状態のhashtagが連合されてきたhtmlに含まれているとhashtagが正しくhashtagに復元されない問題を修正
- Fix: みつけるのアンケート欄にてチャンネルのアンケートが含まれてしまう問題を修正
@@ -26,7 +28,7 @@
### Client
- Feat: アップロードするファイルの名前をランダム文字列にできるように
- Feat: 個別のお知らせにリンクで飛べるように
(Cherry-picked from https://github.com/MisskeyIO/misskey)
(Based on https://github.com/MisskeyIO/misskey/pull/639)
- Enhance: 自分のノートの添付ファイルから直接ファイルの詳細ページに飛べるように
- Enhance: 広告がMisskeyと同一ドメインの場合はRouterで遷移するように
- Enhance: リアクション・いいねの総数を表示するように
@@ -72,6 +74,7 @@
- Fix: 通知をグループ化している際に、人数が正常に表示されないことがある問題を修正
- Fix: 連合なしの状態の読み書きができない問題を修正
- Fix: `/share` で日本語等を含むurlがurlエンコードされない問題を修正
- Fix: ファイルを5つ以上添付してもテキストがないとートが折りたたまれない問題を修正
### Server
- Enhance: エンドポイント`antennas/update`の必須項目を`antennaId`のみに

View File

@@ -152,6 +152,22 @@ redis:
# ID SETTINGS AFTER THAT!
id: "aidx"
# ┌────────────────┐
#───┘ Error tracking └──────────────────────────────────────────
# Sentry is available for error tracking.
# See the Sentry documentation for more details on options.
#sentryForBackend:
# enableNodeProfiling: true
# options:
# dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0'
#sentryForFrontend:
# options:
# dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0'
# ┌─────────────────────┐
#───┘ Other configuration └─────────────────────────────────────

12
locales/index.d.ts vendored
View File

@@ -3364,6 +3364,10 @@ export interface Locale extends ILocale {
* 管理者情報が設定されていません。
*/
"noMaintainerInformationWarning": string;
/**
* 問い合わせ先URLが設定されていません。
*/
"noInquiryUrlWarning": string;
/**
* Botプロテクションが設定されていません。
*/
@@ -5471,6 +5475,14 @@ export interface Locale extends ILocale {
* 有効にすると、タイムラインがキャッシュされていない場合にDBへ追加で問い合わせを行うフォールバック処理を行います。無効にすると、フォールバック処理を行わないことでさらにサーバーの負荷を軽減することができますが、タイムラインが取得できる範囲に制限が生じます。
*/
"fanoutTimelineDbFallbackDescription": string;
/**
* 問い合わせ先URL
*/
"inquiryUrl": string;
/**
* サーバー運営者へのお問い合わせフォームのURLや、運営者の連絡先等が記載されたWebページのURLを指定します。
*/
"inquiryUrlDescription": string;
};
"_accountMigration": {
/**

View File

@@ -837,6 +837,7 @@ administration: "管理"
accounts: "アカウント"
switch: "切り替え"
noMaintainerInformationWarning: "管理者情報が設定されていません。"
noInquiryUrlWarning: "問い合わせ先URLが設定されていません。"
noBotProtectionWarning: "Botプロテクションが設定されていません。"
configure: "設定する"
postToGallery: "ギャラリーへ投稿"
@@ -1383,6 +1384,8 @@ _serverSettings:
fanoutTimelineDescription: "有効にすると、各種タイムラインを取得する際のパフォーマンスが大幅に向上し、データベースへの負荷を軽減することが可能です。ただし、Redisのメモリ使用量は増加します。サーバーのメモリ容量が少ない場合、または動作が不安定な場合は無効にすることができます。"
fanoutTimelineDbFallback: "データベースへのフォールバック"
fanoutTimelineDbFallbackDescription: "有効にすると、タイムラインがキャッシュされていない場合にDBへ追加で問い合わせを行うフォールバック処理を行います。無効にすると、フォールバック処理を行わないことでさらにサーバーの負荷を軽減することができますが、タイムラインが取得できる範囲に制限が生じます。"
inquiryUrl: "問い合わせ先URL"
inquiryUrlDescription: "サーバー運営者へのお問い合わせフォームのURLや、運営者の連絡先等が記載されたWebページのURLを指定します。"
_accountMigration:
moveFrom: "別のアカウントからこのアカウントに移行"

View File

@@ -316,6 +316,7 @@ selectFile: "选择文件"
selectFiles: "选择文件"
selectFolder: "选择文件夹"
selectFolders: "选择多个文件夹"
fileNotSelected: "未选择文件"
renameFile: "重命名文件"
folderName: "文件夹名称"
createFolder: "创建文件夹"
@@ -2358,6 +2359,7 @@ _deck:
alwaysShowMainColumn: "总是显示主列"
columnAlign: "列对齐"
addColumn: "添加列"
newNoteNotificationSettings: "新帖子通知设定"
configureColumn: "列设置"
swapLeft: "向左移动"
swapRight: "向右移动"

View File

@@ -316,6 +316,7 @@ selectFile: "選擇檔案"
selectFiles: "選擇檔案"
selectFolder: "選擇資料夾"
selectFolders: "選擇資料夾"
fileNotSelected: "尚未選擇檔案"
renameFile: "重新命名檔案"
folderName: "資料夾名稱"
createFolder: "新增資料夾"
@@ -471,7 +472,7 @@ retype: "重新輸入"
noteOf: "{user}的貼文"
quoteAttached: "引用"
quoteQuestion: "是否要引用?"
attachAsFileQuestion: "剪貼簿的文字較長。請問是否要改成附加檔案呢?"
attachAsFileQuestion: "剪貼簿的文字較長。請問是否要將其以文字檔的方式附加呢?"
noMessagesYet: "沒有訊息"
newMessageExists: "有新的訊息"
onlyOneFileCanBeAttached: "只能加入一個附件"
@@ -1025,6 +1026,7 @@ thisPostMayBeAnnoyingHome: "發佈到首頁"
thisPostMayBeAnnoyingCancel: "退出"
thisPostMayBeAnnoyingIgnore: "直接發佈貼文"
collapseRenotes: "省略顯示已看過的轉發貼文"
collapseRenotesDescription: "將已做過反應和轉發的貼文折疊顯示。"
internalServerError: "內部伺服器錯誤"
internalServerErrorDescription: "內部伺服器出現意外錯誤。"
copyErrorInfo: "複製錯誤資訊"
@@ -1241,8 +1243,8 @@ alwaysConfirmFollow: "點擊追隨時總是顯示確認訊息"
inquiry: "聯絡我們"
_delivery:
status: "傳送狀態"
stop: "已凍結"
resume: "繼續傳送"
stop: "停止傳送"
resume: "恢復傳送"
_type:
none: "直播中"
manuallySuspended: "手動暫停中"
@@ -1373,6 +1375,8 @@ _serverSettings:
fanoutTimelineDescription: "如果啟用的話檢索各個時間軸的性能會顯著提昇資料庫的負荷也會減少。不過Redis 的記憶體使用量會增加。如果伺服器的記憶體容量比較少或者運行不穩定,可以停用。"
fanoutTimelineDbFallback: "資料庫的回退"
fanoutTimelineDbFallbackDescription: "若啟用,在時間軸沒有快取的情況下將執行回退處理以額外查詢資料庫。若停用,可以透過不執行回退處理來進一步減少伺服器的負荷,但會限制可取得的時間軸範圍。"
inquiryUrl: "聯絡表單網址"
inquiryUrlDescription: "指定伺服器運營者的聯絡表單網址或包含運營者聯絡資訊網頁的網址。"
_accountMigration:
moveFrom: "從其他帳戶遷移到這個帳戶"
moveFromSub: "為另一個帳戶建立別名"
@@ -2358,6 +2362,7 @@ _deck:
alwaysShowMainColumn: "總是顯示主欄"
columnAlign: "對齊欄位"
addColumn: "新增欄位"
newNoteNotificationSettings: "新貼文通知的設定"
configureColumn: "欄位的設定"
swapLeft: "向左移動"
swapRight: "向右移動"

View File

@@ -1,6 +1,6 @@
{
"name": "misskey",
"version": "2024.5.0-beta.5",
"version": "2024.5.0",
"codename": "nasubi",
"repository": {
"type": "git",

View File

@@ -0,0 +1,16 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
export class InquiryUrl1717117195275 {
name = 'InquiryUrl1717117195275'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" ADD "inquiryUrl" character varying(1024)`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "inquiryUrl"`);
}
}

View File

@@ -4,7 +4,7 @@
"private": true,
"type": "module",
"engines": {
"node": ">=20.10.0"
"node": "^20.10.0"
},
"scripts": {
"start": "node ./built/boot/entry.js",
@@ -86,6 +86,8 @@
"@nestjs/core": "10.3.8",
"@nestjs/testing": "10.3.8",
"@peertube/http-signature": "1.7.0",
"@sentry/node": "^8.5.0",
"@sentry/profiling-node": "^8.5.0",
"@simplewebauthn/server": "10.0.0",
"@sinonjs/fake-timers": "11.2.2",
"@smithy/node-http-handler": "2.5.0",

View File

@@ -10,6 +10,8 @@ import * as os from 'node:os';
import cluster from 'node:cluster';
import chalk from 'chalk';
import chalkTemplate from 'chalk-template';
import * as Sentry from '@sentry/node';
import { nodeProfilingIntegration } from '@sentry/profiling-node';
import Logger from '@/logger.js';
import { loadConfig } from '@/config.js';
import type { Config } from '@/config.js';
@@ -71,6 +73,24 @@ export async function masterMain() {
bootLogger.succ('Misskey initialized');
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.disableClustering) {
if (envOption.onlyServer) {
await server();

View File

@@ -7,6 +7,7 @@ import * as fs from 'node:fs';
import { fileURLToPath } from 'node:url';
import { dirname, resolve } from 'node:path';
import * as yaml from 'js-yaml';
import * as Sentry from '@sentry/node';
import type { RedisOptions } from 'ioredis';
type RedisOptionsSource = Partial<RedisOptions> & {
@@ -56,6 +57,8 @@ type Source = {
index: string;
scope?: 'local' | 'global' | string[];
};
sentryForBackend?: { options: Partial<Sentry.NodeOptions>; enableNodeProfiling: boolean; };
sentryForFrontend?: { options: Partial<Sentry.NodeOptions> };
publishTarballInsteadOfProvideRepositoryUrl?: boolean;
@@ -166,6 +169,8 @@ export type Config = {
redisForPubsub: RedisOptions & RedisOptionsSource;
redisForJobQueue: RedisOptions & RedisOptionsSource;
redisForTimelines: RedisOptions & RedisOptionsSource;
sentryForBackend: { options: Partial<Sentry.NodeOptions>; enableNodeProfiling: boolean; } | undefined;
sentryForFrontend: { options: Partial<Sentry.NodeOptions> } | undefined;
perChannelMaxNoteCacheCount: number;
perUserNotificationsMaxCount: number;
deactivateAntennaThreshold: number;
@@ -234,6 +239,8 @@ export function loadConfig(): Config {
redisForPubsub: config.redisForPubsub ? convertRedisOptions(config.redisForPubsub, host) : redis,
redisForJobQueue: config.redisForJobQueue ? convertRedisOptions(config.redisForJobQueue, host) : redis,
redisForTimelines: config.redisForTimelines ? convertRedisOptions(config.redisForTimelines, host) : redis,
sentryForBackend: config.sentryForBackend,
sentryForFrontend: config.sentryForFrontend,
id: config.id,
proxy: config.proxy,
proxySmtp: config.proxySmtp,

View File

@@ -346,10 +346,11 @@ export class CustomEmojiService implements OnApplicationShutdown {
@bindThis
public async populateEmojis(emojiNames: string[], noteUserHost: string | null): Promise<Record<string, string>> {
const emojis = await Promise.all(emojiNames.map(x => this.populateEmoji(x, noteUserHost)));
const res = {} as any;
const res = {} as Record<string, string>;
for (let i = 0; i < emojiNames.length; i++) {
if (emojis[i] != null) {
res[emojiNames[i]] = emojis[i];
const resolvedEmoji = emojis[i];
if (resolvedEmoji != null) {
res[emojiNames[i]] = resolvedEmoji;
}
}
return res;

View File

@@ -497,20 +497,20 @@ export class DriveService {
if (user && !force) {
// Check if there is a file with the same hash
const much = await this.driveFilesRepository.findOneBy({
const matched = await this.driveFilesRepository.findOneBy({
md5: info.md5,
userId: user.id,
});
if (much) {
this.registerLogger.info(`file with same hash is found: ${much.id}`);
if (sensitive && !much.isSensitive) {
if (matched) {
this.registerLogger.info(`file with same hash is found: ${matched.id}`);
if (sensitive && !matched.isSensitive) {
// The file is federated as sensitive for this time, but was federated as non-sensitive before.
// Therefore, update the file to sensitive.
await this.driveFilesRepository.update({ id: much.id }, { isSensitive: true });
much.isSensitive = true;
await this.driveFilesRepository.update({ id: matched.id }, { isSensitive: true });
matched.isSensitive = true;
}
return much;
return matched;
}
}

View File

@@ -154,7 +154,7 @@ export class FetchInstanceMetadataService {
throw new Error('No wellknown links');
}
const links = wellknown.links as any[];
const links = wellknown.links as ({ rel: string, href: string; })[];
const link1_0 = links.find(link => link.rel === 'http://nodeinfo.diaspora.software/ns/schema/1.0');
const link2_0 = links.find(link => link.rel === 'http://nodeinfo.diaspora.software/ns/schema/2.0');

View File

@@ -28,6 +28,7 @@ import type { UsersRepository, NotesRepository, FollowingsRepository, AbuseUserR
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 { 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';
@@ -36,9 +37,8 @@ import { ApResolverService } from './ApResolverService.js';
import { ApAudienceService } from './ApAudienceService.js';
import { ApPersonService } from './models/ApPersonService.js';
import { ApQuestionService } from './models/ApQuestionService.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
import type { Resolver } from './ApResolverService.js';
import type { IAccept, IAdd, IAnnounce, IBlock, ICreate, IDelete, IFlag, IFollow, ILike, IObject, IReject, IRemove, IUndo, IUpdate, IMove } from './type.js';
import type { IAccept, IAdd, IAnnounce, IBlock, ICreate, IDelete, IFlag, IFollow, ILike, IObject, IReject, IRemove, IUndo, IUpdate, IMove, IPost } from './type.js';
@Injectable()
export class ApInboxService {
@@ -90,13 +90,15 @@ export class ApInboxService {
}
@bindThis
public async performActivity(actor: MiRemoteUser, activity: IObject): Promise<void> {
public async performActivity(actor: MiRemoteUser, activity: IObject): Promise<string | void> {
let result = undefined as string | void;
if (isCollectionOrOrderedCollection(activity)) {
const results = [] as [string, string | void][];
const resolver = this.apResolverService.createResolver();
for (const item of toArray(isCollection(activity) ? activity.items : activity.orderedItems)) {
const act = await resolver.resolve(item);
try {
await this.performOneActivity(actor, act);
results.push([getApId(item), await this.performOneActivity(actor, act)]);
} catch (err) {
if (err instanceof Error || typeof err === 'string') {
this.logger.error(err);
@@ -105,8 +107,13 @@ export class ApInboxService {
}
}
}
const hasReason = results.some(([, reason]) => (reason != null && !reason.startsWith('ok')));
if (hasReason) {
result = results.map(([id, reason]) => `${id}: ${reason}`).join('\n');
}
} else {
await this.performOneActivity(actor, activity);
result = await this.performOneActivity(actor, activity);
}
// ついでにリモートユーザーの情報が古かったら更新しておく
@@ -117,42 +124,43 @@ export class ApInboxService {
});
}
}
return result;
}
@bindThis
public async performOneActivity(actor: MiRemoteUser, activity: IObject): Promise<void> {
public async performOneActivity(actor: MiRemoteUser, activity: IObject): Promise<string | void> {
if (actor.isSuspended) return;
if (isCreate(activity)) {
await this.create(actor, activity);
return await this.create(actor, activity);
} else if (isDelete(activity)) {
await this.delete(actor, activity);
return await this.delete(actor, activity);
} else if (isUpdate(activity)) {
await this.update(actor, activity);
return await this.update(actor, activity);
} else if (isFollow(activity)) {
await this.follow(actor, activity);
return await this.follow(actor, activity);
} else if (isAccept(activity)) {
await this.accept(actor, activity);
return await this.accept(actor, activity);
} else if (isReject(activity)) {
await this.reject(actor, activity);
return await this.reject(actor, activity);
} else if (isAdd(activity)) {
await this.add(actor, activity).catch(err => this.logger.error(err));
return await this.add(actor, activity);
} else if (isRemove(activity)) {
await this.remove(actor, activity).catch(err => this.logger.error(err));
return await this.remove(actor, activity);
} else if (isAnnounce(activity)) {
await this.announce(actor, activity);
return await this.announce(actor, activity);
} else if (isLike(activity)) {
await this.like(actor, activity);
return await this.like(actor, activity);
} else if (isUndo(activity)) {
await this.undo(actor, activity);
return await this.undo(actor, activity);
} else if (isBlock(activity)) {
await this.block(actor, activity);
return await this.block(actor, activity);
} else if (isFlag(activity)) {
await this.flag(actor, activity);
return await this.flag(actor, activity);
} else if (isMove(activity)) {
await this.move(actor, activity);
return await this.move(actor, activity);
} else {
this.logger.warn(`unrecognized activity type: ${activity.type}`);
return `unrecognized activity type: ${activity.type}`;
}
}
@@ -234,38 +242,49 @@ export class ApInboxService {
}
@bindThis
private async add(actor: MiRemoteUser, activity: IAdd): Promise<void> {
private async add(actor: MiRemoteUser, activity: IAdd): Promise<string | void> {
if (actor.uri !== activity.actor) {
throw new Error('invalid actor');
return 'invalid actor';
}
if (activity.target == null) {
throw new Error('target is null');
return 'target is null';
}
if (activity.target === actor.featured) {
const note = await this.apNoteService.resolveNote(activity.object);
if (note == null) throw new Error('note not found');
if (note == null) return 'note not found';
await this.notePiningService.addPinned(actor, note.id);
return;
}
throw new Error(`unknown target: ${activity.target}`);
return `unknown target: ${activity.target}`;
}
@bindThis
private async announce(actor: MiRemoteUser, activity: IAnnounce): Promise<void> {
private async announce(actor: MiRemoteUser, activity: IAnnounce): Promise<string | void> {
const uri = getApId(activity);
this.logger.info(`Announce: ${uri}`);
const targetUri = getApId(activity.object);
const resolver = this.apResolverService.createResolver();
await this.announceNote(actor, activity, targetUri);
if (!activity.object) return 'skip: activity has no object property';
const targetUri = getApId(activity.object);
if (targetUri.startsWith('bear:')) return 'skip: bearcaps url not supported.';
const target = await resolver.resolve(activity.object).catch(e => {
this.logger.error(`Resolution failed: ${e}`);
return e;
});
if (isPost(target)) return await this.announceNote(actor, activity, target);
return `skip: unknown object type ${getApType(target)}`;
}
@bindThis
private async announceNote(actor: MiRemoteUser, activity: IAnnounce, targetUri: string): Promise<void> {
private async announceNote(actor: MiRemoteUser, activity: IAnnounce, target: IPost): Promise<string | void> {
const uri = getApId(activity);
if (actor.isSuspended) {
@@ -288,24 +307,21 @@ export class ApInboxService {
// Announce対象をresolve
let renote;
try {
renote = await this.apNoteService.resolveNote(targetUri);
if (renote == null) throw new Error('announce target is null');
renote = await this.apNoteService.resolveNote(target);
if (renote == null) return 'announce target is null';
} catch (err) {
// 対象が4xxならスキップ
if (err instanceof StatusError) {
if (!err.isRetryable) {
this.logger.warn(`Ignored announce target ${targetUri} - ${err.statusCode}`);
return;
return `Ignored announce target ${target.id} - ${err.statusCode}`;
}
this.logger.warn(`Error in announce target ${targetUri} - ${err.statusCode}`);
return `Error in announce target ${target.id} - ${err.statusCode}`;
}
throw err;
}
if (!await this.noteEntityService.isVisibleForMe(renote, actor.id)) {
this.logger.warn('skip: invalid actor for this activity');
return;
return 'skip: invalid actor for this activity';
}
this.logger.info(`Creating the (Re)Note: ${uri}`);
@@ -314,8 +330,7 @@ export class ApInboxService {
const createdAt = activity.published ? new Date(activity.published) : null;
if (createdAt && createdAt < this.idService.parse(renote.id).date) {
this.logger.warn('skip: malformed createdAt');
return;
return 'skip: malformed createdAt';
}
await this.noteCreateService.create(actor, {
@@ -349,11 +364,15 @@ export class ApInboxService {
}
@bindThis
private async create(actor: MiRemoteUser, activity: ICreate): Promise<void> {
private async create(actor: MiRemoteUser, activity: ICreate): Promise<string | void> {
const uri = getApId(activity);
this.logger.info(`Create: ${uri}`);
if (!activity.object) return 'skip: activity has no object property';
const targetUri = getApId(activity.object);
if (targetUri.startsWith('bear:')) return 'skip: bearcaps url not supported.';
// copy audiences between activity <=> object.
if (typeof activity.object === 'object') {
const to = unique(concat([toArray(activity.to), toArray(activity.object.to)]));
@@ -380,7 +399,7 @@ export class ApInboxService {
if (isPost(object)) {
await this.createNote(resolver, actor, object, false, activity);
} else {
this.logger.warn(`Unknown type: ${getApType(object)}`);
return `Unknown type: ${getApType(object)}`;
}
}
@@ -422,7 +441,7 @@ export class ApInboxService {
@bindThis
private async delete(actor: MiRemoteUser, activity: IDelete): Promise<string> {
if (actor.uri !== activity.actor) {
throw new Error('invalid actor');
return 'invalid actor';
}
// 削除対象objectのtype
@@ -581,29 +600,29 @@ export class ApInboxService {
}
@bindThis
private async remove(actor: MiRemoteUser, activity: IRemove): Promise<void> {
private async remove(actor: MiRemoteUser, activity: IRemove): Promise<string | void> {
if (actor.uri !== activity.actor) {
throw new Error('invalid actor');
return 'invalid actor';
}
if (activity.target == null) {
throw new Error('target is null');
return 'target is null';
}
if (activity.target === actor.featured) {
const note = await this.apNoteService.resolveNote(activity.object);
if (note == null) throw new Error('note not found');
if (note == null) return 'note not found';
await this.notePiningService.removePinned(actor, note.id);
return;
}
throw new Error(`unknown target: ${activity.target}`);
return `unknown target: ${activity.target}`;
}
@bindThis
private async undo(actor: MiRemoteUser, activity: IUndo): Promise<string> {
if (actor.uri !== activity.actor) {
throw new Error('invalid actor');
return 'invalid actor';
}
const uri = activity.id ?? activity;
@@ -614,7 +633,7 @@ export class ApInboxService {
const object = await resolver.resolve(activity.object).catch(e => {
this.logger.error(`Resolution failed: ${e}`);
throw e;
return e;
});
// don't queue because the sender may attempt again when timeout

View File

@@ -81,20 +81,20 @@ export class ApNoteService {
const expectHost = this.utilityService.extractDbHost(uri);
if (!validPost.includes(getApType(object))) {
return new Error(`invalid Note: invalid object type ${getApType(object)}`);
return new IdentifiableError('d450b8a9-48e4-4dab-ae36-f4db763fda7c', `invalid Note: invalid object type ${getApType(object)}`);
}
if (object.id && this.utilityService.extractDbHost(object.id) !== expectHost) {
return new Error(`invalid Note: id has different host. expected: ${expectHost}, actual: ${this.utilityService.extractDbHost(object.id)}`);
return new IdentifiableError('d450b8a9-48e4-4dab-ae36-f4db763fda7c', `invalid Note: id has different host. expected: ${expectHost}, actual: ${this.utilityService.extractDbHost(object.id)}`);
}
const actualHost = object.attributedTo && this.utilityService.extractDbHost(getOneApId(object.attributedTo));
if (object.attributedTo && actualHost !== expectHost) {
return new Error(`invalid Note: attributedTo has different host. expected: ${expectHost}, actual: ${actualHost}`);
return new IdentifiableError('d450b8a9-48e4-4dab-ae36-f4db763fda7c', `invalid Note: attributedTo has different host. expected: ${expectHost}, actual: ${actualHost}`);
}
if (object.published && !this.idService.isSafeT(new Date(object.published).valueOf())) {
return new Error('invalid Note: published timestamp is malformed');
return new IdentifiableError('d450b8a9-48e4-4dab-ae36-f4db763fda7c', 'invalid Note: published timestamp is malformed');
}
return null;

View File

@@ -328,3 +328,4 @@ export const isAnnounce = (object: IObject): object is IAnnounce => getApType(ob
export const isBlock = (object: IObject): object is IBlock => getApType(object) === 'Block';
export const isFlag = (object: IObject): object is IFlag => getApType(object) === 'Flag';
export const isMove = (object: IObject): object is IMove => getApType(object) === 'Move';
export const isNote = (object: IObject): object is IPost => getApType(object) === 'Note';

View File

@@ -10,6 +10,8 @@ import { awaitAll } from '@/misc/prelude/await-all.js';
import type { MiAbuseUserReport } from '@/models/AbuseUserReport.js';
import { bindThis } from '@/decorators.js';
import { IdService } from '@/core/IdService.js';
import { isNotNull } from '@/misc/is-not-null.js';
import type { Packed } from '@/misc/json-schema.js';
import { UserEntityService } from './UserEntityService.js';
@Injectable()
@@ -26,6 +28,11 @@ export class AbuseUserReportEntityService {
@bindThis
public async pack(
src: MiAbuseUserReport['id'] | MiAbuseUserReport,
hint?: {
packedReporter?: Packed<'UserDetailedNotMe'>,
packedTargetUser?: Packed<'UserDetailedNotMe'>,
packedAssignee?: Packed<'UserDetailedNotMe'>,
},
) {
const report = typeof src === 'object' ? src : await this.abuseUserReportsRepository.findOneByOrFail({ id: src });
@@ -37,13 +44,13 @@ export class AbuseUserReportEntityService {
reporterId: report.reporterId,
targetUserId: report.targetUserId,
assigneeId: report.assigneeId,
reporter: this.userEntityService.pack(report.reporter ?? report.reporterId, null, {
reporter: hint?.packedReporter ?? this.userEntityService.pack(report.reporter ?? report.reporterId, null, {
schema: 'UserDetailedNotMe',
}),
targetUser: this.userEntityService.pack(report.targetUser ?? report.targetUserId, null, {
targetUser: hint?.packedTargetUser ?? this.userEntityService.pack(report.targetUser ?? report.targetUserId, null, {
schema: 'UserDetailedNotMe',
}),
assignee: report.assigneeId ? this.userEntityService.pack(report.assignee ?? report.assigneeId, null, {
assignee: report.assigneeId ? hint?.packedAssignee ?? this.userEntityService.pack(report.assignee ?? report.assigneeId, null, {
schema: 'UserDetailedNotMe',
}) : null,
forwarded: report.forwarded,
@@ -51,9 +58,24 @@ export class AbuseUserReportEntityService {
}
@bindThis
public packMany(
reports: any[],
public async packMany(
reports: MiAbuseUserReport[],
) {
return Promise.all(reports.map(x => this.pack(x)));
const _reporters = reports.map(({ reporter, reporterId }) => reporter ?? reporterId);
const _targetUsers = reports.map(({ targetUser, targetUserId }) => targetUser ?? targetUserId);
const _assignees = reports.map(({ assignee, assigneeId }) => assignee ?? assigneeId).filter(isNotNull);
const _userMap = await this.userEntityService.packMany(
[..._reporters, ..._targetUsers, ..._assignees],
null,
{ schema: 'UserDetailedNotMe' },
).then(users => new Map(users.map(u => [u.id, u])));
return Promise.all(
reports.map(report => {
const packedReporter = _userMap.get(report.reporterId);
const packedTargetUser = _userMap.get(report.targetUserId);
const packedAssignee = report.assigneeId != null ? _userMap.get(report.assigneeId) : undefined;
return this.pack(report, { packedReporter, packedTargetUser, packedAssignee });
}),
);
}
}

View File

@@ -43,6 +43,7 @@ export class AntennaEntityService {
withFile: antenna.withFile,
isActive: antenna.isActive,
hasUnreadNote: false, // TODO
notify: false, // 後方互換性のため
};
}
}

View File

@@ -29,6 +29,9 @@ export class BlockingEntityService {
public async pack(
src: MiBlocking['id'] | MiBlocking,
me?: { id: MiUser['id'] } | null | undefined,
hint?: {
blockee?: Packed<'UserDetailedNotMe'>,
},
): Promise<Packed<'Blocking'>> {
const blocking = typeof src === 'object' ? src : await this.blockingsRepository.findOneByOrFail({ id: src });
@@ -36,17 +39,20 @@ export class BlockingEntityService {
id: blocking.id,
createdAt: this.idService.parse(blocking.id).date.toISOString(),
blockeeId: blocking.blockeeId,
blockee: this.userEntityService.pack(blocking.blockeeId, me, {
blockee: hint?.blockee ?? this.userEntityService.pack(blocking.blockeeId, me, {
schema: 'UserDetailedNotMe',
}),
});
}
@bindThis
public packMany(
blockings: any[],
public async packMany(
blockings: MiBlocking[],
me: { id: MiUser['id'] },
) {
return Promise.all(blockings.map(x => this.pack(x, me)));
const _blockees = blockings.map(({ blockee, blockeeId }) => blockee ?? blockeeId);
const _userMap = await this.userEntityService.packMany(_blockees, me, { schema: 'UserDetailedNotMe' })
.then(users => new Map(users.map(u => [u.id, u])));
return Promise.all(blockings.map(blocking => this.pack(blocking, me, { blockee: _userMap.get(blocking.blockeeId) })));
}
}

View File

@@ -35,6 +35,9 @@ export class ClipEntityService {
public async pack(
src: MiClip['id'] | MiClip,
me?: { id: MiUser['id'] } | null | undefined,
hint?: {
packedUser?: Packed<'UserLite'>
},
): Promise<Packed<'Clip'>> {
const meId = me ? me.id : null;
const clip = typeof src === 'object' ? src : await this.clipsRepository.findOneByOrFail({ id: src });
@@ -44,7 +47,7 @@ export class ClipEntityService {
createdAt: this.idService.parse(clip.id).date.toISOString(),
lastClippedAt: clip.lastClippedAt ? clip.lastClippedAt.toISOString() : null,
userId: clip.userId,
user: this.userEntityService.pack(clip.user ?? clip.userId),
user: hint?.packedUser ?? this.userEntityService.pack(clip.user ?? clip.userId),
name: clip.name,
description: clip.description,
isPublic: clip.isPublic,
@@ -55,11 +58,14 @@ export class ClipEntityService {
}
@bindThis
public packMany(
public async packMany(
clips: MiClip[],
me?: { id: MiUser['id'] } | null | undefined,
) {
return Promise.all(clips.map(x => this.pack(x, me)));
const _users = clips.map(({ user, userId }) => user ?? userId);
const _userMap = await this.userEntityService.packMany(_users, me)
.then(users => new Map(users.map(u => [u.id, u])));
return Promise.all(clips.map(clip => this.pack(clip, me, { packedUser: _userMap.get(clip.userId) })));
}
}

View File

@@ -222,6 +222,9 @@ export class DriveFileEntityService {
public async packNullable(
src: MiDriveFile['id'] | MiDriveFile,
options?: PackOptions,
hint?: {
packedUser?: Packed<'UserLite'>
},
): Promise<Packed<'DriveFile'> | null> {
const opts = Object.assign({
detail: false,
@@ -249,7 +252,7 @@ export class DriveFileEntityService {
detail: true,
}) : null,
userId: file.userId,
user: (opts.withUser && file.userId) ? this.userEntityService.pack(file.userId) : null,
user: (opts.withUser && file.userId) ? hint?.packedUser ?? this.userEntityService.pack(file.userId) : null,
});
}
@@ -258,7 +261,10 @@ export class DriveFileEntityService {
files: MiDriveFile[],
options?: PackOptions,
): Promise<Packed<'DriveFile'>[]> {
const items = await Promise.all(files.map(f => this.packNullable(f, options)));
const _user = files.map(({ user, userId }) => user ?? userId).filter(isNotNull);
const _userMap = await this.userEntityService.packMany(_user)
.then(users => new Map(users.map(user => [user.id, user])));
const items = await Promise.all(files.map(f => this.packNullable(f, options, f.userId ? { packedUser: _userMap.get(f.userId) } : {})));
return items.filter(isNotNull);
}

View File

@@ -33,6 +33,9 @@ export class FlashEntityService {
public async pack(
src: MiFlash['id'] | MiFlash,
me?: { id: MiUser['id'] } | null | undefined,
hint?: {
packedUser?: Packed<'UserLite'>
},
): Promise<Packed<'Flash'>> {
const meId = me ? me.id : null;
const flash = typeof src === 'object' ? src : await this.flashsRepository.findOneByOrFail({ id: src });
@@ -42,7 +45,7 @@ export class FlashEntityService {
createdAt: this.idService.parse(flash.id).date.toISOString(),
updatedAt: flash.updatedAt.toISOString(),
userId: flash.userId,
user: this.userEntityService.pack(flash.user ?? flash.userId, me), // { schema: 'UserDetailed' } すると無限ループするので注意
user: hint?.packedUser ?? this.userEntityService.pack(flash.user ?? flash.userId, me), // { schema: 'UserDetailed' } すると無限ループするので注意
title: flash.title,
summary: flash.summary,
script: flash.script,
@@ -52,11 +55,14 @@ export class FlashEntityService {
}
@bindThis
public packMany(
flashs: MiFlash[],
public async packMany(
flashes: MiFlash[],
me?: { id: MiUser['id'] } | null | undefined,
) {
return Promise.all(flashs.map(x => this.pack(x, me)));
const _users = flashes.map(({ user, userId }) => user ?? userId);
const _userMap = await this.userEntityService.packMany(_users, me)
.then(users => new Map(users.map(u => [u.id, u])));
return Promise.all(flashes.map(flash => this.pack(flash, me, { packedUser: _userMap.get(flash.userId) })));
}
}

View File

@@ -10,6 +10,7 @@ import type { } from '@/models/Blocking.js';
import type { MiUser } from '@/models/User.js';
import type { MiFollowRequest } from '@/models/FollowRequest.js';
import { bindThis } from '@/decorators.js';
import type { Packed } from '@/misc/json-schema.js';
import { UserEntityService } from './UserEntityService.js';
@Injectable()
@@ -26,14 +27,36 @@ export class FollowRequestEntityService {
public async pack(
src: MiFollowRequest['id'] | MiFollowRequest,
me?: { id: MiUser['id'] } | null | undefined,
hint?: {
packedFollower?: Packed<'UserLite'>,
packedFollowee?: Packed<'UserLite'>,
},
) {
const request = typeof src === 'object' ? src : await this.followRequestsRepository.findOneByOrFail({ id: src });
return {
id: request.id,
follower: await this.userEntityService.pack(request.followerId, me),
followee: await this.userEntityService.pack(request.followeeId, me),
follower: hint?.packedFollower ?? await this.userEntityService.pack(request.followerId, me),
followee: hint?.packedFollowee ?? await this.userEntityService.pack(request.followeeId, me),
};
}
@bindThis
public async packMany(
requests: MiFollowRequest[],
me?: { id: MiUser['id'] } | null | undefined,
) {
const _followers = requests.map(({ follower, followerId }) => follower ?? followerId);
const _followees = requests.map(({ followee, followeeId }) => followee ?? followeeId);
const _userMap = await this.userEntityService.packMany([..._followers, ..._followees], me)
.then(users => new Map(users.map(u => [u.id, u])));
return Promise.all(
requests.map(req => {
const packedFollower = _userMap.get(req.followerId);
const packedFollowee = _userMap.get(req.followeeId);
return this.pack(req, me, { packedFollower, packedFollowee });
}),
);
}
}

View File

@@ -78,6 +78,10 @@ export class FollowingEntityService {
populateFollowee?: boolean;
populateFollower?: boolean;
},
hint?: {
packedFollowee?: Packed<'UserDetailedNotMe'>,
packedFollower?: Packed<'UserDetailedNotMe'>,
},
): Promise<Packed<'Following'>> {
const following = typeof src === 'object' ? src : await this.followingsRepository.findOneByOrFail({ id: src });
@@ -88,25 +92,35 @@ export class FollowingEntityService {
createdAt: this.idService.parse(following.id).date.toISOString(),
followeeId: following.followeeId,
followerId: following.followerId,
followee: opts.populateFollowee ? this.userEntityService.pack(following.followee ?? following.followeeId, me, {
followee: opts.populateFollowee ? hint?.packedFollowee ?? this.userEntityService.pack(following.followee ?? following.followeeId, me, {
schema: 'UserDetailedNotMe',
}) : undefined,
follower: opts.populateFollower ? this.userEntityService.pack(following.follower ?? following.followerId, me, {
follower: opts.populateFollower ? hint?.packedFollower ?? this.userEntityService.pack(following.follower ?? following.followerId, me, {
schema: 'UserDetailedNotMe',
}) : undefined,
});
}
@bindThis
public packMany(
followings: any[],
public async packMany(
followings: MiFollowing[],
me?: { id: MiUser['id'] } | null | undefined,
opts?: {
populateFollowee?: boolean;
populateFollower?: boolean;
},
) {
return Promise.all(followings.map(x => this.pack(x, me, opts)));
const _followees = opts?.populateFollowee ? followings.map(({ followee, followeeId }) => followee ?? followeeId) : [];
const _followers = opts?.populateFollower ? followings.map(({ follower, followerId }) => follower ?? followerId) : [];
const _userMap = await this.userEntityService.packMany([..._followees, ..._followers], me, { schema: 'UserDetailedNotMe' })
.then(users => new Map(users.map(u => [u.id, u])));
return Promise.all(
followings.map(following => {
const packedFollowee = opts?.populateFollowee ? _userMap.get(following.followeeId) : undefined;
const packedFollower = opts?.populateFollower ? _userMap.get(following.followerId) : undefined;
return this.pack(following, me, opts, { packedFollowee, packedFollower });
}),
);
}
}

View File

@@ -35,6 +35,9 @@ export class GalleryPostEntityService {
public async pack(
src: MiGalleryPost['id'] | MiGalleryPost,
me?: { id: MiUser['id'] } | null | undefined,
hint?: {
packedUser?: Packed<'UserLite'>
},
): Promise<Packed<'GalleryPost'>> {
const meId = me ? me.id : null;
const post = typeof src === 'object' ? src : await this.galleryPostsRepository.findOneByOrFail({ id: src });
@@ -44,7 +47,7 @@ export class GalleryPostEntityService {
createdAt: this.idService.parse(post.id).date.toISOString(),
updatedAt: post.updatedAt.toISOString(),
userId: post.userId,
user: this.userEntityService.pack(post.user ?? post.userId, me),
user: hint?.packedUser ?? this.userEntityService.pack(post.user ?? post.userId, me),
title: post.title,
description: post.description,
fileIds: post.fileIds,
@@ -58,11 +61,14 @@ export class GalleryPostEntityService {
}
@bindThis
public packMany(
public async packMany(
posts: MiGalleryPost[],
me?: { id: MiUser['id'] } | null | undefined,
) {
return Promise.all(posts.map(x => this.pack(x, me)));
const _users = posts.map(({ user, userId }) => user ?? userId);
const _userMap = await this.userEntityService.packMany(_users, me)
.then(users => new Map(users.map(u => [u.id, u])));
return Promise.all(posts.map(post => this.pack(post, me, { packedUser: _userMap.get(post.userId) })));
}
}

View File

@@ -12,6 +12,7 @@ import type { MiUser } from '@/models/User.js';
import type { MiRegistrationTicket } from '@/models/RegistrationTicket.js';
import { bindThis } from '@/decorators.js';
import { IdService } from '@/core/IdService.js';
import { isNotNull } from '@/misc/is-not-null.js';
import { UserEntityService } from './UserEntityService.js';
@Injectable()
@@ -29,6 +30,10 @@ export class InviteCodeEntityService {
public async pack(
src: MiRegistrationTicket['id'] | MiRegistrationTicket,
me?: { id: MiUser['id'] } | null | undefined,
hints?: {
packedCreatedBy?: Packed<'UserLite'>,
packedUsedBy?: Packed<'UserLite'>,
},
): Promise<Packed<'InviteCode'>> {
const target = typeof src === 'object' ? src : await this.registrationTicketsRepository.findOneOrFail({
where: {
@@ -42,18 +47,28 @@ export class InviteCodeEntityService {
code: target.code,
expiresAt: target.expiresAt ? target.expiresAt.toISOString() : null,
createdAt: this.idService.parse(target.id).date.toISOString(),
createdBy: target.createdBy ? await this.userEntityService.pack(target.createdBy, me) : null,
usedBy: target.usedBy ? await this.userEntityService.pack(target.usedBy, me) : null,
createdBy: target.createdBy ? hints?.packedCreatedBy ?? await this.userEntityService.pack(target.createdBy, me) : null,
usedBy: target.usedBy ? hints?.packedUsedBy ?? await this.userEntityService.pack(target.usedBy, me) : null,
usedAt: target.usedAt ? target.usedAt.toISOString() : null,
used: !!target.usedAt,
});
}
@bindThis
public packMany(
targets: any[],
public async packMany(
tickets: MiRegistrationTicket[],
me: { id: MiUser['id'] },
) {
return Promise.all(targets.map(x => this.pack(x, me)));
const _createdBys = tickets.map(({ createdBy, createdById }) => createdBy ?? createdById).filter(isNotNull);
const _usedBys = tickets.map(({ usedBy, usedById }) => usedBy ?? usedById).filter(isNotNull);
const _userMap = await this.userEntityService.packMany([..._createdBys, ..._usedBys], me)
.then(users => new Map(users.map(u => [u.id, u])));
return Promise.all(
tickets.map(ticket => {
const packedCreatedBy = ticket.createdById != null ? _userMap.get(ticket.createdById) : undefined;
const packedUsedBy = ticket.usedById != null ? _userMap.get(ticket.usedById) : undefined;
return this.pack(ticket, me, { packedCreatedBy, packedUsedBy });
}),
);
}
}

View File

@@ -67,6 +67,7 @@ export class MetaEntityService {
feedbackUrl: instance.feedbackUrl,
impressumUrl: instance.impressumUrl,
privacyPolicyUrl: instance.privacyPolicyUrl,
inquiryUrl: instance.inquiryUrl,
disableRegistration: instance.disableRegistration,
emailRequiredForSignup: instance.emailRequiredForSignup,
enableHcaptcha: instance.enableHcaptcha,

View File

@@ -8,9 +8,10 @@ import { DI } from '@/di-symbols.js';
import type { ModerationLogsRepository } from '@/models/_.js';
import { awaitAll } from '@/misc/prelude/await-all.js';
import type { } from '@/models/Blocking.js';
import type { MiModerationLog } from '@/models/ModerationLog.js';
import { MiModerationLog } from '@/models/ModerationLog.js';
import { bindThis } from '@/decorators.js';
import { IdService } from '@/core/IdService.js';
import type { Packed } from '@/misc/json-schema.js';
import { UserEntityService } from './UserEntityService.js';
@Injectable()
@@ -27,6 +28,9 @@ export class ModerationLogEntityService {
@bindThis
public async pack(
src: MiModerationLog['id'] | MiModerationLog,
hint?: {
packedUser?: Packed<'UserDetailedNotMe'>,
},
) {
const log = typeof src === 'object' ? src : await this.moderationLogsRepository.findOneByOrFail({ id: src });
@@ -36,17 +40,20 @@ export class ModerationLogEntityService {
type: log.type,
info: log.info,
userId: log.userId,
user: this.userEntityService.pack(log.user ?? log.userId, null, {
user: hint?.packedUser ?? this.userEntityService.pack(log.user ?? log.userId, null, {
schema: 'UserDetailedNotMe',
}),
});
}
@bindThis
public packMany(
reports: any[],
public async packMany(
reports: MiModerationLog[],
) {
return Promise.all(reports.map(x => this.pack(x)));
const _users = reports.map(({ user, userId }) => user ?? userId);
const _userMap = await this.userEntityService.packMany(_users, null, { schema: 'UserDetailedNotMe' })
.then(users => new Map(users.map(u => [u.id, u])));
return Promise.all(reports.map(report => this.pack(report, { packedUser: _userMap.get(report.userId) })));
}
}

View File

@@ -30,6 +30,9 @@ export class MutingEntityService {
public async pack(
src: MiMuting['id'] | MiMuting,
me?: { id: MiUser['id'] } | null | undefined,
hints?: {
packedMutee?: Packed<'UserDetailedNotMe'>,
},
): Promise<Packed<'Muting'>> {
const muting = typeof src === 'object' ? src : await this.mutingsRepository.findOneByOrFail({ id: src });
@@ -38,18 +41,21 @@ export class MutingEntityService {
createdAt: this.idService.parse(muting.id).date.toISOString(),
expiresAt: muting.expiresAt ? muting.expiresAt.toISOString() : null,
muteeId: muting.muteeId,
mutee: this.userEntityService.pack(muting.muteeId, me, {
mutee: hints?.packedMutee ?? this.userEntityService.pack(muting.muteeId, me, {
schema: 'UserDetailedNotMe',
}),
});
}
@bindThis
public packMany(
mutings: any[],
public async packMany(
mutings: MiMuting[],
me: { id: MiUser['id'] },
) {
return Promise.all(mutings.map(x => this.pack(x, me)));
const _mutees = mutings.map(({ mutee, muteeId }) => mutee ?? muteeId);
const _userMap = await this.userEntityService.packMany(_mutees, me, { schema: 'UserDetailedNotMe' })
.then(users => new Map(users.map(u => [u.id, u])));
return Promise.all(mutings.map(muting => this.pack(muting, me, { packedMutee: _userMap.get(muting.muteeId) })));
}
}

View File

@@ -290,6 +290,7 @@ export class NoteEntityService implements OnModuleInit {
_hint_?: {
myReactions: Map<MiNote['id'], string | null>;
packedFiles: Map<MiNote['fileIds'][number], Packed<'DriveFile'> | null>;
packedUsers: Map<MiUser['id'], Packed<'UserLite'>>
};
},
): Promise<Packed<'Note'>> {
@@ -319,12 +320,13 @@ export class NoteEntityService implements OnModuleInit {
.filter(x => x.startsWith(':') && x.includes('@') && !x.includes('@.')) // リモートカスタム絵文字のみ
.map(x => this.reactionService.decodeReaction(x).reaction.replaceAll(':', ''));
const packedFiles = options?._hint_?.packedFiles;
const packedUsers = options?._hint_?.packedUsers;
const packed: Packed<'Note'> = await awaitAll({
id: note.id,
createdAt: this.idService.parse(note.id).date.toISOString(),
userId: note.userId,
user: this.userEntityService.pack(note.user ?? note.userId, me),
user: packedUsers?.get(note.userId) ?? this.userEntityService.pack(note.user ?? note.userId, me),
text: text,
cw: note.cw,
visibility: note.visibility,
@@ -449,12 +451,20 @@ export class NoteEntityService implements OnModuleInit {
// TODO: 本当は renote とか reply がないのに renoteId とか replyId があったらここで解決しておく
const fileIds = notes.map(n => [n.fileIds, n.renote?.fileIds, n.reply?.fileIds]).flat(2).filter(isNotNull);
const packedFiles = fileIds.length > 0 ? await this.driveFileEntityService.packManyByIdsMap(fileIds) : new Map();
const users = [
...notes.map(({ user, userId }) => user ?? userId),
...notes.map(({ replyUserId }) => replyUserId).filter(isNotNull),
...notes.map(({ renoteUserId }) => renoteUserId).filter(isNotNull),
];
const packedUsers = await this.userEntityService.packMany(users, me)
.then(users => new Map(users.map(u => [u.id, u])));
return await Promise.all(notes.map(n => this.pack(n, me, {
...options,
_hint_: {
myReactions: myReactionsMap,
packedFiles,
packedUsers,
},
})));
}

View File

@@ -52,6 +52,9 @@ export class NoteReactionEntityService implements OnModuleInit {
options?: {
withNote: boolean;
},
hints?: {
packedUser?: Packed<'UserLite'>
},
): Promise<Packed<'NoteReaction'>> {
const opts = Object.assign({
withNote: false,
@@ -62,7 +65,7 @@ export class NoteReactionEntityService implements OnModuleInit {
return {
id: reaction.id,
createdAt: this.idService.parse(reaction.id).date.toISOString(),
user: await this.userEntityService.pack(reaction.user ?? reaction.userId, me),
user: hints?.packedUser ?? await this.userEntityService.pack(reaction.user ?? reaction.userId, me),
type: this.reactionService.convertLegacyReaction(reaction.reaction),
...(opts.withNote ? {
note: await this.noteEntityService.pack(reaction.note ?? reaction.noteId, me),
@@ -81,7 +84,9 @@ export class NoteReactionEntityService implements OnModuleInit {
const opts = Object.assign({
withNote: false,
}, options);
return Promise.all(reactions.map(reaction => this.pack(reaction, me, opts)));
const _users = reactions.map(({ user, userId }) => user ?? userId);
const _userMap = await this.userEntityService.packMany(_users, me)
.then(users => new Map(users.map(u => [u.id, u])));
return Promise.all(reactions.map(reaction => this.pack(reaction, me, opts, { packedUser: _userMap.get(reaction.userId) })));
}
}

View File

@@ -40,6 +40,9 @@ export class PageEntityService {
public async pack(
src: MiPage['id'] | MiPage,
me?: { id: MiUser['id'] } | null | undefined,
hint?: {
packedUser?: Packed<'UserLite'>
},
): Promise<Packed<'Page'>> {
const meId = me ? me.id : null;
const page = typeof src === 'object' ? src : await this.pagesRepository.findOneByOrFail({ id: src });
@@ -91,7 +94,7 @@ export class PageEntityService {
createdAt: this.idService.parse(page.id).date.toISOString(),
updatedAt: page.updatedAt.toISOString(),
userId: page.userId,
user: this.userEntityService.pack(page.user ?? page.userId, me), // { schema: 'UserDetailed' } すると無限ループするので注意
user: hint?.packedUser ?? this.userEntityService.pack(page.user ?? page.userId, me), // { schema: 'UserDetailed' } すると無限ループするので注意
content: page.content,
variables: page.variables,
title: page.title,
@@ -110,11 +113,14 @@ export class PageEntityService {
}
@bindThis
public packMany(
public async packMany(
pages: MiPage[],
me?: { id: MiUser['id'] } | null | undefined,
) {
return Promise.all(pages.map(x => this.pack(x, me)));
const _users = pages.map(({ user, userId }) => user ?? userId);
const _userMap = await this.userEntityService.packMany(_users, me)
.then(users => new Map(users.map(u => [u.id, u])));
return Promise.all(pages.map(page => this.pack(page, me, { packedUser: _userMap.get(page.userId) })));
}
}

View File

@@ -30,6 +30,9 @@ export class RenoteMutingEntityService {
public async pack(
src: MiRenoteMuting['id'] | MiRenoteMuting,
me?: { id: MiUser['id'] } | null | undefined,
hints?: {
packedMutee?: Packed<'UserDetailedNotMe'>
},
): Promise<Packed<'RenoteMuting'>> {
const muting = typeof src === 'object' ? src : await this.renoteMutingsRepository.findOneByOrFail({ id: src });
@@ -37,18 +40,21 @@ export class RenoteMutingEntityService {
id: muting.id,
createdAt: this.idService.parse(muting.id).date.toISOString(),
muteeId: muting.muteeId,
mutee: this.userEntityService.pack(muting.muteeId, me, {
mutee: hints?.packedMutee ?? this.userEntityService.pack(muting.muteeId, me, {
schema: 'UserDetailedNotMe',
}),
});
}
@bindThis
public packMany(
mutings: any[],
public async packMany(
mutings: MiRenoteMuting[],
me: { id: MiUser['id'] },
) {
return Promise.all(mutings.map(x => this.pack(x, me)));
const _users = mutings.map(({ mutee, muteeId }) => mutee ?? muteeId);
const _userMap = await this.userEntityService.packMany(_users, me, { schema: 'UserDetailedNotMe' })
.then(users => new Map(users.map(u => [u.id, u])));
return Promise.all(mutings.map(muting => this.pack(muting, me, { packedMutee: _userMap.get(muting.muteeId) })));
}
}

View File

@@ -28,13 +28,15 @@ export class ReversiGameEntityService {
@bindThis
public async packDetail(
src: MiReversiGame['id'] | MiReversiGame,
hint?: {
packedUser1?: Packed<'UserLite'>,
packedUser2?: Packed<'UserLite'>,
},
): Promise<Packed<'ReversiGameDetailed'>> {
const game = typeof src === 'object' ? src : await this.reversiGamesRepository.findOneByOrFail({ id: src });
const users = await Promise.all([
this.userEntityService.pack(game.user1 ?? game.user1Id),
this.userEntityService.pack(game.user2 ?? game.user2Id),
]);
const user1 = hint?.packedUser1 ?? await this.userEntityService.pack(game.user1 ?? game.user1Id);
const user2 = hint?.packedUser2 ?? await this.userEntityService.pack(game.user2 ?? game.user2Id);
return await awaitAll({
id: game.id,
@@ -49,10 +51,10 @@ export class ReversiGameEntityService {
user2Ready: game.user2Ready,
user1Id: game.user1Id,
user2Id: game.user2Id,
user1: users[0],
user2: users[1],
user1,
user2,
winnerId: game.winnerId,
winner: game.winnerId ? users.find(u => u.id === game.winnerId)! : null,
winner: game.winnerId ? [user1, user2].find(u => u.id === game.winnerId)! : null,
surrenderedUserId: game.surrenderedUserId,
timeoutUserId: game.timeoutUserId,
black: game.black,
@@ -68,22 +70,35 @@ export class ReversiGameEntityService {
}
@bindThis
public packDetailMany(
xs: MiReversiGame[],
public async packDetailMany(
games: MiReversiGame[],
) {
return Promise.all(xs.map(x => this.packDetail(x)));
const _user1s = games.map(({ user1, user1Id }) => user1 ?? user1Id);
const _user2s = games.map(({ user2, user2Id }) => user2 ?? user2Id);
const _userMap = await this.userEntityService.packMany([..._user1s, ..._user2s])
.then(users => new Map(users.map(u => [u.id, u])));
return Promise.all(
games.map(game => {
return this.packDetail(game, {
packedUser1: _userMap.get(game.user1Id),
packedUser2: _userMap.get(game.user2Id),
});
}),
);
}
@bindThis
public async packLite(
src: MiReversiGame['id'] | MiReversiGame,
hint?: {
packedUser1?: Packed<'UserLite'>,
packedUser2?: Packed<'UserLite'>,
},
): Promise<Packed<'ReversiGameLite'>> {
const game = typeof src === 'object' ? src : await this.reversiGamesRepository.findOneByOrFail({ id: src });
const users = await Promise.all([
this.userEntityService.pack(game.user1 ?? game.user1Id),
this.userEntityService.pack(game.user2 ?? game.user2Id),
]);
const user1 = hint?.packedUser1 ?? await this.userEntityService.pack(game.user1 ?? game.user1Id);
const user2 = hint?.packedUser2 ?? await this.userEntityService.pack(game.user2 ?? game.user2Id);
return await awaitAll({
id: game.id,
@@ -94,10 +109,10 @@ export class ReversiGameEntityService {
isEnded: game.isEnded,
user1Id: game.user1Id,
user2Id: game.user2Id,
user1: users[0],
user2: users[1],
user1,
user2,
winnerId: game.winnerId,
winner: game.winnerId ? users.find(u => u.id === game.winnerId)! : null,
winner: game.winnerId ? [user1, user2].find(u => u.id === game.winnerId)! : null,
surrenderedUserId: game.surrenderedUserId,
timeoutUserId: game.timeoutUserId,
black: game.black,
@@ -111,10 +126,21 @@ export class ReversiGameEntityService {
}
@bindThis
public packLiteMany(
xs: MiReversiGame[],
public async packLiteMany(
games: MiReversiGame[],
) {
return Promise.all(xs.map(x => this.packLite(x)));
const _user1s = games.map(({ user1, user1Id }) => user1 ?? user1Id);
const _user2s = games.map(({ user2, user2Id }) => user2 ?? user2Id);
const _userMap = await this.userEntityService.packMany([..._user1s, ..._user2s])
.then(users => new Map(users.map(u => [u.id, u])));
return Promise.all(
games.map(game => {
return this.packLite(game, {
packedUser1: _userMap.get(game.user1Id),
packedUser2: _userMap.get(game.user2Id),
});
}),
);
}
}

View File

@@ -50,11 +50,14 @@ export class UserListEntityService {
public async packMembershipsMany(
memberships: MiUserListMembership[],
) {
const _users = memberships.map(({ user, userId }) => user ?? userId);
const _userMap = await this.userEntityService.packMany(_users)
.then(users => new Map(users.map(u => [u.id, u])));
return Promise.all(memberships.map(async x => ({
id: x.id,
createdAt: this.idService.parse(x.id).date.toISOString(),
userId: x.userId,
user: await this.userEntityService.pack(x.userId),
user: _userMap.get(x.userId) ?? await this.userEntityService.pack(x.userId),
withReplies: x.withReplies,
})));
}

View File

@@ -376,6 +376,12 @@ export class MiMeta {
})
public privacyPolicyUrl: string | null;
@Column('varchar', {
length: 1024,
nullable: true,
})
public inquiryUrl: string | null;
@Column('varchar', {
length: 8192,
nullable: true,

View File

@@ -95,5 +95,10 @@ export const packedAntennaSchema = {
optional: false, nullable: false,
default: false,
},
notify: {
type: 'boolean',
optional: false, nullable: false,
default: false,
},
},
} as const;

View File

@@ -227,6 +227,10 @@ export const packedMetaLiteSchema = {
type: 'string',
optional: false, nullable: true,
},
inquiryUrl: {
type: 'string',
optional: false, nullable: true,
},
serverRules: {
type: 'array',
optional: false, nullable: false,

View File

@@ -204,13 +204,22 @@ export class InboxProcessorService {
// アクティビティを処理
try {
await this.apInboxService.performActivity(authUser.user, activity);
const result = await this.apInboxService.performActivity(authUser.user, activity);
if (result && !result.startsWith('ok')) {
this.logger.warn(`inbox activity ignored (maybe): id=${activity.id} reason=${result}`);
return result;
}
} catch (e) {
if (e instanceof IdentifiableError) {
if (e.id === '689ee33f-f97c-479a-ac49-1b9f8140af99') {
return 'blocked notes with prohibited words';
}
if (e.id === '85ab9bd7-3a41-4530-959d-f07073900109') return 'actor has been suspended';
if (e.id === '85ab9bd7-3a41-4530-959d-f07073900109') {
return 'actor has been suspended';
}
if (e.id === 'd450b8a9-48e4-4dab-ae36-f4db763fda7c') { // invalid Note
return e.message;
}
}
throw e;
}

View File

@@ -37,12 +37,12 @@ export class NodeinfoServerService {
@bindThis
public getLinks() {
return [{
rel: 'http://nodeinfo.diaspora.software/ns/schema/2.1',
href: this.config.url + nodeinfo2_1path
}, {
rel: 'http://nodeinfo.diaspora.software/ns/schema/2.0',
href: this.config.url + nodeinfo2_0path,
}];
rel: 'http://nodeinfo.diaspora.software/ns/schema/2.1',
href: this.config.url + nodeinfo2_1path,
}, {
rel: 'http://nodeinfo.diaspora.software/ns/schema/2.0',
href: this.config.url + nodeinfo2_0path,
}];
}
@bindThis
@@ -108,6 +108,7 @@ export class NodeinfoServerService {
langs: meta.langs,
tosUrl: meta.termsOfServiceUrl,
privacyPolicyUrl: meta.privacyPolicyUrl,
inquiryUrl: meta.inquiryUrl,
impressumUrl: meta.impressumUrl,
repositoryUrl: meta.repositoryUrl,
feedbackUrl: meta.feedbackUrl,

View File

@@ -7,6 +7,7 @@ import { randomUUID } from 'node:crypto';
import * as fs from 'node:fs';
import * as stream from 'node:stream/promises';
import { Inject, Injectable } from '@nestjs/common';
import * as Sentry from '@sentry/node';
import { DI } from '@/di-symbols.js';
import { getIpHash } from '@/misc/get-ip-hash.js';
import type { MiLocalUser, MiUser } from '@/models/User.js';
@@ -17,6 +18,7 @@ import { MetaService } from '@/core/MetaService.js';
import { createTemp } from '@/misc/create-temp.js';
import { bindThis } from '@/decorators.js';
import { RoleService } from '@/core/RoleService.js';
import type { Config } from '@/config.js';
import { ApiError } from './error.js';
import { RateLimiterService } from './RateLimiterService.js';
import { ApiLoggerService } from './ApiLoggerService.js';
@@ -38,6 +40,9 @@ export class ApiCallService implements OnApplicationShutdown {
private userIpHistoriesClearIntervalId: NodeJS.Timeout;
constructor(
@Inject(DI.config)
private config: Config,
@Inject(DI.userIpsRepository)
private userIpsRepository: UserIpsRepository,
@@ -88,6 +93,48 @@ export class ApiCallService implements OnApplicationShutdown {
}
}
#onExecError(ep: IEndpoint, data: any, err: Error): void {
if (err instanceof ApiError || err instanceof AuthenticationError) {
throw err;
} else {
const errId = randomUUID();
this.logger.error(`Internal error occurred in ${ep.name}: ${err.message}`, {
ep: ep.name,
ps: data,
e: {
message: err.message,
code: err.name,
stack: err.stack,
id: errId,
},
});
console.error(err, errId);
if (this.config.sentryForBackend) {
Sentry.captureMessage(`Internal error occurred in ${ep.name}: ${err.message}`, {
extra: {
ep: ep.name,
ps: data,
e: {
message: err.message,
code: err.name,
stack: err.stack,
id: errId,
},
},
});
}
throw new ApiError(null, {
e: {
message: err.message,
code: err.name,
id: errId,
},
});
}
}
@bindThis
public handleRequest(
endpoint: IEndpoint & { exec: any },
@@ -362,31 +409,11 @@ export class ApiCallService implements OnApplicationShutdown {
}
// API invoking
return await ep.exec(data, user, token, file, request.ip, request.headers).catch((err: Error) => {
if (err instanceof ApiError || err instanceof AuthenticationError) {
throw err;
} else {
const errId = randomUUID();
this.logger.error(`Internal error occurred in ${ep.name}: ${err.message}`, {
ep: ep.name,
ps: data,
e: {
message: err.message,
code: err.name,
stack: err.stack,
id: errId,
},
});
console.error(err, errId);
throw new ApiError(null, {
e: {
message: err.message,
code: err.name,
id: errId,
},
});
}
});
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)));
} else {
return await ep.exec(data, user, token, file, request.ip, request.headers).catch((err: Error) => this.#onExecError(ep, data, err));
}
}
@bindThis

View File

@@ -427,6 +427,10 @@ export const meta = {
type: 'string',
optional: false, nullable: true,
},
inquiryUrl: {
type: 'string',
optional: false, nullable: true,
},
repositoryUrl: {
type: 'string',
optional: false, nullable: true,
@@ -513,6 +517,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
feedbackUrl: instance.feedbackUrl,
impressumUrl: instance.impressumUrl,
privacyPolicyUrl: instance.privacyPolicyUrl,
inquiryUrl: instance.inquiryUrl,
disableRegistration: instance.disableRegistration,
emailRequiredForSignup: instance.emailRequiredForSignup,
enableHcaptcha: instance.enableHcaptcha,

View File

@@ -89,10 +89,13 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
.limit(ps.limit)
.getMany();
const _users = assigns.map(({ user, userId }) => user ?? userId);
const _userMap = await this.userEntityService.packMany(_users, me, { schema: 'UserDetailed' })
.then(users => new Map(users.map(u => [u.id, u])));
return await Promise.all(assigns.map(async assign => ({
id: assign.id,
createdAt: this.idService.parse(assign.id).date.toISOString(),
user: await this.userEntityService.pack(assign.user!, me, { schema: 'UserDetailed' }),
user: _userMap.get(assign.userId) ?? await this.userEntityService.pack(assign.user!, me, { schema: 'UserDetailed' }),
expiresAt: assign.expiresAt?.toISOString() ?? null,
})));
});

View File

@@ -107,6 +107,7 @@ export const paramDef = {
feedbackUrl: { type: 'string', nullable: true },
impressumUrl: { type: 'string', nullable: true },
privacyPolicyUrl: { type: 'string', nullable: true },
inquiryUrl: { type: 'string', nullable: true },
useObjectStorage: { type: 'boolean' },
objectStorageBaseUrl: { type: 'string', nullable: true },
objectStorageBucket: { type: 'string', nullable: true },
@@ -422,6 +423,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
set.privacyPolicyUrl = ps.privacyPolicyUrl;
}
if (ps.inquiryUrl !== undefined) {
set.inquiryUrl = ps.inquiryUrl;
}
if (ps.useObjectStorage !== undefined) {
set.useObjectStorage = ps.useObjectStorage;
}

View File

@@ -54,7 +54,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
folderId: ps.folderId ?? IsNull(),
});
return await Promise.all(files.map(file => this.driveFileEntityService.pack(file, { self: true })));
return await this.driveFileEntityService.packMany(files, { self: true });
});
}
}

View File

@@ -71,7 +71,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
.limit(ps.limit)
.getMany();
return await Promise.all(requests.map(req => this.followRequestEntityService.pack(req)));
return await this.followRequestEntityService.packMany(requests, me);
});
}
}

View File

@@ -76,7 +76,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
const reactions = await query.limit(ps.limit).getMany();
return await Promise.all(reactions.map(reaction => this.noteReactionEntityService.pack(reaction, me)));
return await this.noteReactionEntityService.packMany(reactions, me);
});
}
}

View File

@@ -92,9 +92,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
.limit(ps.limit)
.getMany();
const _users = assigns.map(({ user, userId }) => user ?? userId);
const _userMap = await this.userEntityService.packMany(_users, me, { schema: 'UserDetailed' })
.then(users => new Map(users.map(u => [u.id, u])));
return await Promise.all(assigns.map(async assign => ({
id: assign.id,
user: await this.userEntityService.pack(assign.user!, me, { schema: 'UserDetailed' }),
user: _userMap.get(assign.userId) ?? await this.userEntityService.pack(assign.user!, me, { schema: 'UserDetailed' }),
})));
});
}

View File

@@ -118,12 +118,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
const repliedUsersSorted = Object.keys(repliedUsers).sort((a, b) => repliedUsers[b] - repliedUsers[a]);
// Extract top replied users
const topRepliedUsers = repliedUsersSorted.slice(0, ps.limit);
const topRepliedUserIds = repliedUsersSorted.slice(0, ps.limit);
// Make replies object (includes weights)
const repliesObj = await Promise.all(topRepliedUsers.map(async (user) => ({
user: await this.userEntityService.pack(user, me, { schema: 'UserDetailed' }),
weight: repliedUsers[user] / peak,
const _userMap = await this.userEntityService.packMany(topRepliedUserIds, me, { schema: 'UserDetailed' })
.then(users => new Map(users.map(u => [u.id, u])));
const repliesObj = await Promise.all(topRepliedUserIds.map(async (userId) => ({
user: _userMap.get(userId) ?? await this.userEntityService.pack(userId, me, { schema: 'UserDetailed' }),
weight: repliedUsers[userId] / peak,
})));
return repliesObj;

View File

@@ -117,9 +117,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
if (user != null) _users.push(user);
}
return await Promise.all(_users.map(u => this.userEntityService.pack(u, me, {
schema: 'UserDetailed',
})));
const _userMap = await this.userEntityService.packMany(_users, me, { schema: 'UserDetailed' })
.then(users => new Map(users.map(u => [u.id, u])));
return _users.map(u => _userMap.get(u.id)!);
} else {
// Lookup user
if (typeof ps.host === 'string' && typeof ps.username === 'string') {

View File

@@ -157,6 +157,7 @@ describe('アンテナ', () => {
withReplies: false,
excludeBots: false,
localOnly: false,
notify: false,
};
assert.deepStrictEqual(response, expected);
});

View File

@@ -4,77 +4,81 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<MkModalWindow ref="dialogEl" @close="cancel()" @closed="$emit('closed')">
<template #header>:{{ emoji.name }}:</template>
<template #default>
<MkSpacer>
<div style="display: flex; flex-direction: column; gap: 1em;">
<div :class="$style.emojiImgWrapper">
<MkCustomEmoji :name="emoji.name" :normal="true" :useOriginalSize="true" style="height: 100%;"></MkCustomEmoji>
</div>
<MkKeyValue :copy="`:${emoji.name}:`">
<template #key>{{ i18n.ts.name }}</template>
<template #value>{{ emoji.name }}</template>
</MkKeyValue>
<MkKeyValue>
<template #key>{{ i18n.ts.tags }}</template>
<template #value>
<div v-if="emoji.aliases.length === 0">{{ i18n.ts.none }}</div>
<div v-else :class="$style.aliases">
<span v-for="alias in emoji.aliases" :key="alias" :class="$style.alias">
{{ alias }}
</span>
</div>
</template>
</MkKeyValue>
<MkKeyValue>
<template #key>{{ i18n.ts.category }}</template>
<template #value>{{ emoji.category ?? i18n.ts.none }}</template>
</MkKeyValue>
<MkKeyValue>
<template #key>{{ i18n.ts.sensitive }}</template>
<template #value>{{ emoji.isSensitive ? i18n.ts.yes : i18n.ts.no }}</template>
</MkKeyValue>
<MkKeyValue>
<template #key>{{ i18n.ts.localOnly }}</template>
<template #value>{{ emoji.localOnly ? i18n.ts.yes : i18n.ts.no }}</template>
</MkKeyValue>
<MkKeyValue>
<template #key>{{ i18n.ts.license }}</template>
<template #value><Mfm :text="emoji.license ?? i18n.ts.none" /></template>
</MkKeyValue>
<MkKeyValue :copy="emoji.url">
<template #key>{{ i18n.ts.emojiUrl }}</template>
<template #value>
<MkLink :url="emoji.url" target="_blank">{{ emoji.url }}</MkLink>
</template>
</MkKeyValue>
</div>
</MkSpacer>
</template>
</MkModalWindow>
<MkModalWindow ref="dialogEl" @close="cancel()" @closed="$emit('closed')">
<template #header>:{{ emoji.name }}:</template>
<template #default>
<MkSpacer>
<div style="display: flex; flex-direction: column; gap: 1em;">
<div :class="$style.emojiImgWrapper">
<MkCustomEmoji :name="emoji.name" :normal="true" :useOriginalSize="true" style="height: 100%;"></MkCustomEmoji>
</div>
<MkKeyValue :copy="`:${emoji.name}:`">
<template #key>{{ i18n.ts.name }}</template>
<template #value>{{ emoji.name }}</template>
</MkKeyValue>
<MkKeyValue>
<template #key>{{ i18n.ts.tags }}</template>
<template #value>
<div v-if="emoji.aliases.length === 0">{{ i18n.ts.none }}</div>
<div v-else :class="$style.aliases">
<span v-for="alias in emoji.aliases" :key="alias" :class="$style.alias">
{{ alias }}
</span>
</div>
</template>
</MkKeyValue>
<MkKeyValue>
<template #key>{{ i18n.ts.category }}</template>
<template #value>{{ emoji.category ?? i18n.ts.none }}</template>
</MkKeyValue>
<MkKeyValue>
<template #key>{{ i18n.ts.sensitive }}</template>
<template #value>{{ emoji.isSensitive ? i18n.ts.yes : i18n.ts.no }}</template>
</MkKeyValue>
<MkKeyValue>
<template #key>{{ i18n.ts.localOnly }}</template>
<template #value>{{ emoji.localOnly ? i18n.ts.yes : i18n.ts.no }}</template>
</MkKeyValue>
<MkKeyValue>
<template #key>{{ i18n.ts.license }}</template>
<template #value><Mfm :text="emoji.license ?? i18n.ts.none"/></template>
</MkKeyValue>
<MkKeyValue :copy="emoji.url">
<template #key>{{ i18n.ts.emojiUrl }}</template>
<template #value>
<MkLink :url="emoji.url" target="_blank">{{ emoji.url }}</MkLink>
</template>
</MkKeyValue>
</div>
</MkSpacer>
</template>
</MkModalWindow>
</template>
<script lang="ts" setup>
import * as Misskey from 'misskey-js';
import { defineProps, shallowRef } from 'vue';
import MkLink from '@/components/MkLink.vue';
import { i18n } from '@/i18n.js';
import MkModalWindow from '@/components/MkModalWindow.vue';
import MkKeyValue from '@/components/MkKeyValue.vue';
import MkLink from './MkLink.vue';
const props = defineProps<{
emoji: Misskey.entities.EmojiDetailed,
}>();
const emit = defineEmits<{
(ev: 'ok', cropped: Misskey.entities.DriveFile): void;
(ev: 'cancel'): void;
(ev: 'closed'): void;
}>();
const dialogEl = shallowRef<InstanceType<typeof MkModalWindow>>();
const cancel = () => {
function cancel() {
emit('cancel');
dialogEl.value!.close();
};
}
</script>
<style lang="scss" module>

View File

@@ -12,10 +12,13 @@ SPDX-License-Identifier: AGPL-3.0-only
<img :src="instance.iconUrl || '/favicon.ico'" alt="" class="icon"/>
</div>
<MkInfo v-if="thereIsUnresolvedAbuseReport" warn class="info">{{ i18n.ts.thereIsUnresolvedAbuseReportWarning }} <MkA to="/admin/abuses" class="_link">{{ i18n.ts.check }}</MkA></MkInfo>
<MkInfo v-if="noMaintainerInformation" warn class="info">{{ i18n.ts.noMaintainerInformationWarning }} <MkA to="/admin/settings" class="_link">{{ i18n.ts.configure }}</MkA></MkInfo>
<MkInfo v-if="noBotProtection" warn class="info">{{ i18n.ts.noBotProtectionWarning }} <MkA to="/admin/security" class="_link">{{ i18n.ts.configure }}</MkA></MkInfo>
<MkInfo v-if="noEmailServer" warn class="info">{{ i18n.ts.noEmailServerWarning }} <MkA to="/admin/email-settings" class="_link">{{ i18n.ts.configure }}</MkA></MkInfo>
<div class="_gaps_s">
<MkInfo v-if="thereIsUnresolvedAbuseReport" warn>{{ i18n.ts.thereIsUnresolvedAbuseReportWarning }} <MkA to="/admin/abuses" class="_link">{{ i18n.ts.check }}</MkA></MkInfo>
<MkInfo v-if="noMaintainerInformation" warn>{{ i18n.ts.noMaintainerInformationWarning }} <MkA to="/admin/settings" class="_link">{{ i18n.ts.configure }}</MkA></MkInfo>
<MkInfo v-if="noInquiryUrl" warn>{{ i18n.ts.noInquiryUrlWarning }} <MkA to="/admin/moderation" class="_link">{{ i18n.ts.configure }}</MkA></MkInfo>
<MkInfo v-if="noBotProtection" warn>{{ i18n.ts.noBotProtectionWarning }} <MkA to="/admin/security" class="_link">{{ i18n.ts.configure }}</MkA></MkInfo>
<MkInfo v-if="noEmailServer" warn>{{ i18n.ts.noEmailServerWarning }} <MkA to="/admin/email-settings" class="_link">{{ i18n.ts.configure }}</MkA></MkInfo>
</div>
<MkSuperMenu :def="menuDef" :grid="narrow"></MkSuperMenu>
</div>
@@ -61,6 +64,7 @@ const pageProps = ref({});
let noMaintainerInformation = isEmpty(instance.maintainerName) || isEmpty(instance.maintainerEmail);
let noBotProtection = !instance.disableRegistration && !instance.enableHcaptcha && !instance.enableRecaptcha && !instance.enableTurnstile;
let noEmailServer = !instance.enableEmail;
let noInquiryUrl = isEmpty(instance.inquiryUrl);
const thereIsUnresolvedAbuseReport = ref(false);
const currentPage = computed(() => router.currentRef.value.child);
@@ -348,10 +352,6 @@ defineExpose({
> .nav {
.lxpfedzu {
> .info {
margin: 16px 0;
}
> .banner {
margin: 16px;

View File

@@ -30,6 +30,12 @@ SPDX-License-Identifier: AGPL-3.0-only
<template #label>{{ i18n.ts.privacyPolicyUrl }}</template>
</MkInput>
<MkInput v-model="inquiryUrl" type="url">
<template #prefix><i class="ti ti-link"></i></template>
<template #label>{{ i18n.ts._serverSettings.inquiryUrl }}</template>
<template #caption>{{ i18n.ts._serverSettings.inquiryUrlDescription }}</template>
</MkInput>
<MkTextarea v-model="preservedUsernames">
<template #label>{{ i18n.ts.preservedUsernames }}</template>
<template #caption>{{ i18n.ts.preservedUsernamesDescription }}</template>
@@ -86,6 +92,7 @@ const hiddenTags = ref<string>('');
const preservedUsernames = ref<string>('');
const tosUrl = ref<string | null>(null);
const privacyPolicyUrl = ref<string | null>(null);
const inquiryUrl = ref<string | null>(null);
async function init() {
const meta = await misskeyApi('admin/meta');
@@ -97,6 +104,7 @@ async function init() {
preservedUsernames.value = meta.preservedUsernames.join('\n');
tosUrl.value = meta.tosUrl;
privacyPolicyUrl.value = meta.privacyPolicyUrl;
inquiryUrl.value = meta.inquiryUrl;
}
function save() {
@@ -105,6 +113,7 @@ function save() {
emailRequiredForSignup: emailRequiredForSignup.value,
tosUrl: tosUrl.value,
privacyPolicyUrl: privacyPolicyUrl.value,
inquiryUrl: inquiryUrl.value,
sensitiveWords: sensitiveWords.value.split('\n'),
prohibitedWords: prohibitedWords.value.split('\n'),
hiddenTags: hiddenTags.value.split('\n'),

View File

@@ -7,7 +7,21 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkStickyContainer>
<template #header><MkPageHeader/></template>
<MkSpacer :contentMax="600" :marginMin="20">
<div>{{ instance.maintainerEmail }}</div>
<div class="_gaps">
<MkKeyValue>
<template #key>{{ i18n.ts.inquiry }}</template>
<template #value>
<MkLink :url="instance.inquiryUrl" target="_blank">{{ instance.inquiryUrl }}</MkLink>
</template>
</MkKeyValue>
<MkKeyValue>
<template #key>{{ i18n.ts.email }}</template>
<template #value>
<div>{{ instance.maintainerEmail }}</div>
</template>
</MkKeyValue>
</div>
</MkSpacer>
</MkStickyContainer>
</template>
@@ -16,6 +30,8 @@ SPDX-License-Identifier: AGPL-3.0-only
import { i18n } from '@/i18n.js';
import { definePageMetadata } from '@/scripts/page-metadata.js';
import { instance } from '@/instance.js';
import MkKeyValue from '@/components/MkKeyValue.vue';
import MkLink from '@/components/MkLink.vue';
definePageMetadata(() => ({
title: i18n.ts.inquiry,

View File

@@ -6,15 +6,16 @@
import * as Misskey from 'misskey-js';
export function shouldCollapsed(note: Misskey.entities.Note, urls: string[]): boolean {
const collapsed = note.cw == null && note.text != null && (
(note.text.includes('$[x2')) ||
(note.text.includes('$[x3')) ||
(note.text.includes('$[x4')) ||
(note.text.includes('$[scale')) ||
(note.text.split('\n').length > 9) ||
(note.text.length > 500) ||
(note.files.length >= 5) ||
(urls.length >= 4)
const collapsed = note.cw == null && (
note.text != null && (
(note.text.includes('$[x2')) ||
(note.text.includes('$[x3')) ||
(note.text.includes('$[x4')) ||
(note.text.includes('$[scale')) ||
(note.text.split('\n').length > 9) ||
(note.text.length > 500) ||
(urls.length >= 4)
) || note.files.length >= 5
);
return collapsed;

View File

@@ -1,8 +1,9 @@
{
"type": "module",
"name": "misskey-js",
"version": "2024.5.0-beta.5",
"version": "2024.5.0",
"description": "Misskey SDK for JavaScript",
"license": "MIT",
"main": "./built/index.js",
"types": "./built/index.d.ts",
"exports": {
@@ -30,7 +31,8 @@
},
"repository": {
"type": "git",
"url": "git+https://github.com/misskey-dev/misskey.js.git"
"url": "https://github.com/misskey-dev/misskey.git",
"directory": "packages/misskey-js"
},
"devDependencies": {
"@microsoft/api-extractor": "7.43.1",

View File

@@ -4449,6 +4449,8 @@ export type components = {
isActive: boolean;
/** @default false */
hasUnreadNote: boolean;
/** @default false */
notify: boolean;
};
Clip: {
/**
@@ -4831,6 +4833,7 @@ export type components = {
impressumUrl: string | null;
logoImageUrl: string | null;
privacyPolicyUrl: string | null;
inquiryUrl: string | null;
serverRules: string[];
themeColor: string | null;
policies: components['schemas']['RolePolicies'];
@@ -4978,6 +4981,7 @@ export type operations = {
shortName: string | null;
objectStorageS3ForcePathStyle: boolean;
privacyPolicyUrl: string | null;
inquiryUrl: string | null;
repositoryUrl: string | null;
/**
* @deprecated
@@ -8906,6 +8910,7 @@ export type operations = {
feedbackUrl?: string | null;
impressumUrl?: string | null;
privacyPolicyUrl?: string | null;
inquiryUrl?: string | null;
useObjectStorage?: boolean;
objectStorageBaseUrl?: string | null;
objectStorageBucket?: string | null;

630
pnpm-lock.yaml generated
View File

@@ -140,6 +140,12 @@ importers:
'@peertube/http-signature':
specifier: 1.7.0
version: 1.7.0
'@sentry/node':
specifier: ^8.5.0
version: 8.5.0
'@sentry/profiling-node':
specifier: ^8.5.0
version: 8.5.0
'@simplewebauthn/server':
specifier: 10.0.0
version: 10.0.0(encoding@0.1.13)
@@ -3264,6 +3270,154 @@ packages:
'@open-draft/until@2.1.0':
resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==}
'@opentelemetry/api-logs@0.51.1':
resolution: {integrity: sha512-E3skn949Pk1z2XtXu/lxf6QAZpawuTM/IUEXcAzpiUkTd73Hmvw26FiN3cJuTmkpM5hZzHwkomVdtrh/n/zzwA==}
engines: {node: '>=14'}
'@opentelemetry/api@1.8.0':
resolution: {integrity: sha512-I/s6F7yKUDdtMsoBWXJe8Qz40Tui5vsuKCWJEWVL+5q9sSWRzzx6v2KeNsOBEwd94j0eWkpWCH4yB6rZg9Mf0w==}
engines: {node: '>=8.0.0'}
'@opentelemetry/context-async-hooks@1.24.1':
resolution: {integrity: sha512-R5r6DO4kgEOVBxFXhXjwospLQkv+sYxwCfjvoZBe7Zm6KKXAV9kDSJhi/D1BweowdZmO+sdbENLs374gER8hpQ==}
engines: {node: '>=14'}
peerDependencies:
'@opentelemetry/api': '>=1.0.0 <1.9.0'
'@opentelemetry/core@1.24.1':
resolution: {integrity: sha512-wMSGfsdmibI88K9wB498zXY04yThPexo8jvwNNlm542HZB7XrrMRBbAyKJqG8qDRJwIBdBrPMi4V9ZPW/sqrcg==}
engines: {node: '>=14'}
peerDependencies:
'@opentelemetry/api': '>=1.0.0 <1.9.0'
'@opentelemetry/instrumentation-connect@0.36.0':
resolution: {integrity: sha512-k9++bmJZ9zDEs3u3DnKTn2l7QTiNFg3gPx7G9rW0TPnP+xZoBSBTrEcGYBaqflQlrFG23Q58+X1sM2ayWPv5Fg==}
engines: {node: '>=14'}
peerDependencies:
'@opentelemetry/api': ^1.3.0
'@opentelemetry/instrumentation-express@0.39.0':
resolution: {integrity: sha512-AG8U7z7D0JcBu/7dDcwb47UMEzj9/FMiJV2iQZqrsZnxR3FjB9J9oIH2iszJYci2eUdp2WbdvtpD9RV/zmME5A==}
engines: {node: '>=14'}
peerDependencies:
'@opentelemetry/api': ^1.3.0
'@opentelemetry/instrumentation-fastify@0.36.1':
resolution: {integrity: sha512-3Nfm43PI0I+3EX+1YbSy6xbDu276R1Dh1tqAk68yd4yirnIh52Kd5B+nJ8CgHA7o3UKakpBjj6vSzi5vNCzJIA==}
engines: {node: '>=14'}
peerDependencies:
'@opentelemetry/api': ^1.3.0
'@opentelemetry/instrumentation-graphql@0.40.0':
resolution: {integrity: sha512-LVRdEHWACWOczv2imD+mhUrLMxsEjPPi32vIZJT57zygR5aUiA4em8X3aiGOCycgbMWkIu8xOSGSxdx3JmzN+w==}
engines: {node: '>=14'}
peerDependencies:
'@opentelemetry/api': ^1.3.0
'@opentelemetry/instrumentation-hapi@0.38.0':
resolution: {integrity: sha512-ZcOqEuwuutTDYIjhDIStix22ECblG/i9pHje23QGs4Q4YS4RMaZ5hKCoQJxW88Z4K7T53rQkdISmoXFKDV8xMg==}
engines: {node: '>=14'}
peerDependencies:
'@opentelemetry/api': ^1.3.0
'@opentelemetry/instrumentation-http@0.51.1':
resolution: {integrity: sha512-6b3nZnFFEz/3xZ6w8bVxctPUWIPWiXuPQ725530JgxnN1cvYFd8CJ75PrHZNjynmzSSnqBkN3ef4R9N+RpMh8Q==}
engines: {node: '>=14'}
peerDependencies:
'@opentelemetry/api': ^1.3.0
'@opentelemetry/instrumentation-ioredis@0.40.0':
resolution: {integrity: sha512-Jv/fH7KhpWe4KBirsiqeUJIYrsdR2iu2l4nWhfOlRvaZ+zYIiLEzTQR6QhBbyRoAbU4OuYJzjWusOmmpGBnwng==}
engines: {node: '>=14'}
peerDependencies:
'@opentelemetry/api': ^1.3.0
'@opentelemetry/instrumentation-koa@0.40.0':
resolution: {integrity: sha512-dJc3H/bKMcgUYcQpLF+1IbmUKus0e5Fnn/+ru/3voIRHwMADT3rFSUcGLWSczkg68BCgz0vFWGDTvPtcWIFr7A==}
engines: {node: '>=14'}
peerDependencies:
'@opentelemetry/api': ^1.3.0
'@opentelemetry/instrumentation-mongodb@0.43.0':
resolution: {integrity: sha512-bMKej7Y76QVUD3l55Q9YqizXybHUzF3pujsBFjqbZrRn2WYqtsDtTUlbCK7fvXNPwFInqZ2KhnTqd0gwo8MzaQ==}
engines: {node: '>=14'}
peerDependencies:
'@opentelemetry/api': ^1.3.0
'@opentelemetry/instrumentation-mongoose@0.38.1':
resolution: {integrity: sha512-zaeiasdnRjXe6VhYCBMdkmAVh1S5MmXC/0spet+yqoaViGnYst/DOxPvhwg3yT4Yag5crZNWsVXnA538UjP6Ow==}
engines: {node: '>=14'}
peerDependencies:
'@opentelemetry/api': ^1.3.0
'@opentelemetry/instrumentation-mysql2@0.38.1':
resolution: {integrity: sha512-qkpHMgWSDTYVB1vlZ9sspf7l2wdS5DDq/rbIepDwX5BA0N0068JTQqh0CgAh34tdFqSCnWXIhcyOXC2TtRb0sg==}
engines: {node: '>=14'}
peerDependencies:
'@opentelemetry/api': ^1.3.0
'@opentelemetry/instrumentation-mysql@0.38.1':
resolution: {integrity: sha512-+iBAawUaTfX/HAlvySwozx0C2B6LBfNPXX1W8Z2On1Uva33AGkw2UjL9XgIg1Pj4eLZ9R4EoJ/aFz+Xj4E/7Fw==}
engines: {node: '>=14'}
peerDependencies:
'@opentelemetry/api': ^1.3.0
'@opentelemetry/instrumentation-nestjs-core@0.37.1':
resolution: {integrity: sha512-ebYQjHZEmGHWEALwwDGhSQVLBaurFnuLIkZD5igPXrt7ohfF4lc5/4al1LO+vKc0NHk8SJWStuRueT86ISA8Vg==}
engines: {node: '>=14'}
peerDependencies:
'@opentelemetry/api': ^1.3.0
'@opentelemetry/instrumentation-pg@0.41.0':
resolution: {integrity: sha512-BSlhpivzBD77meQNZY9fS4aKgydA8AJBzv2dqvxXFy/Hq64b7HURgw/ztbmwFeYwdF5raZZUifiiNSMLpOJoSA==}
engines: {node: '>=14'}
peerDependencies:
'@opentelemetry/api': ^1.3.0
'@opentelemetry/instrumentation@0.43.0':
resolution: {integrity: sha512-S1uHE+sxaepgp+t8lvIDuRgyjJWisAb733198kwQTUc9ZtYQ2V2gmyCtR1x21ePGVLoMiX/NWY7WA290hwkjJQ==}
engines: {node: '>=14'}
peerDependencies:
'@opentelemetry/api': ^1.3.0
'@opentelemetry/instrumentation@0.51.1':
resolution: {integrity: sha512-JIrvhpgqY6437QIqToyozrUG1h5UhwHkaGK/WAX+fkrpyPtc+RO5FkRtUd9BH0MibabHHvqsnBGKfKVijbmp8w==}
engines: {node: '>=14'}
peerDependencies:
'@opentelemetry/api': ^1.3.0
'@opentelemetry/redis-common@0.36.2':
resolution: {integrity: sha512-faYX1N0gpLhej/6nyp6bgRjzAKXn5GOEMYY7YhciSfCoITAktLUtQ36d24QEWNA1/WA1y6qQunCe0OhHRkVl9g==}
engines: {node: '>=14'}
'@opentelemetry/resources@1.24.1':
resolution: {integrity: sha512-cyv0MwAaPF7O86x5hk3NNgenMObeejZFLJJDVuSeSMIsknlsj3oOZzRv3qSzlwYomXsICfBeFFlxwHQte5mGXQ==}
engines: {node: '>=14'}
peerDependencies:
'@opentelemetry/api': '>=1.0.0 <1.9.0'
'@opentelemetry/sdk-metrics@1.24.1':
resolution: {integrity: sha512-FrAqCbbGao9iKI+Mgh+OsC9+U2YMoXnlDHe06yH7dvavCKzE3S892dGtX54+WhSFVxHR/TMRVJiK/CV93GR0TQ==}
engines: {node: '>=14'}
peerDependencies:
'@opentelemetry/api': '>=1.3.0 <1.9.0'
'@opentelemetry/sdk-trace-base@1.24.1':
resolution: {integrity: sha512-zz+N423IcySgjihl2NfjBf0qw1RWe11XIAWVrTNOSSI6dtSPJiVom2zipFB2AEEtJWpv0Iz6DY6+TjnyTV5pWg==}
engines: {node: '>=14'}
peerDependencies:
'@opentelemetry/api': '>=1.0.0 <1.9.0'
'@opentelemetry/semantic-conventions@1.24.1':
resolution: {integrity: sha512-VkliWlS4/+GHLLW7J/rVBA00uXus1SWvwFvcUDxDwmFxYfg/2VI6ekwdXS28cjI8Qz2ky2BzG8OUHo+WeYIWqw==}
engines: {node: '>=14'}
'@opentelemetry/sql-common@0.40.1':
resolution: {integrity: sha512-nSDlnHSqzC3pXn/wZEZVLuAuJ1MYMXPBwtv2qAbCa3847SaHItdE7SzUq/Jtb0KZmh1zfAbNi3AAMjztTT4Ugg==}
engines: {node: '>=14'}
peerDependencies:
'@opentelemetry/api': ^1.1.0
'@peculiar/asn1-android@2.3.10':
resolution: {integrity: sha512-z9Rx9cFJv7UUablZISe7uksNbFJCq13hO0yEAOoIpAymALTLlvUOSLnGiQS7okPaM5dP42oTLhezH6XDXRXjGw==}
@@ -3287,6 +3441,9 @@ packages:
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
engines: {node: '>=14'}
'@prisma/instrumentation@5.14.0':
resolution: {integrity: sha512-DeybWvIZzu/mUsOYP9MVd6AyBj+MP7xIMrcuIn25MX8FiQX39QBnET5KhszTAip/ToctUuDwSJ46QkIoyo3RFA==}
'@radix-ui/react-compose-refs@1.0.1':
resolution: {integrity: sha512-fDSBgd44FKHa1FRMU59qBMPFcl2PZE+2nmqunj+BWFyYYjnhIDWL2ItDs3rrbJDQOtzt5nIebLCQc4QRfz6LJw==}
peerDependencies:
@@ -3449,6 +3606,37 @@ packages:
'@rushstack/ts-command-line@4.19.2':
resolution: {integrity: sha512-cqmXXmBEBlzo9WtyUrHtF9e6kl0LvBY7aTSVX4jfnBfXWZQWnPq9JTFPlQZ+L/ZwjZ4HrNwQsOVvhe9oOucZkw==}
'@sentry/core@8.5.0':
resolution: {integrity: sha512-SO3ddBzGdha+Oflp+IKwBxj+7ds1q69OAT3VsypTd+WUFQdI9DIhR92Bjf+QQZCIzUNOi79VWOh3aOi3f6hMnw==}
engines: {node: '>=14.18'}
'@sentry/node@8.5.0':
resolution: {integrity: sha512-t9cHAx/wLJYtdVf2XlzKlRJGvwdAp1wjzG0tC4E1Znx74OuUS1cFNo5WrGuOi0/YcWSxiJaxBvtUcsWK86fIgw==}
engines: {node: '>=14.18'}
'@sentry/opentelemetry@8.5.0':
resolution: {integrity: sha512-AbxFUNjuTKQ9ugZrssmGtPxWkBr4USNoP7GjaaGCNwNzvIVYCa+i8dv7BROJiW2lsxNAremULEbh+nbVmhGxDA==}
engines: {node: '>=14.18'}
peerDependencies:
'@opentelemetry/api': ^1.8.0
'@opentelemetry/core': ^1.24.1
'@opentelemetry/instrumentation': ^0.51.1
'@opentelemetry/sdk-trace-base': ^1.23.0
'@opentelemetry/semantic-conventions': ^1.23.0
'@sentry/profiling-node@8.5.0':
resolution: {integrity: sha512-nEXJqVNfZWYi4PakQXBZCJeH59UlnBv+zaYftDNUUXttCmzRXpL1ujNm5mJrJHlWjV7tgIFw02HW3nh2yyKOkw==}
engines: {node: '>=14.18'}
hasBin: true
'@sentry/types@8.5.0':
resolution: {integrity: sha512-eDgkSmKI4+XL0QZm4H3j/n1RgnrbnjXZmjj+LsfccRZQwbPu9bWlc8q7Y7Ty1gOsoUpX+TecNLp2a8CRID4KHA==}
engines: {node: '>=14.18'}
'@sentry/utils@8.5.0':
resolution: {integrity: sha512-fdrCzo8SAYiw9JBhkJPqYqJkDXZ/wICzN7+zcXIuzKNhE1hdoFjeKcPnpUI3bKZCG6e3hT1PTYQXhVw7GIZV9w==}
engines: {node: '>=14.18'}
'@shikijs/core@1.4.0':
resolution: {integrity: sha512-CxpKLntAi64h3j+TwWqVIQObPTED0FyXLHTTh3MKXtqiQNn2JGcMQQ362LftDbc9kYbDtrksNMNoVmVXzKFYUQ==}
@@ -4254,12 +4442,18 @@ packages:
'@types/connect@3.4.35':
resolution: {integrity: sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==}
'@types/connect@3.4.36':
resolution: {integrity: sha512-P63Zd/JUGq+PdrM1lv0Wv5SBYeA2+CORvbrXbngriYY0jzLUWfQMQQxOhjONEz/wlHOAxOdY7CY65rgQdTjq2w==}
'@types/content-disposition@0.5.8':
resolution: {integrity: sha512-QVSSvno3dE0MgO76pJhmv4Qyi/j0Yk9pBp0Y7TJ2Tlj+KCgJWY6qX7nnxCOLkZ3VYRSIk1WTxCvwUSdx6CCLdg==}
'@types/cookie@0.6.0':
resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==}
'@types/cookies@0.9.0':
resolution: {integrity: sha512-40Zk8qR147RABiQ7NQnBzWzDcjKzNrntB5BAmeGCb2p/MIyOE+4BVvc17wumsUqUw00bJYqoXFHYygQnEFh4/Q==}
'@types/cross-spawn@6.0.2':
resolution: {integrity: sha512-KuwNhp3eza+Rhu8IFI5HUXRP0LIhqH5cAjubUvGXXthh4YYBuP2ntwEX+Cz8GJoZUHlKo247wPWOfA9LYEq4cw==}
@@ -4323,9 +4517,15 @@ packages:
'@types/htmlescape@1.1.3':
resolution: {integrity: sha512-tuC81YJXGUe0q8WRtBNW+uyx79rkkzWK651ALIXXYq5/u/IxjX4iHneGF2uUqzsNp+F+9J2mFZOv9jiLTtIq0w==}
'@types/http-assert@1.5.5':
resolution: {integrity: sha512-4+tE/lwdAahgZT1g30Jkdm9PzFRde0xwxBNUyRsCitRvCQB90iuA2uJYdUnhnANRcqGXaWOGY4FEoxeElNAK2g==}
'@types/http-cache-semantics@4.0.4':
resolution: {integrity: sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==}
'@types/http-errors@2.0.4':
resolution: {integrity: sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==}
'@types/http-link-header@1.0.5':
resolution: {integrity: sha512-AxhIKR8UbyoqCTNp9rRepkktHuUOw3DjfOfDCaO9kwI8AYzjhxyrvZq4+mRw/2daD3hYDknrtSeV6SsPwmc71w==}
@@ -4362,9 +4562,21 @@ packages:
'@types/jsrsasign@10.5.14':
resolution: {integrity: sha512-lppSlfK6etu+cuKs40K4rg8As79PH6hzIB+v55zSqImbSH3SE6Fm8MBHCiI91cWlAP3Z4igtJK1VL3fSN09blQ==}
'@types/keygrip@1.0.6':
resolution: {integrity: sha512-lZuNAY9xeJt7Bx4t4dx0rYCDqGPW8RXhQZK1td7d4H6E9zYbLoOtjBvfwdTKpsyxQI/2jv+armjX/RW+ZNpXOQ==}
'@types/keyv@3.1.4':
resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==}
'@types/koa-compose@3.2.8':
resolution: {integrity: sha512-4Olc63RY+MKvxMwVknCUDhRQX1pFQoBZ/lXcRLP69PQkEpze/0cr8LNqJQe5NFb/b19DWi2a5bTi2VAlQzhJuA==}
'@types/koa@2.14.0':
resolution: {integrity: sha512-DTDUyznHGNHAl+wd1n0z1jxNajduyTh8R53xoewuerdBzGo6Ogj6F2299BFtrexJw4NtgjsI5SMPCmV9gZwGXA==}
'@types/koa__router@12.0.3':
resolution: {integrity: sha512-5YUJVv6NwM1z7m6FuYpKfNLTZ932Z6EF6xy2BbtpJSyn13DKNQEkXVffFVSnJHxvwwWh2SAeumpjAYUELqgjyw==}
'@types/lodash@4.14.191':
resolution: {integrity: sha512-BdZ5BCCvho3EIXw6wUCXHe7rS53AIDPLE+JzwgT+OsJk53oBfbSmZZ7CX4VaRoN78N+TJpFi9QPlfIVNmJYWxQ==}
@@ -4401,6 +4613,9 @@ packages:
'@types/mute-stream@0.0.4':
resolution: {integrity: sha512-CPM9nzrCPPJHQNA9keH9CVkVI+WR5kMa+7XEs5jcGQ0VoAGnLv242w8lIVgwAEfmE4oufJRaTc9PNLQl0ioAow==}
'@types/mysql@2.15.22':
resolution: {integrity: sha512-wK1pzsJVVAjYCSZWQoWHziQZbNggXFDUEIGf54g4ZM/ERuP86uGdWeKZWMYlqTPMZfHJJvLPyogXGvCOg87yLQ==}
'@types/node-fetch@2.6.4':
resolution: {integrity: sha512-1ZX9fcN4Rvkvgv4E6PAY5WXUFWFcRWxZa3EW83UjycOB9ljJCedb2CupIP4RZMEwF/M3eTcCihbBRgwtGbg5Rg==}
@@ -4441,9 +4656,15 @@ packages:
'@types/offscreencanvas@2019.7.0':
resolution: {integrity: sha512-PGcyveRIpL1XIqK8eBsmRBt76eFgtzuPiSTyKHZxnGemp2yzGzWpjYKAfK3wIMiU7eH+851yEpiuP8JZerTmWg==}
'@types/pg-pool@2.0.4':
resolution: {integrity: sha512-qZAvkv1K3QbmHHFYSNRYPkRjOWRLBYrL4B9c+wG0GSVGBw0NtJwPcgx/DSddeDJvRGMHCEQ4VMEVfuJ/0gZ3XQ==}
'@types/pg@8.11.5':
resolution: {integrity: sha512-2xMjVviMxneZHDHX5p5S6tsRRs7TpDHeeK7kTTMe/kAC/mRRNjWHjZg0rkiY+e17jXSZV3zJYDxXV8Cy72/Vuw==}
'@types/pg@8.6.1':
resolution: {integrity: sha512-1Kc4oAGzAl7uqUStZCDvaLFqZrW9qWSjXOmBfdgyBP5La7Us6Mg4GBvRlSoaZMhQF/zSj1C8CtKMBkoiT8eL8w==}
'@types/pretty-hrtime@1.0.1':
resolution: {integrity: sha512-VjID5MJb1eGKthz2qUerWT8+R4b9N+CHvGCzg9fn4kWZgaF9AhdYikQio3R7wV8YY1NsQKPaCwKz1Yff+aHNUQ==}
@@ -4507,6 +4728,9 @@ packages:
'@types/serviceworker@0.0.67':
resolution: {integrity: sha512-7TCH7iNsCSNb+aUD9M/36TekrWFSLCjNK8zw/3n5kOtRjbLtDfGYMXTrDnGhSfqXNwpqmt9Vd90w5C/ad1tX6Q==}
'@types/shimmer@1.0.5':
resolution: {integrity: sha512-9Hp0ObzwwO57DpLFF0InUjUm/II8GmKAvzbefxQTihCb7KI6yc9yzf0nLc4mVdby5N4DRCgQM2wCup9KTieeww==}
'@types/simple-oauth2@5.0.7':
resolution: {integrity: sha512-8JbWVJbiTSBQP/7eiyGKyXWAqp3dKQZpaA+pdW16FCi32ujkzRMG8JfjoAzdWt6W8U591ZNdHcPtP2D7ILTKuA==}
@@ -4900,6 +5124,16 @@ packages:
resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==}
engines: {node: '>= 0.6'}
acorn-import-assertions@1.9.0:
resolution: {integrity: sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==}
peerDependencies:
acorn: ^8
acorn-import-attributes@1.9.5:
resolution: {integrity: sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==}
peerDependencies:
acorn: ^8
acorn-jsx@5.3.2:
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
peerDependencies:
@@ -7111,6 +7345,12 @@ packages:
resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==}
engines: {node: '>=6'}
import-in-the-middle@1.4.2:
resolution: {integrity: sha512-9WOz1Yh/cvO/p69sxRmhyQwrIGGSp7EIdcb+fFNVi7CzQGQB8U1/1XrKVSbEd/GNOAeM0peJtmi7+qphe7NvAw==}
import-in-the-middle@1.7.4:
resolution: {integrity: sha512-Lk+qzWmiQuRPPulGQeK5qq0v32k2bHnWrRPFgqyvhw7Kkov5L6MOLOIU3pcWeujc9W4q54Cp3Q2WV16eQkc7Bg==}
import-lazy@4.0.0:
resolution: {integrity: sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==}
engines: {node: '>=8'}
@@ -8275,6 +8515,9 @@ packages:
resolution: {integrity: sha512-qxBgB7Qa2sEQgHFjj0dSigq7fX4k6Saisd5Nelwp2q8mlbAFh5dHV9JTTlF8viYJLSSWgMCZFUom8PJcMNBoJw==}
engines: {node: '>= 8'}
module-details-from-path@1.0.3:
resolution: {integrity: sha512-ySViT69/76t8VhE1xXHK6Ch4NcDd26gx0MzKXLO+F7NOtnqH68d9zF94nT8ZWSxXh8ELOERsnJO/sWt1xZYw5A==}
mri@1.2.0:
resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==}
engines: {node: '>=4'}
@@ -8393,6 +8636,10 @@ packages:
nise@5.1.4:
resolution: {integrity: sha512-8+Ib8rRJ4L0o3kfmyVCL7gzrohyDe0cMFTBa2d364yIrEGMEoetznKJx899YxjybU6bL9SQkYPSBBs1gyYs8Xg==}
node-abi@3.62.0:
resolution: {integrity: sha512-CPMcGa+y33xuL1E0TcNIu4YyaZCxnnvkVaEXrsosR3FxN+fV8xvb7Mzpb7IgKler10qeMkE6+Dp8qJhpzdq35g==}
engines: {node: '>=10'}
node-abort-controller@3.1.1:
resolution: {integrity: sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==}
@@ -8626,6 +8873,10 @@ packages:
resolution: {integrity: sha512-es3mGcDXV6TKPo6n3aohzHm0qxhLyR39MhF6mkD1FwFGjhxnqMqfSIgM0eCpInZvqatve4CxmXcMZw3jnnsaXw==}
hasBin: true
opentelemetry-instrumentation-fetch-node@1.2.0:
resolution: {integrity: sha512-aiSt/4ubOTyb1N5C2ZbGrBvaJOXIZhZvpRPYuUVxQJe27wJZqf/o65iPrqgLcgfeOLaQ8cS2Q+762jrYvniTrA==}
engines: {node: '>18.0.0'}
optionator@0.9.3:
resolution: {integrity: sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==}
engines: {node: '>= 0.8.0'}
@@ -9570,6 +9821,10 @@ packages:
resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
engines: {node: '>=0.10.0'}
require-in-the-middle@7.3.0:
resolution: {integrity: sha512-nQFEv9gRw6SJAwWD2LrL0NmQvAcO7FBwJbwmr2ttPAacfy0xuiOjE5zt+zM4xDyuyvUaxBi/9gb2SoCyNEVJcw==}
engines: {node: '>=8.6.0'}
require-main-filename@2.0.0:
resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==}
@@ -9784,6 +10039,9 @@ packages:
shiki@1.4.0:
resolution: {integrity: sha512-5WIn0OL8PWm7JhnTwRWXniy6eEDY234mRrERVlFa646V2ErQqwIFd2UML7e0Pq9eqSKLoMa3Ke+xbsF+DAuy+Q==}
shimmer@1.2.1:
resolution: {integrity: sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==}
side-channel@1.0.4:
resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==}
@@ -13733,6 +13991,203 @@ snapshots:
'@open-draft/until@2.1.0': {}
'@opentelemetry/api-logs@0.51.1':
dependencies:
'@opentelemetry/api': 1.8.0
'@opentelemetry/api@1.8.0': {}
'@opentelemetry/context-async-hooks@1.24.1(@opentelemetry/api@1.8.0)':
dependencies:
'@opentelemetry/api': 1.8.0
'@opentelemetry/core@1.24.1(@opentelemetry/api@1.8.0)':
dependencies:
'@opentelemetry/api': 1.8.0
'@opentelemetry/semantic-conventions': 1.24.1
'@opentelemetry/instrumentation-connect@0.36.0(@opentelemetry/api@1.8.0)':
dependencies:
'@opentelemetry/api': 1.8.0
'@opentelemetry/core': 1.24.1(@opentelemetry/api@1.8.0)
'@opentelemetry/instrumentation': 0.51.1(@opentelemetry/api@1.8.0)
'@opentelemetry/semantic-conventions': 1.24.1
'@types/connect': 3.4.36
transitivePeerDependencies:
- supports-color
'@opentelemetry/instrumentation-express@0.39.0(@opentelemetry/api@1.8.0)':
dependencies:
'@opentelemetry/api': 1.8.0
'@opentelemetry/core': 1.24.1(@opentelemetry/api@1.8.0)
'@opentelemetry/instrumentation': 0.51.1(@opentelemetry/api@1.8.0)
'@opentelemetry/semantic-conventions': 1.24.1
transitivePeerDependencies:
- supports-color
'@opentelemetry/instrumentation-fastify@0.36.1(@opentelemetry/api@1.8.0)':
dependencies:
'@opentelemetry/api': 1.8.0
'@opentelemetry/core': 1.24.1(@opentelemetry/api@1.8.0)
'@opentelemetry/instrumentation': 0.51.1(@opentelemetry/api@1.8.0)
'@opentelemetry/semantic-conventions': 1.24.1
transitivePeerDependencies:
- supports-color
'@opentelemetry/instrumentation-graphql@0.40.0(@opentelemetry/api@1.8.0)':
dependencies:
'@opentelemetry/api': 1.8.0
'@opentelemetry/instrumentation': 0.51.1(@opentelemetry/api@1.8.0)
transitivePeerDependencies:
- supports-color
'@opentelemetry/instrumentation-hapi@0.38.0(@opentelemetry/api@1.8.0)':
dependencies:
'@opentelemetry/api': 1.8.0
'@opentelemetry/core': 1.24.1(@opentelemetry/api@1.8.0)
'@opentelemetry/instrumentation': 0.51.1(@opentelemetry/api@1.8.0)
'@opentelemetry/semantic-conventions': 1.24.1
transitivePeerDependencies:
- supports-color
'@opentelemetry/instrumentation-http@0.51.1(@opentelemetry/api@1.8.0)':
dependencies:
'@opentelemetry/api': 1.8.0
'@opentelemetry/core': 1.24.1(@opentelemetry/api@1.8.0)
'@opentelemetry/instrumentation': 0.51.1(@opentelemetry/api@1.8.0)
'@opentelemetry/semantic-conventions': 1.24.1
semver: 7.6.0
transitivePeerDependencies:
- supports-color
'@opentelemetry/instrumentation-ioredis@0.40.0(@opentelemetry/api@1.8.0)':
dependencies:
'@opentelemetry/api': 1.8.0
'@opentelemetry/instrumentation': 0.51.1(@opentelemetry/api@1.8.0)
'@opentelemetry/redis-common': 0.36.2
'@opentelemetry/semantic-conventions': 1.24.1
transitivePeerDependencies:
- supports-color
'@opentelemetry/instrumentation-koa@0.40.0(@opentelemetry/api@1.8.0)':
dependencies:
'@opentelemetry/api': 1.8.0
'@opentelemetry/core': 1.24.1(@opentelemetry/api@1.8.0)
'@opentelemetry/instrumentation': 0.51.1(@opentelemetry/api@1.8.0)
'@opentelemetry/semantic-conventions': 1.24.1
'@types/koa': 2.14.0
'@types/koa__router': 12.0.3
transitivePeerDependencies:
- supports-color
'@opentelemetry/instrumentation-mongodb@0.43.0(@opentelemetry/api@1.8.0)':
dependencies:
'@opentelemetry/api': 1.8.0
'@opentelemetry/instrumentation': 0.51.1(@opentelemetry/api@1.8.0)
'@opentelemetry/sdk-metrics': 1.24.1(@opentelemetry/api@1.8.0)
'@opentelemetry/semantic-conventions': 1.24.1
transitivePeerDependencies:
- supports-color
'@opentelemetry/instrumentation-mongoose@0.38.1(@opentelemetry/api@1.8.0)':
dependencies:
'@opentelemetry/api': 1.8.0
'@opentelemetry/core': 1.24.1(@opentelemetry/api@1.8.0)
'@opentelemetry/instrumentation': 0.51.1(@opentelemetry/api@1.8.0)
'@opentelemetry/semantic-conventions': 1.24.1
transitivePeerDependencies:
- supports-color
'@opentelemetry/instrumentation-mysql2@0.38.1(@opentelemetry/api@1.8.0)':
dependencies:
'@opentelemetry/api': 1.8.0
'@opentelemetry/instrumentation': 0.51.1(@opentelemetry/api@1.8.0)
'@opentelemetry/semantic-conventions': 1.24.1
'@opentelemetry/sql-common': 0.40.1(@opentelemetry/api@1.8.0)
transitivePeerDependencies:
- supports-color
'@opentelemetry/instrumentation-mysql@0.38.1(@opentelemetry/api@1.8.0)':
dependencies:
'@opentelemetry/api': 1.8.0
'@opentelemetry/instrumentation': 0.51.1(@opentelemetry/api@1.8.0)
'@opentelemetry/semantic-conventions': 1.24.1
'@types/mysql': 2.15.22
transitivePeerDependencies:
- supports-color
'@opentelemetry/instrumentation-nestjs-core@0.37.1(@opentelemetry/api@1.8.0)':
dependencies:
'@opentelemetry/api': 1.8.0
'@opentelemetry/instrumentation': 0.51.1(@opentelemetry/api@1.8.0)
'@opentelemetry/semantic-conventions': 1.24.1
transitivePeerDependencies:
- supports-color
'@opentelemetry/instrumentation-pg@0.41.0(@opentelemetry/api@1.8.0)':
dependencies:
'@opentelemetry/api': 1.8.0
'@opentelemetry/instrumentation': 0.51.1(@opentelemetry/api@1.8.0)
'@opentelemetry/semantic-conventions': 1.24.1
'@opentelemetry/sql-common': 0.40.1(@opentelemetry/api@1.8.0)
'@types/pg': 8.6.1
'@types/pg-pool': 2.0.4
transitivePeerDependencies:
- supports-color
'@opentelemetry/instrumentation@0.43.0(@opentelemetry/api@1.8.0)':
dependencies:
'@opentelemetry/api': 1.8.0
'@types/shimmer': 1.0.5
import-in-the-middle: 1.4.2
require-in-the-middle: 7.3.0
semver: 7.6.0
shimmer: 1.2.1
transitivePeerDependencies:
- supports-color
optional: true
'@opentelemetry/instrumentation@0.51.1(@opentelemetry/api@1.8.0)':
dependencies:
'@opentelemetry/api': 1.8.0
'@opentelemetry/api-logs': 0.51.1
'@types/shimmer': 1.0.5
import-in-the-middle: 1.7.4
require-in-the-middle: 7.3.0
semver: 7.6.0
shimmer: 1.2.1
transitivePeerDependencies:
- supports-color
'@opentelemetry/redis-common@0.36.2': {}
'@opentelemetry/resources@1.24.1(@opentelemetry/api@1.8.0)':
dependencies:
'@opentelemetry/api': 1.8.0
'@opentelemetry/core': 1.24.1(@opentelemetry/api@1.8.0)
'@opentelemetry/semantic-conventions': 1.24.1
'@opentelemetry/sdk-metrics@1.24.1(@opentelemetry/api@1.8.0)':
dependencies:
'@opentelemetry/api': 1.8.0
'@opentelemetry/core': 1.24.1(@opentelemetry/api@1.8.0)
'@opentelemetry/resources': 1.24.1(@opentelemetry/api@1.8.0)
lodash.merge: 4.6.2
'@opentelemetry/sdk-trace-base@1.24.1(@opentelemetry/api@1.8.0)':
dependencies:
'@opentelemetry/api': 1.8.0
'@opentelemetry/core': 1.24.1(@opentelemetry/api@1.8.0)
'@opentelemetry/resources': 1.24.1(@opentelemetry/api@1.8.0)
'@opentelemetry/semantic-conventions': 1.24.1
'@opentelemetry/semantic-conventions@1.24.1': {}
'@opentelemetry/sql-common@0.40.1(@opentelemetry/api@1.8.0)':
dependencies:
'@opentelemetry/api': 1.8.0
'@opentelemetry/core': 1.24.1(@opentelemetry/api@1.8.0)
'@peculiar/asn1-android@2.3.10':
dependencies:
'@peculiar/asn1-schema': 2.3.8
@@ -13776,6 +14231,14 @@ snapshots:
'@pkgjs/parseargs@0.11.0':
optional: true
'@prisma/instrumentation@5.14.0':
dependencies:
'@opentelemetry/api': 1.8.0
'@opentelemetry/instrumentation': 0.51.1(@opentelemetry/api@1.8.0)
'@opentelemetry/sdk-trace-base': 1.24.1(@opentelemetry/api@1.8.0)
transitivePeerDependencies:
- supports-color
'@radix-ui/react-compose-refs@1.0.1(@types/react@18.0.28)(react@18.3.1)':
dependencies:
'@babel/runtime': 7.23.4
@@ -13922,6 +14385,72 @@ snapshots:
transitivePeerDependencies:
- '@types/node'
'@sentry/core@8.5.0':
dependencies:
'@sentry/types': 8.5.0
'@sentry/utils': 8.5.0
'@sentry/node@8.5.0':
dependencies:
'@opentelemetry/api': 1.8.0
'@opentelemetry/context-async-hooks': 1.24.1(@opentelemetry/api@1.8.0)
'@opentelemetry/core': 1.24.1(@opentelemetry/api@1.8.0)
'@opentelemetry/instrumentation': 0.51.1(@opentelemetry/api@1.8.0)
'@opentelemetry/instrumentation-connect': 0.36.0(@opentelemetry/api@1.8.0)
'@opentelemetry/instrumentation-express': 0.39.0(@opentelemetry/api@1.8.0)
'@opentelemetry/instrumentation-fastify': 0.36.1(@opentelemetry/api@1.8.0)
'@opentelemetry/instrumentation-graphql': 0.40.0(@opentelemetry/api@1.8.0)
'@opentelemetry/instrumentation-hapi': 0.38.0(@opentelemetry/api@1.8.0)
'@opentelemetry/instrumentation-http': 0.51.1(@opentelemetry/api@1.8.0)
'@opentelemetry/instrumentation-ioredis': 0.40.0(@opentelemetry/api@1.8.0)
'@opentelemetry/instrumentation-koa': 0.40.0(@opentelemetry/api@1.8.0)
'@opentelemetry/instrumentation-mongodb': 0.43.0(@opentelemetry/api@1.8.0)
'@opentelemetry/instrumentation-mongoose': 0.38.1(@opentelemetry/api@1.8.0)
'@opentelemetry/instrumentation-mysql': 0.38.1(@opentelemetry/api@1.8.0)
'@opentelemetry/instrumentation-mysql2': 0.38.1(@opentelemetry/api@1.8.0)
'@opentelemetry/instrumentation-nestjs-core': 0.37.1(@opentelemetry/api@1.8.0)
'@opentelemetry/instrumentation-pg': 0.41.0(@opentelemetry/api@1.8.0)
'@opentelemetry/resources': 1.24.1(@opentelemetry/api@1.8.0)
'@opentelemetry/sdk-trace-base': 1.24.1(@opentelemetry/api@1.8.0)
'@opentelemetry/semantic-conventions': 1.24.1
'@prisma/instrumentation': 5.14.0
'@sentry/core': 8.5.0
'@sentry/opentelemetry': 8.5.0(@opentelemetry/api@1.8.0)(@opentelemetry/core@1.24.1(@opentelemetry/api@1.8.0))(@opentelemetry/instrumentation@0.51.1(@opentelemetry/api@1.8.0))(@opentelemetry/sdk-trace-base@1.24.1(@opentelemetry/api@1.8.0))(@opentelemetry/semantic-conventions@1.24.1)
'@sentry/types': 8.5.0
'@sentry/utils': 8.5.0
optionalDependencies:
opentelemetry-instrumentation-fetch-node: 1.2.0
transitivePeerDependencies:
- supports-color
'@sentry/opentelemetry@8.5.0(@opentelemetry/api@1.8.0)(@opentelemetry/core@1.24.1(@opentelemetry/api@1.8.0))(@opentelemetry/instrumentation@0.51.1(@opentelemetry/api@1.8.0))(@opentelemetry/sdk-trace-base@1.24.1(@opentelemetry/api@1.8.0))(@opentelemetry/semantic-conventions@1.24.1)':
dependencies:
'@opentelemetry/api': 1.8.0
'@opentelemetry/core': 1.24.1(@opentelemetry/api@1.8.0)
'@opentelemetry/instrumentation': 0.51.1(@opentelemetry/api@1.8.0)
'@opentelemetry/sdk-trace-base': 1.24.1(@opentelemetry/api@1.8.0)
'@opentelemetry/semantic-conventions': 1.24.1
'@sentry/core': 8.5.0
'@sentry/types': 8.5.0
'@sentry/utils': 8.5.0
'@sentry/profiling-node@8.5.0':
dependencies:
'@sentry/core': 8.5.0
'@sentry/node': 8.5.0
'@sentry/types': 8.5.0
'@sentry/utils': 8.5.0
detect-libc: 2.0.3
node-abi: 3.62.0
transitivePeerDependencies:
- supports-color
'@sentry/types@8.5.0': {}
'@sentry/utils@8.5.0':
dependencies:
'@sentry/types': 8.5.0
'@shikijs/core@1.4.0': {}
'@sideway/address@4.1.4':
@@ -15301,10 +15830,21 @@ snapshots:
dependencies:
'@types/node': 20.12.7
'@types/connect@3.4.36':
dependencies:
'@types/node': 20.12.7
'@types/content-disposition@0.5.8': {}
'@types/cookie@0.6.0': {}
'@types/cookies@0.9.0':
dependencies:
'@types/connect': 3.4.35
'@types/express': 4.17.17
'@types/keygrip': 1.0.6
'@types/node': 20.12.7
'@types/cross-spawn@6.0.2':
dependencies:
'@types/node': 20.12.7
@@ -15372,8 +15912,12 @@ snapshots:
'@types/htmlescape@1.1.3': {}
'@types/http-assert@1.5.5': {}
'@types/http-cache-semantics@4.0.4': {}
'@types/http-errors@2.0.4': {}
'@types/http-link-header@1.0.5':
dependencies:
'@types/node': 20.12.7
@@ -15411,10 +15955,31 @@ snapshots:
'@types/jsrsasign@10.5.14': {}
'@types/keygrip@1.0.6': {}
'@types/keyv@3.1.4':
dependencies:
'@types/node': 20.12.7
'@types/koa-compose@3.2.8':
dependencies:
'@types/koa': 2.14.0
'@types/koa@2.14.0':
dependencies:
'@types/accepts': 1.3.7
'@types/content-disposition': 0.5.8
'@types/cookies': 0.9.0
'@types/http-assert': 1.5.5
'@types/http-errors': 2.0.4
'@types/keygrip': 1.0.6
'@types/koa-compose': 3.2.8
'@types/node': 20.12.7
'@types/koa__router@12.0.3':
dependencies:
'@types/koa': 2.14.0
'@types/lodash@4.14.191': {}
'@types/long@4.0.2': {}
@@ -15445,6 +16010,10 @@ snapshots:
dependencies:
'@types/node': 20.12.7
'@types/mysql@2.15.22':
dependencies:
'@types/node': 20.12.7
'@types/node-fetch@2.6.4':
dependencies:
'@types/node': 20.12.7
@@ -15491,12 +16060,22 @@ snapshots:
'@types/offscreencanvas@2019.7.0': {}
'@types/pg-pool@2.0.4':
dependencies:
'@types/pg': 8.11.5
'@types/pg@8.11.5':
dependencies:
'@types/node': 20.12.7
pg-protocol: 1.6.0
pg-types: 4.0.1
'@types/pg@8.6.1':
dependencies:
'@types/node': 20.12.7
pg-protocol: 1.6.1
pg-types: 2.2.0
'@types/pretty-hrtime@1.0.1': {}
'@types/prop-types@15.7.5': {}
@@ -15554,6 +16133,8 @@ snapshots:
'@types/serviceworker@0.0.67': {}
'@types/shimmer@1.0.5': {}
'@types/simple-oauth2@5.0.7': {}
'@types/sinon@10.0.13':
@@ -16096,6 +16677,15 @@ snapshots:
mime-types: 2.1.35
negotiator: 0.6.3
acorn-import-assertions@1.9.0(acorn@8.11.3):
dependencies:
acorn: 8.11.3
optional: true
acorn-import-attributes@1.9.5(acorn@8.11.3):
dependencies:
acorn: 8.11.3
acorn-jsx@5.3.2(acorn@7.4.1):
dependencies:
acorn: 7.4.1
@@ -19001,6 +19591,21 @@ snapshots:
parent-module: 1.0.1
resolve-from: 4.0.0
import-in-the-middle@1.4.2:
dependencies:
acorn: 8.11.3
acorn-import-assertions: 1.9.0(acorn@8.11.3)
cjs-module-lexer: 1.2.2
module-details-from-path: 1.0.3
optional: true
import-in-the-middle@1.7.4:
dependencies:
acorn: 8.11.3
acorn-import-attributes: 1.9.5(acorn@8.11.3)
cjs-module-lexer: 1.2.2
module-details-from-path: 1.0.3
import-lazy@4.0.0: {}
import-local@3.1.0:
@@ -20509,6 +21114,8 @@ snapshots:
mock-socket@9.3.1: {}
module-details-from-path@1.0.3: {}
mri@1.2.0: {}
ms@2.0.0: {}
@@ -20645,6 +21252,10 @@ snapshots:
just-extend: 4.2.1
path-to-regexp: 1.8.0
node-abi@3.62.0:
dependencies:
semver: 7.6.0
node-abort-controller@3.1.1: {}
node-addon-api@3.2.1:
@@ -20897,6 +21508,15 @@ snapshots:
undici: 5.28.2
yargs-parser: 21.1.1
opentelemetry-instrumentation-fetch-node@1.2.0:
dependencies:
'@opentelemetry/api': 1.8.0
'@opentelemetry/instrumentation': 0.43.0(@opentelemetry/api@1.8.0)
'@opentelemetry/semantic-conventions': 1.24.1
transitivePeerDependencies:
- supports-color
optional: true
optionator@0.9.3:
dependencies:
'@aashutoshrathi/word-wrap': 1.2.6
@@ -21910,6 +22530,14 @@ snapshots:
require-from-string@2.0.2: {}
require-in-the-middle@7.3.0:
dependencies:
debug: 4.3.4(supports-color@8.1.1)
module-details-from-path: 1.0.3
resolve: 1.22.8
transitivePeerDependencies:
- supports-color
require-main-filename@2.0.0: {}
requires-port@1.0.0: {}
@@ -22168,6 +22796,8 @@ snapshots:
dependencies:
'@shikijs/core': 1.4.0
shimmer@1.2.1: {}
side-channel@1.0.4:
dependencies:
call-bind: 1.0.2