diff --git a/%E5%AE%9F%E8%A3%85%E8%AA%AC%E6%98%8E%E6%9B%B8.md b/%E5%AE%9F%E8%A3%85%E8%AA%AC%E6%98%8E%E6%9B%B8.md index 12a57b6..19c435a 100644 --- a/%E5%AE%9F%E8%A3%85%E8%AA%AC%E6%98%8E%E6%9B%B8.md +++ b/%E5%AE%9F%E8%A3%85%E8%AA%AC%E6%98%8E%E6%9B%B8.md @@ -1,7 +1,7 @@ --- title: 'BTRC Hub 仕様書' -subtitle: '現行仕様と計画仕様の統合版(2026-03-23)' -date: '2026-03-23' +subtitle: '現行仕様と計画仕様の統合版(2026-04-20)' +date: '2026-04-20' lang: ja-JP toc: true toc-depth: 2 @@ -11,6 +11,7 @@ geometry: - margin=22mm fontsize: 10pt mainfont: 'Noto Sans CJK JP' +monofont: 'Noto Sans Mono CJK JP' header-includes: - | \usepackage{longtable} @@ -24,66 +25,94 @@ header-includes: # 本書の位置づけ -本書は、**最新ソーススナップショット btrc-hub-feature_297.zip** を起点に、BTRC Hub の現行仕様を再構成したうえで、開発者ヒアリングによって得られた **未実装だが今後仕様書へ載せるべき内容** を統合したものである。 +本書は、2026-04-19 時点で置かれている資材と、2026-04-20 の追加開発者ヒアリングをもとに、BTRC Hub の仕様を最新化したものである。 -本書の役割は 2 つある。 +- `btrc-hub-main.zip`: 現行ソースコード +- `btrc-hub.wiki.zip`: 開発 Wiki +- `btrc-hub.issue.sql`: Gitea issue エクスポート +- `BTRC_Hub_仕様書_2026-03-23.md`: 既存仕様書 -1. **現行仕様の説明書** - 実際にコードへ存在する API・画面・永続データ・制約を整理する。 +本書の目的は、実装済みの事実と将来計画を分離して、開発・公開判断・今後の設計変更に耐える基準文書にすることである。 -2. **計画仕様の設計書** - 一般公開に向けて実装予定の機能、公開段階、運用方針、未確定論点を明文化する。 +## 情報の優先順位 -参照元は次の 5 系統である。 +| 優先順位 | 情報源 | 扱い | +| ---: | --- | --- | +| 1 | 現行ソースコード、`db/schema.rb`、ルーティング | 現行仕様の根拠 | +| 2 | フロントエンドの画面・型・API 呼出 | 画面仕様と実利用導線の根拠 | +| 3 | Wiki と issue | 設計意図、未実装計画、運用課題の根拠 | +| 4 | 既存仕様書 | 体系化済みの文脈として再利用 | -1. バックエンド実装(Rails 8 API) -2. フロントエンド実装(React + Vite) -3. db/schema.rb -4. 既存仕様書 BTRC_Hub_現行仕様書_2026-03-22.md -5. 2026-03-23 時点の開発者ヒアリング結果 - -情報の優先順位は明確である。 - -- **第 1 優先**: 現在のソースコードと db/schema.rb -- **第 2 優先**: フロントエンドの画面実装とルーティング -- **第 3 優先**: 開発者が明示した計画仕様 -- **第 4 優先**: 既存仕様書の文脈整理 - -したがって本書では、記述を次の 3 種に分ける。 +本書では、仕様を次の 3 種に分ける。 | 表記 | 意味 | | --- | --- | -| 現行仕様 | 現在のコードとスキーマで確認できるもの | -| 計画仕様(確定) | 実装は未了だが、方針として採用済のもの | -| 計画仕様(未確定) | 方向性はあるが、具体形が今後詰まるもの | +| 現行仕様 | 現在のコード、スキーマ、画面から確認できるもの | +| 計画仕様(確定) | 実装は未完だが方針として採用済みのもの | +| 計画仕様(未確定) | 方向性はあるが、実装方針・責務・公開範囲が未整理のもの | -この区別を曖昧にすると、「実装済み」と「願望」が混ざって腐る。 -本書はそこを分離し、**開発・公開判断・将来の実装順序づけ** に耐える文書とする。 +## 2026-03-23 版からの主要更新 +今回の最新化で、次の項目を現行仕様へ昇格した。 + +| 項目 | 2026-04-19 時点の扱い | +| --- | --- | +| 投稿版管理 `post_versions` | 実装済み。`/posts/versions` と耕作履歴画面で使用 | +| 投稿履歴からの復元 | フロントで実装済み。ただし復元は専用 API ではなく `PUT /posts/:id` を使う | +| 素材集 `materials` | API と画面が実装済み。ただしトップナビでは非表示 | +| 素材の版テーブル `material_versions` | テーブルのみ存在。モデル・記録処理・API は未接続 | +| Wiki 画像 `wiki_assets` | テーブルのみ存在。現行ルート・モデル・画面は未接続 | +| 上位タグ D&D 操作 | 投稿詳細サイドバーで実装済み。API は admin のみ許可 | +| タグ階層取得 `/tags/with-depth` | 実装済み。素材サイドバーで使用 | +| 利用規約 `/tos` | MDX ページとルートは存在。ただしトップナビでは非表示 | +| R2 ストレージ | production で Active Storage service `:r2` を使用 | +| whenever スケジュール | `nico:sync` と `post_similarity:calc` が `config/schedule.rb` に定義済み | +| `tag_similarities` | テーブル・モデル・計算タスクは存在。ただし現行 UI での明確な表示導線は薄い | +| 素材作成権限 | 公開時も guest に素材作成を許可する方針 | +| Wiki 画像記法 | 専用記法を採用する方針。具体案は `` 系 | + +逆に、次のものは現行仕様として書くと嘘になる。 + +- Wiki 画像アップロードの実用 UI/API +- 素材履歴の表示・復元 +- 汎用設定 `settings` の取得・保存・反映 API +- BAN の認可強制 +- 投稿編集の排他制御 +- 別名タグ管理 UI +- Wiki 本文検索 +- Theatre の会場一覧・作成・編集 +- 管理画面 +- 通知機能 # システム概要 ## 目的 -BTRC Hub は、ぼざろクリーチャーシリーズ関連コンテンツへのリンクを集約し、タグ・Wiki・関連付けによって横断的に検索・整理するためのシステムである。対象はコンテンツ本体ではなく **リンク** であり、知識を共同編集しやすくすることが主眼にある。 +BTRC Hub は、ぼざろクリーチャーシリーズ関連コンテンツへのリンクを集約し、タグ・Wiki・素材・外部同期によって横断的に検索・整理するためのシステムである。 + +重要なのは、保持する対象がコンテンツ本体ではなく **リンクと付帯知識** である点である。開発 Wiki でも、danbooru 的な整理性は参考にしつつ、コンテンツ本体の転載ではなくリンク集約に留める方針が示されている。 -現行ソースから読み取れる中心目的は次の 4 点である。 +## 中核価値 -- 複数プラットフォーム上の関連コンテンツを横断管理すること -- タグ付けによって検索性と再発見性を上げること -- Wiki によってタグ単位の知識を蓄積すること -- ニコニコ外部情報を内部タグ体系へ取り込むこと +現行 BTRC Hub の中核価値は次の 5 つである。 + +1. 複数プラットフォーム上の関連コンテンツをリンクとして集約する。 +2. タグ・別名・上位タグ・外部タグ連携で再発見性を高める。 +3. Wiki によりタグ単位の説明と知識を蓄積する。 +4. ニコニコ外部 DB から投稿・タグ情報を日次同期する。 +5. 素材集・上映会など、整理基盤の周辺機能を育てる。 ## 非目的 -現時点のコードベースは、次を中核機能とはしていない。 +現行の実装思想上、次は中核目的ではない。 - 汎用 SNS 化 -- 本文コメント中心の会話サービス化 +- コメント欄中心の交流サービス化 - コンテンツ本体の保存・転載 -- 多人数配信基盤そのものの実装 +- 動画配信基盤そのものの実装 +- ニジカ Wiki や虹鹿園と同一目的の重複サービス化 -ただし後述のとおり、**上映会(Theatre)機能** はすでにソースへ入っており、完全な SNS ではないが共同視聴寄りの要素は持ち始めている。 +ただし、Theatre や素材集のように、整理基盤から派生する共同利用機能は実装され始めている。 ## 技術構成 @@ -91,276 +120,331 @@ BTRC Hub は、ぼざろクリーチャーシリーズ関連コンテンツへ | --- | --- | | バックエンド | Rails 8.0.2, MySQL 8, Active Storage | | フロントエンド | React 19.1, Vite 6.3, TypeScript 5.8 | -| 状態管理・通信 | TanStack Query 5, Axios | -| UI | Tailwind CSS 3, Framer Motion 12 | -| 補助ライブラリ | Nokogiri, MiniMagick, Discard, diff-lcs, MarkdownIt, react-markdown-editor-lite | -| バッチ | Rake task による同期・移行・類似度計算 | - -## 現行仕様の要点 - -現行ソースを一言で言うと、BTRC Hub は **タグ付きリンク集 UI を持つ知識統合基盤** である。核になっているのは次の 4 本柱である。 - -1. **投稿**: URL を中心としたリンクオブジェクト -2. **タグ**: カテゴリ・別名・上位タグ・外部連携を持つ知識単位 -3. **Wiki**: 行単位ストレージと改訂履歴を持つページ体系 -4. **上映会**: 単一会場内での共同視聴とコメント機能 - -2026-03-08 版から見て特に重要な更新点は次である。 - -- 投稿検索は **タイトル / URL / 各種日時 / 並び順** に対応した -- タグ検索画面が実装され、**詳細条件と並び替え** が入った -- タグ検索は **not: 接頭辞** による否定検索を内部的に扱う -- Theatre 系の **API と画面** が追加された -- tag_name_sanitisation_rules による **タグ名サニタイズ層** が入った -- 旧仕様書で「未実装」としていた項目の一部が、現在は実装済になっている +| 通信・状態管理 | Axios, TanStack Query 5 | +| UI | Tailwind CSS 3, Framer Motion 12, Radix UI 系コンポーネント | +| Markdown | react-markdown, remark-gfm, react-markdown-editor-lite, 独自 Wiki 自動リンク | +| D&D | @dnd-kit/core | +| 画像処理 | MiniMagick | +| 外部取得 | Nokogiri, open-uri, Node screenshot script | +| 論理削除 | discard | +| PDF/差分等 | diff-lcs | +| ストレージ | development/test は Disk、production は Cloudflare R2 相当の S3 互換ストレージ | +| バッチ | Rake task、whenever schedule | # 利用者と認証 ## ユーザモデル -users.role は文字列 enum であり、現行ロールは次の 3 種である。 +`users.role` は文字列 enum であり、現行ロールは次の 3 種である。 | ロール | 意味 | | --- | --- | -| guest | 閲覧中心。編集系は不可 | +| guest | 初期発行ユーザ。閲覧中心 | | member | 投稿・Wiki・タグ更新など通常の編集可 | -| admin | member 権限に加え、管理系操作可 | +| admin | member 権限に加え、上位タグ設定など管理寄り操作可 | + +`User#gte_member?` は `member` または `admin` を真とする。 ## 認証方式 認証は ID / パスワードではなく、**引継ぎコード** による軽量認証である。 -- クライアントは localStorage.user_code を保持する -- API 呼び出し時は X-Transfer-Code ヘッダで送信する -- サーバは users.inheritance_code と照合して current_user を決定する +- ユーザ作成時に UUID 形式の `inheritance_code` を発行する。 +- フロントは `localStorage.user_code` に保持する。 +- API 呼び出し時、`X-Transfer-Code` ヘッダへ付与する。 +- `ApplicationController#authenticate_user` はヘッダ値から `User.find_by(inheritance_code:)` する。 -ApplicationController の認証処理は非常に薄く、**ヘッダからコードを読むだけ** である。禁止ユーザや禁止 IP の強制チェックはここでは行っていない。 +この仕組みは簡単で UX は軽いが、通常のパスワード認証より防御面は薄い。公開段階では、権限範囲を絞る設計が必須である。 ## 初回利用フロー -現行フロントの起動フローは次である。 +現行フロントの起動時処理は次の通り。 -1. localStorage.user_code を確認する -2. 存在すれば POST /users/verify に送る -3. 無効なら POST /users で guest ユーザを新規発行する -4. 有効ならそのユーザ情報を採用する +1. `localStorage.user_code` を読む。 +2. 存在する場合は `POST /users/verify` で検証する。 +3. 無効なら `POST /users` で guest ユーザを新規発行する。 +4. 存在しない場合も `POST /users` で guest ユーザを発行する。 +5. API レスポンスは camelCase に変換してフロント型へ流す。 ## IP 紐づけ -POST /users/verify 実行時、サーバは request.remote_ip を 16 バイトのバイナリに変換し、ip_addresses と user_ips に記録する。したがって現行システムは、少なくとも内部的には **引継ぎコードと IP の対応を保持する設計** である。 +`POST /users/verify` 実行時、サーバは `request.remote_ip` を 16 バイトのバイナリへ変換し、`ip_addresses` と `user_ips` に記録する。 -## 注意点 +したがって、現行システムは **引継ぎコードと IP の対応を保持する土台** を持つ。ただし、`users.banned` と `ip_addresses.banned` はまだ認可処理へ強制接続されていない。 -- users.banned と ip_addresses.banned はスキーマ上に存在する -- しかし現スナップショットでは **禁止フラグを認可で強制する実装は見当たらない** -- guest から member / admin への昇格 API や UI も存在しない +## ユーザ設定画面 -つまり、ユーザ管理の土台はあるが、**運用ロジックはまだ薄い**。 +現行 UI の `/users/settings` では次を扱う。 + +- 表示名更新 +- 引継ぎコード表示 +- 引継ぎコード更新 +- 他ブラウザからの引継ぎ + +`settings` テーブルは存在するが、汎用設定の API・反映処理は未接続である。 # ドメインモデル -## 投稿 posts +## 投稿 `posts` -投稿は URL を中心にしたリンクオブジェクトであり、タイトル、サムネイル、元コンテンツの作成日時範囲、親投稿参照などを持つ。 +投稿は URL を中心にしたリンクオブジェクトである。 | 属性 | 概要 | | --- | --- | -| url | 一意な参照先 URL | -| title | 表示用タイトル | -| thumbnail | Active Storage で保持する実サムネイル | -| thumbnail_base | 外部取得元のサムネイル URL | -| parent_id | 親投稿参照 | -| uploaded_user_id | 投稿者。同期投稿時は NULL | -| original_created_from / original_created_before | 元コンテンツ作成日時の範囲 | +| `url` | 参照先 URL。一意。HTTP/HTTPS のみ | +| `title` | 表示用タイトル。NULL 可 | +| `thumbnail` | Active Storage 添付 | +| `thumbnail_base` | 外部サムネイル元 URL。NULL 可 | +| `parent_id` | 親投稿参照。現行では 1 親 | +| `uploaded_user_id` | 投稿者。同期投稿では NULL | +| `original_created_from` | 元コンテンツ作成日時の下限 | +| `original_created_before` | 元コンテンツ作成日時の上限(より前) | +| `created_at` / `updated_at` | 投稿レコードの作成・更新日時 | -### 現行ルール +### URL 正規化 -- URL は HTTP / HTTPS のみ許可 -- URL は保存前に正規化される - - 前後空白の除去 - - ホストの小文字化 - - パス末尾 / の除去 -- url は一意 -- original_created_from と original_created_before は、両方ある場合 from < before が必須 -- 添付サムネイルは 180x180 にリサイズされる +保存前に次を行う。 -## タグ tags と tag_names +- 前後空白の除去 +- HTTP/HTTPS URL であることの検証 +- host の小文字化 +- path 末尾 `/` の除去 -タグは 2 層構造で管理される。 +### オリジナル作成日時 -- tag_names: タグ名文字列そのもの -- tags: カテゴリや件数を持つ実体タグ +`original_created_from` と `original_created_before` が両方ある場合、`from < before` が必須である。 -この分離により、別名タグは tag_names.canonical_id で表現される。 +この仕様は「日時そのもの」ではなく、「その範囲内にリンク先コンテンツが公開された」ことを表す。ニコニコ同期では分単位で `from` と `before = from + 1 minute` を生成する。 -### カテゴリ +## 投稿版 `post_versions` -現行カテゴリは次の 7 種である。 +`post_versions` は投稿のスナップショット履歴である。 -| カテゴリ | 用途 | +| 属性 | 概要 | | --- | --- | -| deerjikist | ニジラー | -| meme | 原作・ネタ元・ミーム等 | -| character | キャラクター | -| general | 一般 | -| material | 素材 | -| meta | メタタグ | -| nico | ニコニコタグ | - -### タグ名と別名のルール - -- canonical_id が NULL の tag_name が実体名 -- canonical_id が非 NULL のものは別名 -- 別名の参照先も別名であってはならない -- 別名名には : を含めてはならない -- タグまたは Wiki を持つ tag_name は別名化できない +| `post_id` | 対象投稿 | +| `version_no` | 投稿ごとの版番号。1 以上 | +| `event_type` | `create` / `update` / `discard` / `restore` | +| `title` | 当該版のタイトル | +| `url` | 当該版の URL | +| `thumbnail_base` | 当該版のサムネイル元 URL | +| `tags` | 当該版のタグ名スナップショット。空白区切り | +| `parent_id` | 当該版の親投稿 | +| `original_created_from` / `original_created_before` | 当該版の元コンテンツ作成日時範囲 | +| `created_at` | 版作成日時 | +| `created_by_user_id` | 版作成ユーザ。bot 操作は NULL | -### タグ名サニタイズ +`PostVersion` は更新・削除不可の読み取り専用履歴として扱われる。`PostVersionRecorder` は投稿をロックし、直前版と同一スナップショットであれば `update` 版を増やさない。 + +### バックフィル仕様 + +`20260409123700_create_post_versions` では、既存の `post_tags` 履歴から投稿版を再構成する。 + +- 投稿ごとに `version_no = 1` の `create` 版を作る。 +- `post_tags.created_at` / `discarded_at` をイベントとして並べる。 +- 1 秒以内のタグ変更を同一バケットとしてまとめる。 +- 初回タグ付与が `posts.created_at + 1 second` 以内なら `create` 版へ統合する。 + +これは過去履歴の近似復元であり、過去の投稿本文変更を完全に再現するものではない。 + +## タグ `tags` とタグ名 `tag_names` + +タグは 2 層構造である。 + +- `tag_names`: 文字列としての名前。別名もここで扱う。 +- `tags`: カテゴリ・件数・実体としてのタグ。 + +`tags.tag_name_id` は一意であり、実体タグは canonical な `tag_names` を参照する。 + +### カテゴリ + +| カテゴリ | 表示名 | 用途 | +| --- | --- | --- | +| `deerjikist` | ニジラー | 投稿者・人物・作者 | +| `meme` | 原作・ネタ元・ミーム等 | 元ネタ、文脈、シリーズ | +| `character` | キャラクター | キャラクタ | +| `general` | 一般 | 通常タグ | +| `material` | 素材 | 素材タグ | +| `meta` | メタタグ | 運用・状態タグ | +| `nico` | ニコニコタグ | 外部ニコニコタグ | -tag_name_sanitisation_rules は、タグ名の禁則・置換ルールを優先度付きで持つ内部テーブルである。 +### 別名 -- source_pattern は正規表現文字列 -- replacement で置換を行う -- TagName のバリデーション時に TagNameSanitisationRule.sanitise が使われる -- TagNameSanitisationRule.apply! は既存タグ名の一括変換と統合も行う +`tag_names.canonical_id` が NULL のものは実体名、非 NULL のものは別名である。 -これは UI に出ていないが、**タグ命名の下位レイヤ** として重要である。 +制約は次の通り。 -### タグ正規化ルール +- 別名の参照先も別名であってはならない。 +- 別名名には `:` を含められない。 +- タグまたは Wiki を持つ `tag_name` は別名化できない。 +- 検索・投稿正規化・補完では canonical 名へ解決する。 +- `/tags/name/:name` は exact match であり、別名解決 API ではない。 + +### タグ名サニタイズ -投稿作成・更新時にタグ文字列は正規化される。 +`tag_name_sanitisation_rules` は、タグ名を正規表現置換する内部ルールである。 -- 既知プレフィクスでカテゴリ判定する - - general: / gen: - - deerjikist: / djk: - - meme: - - character: / chr: - - material: / mtr: - - meta: -- プレフィクス除去後、別名なら canonical 名へ正規化する -- 必要ならタグを自動生成する +- 主キーは `priority`。 +- `source_pattern` は正規表現。 +- `replacement` で置換する。 +- `TagName` バリデーションで `sanitise(name) == name` を要求する。 +- `apply!` は既存 `TagName` を一括変換し、衝突時はタグ統合または `TagName` 差し替えを行う。 + +この機能は UI には出ていない。鯖缶向けの内部運用層である。 ### 自動付与タグ -通常のタグ正規化では次の自動補完がある。 +`Tag.normalise_tags` は投稿作成・更新時に次を行う。 | 条件 | 自動付与 | | --- | --- | -| 正規化後タグ数が 10 未満 | タグ希望 | -| ニジラー系タグが 1 つも無い | ニジラー情報不詳 | +| `with_tagme: true` かつタグ数が 10 未満 | `タグ希望` | +| ニジラー系タグが 1 件もない | `ニジラー情報不詳` | + +投稿作成時は `with_tagme: true`。投稿更新時は `with_tagme: false` なので、更新時には `タグ希望` は自動付与されない。 ### ニコニコタグの扱い -手入力タグに nico: プレフィクスが含まれていた場合、Tag.normalise_tags は NicoTagNormalisationError を投げる。つまり、nico カテゴリは **通常の手入力経路では直接追加させない設計** である。 +通常投稿のタグ入力で `nico:` プレフィクスが含まれた場合、`Tag::NicoTagNormalisationError` が発生し、リクエストは `400 Bad Request` になる。 -## 投稿とタグの関係 post_tags +`nico` カテゴリは手入力で直接追加するものではなく、ニコニコ同期・ニコタグ連携経路で扱う。 -投稿とタグは多対多で結ばれ、関係自体が履歴対象である。 +## 投稿とタグの関係 `post_tags` -- 関係削除は discarded_at による論理削除 -- created_user_id と deleted_user_id を保持 -- 現在有効な (post_id, tag_id) は一意 -- post_count は counter_cache と削除時減算で保たれる +`post_tags` は投稿とタグの多対多関係であり、同時にタグ付与履歴の基礎である。 -post_tags には is_active と active_unique_key の生成列があり、有効レコード重複を DB レベルでも抑えている。 +- `discarded_at` による論理削除。 +- `created_user_id` と `deleted_user_id` を保持。 +- 有効な `(post_id, tag_id)` は一意。 +- `PostTag#discard_by!` は `tags.post_count` を減算する。 +- 物理削除は禁止される。 -## 上位タグ tag_implications +現在の履歴表示の主役は `post_versions` だが、`post_tags` にもタグ単位のイベント履歴が残る。 -タグには親子関係を付与できる。 +## 上位タグ `tag_implications` -- tag_id が子 -- parent_tag_id が親 -- (tag_id, parent_tag_id) は一意 +`tag_implications` は子タグと親タグの関係である。 + +- `tag_id`: 子タグ +- `parent_tag_id`: 親タグ +- `(tag_id, parent_tag_id)` は一意 - 自己参照は禁止 -Tag.expand_parent_tags により、投稿保存時は **親タグが再帰的に自動付与** される。 +投稿保存時には `Tag.expand_parent_tags` により、指定タグの親タグが再帰的に自動付与される。 + +現行 UI では、投稿詳細サイドバーでタグを D&D することにより、上位タグ設定・解除・カテゴリ変更を行える。成功には admin 権限が必要である。 -## ニコニコタグ連携 nico_tag_relations +## ニコタグ連携 `nico_tag_relations` -nico カテゴリのタグと、それ以外の内部タグを関連付けるテーブルである。 +`nico_tag_relations` は外部ニコニコタグと内部タグの橋渡しである。 -- nico_tag_id は nico カテゴリ必須 -- tag_id は nico カテゴリ禁止 +- `nico_tag_id` は `nico` カテゴリ必須。 +- `tag_id` は `nico` カテゴリ禁止。 +- `GET /tags/nico` でニコタグと連携先タグを取得する。 +- `PUT /tags/nico/:id` で連携先タグを更新する。 -これにより、外部ニコタグから内部タグ知識へ橋渡しできる。 +ニコニコ同期では、新しく付いたニコタグに対してだけ、既存の連携先内部タグを自動付与する。 -## ニジラー deerjikists +## ニジラー `deerjikists` -deerjikists は外部人物識別子と内部タグの対応表である。 +`deerjikists` は外部プラットフォーム上の人物識別子と内部タグを対応させる。 -- 主キーは (platform, code) の複合主キー -- platform は現状 nico / youtube -- 対応先タグは deerjikist カテゴリ必須 +- 主キーは `(platform, code)` の複合主キー。 +- `platform` は `nico` / `youtube`。 +- 対応先タグは `deerjikist` カテゴリ必須。 + +ニコニコ同期では、投稿者コードに対応する `deerjikist` があれば、そのタグを自動付与する。存在しない場合、既存内部タグにニジラー系タグが無ければ `ニジラー情報不詳` を付与する。 ## Wiki -Wiki は単なる本文持ちテーブルではなく、**ページ本体と改訂履歴を分離した版管理システム** である。 +Wiki は、ページ本体と改訂履歴を DB 内で持つ版管理システムである。 -### ページ wiki_pages +### `wiki_pages` -- tag_name_id がタイトルに対応 -- 1 タイトルにつき 1 ページ -- created_user_id / updated_user_id を保持 +- `tag_name_id` がタイトルに対応する。 +- 1 タイトルにつき 1 ページ。 +- `created_user_id` / `updated_user_id` を持つ。 +- `next_asset_no` を持つが、現行の画像アップロード UI/API は未接続。 -### 改訂 wiki_revisions +### `wiki_revisions` -- kind は content または redirect -- base_revision_id により改訂チェーンを持つ -- message を保持できる -- lines_count と tree_sha256 を持つ +- `kind` は `content` または `redirect`。 +- `base_revision_id` で改訂の親を持つ。 +- `message` を持てる。 +- `lines_count` と `tree_sha256` を持つ。 +- `redirect` の場合は `redirect_page_id` を持つ。 ### 行ストレージ -- 本文は wiki_revision_lines を介して行単位で保持される -- 各行本体は wiki_lines に sha256 付きで重複排除保存される +- 本文は `wiki_revision_lines` を介して行単位で構成される。 +- 行本文は `wiki_lines` に SHA-256 付きで保存される。 +- 同じ行は重複排除される。 -### 重要な意味 +これは単なる Markdown 本文 1 カラム保存ではない。DB 上に Git 風の行ストアを持つ構造である。 -現行 Wiki は、ページ本文を毎回丸ごと 1 カラムに保存する方式ではない。**改訂と行ストアを分離した Git 風の構造** を DB 上で持っている。 +## Wiki 画像 `wiki_assets` -## 閲覧済フラグ user_post_views +`wiki_assets` テーブルは存在する。 -ユーザ単位で投稿の閲覧済状態を管理する。 +| 属性 | 概要 | +| --- | --- | +| `wiki_page_id` | 対象ページ | +| `no` | ページ内画像番号 | +| `alt_text` | 代替テキスト | +| `sha256` | ファイル重複判定用 SHA-256 | +| `created_by_user_id` | 作成者 | -- 主キーは (user_id, post_id) の複合主キー -- 投稿詳細画面から付与 / 解除できる +ただし、2026-04-19 時点では `WikiAsset` モデル、controller、route、フロント実装が確認できない。したがって **スキーマ準備済み、機能未接続** と扱う。 -## 類似度 post_similarities, tag_similarities +2026-04-20 の開発者ヒアリングでは、Wiki 画像は通常 URL ではなく **専用記法** で扱う方針になった。候補は `` のように、ページ内画像番号または asset 識別子を Markdown の URL 部へ入れる方式である。実装時は、Markdown レンダラ側で `asset:` スキームを検出し、対象ページの `wiki_assets.no` から Active Storage URL へ解決する。 -投稿とタグには事前計算された類似度テーブルがある。 +## 素材 `materials` -- cos はコサイン類似度 -- 各行の主キーは (対象, 相手) の複合主キー -- 計算結果は上位 20 件のみ保持する +`materials` は素材集の実体である。 -## 上映会 theatres, theatre_comments, theatre_watching_users +| 属性 | 概要 | +| --- | --- | +| `url` | 参考 URL。NULL 可 | +| `file` | Active Storage 添付。画像・動画・音声を主に想定 | +| `tag_id` | 対応タグ。現行モデルでは一意 | +| `parent_id` | 親素材 | +| `created_by_user_id` / `updated_by_user_id` | 作成・更新ユーザ | +| `discarded_at` | 論理削除 | +| `active_url` | 未削除時のみ URL を返す生成列。一意制約あり | -Theatre は、共同視聴用の会場を表すドメインである。 +`Material` は `url` または `file` のどちらかを必須とする。タグは `character` または `material` カテゴリを許容する。これは単なる実装の緩さではなく、`character` を「下位区分としての material を包括する代表素材タグ」として扱う設計判断である。したがって、素材タグとしては `material` が基本だが、代表素材・キャラクタ素材の受け皿として `character` も許容する。 -### theatres +`material_versions` テーブルは存在するが、モデル・履歴記録・API・画面は未接続である。 -| 属性 | 概要 | -| --- | --- | -| name | 会場名 | -| opens_at / closes_at | 公開期間 | -| kind | 種別。現スナップショットでは未活用 | -| current_post_id | 現在上映中の投稿 | -| current_post_started_at | 再生開始時刻 | -| next_comment_no | コメント番号の採番用 | -| host_user_id | 現在のホスト | -| created_by_user_id | 作成者 | +## 閲覧済 `user_post_views` -### theatre_comments +ユーザ単位で投稿の閲覧済状態を管理する。 + +- 主キーは `(user_id, post_id)`。 +- 投稿詳細画面でボタンにより付与・解除できる。 +- 自動既読化の設定は将来構想としてあるが、現行は未接続。 + +## 類似度 `post_similarities` / `tag_similarities` + +投稿・タグには類似度の事前計算テーブルがある。 + +- `post_similarities`: 投稿に付いたタグ集合から投稿同士の cosine 類似度を計算。 +- `tag_similarities`: タグに属する投稿集合からタグ同士の cosine 類似度を計算。 +- それぞれ上位 20 件を保存する。 -- 主キーは (theatre_id, no) -- コメント本文と投稿ユーザを持つ -- discarded_at があるが、現行 API では削除機能なし +投稿詳細では `post.related(limit: 20)` により関連投稿を返す。タグ類似度については、テーブル・モデル・タスクはあるが、現行 UI での主導線は薄い。 -### theatre_watching_users +## 上映会 `theatres` + +Theatre は共同視聴用の会場ドメインである。 + +| テーブル | 役割 | +| --- | --- | +| `theatres` | 会場、現在上映中投稿、ホスト、期間 | +| `theatre_comments` | 会場コメント。主キーは `(theatre_id, no)` | +| `theatre_watching_users` | 在席ユーザ。`expires_at` で 30 秒 TTL | -- 主キーは (theatre_id, user_id) -- expires_at で在席期限を持つ -- active スコープは expires_at >= Time.current +現行では会場一覧・作成・編集は存在しない。トップメニューは固定で `/theatres/1` を指す。 # 現行機能仕様 @@ -368,85 +452,115 @@ Theatre は、共同視聴用の会場を表すドメインである。 ### API -GET /posts +`GET /posts` -### 条件 - -現行実装は次の検索パラメータを受け付ける。 +### パラメータ | パラメータ | 意味 | | --- | --- | -| tags | 空白区切りタグ列 | -| match | all または any | -| title | タイトル部分一致 | -| url | URL 部分一致 | -| original_created_from / original_created_to | 元コンテンツ作成日時範囲 | -| created_from / created_to | 投稿作成日時範囲 | -| updated_from / updated_to | 更新日時範囲 | -| page, limit | ページング | -| order | field:direction | +| `tags` | 空白区切りタグ列 | +| `match` | `all` または `any` | +| `title` | タイトル部分一致 | +| `url` | URL 部分一致 | +| `original_created_from` / `original_created_to` | 元コンテンツ作成日時範囲 | +| `created_from` / `created_to` | 投稿作成日時範囲 | +| `updated_from` / `updated_to` | 投稿更新日時範囲 | +| `page` / `limit` | ページング | +| `order` | `field:direction` | + +`order` の field は次の 5 種である。 + +- `title` +- `url` +- `original_created_at` +- `created_at` +- `updated_at` -order の field は次の 5 種である。 +`title` と `url` は direction 未指定時に昇順、それ以外は降順である。 -- title -- url -- original_created_at -- created_at -- updated_at +### タグ検索 -### タグ検索の意味論 +タグ検索は次の意味を持つ。 -- match=all または未指定: AND 的に絞り込む -- match=any: OR 的に結合する -- 各タグリテラルは検索前に TagName.canonicalise で canonical 名へ正規化される -- not: 接頭辞を付けたタグは否定条件になる +| 指定 | 意味 | +| --- | --- | +| `match=all` または未指定 | 各タグを AND 的に絞り込む | +| `match=any` | 各タグ条件を OR 的に結合する | +| `not:タグ名` | 否定条件 | +| 別名タグ | 検索前に canonical 名へ解決される | + +`match=any` では否定条件も OR の一部として扱われる。つまり `A not:B` は「A が付いている、または B が付いていない」に近い挙動になる。ここは直感とずれやすい。 -したがって現行仕様では、**別名吸収付きの AND / OR / NOT 相当** が最低限入っている。2026-03-08 版の「OR / NOT 未実装」は、もう事実ではない。 +### 更新日時 -### 更新日時の扱い +一覧返却の `updated_at` は、単純な `posts.updated_at` ではない。 -投稿一覧の updated_at は単純な posts.updated_at ではない。バックエンドは +サーバは次の大きい方を `updated_at_all` として返し、並び替えにも使う。 -- 投稿本体の updated_at -- post_tags の最新 updated_at +- `posts.updated_at` +- 対象投稿に紐づく `post_tags.updated_at` の最大値 -の大きい方を updated_at_all として計算し、それを返却にも並び替えにも使う。つまり、**タグ変更も投稿更新として扱う** のが現行仕様である。 +タグ変更も投稿更新として扱う設計である。 -### ランダム遷移 +### 返却 -GET /posts/random が存在する。これは tags と match による絞り込み結果からランダム 1 件を返す。 +返却は `{ posts, count }`。各投稿にはタグ・投稿者・サムネイル URL が含まれる。 + +## ランダム投稿 + +### API + +`GET /posts/random` + +`tags` と `match` を受け取り、絞り込み結果から `RAND()` で 1 件返す。 ## 投稿詳細 ### API -GET /posts/:id +`GET /posts/:id` -### 返却内容 +### 返却 - 投稿基本情報 -- タグ木構造 -- 関連投稿(最大 20 件) +- タグツリー +- 関連投稿 最大 20 件 - 閲覧済フラグ -### 埋め込み表示 +タグは `tag_implications` に基づきツリー化される。ループがあっても再帰停止する。 -現行の専用埋め込み対応は次の通り。 +### 画面 -| サイト | 現行条件 | -| --- | --- | -| ニコニコ動画 | nicovideo.jp/watch/... | -| YouTube | youtube.com で v パラメータあり | -| X / Twitter | x.com または twitter.com の /status/:id | -| その他 | ユーザ確認後に iframe | +投稿詳細画面では次が表示される。 + +- 埋め込み表示 +- タグツリー +- 投稿情報サイドバー +- 関連投稿 +- 編集タブ +- 閲覧済トグル +- 履歴リンク -つまり「YouTube 全般」ではない。youtu.be などは現実装上は専用扱いされない。 +投稿詳細サイドバーでは、D&D により上位タグ設定・解除・カテゴリ変更を試みる。API 側で admin 権限が要求されるため、member / guest は失敗する。 + +## 投稿埋め込み + +`PostEmbed` の現行専用対応は次の通り。 + +| サイト | 条件 | 表示 | +| --- | --- | --- | +| ニコニコ動画 | `nicovideo.jp/watch/:id` | `NicoViewer` | +| YouTube | `youtube.com` かつ `v` パラメータあり | `react-youtube` | +| X / Twitter | `x.com` または `twitter.com` の `/status/:id` | `TwitterEmbed` | +| その他 | ユーザ確認後 | `iframe` | + +`youtu.be`、Pixiv、Bluesky、Tiktok、bilibili、ニジカ投稿局などは専用埋め込み未実装である。 ## 新規投稿 ### API -POST /posts +`POST /posts` ### 権限 @@ -454,62 +568,122 @@ member 以上。 ### 入力 -- title -- url -- thumbnail -- tags -- original_created_from -- original_created_before +- `title` +- `url` +- `thumbnail` +- `tags` +- `original_created_from` +- `original_created_before` ### 保存フロー -1. 投稿保存 -2. サムネイル添付と縮小 -3. タグ正規化 -4. 親タグ展開 -5. post_tags 同期 +1. 投稿レコード作成。 +2. サムネイル添付。 +3. タグ名正規化。 +4. 親タグ展開。 +5. `post_tags` 同期。 +6. サムネイルを 180x180 へリサイズ。 +7. `post_versions` に `create` 版を記録。 + +`nico:` タグが含まれる場合は `400 Bad Request`。 ## 投稿編集 ### API -PUT /posts/:id +`PUT /posts/:id` ### 権限 member 以上。 -### 現行仕様 +### 更新対象 + +- `title` +- `tags` +- `original_created_from` +- `original_created_before` + +URL とサムネイルの通常編集は現行 API では扱っていない。 -- 更新対象は title, original_created_from, original_created_before, tags -- 既存の nico カテゴリタグは維持したまま、一般タグ側を再計算する -- 排他制御は未実装 +既存の `nico` カテゴリタグは維持され、一般タグ側だけが再正規化される。その後、親タグ展開と `post_tags` 同期を行い、差分があれば `post_versions` に `update` 版を記録する。 -## 閲覧済フラグ +### 排他制御 -- POST /posts/:id/viewed -- DELETE /posts/:id/viewed +現行 API には投稿編集の排他制御がない。issue では `updated_at` をリクエストに含め、差があれば 409 を返す構想があるが、未実装である。 -認証済ユーザであれば操作可能。 +## 閲覧済 -## 投稿履歴 +| API | 意味 | +| --- | --- | +| `POST /posts/:id/viewed` | 閲覧済にする | +| `DELETE /posts/:id/viewed` | 未閲覧に戻す | + +認証済ユーザなら操作可能である。 + +## 投稿版履歴 ### API -GET /posts/changes +`GET /posts/versions` + +### パラメータ + +| パラメータ | 意味 | +| --- | --- | +| `post` | 投稿 ID で絞り込み | +| `tag` | タグ ID で絞り込み | +| `page` / `limit` | ページング | + +タグで絞り込む場合、現在版または直前版のタグスナップショットに対象タグ名が含まれる版を返す。これにより、タグが追加された版だけでなく、削除された版も拾える。 + +### 返却 + +返却は `{ versions, count }`。 + +各 version は次を含む。 + +- `post_id` +- `version_no` +- `event_type` +- `title.current` / `title.prev` +- `url.current` / `url.prev` +- `thumbnail.current` / `thumbnail.prev` +- `thumbnail_base.current` / `thumbnail_base.prev` +- `tags[]`: `context` / `added` / `removed` +- `original_created_from.current` / `.prev` +- `original_created_before.current` / `.prev` +- `created_at` +- `created_by_user` -### 条件 +現行では `thumbnail.current` / `prev` は常に NULL で、サムネイル表示は `thumbnail_base` に依存する。 -- id: 投稿 ID で絞り込み -- tag: タグ ID で絞り込み -- page, limit +### 画面 -### 意味 +フロントの `/posts/changes` は、内部的には `GET /posts/versions` を呼ぶ。つまり画面名は「耕作履歴」だが、主役データはタグイベントではなく投稿版スナップショットである。 + +### 復元 + +履歴画面の「復元」は専用の restore API ではない。選択版の `title`、`tags`、`original_created_from`、`original_created_before` を `PUT /posts/:id` へ送る。 + +そのため、以下の制限がある。 + +- `url` は復元されない。 +- `thumbnail_base` は現時点では復元されない。将来的に入力欄を追加した段階で復元対象へ含める方針である。 +- `event_type = restore` の版は現行フローでは作られない。 +- `nico:` タグは復元送信時に除外される。 + +2026-04-20 の開発者ヒアリングでは、`thumbnail_base` は将来的に復元対象へ含める方針とされた。ただし現行フロントの編集入力欄に出てこない項目であるため、現段階では復元しない。これは「履歴として保持しない」のではなく、「編集 UI が責任を持てる項目だけ戻す」という暫定仕様である。 + +## 旧タグ変更履歴 + +### API -返すのは投稿本体の変更履歴ではなく、**タグ付与関係の履歴** である。 +`GET /posts/changes` -- 追加は change_type = add -- 削除は change_type = remove +この API は `post_tags` から `add` / `remove` のイベント列を生成する。現行画面の主導線ではなくなっているが、API としては残っている。 + +`/posts/versions` と `/posts/changes` は意味が違う。2026-04-20 の開発者ヒアリングでは、`/posts/changes` は将来的に廃止する可能性が高いと整理された。現時点では後方互換上残っている旧 API として扱う。 # タグ機能 @@ -517,92 +691,125 @@ GET /posts/changes ### API -GET /tags +`GET /tags` -### 条件 +### パラメータ | パラメータ | 意味 | | --- | --- | -| post | 特定投稿に付いたタグだけに絞る | -| name | 名前部分一致 | -| category | カテゴリ一致 | -| post_count_gte / post_count_lte | 投稿件数範囲 | -| created_from / created_to | 作成日時範囲 | -| updated_from / updated_to | 更新日時範囲 | -| page, limit | ページング | -| order | 並び順 | - -order の field は name, category, post_count, created_at, updated_at に対応する。カテゴリ並びは DB 上で独自順序化されている。 - -### フロント画面 - -/tags 画面は現時点で実装済みであり、名前・カテゴリ・件数・日時の詳細条件と並び替え UI を持つ。 +| `post` | 特定投稿に付いたタグだけへ絞る | +| `name` | 名前部分一致 | +| `category` | カテゴリ一致 | +| `post_count_gte` / `post_count_lte` | 投稿件数範囲 | +| `created_from` / `created_to` | 作成日時範囲 | +| `updated_from` / `updated_to` | 更新日時範囲 | +| `page` / `limit` | ページング | +| `order` | `field:direction` | + +`order` の field は次の 5 種である。 + +- `name` +- `category` +- `post_count` +- `created_at` +- `updated_at` + +カテゴリ並びは独自順序である。 + +1. `deerjikist` +2. `meme` +3. `character` +4. `general` +5. `material` +6. `meta` +7. `nico` ## タグ補完 ### API -GET /tags/autocomplete +`GET /tags/autocomplete` ### パラメータ | パラメータ | デフォルト | 意味 | | --- | --- | --- | -| q | なし | 検索文字列 | -| nico | true | nico: 接頭辞側候補を含める | -| present | true | post_count > 0 のタグに絞る | +| `q` | なし | 検索文字列 | +| `nico` | `true` | `nico:` 側候補を含める | +| `present` | `true` | `post_count > 0` のタグに絞る | -### 現行挙動 +### 挙動 -- q 先頭の not: は除去してから補完する -- canonical 名の前方一致に加えて、別名前方一致も拾う -- 別名ヒット時は canonical 側タグを返し、matched_alias を付ける -- 返却順は post_count DESC, tag_names.name -- 最大 20 件 - -2026-03-08 版にあった present_only というパラメータ名は現行実装と一致しない。**現在のパラメータ名は present** である。 +- `q` 先頭の `not:` は除去して補完する。 +- canonical 名の前方一致を拾う。 +- 別名の前方一致も拾い、canonical 側のタグを返す。 +- 別名ヒット時は `matched_alias` を付ける。 +- 並びは `post_count DESC, tag_names.name`。 +- 最大 20 件。 ## タグ詳細 -- GET /tags/:id -- GET /tags/name/:name +| API | 意味 | +| --- | --- | +| `GET /tags/:id` | ID でタグ取得 | +| `GET /tags/name/:name` | 名前完全一致でタグ取得 | +| `GET /tags/:id/deerjikists` | タグに紐づくニジラー一覧 | +| `GET /tags/name/:name/deerjikists` | 名前指定版 | +| `GET /tags/name/:name/materials` | 素材ツリー取得 | + +`/tags/name/:name` は exact match であり、別名解決をしない。 + +## タグ階層取得 + +### API + +`GET /tags/with-depth` + +### パラメータ + +| パラメータ | 意味 | +| --- | --- | +| `parent` | 親タグ ID。未指定ならルートタグを返す | -/tags/name/:name は exact match であり、別名から canonical タグへ自動解決する API ではない。検索と補完は canonical 化するが、**詳細取得 API は exact name** である点に注意が要る。 +### 返却 + +対象カテゴリは `meme` / `character` / `material`。各タグに `has_children` と `children: []` を付けて返す。 + +現行では素材サイドバーで使用される。`meme` は子を持つものだけ表示される。 ## タグ更新 ### API -PUT /tags/:id +`PUT /tags/:id` ### 権限 member 以上。 -### 現行でできること +### 更新対象 -- タグ名変更 -- カテゴリ変更 +- `name` +- `category` -### 現行でできないこと +タグ名変更は `tag.tag_name.update!`、カテゴリ変更は `tag.update!` で行われる。 -- 別名タグ作成 UI / API -- tag sanitisation rule の管理 UI / API -- タグ統合 UI / API +## 上位タグ管理 -内部実装はかなり進んでいるが、表面 API はまだ薄い。 +| API | 意味 | 権限 | +| --- | --- | --- | +| `POST /tags/:parent_id/children/:child_id` | child を parent の子にする | admin | +| `DELETE /tags/:parent_id/children/:child_id` | child を parent の子から外す | admin | -## 上位タグ管理 +現行フロントでは、投稿詳細サイドバーの D&D 操作からこれらを叩く。 -- POST /tags/:parent_id/children/:child_id -- DELETE /tags/:parent_id/children/:child_id +ただし、専用の「上位タグ設定画面」は未実装である。 -権限は admin。管理 UI は現スナップショットに存在しない。 +## 別名タグ管理 -## タグからニジラー参照 +モデル・検索・補完・正規化の土台はあるが、別名の作成・解除・一覧管理 UI/API は未実装である。 -- GET /tags/:id/deerjikists -- GET /tags/name/:name/deerjikists +issue では「管理者用別名タグ作成画面」が残っている。公開前にここはかなり重要である。 # ニコニコ連携 @@ -610,134 +817,114 @@ member 以上。 ### API -GET /tags/nico +`GET /tags/nico` -### 現行仕様 +### 挙動 -- updated_at DESC で取得 -- cursor に ISO8601 時刻を渡すカーソルページング -- limit デフォルトは 20 -- 各ニコタグに対して linked_tags を返す +- `Tag.nico_tags` を `updated_at DESC` で取得する。 +- `cursor` に ISO8601 時刻を渡すと、その時刻より古いものを取得する。 +- `limit` デフォルトは 20。 +- 各ニコタグに `linked_tags` を付与して返す。 ## ニコタグ更新 ### API -PUT /tags/nico/:id +`PUT /tags/nico/:id` ### 権限 member 以上。 -### 現行仕様 +### 挙動 -- 入力 tags を内部タグ列として再正規化する -- 連携先に nico カテゴリタグは指定不可 +- 対象タグは `nico` カテゴリ必須。 +- 入力 `tags` を通常タグとして再正規化する。 +- 連携先に `nico` カテゴリは指定不可。 +- 成功時、連携先タグ配列を返す。 -## 日次同期バッチ nico:sync +## 日次同期 `nico:sync` -現行タスクは、外部ニコニコ DB から動画情報を取り込み、投稿・タグ体系へ反映する。 +現行タスクは外部 Python スクリプト `get_videos.py` を呼び、ニコニコ動画情報を取り込む。 ### 主な流れ -1. 外部 Python スクリプト get_videos.py を実行 -2. ニコニコ動画 URL 既存投稿を探し、あれば更新、なければ新規作成 -3. タイトル・投稿日・サムネイルを反映 -4. 外部タグを nico: 接頭辞付き内部タグへ変換 -5. 新規に付いたニコタグについてだけ、連携済内部タグを自動付与 -6. ニジラー対応が見つからなければ ニジラー情報不詳 を補う -7. 内部タグ構成が変化した場合は bot操作 を付与 - -新規投稿時に初期付与されるメタタグは次である。 - -- タグ希望 -- bot操作 -- ニコニコ -- 動画 - -これは単なるミラーではなく、**外部タグを内部知識へ変換する ETL** と見た方が正確である。 +1. `MYSQL_USER`、`MYSQL_PASS`、`NIZIKA_NICO_PATH` を環境変数から読む。 +2. `python3 #{NIZIKA_NICO_PATH}/get_videos.py` を実行する。 +3. 既存投稿は `nicovideo.jp/watch/:code` の正規表現で検索する。 +4. タイトル、元投稿日、サムネイルを更新する。 +5. 新規投稿なら `タグ希望`、`bot操作`、`ニコニコ`、`動画` を初期付与する。 +6. 外部タグを `nico:` 付き内部タグへ変換する。 +7. 新しく追加されたニコタグについてだけ、連携済内部タグを展開する。 +8. deerjikist 対応があればニジラータグを付与する。 +9. 必要に応じて `bot操作` を追加する。 +10. 作成・変更があれば `PostVersionRecorder` で投稿版を記録する。 -# ニジラー管理 +これは単なるミラーではなく、外部タグを内部知識へ変換する ETL である。 -## API - -- GET /deerjikists/:platform/:code -- PUT /deerjikists/:platform/:code -- DELETE /deerjikists/:platform/:code - -## 権限 - -更新・削除は member 以上。 - -## 制約 - -- platform は現状 nico / youtube -- 紐づけ先タグは deerjikist カテゴリ必須 - -# Wiki +# Wiki 機能 ## 一覧・検索 -### API - -- GET /wiki -- GET /wiki/search - -### 現行挙動 +| API | 意味 | +| --- | --- | +| `GET /wiki` | Wiki 一覧またはタイトル検索 | +| `GET /wiki/search` | `index` への委譲 | -- title 未指定なら全件 -- title 指定時は tag_names.name LIKE %title% で上位 20 件 +`title` 未指定なら全件、指定時は `tag_names.name LIKE %title%` で最大 20 件を返す。 -フロントの /wiki 画面には「内容」入力欄もあるが、バックエンドはそれを受け取っていない。**現行の Wiki 検索はタイトル部分一致のみ** である。 +フロントの `/wiki` は検索画面であり、本文検索欄に見える UI があっても、バックエンドは本文を検索していない。現行の Wiki 検索はタイトル検索のみである。 ## 詳細表示 -### API - -- GET /wiki/:id -- GET /wiki/title/:title +| API | 意味 | +| --- | --- | +| `GET /wiki/:id` | ID 指定 | +| `GET /wiki/title/:title` | タイトル完全一致 | -### パラメータ +`version` パラメータがある場合は指定改訂を表示する。なければ最新改訂を表示する。 -- version: 指定時はその改訂を表示 +返却には次が含まれる。 -### 返却内容 +- `id` +- `title` +- `body` +- `revision_id` +- `pred` +- `succ` +- `updated_at` -- id, title -- body -- revision_id -- pred, succ -- updated_at +## リダイレクト -### リダイレクト +改訂が `redirect` の場合、サーバはリダイレクト先タイトルへ `301 Moved Permanently` で遷移させる。 -改訂 kind = redirect の場合、サーバは 301 Moved Permanently でリダイレクト先タイトルへ飛ばす。 +ただし現行 UI にはリダイレクト作成・編集導線がない。 ## 存在確認 -- GET /wiki/:id/exists -- GET /wiki/title/:title/exists - -存在すれば 204 No Content、無ければ 404。 +| API | 成功時 | 失敗時 | +| --- | --- | --- | +| `GET /wiki/:id/exists` | 204 | 404 | +| `GET /wiki/title/:title/exists` | 204 | 404 | ## 差分表示 ### API -GET /wiki/:id/diff?from=...&to=... +`GET /wiki/:id/diff?from=...&to=...` -### 現行仕様 +### 挙動 -- from 省略時は空本文相当との比較 -- to 省略時は現行改訂との比較 -- content 改訂同士のみ比較可能 -- 結果は context, added, removed の列として返す +- `from` 省略時は空本文相当。 +- `to` 省略時は現行改訂。 +- `content` 改訂同士のみ比較可。 +- 結果は `context` / `added` / `removed` の列で返す。 ## 作成 ### API -POST /wiki +`POST /wiki` ### 権限 @@ -745,99 +932,140 @@ member 以上。 ### 入力 -- title -- body -- message(任意) +- `title` +- `body` +- `message` 任意 -### 保存 - -- 対応する tag_name を作成または再利用 -- Wiki::Commit.content! により初回改訂を生成 +タイトルに対応する `TagName` を作成または再利用し、`Wiki::Commit.content!` で初回改訂を作る。 ## 更新 ### API -PUT /wiki/:id +`PUT /wiki/:id` ### 権限 member 以上。 -### 現行仕様 +### 挙動 -- タイトル変更は受け付けない -- body と message を受け取れる -- 実際のコミットは Wiki::Commit.content! を通る +- `title` と `body` は必須。 +- タイトル変更は受け付けない。変更されていれば 422。 +- `message` は受け取れる。 +- `Wiki::Commit.content!` で改訂を作る。 ### 競合制御の実態 -Wiki::Commit 自体は base_revision_id による競合検出を備えている。しかし WikiPagesController#update はクライアント送信値を使わず、**サーバ側で今の current_revision.id を読み直してそれをベースにしている**。つまり、コード上は衝突検出器があるが、現行 API とフロントはそれを活かし切っていない。 +`Wiki::Commit` は `base_revision_id` による競合検出を持つ。しかし `WikiPagesController#update` はクライアントから受けた基底版ではなく、更新直前にサーバ側で `page.current_revision.id` を読み直して使う。 + +つまり、衝突検出器はあるが、現行 API はクライアント起点の競合検出を十分に活かしていない。 ## 履歴 ### API -GET /wiki/changes +`GET /wiki/changes` -### 条件 +### パラメータ -- id を渡すと特定ページ履歴だけ返す -- 未指定なら全体履歴 +| パラメータ | 意味 | +| --- | --- | +| `id` | Wiki ページ ID。未指定なら全体履歴 | -### 現行仕様 +最大 200 件の `WikiRevision` を返す。`pred` は返るが、`succ` は常に NULL である。 -- WikiRevision 単位の履歴を最大 200 件返す -- pred は返るが succ は常に nil +# 素材集 -## フロント実装の注意点 +## 位置づけ -- 新規・編集画面は Markdown エディタを使う -- message 入力欄はフロントにまだ出ていない -- リダイレクト編集 UI も無い -- タイトル入力欄はあるが、更新時は変更するとバックエンドが 422 を返す +素材集は、タグに紐づく画像・動画・音声・参考 URL を管理する機能である。 -# 設定・ユーザ管理 +現行ではルートと画面は存在するが、トップナビでは `素材` メニューが `visible: false` になっている。つまり機能は実装され始めているが、正式導線としてはまだ隠し気味である。 -## 現行 UI でできること +## 画面 -/users/settings 画面でできるのは次である。 +| パス | 概要 | +| --- | --- | +| `/materials` | 素材一覧/タグ別素材ツリー | +| `/materials/new` | 素材追加 | +| `/materials/:id` | 素材詳細・編集 | +| `/materials/search` | import はあるがルートはコメントアウト | -- 表示名更新 -- 引継ぎコード表示 -- 他ブラウザからの引継ぎ +素材一覧は左サイドバーのタグ階層から対象タグを選び、対応する素材と子タグの素材を表示する。 ## API -- POST /users -- POST /users/verify -- GET /users/me -- POST /users/code/renew -- PUT /users/:id +| API | 権限 | 意味 | +| --- | --- | --- | +| `GET /materials` | 認証不要 | 素材一覧 | +| `GET /materials/:id` | 認証不要 | 素材詳細 | +| `POST /materials` | 認証済み、guest 可 | 素材作成 | +| `PUT /materials/:id` | member 以上 | 素材更新 | +| `DELETE /materials/:id` | member 以上 | 論理削除 | -## 注意点 +## 一覧 + +`GET /materials` は次を受け付ける。 + +| パラメータ | 意味 | +| --- | --- | +| `page` / `limit` | ページング | +| `tag_id` | タグで絞り込み | +| `parent_id` | 親素材で絞り込み | + +作成日時降順、ID 降順で返す。 + +## 詳細 + +`GET /materials/:id` は素材情報に加え、対応タグの Wiki 本文を `wiki_page_body` として返す。 + +## 作成・更新 + +### 作成 + +`POST /materials` は認証済みなら可能で、member 権限を要求しない。BTRC Hub では初回利用時に guest ユーザが自動発行されるため、公開時も guest に素材作成を許可する仕様とする。 + +入力は次の通り。 + +- `tag` +- `file` +- `url` + +`tag` は必須。`file` または `url` のどちらかは必須。対象タグがなければ作成し、カテゴリは `material` になる。 -- settings テーブルは存在する -- しかし **汎用ユーザ設定 API も反映ロジックも現行スナップショットでは未接続** -- フロントには /users や /users/:id への隠しメニュー項目があるが、対応ルートは未実装 +### 更新 + +`PUT /materials/:id` は member 以上。 + +`tag` と、`file` または `url` が必須。`file` が無い状態で更新すると既存添付ファイルは purge される。 + +## 現行の弱点 + +- `material_versions` は未接続。 +- 素材追加は認証済み guest でも可能であり、公開時には再検討が必要。 +- ファイル種別の厳密なサーバ側制限は薄い。 +- タグ一意のため、1 タグに複数素材を持つ設計ではない。 # プレビュー取得 ## API -- GET /preview/title -- GET /preview/thumbnail - -## 権限 +| API | 意味 | +| --- | --- | +| `GET /preview/title` | 対象 URL の `