コンテンツにスキップ

ブログ API

AI が生成したブログ記事(blog)を管理するエンドポイントです。 ブログには次の2系統があり、どちらも同じ blog コレクションに保存されます。

出自(origin) 生成元 起点となる API
rewrite blog_rewriter(過去ブログのリライト) POST /v1/old_blogs/{old_blog_id}/rewrite・POST /v1/blogs/generation
generation blog_generation(レース回顧・予想の書き下ろし) POST /v1/blogs/generate

origin は保存しておらず、old_blog_id の有無から導出します。

ブログのステータス

ステータス 説明
generating 生成中。AI による生成・ビルドが実行中
draft 下書き。デフォルト
published 公開

generating は POST /v1/blogs の入力としては指定できません(400 を返します)。

ブログスキーマ

フィールド データ型 備考
id string _id
name string タイトル。本文 HTML にも見出しとして焼き込まれる
race_id string | null 紐づくレースの _id
race_type enum | null auto_racing / bicycle_racing / horse_racing
blog_type string | null シリーズ種別(例: ジョッキーインタビュー)
origin enum rewrite / generation。old_blog_id の有無から導出
old_blog_id string | null リライト元の過去ブログの _id
source_url string | null リライト元の掲載元 URL
category_id string | null category._id
category string | null カテゴリ名。category_id から解決する。削除済み・不正な ID では null
body_html string HTML形式。blog_builder が構築する
status enum generating / draft / published
created_at number UNIXタイムスタンプ(ミリ秒)
updated_at number UNIXタイムスタンプ(ミリ秒)

POST /v1/blogs/generate

概要

レースの情報からブログを新規生成します(元記事なし)。

kind によって書くものが変わります。

kind 内容 入力 必要な前提
recap(既定) レース回顧 レース結果・レース動画・コース画像(競馬のみ) 着順があること(不成立・中止は不可)
prediction レース予想 出走表と AI予想印(race.prediction) 予想データが登録済みであること

ブログを generating 状態で作成し、SQS(blog-generation キュー)経由で blog_generation Lambda へジョブを依頼します。

カテゴリは自動で紐づけます(回顧 → 「レース結果・回顧」、予想 → 「レース予想・検証」)。 カテゴリマスタに該当名が無い場合はカテゴリなしで作成します。

リクエストとレスポンス

ヘッダー

リクエストヘッダー

  • Content-Type: application/json

URI

POST /v1/blogs/generate

リクエストボディ

{
  "race_id": "string",
  "kind": "recap",
  "name": "string"
}
フィールド データ型 必須 備考
race_id string ◯ レースの _id
kind enum recap / prediction。デフォルト: recap
name string タイトル。省略時はレース名から「◯◯のレース回顧」「◯◯のレース予想」を自動生成する

レスポンス

202 Accepted

{
  "id": "string"
}

例外処理

説明 ステータスコード ステータス名
リクエストボディが不正 422 Unprocessable Entity
レースが存在しない 404 Not Found
kind=prediction で予想データが未登録 422 Unprocessable Entity
kind=recap で着順が無い(不成立・中止) 422 Unprocessable Entity
内部サーバーエラー 500 Internal Server Error

処理フロー

flowchart TD
    Start([POST /v1/blogs/generate]) --> Race[race_id でレースを取得]
    Race -->|存在しない| E404[404 Not Found]
    Race -->|kind=prediction| PredCheck[prediction の有無を確認]
    Race -->|kind=recap| VoidCheck[着順の有無を確認]
    PredCheck -->|未登録| E422[422 Unprocessable Entity]
    VoidCheck -->|着順なし| E422
    PredCheck -->|OK| Category[カテゴリを解決]
    VoidCheck -->|OK| Category
    Category --> Save[blog を generating で保存]
    Save --> SendSQS[SQS blog-generation キューへ送信]
    SendSQS --> Success[202 Accepted]

ルーティング順序

/blogs/generate は /blogs/{blog_id} より先に宣言されています。 後に宣言すると generate が blog_id として解釈されてしまうためです。


POST /v1/blogs/generation

概要

過去ブログ(old_blog)を指定してリライトを開始します。 ブログ名・カテゴリを指定できる点が POST /v1/old_blogs/{old_blog_id}/rewrite との違いです。

ブログを generating 状態で作成し、SQS(blog-rewrite キュー)経由で blog_rewriter Lambda へジョブを依頼します。 レース紐づき・シリーズ種別・掲載元 URL・埋め込みベクトルは old_blog から引き継ぎます。

リクエストとレスポンス

URI

POST /v1/blogs/generation

リクエストボディ

{
  "old_blog_id": "string",
  "category_id": "string",
  "name": "string"
}
フィールド データ型 必須 備考
old_blog_id string ◯ old_blog._id
category_id string category._id
name string タイトル。省略時は old_blog.name を使う

レスポンス

202 Accepted

{
  "id": "string"
}

例外処理

説明 ステータスコード ステータス名
old_blog_id が存在しない 400 Bad Request
内部サーバーエラー 500 Internal Server Error

POST /v1/blogs

概要

ブログを手動で新規作成します。

URI

POST /v1/blogs

リクエストボディ

{
  "name": "string",
  "body_html": "string",
  "race_id": "string",
  "race_type": "horse_racing",
  "old_blog_id": "string",
  "category_id": "string",
  "blog_type": "string",
  "status": "draft"
}
フィールド データ型 必須 備考
name string ◯
body_html string ◯ HTML形式
race_id string レースの _id
race_type enum auto_racing / bicycle_racing / horse_racing
old_blog_id string old_blog._id
category_id string category._id
blog_type string シリーズ種別
status enum draft / published。省略時は draft。generating は指定できない

レスポンス

201 Created

ブログスキーマを返します。

例外処理

説明 ステータスコード ステータス名
リクエストボディが不正 422 Unprocessable Entity
status に generating を指定した 400 Bad Request
内部サーバーエラー 500 Internal Server Error

GET /v1/blogs

概要

ブログの一覧を取得します。

リクエストとレスポンス

URI

GET /v1/blogs

クエリパラメータ

パラメータ データ型 必須 備考
id string ブログ ID の完全一致検索。ObjectId として不正な値は該当なしとして扱う
name string タイトルの部分一致検索(大文字小文字を区別しない)
race_id string レースでフィルタ
race_type enum auto_racing / bicycle_racing / horse_racing でフィルタ
blog_type string シリーズ種別でフィルタ
category_id string カテゴリでフィルタ
origin enum rewrite / generation でフィルタ(old_blog_id の有無に変換される)
status enum generating / draft / published でフィルタ
page number 取得するページ番号。デフォルト: 1
size number 1ページあたりの件数。デフォルト: 10
order enum asc / desc(updated_at 基準)。デフォルト: desc

レスポンス

200 OK

{
  "list": [
    {
      "id": "string",
      "name": "string",
      "race_id": "string",
      "race_type": "horse_racing",
      "blog_type": null,
      "origin": "rewrite",
      "old_blog_id": "string",
      "source_url": "string",
      "category_id": "string",
      "category": "レース結果・回顧",
      "body_html": "string",
      "status": "draft",
      "created_at": 1234567890000,
      "updated_at": 1234567890000
    }
  ],
  "current_page": 1,
  "total_count": 10
}

カテゴリ名(category)は ID ごとに引くと N+1 になるため、一覧では一括で解決しています。


GET /v1/blogs/{blog_id}

概要

指定したブログを取得します。

URI

GET /v1/blogs/{blog_id}

レスポンス

200 OK

ブログスキーマを返します。

例外処理

説明 ステータスコード ステータス名
ブログ記事が存在しない 404 Not Found
内部サーバーエラー 500 Internal Server Error

PUT /v1/blogs/{blog_id}

概要

指定したブログを更新します。

タイトル更新時は本文の見出しも差し替える

タイトル(name)は本文 HTML にも h2 見出しとして焼き込まれています。 name だけを更新すると本文の見出しが古いまま残るため、サーバー側で本文の見出しも あわせて差し替えます。body_html を同時に更新する場合は、渡された body_html を正とします。

URI

PUT /v1/blogs/{blog_id}

リクエストボディ

{
  "name": "string",
  "body_html": "string",
  "old_blog_id": "string",
  "category_id": "string",
  "blog_type": "string",
  "status": "published"
}
フィールド データ型 必須 備考
name string タイトル
body_html string HTML形式
old_blog_id string old_blog._id
category_id string category._id
blog_type string シリーズ種別
status enum generating / draft / published

指定されなかった(null の)フィールドは更新しません。

レスポンス

200 OK

更新後のブログスキーマを返します。

例外処理

説明 ステータスコード ステータス名
リクエストボディが不正 422 Unprocessable Entity
category_id 指定時、カテゴリが存在しない 400 Bad Request
ブログ記事が存在しない 404 Not Found
内部サーバーエラー 500 Internal Server Error

DELETE /v1/blogs/{blog_id}

概要

指定したブログを論理削除します。

URI

DELETE /v1/blogs/{blog_id}

レスポンス

204 No Content

例外処理

説明 ステータスコード ステータス名
ブログ記事が存在しない 404 Not Found
内部サーバーエラー 500 Internal Server Error

POST /v1/blogs/{blog_id}/thumbnail_generate

概要

ブログのサムネイル画像生成を開始します。

SQS(thumbnail-generation キュー)経由で thumbnail_generation Lambda へジョブを依頼します。 Lambda はレース動画から AI が選定した瞬間のフレームを切り出し、S3 へ保存したうえで、 ブログ本文の「10秒サマリー」直下へ <img class="nb-thumbnail"> として埋め込みます。

レースに紐づかないブログではサムネイルを生成できません(レース動画が無いため)。

URI

POST /v1/blogs/{blog_id}/thumbnail_generate

レスポンス

202 Accepted(ボディなし)

例外処理

説明 ステータスコード ステータス名
ブログ記事が存在しない 404 Not Found
ブログ記事が生成中(status=generating) 400 Bad Request
ブログにレースが紐づいていない 400 Bad Request
内部サーバーエラー 500 Internal Server Error

POST /v1/blogs/{blog_id}/related_generate

概要

関連情報(類似ブログへのリンク)を生成し、本文 HTML へ挿入します。

ブログの埋め込みベクトルと、ベクトルを持つ他ブログをコサイン類似度で比較し、 距離が近い順に 2件 を記事下部の「関連情報」セクションとして挿入します。

同じ記事とみなされる候補は除外します。

除外条件 理由
同じ old_blog からリライトされたブログ ベクトルが同一になるため
タイトルが完全一致するブログ 同一記事の重複登録
類似度が 0.99 以上のブログ 内容が実質同一

生成中(status=generating)のブログは候補に含めません。 ビルド時に挿入されたレースカードは、再生成後も関連情報の先頭に残します。

本エンドポイントは非同期ではなく、その場で本文を更新して返します。

URI

POST /v1/blogs/{blog_id}/related_generate

レスポンス

200 OK

更新後のブログスキーマを返します。

例外処理

説明 ステータスコード ステータス名
ブログ記事が存在しない 404 Not Found
ブログ記事が生成中(status=generating) 400 Bad Request
ブログにベクトルが設定されていない 400 Bad Request
関連ブログの候補が存在しない 400 Bad Request
内部サーバーエラー 500 Internal Server Error

処理フロー

flowchart TD
    Start([POST /v1/blogs/blog_id/related_generate]) --> Fetch[ブログを取得]
    Fetch -->|存在しない| E404[404 Not Found]
    Fetch -->|generating| E400[400 Bad Request]
    Fetch -->|ベクトルなし| E400
    Fetch -->|OK| Candidates[ベクトルを持つ他ブログを取得]
    Candidates --> Select[同一記事を除外しコサイン類似度で上位2件を選出]
    Select -->|候補なし| E400
    Select -->|OK| Insert[関連情報セクションを body_html へ挿入]
    Insert --> Success[200 OK]