← 一覧に戻る

管理ダッシュボード + 意見コマンド + 会話履歴 + マーケティングトラッカー

2026年3月15日 20:21 更新
MD から自動変換されたページです。内容について質問があれば右下の ? ボタンからどうぞ。

Context

Phase 2b(ユーザーを集める)に入り、プロダクトの全状態を1画面で把握できる司令塔が必要。 前回プラン(plans/majestic-sparking-pelican.md)に以下3点を追加統合する:

  1. 会話履歴ログ — ユーザーとBotの全やり取りをD1に蓄積(90日保持)
  2. フィードバック返信 — ダッシュボードから特定ユーザーにLINE Pushで返信
  3. プライバシーポリシー更新 — メッセージ内容の用途拡大 + 会話履歴の保存期間追加

DB スキーマ変更

前回プランから引き継ぐテーブル(変更なし)

新規: conversation_logs テーブル

CREATE TABLE conversation_logs (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  line_user_id TEXT NOT NULL,
  role TEXT NOT NULL,           -- 'user' or 'bot'
  content TEXT NOT NULL,
  created_at INTEGER DEFAULT (unixepoch())
);
CREATE INDEX idx_conversation_logs_user ON conversation_logs(line_user_id, created_at);

変更: feedback テーブルに返信追跡カラム追加

ALTER TABLE feedback ADD COLUMN replied_at INTEGER;
ALTER TABLE feedback ADD COLUMN reply_content TEXT;

会話履歴ログの実装

ヘルパー関数

async function logConversation(db: D1Database, userId: string, role: "user" | "bot", content: string) {
  await db.prepare(
    "INSERT INTO conversation_logs (line_user_id, role, content) VALUES (?, ?, ?)"
  ).bind(userId, role, content).run();
}

replyAndLog ラッパー

既存の replyToLine を直接変更せず、webhook 内で使うラッパーを作成:

async function replyAndLog(
  token: string, accessToken: string, text: string,
  db: D1Database, userId: string
) {
  await replyToLine(token, accessToken, text);
  await logConversation(db, userId, "bot", text);
}

Flex メッセージ(replyMessages)の場合は、altText をログに記録:

async function replyMessagesAndLog(
  token: string, accessToken: string, messages: unknown[],
  db: D1Database, userId: string, logText: string
) {
  await replyMessages(token, accessToken, messages);
  await logConversation(db, userId, "bot", logText);
}

ログ挿入ポイント

  1. ユーザーメッセージ: テキストメッセージ受信時(const text = event.message.text の直後、1箇所)
  2. Bot返信: 全 replyToLinereplyAndLog に置換、全 replyMessagesreplyMessagesAndLog に置換
  3. Postback応答(キャンセル・退会等): replyToLine/replyMessages → ラッパーに置換

90日クリーンアップ

handleScheduled(cron)の末尾に追加:

const ninetyDaysAgo = nowUnix - 90 * 86400;
await env.DB.prepare(
  "DELETE FROM conversation_logs WHERE created_at < ?"
).bind(ninetyDaysAgo).run();

API エンドポイント

前回プランから引き継ぐ(変更なし)

メソッド パス 用途
GET /admin/stats バジェット+ファネル(既存)
GET /admin/outreach チャネル別マーケ進捗
POST /admin/outreach マーケ進捗更新
GET /admin/line-info LINE Bot情報(1時間TTLキャッシュ)
GET /admin/milestones チェックリスト
POST /admin/milestones マイルストーン更新

変更: GET /admin/feedback(会話履歴付き)

フィードバック一覧に、各フィードバックの投稿者の直近会話履歴(投稿前7日間、最大20件)を付与。 line_user_id はレスポンスに含めない(プライバシー保護)。返信は id で行う。

{
  "items": [
    {
      "id": 1,
      "content": "朝5時にも使いたい",
      "created_at": 1710000000,
      "replied_at": null,
      "reply_content": null,
      "conversation": [
        { "role": "user", "content": "明日5時に起こして", "created_at": 1709999000 },
        { "role": "bot", "content": "2026年3月16日 5時00分 に『起こして』で電話するね", "created_at": 1709999001 },
        { "role": "user", "content": "意見", "created_at": 1709999900 },
        { "role": "bot", "content": "感想や気づいたこと、こうしてほしいってことをそのまま送ってね!", "created_at": 1709999901 },
        { "role": "user", "content": "朝5時にも使いたい", "created_at": 1710000000 }
      ]
    }
  ]
}

新規: POST /admin/feedback/:id/reply(フィードバック返信)

LINE Push API でフィードバック投稿者に返信。二重送信防止付き。

POST /admin/feedback/:id/reply
Body: { "message": "ありがとう!改善したよ!" }
Response: { "ok": true }

実装:

  1. feedback テーブルから id で検索 → line_user_id 取得
  2. replied_at が既にあれば 409 Conflict
  3. LINE Push API POST https://api.line.me/v2/bot/message/push で送信
  4. feedback テーブルの replied_at + reply_content を更新

Push API 呼び出し:

await fetch("https://api.line.me/v2/bot/message/push", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    Authorization: `Bearer ${accessToken}`,
  },
  body: JSON.stringify({
    to: lineUserId,
    messages: [{ type: "text", text: message }],
  }),
});

注意: Push は月200通制限(無料プラン)。β規模では問題なし。

LINE Bot「意見」コマンド

前回プランと同じ。既に src/index.ts に実装済み(L826-846)。テスト追加のみ。

プライバシーポリシー更新(docs/privacy-policy.html

第2条: メッセージ内容の用途を拡大

変更前:

LINEトークルームに入力されたメッセージ内容(リマインダーの日時・内容の検出に使用)

変更後:

LINEトークルームに入力されたメッセージ内容(サービス提供・改善に使用)

第7条: 会話履歴の保存期間を追加

保存期間テーブルに1行追加:

データの種類 保存期間
会話履歴データ(送受信メッセージ) 送受信日から90日間

ダッシュボード HTML(public/admin/index.html

前回プランのレイアウトを踏襲。フィードバックセクションを強化:

├─────────────────────────────────┤
│ [5] フィードバック               │  ← /admin/feedback
│                                  │
│ ┌──────────────────────────────┐│
│ │ 「朝5時にも使いたい」  3/15  ││
│ │ ▼ 会話履歴                   ││
│ │   👤 明日5時に起こして       ││
│ │   🤖 5時00分に電話するね     ││
│ │   👤 意見                    ││
│ │   🤖 感想を送ってね!        ││
│ │   👤 朝5時にも使いたい       ││
│ │ [返信する] テキスト入力欄     ││
│ └──────────────────────────────┘│
│                                  │
│ ┌──────────────────────────────┐│
│ │ 「電話が聞こえにくい」 3/14  ││
│ │ ✅ 返信済み: 「音量上げたよ」││
│ └──────────────────────────────┘│
├─────────────────────────────────┤

TDD イテレーション

# 内容 テスト数
1 conversation_logs + logConversation ヘルパー + 基本テスト +2
2 webhook の全 replyToLinereplyAndLog 置換 + ユーザーメッセージログ +2
3 「意見」コマンドのテスト追加(既存実装の検証) +3
4 GET /admin/feedback(会話履歴付き) +3
5 POST /admin/feedback/:id/reply(Push送信 + 二重送信防止) +3
6 GET/POST /admin/outreach +3
7 GET /admin/line-info(LINE API proxy + 1時間TTLキャッシュ) +3
8 GET/POST /admin/milestones +3
9 ダッシュボード HTML(テストなし、手動確認) 0
10 プライバシーポリシー更新 0

合計: +22テスト

変更ファイル一覧

ファイル 種別 内容
schema.sql 変更 conversation_logs テーブル追加 + feedback に2カラム追加
src/index.ts 変更 会話ログ機能 + feedback返信エンドポイント + 5本のAPIエンドポイント
src/index.test.ts 変更 +22テスト
public/admin/index.html 新規 ダッシュボード本体
docs/privacy-policy.html 変更 第2条・第7条の微修正
public/privacy-policy.html 変更 同上(Workers Static Assets 配信用コピー)

検証手順

  1. tsc --noEmit && pnpm test(全テスト通過)
  2. wrangler dev → LINE Bot で数メッセージ送信 → D1 の conversation_logs にログ確認
  3. LINE Bot で「意見」→ テキスト送信 → /admin/feedback に会話履歴付きで反映確認
  4. curl -X POST .../admin/feedback/1/reply -d '{"message":"ありがとう!"}' → LINE に Push 着信確認
  5. localhost:8787/admin でダッシュボード表示確認
  6. プライバシーポリシーページの表示確認
📝 質問モード — テキストを選択してね
✓ 質問を送信しました