Appearance
API-005 外部データ取込 REST
概要
min-repo HTML インポート(ADM-011)と ana-slo データ取得(ADM-020)の「HTML 貼り付け → 機種名の紐付け確認 → 取込」を、管理画面を開かずに行うための REST。取込担当ロール(min_repo_importer)のユーザーがアプリケーションパスワードで呼ぶ。名前空間は data-import/v1。
処理は両画面と同じ DailyDataHtmlImportService を通す(HTML 検証・パース、紐付けの保存、db2023 への取込、取込履歴の記録)。画面で取り込んだ場合と REST で取り込んだ場合で、保存される紐付けと取り込まれる行は同じになる。
{source} は min-repo または ana-slo(DataImportSourceEnum)。取込元ごとに固定のルート(/min-repo/preview 等)を登録し、権限判定と処理はルートの取込元で行う。取込元ごとに取り込めるホールが決まっている。
source | 取り込めるホール(hall) | 必要な Capability |
|---|---|---|
min-repo | island / espasu | import_min_repo_html |
ana-slo | island / espasu / bigapple | import_ana_slo_html |
共通入力(preview / import)
JSON ボディ。
| param | 必須 | 型・制約 | 説明 |
|---|---|---|---|
source | いいえ | ルートの取込元と同じ値 | 省略可。指定するときはルートの {source} と同じ値にする(違えば 400) |
html | 条件 | string(5MB 以下) | 取込元ページの HTML。html_base64 とどちらか一方 |
html_base64 | 条件 | Base64(デコード後 5MB 以下) | HTML を Base64 にしたもの。WAF が HTML 本文を弾く環境向け。空白・改行は無視する |
date | はい | YYYY-MM-DD(実在する日付) | データの日付 |
hall | はい | 上表のホールキー | 取込先ホール |
mappings | いいえ | array(500 件以下) | 機種名の紐付け指定。各行は kishu_name(255 文字以内。前後の空白を除いた文字列で HTML 上の機種名と照合)と、master_id(既存マスタ ID)または new_name(新しい機種マスタ名)のどちらか一方 |
5MB の上限は DailyDataHtmlImportConstants::MAX_HTML_LENGTH(画面と共通)。
紐付けの決まり方
HTML 上の機種名ごとに、次の順で紐付け先を決める。画面の紐付け確認フォームで「現在の紐付けのまま確定」するのと同じ結果になる。
mappingsにmaster_idがあればその既存マスタmappingsにnew_nameがあればその名前の機種マスタ(同名が未登録なら新規登録)- すでに紐付いていればその機種マスタ
- どれでもなければ未紐付け(
unmapped)
mappings の kishu_name が HTML に無い機種名のときは 400(unknown に列挙)。
API-005-1 POST /{source}/preview
HTML を検証・パースし、機種ごとの紐付け状況を返す。DB には書かない。
入力(リクエスト)
共通入力に加えて次を受け付ける。
| param | 必須 | 型・制約 | 説明 |
|---|---|---|---|
include_masters | いいえ | bool | true のとき紐付け候補の機種マスタ一覧を masters に含める |
出力(レスポンス)
| field | 型 | 説明 |
|---|---|---|
success | bool | true |
source | string | 取込元 |
date | string | 日付 |
hall | string | ホールキー |
row_count | int | パースした行数 |
ready_to_import | bool | 未紐付けの機種が無く、同じ入力で import できるか |
unmapped | string[] | mappings を反映しても未紐付けの機種名 |
items | array | 機種名ごとの現在の紐付け { kishu_name, status, kishu_id, master_name, master_inactive, suggested_name }[]。status は画面の紐付け確認と同じ |
masters | array | include_masters 指定時のみ。{ id: int, name: string }[] |
HTTP ステータス: 200
API-005-2 POST /{source}/import
紐付けを保存して db2023 に取り込み、取込履歴に記録する。
入力(リクエスト)
共通入力に加えて次を受け付ける。
| param | 必須 | 型・制約 | 説明 |
|---|---|---|---|
delete_existing | いいえ | bool | true のとき同じ日付・ホールの既存データを削除してから取り込む(省略時 false) |
出力(レスポンス)
| field | 型 | 説明 |
|---|---|---|
success | bool | true |
message | string | DataImportRestCopy::IMPORT_SUCCESS |
source | string | 取込元 |
date | string | 日付 |
hall | string | ホールキー |
success_count | int | 取り込んだ行数 |
deleted_count | int | 削除した既存行数 |
delete_existing | bool | 既存データを削除したか |
new_name_count | int | new_name で紐付けた機種の数 |
HTTP ステータス: 200
同じ date・hall の取込が実行中のときは 409 を返す。ロックは DailyDataHtmlImportService が取るため、取込元をまたいで、また管理画面(ADM-011 / ADM-020)の取込とも排他になる(両取込元とも同じ db2023 の行に書くため)。Service は OptionRowLockInterface に依存し、実装の WordPressOptionRowLock が wp_options に所有トークン付きの行(_option_row_lock_<md5>、autoload しない)を 1 つ入れて取る。異常終了しても 300 秒経てば次の取込が引き継ぐ。DB エラーでロックを取れなかったときは 503(Messages::DAILY_DATA_IMPORT_LOCK_UNAVAILABLE)を返し、error_log に [option-row-lock] acquire failed: db error を 1 行書く。
ロックの有効期間(DailyDataHtmlImportService::IMPORT_LOCK_TTL_SECONDS = 300 秒)は PHP の max_execution_time より長くしておく。短いと、取込中に期限が切れて同じ日付・ホールの取込が重なり得る。MCP の import はクライアント側で 180 秒で打ち切るが、サーバー側の取込は止まらずロックも持ったままになる。打ち切り後すぐに再実行すると 409 になるので、latest-day や history で結果を確かめてから再実行する。致命的エラーや実行時間超過で解放されなかったロック行は、同じ日付・ホールを取り込まない限り wp_options に残る。期限切れの行は daily WP-Cron が読んだ値と一致する場合だけ消す(保守クリーンアップバッチ)。
API-005-3 GET /{source}/latest-day
ホールの db2023 最新日と、その翌日(次に取り込む想定の日付)を返す。ADM-020 の日付ズレ確認と同じ値。
入力(リクエスト)
| param | 必須 | 型・制約 | 説明 |
|---|---|---|---|
hall | はい | 上表のホールキー | 対象ホール |
出力(レスポンス)
| field | 型 | 説明 |
|---|---|---|
success | bool | true |
source | string | 取込元 |
hall | string | ホールキー |
latest_day | string/null | db2023 の最新日(YYYY-MM-DD)。無ければ null |
expected_next_day | string/null | 最新日の翌日。無ければ null |
HTTP ステータス: 200
API-005-4 GET /history
取込履歴(画面・REST の両方、min-repo / ana-slo の両方)を新しい順に返す。
入力(リクエスト)
| param | 必須 | 型・制約 | 説明 |
|---|---|---|---|
limit | いいえ | 1〜50 | 件数(省略時 20) |
source | いいえ | min-repo / ana-slo | 指定した取込元だけに絞る |
出力(レスポンス)
| field | 型 | 説明 |
|---|---|---|
success | bool | true |
items | array | { source, date, hall_keys, imported_at, row_count, user_id }[]。source が無い旧形式の履歴は min-repo、user_id は null |
HTTP ステータス: 200
失敗・エラー条件
失敗時のボディは { success: false, message }。message は固定文言だけを返し、例外メッセージや HTML は載せない。
| 条件 | HTTP | message |
|---|---|---|
ボディ・クエリの source がルートの取込元と違う | 400 | DataImportRestCopy::SOURCE_MISMATCH |
source 不正(history) | 400 | DataImportRestCopy::SOURCE_INVALID |
html / html_base64 が無い・両方ある | 400 | DataImportRestCopy::HTML_REQUIRED |
html_base64 をデコードできない | 400 | DataImportRestCopy::HTML_BASE64_INVALID |
| HTML が 5MB を超える | 400 | DataImportRestCopy::HTML_TOO_LONG |
date 不正 | 400 | DataImportRestCopy::DATE_INVALID |
hall がその取込元で取り込めない | 400 | DataImportRestCopy::HALL_INVALID |
delete_existing が bool として読めない | 400 | DataImportRestCopy::DELETE_EXISTING_INVALID |
mappings の形式不正 | 400 | DataImportRestCopy::MAPPINGS_INVALID |
mappings が 500 件を超える | 400 | DataImportRestCopy::MAPPINGS_TOO_MANY |
mappings に HTML に無い機種名がある | 400 | DataImportRestCopy::MAPPINGS_UNKNOWN(unknown 付き) |
| HTML が取込元のページとして検証できない(min-repo) | 400 | DataImportRestCopy::HTML_INVALID(errors 付き) |
| HTML からデータ行を取り出せない | 400 | 画面と同じパース失敗文言 |
紐付けの保存に失敗(master_id が無効等) | 400 | DataImportRestCopy::MAPPING_SAVE_FAILED |
limit 不正 | 400 | DataImportRestCopy::LIMIT_INVALID |
| 権限が無い | 403 | WordPress REST 標準の 403 |
| 同じ日付・ホールの取込が実行中(画面の取込も含む) | 409 | Messages::DAILY_DATA_IMPORT_LOCKED |
| DB エラーで取込ロックを取れない | 503 | Messages::DAILY_DATA_IMPORT_LOCK_UNAVAILABLE |
| 未紐付けの機種が残っている(import) | 422 | DataImportRestCopy::UNMAPPED_EXISTS(unmapped 付き) |
| レート制限を超えた | 429 | Messages::RATE_LIMIT_EXCEEDED |
| db2023 への取込に失敗 | 500 | DataImportRestCopy::IMPORT_FAILED |
| 最新日の取得に失敗 | 500 | DataImportRestCopy::LATEST_DAY_FAILED |
| preview / import の検証・パース中の予期しない例外 | 500 | DataImportRestCopy::UNEXPECTED_ERROR |
レート制限
ユーザーごとの固定ウィンドウ(60 秒)。BotRestRateLimiter(transient)で数える。
| 対象 | 上限 | カウンタを保存できないとき |
|---|---|---|
| import | 10 回 / 分 | 拒否(429) |
| preview | 30 回 / 分 | 通す |
| latest-day / history(共通) | 60 回 / 分 | 通す |
監査ログ
preview と import は BotRestAuditLogger で error_log に 1 行 JSON(接頭辞 [bot-rest-audit])を書く。記録するのはルート・ユーザー ID・取込元・日付・ホール・行数・結果(outcome)・件数だけで、HTML 本文と機種名の一覧は書かない(配列は件数に置き換える)。予期しない例外は outcome: error とし、例外の内容はレスポンスに載せず error_log にだけ書く。
権限・nonce
全エンドポイント共通。DataImportRestHandler + WordPressDataImportPermissionChecker。{source} のあるルートはルートの取込元の Capability を(ボディ・クエリの source では判定しない)、/history はどちらか一方の Capability を求める。権限が無いときは WordPress REST 標準の 403。アプリケーションパスワード(Basic 認証)で呼ぶ前提のため nonce は検証しない。Local バイパスの条件は WordPressPermissionChecker と同じ。