Skip to content

feat: コントロールパネルからインスタンス独自のサウンドを追加できるように - #1289

Draft
4ster1sk wants to merge 16 commits into
yojo-art:developfrom
4ster1sk:feat/195-instance-custom-sounds
Draft

4ster1sk wants to merge 16 commits into
yojo-art:developfrom
4ster1sk:feat/195-instance-custom-sounds

Conversation

@4ster1sk

@4ster1sk 4ster1sk commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

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の通知サウンド設定)でインスタンスサウンドを選択可能に
  • 音声再生の URL 解決と、削除済みサウンド選択時のデフォルト音へのフォールバックを実装
  • コントロールパネル「その他」にインスタンスサウンド管理セクションを追加(追加・一覧・再生・削除)
  • ロール設定に canManageCustomSounds ポリシーを追加

i18n

  • ja-JP.yml / en-US.yml に必要な文言を追加

Why

インスタンス独自のサウンドを登録するには、従来はリポジトリに mp3 ファイルを追加してリリース・デプロイが必要でした。管理者がコントロールパネルからサウンドを登録・管理でき、ユーザーが設定画面で選択できるようにするためです。

Additional info

  • 削除したサウンドを選択していたユーザーは、再生時に自動でデフォルトサウンドへフォールバックします(設定変更は不要)
  • 同じドライブファイルを別のサウンドとして再登録しようとするとエラーになります(FILE_ALREADY_USED、DB 側の一意制約で保護)
  • e2e テスト(packages/backend/test/e2e/custom-sounds.ts)を追加
sound-list-fullpage-light

Checklist

  • コントリビューションガイドを読みました( Read the contribution guide)
  • ローカル環境で動作しました(Test working in a local environment)
  • (必要なら)CHANGELOG_YOJO.mdの更新((If needed) Update CHANGELOG_YOJO.md)
  • (必要なら)テストの追加((If possible) Add tests)

close: #195

@4ster1sk 4ster1sk changed the title feat: コントロールパネルからインスタンス独自のサウンドを追加できるように (#195) feat: コントロールパネルからインスタンス独自のサウンドを追加できるように Aug 13, 2026
@codecov

codecov Bot commented Aug 13, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 15.84158% with 85 lines in your changes missing coverage. Please review.
✅ Project coverage is 22.29%. Comparing base (5c84918) to head (c395b29).
⚠️ Report is 93 commits behind head on develop.

Files with missing lines Patch % Lines
packages/backend/src/core/CustomSoundService.ts 18.75% 20 Missing and 6 partials ⚠️
...server/api/endpoints/admin/custom-sounds/create.ts 0.00% 20 Missing and 4 partials ⚠️
...server/api/endpoints/admin/custom-sounds/delete.ts 0.00% 9 Missing and 2 partials ⚠️
...c/server/api/endpoints/admin/custom-sounds/list.ts 0.00% 8 Missing and 1 partial ⚠️
...kend/src/server/api/endpoints/get-custom-sounds.ts 0.00% 8 Missing and 1 partial ⚠️
packages/frontend/src/utility/sound.ts 0.00% 1 Missing and 4 partials ⚠️
packages/frontend/src/router.definition.ts 66.66% 1 Missing ⚠️
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.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions

github-actions Bot commented Aug 13, 2026

Copy link
Copy Markdown
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",
[Get diff files from Workflow Page](https://github.com/yojo-art/cherrypick/actions/runs/32628152810)

@github-actions

github-actions Bot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Backend memory usage comparison

Before GC

Metric base (MB) head (MB) Diff (MB) Diff (%)
VmRSS 451.99 MB 457.10 MB +5.11 MB +1.13%
VmHWM 451.99 MB 457.10 MB +5.11 MB +1.13%
VmSize 3232.01 MB 3238.31 MB +6.30 MB +0.19%
VmData 2034.67 MB 2039.39 MB +4.71 MB +0.23%

After GC

Metric base (MB) head (MB) Diff (MB) Diff (%)
VmRSS 451.67 MB 457.09 MB +5.41 MB +1.19%
VmHWM 451.94 MB 457.09 MB +5.14 MB +1.13%
VmSize 3231.62 MB 3238.15 MB +6.53 MB +0.20%
VmData 2034.28 MB 2039.22 MB +4.93 MB +0.24%

After Request

Metric base (MB) head (MB) Diff (MB) Diff (%)
VmRSS 451.41 MB 456.95 MB +5.54 MB +1.22%
VmHWM 452.12 MB 457.24 MB +5.11 MB +1.13%
VmSize 3231.53 MB 3238.15 MB +6.61 MB +0.20%
VmData 2034.20 MB 2039.22 MB +5.02 MB +0.24%

See workflow logs for details

@4ster1sk
4ster1sk force-pushed the feat/195-instance-custom-sounds branch from af5da97 to ee53d07 Compare August 15, 2026 13:00
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
4ster1sk force-pushed the feat/195-instance-custom-sounds branch from e732e30 to b757ea5 Compare August 16, 2026 01:31
- 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 を返すまでポーリングするように変更
Comment thread packages/backend/src/core/CustomSoundService.ts
Comment thread packages/backend/test-server/entry.ts
Comment thread packages/backend/src/server/api/endpoints/admin/custom-sounds/create.ts Outdated
@kozakura913 kozakura913 added this to the 1.11.0 milestone Aug 20, 2026
@kozakura913 kozakura913 removed this from the 1.11.0 milestone Aug 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

コントロールパネルからインスタンス独自のサウンドを追加

2 participants