| @@ -39,20 +39,6 @@ | |||||
| - [ ] サイズ上限・タイムアウト・content-type 検査を追加する | - [ ] サイズ上限・タイムアウト・content-type 検査を追加する | ||||
| - [ ] 異常時に内部例外メッセージを返さない | - [ ] 異常時に内部例外メッセージを返さない | ||||
| ### 4. [P0] PATCH /tags/:id でシステムタグ名を変更できる抜け道を塞ぐ | |||||
| **種別**: bug | |||||
| **対象** | |||||
| - `backend/app/controllers/tags_controller.rb` | |||||
| **背景 / 問題** | |||||
| TagsController#update_all には system_tag_name? の保護があるが、PATCH 側の update には同等の保護がない。PUT/PATCH の差で「タグ希望」「bot操作」などのシステムタグを壊せる。 | |||||
| **完了条件** | |||||
| - [ ] PATCH update でも system_tag_name? を検査する | |||||
| - [ ] システムタグの category 変更可否も仕様化する | |||||
| - [ ] PUT/PATCH 両方の request spec を追加する | |||||
| ### 5. [P0] GET /users/me を query code 依存から current_user 依存に直す | ### 5. [P0] GET /users/me を query code 依存から current_user 依存に直す | ||||
| @@ -110,112 +96,7 @@ UsersController#me が params[:code] でユーザを探し、inheritance_code | |||||
| - [ ] 許可する埋め込み先を定義する | - [ ] 許可する埋め込み先を定義する | ||||
| - [ ] 未知サイト iframe は警告または opt-in にする | - [ ] 未知サイト iframe は警告または opt-in にする | ||||
| ### 8. [P1] WikiAsset 系のスキーマだけ存在する状態を解消する | |||||
| **種別**: bug / schema drift | |||||
| **対象** | |||||
| - `backend/db/schema.rb` | |||||
| - `backend/db/migrate` | |||||
| - `backend/app/models` | |||||
| - `backend/app/controllers` | |||||
| - `frontend/src/pages/wiki/*` | |||||
| **背景 / 問題** | |||||
| schema.rb に wiki_assets と wiki_pages.next_asset_no があるが、対応 migration / model / controller / frontend 導線が見当たらない。スキーマ幽霊は開発者の判断を腐らせる。 | |||||
| **完了条件** | |||||
| - [ ] 必要なら migration/model/controller/UI を追加する | |||||
| - [ ] 不要なら schema から消す migration を作る | |||||
| - [ ] Wiki 画像アップロード issue と統合するか決める | |||||
| - [ ] schema:load と migrate の差分が出ないことを CI で確認する | |||||
| ### 9. [P1] material_versions を実際に記録するか、削除するか決める | |||||
| **種別**: bug / design debt | |||||
| **対象** | |||||
| - `backend/db/migrate/20260329034700_create_materials.rb` | |||||
| - `backend/db/schema.rb` | |||||
| - `backend/app/models/material.rb` | |||||
| - `backend/app/controllers/materials_controller.rb` | |||||
| **背景 / 問題** | |||||
| material_versions テーブルはあるが、MaterialVersion モデルや recorder が見当たらない。履歴があるように見えて実際は空のままになる設計は危険。 | |||||
| **完了条件** | |||||
| - [ ] MaterialVersion モデルと記録処理を作る、またはテーブルを撤去する | |||||
| - [ ] create/update/destroy で履歴が残る | |||||
| - [ ] 素材復元の要否を決める | |||||
| ### 10. [P1] Wiki の新旧履歴系統を統合する | |||||
| **種別**: design / bug | |||||
| **対象** | |||||
| - `backend/app/controllers/wiki_pages_controller.rb` | |||||
| - `backend/app/models/wiki_revision.rb` | |||||
| - `backend/app/models/wiki_version.rb` | |||||
| - `frontend/src/pages/wiki/WikiHistoryPage.tsx` | |||||
| **背景 / 問題** | |||||
| wiki_revisions と wiki_versions が並存しており、/wiki/changes は WikiRevision を見ている。一方で 2026-04-26 時点の設計は wiki_versions を新履歴としている。二重履歴は必ずズレる。 | |||||
| **完了条件** | |||||
| - [ ] どちらを公開履歴 API とするか決める | |||||
| - [ ] 履歴一覧・差分・復元が同じ履歴系統を参照する | |||||
| - [ ] 旧系統を残す場合は責務を明文化する | |||||
| ### 11. [P1] Wiki 編集で base_revision_id を送信し、競合検出を有効化する | |||||
| **種別**: bug / UX | |||||
| **対象** | |||||
| - `frontend/src/pages/wiki/WikiEditPage.tsx` | |||||
| - `backend/app/controllers/wiki_pages_controller.rb` | |||||
| - `backend/app/services/wiki/commit.rb` | |||||
| **背景 / 問題** | |||||
| バックエンド側は base_revision_id を受け取れるが、フロントが送っていないため実質的な編集競合検出になっていない。複数人編集では後勝ち上書きが起こる。 | |||||
| **完了条件** | |||||
| - [ ] GET /wiki が revision_id を返し、編集フォームが保持する | |||||
| - [ ] PUT /wiki/:id が base_revision_id を受け取る | |||||
| - [ ] 競合時 409 を返す | |||||
| - [ ] フロントで差分確認または再読み込み導線を出す | |||||
| ### 12. [P1] Wiki 検索の「本文」欄を実装するか UI から消す | |||||
| **種別**: bug / UX | |||||
| **対象** | |||||
| - `frontend/src/pages/wiki/WikiSearchPage.tsx` | |||||
| - `backend/app/controllers/wiki_pages_controller.rb` | |||||
| **背景 / 問題** | |||||
| フロントには本文検索っぽい state があるが、バックエンドはタイトル部分一致しか見ていない。検索欄が嘘をついている状態。 | |||||
| **完了条件** | |||||
| - [ ] 本文検索を実装する、または UI から本文欄を消す | |||||
| - [ ] MySQL FULLTEXT / LIKE / 外部検索のどれを使うか決める | |||||
| - [ ] 検索結果に一致箇所スニペットを出す | |||||
| ### 13. [P1] Wiki タイトル表示を tag alias と canonical に連動させる | |||||
| **種別**: feature / correctness | |||||
| **対象** | |||||
| - `backend/app/controllers/wiki_pages_controller.rb` | |||||
| - `backend/app/models/tag_name.rb` | |||||
| - `frontend/src/pages/wiki/WikiDetailPage.tsx` | |||||
| **背景 / 問題** | |||||
| タグ検索・補完は別名を吸収する一方で、Wiki の title API は exact match 寄り。別名タグから正規 Wiki に自然遷移できないと、タグ基盤と Wiki 基盤が割れる。 | |||||
| **完了条件** | |||||
| - [ ] 別名タイトルアクセス時は canonical Wiki へリダイレクトする | |||||
| - [ ] 別名解除・変更時の Wiki 参照先を仕様化する | |||||
| - [ ] TagLink / WikiLink の URL 生成を統一する | |||||
| ### 14. [P1] WikiHistoryPage の null user クラッシュを防ぐ | ### 14. [P1] WikiHistoryPage の null user クラッシュを防ぐ | ||||
| @@ -250,23 +131,7 @@ tagId ? useQuery(...) : ... のような条件付き hook と、権限分岐 ret | |||||
| - [ ] enabled オプションで条件制御する | - [ ] enabled オプションで条件制御する | ||||
| - [ ] eslint-plugin-react-hooks を CI で必須化する | - [ ] eslint-plugin-react-hooks を CI で必須化する | ||||
| ### 16. [P1] タグ補完コンポーネントの重複実装を統合する | |||||
| **種別**: refactor / UX | |||||
| **対象** | |||||
| - `frontend/src/components/TagSearch.tsx` | |||||
| - `frontend/src/components/common/TagInput.tsx` | |||||
| - `frontend/src/components/PostFormTagsArea.tsx` | |||||
| **背景 / 問題** | |||||
| 同系統のタグ補完処理がコピペで 3 箇所に分散している。stale suggestions バグも横展開されている。タグ広場の中核 UI なので、ここは部品化すべき。 | |||||
| **完了条件** | |||||
| - [ ] 共通 hook useTagAutocomplete を作る | |||||
| - [ ] 共通候補ポップアップを作る | |||||
| - [ ] キーボード操作・クリック・IME 入力を同じ仕様にする | |||||
| - [ ] 既存 3 箇所を置換する | |||||
| ### 17. [P1] タグ補完の stale state バグを直す | ### 17. [P1] タグ補完の stale state バグを直す | ||||
| @@ -335,84 +200,7 @@ count に q.size を使うと、includes 済み relation のロード状況次 | |||||
| - [ ] includes と count を分離する | - [ ] includes と count を分離する | ||||
| - [ ] 大量タグでの性能 spec または benchmark を残す | - [ ] 大量タグでの性能 spec または benchmark を残す | ||||
| ### 21. [P1] PostsController#changes を DB ページングにする | |||||
| **種別**: performance | |||||
| **対象** | |||||
| - `backend/app/controllers/posts_controller.rb` | |||||
| **背景 / 問題** | |||||
| post_tags 履歴を Ruby 配列に変換してから sort/slice している。履歴が増えると API 一発で全履歴を舐める。 | |||||
| **完了条件** | |||||
| - [ ] 追加・削除イベントを SQL で union する、または専用 history table/view を作る | |||||
| - [ ] page/limit を DB 側で効かせる | |||||
| - [ ] count も DB で取る | |||||
| ### 22. [P1] ORDER RAND() を避けるランダム取得方式に変える | |||||
| **種別**: performance | |||||
| **対象** | |||||
| - `backend/app/controllers/posts_controller.rb` | |||||
| - `backend/app/controllers/theatres_controller.rb` | |||||
| **背景 / 問題** | |||||
| /posts/random と theatre next_post が ORDER RAND() を使う。件数が増えるほど DB を丸く焼く。 | |||||
| **完了条件** | |||||
| - [ ] 候補 count + offset、id 範囲乱択、事前シャッフルキュー等へ置換する | |||||
| - [ ] フィルタ条件つきランダムでも破綻しない | |||||
| - [ ] RAND 使用を消す | |||||
| ### 23. [P1] LIKE 検索の % / _ ワイルドカードをエスケープする | |||||
| **種別**: bug / correctness | |||||
| **対象** | |||||
| - `backend/app/controllers/posts_controller.rb` | |||||
| - `backend/app/controllers/tags_controller.rb` | |||||
| **背景 / 問題** | |||||
| title/url/name/autocomplete 等で LIKE にユーザ入力を渡しているが、sanitize_sql_like が徹底されていない。SQL injection ではないが、検索語の % や _ がワイルドカードとして暴れる。 | |||||
| **完了条件** | |||||
| - [ ] LIKE 入力は ActiveRecord::Base.sanitize_sql_like を通す | |||||
| - [ ] 特殊文字検索の spec を追加する | |||||
| - [ ] UI の検索説明も合わせる | |||||
| ### 24. [P1] match=any と not: の意味論を再設計する | |||||
| **種別**: design / bug | |||||
| **対象** | |||||
| - `backend/app/controllers/posts_controller.rb` | |||||
| - `frontend/src/pages/posts/PostSearchPage.tsx` | |||||
| **背景 / 問題** | |||||
| OR 検索で not: を混ぜると「A OR not:B」型になり、ほぼ全件に近い結果を返しうる。仕様として正しくても直感的ではない。 | |||||
| **完了条件** | |||||
| - [ ] OR/AND/NOT の真理値表を仕様化する | |||||
| - [ ] UI に説明を出すか、not 条件だけ常に AND とするか決める | |||||
| - [ ] 複合検索の request spec を追加する | |||||
| ### 25. [P1] 閲覧済み API を冪等にする | |||||
| **種別**: bug | |||||
| **対象** | |||||
| - `backend/app/controllers/posts_controller.rb` | |||||
| - `backend/app/models/user_post_view.rb` | |||||
| **背景 / 問題** | |||||
| POST /posts/:id/viewed が current_user.viewed_posts << Post.find で追加しており、二重押しやリトライで重複例外を出しうる。 | |||||
| **完了条件** | |||||
| - [ ] create_or_find_by! にする | |||||
| - [ ] 既に閲覧済みでも 204/200 を返す | |||||
| - [ ] DELETE も未閲覧時に成功扱いにするか仕様化する | |||||
| ### 26. [P2] 投稿編集で URL とサムネイルを扱えるようにするか、非対応を UI に明記する | ### 26. [P2] 投稿編集で URL とサムネイルを扱えるようにするか、非対応を UI に明記する | ||||
| @@ -431,72 +219,7 @@ POST /posts/:id/viewed が current_user.viewed_posts << Post.find で追加し | |||||
| - [ ] 履歴に URL/thumbnail 変更を残す | - [ ] 履歴に URL/thumbnail 変更を残す | ||||
| - [ ] UI の項目と API を一致させる | - [ ] UI の項目と API を一致させる | ||||
| ### 27. [P1] タグ上位関係の循環を DB/モデル/API で禁止する | |||||
| **種別**: bug / data integrity | |||||
| **対象** | |||||
| - `backend/app/models/tag_implication.rb` | |||||
| - `backend/app/models/tag.rb` | |||||
| - `backend/app/controllers/tags_controller.rb` | |||||
| **背景 / 問題** | |||||
| 自己参照は禁止されているが、多段循環 A→B→C→A の防止が弱い。expand_parent_tags は seen で無限ループは避けるが、データ構造として壊れている。 | |||||
| **完了条件** | |||||
| - [ ] 追加時に循環検出する | |||||
| - [ ] 既存データ監査 rake task を作る | |||||
| - [ ] 循環時は 422 を返す | |||||
| - [ ] request spec を追加する | |||||
| ### 28. [P1] タグの別名・上位タグ変更権限を整理する | |||||
| **種別**: security / design | |||||
| **対象** | |||||
| - `backend/app/controllers/tags_controller.rb` | |||||
| - `frontend/src/pages/tags/TagDetailPage.tsx` | |||||
| **背景 / 問題** | |||||
| 上位タグ専用 route は admin なのに、PUT /tags/:id の update_all では member が aliases / parent_tags を変更できる。権限モデルが矛盾している。 | |||||
| **完了条件** | |||||
| - [ ] 別名変更・上位タグ変更を member/admin どちらに許すか決める | |||||
| - [ ] API ごとの権限を統一する | |||||
| - [ ] UI も role で表示制御する | |||||
| - [ ] request spec を追加する | |||||
| ### 29. [P1] Tag.normalise_tags が既存タグ category を暗黙変更する挙動を見直す | |||||
| **種別**: data integrity | |||||
| **対象** | |||||
| - `backend/app/models/tag.rb` | |||||
| **背景 / 問題** | |||||
| プレフィクス付き入力で既存 tag の category を update! している。投稿時のタグ入力だけで既存タグの分類が変わるのは危険。 | |||||
| **完了条件** | |||||
| - [ ] 既存タグの category は暗黙変更しない | |||||
| - [ ] 変更が必要な場合は専用 API に寄せる | |||||
| - [ ] 既存挙動を残すなら警告と履歴を必須にする | |||||
| ### 30. [P1] TagNameSanitisationRule の正規表現 DoS と管理導線を対策する | |||||
| **種別**: security / ops | |||||
| **対象** | |||||
| - `backend/app/models/tag_name_sanitisation_rule.rb` | |||||
| - `backend/db/migrate/20260309123200_create_tag_name_sanitisation_rules.rb` | |||||
| **背景 / 問題** | |||||
| サニタイズ規則は強力だが、正規表現を DB に保存して一括適用する。管理 UI/API を作るなら ReDoS と誤爆一括変更への安全柵が必須。 | |||||
| **完了条件** | |||||
| - [ ] 鯖缶専用にする | |||||
| - [ ] 適用前 dry-run を出す | |||||
| - [ ] 変更件数と影響タグを表示する | |||||
| - [ ] 危険 regex を検出またはタイムアウトする | |||||
| # | |||||
| ### 31. [P2] NicoTagsController の cursor / id エラーを 400/404 にする | ### 31. [P2] NicoTagsController の cursor / id エラーを 400/404 にする | ||||