Conversation
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## develop #1289 +/- ##
===========================================
- Coverage 23.52% 22.29% -1.24%
===========================================
Files 1256 1267 +11
Lines 48287 48662 +375
Branches 14209 14315 +106
===========================================
- Hits 11358 10847 -511
- Misses 29172 29837 +665
- Partials 7757 7978 +221 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Contributor
|
このPRによるapi.jsonの差分 差分はこちら--- base
+++ head
@@ -5955,6 +5955,537 @@
}
}
},
+ "/admin/custom-sounds/create": {
+ "post": {
+ "operationId": "post___admin___custom-sounds___create",
+ "summary": "admin/custom-sounds/create",
+ "description": "No description provided.\n\n**Credential required**: *Yes* / **Permission**: *write:admin:custom-sounds*",
+ "externalDocs": {
+ "description": "Source code",
+ "url": "https://github.com/yojo-art/cherrypick/blob/develop/packages/backend/src/server/api/endpoints/admin/custom-sounds/create.ts"
+ },
+ "tags": [
+ "admin"
+ ],
+ "security": [
+ {
+ "bearerAuth": []
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 256
+ },
+ "fileId": {
+ "type": "string",
+ "format": "misskey:id"
+ }
+ },
+ "required": [
+ "name",
+ "fileId"
+ ]
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "OK (with results)",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string",
+ "format": "id"
+ },
+ "name": {
+ "type": "string"
+ },
+ "driveFile": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "format": "id"
+ },
+ "url": {
+ "type": [
+ "string",
+ "null"
+ ]
+ }
+ },
+ "required": [
+ "id",
+ "name",
+ "driveFile",
+ "url"
+ ]
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Client error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Error"
+ },
+ "examples": {
+ "NO_SUCH_FILE": {
+ "value": {
+ "error": {
+ "message": "No such file.",
+ "code": "NO_SUCH_FILE",
+ "id": "d96a6683-be10-4c7a-9a3a-93b1c2f1ac8b"
+ }
+ }
+ },
+ "UNSUPPORTED_FILE_TYPE": {
+ "value": {
+ "error": {
+ "message": "Unsupported file type.",
+ "code": "UNSUPPORTED_FILE_TYPE",
+ "id": "d1a16b90-6cdc-49f2-a191-cc2204d2049a"
+ }
+ }
+ },
+ "INVALID_PARAM": {
+ "value": {
+ "error": {
+ "message": "Invalid param.",
+ "code": "INVALID_PARAM",
+ "id": "3d81ceae-475f-4600-b2a8-2bc116157532"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Authentication error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Error"
+ },
+ "examples": {
+ "CREDENTIAL_REQUIRED": {
+ "value": {
+ "error": {
+ "message": "Credential required.",
+ "code": "CREDENTIAL_REQUIRED",
+ "id": "1384574d-a912-4b81-8601-c7b1c4085df1"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Error"
+ },
+ "examples": {
+ "AUTHENTICATION_FAILED": {
+ "value": {
+ "error": {
+ "message": "Authentication failed. Please ensure your token is correct.",
+ "code": "AUTHENTICATION_FAILED",
+ "id": "b0a7f5f8-dc2f-4171-b91f-de88ad238e14"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "418": {
+ "description": "I'm Ai",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Error"
+ },
+ "examples": {
+ "I_AM_AI": {
+ "value": {
+ "error": {
+ "message": "You sent a request to Ai-chan, CherryPick's showgirl, instead of the server.",
+ "code": "I_AM_AI",
+ "id": "60c46cd1-f23a-46b1-bebe-5d2b73951a84"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Internal server error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Error"
+ },
+ "examples": {
+ "INTERNAL_ERROR": {
+ "value": {
+ "error": {
+ "message": "Internal error occurred. Please contact us if the error persists.",
+ "code": "INTERNAL_ERROR",
+ "id": "5d37dbcb-891e-41ca-a3d6-e690c97775ac"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/admin/custom-sounds/delete": {
+ "post": {
+ "operationId": "post___admin___custom-sounds___delete",
+ "summary": "admin/custom-sounds/delete",
+ "description": "No description provided.\n\n**Credential required**: *Yes* / **Permission**: *write:admin:custom-sounds*",
+ "externalDocs": {
+ "description": "Source code",
+ "url": "https://github.com/yojo-art/cherrypick/blob/develop/packages/backend/src/server/api/endpoints/admin/custom-sounds/delete.ts"
+ },
+ "tags": [
+ "admin"
+ ],
+ "security": [
+ {
+ "bearerAuth": []
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string",
+ "format": "misskey:id"
+ }
+ },
+ "required": [
+ "id"
+ ]
+ }
+ }
+ }
+ },
+ "responses": {
+ "204": {
+ "description": "OK (without any results)"
+ },
+ "400": {
+ "description": "Client error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Error"
+ },
+ "examples": {
+ "NO_SUCH_SOUND": {
+ "value": {
+ "error": {
+ "message": "No such sound.",
+ "code": "NO_SUCH_SOUND",
+ "id": "9ec39198-241a-49f3-9efb-f74b22fdb8c9"
+ }
+ }
+ },
+ "INVALID_PARAM": {
+ "value": {
+ "error": {
+ "message": "Invalid param.",
+ "code": "INVALID_PARAM",
+ "id": "3d81ceae-475f-4600-b2a8-2bc116157532"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Authentication error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Error"
+ },
+ "examples": {
+ "CREDENTIAL_REQUIRED": {
+ "value": {
+ "error": {
+ "message": "Credential required.",
+ "code": "CREDENTIAL_REQUIRED",
+ "id": "1384574d-a912-4b81-8601-c7b1c4085df1"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Error"
+ },
+ "examples": {
+ "AUTHENTICATION_FAILED": {
+ "value": {
+ "error": {
+ "message": "Authentication failed. Please ensure your token is correct.",
+ "code": "AUTHENTICATION_FAILED",
+ "id": "b0a7f5f8-dc2f-4171-b91f-de88ad238e14"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "418": {
+ "description": "I'm Ai",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Error"
+ },
+ "examples": {
+ "I_AM_AI": {
+ "value": {
+ "error": {
+ "message": "You sent a request to Ai-chan, CherryPick's showgirl, instead of the server.",
+ "code": "I_AM_AI",
+ "id": "60c46cd1-f23a-46b1-bebe-5d2b73951a84"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Internal server error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Error"
+ },
+ "examples": {
+ "INTERNAL_ERROR": {
+ "value": {
+ "error": {
+ "message": "Internal error occurred. Please contact us if the error persists.",
+ "code": "INTERNAL_ERROR",
+ "id": "5d37dbcb-891e-41ca-a3d6-e690c97775ac"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/admin/custom-sounds/list": {
+ "post": {
+ "operationId": "post___admin___custom-sounds___list",
+ "summary": "admin/custom-sounds/list",
+ "description": "No description provided.\n\n**Credential required**: *Yes* / **Permission**: *read:admin:custom-sounds*",
+ "externalDocs": {
+ "description": "Source code",
+ "url": "https://github.com/yojo-art/cherrypick/blob/develop/packages/backend/src/server/api/endpoints/admin/custom-sounds/list.ts"
+ },
+ "tags": [
+ "admin"
+ ],
+ "security": [
+ {
+ "bearerAuth": []
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK (with results)",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string",
+ "format": "id"
+ },
+ "name": {
+ "type": "string"
+ },
+ "driveFile": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "format": "id"
+ },
+ "url": {
+ "type": [
+ "string",
+ "null"
+ ]
+ }
+ },
+ "required": [
+ "id",
+ "name",
+ "driveFile",
+ "url"
+ ]
+ }
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Client error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Error"
+ },
+ "examples": {
+ "INVALID_PARAM": {
+ "value": {
+ "error": {
+ "message": "Invalid param.",
+ "code": "INVALID_PARAM",
+ "id": "3d81ceae-475f-4600-b2a8-2bc116157532"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Authentication error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Error"
+ },
+ "examples": {
+ "CREDENTIAL_REQUIRED": {
+ "value": {
+ "error": {
+ "message": "Credential required.",
+ "code": "CREDENTIAL_REQUIRED",
+ "id": "1384574d-a912-4b81-8601-c7b1c4085df1"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Error"
+ },
+ "examples": {
+ "AUTHENTICATION_FAILED": {
+ "value": {
+ "error": {
+ "message": "Authentication failed. Please ensure your token is correct.",
+ "code": "AUTHENTICATION_FAILED",
+ "id": "b0a7f5f8-dc2f-4171-b91f-de88ad238e14"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "418": {
+ "description": "I'm Ai",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Error"
+ },
+ "examples": {
+ "I_AM_AI": {
+ "value": {
+ "error": {
+ "message": "You sent a request to Ai-chan, CherryPick's showgirl, instead of the server.",
+ "code": "I_AM_AI",
+ "id": "60c46cd1-f23a-46b1-bebe-5d2b73951a84"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Internal server error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Error"
+ },
+ "examples": {
+ "INTERNAL_ERROR": {
+ "value": {
+ "error": {
+ "message": "Internal error occurred. Please contact us if the error persists.",
+ "code": "INTERNAL_ERROR",
+ "id": "5d37dbcb-891e-41ca-a3d6-e690c97775ac"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
"/admin/delete-account": {
"post": {
"operationId": "post___admin___delete-account",
@@ -56668,6 +57199,169 @@
}
}
},
+ "/get-custom-sounds": {
+ "post": {
+ "operationId": "post___get-custom-sounds",
+ "summary": "get-custom-sounds",
+ "description": "No description provided.\n\n**Credential required**: *No*",
+ "externalDocs": {
+ "description": "Source code",
+ "url": "https://github.com/yojo-art/cherrypick/blob/develop/packages/backend/src/server/api/endpoints/get-custom-sounds.ts"
+ },
+ "tags": [
+ "meta"
+ ],
+ "responses": {
+ "200": {
+ "description": "OK (with results)",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string",
+ "format": "id",
+ "example": "xxxxxxxxxx"
+ },
+ "name": {
+ "type": "string"
+ },
+ "driveFile": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "format": "id"
+ },
+ "url": {
+ "type": [
+ "string",
+ "null"
+ ]
+ }
+ },
+ "required": [
+ "id",
+ "name",
+ "driveFile",
+ "url"
+ ]
+ }
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Client error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Error"
+ },
+ "examples": {
+ "INVALID_PARAM": {
+ "value": {
+ "error": {
+ "message": "Invalid param.",
+ "code": "INVALID_PARAM",
+ "id": "3d81ceae-475f-4600-b2a8-2bc116157532"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Authentication error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Error"
+ },
+ "examples": {
+ "CREDENTIAL_REQUIRED": {
+ "value": {
+ "error": {
+ "message": "Credential required.",
+ "code": "CREDENTIAL_REQUIRED",
+ "id": "1384574d-a912-4b81-8601-c7b1c4085df1"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Error"
+ },
+ "examples": {
+ "AUTHENTICATION_FAILED": {
+ "value": {
+ "error": {
+ "message": "Authentication failed. Please ensure your token is correct.",
+ "code": "AUTHENTICATION_FAILED",
+ "id": "b0a7f5f8-dc2f-4171-b91f-de88ad238e14"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "418": {
+ "description": "I'm Ai",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Error"
+ },
+ "examples": {
+ "I_AM_AI": {
+ "value": {
+ "error": {
+ "message": "You sent a request to Ai-chan, CherryPick's showgirl, instead of the server.",
+ "code": "I_AM_AI",
+ "id": "60c46cd1-f23a-46b1-bebe-5d2b73951a84"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Internal server error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Error"
+ },
+ "examples": {
+ "INTERNAL_ERROR": {
+ "value": {
+ "error": {
+ "message": "Internal error occurred. Please contact us if the error persists.",
+ "code": "INTERNAL_ERROR",
+ "id": "5d37dbcb-891e-41ca-a3d6-e690c97775ac"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
"/get-online-users-count": {
"get": {
"operationId": "get___get-online-users-count",
@@ -104564,6 +105258,9 @@
"canManageAvatarDecorations": {
"type": "boolean"
},
+ "canManageCustomSounds": {
+ "type": "boolean"
+ },
"canSearchNotes": {
"type": "boolean"
},
@@ -104690,6 +105387,7 @@
"inviteExpirationTime",
"canManageCustomEmojis",
"canManageAvatarDecorations",
+ "canManageCustomSounds",
"canSearchNotes",
"canSearchUsers",
"canAdvancedSearchNotes", |
Contributor
Backend memory usage comparisonBefore GC
After GC
After Request
|
4ster1sk
force-pushed
the
feat/195-instance-custom-sounds
branch
from
August 15, 2026 13:00
af5da97 to
ee53d07
Compare
4ster1sk
added a commit
to 4ster1sk/cherrypick
that referenced
this pull request
Aug 15, 2026
- admin/other-settings で canManageCustomSounds ポリシーなしのモデレータでも re-index 進捗表示が壊れないよう、サウンド一覧の取得をポリシーで分岐し エラーを try/catch で握るように - CustomSoundService.packMany の N+1 クエリを In() による一括取得に変更 - サウンド削除確認ダイアログに、ドライブファイルは残る旨のメッセージを追加
- MiCustomSound エンティティと custom_sound テーブルを追加
- admin/custom-sounds/{create,list,delete} と公開 get-custom-sounds API を追加
- ロールポリシー canManageCustomSounds を追加
- サウンド設定でインスタンスサウンドを選択可能に
- 削除時はデフォルト音にフォールバック
- MiCustomSound を TypeORM エンティティ登録リスト (postgres.ts) に追加 - migration を typeorm migration:generate で生成した形式に置き換え
- サウンド追加を os.form ダイアログで行うように変更 - 一覧の再生ボタンに「聴く」ラベルを追加 - 削除ボタンを「×」アイコンに変更し赤色に - custom_sound の url を fileId から動的解決するよう変更 - fileId のカラム長を varchar(32) に修正 - テスト音声ファイル3つを追加
- admin/other-settings で canManageCustomSounds ポリシーなしのモデレータでも re-index 進捗表示が壊れないよう、サウンド一覧の取得をポリシーで分岐し エラーを try/catch で握るように - CustomSoundService.packMany の N+1 クエリを In() による一括取得に変更 - サウンド削除確認ダイアログに、ドライブファイルは残る旨のメッセージを追加
4ster1sk
force-pushed
the
feat/195-instance-custom-sounds
branch
from
August 16, 2026 01:31
e732e30 to
b757ea5
Compare
- feat/195-instance-custom-sounds のレビュー指摘 4 件に対応(CHANGELOG を除く) - en-US.yml に fileAlreadyUsed / fileAlreadyUsedTitle を追加(空ダイアログ防止) - カスタムサウンド管理UIを /settings/custom-sounds へ移設し、canManageCustomSounds ロール保持者(非 admin/mod)でも管理可能に(iAmModerator ガードの外へ) - tl-note-notification.ts の get-custom-sounds 取得失敗時に空配列へフォールバック - sounds.sound.vue で url:null の破損サウンドを選択肢から除外
- 参照ドライブファイル削除後に get-custom-sounds が url: null を返すことを検証 (フロントの破損サウンド除外ロジックが依存するコントラクト) - canManageCustomSounds ポリシー保持者が admin/custom-sounds/list を参照できることを検証 - test-server/entry.ts で NODE_ENV=test を loadConfig() より先に設定。 従来は loadConfig() が production として実行され、originEnv に production が記録され、 sendEnvResetRequest で NODE_ENV が production に戻り signup が 400 になる e2e 全体の問題を修正
- ドライブファイル削除の DB 反映を固定時間待つ代わりに、 get-custom-sounds が url: null を返すまでポーリングするように変更
kozakura913
requested changes
Aug 19, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
コントロールパネル「その他」から、インスタンス独自のサウンドを登録・管理できるようにしました。登録したサウンドは、ユーザーがサウンド設定(
settings/sounds・デッキTLの通知サウンド設定)で選択できるようになります。Backend
MiCustomSoundエンティティとcustom_soundテーブルを追加(migration 付き)canManageCustomSoundsを新設(canManageAvatarDecorationsと同様の権限制御)admin/custom-sounds/create(音声ファイルをドライブから指定して登録)admin/custom-sounds/list(管理者用一覧)admin/custom-sounds/delete(削除)get-custom-sounds(公開・ログイン不要の一覧)CustomSoundServiceを追加Frontend
settings/sounds・デッキTLの通知サウンド設定)でインスタンスサウンドを選択可能にcanManageCustomSoundsポリシーを追加i18n
ja-JP.yml/en-US.ymlに必要な文言を追加Why
インスタンス独自のサウンドを登録するには、従来はリポジトリに mp3 ファイルを追加してリリース・デプロイが必要でした。管理者がコントロールパネルからサウンドを登録・管理でき、ユーザーが設定画面で選択できるようにするためです。
Additional info
FILE_ALREADY_USED、DB 側の一意制約で保護)packages/backend/test/e2e/custom-sounds.ts)を追加Checklist
close: #195