Phase 2b(ユーザーを集める)に入り、プロダクトの全状態を1画面で把握できる司令塔が必要。
前回プラン(plans/majestic-sparking-pelican.md)に以下3点を追加統合する:
feedback テーブル(既に schema.sql に存在)users.awaiting_feedback(既に schema.sql に存在)outreach テーブル(既に schema.sql に存在)line_api_cache テーブル(既に schema.sql に存在)milestones テーブル(既に schema.sql に存在)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);
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();
}
既存の 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);
}
const text = event.message.text の直後、1箇所)replyToLine → replyAndLog に置換、全 replyMessages → replyMessagesAndLog に置換replyToLine/replyMessages → ラッパーに置換handleScheduled(cron)の末尾に追加:
const ninetyDaysAgo = nowUnix - 90 * 86400;
await env.DB.prepare(
"DELETE FROM conversation_logs WHERE created_at < ?"
).bind(ninetyDaysAgo).run();
| メソッド | パス | 用途 |
|---|---|---|
| GET | /admin/stats |
バジェット+ファネル(既存) |
| GET | /admin/outreach |
チャネル別マーケ進捗 |
| POST | /admin/outreach |
マーケ進捗更新 |
| GET | /admin/line-info |
LINE Bot情報(1時間TTLキャッシュ) |
| GET | /admin/milestones |
チェックリスト |
| POST | /admin/milestones |
マイルストーン更新 |
フィードバック一覧に、各フィードバックの投稿者の直近会話履歴(投稿前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 }
]
}
]
}
LINE Push API でフィードバック投稿者に返信。二重送信防止付き。
POST /admin/feedback/:id/reply
Body: { "message": "ありがとう!改善したよ!" }
Response: { "ok": true }
実装:
feedback テーブルから id で検索 → line_user_id 取得replied_at が既にあれば 409 ConflictPOST https://api.line.me/v2/bot/message/push で送信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通制限(無料プラン)。β規模では問題なし。
前回プランと同じ。既に src/index.ts に実装済み(L826-846)。テスト追加のみ。
docs/privacy-policy.html)変更前:
LINEトークルームに入力されたメッセージ内容(リマインダーの日時・内容の検出に使用)
変更後:
LINEトークルームに入力されたメッセージ内容(サービス提供・改善に使用)
保存期間テーブルに1行追加:
| データの種類 | 保存期間 |
|---|---|
| 会話履歴データ(送受信メッセージ) | 送受信日から90日間 |
public/admin/index.html)前回プランのレイアウトを踏襲。フィードバックセクションを強化:
├─────────────────────────────────┤
│ [5] フィードバック │ ← /admin/feedback
│ │
│ ┌──────────────────────────────┐│
│ │ 「朝5時にも使いたい」 3/15 ││
│ │ ▼ 会話履歴 ││
│ │ 👤 明日5時に起こして ││
│ │ 🤖 5時00分に電話するね ││
│ │ 👤 意見 ││
│ │ 🤖 感想を送ってね! ││
│ │ 👤 朝5時にも使いたい ││
│ │ [返信する] テキスト入力欄 ││
│ └──────────────────────────────┘│
│ │
│ ┌──────────────────────────────┐│
│ │ 「電話が聞こえにくい」 3/14 ││
│ │ ✅ 返信済み: 「音量上げたよ」││
│ └──────────────────────────────┘│
├─────────────────────────────────┤
| # | 内容 | テスト数 |
|---|---|---|
| 1 | conversation_logs + logConversation ヘルパー + 基本テスト |
+2 |
| 2 | webhook の全 replyToLine → replyAndLog 置換 + ユーザーメッセージログ |
+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 配信用コピー) |
tsc --noEmit && pnpm test(全テスト通過)wrangler dev → LINE Bot で数メッセージ送信 → D1 の conversation_logs にログ確認/admin/feedback に会話履歴付きで反映確認curl -X POST .../admin/feedback/1/reply -d '{"message":"ありがとう!"}' → LINE に Push 着信確認localhost:8787/admin でダッシュボード表示確認