目次
- 本書の位置づけ
- システム概要
- 技術構成
- ルーティング概要
- 認証・ユーザ・BAN
- エラー応答
- 投稿仕様
- タグ仕様
- Nico タグ仕様
- YouTube 同期仕様
- ニジラー紐づけ仕様
- Wiki 仕様
- 素材仕様
- 上映会仕様
- Gekanator 仕様
- プレビュー API
- 類似度計算
- データベース仕様
- フロントエンド仕様
- テスト状況
- 実装上の不整合・危険点
- P0: Preview API の SSRF/リソース消費リスク
- P0: guest 自動作成による users 肥大化
- P0: 素材作成権限が緩い
- P1: タグ親子編集権限が二重化
- P1: post_versions の parent post 履歴 API が不足
- P1: material_versions, wiki_assets は必要だが未実装
- P1: inheritance_code の一意制約なし
- P1: 上映会 host 乗っ取り耐性
- P2: Material のバリデーション文言と許可カテゴリのズレ
- P2: Gekanator AI 変換 API は未実装
- P2: issue 管理との接続が手動
- 開発者ヒアリング反映結果
- Gitea 課題一覧反映
- 次回更新方針
- 外部参照
title, subtitle, date, lang, toc, toc-depth, numbersections
| title | subtitle | date | lang | toc | toc-depth | numbersections |
|---|---|---|---|---|---|---|
| BTRC Hub / タグ広場 現行実装仕様書 | ソース / Wiki / 旧本番DB / 開発者ヒアリング / Gitea 課題一覧 反映版 | 2026-06-11 | ja-JP | true | 3 | true |
本書の位置づけ
本書は、添付された btrc-hub-main.zip の現行ソース、btrc-hub.wiki.zip の Wiki、btrc_hub_20260425時点本番DBデータ.zip の旧本番 DB ダンプ、既存仕様書 BTRC_Hub_現行実装仕様書_2026-05-10_ヒアリング反映版.md、開発者ヒアリング回答、添付 Gitea 課題一覧 JSON を確認し、2026-06-11 時点のタグ広場の実装事実と確定意図を再構成した仕様書である。
本書は願望ではなく、まずコード・スキーマ・画面・テストに現れてゐる事実を書く。その上で、開発者ヒアリング回答を仕様決定として本文へ反映する。実装が仕様決定に追いついてゐない箇所は 仕様決定済み・実装未反映 として明示する。
確認対象
| 区分 | 確認対象 |
|---|---|
| バックエンド | Rails 8 API、models、controllers、services、representations、routes、schema、RSpec |
| フロントエンド | React/Vite/TypeScript、routes、pages、components、lib、types、Vitest |
| Wiki | 開発 Wiki、テーブル定義書、環境構築手順 |
| DB | 2026-04-25 旧本番 DB SQL ダンプ。現行 schema との差分あり |
| 既存仕様 | 2026-05-10 版仕様書 |
| 課題一覧 | 添付 Gitea issue JSON 50 件。全件 open。P1/P2/P3・status・area・type を集計して反映 |
表記ルール
| 表記 | 意味 |
|---|---|
| 現行仕様 | ソース・スキーマ・画面・テストで確認できる仕様 |
| 実装あり・UI薄い | API/モデルはあるが画面導線が弱い、または管理導線が薄い |
| 未実装・残骸候補 | スキーマだけある、または途中の設計痕跡はあるが呼び出し実装が薄いもの |
| 注意 | 仕様として固定するには危険な実装差・不整合・セキュリティ上の穴 |
| 開発者ヒアリング | コードだけでは判断不能だったが、開発者回答により仕様確定した事項 |
本版で確定した設計判断
開発者ヒアリングにより、次を仕様として確定する。
| ID | 決定事項 | 実装状態 |
|---|---|---|
| H-001 | タグ親子関係は member 以上が編集可能。UI の親タグ欄は全員に表示し、変更操作は member 以上に限る。D&D だけ admin 専用だったのは履歴管理前の安全策であり、恒久方針ではない | 一部未反映。TagChildrenController は admin 限定のまま |
| H-002 | 素材作成は member 以上を原則とする。URL-only 素材だけ guest 許可の余地あり。file 素材は storage 圧迫防止のため guest 不可 | 未反映。現行 POST /materials は current_user のみ |
| H-003 | 初回閲覧で guest user を作る単純設計は継続。ただし bot/cookie なしアクセス対策は未定 | 現行通り。ただし users 肥大化が残る |
| H-004 | 投稿履歴には親投稿変更を必ず表示する。API は parent_posts: [{ id, title }] を返し、表示は現在 title に加へて当時 title を注記する方向 |
未反映。API が返してゐない |
| H-005 | 素材履歴は必要。snapshot 対象は tag、URL、file blob、更新者。parent は廃止予定 | テーブルあり、実装薄い |
| H-006 | Wiki asset は Wiki 内画像/添付として実装予定。wiki_pages.next_asset_no と連動し、バイト列 SHA256 をキー情報に使ふ |
テーブルあり、導線未確認 |
| H-007 | Gekanator は恒久 admin 専用ではない。管理者側で学習・調整後、一般ユーザ向けに公表する | 現行は admin 専用 |
| H-008 | Gekanator AI は質問分類だけでなく、既存投稿への回答補完まで行ふ。初期モデルは低コスト structured output 対応モデルを環境変数で差し替へる | converter 未実装 |
| H-009 | 上映会 host 制御は、本来サーバ側で担ふ方向。現行の active watching user 自動 host は暫定色が強い | 現行は自動 host |
| H-010 | Preview API は guest 利用を直ちに禁止はしない。ただし private IP 拒否、redirect 検査、content length 上限等は必須。クリーンに守れないなら機能廃止も検討 | 未反映 |
| H-011 | issue は全件共有が望ましい。今回は添付 JSON 50 件を反映する | 反映済み |
この表と本文が衝突する場合、本文の「確定仕様」節を優先する。現行実装との差分は、修正対象であって仕様の揺れではない。
用語: 公開と公表
現行のタグ広場は URL として到達可能であり、実質的にはすでに 公開 状態である。一方、界隈へ明示的に告知し、利用導線を整へ、人を呼び込む状態を 公表 と呼ぶ。
したがって「一般公開前」という古い表現は、本書では原則として 公表前 と読み替へる。
システム概要
目的
BTRC Hub / タグ広場は、ぼざろクリーチャー関連コンテンツへのリンクを収集し、タグ・Wiki・素材・上映会・推測ゲームを通じて、作品群と関連知識を整理・再発見しやすくする共同編集型基盤である。
開発 Wiki の Home では、目的が次のやぅに整理されてゐる。
- ぼざろクリーチャーシリーズ関連のあらゆるコンテンツへのリンクを保持する。
- 各リンクにタグを付け、検索しやすくする。
- ニコニコのタグ数制限への対応。
- プラットフォームを超越し、あらゆるユーザによってぼざクリを一元管理できるやぅにする。
- コンテンツ本体ではなくリンクを保持する。
- SNS 性はなるべく排除する。
中核ドメイン
| ドメイン | 内容 |
|---|---|
| 投稿 | 外部 URL によるリンクデータ。タグ・サムネ・親子関係・閲覧済み状態・類似投稿を持つ |
| タグ | カテゴリ、別名、親タグ、外部 Nico タグ連携、ニジラー紐づけ、素材紐づけを持つ分類単位 |
| Wiki | タグ名と結びつく説明ページ。行単位ストアと改訂履歴を持つ |
| 素材 | キャラクター/素材タグに紐づくファイルまたは URL |
| 上映会 | 投稿を共同視聴し、在席・ホスト・コメント・番組表・スキップ投票を扱ふ |
| Gekanator | 管理者向けの「投稿当て」推測ゲーム兼質問学習機構 |
非目的
現行実装は次を主目的にしてゐない。
- 汎用 SNS。
- 雑談掲示板。
- 外部コンテンツ本体の転載保存。
- 本格的な動画配信基盤。
- メール/パスワード式の通常アカウント管理。
- 誰でも自由に高度編集できる完全オープン Wiki。
ただし、上映会コメント・素材投稿・Gekanator 学習など、単なるリンク集より機能はかなり重い。仕様境界を曖昧にすると、SNS 化・荒らし対応・権限崩壊が一気に来る。ここは甘く見ないこと。
技術構成
バックエンド
| 項目 | 現行 |
|---|---|
| 言語 | Ruby |
| フレームワーク | Rails ~> 8.0.2 API |
| DB | MySQL 8 系想定。Gemfile には sqlite3 も残る |
| ファイル | Active Storage。S3/R2 互換想定あり |
| 画像処理 | image_processing / MiniMagick |
| HTML 解析 | Nokogiri |
| Wiki 移行/旧資産 | gollum / gollum-lib |
| 差分 | diff-lcs |
| テスト | RSpec |
| BAN/soft delete | discard |
| i18n | rails-i18n |
フロントエンド
| 項目 | 現行 |
|---|---|
| UI | React 19.1 + Vite 6.3 |
| 言語 | TypeScript 5.8 |
| 通信 | Axios。レスポンスは camelcase-keys で deep camelCase 化 |
| 状態/取得 | TanStack Query、localStorage、必要箇所で state |
| スタイル | Tailwind CSS、shadcn 風ローカル UI、Framer Motion |
| Markdown | react-markdown、react-markdown-editor-lite、remark-gfm、wiki autolink |
| テスト | Vitest、Testing Library、jsdom |
検証コマンド
リポジトリ文書上の推奨コマンドは次である。
cd backend && bundle exec rspec
cd frontend && npm run test:run && npm run build && npm run lint
ルーティング概要
バックエンド API
主な API は次である。
| 領域 | 代表エンドポイント |
|---|---|
| 投稿 | GET /posts, GET /posts/:id, POST /posts, PUT/PATCH /posts/:id, GET /posts/random, GET /posts/versions, POST/DELETE /posts/:id/viewed |
| タグ | GET /tags, GET /tags/:id, PUT/PATCH /tags/:id, GET /tags/autocomplete, GET /tags/with-depth, GET /tags/versions |
| Nico タグ | GET /tags/nico, PUT /tags/nico/:id |
| タグ親子 | POST /tags/:parent_id/children/:child_id, DELETE /tags/:parent_id/children/:child_id |
| Wiki | GET/POST /wiki, GET/PUT /wiki/:id, GET /wiki/search, GET /wiki/changes, GET /wiki/:id/diff, GET /wiki/title/:title |
| 素材 | GET/POST /materials, GET/PUT/DELETE /materials/:id |
| ニジラー紐づけ | GET/PUT/DELETE /deerjikists/:platform/:code |
| プレビュー | GET /preview/title, GET /preview/thumbnail |
| ユーザ | POST /users, POST /users/verify, POST /users/code/renew, GET /users/me, PUT/PATCH /users/:id |
| 上映会 | GET /theatres/:id, PUT /watching, PATCH /next_post, PUT/DELETE /skip_vote, GET /post_selection_weights, comments/programmes/skip_events |
| Gekanator | GET /gekanator/posts, GET /gekanator/questions, POST /gekanator/games, POST /gekanator/question_suggestions, POST /ai_convert |
フロントエンド画面
| パス | 画面 |
|---|---|
/posts |
投稿一覧 |
/posts/new |
投稿作成 |
/posts/search |
投稿検索 |
/posts/:id |
投稿詳細/編集 |
/posts/changes |
投稿履歴 |
/tags |
タグ一覧 |
/tags/:id |
タグ詳細/編集 |
/tags/:id/deerjikists |
ニジラー紐づけ |
/tags/nico, /nico/tags |
Nico タグ一覧/連携編集 |
/tags/changes |
タグ履歴 |
/wiki |
Wiki 検索 |
/wiki/:title |
Wiki 表示 |
/wiki/new |
Wiki 新規作成 |
/wiki/:id/edit |
Wiki 編集 |
/wiki/:id/diff |
Wiki 差分 |
/wiki/changes |
Wiki 履歴 |
/materials |
素材一覧 |
/materials/new |
素材作成 |
/materials/:id |
素材詳細 |
/theatres/:id |
上映会 |
/gekanator |
管理者専用 Gekanator |
/users/settings, /settings |
ユーザ設定 |
/tos |
利用規約 |
/more |
その他 |
認証・ユーザ・BAN
認証方式
通常のログインではなく、引継ぎコードによる軽量認証である。
users.inheritance_codeが認証トークン。- フロントは
localStorage.user_codeに保存。 - API 呼び出し時、
X-Transfer-Codeヘッダに付与。 - バックエンドは
ApplicationController#authenticate_userでcurrent_userを設定する。
初回利用フロー
フロント起動時に次を行ふ。
localStorage.user_codeがあればPOST /users/verify。- 有効なら返却ユーザを採用。
- 無効またはコードなしなら
POST /usersで guest ユーザを作成。 - 新規作成された
inheritance_codeを localStorage へ保存。
確定仕様: guest 自動作成
初回閲覧で guest user を作る仕様は継続する。理由は、仕様分岐を増やさず単純に保つためである。
ただし、旧本番 DB で users = 42805 件あるため、bot または cookie/localStorage を保持しないアクセスにより users が肥大化してゐる可能性は高い。これは仕様として許容されたわけではなく、未決定の運用リスクである。
bot / cookie なしアクセスへの推奨方針
現時点では次を推奨仕様とする。
| 層 | 方針 |
|---|---|
| フロント | localStorage が使へない環境では編集系 UI を出さず、POST /users を連打しない |
| API | POST /users に IP 単位・UA 単位の軽い rate limit を置く |
| DB | users に last_seen_at, created_ip_address_id, user_agent_hash の追加を検討する |
| 運用 | 一定期間一度も編集/投票/コメントしてゐない guest を掃除できる Rake task を用意する |
| bot 対策 | 明確な bot UA は user を作らず 403/204 に寄せる。検索エンジン等に guest ID を発行しない |
初回閲覧 guest 自動作成を維持するなら、最低限 POST /users の rate limit と掃除 task は必要である。ここを放置すると、BAN・分析・バックアップが砂嵐になる。
ロール
| role | 意味 |
|---|---|
guest |
自動生成される通常閲覧者。閲覧、閲覧済み、上映会在席、コメント、skip 投票など軽い参加行為の主体 |
member |
投稿・タグ・Wiki・素材等の編集者 |
admin |
管理者。管理者専用調整機能や未公開ツールを利用可能 |
User#gte_member? は member または admin を許可する。
権限原則
| 操作 | 権限 |
|---|---|
| 投稿作成/更新 | member+ |
| タグ編集 | member+ |
| タグ親子編集 | member+。現行の D&D/admin 限定は暫定 |
| Wiki 作成/更新 | member+ |
| 素材作成 | member+。URL-only guest 許可は将来検討 |
| file 素材作成 | member+ 必須 |
| Gekanator 調整 | admin |
| Gekanator 公開プレイ | 将来 public 化予定 |
| Preview API | public/guest 相当を維持する可能性あり。ただし SSRF 対策必須 |
BAN
ApplicationController の before_action は次の順序で動く。
reject_banned_ip_address!authenticate_userreject_banned_user!
したがって BAN は全 API にかかる。
| 対象 | 判定 | 応答 |
|---|---|---|
| IP BAN | ip_addresses.banned_at が存在 |
403 |
| User BAN | users.banned_at が存在 |
403 |
IP は IPAddr.new(request.remote_ip).hton により binary 化して ip_addresses.ip_address に保存する。
注意
users.inheritance_code はモデル上必須・64文字以内だが、現行 schema では一意 index が見当たらない。UUID なので衝突可能性は低いが、認証トークンに DB 一意制約がないのは設計として弱い。DB 一意 index を追加すべきである。
エラー応答
バリデーションエラー
ApplicationController#render_validation_error 系は概ね次の JSON を返す。
{
"type": "validation_error",
"message": "入力内容を確認してください.",
"errors": {},
"base_errors": []
}
フロントは camelcase-keys により baseErrors などへ変換して扱ふ。
競合
投稿更新では version based optimistic concurrency があり、競合時は 409 を返す。
主なフィールド:
error: 'conflict'messagepost_idbase_version_nocurrent_version_nobasecurrentminechangesconflictsmergeable
投稿仕様
投稿モデル
posts は外部 URL を中心とするリンク記録である。
| 属性 | 仕様 |
|---|---|
url |
必須・一意・HTTP/HTTPS のみ |
title |
NULL 可 |
thumbnail_base |
外部サムネイル URL。長さ 2000 |
thumbnail |
Active Storage 添付 |
uploaded_user_id |
投稿者。同期投稿では NULL 可 |
original_created_from |
元コンテンツ作成日時の下限 |
original_created_before |
元コンテンツ作成日時の上限 |
version_no |
投稿内の現行版番号。1 以上 |
URL 正規化
保存前に次を行ふ。
- 前後空白の除去。
- URI として parse できる場合、host を小文字化。
- path 末尾の
/を除去。 - HTTP/HTTPS 以外は不正。
元コンテンツ日時
original_created_from と original_created_before の両方がある場合、from < before が必須である。
同期系では動画公開時刻を from = 公開時刻の秒切捨て, before = from + 1分 として持つ。
投稿作成
POST /posts は member 以上が必要。
主要入力:
| 入力 | 仕様 |
|---|---|
title |
任意 |
url |
必須 |
thumbnail |
任意。添付時は 180x180 JPEG へ変換 |
thumbnail_base |
任意 |
tags |
空白区切りタグ文字列 |
parent_post_ids |
必須。空でも送る必要あり |
original_created_from / before |
任意 |
タグは Tag.normalise_tags! で正規化され、別名解決、カテゴリ prefix 解釈、タグ希望/ニジラー不詳の自動付与、親タグ展開が行はれる。
投稿更新
PUT/PATCH /posts/:id は member 以上が必要。
現行では version based optimistic concurrency が実装されてゐる。
| パラメータ | 仕様 |
|---|---|
base_version_no |
force でない限り必須。正整数 |
force |
強制上書き |
merge |
競合がなければ自動マージ |
force と merge の同時指定は禁止。
競合判定対象:
titleoriginal_created_fromoriginal_created_beforetag_namesparent_post_ids
スカラー値は、base から current と mine が別々に変更され、かつ値が違ふ場合に競合する。集合値は、同じ要素について片方が追加し片方が削除してゐる場合に競合する。
merge 可能なら、現在状態と自分の変更を統合する。競合があれば 409。
タグ処理
投稿タグは post_tags に保持される。
- 物理削除ではなく
discarded_atによる論理削除。 - 同一投稿・同一タグの active 重複は禁止。
PostTag#destroyは読み取り専用例外を投げる。discard_by!によりdiscarded_atとdeleted_userを設定し、tags.post_countを減算する。
投稿に手動で nico: タグを入れることは通常禁止される。Nico タグは同期/連携経由で扱ふ。
親子投稿
post_implications は投稿間の多対多親子関係である。
| カラム | 意味 |
|---|---|
post_id |
子投稿 |
parent_post_id |
親投稿 |
制約:
- 複合主キー。
- 自己親は禁止。
- 存在しない親 ID はエラー。
フロントの PostList は親子投稿の存在に応じてカード枠を変へる。
関連投稿
post_similarities は、投稿同士の類似度を保持する。
Similarity::Calc.call(Post, :tags)により生成。- 各投稿につき上位 20 件を保存。
- 類似度はタグ集合の cosine similarity。
Post#related(limit:)はcos DESCで取得する。
投稿一覧検索
GET /posts は次の絞り込みを持つ。
| パラメータ | 仕様 |
|---|---|
url |
URL 部分一致 |
title |
タイトル部分一致 |
tags |
空白区切りタグ。別名 canonicalise あり |
match |
all または any |
not: prefix |
除外タグ指定 |
original_created_from/to |
元作成日時範囲 |
created_from/to |
作成日時範囲 |
updated_from/to |
更新日時範囲。タグ更新も考慮 |
order |
title, url, original_created_at, created_at, updated_at + asc/desc |
page, limit |
1 未満は 1 へ補正 |
updated_at sort は、投稿本体の updated_at と post_tags.updated_at の最大値を使ふ。
投稿履歴
post_versions は immutable snapshot である。
| 属性 | 内容 |
|---|---|
post_id |
対象投稿 |
version_no |
投稿内連番 |
event_type |
create, update, discard, restore |
title, url, thumbnail_base |
投稿本体 |
tags |
タグ名の空白区切り snapshot。将来 tags_json へ移行予定 |
parent_post_ids |
親投稿 ID の空白区切り snapshot |
original_created_from/before |
元日時範囲 |
created_by_user_id |
操作者 |
VersionRecorder は次を保証する。
- 永続化済み version は readonly。
- 初回イベントは
createでなければならない。 - 同一 snapshot の update は版を増やさない。
- 作成後、対象 record の
version_noを更新する。
確定仕様: 親投稿変更履歴
投稿履歴画面では、親投稿の追加・削除を必ず表示する。現行 API が返してゐないのはバグである。
GET /posts/versions は各 version に少なくとも次を返すべきである。
parentPosts: Array<{
id: number
title: string | null
historicalTitle?: string | null
}>
API の snake_case 原型は次を想定する。
{
"parent_posts": [
{
"id": 123,
"title": "現在のタイトル",
"historical_title": "履歴作成当時のタイトル"
}
]
}
表示仕様:
- 通常表示は現在 title を使ふ。
- 当時 title が現在 title と異なる場合、
(当時タイトル:{historical_title})を付記する。 - 対象投稿が削除済みまたは参照不能なら、
#123(現在参照不可)のやぅに ID を残す。
タグ履歴についても同様に、現在名だけでなく当時の名前・カテゴリを表示できる形が望ましい。添付 issue #354 により、post_versions.tags_json を作り、旧 post_versions.tags を廃止する方向が課題化されてゐる。
実装差分
フロント PostVersion 型には parentPosts がある。しかし現行 PostVersionsController#index は parent_post_ids を select/serialize しておらず、parentPosts を返してゐない。これは仕様決定済みの実装バグである。
タグ仕様
タグ名とタグ実体
タグは tag_names と tags に分離される。
| テーブル | 役割 |
|---|---|
tag_names |
名前文字列、canonical/alias 関係、Wiki ページとの結合点 |
tags |
カテゴリ、投稿件数、実体 ID、version_no |
Tag#name は tag_name.name の delegate である。
タグカテゴリ
| category | 表示/用途 |
|---|---|
deerjikist |
ニジラー |
meme |
原作・ネタ元・ミーム等 |
character |
キャラクター |
general |
一般 |
material |
素材 |
meta |
メタタグ |
nico |
ニコニコタグ |
nico タグは名前が必ず nico: で始まる必要があり、非 nico タグが nico: で始まることも禁止される。
システムタグ
Tag には次の作成ヘルパがある。
| メソッド | タグ名 | 用途 |
|---|---|---|
Tag.tagme |
タグ希望 |
タグ不足の印 |
Tag.bot |
bot操作 |
bot/sync 由来の印 |
Tag.no_deerjikist |
ニジラー情報不詳 |
ニジラー不明 |
Tag.video |
動画 |
動画投稿 |
Tag.niconico |
ニコニコ |
ニコニコ由来 |
Tag.youtube |
YouTube |
YouTube 由来 |
タグ希望, bot操作, ニジラー情報不詳, 動画, ニコニコ は名称変更が明示的に禁止される。
タグ正規化
Tag.normalise_tags! の仕様:
- 入力はタグ名配列。
- 空白・空文字を除去。
- カテゴリ prefix を解釈する。
TagName.canonicaliseにより別名を正規名へ解決する。- 存在しない tag_name/tag は作成する。
with_tagmeが true かつタグ数が 10 未満でタグ希望がなければ追加。with_no_deerjikistが true かつ deerjikist タグがなければニジラー情報不詳を追加。deny_nicoが true ならnico:prefix はエラー。
対応 prefix:
| prefix | category |
|---|---|
general:, gen: |
general |
deerjikist:, djk: |
deerjikist |
meme: |
meme |
character:, chr: |
character |
material:, mtr: |
material |
meta: |
meta |
別名
tag_names.canonical_id により別名を表現する。
canonical_id = NULL: 正規名。canonical_id != NULL: 別名。- 別名の参照先は正規名でなければならない。
- 別名名に
:は含められない。 - タグまたは Wiki ページを持つ tag_name は別名化できない。
タグ詳細の full update では、名称変更時に旧名が aliases に追加される。
タグ親子
tag_implications は子タグから親タグへの関係を表す。
| カラム | 意味 |
|---|---|
tag_id |
子タグ |
parent_tag_id |
親タグ |
- 同一組合せは一意。
- 自己親は禁止。
- 投稿側では、付与タグの親タグが再帰的に展開される。
確定仕様: タグ親子編集権限
タグ親子関係の編集は member 以上 が可能である。
UI 仕様:
| ユーザ | 親タグ欄の表示 | 変更 |
|---|---|---|
| guest | 表示する | 不可 |
| member | 表示する | 可 |
| admin | 表示する | 可 |
現行の D&D 操作が admin 限定だった理由は、履歴管理がなかった時期に誤操作を避けるための安全策である。現在は履歴管理があるため、admin 限定を続ける必然性は薄い。
実装差分
TagChildrenController の親子追加/削除 API は admin 限定である。一方、TagsController#update_all は member 以上で parent_tags を更新できる。正しい仕様は member+ なので、専用 API 側の権限を揃へる必要がある。
追加制約
添付 issue #332 により、上位タグの循環登録禁止が P1 バグとして課題化されてゐる。タグ親子編集を member+ に開くなら、循環検出は必須である。
タグ一覧/検索
GET /tags は次を持つ。
| パラメータ | 仕様 |
|---|---|
post |
投稿 ID で絞り込み |
name |
名前部分一致 |
category |
カテゴリ |
post_count_gte/lte |
投稿件数範囲 |
created_from/to |
作成日時 |
updated_from/to |
更新日時 |
order |
name, category, post_count, created_at, updated_at |
カテゴリ順は独自順で、deerjikist, meme, character, general, material, meta, nico の順を使ふ。
タグ autocomplete
GET /tags/autocomplete:
| パラメータ | 仕様 |
|---|---|
q |
検索語 |
nico |
nico タグを含めるか。既定 true |
present |
投稿件数 > 0 のものに絞るか。既定 true |
別名 hit も見て、matched_alias を返す。
タグ履歴
tag_versions は immutable snapshot。
| 属性 | 内容 |
|---|---|
tag_id |
対象タグ |
version_no |
タグ内連番 |
event_type |
create/update/discard/restore |
name |
タグ名 snapshot |
category |
カテゴリ snapshot |
aliases |
別名空白区切り snapshot |
parent_tag_ids |
親タグ ID 空白区切り snapshot |
created_by_user_id |
操作者 |
TagVersioning は更新前 snapshot と更新後記録を制御する。
Nico タグ仕様
概要
ニコニコ由来のタグは内部タグとは別性質である。
tags.category = 'nico'tag_names.nameは必ずnico:で始まる。- 通常の手動入力では
nico:は拒否される。 - 内部タグとの連携は
nico_tag_relationsで行ふ。
Nico タグ連携
nico_tag_relations:
| カラム | 意味 |
|---|---|
nico_tag_id |
nico カテゴリタグ |
tag_id |
内部タグ |
NicoTagsController#update は member 以上が利用可能。
- 対象 tag は nico でなければならない。
- 連携先は通常タグで、nico から nico への連携は禁止。
- 連携先タグも version snapshot される。
nico_tag_versionsへ連携状態が記録される。
Nico 同期
backend/lib/tasks/sync_nico.rake は外部 NIZIKA_NICO_PATH の Python スクリプトから動画情報を取得する。
処理概要:
- 動画 code/title/uploaded_at/tags を取得。
- 既存投稿を
nicovideo.jp/watch/<code>で検索。 - なければ投稿作成。
- サムネイルを HTML meta から取得して Active Storage 添付。
タグ希望,bot操作,ニコニコ,動画を付与。- ニコニコの生タグを
nico:<raw>としてnicoタグ化。 - Nico タグに連携された内部タグも付与。
- 投稿版/Nico タグ版を記録。
Nico 逆連携
backend/lib/tasks/export_nico.rake は、タグ広場上のニコニコ動画 ID を外部 tracked_videos.put_bulk_upsert へ渡す。
YouTube 同期仕様
backend/lib/tasks/sync_posts.rake は Youtube::Sync.new.sync! を呼ぶ。
検出対象
- 検索語:
ぼざろクリーチャーシリーズ伊地知ニジカ伊地知虹鹿
- プレイリスト ID 3 件。
- 検索対象は直近 14 日。
同期処理
- YouTube Data API で動画 ID を取得。
videosAPI で snippet/status/contentDetails を取得。youtube.com/watch?v=<id>またはyoutu.be/<id>として既存投稿検索。- なければ投稿作成。
- サムネイルを添付。
- 新規時
タグ希望,bot操作,YouTube,動画を付与。 - チャンネル ID が
deerjikistsに登録済みなら該当 deerjikist タグを付与。 - 未登録かつ deerjikist タグなしなら
ニジラー情報不詳を付与。 - 投稿版を記録。
ニジラー紐づけ仕様
deerjikists
deerjikists は外部プラットフォーム上のユーザ/チャンネルとタグを紐づける。
| カラム | 意味 |
|---|---|
platform |
nico または youtube |
code |
外部 ID |
tag_id |
deerjikist カテゴリのタグ |
複合主キーは (platform, code)。
Deerjikist モデルは紐づけ先タグが deerjikist カテゴリであることを要求する。
API
| API | 権限 | 内容 |
|---|---|---|
GET /deerjikists/:platform/:code |
public | 紐づけ取得 |
PUT /deerjikists/:platform/:code |
member+ | 紐づけ作成/更新 |
DELETE /deerjikists/:platform/:code |
member+ | 紐づけ削除 |
PUT /tags/:id/deerjikists |
member+ | タグ側から複数紐づけ更新 |
YouTube では @handle が渡された場合、YouTube ページを取得して UC... channel ID へ正規化する処理がある。
注意
YouTube handle 正規化は外部 HTML 取得に依存してゐる。失敗時は nil になり得るため、UI 側のエラー誘導が重要。
Wiki 仕様
Wiki ページとタグ名
wiki_pages は tag_name_id と一対一に紐づく。
- Wiki のタイトルは
tag_name.name。 - タグ実体ではなく tag_name に紐づくため、タグがなくても Wiki ページは存在し得る。
- タイトル変更時は tag_name を変更する。
現行ストレージ
Wiki は本文を wiki_pages.body に持つだけではない。現行は行単位の改訂履歴を持つ。
| テーブル | 役割 |
|---|---|
wiki_pages |
ページ本体/現在情報 |
wiki_revisions |
改訂ヘッダ。kind/content/redirect、tree_sha 等 |
wiki_lines |
行本文を SHA256 で重複排除 |
wiki_revision_lines |
改訂と行の順序 |
wiki_versions |
ページ title/body の version snapshot |
作成/更新
Wiki 作成/更新は member 以上。
Wiki::Commit.create_content! は次を行ふ。
- CRLF を LF へ統一。
- 不正 UTF-8 を置換文字で救済。
- 末尾改行を strip。
- 行ごとに SHA256 を計算。
- 未登録行だけ
wiki_linesに upsert。 wiki_revision_linesに行順を保存。wiki_versionsを記録。base_revision_idが渡された場合、現在 revision と一致しなければ conflict。
差分
GET /wiki/:id/diff は diff-lcs による行差分を返す。
返却 type:
contextaddedremoved
content revision 同士のみ差分対象。
redirect
Wiki::Commit.redirect! は現行では raise '廃止しました.' で無効化されてゐる。ただし schema と controller には redirect revision の痕跡がある。
Wiki asset
wiki_assets は Wiki 内画像/添付ファイルのための将来機能である。
確定仕様:
- Wiki ページ内に画像または添付ファイルを持たせる。
wiki_pages.next_asset_noと連動し、ページ内 asset 番号を採番する。wiki_assets.sha256は添付バイト列を SHA256 に通した値を保持する。- 同一バイト列の重複検出・再利用・整合性確認に
sha256を使ふ。 - 本体保存は Active Storage を前提にする。
推奨カラム意味:
| カラム | 意味 |
|---|---|
wiki_page_id |
所属 Wiki ページ |
no |
ページ内 asset 番号 |
sha256 |
添付バイト列の SHA256 |
filename |
表示/ダウンロード用ファイル名 |
content_type |
MIME type |
byte_size |
サイズ |
created_by_user_id |
追加者 |
現行ソースでは明確な model/controller/画面導線は確認できない。したがって、schema は将来機能として正当化されたが、実装は未完である。
素材仕様
Material
materials はタグに紐づく素材である。
| 属性 | 仕様 |
|---|---|
tag_id |
必須・一意 |
url |
任意。file がなければ必須 |
file |
Active Storage 添付。url がなければ必須 |
parent_id |
親素材。任意だが廃止予定 |
created_by_user_id |
作成者 |
updated_by_user_id |
更新者 |
discarded_at |
論理削除 |
Material は MyDiscard ではなく独自に default_scope -> { kept } を持つ。
タグ制約
素材に紐づくタグは character または material カテゴリでなければならない。
注意: バリデーション文言は「素材カテゴリのタグ」と言ってゐるが、実装上は character も許可してゐる。仕様としては character も素材を持てる で確定し、文言を直すべきである。
API
| API | 現行権限 | 確定仕様 | 内容 |
|---|---|---|---|
GET /materials |
public | public | 素材一覧 |
GET /materials/:id |
public | public | 素材詳細。関連 Wiki 本文も返す |
POST /materials |
current_user 必須 | member+ | 素材作成 |
PUT/PATCH /materials/:id |
member+ | member+ | 素材更新 |
DELETE /materials/:id |
member+ | member+ | 論理削除 |
確定仕様: 素材作成権限
素材作成は member 以上を原則とする。現行の guest 作成可能状態は設計意図ではなく実装バグとして扱ふ。
ただし、URL-only 素材については guest への開放余地がある。理由は、file 付き素材と違ひ、オブジェクト・ストレージを直接圧迫しないためである。
推奨仕様:
| 入力 | guest | member+ | 理由 |
|---|---|---|---|
| URL-only | 将来検討 | 可 | storage 圧迫がない。荒らし URL への moderation は別途必要 |
| file-only | 不可 | 可 | storage 圧迫・危険ファイル・著作権対応が重い |
| URL + file | 不可 | 可 | file を含むため member+ |
現時点では単純に POST /materials を member+ へ制限するのが最優先である。URL-only guest 開放は、rate limit、通報/削除導線、URL preview の安全化後に検討する。
確定仕様: material_versions
material_versions は必要なテーブルであり、残骸ではない。素材にも履歴を持たせる。
snapshot 対象:
| 対象 | 必要性 |
|---|---|
| tag | 素材が何に紐づいてゐたかを復元するため必要 |
| URL | URL-only / 参照先変更履歴として必要 |
| file blob | 添付ファイル差替え履歴として必要 |
| 更新者 | 誰が変更したかを追跡するため必要 |
| parent | 廃止予定のため新仕様では対象外 |
推奨 material_versions 返却形:
interface MaterialVersion {
id: number
materialId: number
versionNo: number
eventType: 'create' | 'update' | 'discard' | 'restore'
tag: { id: number, name: string | null, category: string | null } | null
url: string | null
fileBlobId: number | null
fileUrl: string | null
createdByUser: { id: number, name: string | null } | null
createdAt: string
}
添付 issue #306 にも material_versions 実装が明記されてゐる。素材管理は UI だけでなく履歴まで含めて完了とみなすべきである。
上映会仕様
概要
theatres は共同視聴部屋である。
| 属性 | 意味 |
|---|---|
name |
部屋名 |
opens_at / closes_at |
開始/終了 |
kind |
種別 |
current_post_id |
現在再生中投稿 |
current_post_started_at |
再生開始時刻 |
host_user_id |
現在ホスト |
next_comment_no |
コメント採番 |
在席とホスト
PUT /theatres/:id/watching はログイン済みユーザを在席として更新する。
theatre_watching_users.expires_atは 30 秒後。- 現在 host がいない、または host が active でなければ、呼び出しユーザが host になる。
- フロントは約 1.5 秒ごとに watching を送る。
返却:
host_flgpost_idpost_started_atpost_elapsed_mswatching_usersskip_vote
確定仕様: host 制御の方向性
現行の active watching user から自動で host を選ぶ仕様は暫定である。開発者意図としては、上映会の進行制御はできるだけサーバ側で担ひたい。
したがって、長期仕様では次を目標にする。
| 領域 | 方針 |
|---|---|
| 次投稿決定 | サーバが theatre 状態・番組表・skip イベント・重みを見て決定 |
| 再生不能検知 | クライアント通知は使ふが、最終判断はサーバ状態に集約 |
| host | 手動操作が必要な暫定制御者。恒久的な絶対権限者ではない |
| 荒らし耐性 | 公表前に host 乗っ取り・連続 next・skip 連打への制限を入れる |
skip 投票と next_post の権限を分離するか という問いは、現時点では仕様語彙が曖昧だった。ここでは次の意味に定義する。
- skip 投票: 視聴者全員ができる意思表示。
- next_post: 現行では host だけが実行できる即時進行操作。
- 将来仕様: next_post も単なる host 命令ではなく、サーバの進行判断 API に寄せる。
次投稿選択
PATCH /theatres/:id/next_post は現行 host のみ実行可能。
TheatrePostSelector は次の投稿を候補にする。
urlにnicovideo.jp,youtube.com/watch,youtu.beを含む投稿。- 現在投稿は除外。
重みは 1.0 / (1.0 + penalty)。
penalty は、active user が過去に skip した投稿のタグに基づく。つまり、視聴者が嫌がったタグを持つ投稿ほど選ばれにくい。
添付 issue #360 により、上映会で上位タグを持つタグが表示されないバグが P1 として課題化されてゐる。上映会はタグ継承・親タグ展開と強く結びつくため、タグ表示は単純な直接付与タグだけでは不足する。
番組表
theatre_programmes は再生履歴/番組表である。
| カラム | 意味 |
|---|---|
theatre_id |
部屋 |
position |
連番位置 |
post_id |
投稿 |
created_at |
追加時刻 |
TheatrePostAdvancer は次投稿へ進むたびに position を加算して programme を作る。
コメント
theatre_comments は部屋内コメントである。
- 主キーは
(theatre_id, no)。 - 投稿時に theatre を lock し、
next_comment_noを採番する。 - 削除は投稿者本人のみ。
- 削除後は
discarded_atを設定し、一覧ではcontent: null,deleted: trueとして返す。 - フロントは最新 20 件を中心に取得する。
スキップ投票
theatre_skip_votes は (theatre_id, post_id, user_id) 複合主キー。
PUT /skip_vote:
- ログイン必須。
post_id必須。- watching を更新。
- 現在投稿と request post_id が違へば 409。
- vote 作成。
- active users の過半数に達したら skip 確定。
必要票数:
required_count = floor(active_watching_users_count / 2) + 1
スキップ確定時:
TheatreSkipFinalizerがtheatre_skip_eventsを作る。- voter 一覧を
theatre_skip_event_votersに保存。 - skip 時点の投稿タグを
theatre_skip_event_tagsに保存。 - 当該投稿の skip vote を削除。
TheatrePostAdvancerで次投稿へ進む。
DELETE /skip_vote で自分の投票を取り消せる。
再生同期
フロント TheatreDetailPage は server elapsed と player current time の差が 5 秒を超えた場合に seek する。
- YouTube は
react-youtube。 - ニコニコは
NicoViewerによる JS APIpostMessage。 - duration が 0 以下など再生不能っぽい場合、現行では host が次投稿へ進める。
埋め込み
PostEmbed の対応:
| URL | 表示 |
|---|---|
| NicoVideo | NicoViewer iframe + postMessage |
| YouTube | react-youtube |
| Twitter/X status | blockquote + widgets script |
| その他 | 確認後 iframe |
ニコニコ iframe の loadComplete timeout は 8000 ms。
Gekanator 仕様
概要
Gekanator は投稿当てゲームである。投稿群に関する質問を出し、回答から候補投稿を絞り、最終的に投稿を推測する。
用途はゲームだけではない。終了後の質問追加・回答保存により、投稿間類似や識別質問を蓄積する学習機構でもある。
確定仕様: 公開範囲
Gekanator は恒久的な admin-only ツールではない。
段階:
| 段階 | 公開範囲 | 目的 |
|---|---|---|
| 現行 | admin のみ | 学習データ作成、質問品質確認、パラメータ調整 |
| 調整後 | member または限定ユーザ | 追加学習、UX 確認 |
| 公表版 | 一般ユーザ | おたのしみゲームとして公開 |
公表版では、内部パラメータ・候補スコア・調整用情報は非表示にする。添付 issue #361 により「グカネータ公開」が課題化されてゐる。
現行権限
バックエンド API は current_user&.admin? でなければ 404 を返す。フロントも /gekanator は admin 以外 NotFound を表示する。
これは現行実装であって最終仕様ではない。
投稿カタログ
GET /gekanator/posts は現行 admin 専用。
返却投稿:
- 全投稿。
tagspreload。- サムネイル付き。
- 並びは
COALESCE(original_created_before - 1分, original_created_from, created_at) DESC, id DESC。
質問カタログ
GET /gekanator/questions は現行 admin 専用。
質問 kind:
| kind | 内容 |
|---|---|
tag |
特定タグを含むか |
source |
URL host/source |
title |
タイトル性質 |
original_date |
年/月/月日 |
post_similarity |
特定投稿との近さ/例示回答 |
質問 source:
user_suggestedai_generatedadmin_curated
status:
pendingacceptedrejecteddisabled
現行 API は accepted questions を返す。
回答値
GekanatorQuestionSuggestion::ANSWERS:
| 値 | 意味 |
|---|---|
yes |
はい |
no |
いいえ |
partial |
部分的にそう |
probably_no |
たぶん違ふ |
unknown |
わからない |
フロント推測ロジック
frontend/src/lib/gekanator.ts と GekanatorPage.tsx による。
主な定数:
| 定数 | 値 | 意味 |
|---|---|---|
questionsBetweenGuesses |
25 | 通常推測までの質問数 |
minQuestionsBeforeCertainGuess |
5 | 確信時の最低質問数 |
certainGuessPercent |
99.5 | ほぼ確定判定 |
runnerUpMaxPercent |
0.5 | 2位候補の上限 |
hardMaxQuestions |
80 | 最大質問数 |
softenedAnswerWeight |
0.35 | 答え緩和重み |
confidenceTemperature |
6 | 確率化温度 |
maxQuestionSuggestionsPerGame |
3 | 追加質問上限 |
質問選択は、候補分割力、冗長性、排他条件、優先度、seed による決定性を組み合はせる。
候補が潰れる場合、高難度・非 unknown の過去回答を 0.35 に緩和して復旧を試みる。
確定仕様: 質問数ルール
通常は 25 問を経過するまで推測しない。「続けますか → はい」の流れでも 25 問ルールを維持する。
例外は、100% 近似に近い確信状態で、候補が実質的に一意になった場合のみである。ただし、この例外も最低質問数・2 位候補との差・誤答時の UX を見て慎重に扱ふ。
ゲーム保存
POST /gekanator/games は現行 admin 専用。
入力:
guessed_post_idcorrect_post_idanswersJSON
保存値:
won = guessed_post_id == correct_post_idquestion_count = answers.length
質問追加
終了後、ユーザは質問を最大 3 件追加できる。
POST /gekanator/question_suggestions:
- 現行 admin 専用。
game_idquestion_text1000 文字以内。answerは enum。unknownは promoter で質問化されない。
Gekanator::QuestionSuggestionPromoter は unknown 以外を accepted な post_similarity question として即昇格させ、correct_post に対する example を作る。
追加質問回答
GET /gekanator/games/:id/extra_questions は、ゲーム後に correct_post へ未回答の post_similarity question を最大 2 件返す。
POST /extra_question_answers は、それらへの回答を GekanatorQuestionExample として保存する。
確定仕様: AI 変換
POST /gekanator/question_suggestions/:id/ai_convert は存在するが、Gekanator::QuestionSuggestionAiConverter は現行 NotImplementedError を投げる。
AI は質問分類だけでなく、既存投稿への回答補完も行ふ。
担当範囲:
| 処理 | 内容 |
|---|---|
| 質問分類 | ユーザ文を tag, source, title, original_date, post_similarity 等へ分類 |
| 正規化 | 曖昧な質問文をゲームで扱へる構造へ変換 |
| 回答補完 | 既存投稿群に対して yes/no/partial/probably_no/unknown を推定 |
| 信頼度 | AI 推定には confidence を持たせ、低信頼は pending に留める |
| 監査 | AI が作った質問・回答は ai_generated として追跡可能にする |
AI モデル選定
初期実装は OpenAI Responses API + Structured Outputs を前提とする。
推奨初期値:
| 項目 | 値 |
|---|---|
| 環境変数 | GEKANATOR_AI_MODEL |
| 初期モデル | gpt-5.4-mini |
| 出力形式 | JSON Schema strict structured output |
| reasoning | none または low から開始 |
| 大量補完 | Batch API または夜間 Rake task |
| 予算 | 仮置き。半年 500 円程度を目標に、run 数・token 数・batch 割引で調整 |
gpt-5.4-mini は価格と品質の妥協点としての初期値であり、固定ではない。分類だけなら将来 gpt-5.4-nano 等のさらに低価格モデルへ落とせるやぅに、モデル ID は必ず環境変数化する。
旧メモの AI 予算:
| 項目 | 値 |
|---|---|
| 月上限 | 450 円 |
| 1 run 見積 | 5 円 |
| 超過見込み | 402 blocked_budget |
この見積は仮置きであり、確定予算ではない。現在の希望は「半年で 500 円くらゐ」であるため、1 run 5 円の設計は高すぎる。AI 補完は per game 即時実行ではなく、batch 化・cache 化・差分実行に寄せるべきである。
プレビュー API
タイトル取得
GET /preview/title:
- current_user 必須。
- URL 必須。
- scheme がなければ
http://を補完。 URI.openで HTML 取得。- Nokogiri で
<title>を返す。 - open/read timeout は 5 秒。
サムネイル生成
GET /preview/thumbnail:
- current_user 必須。
- URL 必須。
- scheme がなければ
http://を補完。 node lib/screenshot.js <url> <path>を実行。- 生成画像を MiniMagick で 180x180 に resize。
- PNG inline で返す。
確定仕様: セキュリティ方針
Preview API は guest 相当にも提供したい機能である。ただし、危険性は高い。クリーンに守れないなら機能廃止も検討対象である。
最低必須対策:
| 対策 | 必須度 | 内容 |
|---|---|---|
| scheme allowlist | 必須 | http, https のみ |
| DNS 解決後 IP 検査 | 必須 | private, loopback, link-local, multicast, metadata IP を拒否 |
| redirect 検査 | 必須 | redirect 先 URL も同じ検査を繰り返す |
| content length 上限 | 必須 | title HTML 取得、画像、screenshot 出力に上限 |
| timeout | 必須 | connect/read/browser 全て短めに制限 |
| concurrency 制限 | 必須 | screenshot は特に重いので同時実行数を絞る |
| rate limit | 必須 | IP + user + URL host 単位で制限 |
| user agent | 推奨 | 明示 UA を使ひ、必要なら deny されても落ちない UX にする |
| cache | 推奨 | 同一 URL の title/thumbnail を短時間 cache |
rate limit 推奨仕様
Preview API を guest に残すなら、Rails 単体の before_action ではなく、Rack middleware または reverse proxy で一次防衛するのがよい。
推奨値の初期案:
| 単位 | /preview/title |
/preview/thumbnail |
|---|---|---|
| IP | 60 req / 10 min | 10 req / 10 min |
| user | 120 req / 10 min | 20 req / 10 min |
| URL host | 30 req / 10 min | 10 req / 10 min |
thumbnail は headless browser を起動するため、title より厳しくする。
実装差分
現行 API は current_user 必須だが、guest は自動作成されるため実質的に広く使へる。任意 URL をサーバから取得・Node screenshot するので、SSRF/内部ネットワーク到達/リソース消費のリスクがある。これは公表前の赤信号である。
類似度計算
Similarity::Calc は汎用化されてゐる。
| task | 対象 | 関係 |
|---|---|---|
post_similarity:calc |
Post | tags による投稿類似 |
tag_similarity:calc |
Tag | posts によるタグ類似 |
計算仕様:
- 対象集合 ID を sort。
- 2集合の intersection size を計算。
cos = intersection / sqrt(|a| * |b|)。- 各 record につき上位 20 件を保存。
- similarity table は全削除後に insert_all。
データベース仕様
現行 schema の主要テーブル
2026-06-10 migration まで反映された schema では、主に次のテーブルが存在する。
| 領域 | テーブル |
|---|---|
| Active Storage | active_storage_blobs, active_storage_attachments, active_storage_variant_records |
| ユーザ/BAN | users, ip_addresses, user_ips, settings, user_post_views |
| 投稿 | posts, post_tags, post_implications, post_versions, post_similarities |
| タグ | tags, tag_names, tag_implications, tag_versions, tag_similarities, tag_name_sanitisation_rules |
| Nico | nico_tag_relations, nico_tag_versions |
| ニジラー | deerjikists |
| Wiki | wiki_pages, wiki_revisions, wiki_lines, wiki_revision_lines, wiki_versions, wiki_assets |
| 素材 | materials, material_versions |
| 上映会 | theatres, theatre_comments, theatre_watching_users, theatre_programmes, theatre_skip_votes, theatre_skip_events, theatre_skip_event_tags, theatre_skip_event_voters |
| Gekanator | gekanator_games, gekanator_questions, gekanator_question_suggestions, gekanator_question_examples, gekanator_ai_runs |
旧本番 DB ダンプの利用実態
2026-04-25 ダンプは現行 schema より古い。Gekanator、上映会スキップ、投稿親子など一部は含まれてゐない。とはいへ、当時の利用規模を把握するには有用である。
主な行数:
| テーブル | 行数 |
|---|---|
users |
42805 |
posts |
893 |
tags |
5961 |
tag_names |
5996 |
post_tags |
45345 |
post_versions |
14351 |
tag_versions |
2026 |
nico_tag_versions |
3842 |
nico_tag_relations |
314 |
deerjikists |
99 |
materials |
37 |
wiki_pages |
51 |
wiki_revisions |
197 |
theatres |
1 |
theatre_comments |
98 |
post_similarities |
43140 |
tag_similarities |
116760 |
解釈:
usersが極端に多い。自動 guest 生成と bot/巡回アクセスの影響が濃い。post_tagsが 4.5 万件あり、投稿あたりタグ量はかなり多い。post_versionsが 1.4 万件あり、同期/編集履歴が大量に蓄積されてゐる。- 類似度テーブルは
posts * 20/tags * 20に近い規模で、静的計算済みキャッシュとして動いてゐる。
フロントエンド仕様
API 通信
frontend/src/lib/api.ts:
- Axios base URL は
API_BASE_URL。 - 全リクエストに
X-Transfer-Codeを付与。 - JSON レスポンスは deep camelCase 化。
responseType: 'blob'の場合は変換しない。
型定義
frontend/src/types.ts は API レスポンスの期待形を定義する。ただし、前述の PostVersion.parentPosts のやぅに、実 API とズレてゐる箇所がある。
編集権限
canEditContent は admin または member を true とする。
画面設計上の特徴
App.tsx初期化時に guest 自動作成。- route transition に
framer-motion。 - 上部ナビ
TopNav。 - MDX 利用規約。
- Gekanator は admin only route。
- Post detail は pathname を key にして再 mount。
テスト状況
バックエンド
RSpec が整備されてゐる。確認できる主要テスト領域:
- users / auth / BAN 系。
- posts / post implications / versions / conflict。
- tags / aliases / nico tags / tag children / deerjikists。
- wiki / commit / diff / conflict / title collision / history integrity。
- materials。
- theatres / comments / programmes。
- Gekanator games / learning。
- YouTube/Nico sync tasks。
- similarity calculation。
フロントエンド
Vitest が導入され、かなり広範囲にテストがある。
確認できる主な領域:
- Post 系 components/pages。
- Tag 系 components/pages。
- Wiki pages。
- Materials pages。
- Theatre page。
- Gekanator scoring lib。
- API utility / error handling。
- common UI components。
- NicoViewer / PostEmbed / TwitterEmbed。
2026-05-10 時点の「フロントテストなし」状態からは明確に改善してゐる。
実装上の不整合・危険点
この節は、仕様決定後も実装が追いついてゐない箇所を列挙する。ここを曖昧にしたまま公表すると、設計負債が増える。
P0: Preview API の SSRF/リソース消費リスク
/preview/title と /preview/thumbnail はサーバから任意 URL へアクセスする。guest 自動生成により、事実上かなり広い入口である。
仕様としては guest 提供の余地を残すが、次は必須である。
- private IP / localhost / link-local / metadata IP の拒否。
- scheme allowlist を http/https に固定。
- redirect 先検査。
- content length 上限。
- screenshot queue/timeout/concurrency 制限。
- IP + user + host rate limit。
守れないなら機能ごと廃止を検討する。便利さよりサーバ防衛が優先である。
P0: guest 自動作成による users 肥大化
旧 DB で users = 42805。実利用規模に比して異様に多い。bot が来るたび user を発行してゐる可能性が高い。
guest 自動作成は継続するが、以下は未実装リスクとして残る。
POST /usersrate limit。- bot UA / localStorage 不可環境への抑制。
- 編集実績のない guest 掃除 task。
- user 分析用の
last_seen_at等。
P0: 素材作成権限が緩い
POST /materials は current_user だけを要求する。guest 自動作成と合はせると、匿名同然で素材作成できる。
確定仕様は member+ 作成である。file 素材は storage を食ふため、ここは早めに閉じるべきである。
P1: タグ親子編集権限が二重化
TagChildrenController: admin のみ。TagsController#update_all: member 以上でparent_tags更新可。
確定仕様は member+ なので、専用 API 側を修正する。あはせて issue #332 の循環登録禁止を入れないと、member 開放は危ない。
P1: post_versions の parent post 履歴 API が不足
schema と snapshot には parent_post_ids があるが、PostVersionsController が返してゐない。フロント型には parentPosts がある。
確定仕様として、親投稿変更履歴は表示必須である。parent_posts の返却を追加する。
P1: material_versions, wiki_assets は必要だが未実装
開発者回答により、どちらも将来機能として必要と確定した。
material_versions: tag, URL, file blob, 更新者の履歴。wiki_assets: Wiki 内画像/添付、next_asset_no、sha256 連動。
したがって「残骸として削除」ではなく「仕様化して実装」が正しい。
P1: inheritance_code の一意制約なし
認証トークンである以上、DB 一意 index を張るべき。UUID 衝突は現実的に低くても、仕様としては弱い。
P1: 上映会 host 乗っ取り耐性
active host が切れたら次の watching ユーザが host になる。小規模内輪ではよいが、公表後は荒らしが再生制御を握る可能性がある。
長期方針はサーバ主導進行である。現行 host は暫定制御者と見るべきで、連続 next、短時間 host 交替、再生不能偽装への防御が必要である。
P2: Material のバリデーション文言と許可カテゴリのズレ
文言は「素材カテゴリ」だが、実装は character も許可する。仕様として character も素材を持てるため、文言を直すべきである。
P2: Gekanator AI 変換 API は未実装
API と予算 model はあるが converter は NotImplemented。公表版では質問構成 AI を稼動させる予定なので、未実装表示ではなく実装対象である。
P2: issue 管理との接続が手動
今回の issue 一覧は添付 JSON で取り込んだ。継続的な同期手段は未確定である。全件共有方針は妥当だが、手動添付だけだと仕様書がすぐ腐る。
開発者ヒアリング反映結果
本章は、前版で未確定だった H-001 から H-011 への回答を記録する。回答内容は本文へ反映済みである。
| ID | 状態 | 反映先 |
|---|---|---|
| H-001 | 回答済み | タグ仕様 / タグ親子 |
| H-002 | 回答済み | 素材仕様 / 素材作成権限 |
| H-003 | 回答済み | 認証・ユーザ・BAN / guest 自動作成 |
| H-004 | 回答済み | 投稿仕様 / 投稿履歴 |
| H-005 | 回答済み | 素材仕様 / material_versions |
| H-006 | 回答済み | Wiki 仕様 / Wiki asset |
| H-007 | 回答済み | Gekanator 仕様 / 公開範囲 |
| H-008 | 回答済み | Gekanator 仕様 / AI 変換 |
| H-009 | 回答済み | 上映会仕様 / host 制御 |
| H-010 | 回答済み | Preview API / セキュリティ方針 |
| H-011 | 回答済み | Gitea 課題一覧反映 |
残る未決定事項
回答後も、次はまだ設計決定が必要である。
| 項目 | 未決定内容 | 推奨 |
|---|---|---|
| guest bot 対策 | cookie/localStorage なしアクセス、bot UA、掃除 task の詳細 | POST /users rate limit と未使用 guest 掃除 task から着手 |
| URL-only 素材の guest 開放 | いつ・どの条件で許可するか | 当面は member+ に閉じる |
| Gekanator AI 予算 | 半年 500 円程度をどう守るか | batch 化、cache 化、差分実行、model env 化 |
| 上映会 host | 完全サーバ主導へどう移行するか | host を暫定権限に格下げし、server advancer を育てる |
| Preview API | 防御実装不能な場合の廃止判断 | SSRF 防御を実装できなければ thumbnail 生成から止める |
| issue 同期 | 今後どう ChatGPT と共有し続けるか | 当面は JSON 添付。将来は read-only token + 手元 export script |
Gitea 課題一覧反映
添付された Gitea issue JSON を取り込み、2026-06-11 時点の仕様書へ反映した。今回の添付には 50 件が含まれ、全件 open である。
集計
Priority
| priority | 件数 |
|---|---|
P1 |
8 |
P2 |
26 |
P3 |
15 |
優先度なし |
1 |
Status
| status | 件数 |
|---|---|
status/blocked |
8 |
status/in-progress |
1 |
status/ready |
39 |
status/review |
1 |
statusなし |
1 |
Area
| area | 件数 |
|---|---|
area/backend |
26 |
area/frontend |
25 |
Type
| type | 件数 |
|---|---|
type/bug |
8 |
type/enhancement |
24 |
type/task |
17 |
typeなし |
1 |
P1 課題
P1 は公表前または主要 UX に直撃するものとして扱ふ。
| issue | title | status | area | type |
|---|---|---|---|---|
| #360 | 上映会で上位タグを持つタグが表示されないバグ | status/ready | area/backend, area/frontend | type/bug |
| #356 | 天保暦対応 | status/ready | area/frontend | type/enhancement |
| #353 | 局所記載 (#351) | |||
| #344 | 今後の課題整理 | status/ready | type/task | |
| #334 | posts.thumbnail_base を後からでも変更できるやぅにする | status/ready | area/backend, area/frontend | type/enhancement |
| #332 | 上位タグの循環を登録できないやぅにする | status/ready | area/backend | type/bug |
| #306 | 素材管理(#99 の続き) | status/ready | area/backend, area/frontend | type/enhancement |
| #170 | 別の投稿からタグ・インポート | status/ready | area/backend, area/frontend | type/enhancement |
仕様へ反映した主な issue
| issue | 仕様上の扱ひ |
|---|---|
| #361 グカネータ公開 | Gekanator は恒久 admin-only ではなく、公表予定機能として定義した |
| #360 上映会で上位タグを持つタグが表示されないバグ | 上映会のタグ表示は親タグ/継承タグを含むべき既知バグとして記載した |
| #356 天保暦対応 | 日付表示/旧暦対応の未実装拡張として課題一覧に反映した |
#354 post_versions.tags_json |
投稿履歴タグ snapshot の構造化予定として記載した |
| #353/#351 局所記載 | 投稿タグに対する sections 構造の未完機能として扱ふ |
| #337 Wiki 履歴 | Wiki 履歴は wiki_versions を根拠にする方針として課題一覧に反映した |
| #336 Wiki 本文検索 | Wiki 検索仕様の既知バグとして扱ふ |
#334 posts.thumbnail_base 変更 |
投稿更新対象の拡張課題として扱ふ |
| #332 タグ親子循環禁止 | タグ親子 member 開放前の必須制約として記載した |
| #306 素材管理 | material_versions を必要機能として確定した根拠に含めた |
| #301 備考欄 | post_remarks, post_remark_versions の将来拡張として扱ふ |
| #227 限定公開 | 投稿閲覧権限モデルの将来拡張として扱ふ |
| #123 同定文字 | タグ正規化/検索同定の将来仕様として扱ふ |
全 issue 一覧
| issue | kind | title | priority | status | type | area | milestone | due |
|---|---|---|---|---|---|---|---|---|
| #361 | Issue | 【おたのしみ】グカネータ公開 | P3 |
status/ready |
type/enhancement |
area/backend, area/frontend | ||
| #360 | Issue | 上映会で上位タグを持つタグが表示されないバグ | P1 |
status/ready |
type/bug |
area/backend, area/frontend | ||
| #356 | Issue | 天保暦対応 | P1 |
status/ready |
type/enhancement |
area/frontend | ||
| #354 | Issue | post_versions.tags_json の作成 → post_versions.tags を廃止 |
P2 |
status/ready |
type/enhancement |
area/backend, area/frontend | ||
| #353 | PR | 局所記載 (#351) | P1 |
`` | `` | |||
| #352 | Issue | サムネつきで手動投稿した際にエラーとなる | P2 |
status/ready |
type/bug |
area/backend | ||
| #351 | Issue | 局所記載のしくみ作り | `` | status/review |
type/enhancement |
area/backend, area/frontend | ||
| #344 | Issue | 今後の課題整理 | P1 |
status/ready |
type/task |
|||
| #337 | Issue | Wiki 履歴,wiki_versions を根拠にする | P2 |
status/ready |
type/enhancement |
area/backend, area/frontend | ||
| #336 | Issue | Wiki 検索で本文が検索できないバグ | P2 |
status/ready |
type/bug |
area/backend | ||
| #335 | Issue | API の直発行をやめる | P2 |
status/ready |
type/task |
area/frontend | ||
| #334 | Issue | posts.thumbnail_base を後からでも変更できるやぅにする | P1 |
status/ready |
type/enhancement |
area/backend, area/frontend | ||
| #332 | Issue | 上位タグの循環を登録できないやぅにする | P1 |
status/ready |
type/bug |
area/backend | ||
| #322 | Issue | ニジラー情報の履歴管理 | P2 |
status/ready |
type/enhancement |
area/backend | ||
| #320 | Issue | ニコニコ連携画面の TextArea に対するタグ補完 | P2 |
status/ready |
type/enhancement |
area/frontend | ||
| #306 | Issue | 素材管理(#99 の続き) | P1 |
status/ready |
type/enhancement |
area/backend, area/frontend | ||
| #301 | Issue | 備考欄の作成 | P2 |
status/ready |
type/enhancement |
area/backend, area/frontend | ||
| #291 | Issue | “閲覧済” を押しても反映されないバグ | P2 |
status/ready |
type/bug |
area/frontend | ||
| #285 | Issue | モデルに対する Spec | P3 |
status/ready |
type/task |
area/backend | ||
| #283 | Issue | deerjikists.tag_id に外部キー制約 |
P2 |
status/ready |
type/bug |
area/backend | ||
| #279 | Issue | 検索のワイルドカード | P2 |
status/in-progress |
type/enhancement |
area/backend | ||
| #273 | Issue | タグ補完コンポーネント共通化 | P2 |
status/ready |
type/task |
area/frontend | ||
| #272 | Issue | タグ補完,前方検索を優先表示するが,後方検索も行ふやぅにする(末尾に追記) | P2 |
status/ready |
type/enhancement |
area/backend | ||
| #270 | Issue | 個人用メモを作成可能に | P2 |
status/ready |
type/enhancement |
area/backend, area/frontend | ||
| #268 | Issue | タグのドラッグがスマホのスクロールと干渉する問題 | P2 |
status/ready |
type/bug |
area/frontend | ||
| #266 | Issue | Wiki 新規作成直後に内容表示されないバグ対応 | P2 |
status/ready |
type/bug |
area/frontend | ||
| #242 | Issue | tags.post_count にインデクス |
P2 |
status/ready |
type/task |
area/backend | ||
| #235 | Issue | フロントのビルド軽量化 | P2 |
status/ready |
type/task |
area/frontend | ||
| #227 | Issue | 限定公開用のしくみ作り | P3 |
status/ready |
type/enhancement |
area/backend | ||
| #225 | Issue | GET /tag_names/name/:name により,タグや Wiki を含む情報を返す | P2 |
status/ready |
type/enhancement |
area/backend | ||
| #221 | Issue | Wiki 排他 | P2 |
status/ready |
type/enhancement |
area/backend, area/frontend | ||
| #213 | Issue | 広場追加の自動チェックボックス,ボタンにして押したタイミングでの取得にする | P2 |
status/ready |
type/enhancement |
area/frontend | ||
| #205 | Issue | 申請フォーム | P2 |
status/ready |
type/enhancement |
area/backend | ||
| #172 | Issue | 上位タグ設定画面 | P3 |
status/ready |
type/enhancement |
area/backend, area/frontend | ||
| #170 | Issue | 別の投稿からタグ・インポート | P1 |
status/ready |
type/enhancement |
area/backend, area/frontend | 一般公開 | |
| #164 | Issue | 【運用】bot 操作タグの投稿を 0 件までに減らす | P3 |
status/blocked |
type/task |
一般公開 | 2026-06-30 | |
| #163 | Issue | 【運用】bot 操作タグの投稿を 100 件までに減らす | P3 |
status/blocked |
type/task |
2026-06-15 | ||
| #162 | Issue | 【運用】bot 操作タグの投稿を 200 件までに減らす | P3 |
status/blocked |
type/task |
2026-05-31 | ||
| #161 | Issue | 【運用】bot 操作タグの投稿を 300 件までに減らす | P3 |
status/blocked |
type/task |
2026-05-15 | ||
| #160 | Issue | 【運用】bot 操作タグの投稿を 400 件までに減らす | P3 |
status/blocked |
type/task |
2026-04-30 | ||
| #159 | Issue | 【運用】bot 操作タグの投稿を 500 件までに減らす | P3 |
status/blocked |
type/task |
2026-04-15 | ||
| #158 | Issue | 【運用】bot 操作タグの投稿を 600 件までに減らす | P3 |
status/blocked |
type/task |
2026-03-31 | ||
| #152 | Issue | Pixiv の埋込み | P2 |
status/ready |
type/enhancement |
area/frontend | ||
| #151 | Issue | ニジカ投稿局の埋込み | P2 |
status/ready |
type/enhancement |
area/frontend | ||
| #150 | Issue | bilibili の埋込み | P2 |
status/ready |
type/enhancement |
area/frontend | ||
| #149 | Issue | Tiktok の埋込み | P2 |
status/ready |
type/enhancement |
area/frontend | ||
| #147 | Issue | コントローラをサービスに分離 | P3 |
status/ready |
type/task |
area/backend | ||
| #138 | Issue | bot 操作タグのみに限定し,自動で楽曲に関するタグを付与するバッチ作成 | P3 |
status/ready |
type/task |
area/backend | ||
| #123 | Issue | 同定文字の制定 | P3 |
status/ready |
type/task |
一般公開 | ||
| #122 | Issue | 【運用】bot 操作タグの投稿を 700 件までに減らす | P3 |
status/blocked |
type/task |
2026-03-15 |
次回更新方針
- 本書を実装作業用に分解し、P0/P1 から Codex 向け issue prompt を作る。
POST /materials権限修正、PostVersionsControllerのparent_posts返却、Preview API 防御を先に潰す。- RSpec/Vitest/build/lint を実行し、仕様ではなく検証報告として別章へ追記する。
- API レスポンス例を主要 endpoint ごとに追加する。
- DB ER 図またはテーブル関係図を別紙化する。
- Gitea issue の次回添付または export script により、仕様書を腐らせない運用を決める。
外部参照
Gekanator AI モデル選定では、2026-06-11 時点の OpenAI 公式ドキュメントを参照した。モデル ID は変動し得るため、仕様では GEKANATOR_AI_MODEL による差し替へを必須とする。
- OpenAI Models: https://developers.openai.com/api/docs/models
- OpenAI Structured Outputs: https://developers.openai.com/api/docs/guides/structured-outputs
- OpenAI API Pricing: https://openai.com/api/pricing/