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 b46556a..d11f2e8 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: 'ソースコード・Wiki・Issue スナップショットから再構成した仕様' -date: '2026-03-08' +subtitle: '最新ソーススナップショット(btrc-hub-feature_297.zip)ベース' +date: '2026-03-22' lang: ja-JP toc: true toc-depth: 2 @@ -24,850 +24,1125 @@ header-includes: # 本書の位置づけ -本書は、以下の情報源を突き合わせて **2026-03-08 時点の BTRC Hub の現行仕様** を再構成したものである。 +本書は、**最新ソーススナップショット btrc-hub-feature_297.zip** を起点に、BTRC Hub の現行仕様を再構成したものである。主に参照したのは次の 4 系統である。 1. バックエンド実装(Rails 8 API) 2. フロントエンド実装(React + Vite) -3. 開発 Wiki -4. Gitea issue 一覧 +3. db/schema.rb +4. 既存仕様書 BTRC_Hub_現行仕様書_2026-03-08.md -本書では、情報の優先順位を次の通りに置く。 +情報の優先順位は明確である。 -- **第 1 優先**: 現在のソースコードと `db/schema.rb「 -- **第 2 優先**: Wiki のテーブル定義・方針文書 -- **第 3 優先**: issue の記述 +- **第 1 優先**: 現在のソースコードと db/schema.rb +- **第 2 優先**: フロントエンドの画面実装とルーティング +- **第 3 優先**: 既存仕様書の文脈整理 -理由は単純で、Wiki と issue は過去の設計意図や予定を含み、現実装とズレている箇所があるためである。よって、本書は「夢想」ではなく、**現に動いている仕様を基準にした仕様書** として読むこと。 +したがって本書は、「こうしたい」ではなく **現にコードがそうなっているか** を基準に書いている。過去の設計メモや backlog は、現行仕様の証拠には使っていない。 # システム概要 ## 目的 -BTRC Hub は、ぼざろクリーチャーシリーズ関連コンテンツへのリンクを集約し、それらにタグを付与して横断的に検索・整理できるようにするリンク集システムである。対象はコンテンツ本体ではなく **リンク** であり、Danbooru 的なタグ付け・Wiki・関連付けの思想を継承しつつ、SNS 的な会話機能は極力持たせない方針である。 +BTRC Hub は、ぼざろクリーチャーシリーズ関連コンテンツへのリンクを集約し、タグ・Wiki・関連付けによって横断的に検索・整理するためのシステムである。対象はコンテンツ本体ではなく **リンク** であり、知識を共同編集しやすくすることが主眼にある。 -主目的は次の 3 点である。 +現行ソースから読み取れる中心目的は次の 4 点である。 -- ニコニコ側のタグ数制限を補完すること - 複数プラットフォーム上の関連コンテンツを横断管理すること -- タグ、Wiki、関連付けを通じて共同で知識を蓄積すること +- タグ付けによって検索性と再発見性を上げること +- Wiki によってタグ単位の知識を蓄積すること +- ニコニコ外部情報を内部タグ体系へ取り込むこと ## 非目的 -現時点の方針上、次は中心機能ではない。 +現時点のコードベースは、次を中核機能とはしていない。 -- コメント欄や掲示板のような強い SNS 機能 -- コンテンツ本体の保存・転載 - 汎用 SNS 化 +- 本文コメント中心の会話サービス化 +- コンテンツ本体の保存・転載 +- 多人数配信基盤そのものの実装 + +ただし後述のとおり、**上映会(Theatre)機能** はすでにソースへ入っており、完全な SNS ではないが共同視聴寄りの要素は持ち始めている。 ## 技術構成 | 層 | 現行構成 | | --- | --- | -| バックエンド | Rails 8.0 API, MySQL 8, Active Storage | -| フロントエンド | React 19, Vite 6, TypeScript 5, TanStack Query 5 | -| UI | Tailwind CSS, Framer Motion | -| 補助ライブラリ | Nokogiri, MiniMagick, Discard, diff-lcs | -| バッチ | Rake task による同期・類似度計算 | +| バックエンド | 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 本柱である。 -- 投稿一覧 」/posts「 -- 投稿詳細 」/posts/:id「 -- 投稿履歴 」/posts/changes「 -- 新規投稿 」/posts/new「 -- ニコニコ連携 」/tags/nico「 -- Wiki 一覧・検索 」/wiki「 -- Wiki 詳細 」/wiki/:title「 -- Wiki 新規作成 」/wiki/new「 -- Wiki 編集 」/wiki/:id/edit「 -- Wiki 差分 」/wiki/:id/diff「 -- Wiki 変更履歴 」/wiki/changes「 -- 設定 」/users/settings「 +1. **投稿**: URL を中心としたリンクオブジェクト +2. **タグ**: カテゴリ・別名・上位タグ・外部連携を持つ知識単位 +3. **Wiki**: 行単位ストレージと改訂履歴を持つページ体系 +4. **上映会**: 単一会場内での共同視聴とコメント機能 -# 仕様整理の結論 +2026-03-08 版から見て特に重要な更新点は次である。 -まず結論を先に書く。 +- 投稿検索は **タイトル / URL / 各種日時 / 並び順** に対応した +- タグ検索画面が実装され、**詳細条件と並び替え** が入った +- タグ検索は **not: 接頭辞** による否定検索を内部的に扱う +- Theatre 系の **API と画面** が追加された +- tag_name_sanitisation_rules による **タグ名サニタイズ層** が入った +- 旧仕様書で「未実装」としていた項目の一部が、現在は実装済になっている -1. **BTRC Hub の核は「投稿」「タグ」「Wiki」の 3 本柱** である。 -2. タグは単なる文字列ではなく、**カテゴリ、別名、上位タグ、ニコタグ連携、ニジラー連携** を持つ。 -3. Wiki は 1 タイトル 1 ページではあるが、内部的には **改訂履歴を持つバージョン管理対象** である。 -4. 検索は現時点では主に **タグ AND 検索 / ANY 検索 / 補完検索** であり、OR / NOT は未実装である。 -5. いくつかのテーブルやカラムは存在するが、**UI も API もまだ追いついていない箇所がある**。この点を曖昧にすると仕様書として腐る。 - -# 利用者と認可 +# 利用者と認証 ## ユーザモデル -」users.role「 は文字列 enum であり、現行のロールは次の 3 種。 +users.role は文字列 enum であり、現行ロールは次の 3 種である。 | ロール | 意味 | | --- | --- | -| 」guest「 | 閲覧中心。編集系は不可 | -| 」member「 | 投稿・Wiki・タグ更新など通常の編集可 | -| 」admin「 | member 権限に加え、管理系操作可 | +| guest | 閲覧中心。編集系は不可 | +| member | 投稿・Wiki・タグ更新など通常の編集可 | +| admin | member 権限に加え、管理系操作可 | ## 認証方式 -認証は一般的なログイン画面ではなく、**引継ぎコード** による軽量認証である。 +認証は ID / パスワードではなく、**引継ぎコード** による軽量認証である。 + +- クライアントは localStorage.user_code を保持する +- API 呼び出し時は X-Transfer-Code ヘッダで送信する +- サーバは users.inheritance_code と照合して current_user を決定する -- クライアントは 」localStorage「 に 」user_code「 を保存する。 -- API 呼び出し時は 」X-Transfer-Code「 ヘッダで送信する。 -- サーバは 」users.inheritance_code「 と照合して 」current_user「 を決定する。 +ApplicationController の認証処理は非常に薄く、**ヘッダからコードを読むだけ** である。禁止ユーザや禁止 IP の強制チェックはここでは行っていない。 ## 初回利用フロー -1. フロント起動時に 」localStorage.user_code「 を確認する。 -2. あれば 」/users/verify「 に送る。 -3. 無効なら 」/users「 で guest ユーザを新規発行する。 -4. 有効ならそのユーザを継続利用する。 +現行フロントの起動フローは次である。 + +1. localStorage.user_code を確認する +2. 存在すれば POST /users/verify に送る +3. 無効なら POST /users で guest ユーザを新規発行する +4. 有効ならそのユーザ情報を採用する ## IP 紐づけ -」/users/verify「 実行時、リクエスト元 IP は 」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 も存在しない -ここは「項目はあるが、運用ロジックは未実装または未接続」という理解が正しい。 +つまり、ユーザ管理の土台はあるが、**運用ロジックはまだ薄い**。 # ドメインモデル -## 投稿 」posts「 - -投稿は「URL を中心にしたリンクオブジェクト」であり、タイトル、サムネイル、元コンテンツの作成日時範囲などを持つ。 +## 投稿 posts -主要属性は以下。 +投稿は URL を中心にしたリンクオブジェクトであり、タイトル、サムネイル、元コンテンツの作成日時範囲、親投稿参照などを持つ。 | 属性 | 概要 | | --- | --- | -| 」url「 | 一意な参照先 URL | -| 」title「 | 表示用タイトル | -| 」thumbnail「 | Active Storage で保持する実サムネイル | -| 」thumbnail_base「 | 外部取得元のサムネイル URL | -| 」parent_id「 | 親投稿参照。現時点では本格機能化前 | -| 」uploaded_user_id「 | 投稿者。自動投稿時は NULL | -| 」original_created_from` / `original_created_before「 | 元コンテンツ作成日時の範囲 | - -### 投稿に関する現行ルール - -- URL は HTTP / HTTPS のみ許可。 -- URL は正規化される。 - - ホストは小文字化 - - 末尾スラッシュは削除 -- 」url「 は一意。 -- 」original_created_from「 と 」original_created_before「 は、両方ある場合 」from < before「 が必須。 -- サムネイル添付時は 180x180 にリサイズされる。 - -## タグ 」tags` + `tag_names「 +| url | 一意な参照先 URL | +| title | 表示用タイトル | +| thumbnail | Active Storage で保持する実サムネイル | +| thumbnail_base | 外部取得元のサムネイル URL | +| parent_id | 親投稿参照 | +| uploaded_user_id | 投稿者。同期投稿時は NULL | +| original_created_from / original_created_before | 元コンテンツ作成日時の範囲 | + +### 現行ルール + +- URL は HTTP / HTTPS のみ許可 +- URL は保存前に正規化される + - 前後空白の除去 + - ホストの小文字化 + - パス末尾 / の除去 +- url は一意 +- original_created_from と original_created_before は、両方ある場合 from < before が必須 +- 添付サムネイルは 180x180 にリサイズされる + +## タグ tags と tag_names タグは 2 層構造で管理される。 -- 」tag_names「: 文字列名そのもの -- 」tags「: 実体タグ。カテゴリや件数を持つ +- tag_names: タグ名文字列そのもの +- tags: カテゴリや件数を持つ実体タグ -この分離によって、**別名タグ** を 」tag_names.canonical_id「 で実装している。 +この分離により、別名タグは tag_names.canonical_id で表現される。 ### カテゴリ -現行カテゴリは次の 7 種。 +現行カテゴリは次の 7 種である。 | カテゴリ | 用途 | | --- | --- | -| 」deerjikist「 | ニジラー | -| 」meme「 | 元ネタ | -| 」character「 | キャラクタ | -| 」general「 | 一般 | -| 」material「 | 素材 | -| 」nico「 | ニコニコタグ | -| 」meta「 | メタタグ | - -### 別名タグのルール - -- 」tag_names.canonical_id「 が NULL のものが実体名。 -- NULL でないものは別名。 -- 別名はさらに別名を指してはならない。 -- 別名にはプレフィクス 」:「 を含めない。 -- 」tag「 や 」wiki_page「 を持つ 」tag_name「 は別名化できない。 +| deerjikist | ニジラー | +| meme | 原作・ネタ元・ミーム等 | +| character | キャラクター | +| general | 一般 | +| material | 素材 | +| meta | メタタグ | +| nico | ニコニコタグ | + +### タグ名と別名のルール + +- canonical_id が NULL の tag_name が実体名 +- canonical_id が非 NULL のものは別名 +- 別名の参照先も別名であってはならない +- 別名名には : を含めてはならない +- タグまたは Wiki を持つ tag_name は別名化できない + +### タグ名サニタイズ + +tag_name_sanitisation_rules は、タグ名の禁則・置換ルールを優先度付きで持つ内部テーブルである。 + +- source_pattern は正規表現文字列 +- replacement で置換を行う +- TagName のバリデーション時に TagNameSanitisationRule.sanitise が使われる +- TagNameSanitisationRule.apply! は既存タグ名の一括変換と統合も行う + +これは UI に出ていないが、**タグ命名の下位レイヤ** として重要である。 ### タグ正規化ルール 投稿作成・更新時にタグ文字列は正規化される。 -- 既知プレフィクスでカテゴリ判定する。 - - 例: 」general:`, `gen:`, `deerjikist:`, `djk:`, `character:`, `chr:`, `material:`, `mtr:`, `meta:「 -- プレフィクス除去後、別名なら canonical 名へ正規化する。 -- 必要ならタグを自動生成する。 +- 既知プレフィクスでカテゴリ判定する + - general: / gen: + - deerjikist: / djk: + - meme: + - character: / chr: + - material: / mtr: + - meta: +- プレフィクス除去後、別名なら canonical 名へ正規化する +- 必要ならタグを自動生成する ### 自動付与タグ -現行コード上、通常投稿のタグ処理には次の自動補完がある。 +通常のタグ正規化では次の自動補完がある。 | 条件 | 自動付与 | | --- | --- | -| 新規投稿でタグ数が 10 未満、かつ未付与 | 」タグ希望「 | -| ニジラー系タグが 1 つも無い | 」ニジラー情報不詳「 | +| 正規化後タグ数が 10 未満 | タグ希望 | +| ニジラー系タグが 1 つも無い | ニジラー情報不詳 | ### ニコニコタグの扱い -」nico:「 プレフィクス付きタグは一般投稿入力から直接追加させない。 +手入力タグに nico: プレフィクスが含まれていた場合、Tag.normalise_tags は NicoTagNormalisationError を投げる。つまり、nico カテゴリは **通常の手入力経路では直接追加させない設計** である。 -つまり、**ニコタグは通常の手入力タグとは別レーン** である。現在の設計思想は、ニコタグを別カテゴリ・別管理対象として扱うことにある。 +## 投稿とタグの関係 post_tags -## 投稿とタグの関係 」post_tags「 +投稿とタグは多対多で結ばれ、関係自体が履歴対象である。 -投稿とタグは多対多。リレーションは **論理削除** される。 +- 関係削除は discarded_at による論理削除 +- created_user_id と deleted_user_id を保持 +- 現在有効な (post_id, tag_id) は一意 +- post_count は counter_cache と削除時減算で保たれる -- 削除は 」discarded_at「 に記録する。 -- 作成者 」created_user_id「、削除者 」deleted_user_id「 を持つ。 -- 現在有効な 」(post_id, tag_id)「 は一意。 -- 論理削除時、」tags.post_count「 を減算する。 +post_tags には is_active と active_unique_key の生成列があり、有効レコード重複を DB レベルでも抑えている。 -このため、現行システムでは「今そのタグが付いているか」だけでなく、**タグ変更履歴** も追跡可能である。 - -## 上位タグ 」tag_implications「 +## 上位タグ tag_implications タグには親子関係を付与できる。 -- 」tag_id「 が子 -- 」parent_tag_id「 が親 +- tag_id が子 +- parent_tag_id が親 +- (tag_id, parent_tag_id) は一意 - 自己参照は禁止 -投稿保存時には 」Tag.expand_parent_tags「 が走り、**親タグが再帰的に自動付与** される。 +Tag.expand_parent_tags により、投稿保存時は **親タグが再帰的に自動付与** される。 -## ニコニコタグ連携 」nico_tag_relations` +## ニコニコタグ連携 nico_tag_relations -`nico「 カテゴリのタグと、それ以外の内部タグを関連付けるテーブル。 +nico カテゴリのタグと、それ以外の内部タグを関連付けるテーブルである。 -- 」nico_tag_id「 は 」nico「 カテゴリ必須 -- 」tag_id「 は 」nico「 カテゴリ不可 +- nico_tag_id は nico カテゴリ必須 +- tag_id は nico カテゴリ禁止 -ニコニコ同期時、新しく見つかった外部タグに対応する内部タグを自動付与するための基盤である。 +これにより、外部ニコタグから内部タグ知識へ橋渡しできる。 -## ニジラー 」deerjikists` +## ニジラー deerjikists -`deerjikists「 はプラットフォーム上の人物・投稿者識別子をタグに結びつけるテーブルである。 +deerjikists は外部人物識別子と内部タグの対応表である。 -- 主キーは 」(platform, code)「 の複合主キー -- 」platform「 は現状 」nico` / `youtube「 -- 参照先タグは 」deerjikist「 カテゴリ必須 +- 主キーは (platform, code) の複合主キー +- platform は現状 nico / youtube +- 対応先タグは deerjikist カテゴリ必須 -## Wiki 」wiki_pages`, `wiki_revisions`, `wiki_lines`, `wiki_revision_lines「 +## Wiki -Wiki は単なる本文持ちテーブルではない。**ページ本体と改訂履歴を分離した版管理システム** になっている。 +Wiki は単なる本文持ちテーブルではなく、**ページ本体と改訂履歴を分離した版管理システム** である。 -### ページ単位 +### ページ wiki_pages -- 」wiki_pages.tag_name_id「 がページタイトルに対応する +- tag_name_id がタイトルに対応 - 1 タイトルにつき 1 ページ -- 」created_user_id`, `updated_user_id「 を持つ +- created_user_id / updated_user_id を保持 + +### 改訂 wiki_revisions + +- kind は content または redirect +- base_revision_id により改訂チェーンを持つ +- message を保持できる +- lines_count と tree_sha256 を持つ + +### 行ストレージ + +- 本文は wiki_revision_lines を介して行単位で保持される +- 各行本体は wiki_lines に sha256 付きで重複排除保存される -### 改訂単位 +### 重要な意味 -- 」wiki_revisions.kind「 は 」content「 または 」redirect「 -- 内容改訂は 」wiki_revision_lines「 を介して行単位で保持 -- 各行は 」wiki_lines「 に SHA-256 で重複排除して保存 -- 」base_revision_id「 により改訂チェーンを持つ -- 」message「 に編集メッセージを持てる +現行 Wiki は、ページ本文を毎回丸ごと 1 カラムに保存する方式ではない。**改訂と行ストアを分離した Git 風の構造** を DB 上で持っている。 -### Wiki の意味 +## 閲覧済フラグ user_post_views -要するに、現行 Wiki は **Git 的な差分・履歴志向をデータベースで再実装したもの** である。ここを単なる markdown カラムだと思うと仕様を読み違える。 +ユーザ単位で投稿の閲覧済状態を管理する。 -## 閲覧済フラグ 」user_post_views「 +- 主キーは (user_id, post_id) の複合主キー +- 投稿詳細画面から付与 / 解除できる -ユーザ単位で投稿の閲覧済状態を持つ。 +## 類似度 post_similarities, tag_similarities -- 主キーは 」(user_id, post_id)「 の複合主キー -- 詳細画面から切替可能 +投稿とタグには事前計算された類似度テーブルがある。 -## 類似度 」post_similarities`, `tag_similarities「 +- cos はコサイン類似度 +- 各行の主キーは (対象, 相手) の複合主キー +- 計算結果は上位 20 件のみ保持する -投稿とタグには事前計算された類似度テーブルが存在する。 +## 上映会 theatres, theatre_comments, theatre_watching_users -- 」cos「 はコサイン類似度 -- 投稿詳細では 」post.related「 として利用 -- Rake task により再計算する構成 +Theatre は、共同視聴用の会場を表すドメインである。 + +### theatres + +| 属性 | 概要 | +| --- | --- | +| name | 会場名 | +| opens_at / closes_at | 公開期間 | +| kind | 種別。現スナップショットでは未活用 | +| current_post_id | 現在上映中の投稿 | +| current_post_started_at | 再生開始時刻 | +| next_comment_no | コメント番号の採番用 | +| host_user_id | 現在のホスト | +| created_by_user_id | 作成者 | + +### theatre_comments + +- 主キーは (theatre_id, no) +- コメント本文と投稿ユーザを持つ +- discarded_at があるが、現行 API では削除機能なし + +### theatre_watching_users + +- 主キーは (theatre_id, user_id) +- expires_at で在席期限を持つ +- active スコープは expires_at >= Time.current # 現行機能仕様 ## 投稿一覧・検索 -### 一覧表示 +### API -」GET /posts「 により投稿一覧を取得する。 +GET /posts -- デフォルト件数は 20 -- 」page` / `limit「 によるページネーションあり -- 」cursor「 を使ったカーソル取得にも対応 -- ソート基準は概ね次の優先順 - 1. 」original_created_before - 1 秒` - 2. `original_created_from` - 3. `created_at「 +### 条件 -### 検索条件 +現行実装は次の検索パラメータを受け付ける。 -」tags「 クエリに空白区切りでタグ名を渡す。 +| パラメータ | 意味 | +| --- | --- | +| 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 | -- 」match=all「 または未指定: AND 検索 -- 」match=any「: ANY 検索 +order の field は次の 5 種である。 -タグ指定時は、検索前に 」TagName.canonicalise「 により別名が正規化される。よって、**別名で検索しても実体タグに吸収される**。 +- title +- url +- original_created_at +- created_at +- updated_at -### 現時点で未実装の検索 +### タグ検索の意味論 -issue 上は次が残っている。 +- match=all または未指定: AND 的に絞り込む +- match=any: OR 的に結合する +- 各タグリテラルは検索前に TagName.canonicalise で canonical 名へ正規化される +- not: 接頭辞を付けたタグは否定条件になる -- 投稿一覧の明示的なソート指定 -- OR / NOT 検索 -- 独立したタグ検索画面 +したがって現行仕様では、**別名吸収付きの AND / OR / NOT 相当** が最低限入っている。2026-03-08 版の「OR / NOT 未実装」は、もう事実ではない。 -つまり、現行検索はまだ完成形ではない。 +### 更新日時の扱い + +投稿一覧の updated_at は単純な posts.updated_at ではない。バックエンドは + +- 投稿本体の updated_at +- post_tags の最新 updated_at + +の大きい方を updated_at_all として計算し、それを返却にも並び替えにも使う。つまり、**タグ変更も投稿更新として扱う** のが現行仕様である。 + +### ランダム遷移 + +GET /posts/random が存在する。これは tags と match による絞り込み結果からランダム 1 件を返す。 ## 投稿詳細 -」GET /posts/:id「 で取得できる内容は次の通り。 +### API + +GET /posts/:id + +### 返却内容 - 投稿基本情報 - タグ木構造 -- 関連投稿最大 20 件 +- 関連投稿(最大 20 件) - 閲覧済フラグ ### 埋め込み表示 現行の専用埋め込み対応は次の通り。 -| サイト | 対応 | +| サイト | 現行条件 | | --- | --- | -| ニコニコ動画 | 専用 viewer | -| YouTube | 」react-youtube「 による埋め込み | -| Twitter / X | 専用埋め込み | -| その他 | 確認ダイアログ付き iframe | +| ニコニコ動画 | nicovideo.jp/watch/... | +| YouTube | youtube.com で v パラメータあり | +| X / Twitter | x.com または twitter.com の /status/:id | +| その他 | ユーザ確認後に iframe | -未対応だが backlog にあるものは TikTok, bilibili, Pixiv など。 +つまり「YouTube 全般」ではない。youtu.be などは現実装上は専用扱いされない。 ## 新規投稿 -」POST /posts「 は member 以上のみ可能。 +### API -入力は概ね以下。 +POST /posts -- URL -- タイトル -- サムネイル画像 -- タグ文字列 -- 元コンテンツ作成日時範囲 +### 権限 -保存時の流れは次の通り。 +member 以上。 + +### 入力 + +- title +- url +- thumbnail +- tags +- original_created_from +- original_created_before + +### 保存フロー 1. 投稿保存 -2. サムネイル縮小 +2. サムネイル添付と縮小 3. タグ正規化 4. 親タグ展開 -5. 」post_tags「 同期 +5. post_tags 同期 ## 投稿編集 -」PUT /posts/:id「 は member 以上のみ可能。 +### API + +PUT /posts/:id -現行仕様では次を更新できる。 +### 権限 -- タイトル -- タグ -- 元コンテンツ作成日時範囲 +member 以上。 -編集時は既存の 」nico「 カテゴリタグを維持した上で、一般タグ側を再計算する。 +### 現行仕様 -### 制約 +- 更新対象は title, original_created_from, original_created_before, tags +- 既存の nico カテゴリタグは維持したまま、一般タグ側を再計算する +- 排他制御は未実装 -- 排他制御は未実装。issue #171 が未着手。 -- 親投稿対応はスキーマ上の準備のみで、本格機能は未実装。 +## 閲覧済フラグ + +- POST /posts/:id/viewed +- DELETE /posts/:id/viewed + +認証済ユーザであれば操作可能。 ## 投稿履歴 -」GET /posts/changes「 では 」post_tags「 の追加・削除をイベント列として返す。 +### API -- 追加は 」change_type = add「 -- 削除は 」change_type = remove「 -- ユーザが紐づく場合は操作者も返す +GET /posts/changes -投稿そのものの編集履歴ではなく、**タグ変更履歴** である点に注意。 +### 条件 -## タグ一覧・補完 +- id: 投稿 ID で絞り込み +- tag: タグ ID で絞り込み +- page, limit -### タグ取得 +### 意味 -」GET /tags「 +返すのは投稿本体の変更履歴ではなく、**タグ付与関係の履歴** である。 -- 全タグを返す -- 」post「 パラメータ指定で特定投稿に付いたタグだけ返せる +- 追加は change_type = add +- 削除は change_type = remove -### 補完検索 +# タグ機能 -」GET /tags/autocomplete?q=...「 +## タグ一覧・検索 -現行仕様はかなり実用寄りで、以下を同時にやっている。 +### API -- 前方一致検索 -- 別名ヒット時の実体タグ返却 -- 」matched_alias「 の返却 -- 」present_only=true「 で件数 0 のタグを除外 -- 」nico=true「 で 」nico:「 接頭辞側の候補も含める -- 」post_count DESC, tag_names.name「 で整列 +GET /tags -### タグ詳細 +### 条件 -- 」GET /tags/:id` -- `GET /tags/name/:name「 +| パラメータ | 意味 | +| --- | --- | +| post | 特定投稿に付いたタグだけに絞る | +| name | 名前部分一致 | +| category | カテゴリ一致 | +| post_count_gte / post_count_lte | 投稿件数範囲 | +| created_from / created_to | 作成日時範囲 | +| updated_from / updated_to | 更新日時範囲 | +| page, limit | ページング | +| order | 並び順 | -いずれも 」TagRepr「 による基本情報を返す。 +order の field は name, category, post_count, created_at, updated_at に対応する。カテゴリ並びは DB 上で独自順序化されている。 -## ニコニコ連携 +### フロント画面 -### ニコタグ一覧 +/tags 画面は現時点で実装済みであり、名前・カテゴリ・件数・日時の詳細条件と並び替え UI を持つ。 -」GET /tags/nico` +## タグ補完 -- `updated_at DESC「 で取得 -- カーソルページングあり -- 各ニコタグに対する 」linked_tags「 を返す +### API + +GET /tags/autocomplete -### ニコタグ更新 +### パラメータ -」PUT /tags/nico/:id「 +| パラメータ | デフォルト | 意味 | +| --- | --- | --- | +| q | なし | 検索文字列 | +| nico | true | nico: 接頭辞側候補を含める | +| present | true | post_count > 0 のタグに絞る | -- member 以上のみ -- 入力された内部タグ列を再正規化 -- 」nico「 タグを連携先に指定することは禁止 +### 現行挙動 -### 日次同期バッチ +- q 先頭の not: は除去してから補完する +- canonical 名の前方一致に加えて、別名前方一致も拾う +- 別名ヒット時は canonical 側タグを返し、matched_alias を付ける +- 返却順は post_count DESC, tag_names.name +- 最大 20 件 -Rake task 」nico:sync「 により、外部のニコニコ DB から同期を行う。 +2026-03-08 版にあった present_only というパラメータ名は現行実装と一致しない。**現在のパラメータ名は present** である。 -現行コードから読み取れる同期仕様は次の通り。 +## タグ詳細 -- ニコニコ動画 URL 既存投稿を探索し、あれば更新、なければ新規作成 -- 新規作成時は 」タグ希望`, 「bot操作」, 「ニコニコ」, 「動画」 を付与 -- 外部ニコタグは `nico:「 接頭辞付きで内部タグ化 -- 新規に付いたニコタグについてのみ、連携された内部タグを自動付与 -- ニジラー対応が見つからなければ 」ニジラー情報不詳「 を補う -- 内部タグ構成が変化した場合は 」bot操作「 を付与 -- タグ削除は 」post_tags「 の論理削除で反映 +- GET /tags/:id +- GET /tags/name/:name -これは「単にニコタグを写す」のではなく、**外部タグを内部知識へ変換する ETL** に近い。 +/tags/name/:name は exact match であり、別名から canonical タグへ自動解決する API ではない。検索と補完は canonical 化するが、**詳細取得 API は exact name** である点に注意が要る。 -## ニジラー管理 +## タグ更新 -### 個別参照 +### API -」GET /deerjikists/:platform/:code「 +PUT /tags/:id -### 登録・更新 +### 権限 -」PUT /deerjikists/:platform/:code「 +member 以上。 -- member 以上 -- 対象タグは 」deerjikist「 カテゴリ必須 +### 現行でできること -### 削除 +- タグ名変更 +- カテゴリ変更 -」DELETE /deerjikists/:platform/:code「 +### 現行でできないこと -- member 以上 +- 別名タグ作成 UI / API +- tag sanitisation rule の管理 UI / API +- タグ統合 UI / API + +内部実装はかなり進んでいるが、表面 API はまだ薄い。 ## 上位タグ管理 -」POST /tags/:parent_id/children/:child_id` -`DELETE /tags/:parent_id/children/:child_id「 +- POST /tags/:parent_id/children/:child_id +- DELETE /tags/:parent_id/children/:child_id -- admin のみ -- 子タグに親タグを付ける / 外す +権限は admin。管理 UI は現スナップショットに存在しない。 -管理 UI はまだないが、API はある。 +## タグからニジラー参照 -## Wiki +- GET /tags/:id/deerjikists +- GET /tags/name/:name/deerjikists + +# ニコニコ連携 + +## ニコタグ一覧 + +### API + +GET /tags/nico + +### 現行仕様 + +- updated_at DESC で取得 +- cursor に ISO8601 時刻を渡すカーソルページング +- limit デフォルトは 20 +- 各ニコタグに対して linked_tags を返す + +## ニコタグ更新 + +### API + +PUT /tags/nico/:id + +### 権限 + +member 以上。 + +### 現行仕様 + +- 入力 tags を内部タグ列として再正規化する +- 連携先に nico カテゴリタグは指定不可 + +## 日次同期バッチ nico:sync + +現行タスクは、外部ニコニコ DB から動画情報を取り込み、投稿・タグ体系へ反映する。 + +### 主な流れ + +1. 外部 Python スクリプト get_videos.py を実行 +2. ニコニコ動画 URL 既存投稿を探し、あれば更新、なければ新規作成 +3. タイトル・投稿日・サムネイルを反映 +4. 外部タグを nico: 接頭辞付き内部タグへ変換 +5. 新規に付いたニコタグについてだけ、連携済内部タグを自動付与 +6. ニジラー対応が見つからなければ ニジラー情報不詳 を補う +7. 内部タグ構成が変化した場合は bot操作 を付与 + +新規投稿時に初期付与されるメタタグは次である。 + +- タグ希望 +- bot操作 +- ニコニコ +- 動画 + +これは単なるミラーではなく、**外部タグを内部知識へ変換する ETL** と見た方が正確である。 -### 一覧・検索 +# ニジラー管理 -- 」GET /wiki` -- `GET /wiki/search` +## API -`title「 指定がなければ全件、あれば部分一致上位 20 件。 +- GET /deerjikists/:platform/:code +- PUT /deerjikists/:platform/:code +- DELETE /deerjikists/:platform/:code -### 詳細表示 +## 権限 -- 」GET /wiki/:id` -- `GET /wiki/title/:title「 +更新・削除は member 以上。 -現行の返却内容は次を含む。 +## 制約 -- タイトル -- 本文 -- 」revision_id「 -- 前後改訂 ID (」pred`, `succ`) -- `updated_at「 +- platform は現状 nico / youtube +- 紐づけ先タグは deerjikist カテゴリ必須 + +# Wiki + +## 一覧・検索 + +### API + +- GET /wiki +- GET /wiki/search + +### 現行挙動 + +- title 未指定なら全件 +- title 指定時は tag_names.name LIKE %title% で上位 20 件 + +フロントの /wiki 画面には「内容」入力欄もあるが、バックエンドはそれを受け取っていない。**現行の Wiki 検索はタイトル部分一致のみ** である。 + +## 詳細表示 + +### API + +- GET /wiki/:id +- GET /wiki/title/:title + +### パラメータ + +- version: 指定時はその改訂を表示 + +### 返却内容 + +- id, title +- body +- revision_id +- pred, succ +- updated_at ### リダイレクト -改訂が 」redirect「 の場合、サーバは対象タイトルへ 301 リダイレクトを返す。 +改訂 kind = redirect の場合、サーバは 301 Moved Permanently でリダイレクト先タイトルへ飛ばす。 + +## 存在確認 + +- GET /wiki/:id/exists +- GET /wiki/title/:title/exists + +存在すれば 204 No Content、無ければ 404。 -### 存在確認 +## 差分表示 -- 」GET /wiki/:id/exists` -- `GET /wiki/title/:title/exists「 +### API + +GET /wiki/:id/diff?from=...&to=... + +### 現行仕様 + +- from 省略時は空本文相当との比較 +- to 省略時は現行改訂との比較 +- content 改訂同士のみ比較可能 +- 結果は context, added, removed の列として返す + +## 作成 -### 差分表示 +### API + +POST /wiki + +### 権限 -」GET /wiki/:id/diff?from=...&to=...` +member 以上。 -- `content「 改訂同士のみ比較可 -- 差分は 」context`, `added`, `removed「 の列で返す +### 入力 -### 作成 +- title +- body +- message(任意) -」POST /wiki「 +### 保存 -- member 以上 -- 」title`, `body「 必須 -- タイトルに対応する 」tag_name「 を作成または再利用 -- 初回改訂を 」Wiki::Commit.content!「 で保存 +- 対応する tag_name を作成または再利用 +- Wiki::Commit.content! により初回改訂を生成 -### 更新 +## 更新 + +### API -」PUT /wiki/:id「 +PUT /wiki/:id + +### 権限 + +member 以上。 + +### 現行仕様 -- member 以上 - タイトル変更は受け付けない -- 本文を書き換えて新改訂を追加する +- body と message を受け取れる +- 実際のコミットは Wiki::Commit.content! を通る + +### 競合制御の実態 + +Wiki::Commit 自体は base_revision_id による競合検出を備えている。しかし WikiPagesController#update はクライアント送信値を使わず、**サーバ側で今の current_revision.id を読み直してそれをベースにしている**。つまり、コード上は衝突検出器があるが、現行 API とフロントはそれを活かし切っていない。 + +## 履歴 + +### API + +GET /wiki/changes -### 履歴 +### 条件 -」GET /wiki/changes「 +- id を渡すと特定ページ履歴だけ返す +- 未指定なら全体履歴 -- 全体履歴または特定ページ履歴を返す -- 現行返却内容は revision 単位 +### 現行仕様 -### 編集競合 +- WikiRevision 単位の履歴を最大 200 件返す +- pred は返るが succ は常に nil -」Wiki::Commit「 には 」Conflict「 例外とページロックがあり、競合時は 409 を返す設計である。 +## フロント実装の注意点 -ただし、現行の更新 API はクライアントからベース revision を明示送信させていない。したがって、**厳密な意味での楽観ロックが完成しているとは言い難い**。ここは仕様書で盛ると嘘になる。 +- 新規・編集画面は Markdown エディタを使う +- message 入力欄はフロントにまだ出ていない +- リダイレクト編集 UI も無い +- タイトル入力欄はあるが、更新時は変更するとバックエンドが 422 を返す -## 設定・ユーザ管理 +# 設定・ユーザ管理 -### 現行 UI でできること +## 現行 UI でできること -設定画面 」/users/settings「 で現にできるのは次。 +/users/settings 画面でできるのは次である。 - 表示名更新 - 引継ぎコード表示 -- ほかブラウザからの引継ぎ +- 他ブラウザからの引継ぎ -### API +## API -- 」POST /users「 新規 guest 発行 -- 」POST /users/verify「 引継ぎコード検証 + IP 紐づけ -- 」POST /users/code/renew「 引継ぎコード再発行 -- 」GET /users/me?code=...「 ユーザ取得 -- 」PUT /users/:id「 表示名更新 +- POST /users +- POST /users/verify +- GET /users/me +- POST /users/code/renew +- PUT /users/:id -### 留意点 +## 注意点 -」settings「 テーブルは存在するが、現スナップショットでは **汎用ユーザ設定 API / 反映ロジックは未完成** である。issue #34, #35 がそのまま残っているため、このテーブルは将来拡張用の足場と見なすべきである。 +- settings テーブルは存在する +- しかし **汎用ユーザ設定 API も反映ロジックも現行スナップショットでは未接続** +- フロントには /users や /users/:id への隠しメニュー項目があるが、対応ルートは未実装 -## プレビュー取得 +# プレビュー取得 -」/preview/title「 と 」/preview/thumbnail「 は member 権限ではなく **認証済ユーザなら利用可能** な補助 API である。 +## API -- title: 対象 URL の HTML title を取得 -- thumbnail: Puppeteer スクリーンショット経由で 180x180 サムネイル生成 +- GET /preview/title +- GET /preview/thumbnail -ただし TODO が残っており、既知サイト個別最適化は未実装。仕様としては **暫定ユーティリティ** の域を出ていない。 +## 権限 -# 画面仕様の要約 +認証済ユーザであれば利用可能。 -## 投稿一覧画面 +## 現行仕様 -- 左または下部に TagSidebar を表示 -- 検索欄でタグ入力 + 補完 -- 単一タグ検索時は Wiki タブを併設 -- ランダム遷移あり -- モバイルではタグ一覧を折りたたむ +- title は対象 URL の HTML を開き